package com.niklim.clicktrace.capture.voter;
import java.awt.Color;
import java.awt.image.BufferedImage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.niklim.clicktrace.TimeMeter;
/**
* Samples image horizontally and vertically with lines. Each line shifted by
* LINE_SHIFT pixels from top to bottom and left to right. Each line is compared
* with corresponding line of the second image.
*
* If more than LINE_COUNT_THRESHOLD_COEFF lines differ, then change is
* detected. Two lines differ when at least
* {@link ChangeSensitivity#lineCountThresholdCoeff} their corresponding pixels
* differ. Two pixels differ when: see {@link LineVoter#PIXEL_DIFF_THRESHOLD}
* JavaDoc.
*
*/
public class LineVoter implements ChangeVoter {
private static final Logger log = LoggerFactory.getLogger(LineVoter.class);
/**
* Defines how sensitive {@link LineVoter} is.
*/
public static enum ChangeSensitivity {
HIGH(0, 0.005, 10, 3), NORMAL(0.005, 0.01, 10, 5), LOW(0.05, 0.05, 30, 5);
/**
* used to define when two corresponding lines differ
*/
final double lineDiffThresholdCoeff;
/**
* used to define how many corresponding lines between two shots must
* differ to decide that the shots differ
*/
final double lineCountThresholdCoeff;
/**
* defines when two pixels differ: put pixels in 3dim RGB space and
* calculate Manhattan distance, if distance is greater than
* PIXEL_DIFF_THRESHOLD, then pixels differ
*/
final int pixelDiffThreshold;
/**
* defines line sampling rate in pixels. i.e. if lineShift = 5, then we
* take lines at 1st pixel, 6th pixel, 11th pixel...
*/
final int lineShift;
ChangeSensitivity(double lineDiffThresholdCoeff, double lineCountThresholdCoeff, int pixelDiffThreshold,
int lineShift) {
this.lineDiffThresholdCoeff = lineDiffThresholdCoeff;
this.lineCountThresholdCoeff = lineCountThresholdCoeff;
this.pixelDiffThreshold = pixelDiffThreshold;
this.lineShift = lineShift;
}
}
private ChangeSensitivity sensitivity;
public LineVoter(ChangeSensitivity sensitivity) {
this.sensitivity = sensitivity;
}
/**
* Returns pixel from BufferedImage depending on line orientation.
*/
private static interface PixelPicker {
Color pick(BufferedImage prev, int discretePos, int continuousPos);
}
PixelPicker verticalPixelPicker = new PixelPicker() {
public Color pick(BufferedImage prev, int discretePos, int continuousPos) {
return new Color(prev.getRGB(discretePos, continuousPos));
}
};
PixelPicker horizontalPixelPicker = new PixelPicker() {
public Color pick(BufferedImage prev, int discretePos, int continuousPos) {
return new Color(prev.getRGB(continuousPos, discretePos));
}
};
@Override
public Vote vote(BufferedImage prev, BufferedImage current) {
TimeMeter tm = TimeMeter.start("LineVoter.vote", log);
if (prev.getWidth() != current.getWidth() || prev.getHeight() != current.getHeight()) {
throw new RuntimeException("Prev and current images have different size");
}
int width = prev.getWidth();
int height = prev.getHeight();
int differingLinesCount = 0;
double verticalLineDiffThreshold = height * sensitivity.lineDiffThresholdCoeff;
double horizontalLineDiffThreshold = width * sensitivity.lineDiffThresholdCoeff;
differingLinesCount += calculateDifferingLinesCount(prev, current, width, height, verticalLineDiffThreshold,
verticalPixelPicker);
differingLinesCount += calculateDifferingLinesCount(prev, current, height, width, horizontalLineDiffThreshold,
horizontalPixelPicker);
tm.stop();
if (differingLinesCount > (width / sensitivity.lineShift + height / sensitivity.lineShift)
* sensitivity.lineCountThresholdCoeff) {
return Vote.SAVE;
} else {
return Vote.ABSTAIN;
}
}
private int calculateDifferingLinesCount(BufferedImage prev, BufferedImage current, int lengthDiscrete,
int lengthContinuous, double lineDiffThreshold, PixelPicker pixelPicker) {
int differingLinesCounter = 0;
for (int i = 0; i < lengthDiscrete; i += sensitivity.lineShift) {
int pixelDiffCount = 0;
for (int j = 0; j < lengthContinuous; j++) {
Color prevColor = pixelPicker.pick(prev, i, j);
Color currentColor = pixelPicker.pick(current, i, j);
int pxDiff = calculatePixelDifference(prevColor, currentColor);
if (pxDiff > sensitivity.pixelDiffThreshold) {
pixelDiffCount++;
}
}
if (lineDiffThreshold < pixelDiffCount) {
differingLinesCounter++;
}
}
return differingLinesCounter;
}
private int calculatePixelDifference(Color prevColor, Color currentColor) {
int redDiff = Math.abs(prevColor.getRed() - currentColor.getRed());
int greenDiff = Math.abs(prevColor.getGreen() - currentColor.getGreen());
int blueDiff = Math.abs(prevColor.getBlue() - currentColor.getBlue());
int diff = redDiff + greenDiff + blueDiff;
return diff;
}
}