/** * Copyright 2014 Comcast Cable Communications Management, LLC * * This file is part of CATS. * * CATS 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 3 of the License, or * (at your option) any later version. * * CATS 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 CATS. If not, see <http://www.gnu.org/licenses/>. */ package com.comcast.cats.image; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.WritableRaster; /** * Contains the core function(s) that are used for image comparison. * Because this is a utility class, an instance of it cannot be created. */ public class CoreImageCompare { private CoreImageCompare() { } /** * Compares the specified images based on the specified tolerances. * Parameters are not checked (for nulls or out of range values) to maximize performance. Expected values * are specified in parameter java docs. * * @param currentImage * The image to check. Cannot be null. * @param expectedImage * The expected image. Cannot be null. * @param matchPercent * The match percent needed for the image compare to be successful. * Value should be between 0 - 100 for correct results. * Negative value will always result in true. > 100 will always result in false. * @param redTolerance * The red tolerance. * Value should be between 0 - 255 for correct results. * Negative value will always result in false if matchPercent > 0 * @param greenTolerance * The green tolerance. * Value should be between 0 - 255 for correct results. * Negative value will always result in false if matchPercent > 0 * @param blueTolerance * The blue tolerance. * Value should be between 0 - 255 for correct results. * Negative value will always result in false if matchPercent > 0 * @return true if the images are equal. * false is the images are not equal. */ public static boolean compareImages(BufferedImage currentImage, BufferedImage expectedImage, float matchPercent, long redTolerance, long greenTolerance, long blueTolerance) { int w1 = currentImage.getWidth(null); int h1 = currentImage.getHeight(null); int w2 = expectedImage.getWidth(null); int h2 = expectedImage.getHeight(null); if ((w1 != w2) || (h1 != h2)) { //Log.err("ERROR! Images are not the same size (src is " + w1 + "x" + h1 + " and dst is " + w2 + "x" + h2 + ")"); return false; } int pixelMatch = 0; int arraySize = w1 * h1; int[] rgbs1 = new int[arraySize]; int[] rgbs2 = new int[arraySize]; // Fetch all of the ARGB pixel values from currentImage and expectedImage currentImage.getRGB(0, 0, w1, h1, rgbs1, 0, w1); expectedImage.getRGB(0, 0, w2, h2, rgbs2, 0, w2); final float COEFF = 100.0f / (float) arraySize; for (int col = 0; col < w1; col++) { for (int idx = col; idx < rgbs1.length; idx += w1) { int rgb1 = rgbs1[idx]; int red1 = (rgb1 & 0xff0000) >> 16; int green1 = (rgb1 & 0x00ff00) >> 8; int blue1 = rgb1 & 0x0000ff; int rgb2 = rgbs2[idx]; int red2 = (rgb2 & 0xff0000) >> 16; int green2 = (rgb2 & 0x00ff00) >> 8; int blue2 = rgb2 & 0x0000ff; if ((Math.abs(red1 - red2) <= redTolerance) && (Math.abs(green1 - green2) <= greenTolerance) && (Math.abs(blue1 - blue2) <= blueTolerance)) { pixelMatch++; } } } float actualMatchPercent = pixelMatch * COEFF; if (actualMatchPercent < matchPercent) { return false; } return true; } /** * Search for a particular image on the specified search area of another image, based on the specified tolerances. * * @param refImage * The image to find out. Cannot be null. * @param targetImage * The full target image. Cannot be null. * This is the image on which we are going to perform the search for small image in the defined search area. * @param x * The starting X position of the search area in the target * image. * @param y * The starting Y position of the search area in the target * image. * @param width * The Width of search area in the target image. * @param height * The Height of search area in the target image. * @param matchPercent * The match percent needed for the image compare to be * successful. Value should be between 0 - 100 for correct * results. Negative value will always result in true. > 100 will * always result in false. * @param redTolerance * The red tolerance. Value should be between 0 - 255 for correct * results. Negative value will always result in false if * matchPercent > 0 * @param greenTolerance * The green tolerance. Value should be between 0 - 255 for * correct results. Negative value will always result in false if * matchPercent > 0 * @param blueTolerance * The blue tolerance. Value should be between 0 - 255 for * correct results. Negative value will always result in false if * matchPercent > 0 * @return ImageCompareResult */ public static ImageCompareResult findImageOnTargetRegion( BufferedImage refImage, BufferedImage fullTargetImage, int x, int y, int width, int height, float matchPercent, int redTolerance, int greenTolerance, int blueTolerance ) { ImageCompareResult icResult = new ImageCompareResult(); // Getting the sub image on which the search need to be performed. BufferedImage bigImage = fullTargetImage.getSubimage( x, y, width, height ); int bwidth = bigImage.getWidth(); int bheight = bigImage.getHeight(); int swidth = refImage.getWidth(); int sheight = refImage.getHeight(); // Getting the pixels of the big image (image on which the search is to be performed) and small image (the image to be found out) int[][] bigImgPix = getColorModel( bigImage ); int[][] smallImgPix = getColorModel( refImage ); // Getting pixel tolerance to be allowed in the comparison float unmatchedTolerance = ( swidth * sheight ) * ( 100.0f - matchPercent ) / 100; boolean found = true; // Iterating over the big image pixels and placing the small image over each position and comparing whether it matches or not. for ( int colIdx = 0; colIdx <= ( bwidth - swidth ); colIdx++ ) { for ( int rowIdx = 0; rowIdx <= ( bheight - sheight ); rowIdx++ ) { found = true; int unmatchCount = 0; // placing the small image on the big image and doing comparison for ( int sImgColIdx = 0, bImgColIdx = colIdx; sImgColIdx < swidth && bImgColIdx < bwidth; sImgColIdx++, bImgColIdx++ ) { for ( int sImgRowIdx = 0, bImgRowIdx = rowIdx; sImgRowIdx < sheight && bImgRowIdx < bheight; sImgRowIdx++, bImgRowIdx++ ) { // Getting the pixels at a particular position int bigImgPixel = bigImgPix[ bImgColIdx ][ bImgRowIdx ]; int smallImgPixel = smallImgPix[ sImgColIdx ][ sImgRowIdx ]; // Applying tolerances int red1 = ( bigImgPixel & 0xff0000 ) >> 16; int green1 = ( bigImgPixel & 0x00ff00 ) >> 8; int blue1 = bigImgPixel & 0x0000ff; int red2 = ( smallImgPixel & 0xff0000 ) >> 16; int green2 = ( smallImgPixel & 0x00ff00 ) >> 8; int blue2 = smallImgPixel & 0x0000ff; // comparing the pixels if ( !( ( Math.abs( red1 - red2 ) <= redTolerance ) && ( Math.abs( green1 - green2 ) <= greenTolerance ) && ( Math.abs( blue1 - blue2 ) <= blueTolerance ) ) ) { // If the unmatched count exceeds the unmatched tolerance, breaking from the small image pixel iteration loop. if ( unmatchCount >= unmatchedTolerance ) { found = false; break; } unmatchCount++; } } if ( !found ) { break; } } if ( found ) { icResult = createResult( x + colIdx, y + rowIdx, swidth, sheight, x, y, width, height, fullTargetImage ); return icResult; } } } return icResult; } private static ImageCompareResult createResult( int resultX, int resultY, int resultWidth, int resultHeight, int searchX, int searchY, int searchWidth, int searchHeight, BufferedImage image ) { BufferedImage clonedImage = getClone( image ); Graphics2D gc = clonedImage.createGraphics(); gc.setColor( Color.RED ); gc.drawRect( resultX, resultY, resultWidth, resultHeight ); gc.setStroke( new BasicStroke( 1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[] { 10.0f }, 0.0f )); gc.setColor( Color.WHITE ); gc.drawRect( searchX, searchY, searchWidth, searchHeight ); RegionInfo regionInfo = new RegionInfo(); regionInfo.setX( resultX ); regionInfo.setY( resultY ); regionInfo.setWidth( resultWidth ); regionInfo.setHeight( resultHeight ); return new ImageCompareResult(true, regionInfo, clonedImage); } private static int[][] getColorModel( BufferedImage image ) { int w = image.getWidth(); int h = image.getHeight(); int[][] list = new int[ w ][ h ]; for ( int i = 0; i < h; i++ ) { for ( int j = 0; j < w; j++ ) { int pixel = image.getRGB( j, i ); list[ j ][ i ] = pixel; } } return list; } private static BufferedImage getClone( BufferedImage image ) { ColorModel colorModel = image.getColorModel(); WritableRaster raster = image.copyData( null ); return new BufferedImage( colorModel, raster, colorModel.isAlphaPremultiplied(), null ); } }