package net.hearthstats.game.imageanalysis; import java.awt.image.BufferedImage; import net.hearthstats.util.Coordinate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * Tests whether a set of pixels relative to a reference point match expected * colour values. Unlike the * {@link net.hearthstats.analysis.IndividualPixelAnalyser} which expects pixels * to be in a particular spot, the RelativePixelAnalyser can handle objects in * unknown position by finding a reference point across a broad section of the * screen and then calculating all the other pixels relative to that reference * point. * </p> * <p> * This makes the RelativePixelAnalyser most suitable for moving objects like * the victory and defect popups * </p> * * @author gtch */ public class RelativePixelAnalyser extends CoordinateCacheBase { private final static Logger debugLog = LoggerFactory.getLogger(RelativePixelAnalyser.class); private float cachedRatio = 0; private float lastImageHeight = 0; Coordinate findRelativePixel(BufferedImage image, UniquePixel boundingBoxTopLeft, UniquePixel boundingBoxBottomRight, int xSamples, int ySamples) { UniquePixelIdentifier upiTopLeft = new UniquePixelIdentifier(boundingBoxTopLeft.x(), boundingBoxTopLeft.y(), image.getWidth(), image.getHeight()); UniquePixelIdentifier upiBottomRight = new UniquePixelIdentifier(boundingBoxBottomRight.x(), boundingBoxBottomRight.y(), image.getWidth(), image.getHeight()); Coordinate coordinateTopLeft = getCachedCoordinate(upiTopLeft); Coordinate coordinateBottomRight = getCachedCoordinate(upiBottomRight); float xStepSize = (float) (coordinateBottomRight.x() - coordinateTopLeft.x()) / (float) (xSamples - 1); float yStepSize = (float) (coordinateBottomRight.y() - coordinateTopLeft.y()) / (float) (ySamples - 1); debugLog.debug("relative pixel bounding box: topLeft={},{} bottomRight={},{} stepSize={},{}", coordinateTopLeft.x(), coordinateTopLeft.y(), coordinateBottomRight.x(), coordinateBottomRight.y(), xStepSize, yStepSize); for (int yCount = 0; yCount < ySamples; yCount++) { int y = coordinateTopLeft.y() + (int) (yCount * xStepSize); for (int xCount = 0; xCount < xSamples; xCount++) { int x = coordinateTopLeft.x() + (int) (xCount * xStepSize); int rgb = image.getRGB(x, y); int red = (rgb >> 16) & 0xFF; int green = (rgb >> 8) & 0xFF; int blue = (rgb & 0xFF); // The two bounding box pixels might have different colour ranges, so // test both: if either one matches than this pixel is considered to be // a match if (red >= boundingBoxTopLeft.minRed && red <= boundingBoxTopLeft.maxRed && green >= boundingBoxTopLeft.minGreen && green <= boundingBoxTopLeft.maxGreen && blue >= boundingBoxTopLeft.minBlue && blue <= boundingBoxTopLeft.maxBlue) { // This pixel is inside the expected range so it's an immediate match debugLog.debug("a matched reference pixel at {},{}", x, y); return new Coordinate(x, y); } if (red >= boundingBoxBottomRight.minRed && red <= boundingBoxBottomRight.maxRed && green >= boundingBoxBottomRight.minGreen && green <= boundingBoxBottomRight.maxGreen && blue >= boundingBoxBottomRight.minBlue && blue <= boundingBoxBottomRight.maxBlue) { // This pixel is inside the expected range so it's an immediate match debugLog.debug("b matched reference pixel at {},{}", x, y); return new Coordinate(x, y); } } } return null; } int countMatchingRelativePixels(BufferedImage image, Coordinate referencePixel, UniquePixel[] relativePixels) { int matches = 0; float ratio; if (lastImageHeight == image.getHeight()) { // Use the stored ratio ratio = cachedRatio; } else { // Calculate the ratio and store it for next time lastImageHeight = image.getHeight(); ratio = (float) lastImageHeight / (float) PixelLocation.REFERENCE_SIZE.y(); cachedRatio = ratio; } for (UniquePixel relativePixel : relativePixels) { int x = referencePixel.x() + (int) (relativePixel.x() * ratio); int y = referencePixel.y() + (int) (relativePixel.y() * ratio); int rgb = image.getRGB(x, y); int red = (rgb >> 16) & 0xFF; int green = (rgb >> 8) & 0xFF; int blue = (rgb & 0xFF); if (red >= relativePixel.minRed && red <= relativePixel.maxRed && green >= relativePixel.minGreen && green <= relativePixel.maxGreen && blue >= relativePixel.minBlue && blue <= relativePixel.maxBlue) { // This pixel is inside the expected range so it's an immediate match debugLog.debug("relative pixel at {}, {} matched {}", x, y, relativePixel); matches++; } else { debugLog.debug("relative pixel at {}, {} did not match {}", x, y, relativePixel); } } return matches; } }