Using Test Images with RendererForTinInspection - gwlucastrig/Tinfour GitHub Wiki
Under construction -- More content coming soon
Introduction
The RendererForTinInspection utility assists software development by producing diagnostic images from Tinfour's Triangulated Irregular Network (TIN) classes (in Tinfour, all TINs are proper Delaunay triangulations). The utility is configured to illustrate the structural elements of the TIN and provide visual insights into the way the software works. The RendererForTinInspection utility is included in Tinfour’s core module and can be integrated into test and example applications with minimal effort.
The assist in understanding the use of the renderer class and the images it produces, you may find it helpful to see our documentation for the concepts and data structures used in Tinfour. These concepts are described in detail in our document Data Elements and Algorithms for the Tinfour Libary. You may also find it useful to see some of the other articles in this wiki, particularly the Tutorial Using Edge-Based Techniques.
What the renderer does
The figure below shows an example image created from a simple TIN that was built from 5 vertices.
images/RendererForTinInspection/Example1.png
What the image tells us
The image created by RendererForTinInspection shows the elements in the Triangulated Irregular Network (TIN) created by the IncrementalTin class.
Vertices
- Vertices are labeled with their integer index value.
- The vertex index is an arbitrary value assigned to a vertex by the application code.
- Vertex index values do not have to be unique, but making them unique can help with debugging and development.
Edges
- Edges are created and managed by the Tinfour Incremental TIN classes.
- Each edge has two sides. One for each direction of traversal.
- Each side is numbered with a unique value (the "edge index").
- Each side of the edge is a unique object.
- Each side has a forward and reverse link. Taken together, these links form a triangle.
Triangles
- Triangles are established by setting the link values for edges.
- A forward traversal of a triangle’s edges is always assigned in counterclockwise (anticlockwise) order.
In the picture, edge-index labels are placed on the left side of a edge. So for the edge connecting vertex 0 to vertex 1, the index label "0" is placed to the left of the edge based on its direction of traversal. When the edge is traversed in the opposite direction, from vertex 1 to 0, the label "1" is placed on the left side of the edge.
In Tinfour, triangles are always traversed in counterclockwise order. So the triangle at the bottom of the image is defined by vertices 0, 1, and 4, in that order. Internally to the code, the triangle is represented by edges 0, 23, and 20, in that order.
Using the RendererForInspection class to create an image
The following code snippet was used to create the image for the example above.
// Construct a Triangulated Irregular Network (TIN),
// Add vertices with arguments (x, y, z, index)
IncrementalTin tin = new IncrementalTin(1.0);
tin.add(new Vertex(0.0, 0.0, 10.0, 0));
tin.add(new Vertex(1.0, 0.0, 15.0, 1));
tin.add(new Vertex(1.0, 1.0, 20.0, 2));
tin.add(new Vertex(0.0, 1.0, 15.0, 3));
tin.add(new Vertex(0.5, 0.5, 13.0, 4));
// Construct the renderer and set options for display
RendererForTinInspection renderer = new RendererForTinInspection(tin);
renderer.setEdgeLabelEnabled(true);
renderer.setEdgeLabelDualSideConfiguration(true);
renderer.setVertexLabelEnabledToShowIndex(true);
// Make a 500-by-500 pixel image with 100 pixels of padding
BufferedImage image = renderer.renderImage(500, 500, 100);
ImageIO.write(image, "PNG", new File("Example1.png");
The coordinate system for the image
The code for Example 1 creates a Delaunay triangulation with a coordinate domain in the range 0 to 1 along both the X and Y axes. To create the image, the RendererForTinInspection class constructs a coordinate transformation that converts Cartesian coordinates to pixel coordinates. In some cases, we might want to add our own mark up elements to an image. To do so, an application needs access to the coordinate transform and other metadata. That metadata is available using the alternate rendering method shown in the example below.
// Make a 500-by-500 pixel image with 100 pixels of padding
// but get an object that contains additional data related
// to the rendering surface.
RenderingSurfaceAid rsa = renderer.render(500, 500, 100);
BufferedImage image = rsa.getBufferedImage();
Graphics2D g2d = rsa.getGraphics2D();
AffineTransform af = rsa.getCartesianToPixelTransform();
// Use the affine transform to map a Cartesian coordinates
// to pixel coordinates, and draw a small X
// inside the triangle with vertices 0,4,3
Point2D cartesian = new Point2D.Double(0.25, 0.5);
Point2D pixel = new Point2D.Double();
af.transform(cartesian, pixel);
int iX = (int) pixel.getX();
int iY = (int) pixel.getY();
g2d.setColor(Color.red);
g2d.setStroke(new BasicStroke(3.0f));
g2d.drawLine(iX - 10, iY - 10, iX + 10, iY + 10);
g2d.drawLine(iX + 10, iY - 10, iX - 10, iY + 10);
The results are shown in the image below:
images/RendererForTinInspection/Example2.png
Having access to the affine transform would also be useful if we were creating an interactive application in which we wished to allow the user move his mouse over an image and see the Cartesian coordinates for his mouse-pointer position. The RenderingSurfaceAid class provides access to the appropriate transform as shown in the code snippet below:
AffineTransform pixelToCartesian = rsa.getPixelToCartesianTransform();
Overlays and underlays
While the example above used the AffineTransform to convert Cartesian coordinates to pixels for rendering, the overlays and underlays methods allow you to let the RendererForTinInspection class do the conversions for you. In a rendering operation, an underlay is an object that is drawn to the image before the TIN elements. So an underlay is covered by the TIN features. An overlay is an object that is rendered after the TIN elements. So an overlay covers the TIN features.
The example code below adds a pale orange triangle to the image as an underlay. Because the code does not do any rendering on its own, it does not require the graphics-related objects and metadata provided by the RenderingSurfaceAid. So the example uses the simpler renderImage() method to produce a BufferedImage instance.
// Construct the renderer and allow it to use the default options for drawing.
RendererForTinInspection renderer = new RendererForTinInspection(tin);
// Add a triangle feature to the "underlays" for the renderer
// using the Java standard Path2D class.
// The feature is specified using Cartesian coordinates.
// The renderer will handle converting these to pixel coordinates.
Path2D path2d = new Path2D.Double();
path2d.moveTo(0.0, 0.0); // Vertex 0
path2d.lineTo(0.5, 0.5); // Vertex 4
path2d.lineTo(0.0, 1.0); // Vertex 3;
path2d.closePath();
// Add the path2d as an underlay with a pale orange color.
// The arguments for addUnderlay are (Shape, Color, stroke, fill_option)
// Recall that Path2D is an implementation of Java's Shape interface.
Color triangleColor = new Color(255, 240, 196); // a pale orange
BasicStroke triangleStroke = new BasicStroke(1.0f);
renderer.addUnderlay(path2d, triangleColor, triangleStroke, true);
// Draw the image
BufferedImage image = renderer.renderImage(500, 500, 100);
The results are shown below. As noted, the TIN elements are drawn on top of the underlay feature.
images/RendererForTinInspection/Example3.png
One other thing that you may notice in the example image above is that only the even-numbered sides of the edges are labeled. That is the default value for the renderer. To enable odd-numbered labels, you need to call
renderer.setEdgeLabelDualSideConfiguration(true);
When drawing an image for a TIN that includes more than a small number of vertices, the resulting Delaunay triangulation will include so many edges that the labels can clutter the image to the point that it becomes unreadable. In such cases, suppressing the odd-numbered edges will reduce the clutter. Knowing that the triangules are always oriented counter clockwise and that the odd-numbered edges are always valued one greater than their partnered even-numbered edge, allows us to deduce the values for the missing labels.
Pixel coordinates and upside down images
Some data sources use pixel coordinates rather than following the conventions of Cartesian systems. Pixel coordinate define the upper-left corner of an image as the origin with y cooordinates increasing downward (rather than upward, as in Cartesian systems). For example, the font-based glyphs used in Java define letterforms based on pixel coordinates. So the upper vertices in the glyph have small y coordinates and the lower vertices have larger magnitude y coordinates. But, because the default behavior of RendererForTinInspection is to assune that the source data is in the form of Cartesian coordinates, pixel-based glyphs will be rendered upside down. To accommodate data based on vertices based on on the pixel convention, the RenderForTinInspection class implements a method that allows an application to instruct the renderer to treat the data as if it was based on pixel coordinates:
renderer.setCoordinateSystemIsPixels(true);
The results are shown in the figure below. Coordinates for vertices were obtained using Java's TextLayout class to find the outline for the uppercase letter "A". Java defines glyphs using pixel coordinates, so the default behavior for the renderer class was to draw the overall letterform upside down. When the system is specfied as pixels, the letter is shown with its standard orientation. Note that both pictures are based on the same set of vertices with the same (x, y) coordinates, but the geometry of the vertices is interpreted differently. The positions of the vertices are depicted differently in the two pictures, but the labeling for vertices is consistent for both.
images/RendererForTinInspection/Example5.png
Correlating images with other diagnostic information
Finally, we will conclude by correlating an image with text information obtained from the Tinfour API.
The image below comes from from Tinfour's AlphaShapeDemoImage class. The demonstration class uses the RendererForTinInspection class to produce its images. In this example, we show the layout for an TIN created from a letter A. In the demostration application, this TIN serves as the input into Tinfour's AlphaShape class. Although the results of the AlphaShape processing are not shown in this example image, interested readers may view the results by running the AlphaShapeDemoImage application. processing
images/RendererForTinInspection/Example4.png
In the image, note that vertices 58, 69, and 53 form a somewhat larger triangle in the lower part of the enclosed section of the letter (the "hole" section of the letter). The triangle is composed of edges 241, 304, and 337. Although the odd numbered edges are not labeled (again, to reduce clutter), we can deduce their values by remembering that triangles are oriented counterclockwise and that edge index labels are always drawn to the left of the direction of traversal.
We can obtain text for the edges using the following code:
for (IQuadEdge edge : tin.edges()) {
System.out.println(" " + edge.toString()); // print even numbered edges
System.out.println(" " + edge.getDual().toString()); // print odd numbered edges
}
This code produces the text below. For clarity, we've omitted all edges except those in the triangle of interest.
index reverse edge vertex A vertex B forward edge
241 337 <-- ( 58, 69) --> 304
304 241 <-- ( 69, 53) --> 337
337 304 <-- ( 53, 58) --> 241
Conclusion
The RendererForTinInspection class produces images that can be used with other Tinfour diagnostic features for development, inspection, and debugging of Tinfour applications. Assigning unique index values to input vertices provides even more accessibility in examining Tinfour results.