/**
* 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.image.BufferedImage;
import java.awt.image.RasterFormatException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Contains the core function(s) that are used for image region comparison.
* Because this is a utility class, an instance of it cannot be created.
*/
public class CoreRegionCompare {
// TODO: At some point this class should be generalised to handle ImageCompareRegionInfo and OCRRegionInfo.
private CoreRegionCompare() { }
/**
* Direction of the rectangular spiral algorithm.
*/
static enum DIRECTION {
RIGHT,
DOWN,
LEFT,
UP
}
/**
* The rectangle shape.
*/
static enum SHAPE {
SQUARE,
VRECT,
HRECT
}
public static ImageCompareResult findRegionOnTargetRegion( ImageCompareRegionInfo smallRegion,
RegionInfo largeRegion, BufferedImage precapturedImage, BufferedImage liveImage ) throws IOException
{
BufferedImage smallImage = precapturedImage.getSubimage( smallRegion.getX(), smallRegion.getY(),
smallRegion.getWidth(), smallRegion.getHeight() );
return CoreImageCompare.findImageOnTargetRegion( smallImage, liveImage, largeRegion.getX(), largeRegion.getY(),
largeRegion.getWidth(), largeRegion.getHeight(), smallRegion.getMatchPct(), smallRegion.getRedTolerance(),
smallRegion.getGreenTolerance(), smallRegion.getBlueTolerance() );
}
/**
* Helper function. Puts info into a ArrrayList and calls doRegionCompare(List< RegionInfo > infoList, BufferedImage snapshot, BufferedImage liveImage).
*
* @param info The region info.
* @param precapturedImage The precaptured image.
* @param liveImage The live image.
* @return true if the region is found in the live image.
* @throws IllegalArgumentException if images are not the same size.
*/
public static boolean doRegionCompare(ImageCompareRegionInfo info, BufferedImage precapturedImage, BufferedImage liveImage) {
List<ImageCompareRegionInfo> infoList = new ArrayList<ImageCompareRegionInfo>(1);
infoList.add(info);
if (doRegionCompare(infoList, precapturedImage, liveImage)) {
return true;
}
return false;
}
/**
* Checks the liveImage for each region found in the infoList based on the precapturedImage.
* Uses a rectangular spiral starting at the expected origin of the image and spiraling clockwise
* in a square/rectangle motion until the image is found or each pixel has been checked within the
* regions x + y tolerance.
*
* @param infoList The list of regions.
* @param precapturedImage The precaptured image.
* @param liveImage The live image (or image to compare against).
* @return <b>true</b> if all the regions are found in the live image.
* @throws IllegalArgumentException if images are not the same size.
* @throws NullPointerException if precapturedImage or liveImage is null.
*/
public static boolean doRegionCompare(List<ImageCompareRegionInfo> infoList, BufferedImage precapturedImage, BufferedImage liveImage) {
boolean found = false;
int w1 = precapturedImage.getWidth(null);
int h1 = precapturedImage.getHeight(null);
int w2 = liveImage.getWidth(null);
int h2 = liveImage.getHeight(null);
if ((w1 != w2) || (h1 != h2)) {
throw new IllegalArgumentException("precapturedImage and liveImage must be the same size");
}
if (infoList != null && !infoList.isEmpty()) {
BufferedImage expectedImage = null;
BufferedImage currentImage = null;
for (ImageCompareRegionInfo info : infoList) {
try {
expectedImage = precapturedImage.getSubimage(info.getX(), info.getY(), info.getWidth(), info.getHeight());
} catch (RasterFormatException rfe) {
return false;
}
found = false;
int minX = info.getX() - info.getXTolerance();
int maxX = info.getX() + info.getXTolerance();
int curX = info.getX();
int minY = info.getY() - info.getYTolerance();
int maxY = info.getY() + info.getYTolerance();
int curY = info.getY();
// First lets see if its in the original position.
// This can never throw a raster exception because the images are the precapturedImage
// and liveImage are same size. We got the expectedImage just fine so we can get current image too.
currentImage = liveImage.getSubimage(curX, curY, info.getWidth(), info.getHeight());
if (CoreImageCompare.compareImages(currentImage, expectedImage, info.getMatchPct(), info.getRedTolerance(),
info.getGreenTolerance(), info.getBlueTolerance())) {
found = true;
continue;
}
DIRECTION direction = DIRECTION.RIGHT;
int distance = 1;
SHAPE recType;
if (info.getYTolerance() == info.getXTolerance()) {
recType = SHAPE.SQUARE;
} else if (info.getYTolerance() > info.getXTolerance()) {
recType = SHAPE.VRECT;
} else {
recType = SHAPE.HRECT;
}
boolean keepSearching = true;
while (keepSearching) {
switch (direction) {
case RIGHT:
for (int i = 0; i != distance; ++i) {
++curX;
if (curX <= maxX) {
try {
currentImage = liveImage.getSubimage(curX, curY, info.getWidth(), info.getHeight());
if (CoreImageCompare.compareImages(currentImage, expectedImage, info.getMatchPct(), info.getRedTolerance(),
info.getGreenTolerance(), info.getBlueTolerance())) {
found = true;
keepSearching = false;
break;
}
} catch (RasterFormatException rfe) {
rfe = null;
}
} else {
if (recType == SHAPE.SQUARE) {
// Square always ends in this direction
keepSearching = false;
break;
} else if (recType == SHAPE.VRECT) {
// Either a jump down or finish.
if (curY > minY) {
curY += distance++;
direction = DIRECTION.LEFT;
break;
} else {
// No match found.
found = false;
keepSearching = false;
break;
}
}
}
}
if (direction == DIRECTION.RIGHT) {
direction = DIRECTION.DOWN;
// Is this the last time we will be going
// Right then down?
if (recType == SHAPE.HRECT && (curX == maxX || curY == minY)) {
++distance;
}
}
break;
case DOWN:
for (int i = 0; i != distance; ++i) {
++curY;
if (curY <= maxY) {
try {
currentImage = liveImage.getSubimage(curX, curY, info.getWidth(), info.getHeight());
if (CoreImageCompare.compareImages(currentImage, expectedImage, info.getMatchPct(), info.getRedTolerance(),
info.getGreenTolerance(), info.getBlueTolerance())) {
found = true;
keepSearching = false;
break;
}
} catch (RasterFormatException rfe) {
rfe = null;
}
} else {
// Can never end going down
// so jump up
if (recType == SHAPE.HRECT) {
curX -= distance++;
direction = DIRECTION.UP;
break;
}
}
}
if (direction == DIRECTION.DOWN) {
++distance;
direction = DIRECTION.LEFT;
}
break;
case LEFT:
for (int i = 0; i != distance; ++i) {
--curX;
if (curX >= minX) {
try {
currentImage = liveImage.getSubimage(curX, curY, info.getWidth(), info.getHeight());
if (CoreImageCompare.compareImages(currentImage, expectedImage, info.getMatchPct(), info.getRedTolerance(),
info.getGreenTolerance(), info.getBlueTolerance())) {
found = true;
keepSearching = false;
break;
}
} catch (RasterFormatException rfe) {
rfe = null;
}
} else {
// Can never end going left
// so jump up
if (recType == SHAPE.VRECT) {
curY -= distance++;
direction = DIRECTION.RIGHT;
break;
}
}
}
if (direction == DIRECTION.LEFT) {
direction = DIRECTION.UP;
}
break;
case UP:
for (int i = 0; i != distance; ++i) {
--curY;
if (curY >= minY) {
try {
currentImage = liveImage.getSubimage(curX, curY, info.getWidth(), info.getHeight());
if (CoreImageCompare.compareImages(currentImage, expectedImage, info.getMatchPct(), info.getRedTolerance(),
info.getGreenTolerance(), info.getBlueTolerance())) {
found = true;
keepSearching = false;
break;
}
} catch (RasterFormatException rfe) {
rfe = null;
}
} else {
// Either a jump down or finish.
if (recType == SHAPE.HRECT) {
if (curX > minX) {
curX += distance++;
direction = DIRECTION.DOWN;
break;
} else {
keepSearching = false;
break;
}
}
}
}
if (direction == DIRECTION.UP) {
++distance;
direction = DIRECTION.RIGHT;
}
break;
default:
break;
}
}
// The image was not found, no point in checking any of the other
// regions.
if (!found) {
break;
}
}
}
return found;
}
}