/*
* Copyright (C) 2013 University of Dundee & Open Microscopy Environment.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openmicroscopy.shoola.keywords;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.util.NoSuchElementException;
import javax.swing.JPanel;
import abbot.finder.BasicFinder;
import abbot.finder.ComponentNotFoundException;
import abbot.finder.Matcher;
import abbot.finder.MultipleComponentsFoundException;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
/**
* Robot Framework SwingLibrary keyword library offering methods for checking thumbnails.
* @author m.t.b.carroll@dundee.ac.uk
* @since 4.4.9
*/
public class ThumbnailCheckLibrary
{
/** Allow Robot Framework to instantiate this library only once. */
public static final String ROBOT_LIBRARY_SCOPE = "GLOBAL";
/**
* An iterator over the integer pixel values of a rendered image,
* first increasing <em>x</em>, then <em>y</em> when <em>x</em> wraps back to 0.
* This is written so as to be scalable over arbitrary image sizes
* and to not cause heap allocations during the iteration.
* @author m.t.b.carroll@dundee.ac.uk
* @since 4.4.9
*/
private static class IteratorIntPixel {
final Raster raster;
final int width;
final int height;
final int[] pixel = new int[1];
int x = 0;
int y = 0;
/**
* Create a new pixel iterator for the given image.
* The image is assumed to be of a type that packs data for each pixel into an <code>int</code>.
* @param image the image over whose pixels to iterate
*/
IteratorIntPixel(RenderedImage image) {
this.raster = image.getData();
this.width = image.getWidth();
this.height = image.getHeight();
}
/**
* @return if any pixels remain to be read with {@link #next()}
*/
boolean hasNext() {
return y < height;
}
/**
* @return the next pixel
* @throws NoSuchElementException if no more pixels remain
*/
int next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
raster.getDataElements(x, y, pixel);
if (++x == width) {
x = 0;
++y;
}
return pixel[0];
}
}
/**
* Find the thumbnail <code>Component</code> in the AWT hierarchy.
* @param panelType if the thumbnail should be the whole <code>"image node"</code> or just its <code>"thumbnail"</code> canvas
* @param imageFilename the name of the image whose thumbnail is to be rasterized
* @return the AWT <code>Component</code> for the thumbnail
* @throws MultipleComponentsFoundException if multiple thumbnails are for the given image name
* @throws ComponentNotFoundException if no thumbnails are for the given image name
*/
private static Component componentFinder(final String panelType, final String imageFilename)
throws ComponentNotFoundException, MultipleComponentsFoundException {
return new BasicFinder().find(new Matcher() {
private final String soughtName = panelType + " for " + imageFilename;
public boolean matches(Component component) {
return component instanceof JPanel && this.soughtName.equals(component.getName());
}});
}
/**
* Convert the thumbnail for the image of the given filename into rasterized pixel data.
* Each pixel is represented by an <code>int</code>.
* @param panelType if the thumbnail should be the whole <code>"image node"</code> or just its <code>"thumbnail"</code> canvas
* @param imageFilename the name of the image whose thumbnail is to be rasterized
* @return the image on the thumbnail
* @throws MultipleComponentsFoundException if multiple thumbnails are for the given image name
* @throws ComponentNotFoundException if no thumbnails are for the given image name
*/
private static RenderedImage captureImage(final String panelType, final String imageFilename)
throws ComponentNotFoundException, MultipleComponentsFoundException {
final JPanel thumbnail = (JPanel) componentFinder(panelType, imageFilename);
final int width = thumbnail.getWidth();
final int height = thumbnail.getHeight();
final BufferedImage image = new BufferedImage(width, height, StaticFieldLibrary.IMAGE_TYPE);
final Graphics2D graphics = image.createGraphics();
if (graphics == null) {
throw new RuntimeException("thumbnail is not displayable");
}
thumbnail.paint(graphics);
graphics.dispose();
return image;
}
/**
* <table>
* <td>Get Thumbnail Border Color</td>
* <td>name of image whose thumbnail is queried</td>
* </table>
* @param imageFilename the name of the image
* @return the color of the thumbnail's corner pixel
* @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
* @throws ComponentNotFoundException if no thumbnails exist for the given name
*/
public String getThumbnailBorderColor(String imageFilename)
throws ComponentNotFoundException, MultipleComponentsFoundException {
final RenderedImage image = captureImage("image node", imageFilename);
final IteratorIntPixel pixels = new IteratorIntPixel(image);
if (!pixels.hasNext()) {
throw new RuntimeException("image node has no pixels");
}
return Integer.toHexString(pixels.next());
}
/**
* <table>
* <td>Is Thumbnail Monochromatic</td>
* <td>name of image whose thumbnail is queried</td>
* </table>
* @param imageFilename the name of the image
* @return if the image's thumbnail canvas is solidly one color
* @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
* @throws ComponentNotFoundException if no thumbnails exist for the given name
*/
public boolean isThumbnailMonochromatic(String imageFilename)
throws ComponentNotFoundException, MultipleComponentsFoundException {
final RenderedImage image = captureImage("thumbnail", imageFilename);
final IteratorIntPixel pixels = new IteratorIntPixel(image);
if (!pixels.hasNext()) {
throw new RuntimeException("thumbnail image has no pixels");
}
final int oneColor = pixels.next();
while (pixels.hasNext()) {
if (pixels.next() != oneColor) {
return false;
}
}
return true;
}
/**
* <table>
* <td>Get Thumbnail Hash</td>
* <td>name of image whose thumbnail is queried</td>
* </table>
* @param imageFilename the name of the image
* @return the hash of the thumbnail canvas image
* @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
* @throws ComponentNotFoundException if no thumbnails exist for the given name
*/
public String getThumbnailHash(String imageFilename)
throws ComponentNotFoundException, MultipleComponentsFoundException {
final RenderedImage image = captureImage("thumbnail", imageFilename);
final IteratorIntPixel pixels = new IteratorIntPixel(image);
final Hasher hasher = Hashing.goodFastHash(128).newHasher();
while (pixels.hasNext()) {
hasher.putInt(pixels.next());
}
return hasher.hash().toString();
}
/**
* <table>
* <td>Get Name Of Thumbnail For Image</td>
* <td>name of image whose thumbnail is queried</td>
* </table>
* @param imageFilename the name of the image
* @return the return value of the corresponding <code>ThumbnailCanvas.getName()</code>
* @throws MultipleComponentsFoundException if multiple thumbnails exist for the given name
* @throws ComponentNotFoundException if no thumbnails exist for the given name
*/
public String getNameOfThumbnailForImage(final String imageFilename)
throws ComponentNotFoundException, MultipleComponentsFoundException {
return componentFinder("thumbnail", imageFilename).getName();
}
}