## Sunday, July 22, 2012

### Visual testing of a triangle filling graphic method

Some time ago I was invited by a big company for a technical interview where I was asked by a test engineer - "how to write test for a method filling a triangle area?".. I didn't find the answer for the provided time (some inappropriate ideas like to use a neural network chased each other in my brain), but the problem was very interesting for me and an idea dawned me on the next day..
Idea: A Triangle is a very easy shape but it is very hard to distinguish the shape in an image by a computer, but a rectangle is much easier to be processed by a computer, thus we should just make a rectangle from two triangles and then we will check that the rectangle area is presented and artifacts-free, also we can check that there are not any points outside of the area..
I have written some Java code to check the idea. There is not a method in the java.awt.Graphics object to fill a triangle thus I have written such one:
```public class TriangleFiller {
/**
* The method fills a triangle area and we check that the method fills a triangle
* @param graphics the graphics context
* @param xcoords an array contains the x coordinates for vertices (must have 3 positions)
* @param ycoords an array contains the y coordinates for vertices (must have 3 positions)
*/
public static void fillTriangle(final Graphics graphics, int[] xcoords, int[] ycoords) {
if (xcoords.length != 3 || ycoords.length != 3) {
throw new IllegalArgumentException("Triangle must have 3 points");
}
final Polygon polygon = new Polygon(xcoords, ycoords, 3);
// we need use draw+fill because the fill operation fills the inside area
graphics.drawPolygon(polygon);
graphics.fillPolygon(polygon);
}
}
```

Then I wrote a unit test to make the "visual check" of the method just on an image and it works well:
```package com.igormaznitsa.testtriangle;

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;
import static org.junit.Assert.*;
import org.junit.Test;

public class TriangleFillerTest {

private boolean checkTriangleCornerPoints(final BufferedImage baseRGBImage, final int pointColor, final int[] xVerticies, final int[] yVerticies) {
for (int pointIndex = 0; pointIndex < 3; pointIndex++) {
final int x = xVerticies[pointIndex];
final int y = yVerticies[pointIndex];

if ((baseRGBImage.getRGB(x, y) & 0xFFFFFF) != pointColor) {
return false;
}
}
return true;
}

private boolean checkRectangleAreaHasBeenFilledOnly(final BufferedImage baseRGBImage, final int fillColor, final int backgroundColor, final int areaLeftX, final int areaTopY, final int areaWidth, final int areaHeight) {
final int areaRightX = areaLeftX + areaWidth;
final int areaBottomY = areaTopY + areaHeight;

final int imagewidth = baseRGBImage.getWidth();
final int imageheight = baseRGBImage.getHeight();

for (int y = 0; y < imageheight; y++) {
for (int x = 0; x < imagewidth; x++) {
final int rgb = baseRGBImage.getRGB(x, y) & 0xFFFFFF;
final boolean notInArea = x < areaLeftX || x > areaRightX || y < areaTopY || y > areaBottomY;
if (notInArea) {
assertEquals("Must be background color "+backgroundColor, backgroundColor, rgb);
} else {
assertEquals("Must be fill color " + fillColor, fillColor, rgb);
}
}
}
return true;
}

@Test
public void testFillTriangle_VisualTest() throws Exception {
// the graphic log image file
final File gfxLogFile = new File("./testimage.png");

// create a RGB memory rendered image which will be our base for the test
final BufferedImage baseTestImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
final Graphics2D gfx = (Graphics2D) baseTestImage.getGraphics();

// we must disable antialasing for the graphics to avoid artefacts
((Graphics2D) gfx).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);

final int backColor = 0x000000;
final int shapeColor = 0xFFFFFF;

// fill the image by our background color
gfx.setColor(new Color(backColor));
gfx.clearRect(0, 0, 200, 200);

// set the shape color
gfx.setColor(new Color(shapeColor));

// our triangles, we use non-equaterial triangles, to make a rectangle, not a square
final int[] firstTriangleX = new int[]{10, 10, 50};
final int[] firstTriangleY = new int[]{10, 150, 150};
final int[] secondTriangleX = new int[]{10, 50, 50};
final int[] secondTriangleY = new int[]{10, 10, 150};

// draw the first triangle
TriangleFiller.fillTriangle(gfx, firstTriangleX, firstTriangleY);

// save the current graphic state as an image (like log)
ImageIO.write(baseTestImage, "png", gfxLogFile);

assertTrue("The image must have set the points of vertices", checkTriangleCornerPoints(baseTestImage, shapeColor, firstTriangleX, firstTriangleY));

// draw the second triangle, to get a filled rectangular area on the image
TriangleFiller.fillTriangle(gfx, secondTriangleX, secondTriangleY);

gfx.dispose();

// save the current graphic state as an image (like log)
ImageIO.write(baseTestImage, "png", gfxLogFile);

assertTrue("Only the rectangle area must be filled, without artefacts",checkRectangleAreaHasBeenFilledOnly(baseTestImage, shapeColor, backColor, 10, 10, 40, 140));
}
}
```