package net.hearthstats.game.imageanalysis;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.imageio.ImageIO;
import net.hearthstats.game.Screen;
import net.hearthstats.game.imageanalysis.RelativePixelAnalyser;
import net.hearthstats.game.imageanalysis.ScreenAnalyser;
import net.hearthstats.game.imageanalysis.UniquePixel;
import net.hearthstats.util.Coordinate;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class tests the RelativePixelAnalyser against a folder of screenshots of victory and defeat screens
* (which you need to provide yourself) to ensure that the victory and defeat detection is accurate.
* It generates a set of HTML files showing each screenshot with information about whether it matched victory
* or defeat for screenshot.
*
* @author gtch
*/
public class RelativePixelAnalyserMain {
private final static Logger log = LoggerFactory.getLogger(RelativePixelAnalyserMain.class);
private final static String IMAGE_PATH = "/tmp/hearthstats";
private final static int PAGE_SIZE = 25;
public static void main(String[] args) throws Exception {
new RelativePixelAnalyserMain().testFindRelativePixel();
}
public void testFindRelativePixel() throws Exception {
ScreenAnalyser analyser = new ScreenAnalyser();
RelativePixelAnalyser relativePixelAnalyser = new RelativePixelAnalyser();
File imageFolder = new File(IMAGE_PATH);
File[] imageArray = imageFolder.listFiles();
Assert.assertNotNull("No files found in " + IMAGE_PATH + ". Please make sure you've set the path to a folder that contains screenshots from Hearthstone", imageArray);
List<File> images = new ArrayList<>(imageArray.length);
for (File image : imageArray) {
if (image.getName().endsWith(".png")) {
// Determine if this is a match end image
BufferedImage bufferedImage = ImageIO.read(image);
Screen screen = analyser.identifyScreen(bufferedImage, null);
if (screen == Screen.MATCH_NAXXRAMAS_END || screen == Screen.MATCH_ORGRIMMAR_END || screen == Screen.MATCH_PANDARIA_END
|| screen == Screen.MATCH_STORMWIND_END || screen == Screen.MATCH_STRANGLETHORN_END) {
// This is a match end screen, so it is suitable for testing with the RelativePixelAnalyser
images.add(image);
}
bufferedImage.flush();
}
}
Assert.assertFalse("No match end images found in " + IMAGE_PATH + ". Please make sure you've set the path to a folder that contains screenshots from Hearthstone", images.size() == 0);
int page = 0;
int pageCount = (images.size() / PAGE_SIZE) + 1;
while (page < pageCount) {
page++;
String filename = IMAGE_PATH + "/relative-test-" + page + ".html";
try (BufferedWriter output = new BufferedWriter(new FileWriter(filename))) {
writeHtmlHeader(output, page, pageCount);
List<Coordinate> coordinates = new ArrayList<>();
for (int i = (page - 1) * PAGE_SIZE; i < images.size() && i < page * PAGE_SIZE; i++) {
File image = images.get(i);
output.write("<tr>" +
"<td colspan=\"3\" class=\"filename\"><h2>");
output.write(image.getName());
output.write("</h2></td>" +
"</tr>" +
"<tr>" +
"<td><div><img src=\"");
output.write(image.getName());
output.write("\" id=\"img_");
output.write(String.valueOf(i - ((page - 1) * PAGE_SIZE)));
output.write("\" alt=\"");
output.write(image.getName());
output.write("\" width=\"400\"></div></td>");
output.write("<td><canvas id=\"canvas_");
output.write(String.valueOf(i - ((page - 1) * PAGE_SIZE)));
output.write("\" width=\"300\" height=\"300\"></td>");
try {
log.debug("***** Testing Image {} *****", image.getName());
BufferedImage bufferedImage = ImageIO.read(image);
Coordinate coordinate = relativePixelAnalyser.findRelativePixel(bufferedImage, UniquePixel.VICTORY_DEFEAT_REFBOX_TL, UniquePixel.VICTORY_DEFEAT_REFBOX_BR, 8, 11);
coordinates.add(coordinate);
output.write("<td class=\"");
if (coordinate == null) {
output.write("matchzero");
} else {
output.write("matchone");
}
output.write("\">");
if (coordinate != null) {
output.write("<div>Reference Pixel = ");
output.write(String.valueOf(coordinate.x()));
output.write(", ");
output.write(String.valueOf(coordinate.y()));
output.write("</div>");
int victory1Matches = relativePixelAnalyser.countMatchingRelativePixels(bufferedImage, coordinate, new UniquePixel[] {
UniquePixel.VICTORY_REL_1A, UniquePixel.VICTORY_REL_1B
});
int victory2Matches = relativePixelAnalyser.countMatchingRelativePixels(bufferedImage, coordinate, new UniquePixel[] {
UniquePixel.VICTORY_REL_2A, UniquePixel.VICTORY_REL_2B, UniquePixel.VICTORY_REL_2C
});
int defeat1Matches = relativePixelAnalyser.countMatchingRelativePixels(bufferedImage, coordinate, new UniquePixel[] {
UniquePixel.DEFEAT_REL_1A, UniquePixel.DEFEAT_REL_1B, UniquePixel.DEFEAT_REL_1C, UniquePixel.DEFEAT_REL_1D, UniquePixel.DEFEAT_REL_1E
});
int defeat2Matches = relativePixelAnalyser.countMatchingRelativePixels(bufferedImage, coordinate, new UniquePixel[] {
UniquePixel.DEFEAT_REL_2A
});
output.write("<div>Count of V1 matches: ");
output.write(String.valueOf(victory1Matches));
output.write("</div>");
output.write("<div>Count of V2 matches: ");
output.write(String.valueOf(victory2Matches));
output.write("</div>");
output.write("<div>Count of D1 matches: ");
output.write(String.valueOf(defeat1Matches));
output.write("</div>");
output.write("<div>Count of D2 matches: ");
output.write(String.valueOf(defeat2Matches));
output.write("</div>");
if (victory1Matches > 0 && victory2Matches == 3 && defeat1Matches == 0 && defeat2Matches == 0) {
output.write("<div><b>MATCHED VICTORY</b></div>");
}
if (victory1Matches == 0 && victory2Matches == 0 && defeat1Matches > 0 && defeat2Matches == 1) {
output.write("<div><b>MATCHED DEFEAT</b></div>");
}
}
output.write("</td>");
} catch (IOException e) {
log.warn("Cannot handle image " + image.getName() + " due to exception", e);
output.write("<b>Exception</b></td></tr>");
}
output.write("</tr>");
}
writeCanvasJavascript(output, coordinates);
writeHtmlFooter(output, page, pageCount);
} catch (IOException e) {
Assert.fail("IOException writing file " + filename);
throw e;
}
}
}
private void writeHtmlHeader(BufferedWriter output, int page, int pageCount) throws IOException {
output.write("<html>" +
"<head>" +
"<title>HeartStats Companion - Relative Pixel Location Test</title>" +
"<style type=\"text/css\">" +
"html, body, p, div, th, td { font-family: Helvetica, Arial; } " +
".nav { background-color: #f8f8f8; padding: 10px 20px; text-align: center; font-size: 123%; margin: 10px 0; } " +
".matchzero { background-color: #f4f4f4; vertical-align: middle; } " +
".matchone { background-color: #e8ffe8; vertical-align: middle; } " +
".matchmulti { background-color: #fff8e8; vertical-align: middle; } " +
".extra { vertical-align: top; } " +
"h2 { margin: 20px 0 0 0; font-size: 14pt; } " +
".cd { color: #FFFFFF; text-align: center; font-weight: bold; } " +
".cl { color: #000000; text-align: center; font-weight: bold; } " +
"tr, td, th { vertical-align: middle; padding: 5px 8px; } " +
"</style></head>");
output.write("<body><h1>HeartStats Companion Relative Pixel Location Test</h1><p>This test was executed at ");
output.write(String.format("%1$tr, %1$te %1$tb %1$tY", new Date()));
output.write(".</p>");
writeHtmlPageNav(output, page, pageCount);
output.write("<table>" +
"<col width=\"400\">" +
"<col width=\"300\">" +
"<col width=\"250\">" +
"<thead>" +
"<tr>" +
"<th>Image</th>" +
"<th>Reference Pixel</th>" +
"<th>Test Results</th>" +
"</tr>" +
"</thead><tbody>");
}
private void writeHtmlFooter(BufferedWriter output, int page, int pageCount) throws IOException {
output.write("</tbody></table>");
writeHtmlPageNav(output, page, pageCount);
output.write("</body></html>");
}
/**
* Writes an inline Javascript which zooms in on the reference pixel area and highlights the reference pixel location.
*/
private void writeCanvasJavascript(BufferedWriter output, List<Coordinate> coordinates) throws IOException {
output.write("<script type=\"text/javascript\">\n" +
"function drawPixel(id, x, y) {\n" +
"\n" +
" var canvas = document.getElementById(\"canvas_\" + id);\n" +
" var ctx = canvas.getContext(\"2d\");\n" +
" \n" +
" var image = new Image();\n" +
" image.onload = function()\n" +
" {\n" +
"\t\tvar xoff = Math.floor(x / 200) * 200 - 50;\n" +
"\t\tvar yoff = Math.floor(y / 200) * 200 - 50;\n" +
"\t\tctx.drawImage(image, xoff, yoff, 300, 300, 0, 0, 300, 300);\n" +
"\t\tctx.beginPath();\n" +
"\t\tx = x - xoff;\n" +
"\t\ty = y - yoff;\n" +
"\t\tctx.strokeStyle = \"rgba(255,255,255,0.8)\";\n" +
"\t\tctx.lineWidth = 4\n" +
"\t\tctx.strokeRect(x-6, y-6, 12, 12);\n" +
"\t\tctx.strokeStyle = \"rgba(0,0,0,0.8)\";\n" +
"\t\tctx.lineWidth = 1\n" +
"\t\tctx.strokeRect(x-4, y-4, 8, 8);\n" +
"\t\tctx.strokeRect(x-8, y-8, 16, 16);\n" +
" }\n" +
" image.src = document.getElementById(\"img_\" + id).src;\t\n" +
"}\n" +
"\n" +
"window.addEventListener(\"DOMContentLoaded\", function()\n" +
"{\n");
for (int i = 0; i < coordinates.size(); i++) {
Coordinate coordinate = coordinates.get(i);
if (coordinate != null) {
output.write("drawPixel(\"" + i + "\", " + coordinate.x()
+ ", " + coordinate.y() + ");\n");
}
}
output.write("});\n" +
"</script>");
}
private void writeHtmlPageNav(BufferedWriter output, int page, int pageCount) throws IOException {
if (pageCount > 1) {
output.write("<div class=\"nav\">");
if (page > 1) {
output.write("<a href=\"relative-test-" + (page - 1) + ".html\">< Prev Page</a> ");
}
for (int i = 1; i <= pageCount; i++) {
output.write("<a href=\"relative-test-" + i + ".html\">" + i + "</a> ");
}
if (page < pageCount) {
output.write("<a href=\"relative-test-" + (page + 1) + ".html\">Next Page ></a> ");
}
output.write("</div>");
}
}
}