Pixel Perfect Programming

In any project with multiple display devices that need to work together, an important part of the setup process is aligning and calibrating the displays to create a single canvas.  The best way to ensure that the virtual display space is coherent is to use display grids that highlight intersection points while adjusting physical display alignment and video signal positioning.  Grids are not only necessary while calibrating the final image, but are an invaluable template for talking about how to create media that will take advantage of a display surface, rather than be obstructed by it.  Good templates are important and need to be developed for an exact canvas.

Not too long ago, this usually meant going to Photoshop or Paint.NET and drawing.  First I would need to create a new document that is the total size of the canvas, taking into account the display pixels as well as any virtual pixels for mullions or gaps between the adjacent displays.  I wanted the grids to be precise, with each grid line exactly one pixel wide, so I would zoom all the way in to draw one pixel accurately.  Then I count down 96 pixels and draw another line, but it’s really hard to count to 96 on screen, especially when I have to scroll because everything is zoomed so big:

This takes a really long time, but I finally made a complex alignment grid with different colored layers for circles and diagonals, and straight grids.

A finished projection alignment grid

Then another project came along and I had to do it all over.  I tried scaling an old grid to match the new resolution, but then everything starts to alias and blur and you can’t actually tell where the precise lines are anymore.

I thought that there must be an easier way.  Without too much trouble I was able to leverage Windows Presentation Foundation to do the job.  I parametrized the important attributes of the grid and created Properties that I could set according to the project:

DisplaySizeX = 1920;
DisplaySizeY = 1080;
DisplayCountX = 4;
DisplayCountY = 6;
GridSpacingWidth = 128;
GridSpacingHeight = 128;
MullionX = 24;
MullionY = 120;
GenerateDiagonals = true;
GenerateCircles = true;

Using a Canvas inside a Viewbox, I was able to work with the native dimensions of my virtual canvas, but display an onscreen preview, like a thumbnail.  In XAML, this is all you need:

<Viewbox>
<Canvas Name="RenderCanvas" Background="Black" />
</Viewbox>

With the parameters set, WPF’s Canvas layout container allowed me to draw to screen with absolute coordinates using primitives like Line, Rectangle, and Ellipse (everything you need for a grid).  Here is a snippet of the loop used to populate the virtual canvas with display devices represented by rectangles:

for(int x = 0; x < DisplayCountX; x++) {
for(int y = 0; y < DisplayCountY; y++) {
Rectangle newRect = new Rectangle()
{
Width = DisplaySizeX,
Height = DisplaySizeY
};
newRect.SetValue(Canvas.LeftProperty,
(double)(x * DisplaySizeX) + (x * MullionX));
newRect.SetValue(Canvas.TopProperty,
(double)(y * DisplaySizeY) + (y * MullionY));
newRect.Stroke = new SolidColorBrush(Colors.Wheat);
newRect.Fill = new SolidColorBrush(Colors.DarkGray);
RenderCanvas.Children.Add(newRect);
}
}

Ultimately, I added the logic to draw all of the alignment elements I needed, including horizontal and vertical grid lines, circles, diagonals and some text to uniquely identify each screen in the array. It looks like this:

This isn’t as detailed as many alignment grids, but it is straight-forward to add more elements and colors using the same process.  The important thing is that all the time you spend making a useful template is reusable, because it isn’t locked to a specific dimension.

Finally, this is what makes the whole process worthwhile: In WPF, anything you can draw to screen, you can also render to an image file. It’s like taking a programmatic screenshot, but with much more control. Cribbing some code from Rick Strahl’s blog, I added a method to save a finished png file to disk:

private void SaveToBitmap(FrameworkElement surface, string filename) {
Transform xform = surface.LayoutTransform;
surface.LayoutTransform = null;
int width = (int)surface.Width;
int height = (int)surface.Height;
Size sSize = new Size(width, height);
surface.Measure(sSize);
surface.Arrange(new Rect(sSize));
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(width, height,
96, 96, PixelFormats.Pbgra32);
renderBitmap.Render(surface);
using(FileStream fStream = new FileStream(filename, FileMode.Create)) {
PngBitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(renderBitmap));
pngEncoder.Save(fStream);
}
surface.LayoutTransform = xform;
}

As a programmer, this saved me a lot of time. Instead of working with graphic design tools, I used the tools I was familiar with and took advantage of WPF’s support for media and imaging. I have attached a complete sample project of how this first part works.

But this is just the beginning of the things this is useful for: Using Data Binding, I exposed each parameter as an input field, so the user can interactively build a grid image by adjusting the values and see the thumbnail preview update live. Furthermore, instead of generating a single static image, this same process can be used to produce test video content by updating the RenderCanvas and saving a series of images as a frame sequence. This ends up being much easier than going to a timeline based non-linear editing station to create very simple graphics with a timecode display.