package cz.nkp.differ.compare.io.pure;
import cz.nkp.differ.compare.io.ImageProcessor;
import cz.nkp.differ.compare.io.ImageProcessorResult;
import cz.nkp.differ.compare.metadata.ImageMetadata;
import cz.nkp.differ.compare.metadata.MetadataExtractor;
import cz.nkp.differ.compare.metadata.MetadataExtractors;
import cz.nkp.differ.compare.metadata.MetadataSource;
import cz.nkp.differ.exceptions.ImageDifferException;
import cz.nkp.differ.images.ImageLoader;
import cz.nkp.differ.images.ImageManipulator;
import cz.nkp.differ.listener.ProgressListener;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.util.concurrent.Executors;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.apache.commons.codec.digest.DigestUtils;
/**
* Implementation of ImageProcessor in Java
*
* @author xrosecky
*/
public class PureImageProcessor extends ImageProcessor {
private ImageLoader imageLoader;
private MetadataExtractors extractors;
private MetadataSource core = new MetadataSource(0, "", "", "core");
public PureImageProcessor(ImageLoader imageLoader, MetadataExtractors extractors) {
this.imageLoader = imageLoader;
this.extractors = extractors;
}
private class ProcessImageTask implements Callable<PureImageProcessorResult> {
private File image;
public ProcessImageTask(File image) {
this.image = image;
}
@Override
public PureImageProcessorResult call() throws Exception {
return processImage(image, null);
}
}
private class ImageMetadataTask implements Callable<List<ImageMetadata>> {
private MetadataExtractor extractor;
private File image;
public ImageMetadataTask(MetadataExtractor extractor, File image) {
this.extractor = extractor;
this.image = image;
}
@Override
public List<ImageMetadata> call() throws Exception {
return extractor.getMetadata(image);
}
}
@Override
public PureImageProcessorResult processImage(File image, ProgressListener callback) throws ImageDifferException {
BufferedImage fullImage = imageLoader.load(image);
if (fullImage == null) {
throw new ImageDifferException(ImageDifferException.ErrorCode.IMAGE_READ_ERROR, "Unsupported file type");
}
java.awt.Image preview = ImageManipulator.getBitmapScaledImage(fullImage, this.getConfig().getImageWidth(), true);
PureImageProcessorResult result = new PureImageProcessorResult(fullImage, preview);
result.setType(ImageProcessorResult.Type.IMAGE);
processImage(fullImage, result);
result.getMetadata().add(new ImageMetadata("height", new Integer(fullImage.getHeight()), core));
result.getMetadata().add(new ImageMetadata("width", new Integer(fullImage.getWidth()), core));
List<Callable<List<ImageMetadata>>> tasks = new ArrayList<Callable<List<ImageMetadata>>>();
for (MetadataExtractor extractor : extractors.getExtractors()) {
tasks.add(new ImageMetadataTask(extractor, image));
}
List<Future<List<ImageMetadata>>> futures = execute(tasks);
for (Future<List<ImageMetadata>> future : futures) {
if (future.isDone()) {
try {
result.getMetadata().addAll(future.get());
} catch (InterruptedException ie) {
throw new ImageDifferException(ImageDifferException.ErrorCode.IMAGE_READ_ERROR, "Timeout exceeded", ie);
} catch (ExecutionException ee) {
handleException(ee);
}
}
}
Map<String, Object> results = new HashMap<String, Object>();
Set<String> conflicts = new HashSet<String>();
for (ImageMetadata data : result.getMetadata()) {
String key = data.getKey();
Object val1 = data.getValue();
Object val2 = results.get(key);
if (val2 != null && !val2.toString().equals(val1.toString())) {
conflicts.add(key);
}
if (val2 == null) {
results.put(key, val1);
}
}
for (ImageMetadata data : result.getMetadata()) {
String key = data.getKey();
data.setConflict(conflicts.contains(key));
}
result.setType(ImageProcessorResult.Type.IMAGE);
return result;
}
@Override
public ImageProcessorResult[] processImages(File a, File b, ProgressListener callback) throws ImageDifferException {
PureImageProcessorResult results[] = new PureImageProcessorResult[3];
try {
Callable<PureImageProcessorResult> task1 = new ProcessImageTask(a);
Callable<PureImageProcessorResult> task2 = new ProcessImageTask(b);
List<Future<PureImageProcessorResult>> futures = this.execute(Arrays.asList(task1, task2));
int index = 0;
for (Future<PureImageProcessorResult> future : futures) {
if (future.isDone()) {
results[index] = future.get();
} else {
results[index] = null;
}
index++;
}
} catch (InterruptedException ie) {
throw new ImageDifferException(ImageDifferException.ErrorCode.IMAGE_READ_ERROR, "Timeout exceeded", ie);
} catch (ExecutionException ee) {
this.handleException(ee);
}
results[0] = this.processImage(a, null);
results[1] = this.processImage(b, null);
try {
BufferedImage compareFull = getImagesDifference(results[0].getFullImage(), results[1].getFullImage());
java.awt.Image comparePreview = ImageManipulator.getBitmapScaledImage(compareFull, this.getConfig().getImageWidth(), true);
PureImageProcessorResult result = new PureImageProcessorResult(compareFull, comparePreview);
result.setType(ImageProcessorResult.Type.COMPARISON);
this.processImage(compareFull, result);
this.addMetrics(result);
result.setType(ImageProcessorResult.Type.COMPARISON);
results[2] = result;
} catch (Exception ex) {
ex.printStackTrace();
results[2] = null;
}
return results;
}
private <T> List<Future<T>> execute(List<Callable<T>> tasks) throws ImageDifferException {
ExecutorService executor = null;
try {
executor = Executors.newCachedThreadPool();
return executor.invokeAll(tasks);
} catch (InterruptedException ie) {
throw new ImageDifferException(ImageDifferException.ErrorCode.IMAGE_READ_ERROR, "Timeout exceeded", ie);
} finally {
if (executor != null) {
executor.shutdown();
}
}
}
private void handleException(ExecutionException ee) throws ImageDifferException {
if (ee.getCause() instanceof ImageDifferException) {
throw (ImageDifferException) ee.getCause();
} else if (ee.getCause() instanceof RuntimeException) {
throw (RuntimeException) ee.getCause();
} else {
throw new ImageDifferException(ImageDifferException.ErrorCode.IMAGE_READ_ERROR, "Error when reading image", ee);
}
}
private void addMetrics(PureImageProcessorResult result) {
long size = result.getHeight() * result.getWidth();
int[][] histogram = result.getHistogram();
String[] colours = new String[]{"red", "green", "blue"};
MetadataSource mseSource = new MetadataSource(0, "", "", "MSE");
MetadataSource psnrSource = new MetadataSource(0, "", "", "PSNR");
for (int i = 0; i < 3; i++) {
String colour = colours[i];
long sqSum = 0;
int[] values = histogram[i];
for (int j = 0; j < values.length; j++) {
sqSum += values[j] * j * j;
}
double mse = (1.0 / size) * sqSum;
double psnr = 10 * Math.log10((255 * 255) / mse);
result.getMetadata().add(new ImageMetadata(colour, mse, mseSource));
result.getMetadata().add(new ImageMetadata(colour, psnr, psnrSource));
}
}
private PureImageProcessorResult processImage(BufferedImage image, PureImageProcessorResult result) {
int width = image.getWidth();
int height = image.getHeight();
result.setWidth(width);
result.setHeight(height);
// histogram
int[] imagePixelCache = new int[width * height];
image.getRGB(0, 0, width, height, imagePixelCache, 0, width); //Get all pixels
int[][] bins = new int[3][256];
for (int thisPixel = 0; thisPixel < width * height; thisPixel++) {
int rgbCombined = imagePixelCache[thisPixel];
int red = new Color(rgbCombined).getRed();
bins[0][red]++;
int green = new Color(rgbCombined).getGreen();
bins[1][green]++;
int blue = new Color(rgbCombined).getBlue();
bins[2][blue]++;
}
result.setHistogram(bins);
// checksum
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
for (int i : imagePixelCache) {
byteStream.write(i);
}
String md5 = DigestUtils.md5Hex(byteStream.toByteArray());
try {
byteStream.close();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
result.setMD5Checksum(md5);
return result;
}
private static BufferedImage getImagesDifference(BufferedImage image1, BufferedImage image2) {
if (image1 == null) {
throw new NullPointerException("image1");
}
if (image2 == null) {
throw new NullPointerException("image2");
}
if (image1.getWidth(null) != image2.getWidth(null)
|| image1.getHeight(null) != image2.getHeight(null)) {
throw new IllegalArgumentException("Cannot XOR images that are differing dimensions. XOR'ing failed.");
}
if (image1.getTransparency() != image2.getTransparency()) {
throw new IllegalArgumentException("Cannot XOR images that are differing transparencies. XOR'ing failed.");
}
int type = BufferedImage.TYPE_INT_RGB;
if (image1.getType() != type) {
image1 = convert(image1, type);
}
if (image2.getType() != type) {
image2 = convert(image2, type);
}
int width = image1.getWidth(null);
int height = image1.getHeight(null);
int resolution = width * height;
int[] combo1Pixels = new int[resolution];
int[] combo2Pixels = new int[resolution];
int[] imagePixels = new int[resolution];
image1.getRGB(0, 0, width, height, combo1Pixels, 0, width); //Get all pixels
image2.getRGB(0, 0, width, height, combo2Pixels, 0, width); //Get all pixels
for (int pixel = 0; pixel < resolution; pixel++) {
Color pixel1 = new Color(combo1Pixels[pixel]);
Color pixel2 = new Color(combo2Pixels[pixel]);
Color diff = new Color(
Math.abs(pixel1.getRed() - pixel2.getRed()),
Math.abs(pixel1.getGreen() - pixel2.getGreen()),
Math.abs(pixel1.getBlue() - pixel2.getBlue()));
//imagePixels[pixel] = Math.abs(combo1Pixels[pixel] - combo2Pixels[pixel]);
imagePixels[pixel] = diff.getRGB();
}
int imageType = image1.getType();
if (imageType == BufferedImage.TYPE_CUSTOM) {
imageType = BufferedImage.TYPE_INT_RGB;
}
BufferedImage imageXOR = new BufferedImage(width, height, imageType);
imageXOR.setRGB(0, 0, width, height, imagePixels, 0, width); //Set all pixels
return imageXOR;
}
private static BufferedImage convert(BufferedImage image, int type) {
BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight(), type);
ColorConvertOp op = new ColorConvertOp(null);
op.filter(image, result);
return result;
}
}