/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.github.jipsg.common.image.diff.impl; import org.github.jipsg.common.image.diff.ImageDiffer; import java.awt.*; import java.awt.image.BufferedImage; /** * Compare two images to check if they are identical - based on Apache PDFBox test code. */ public class XorImageDiffer implements ImageDiffer { /** * The number of color steps to be considered equal when diffing two RGB images */ private int colorStepDifferenceThreshold; public XorImageDiffer() { this.colorStepDifferenceThreshold = 1; } @Override public BufferedImage diff(BufferedImage bim1, BufferedImage bim2) { return diffImages(bim1, bim2, colorStepDifferenceThreshold); } /** * Get the difference between two images, identical colors are set to white, * differences are xor-ed, the highest bit of each color is reset to avoid * colors that are too light * * @param bim1 Buffered image * @param bim2 Buffered image * @param threshold the color steps difference to ne considered identical * @return If the images are different, the function returns a diff image If * the images are identical, the function returns null If the size is * different, a black border on the bottom and the right is created */ public BufferedImage diffImages(BufferedImage bim1, BufferedImage bim2, int threshold) { BufferedImage result = null; int minWidth = Math.min(bim1.getWidth(), bim2.getWidth()); int minHeight = Math.min(bim1.getHeight(), bim2.getHeight()); int maxWidth = Math.max(bim1.getWidth(), bim2.getWidth()); int maxHeight = Math.max(bim1.getHeight(), bim2.getHeight()); if (minWidth != maxWidth || minHeight != maxHeight) { result = createEmptyDiffImage(minWidth, minHeight, maxWidth, maxHeight); } for (int x = 0; x < minWidth; ++x) { for (int y = 0; y < minHeight; ++y) { int rgb1 = bim1.getRGB(x, y); int rgb2 = bim2.getRGB(x, y); if (rgb1 != rgb2 // don't bother about differences of 1 color step && (Math.abs((rgb1 & 0xFF) - (rgb2 & 0xFF)) > threshold || Math.abs(((rgb1 >> 8) & 0xFF) - ((rgb2 >> 8) & 0xFF)) > threshold || Math.abs(((rgb1 >> 16) & 0xFF) - ((rgb2 >> 16) & 0xFF)) > threshold)) { if (result == null) { result = createEmptyDiffImage(minWidth, minHeight, maxWidth, maxHeight); } int r = Math.abs((rgb1 & 0xFF) - (rgb2 & 0xFF)); int g = Math.abs((rgb1 & 0xFF00) - (rgb2 & 0xFF00)); int b = Math.abs((rgb1 & 0xFF0000) - (rgb2 & 0xFF0000)); result.setRGB(x, y, 0xFFFFFF - (r | g | b)); } else { if (result != null) { result.setRGB(x, y, Color.WHITE.getRGB()); } } } } if (result == null) { result = createEmpty(minWidth, minHeight); } return result; } /** * Create an image; the part between the smaller and the larger image is * painted black, the rest in white * * @param minWidth width of the smaller image * @param minHeight width of the smaller image * @param maxWidth height of the larger image * @param maxHeight height of the larger image */ private BufferedImage createEmptyDiffImage(int minWidth, int minHeight, int maxWidth, int maxHeight) { BufferedImage bim3 = new BufferedImage(maxWidth, maxHeight, BufferedImage.TYPE_INT_RGB); Graphics graphics = bim3.getGraphics(); if (minWidth != maxWidth || minHeight != maxHeight) { graphics.setColor(Color.BLACK); graphics.fillRect(0, 0, maxWidth, maxHeight); } graphics.setColor(Color.WHITE); graphics.fillRect(0, 0, minWidth, minHeight); graphics.dispose(); return bim3; } /** * Create an empty RGB image in white. */ private BufferedImage createEmpty(int width, int height) { BufferedImage bim3 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics graphics = bim3.getGraphics(); graphics.setColor(Color.WHITE); graphics.fillRect(0, 0, width, height); graphics.dispose(); return bim3; } }