Last active
December 17, 2015 21:29
-
-
Save sshongru/5674574 to your computer and use it in GitHub Desktop.
Examines two PNG files and outputs a diff PNG if they are different.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.image.BufferedImage; | |
import java.awt.image.DataBufferByte; | |
import java.io.File; | |
import java.io.IOException; | |
import javax.imageio.ImageIO; | |
public class ImageDiff { | |
private static BufferedImage expectedImage; | |
private static BufferedImage actualImage; | |
private static int numDifferentPixels = 0; | |
public static void main(String[] args) throws IOException { | |
if (args.length != 3) { | |
System.out.println("Usage: java ImageDiff /path/to/expected.png /path/to/actual.png /path/to/diff.png"); | |
return; | |
} | |
File expectedFile = new File(args[0]); | |
File actualFile = new File(args[1]); | |
File diffFile = new File(args[2]); | |
if (!expectedFile.exists()) { | |
System.out.println("Error: Expected file not found at " + expectedFile.getAbsolutePath()); | |
return; | |
} | |
if (!actualFile.exists()) { | |
System.out.println("Error: Actual file not found at " + actualFile.getAbsolutePath()); | |
return; | |
} | |
expectedImage = ImageIO.read(expectedFile); | |
actualImage = ImageIO.read(actualFile); | |
int width = expectedImage.getWidth(); | |
int height = expectedImage.getHeight(); | |
// ensure the images have the same dimensions | |
if (width != actualImage.getWidth()){ | |
System.out.println("Error: Expected image width (" + width + ") does not match actual image width (" + actualImage.getWidth() + ")."); | |
return; | |
} | |
if (height != actualImage.getHeight()){ | |
System.out.println("Error: Expected image height (" + height + ") does not match actual image height (" + actualImage.getHeight() + ")."); | |
return; | |
} | |
int[][] expectedPixels = getPixels(expectedImage); | |
int[][] actualPixels = getPixels(actualImage); | |
// create diff | |
BufferedImage diffImage = getDiff(expectedPixels, actualPixels); | |
// show the results | |
if (numDifferentPixels != 0){ | |
// write the diff to disk | |
ImageIO.write(diffImage, "png", diffFile); // TODO: Support more than just PNG | |
System.out.println(numDifferentPixels + " pixels different, diff created at: " + diffFile); | |
} else { | |
System.out.println("0 pixels different"); | |
} | |
} | |
private static int[][] getPixels(BufferedImage image) { | |
int type = image.getType(); | |
// indexed images will use the standard slow implementation | |
if (type == BufferedImage.TYPE_BYTE_BINARY || | |
type == BufferedImage.TYPE_BYTE_INDEXED || | |
type == BufferedImage.TYPE_BYTE_GRAY){ | |
return getPixelsSlow(image); | |
} | |
// non-indexed images we can easily optimize by getting a byte[] of pixels directly | |
byte[] bytes = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); | |
int width = image.getWidth(); | |
int height = image.getHeight(); | |
int[][] result = new int[height][width]; | |
int y = 0; | |
int x = 0; | |
int pixelLength; // number of bytes that represent a single pixel | |
boolean hasAlphaChannel = image.getAlphaRaster() != null; | |
// TODO: Clean this up into one chunk | |
if (hasAlphaChannel) { | |
pixelLength = 4; | |
for (int bytesIndex = 0; bytesIndex < bytes.length; bytesIndex += pixelLength) { | |
int argb = 0; | |
argb += (((int) bytes[bytesIndex] & 0xFF) << 24); // alpha | |
argb += (((int) bytes[bytesIndex + 3] & 0xFF) << 16); // red | |
argb += (((int) bytes[bytesIndex + 2] & 0xFF) << 8); // green | |
argb += ((int) bytes[bytesIndex + 1] & 0xFF); // blue | |
result[y][x] = argb; | |
x++; | |
if (x == width) { | |
x = 0; | |
y++; | |
} | |
} | |
} else { | |
pixelLength = 3; | |
for (int bytesIndex = 0; bytesIndex < bytes.length; bytesIndex += pixelLength) { | |
int argb = 0; | |
argb += -16777216; // alpha | |
argb += (((int) bytes[bytesIndex + 2] & 0xFF) << 16); // red | |
argb += (((int) bytes[bytesIndex + 1] & 0xFF) << 8); // green | |
argb += ((int) bytes[bytesIndex] & 0xFF); // blue | |
result[y][x] = argb; | |
x++; | |
if (x == width) { | |
x = 0; | |
y++; | |
} | |
} | |
} | |
return result; | |
} | |
/** | |
* A slow, but reliable way of getting RGB values. This works with | |
* all kinds of PNG types including indexed color. | |
*/ | |
private static int[][] getPixelsSlow(BufferedImage image) { | |
int width = image.getWidth(); | |
int height = image.getHeight(); | |
int[][] result = new int[height][width]; | |
for (int row = 0; row < height; row++) | |
for (int col = 0; col < width; col++) | |
result[row][col] = image.getRGB(col, row); | |
return result; | |
} | |
private static BufferedImage getDiff(int[][] expectedPixels, int[][] actualPixels) { | |
int height = expectedPixels.length; | |
int width = expectedPixels[0].length; | |
BufferedImage diffImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); | |
for (int y = 0; y < height; y++){ | |
for (int x = 0; x < width; x++){ | |
// expected pixel | |
int e = expectedPixels[y][x]; | |
int eA = (e >> 24) & 0xFF; // alpha | |
int eR = (e >> 16) & 0xFF; // red | |
int eG = (e >> 8) & 0xFF; // green | |
int eB = (e >> 0) & 0xFF; // blue | |
// actual pixel | |
int a = actualPixels[y][x]; | |
int aA = (a >> 24) & 0xFF; // alpha | |
int aR = (a >> 16) & 0xFF; // red | |
int aG = (a >> 8) & 0xFF; // green | |
int aB = (a >> 0) & 0xFF; // blue | |
// difference | |
int dA = Math.abs(eA - aA); | |
int dR = Math.abs(eR - aR); | |
int dG = Math.abs(eG - aG); | |
int dB = Math.abs(eB - aB); | |
// keep track of failed pixels | |
boolean colorFailure = (dR != 0 || dG != 0 || dB != 0) ? true : false; | |
if (dA != 0 || colorFailure) | |
numDifferentPixels++; | |
// construct the difference pixel | |
int d = 0; | |
d += dR << 16; | |
d += dG << 8; | |
d += dB; | |
// add alpha: | |
// - perfectly matching pixels are transparent | |
// - color channel differences show up with full opacity | |
// - alpha channel differences show up with partial opacity | |
if (dA == 0 && colorFailure){ | |
d += 255 << 24; | |
} else { | |
d += dA << 24; | |
} | |
diffImage.setRGB(x, y, d); | |
} | |
} | |
return diffImage; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment