In JAVA I am trying to programmatically tell if 2 images are equal when displayed on the screen (AKA same image even though they have different color spaces. Is there a piece of code that will return a boolean when presented 2 images?
One of the examples I have is a RGB PNG that I converted to a greyscale PNG. Both images look the same and I would like to prove this programmatically. Another example is two images where they display the exact same color pixels to the screen but the color used for 100% transparent pixels has changed.
Read Both of them using the Image. Get the height and width of both of them to make sure they are equal. Get the pixel values and, get the RGB values of both of the images. Get the sum of the differences between the RGB values of these two images.
The method Files::mismatch, added in Java 12, compares the contents of two files. It returns -1L if the files are identical, and otherwise, it returns the position in bytes of the first mismatch.
The BufferedImage class is a cornerstone of the Java 2D immediate-mode imaging API. It manages the image in memory and provides methods for storing, interpreting, and obtaining pixel data.
I looked at all of the solutions and determined that they could tell you how different the images were or worked for some types of images, but not all of them. Here is the solution I came up with...
package image.utils;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.ImageIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility methods used to interact with images.
*/
public class ImageUtils {
private final static Logger logger = LoggerFactory.getLogger(ImageUtils.class);
private static final boolean equals(final int[] data1, final int[] data2) {
final int length = data1.length;
if (length != data2.length) {
logger.debug("File lengths are different.");
return false;
}
for(int i = 0; i < length; i++) {
if(data1[i] != data2[i]) {
//If the alpha is 0 for both that means that the pixels are 100%
//transparent and the color does not matter. Return false if
//only 1 is 100% transparent.
if((((data1[i] >> 24) & 0xff) == 0) && (((data2[i] >> 24) & 0xff) == 0)) {
logger.debug("Both pixles at spot {} are different but 100% transparent.", Integer.valueOf(i));
} else {
logger.debug("The pixel {} is different.", Integer.valueOf(i));
return false;
}
}
}
logger.debug("Both groups of pixels are the same.");
return true;
}
private static final int[] getPixels(final BufferedImage img, final File file) {
final int width = img.getWidth();
final int height = img.getHeight();
int[] pixelData = new int[width * height];
final Image pixelImg;
if (img.getColorModel().getColorSpace() == ColorSpace.getInstance(ColorSpace.CS_sRGB)) {
pixelImg = img;
} else {
pixelImg = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(img, null);
}
final PixelGrabber pg = new PixelGrabber(pixelImg, 0, 0, width, height, pixelData, 0, width);
try {
if(!pg.grabPixels()) {
throw new RuntimeException();
}
} catch (final InterruptedException ie) {
throw new RuntimeException(file.getPath(), ie);
}
return pixelData;
}
/**
* Gets the {@link BufferedImage} from the passed in {@link File}.
*
* @param file The <code>File</code> to use.
* @return The resulting <code>BufferedImage</code>
*/
@SuppressWarnings("unused")
final static BufferedImage getBufferedImage(final File file) {
Image image;
try (final FileInputStream inputStream = new FileInputStream(file)) {
// ImageIO.read(file) is broken for some images so I went this
// route
image = Toolkit.getDefaultToolkit().createImage(file.getCanonicalPath());
//forces the image to be rendered
new ImageIcon(image);
} catch(final Exception e2) {
throw new RuntimeException(file.getPath(), e2);
}
final BufferedImage converted = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
final Graphics2D g2d = converted.createGraphics();
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return converted;
}
/**
* Compares file1 to file2 to see if they are the same based on a visual
* pixel by pixel comparison. This has issues with marking images different
* when they are not. Works perfectly for all images.
*
* @param file1 First file to compare
* @param file2 Second image to compare
* @return <code>true</code> if they are equal, otherwise
* <code>false</code>.
*/
private final static boolean visuallyCompareJava(final File file1, final File file2) {
return equals(getPixels(getBufferedImage(file1), file1), getPixels(getBufferedImage(file2), file2));
}
/**
* Compares file1 to file2 to see if they are the same based on a visual
* pixel by pixel comparison. This has issues with marking images different
* when they are not. Works perfectly for all images.
*
* @param file1 Image 1 to compare
* @param file2 Image 2 to compare
* @return <code>true</code> if both images are visually the same.
*/
public final static boolean visuallyCompare(final File file1, final File file2) {
logger.debug("Start comparing \"{}\" and \"{}\".", file1.getPath(), file2.getPath());
if(file1 == file2) {
return true;
}
boolean answer = visuallyCompareJava(file1, file2);
if(!answer) {
logger.info("The files \"{}\" and \"{}\" are not pixel by pixel the same image. Manual comparison required.", file1.getPath(), file2.getPath());
}
logger.debug("Finish comparing \"{}\" and \"{}\".", file1.getPath(), file2.getPath());
return answer;
}
/**
* @param file The image to check
* @return <code>true</code> if the image contains one or more pixels with
* some percentage of transparency (Alpha)
*/
public final static boolean containsAlphaTransparency(final File file) {
logger.debug("Start Alpha pixel check for {}.", file.getPath());
boolean answer = false;
for(final int pixel : getPixels(getBufferedImage(file), file)) {
//If the alpha is 0 for both that means that the pixels are 100%
//transparent and the color does not matter. Return false if
//only 1 is 100% transparent.
if(((pixel >> 24) & 0xff) != 255) {
logger.debug("The image contains Aplha Transparency.");
return true;
}
}
logger.debug("The image does not contain Aplha Transparency.");
logger.debug("End Alpha pixel check for {}.", file.getPath());
return answer;
}
}
For grayscale images I've used Mean Square Error as a measure of how different two images are before. Just plug the corresponding pixels from each image into the formula.
Not only can this tell you if they are exactly the same, but also it can tell you how different two images are, albeit in a rather crude manner.
https://en.wikipedia.org/wiki/Mean_squared_error
EDIT:
Note: This is C# code not Java (apologies but that's what I wrote it in originally), however it should be easily transferable.
//Calculates the MSE between two images
private double MSE(Bitmap original, Bitmap enhanced)
{
Size imgSize = original.Size;
double total = 0;
for (int y = 0; y < imgSize.Height; y++)
{
for (int x = 0; x < imgSize.Width; x++)
{
total += System.Math.Pow(original.GetPixel(x, y).R - enhanced.GetPixel(x, y).R, 2);
}
}
return (total / (imgSize.Width * imgSize.Height));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With