I was asked recently to do a relatively simple task: read a TGA file, reduce the size and write it back out. I felt that was too simple so I decided to go a little overboard and also to release my code to the world. A nice bonus is that I have the start of a new tool.

The task was set to only take a few hours.  It ended up taking 24 hours and I decided to keep track of the process and document it.

 

Brewing Ideas

The first thing I did was to lookup the TGA format. My first search of "TGA Format" landed me at http://local.wasp.uwa.edu.au/~pbourke/dataformats/tga/, which has a nice overview and sample code but was a bit limited. I ended up having to go straight to the actual format specification linked through the Wikipedia entry to nail down the details. I didn't jump right into the work. I wanted to let ideas form in my mind of everything I wanted to accomplish. I find that helps a lot as a general rule, to start thinking about a task a few days before you start. I feel this helps me to come up with multiple solutions and to have a really good idea of how I'm going to solve a particular problem before I even start.

A program I've used in the past to do image processing is ReaConverter. It was very useful for generating thumbnails from hundreds of images for a site I was working on. Another tool that I'm sure most game developers are familiar with is Perforce. Why am I mentioning these? They were my inspiration for what I wanted to accomplish with this tool: a batch image processing tool with a strong commandline interface.

 

Setting up the code - 2 Hours

The first coding task was to setup the project and the framework I would use. My first design of classes looked like this:

  • Image - The data container for any loaded image.
  • ImageLoader - The base class of all supported formats, used to load images.
  • ImageOperation - The base class for all operations. Will create a new image, not modify an existing one.

 

Commandline Processing - 5.5 Hours

After setting up the core classes I started work on command line processing. Relatively straight forward work but I wanted a clean way to parse information into the formats and operations and to expose help information but I didn't want to pollute the classes themselves. I decided I wanted to have a clean puplic lib style of code so I created a set of intermidiate classes to do all the command line work that would be hidden in the private code:

  • ImageCommand - Base classes used for discovery and setup of anything exposed to the command line.
  • ImageLoaderCmd, ImageOperationCmd - These classes gave categories to the ImageCommand so I could search for one or the other specifically.

With these worker classes in place commandline processing became a simple for loop of searching for multiple operations a single loader, and just a few constants like 'help' and global settings. Towards the end of setting up all the commandline processing work I ended up with two new worker objects:

ImageProcessor

This class would be built up with all the information required to discover and open the files, process them, and save them in either their original format or a new format with certain modification to the files names.

FileIterator

This object simplifies the task of searching for files. Given a dos style file specification (*.tga), it can iterate all files in a directory and has the option for searching in sub directories as well. It is also used to find the file specification in the command line.

 

Time for some real work - 6 Hours

Next step was to add the TGA loader and resize operation. I decided to go with the resize since it should be relatively straightforward. It took about 2 hours, but a good chunk of that time ended up going to ironing out little details in how I handled Image data. The resize operation can asjust the size of an image to a direct size, by a certain amount or by a percentage and has 2 blending modes available:

  1. Point: This blending mode finds the nearest pixel to the relative position in the original image. Gives a nice pixelated look when increasing the size of an image.
  2. Blend: This mode will find the position of a pixel in the original image and sample neighbouring pixels by an amount depending on the scaling. The sample pixels are averaged out into the final value.

TGA Loading and saving took about 4 hours. I had to go into the specifications a few times to figure out little details about how the Run Length Encoding (RLE) works. I eventually found a clean way to load and convert all supported TGA formats (Greyscale, 16,24,32 bit, with and without RLE). Template functions can really help to both clean up code and remove branches from loops without having to copy paste the loop multiple times. Templates are especially useful since the loading and saving of RLE and Normal TGA files are completely different.

 

Compiling - 2 hours

 

Compiling gets its own section because up until this point I've only been coding. 13.5 hours and I haven't hit F7 or Ctrl+B once. Needless to say I had a few compiler errors. I guess it's like writing an article. I try to get all my ideas on paper before trying to edit any one section. Only once I have the whole picture in place I can start verifying it as a whole. This makes sure that I can keep a constant stream of ideas flowing without having to break the flow and focus on details every so often.

 

Debugging - 5 hours

This is where things got fun. After such a long programming session (it was broken up by a few days, not 13 hours straight) I generally like to take the debugging approach of walking through my code. So I setup the required commandline arguments, got a test picture and started walking. The first hour or so was just making sure all the text parsing was working and the ImageProcessor was being setup properly. It took a good few hours to work out the kinks in TGA serialization code. It's not obvious in the TGA specs things like: "RLE has a maximum length of 128 character and stops at the end of scan lines". There's a lot of little details to work out that can only be found by stepping through and looking at the data. Having reference data can also be a life saver. Simply creating a RLE TGA in some other program and looking at the data in Visual Studio's binary editor can reveal a lot.

The last couple hours was spent making sure the resize worked (why does conversion to 16 bit turn my orange into blue?), and verifying that all the different options related to how the file names are generated was working correctly.

 

Testing - 1 hours

 

I'm a big fan of unit testing. But testing something like image conversions it a bit hard for a unit test so I did the next best thing. I setup a dos .bat file to open a single TGA file and resave it using as many different options as I can. This can be seen in the Test folder of attached code. Once you compile the program just run the .bat file and you will end up with a bunch of images in the results folder. They are saved in different formats and scaled large and small. Once all my result images looked perfect I considered it a job well done.

 

Cleanup - 3.5 hours

I like to ensure I have clean code that is easy to read and understand. I made sure the project and file setup makes sense, added comments where I think it's necessary, and made sure to have some documentation. I also spent time making sure that the command line help looks good using perforce as a direct example.

So here it is. An image processing framework that supports one image format and one operation. It's released under the Beerware license like everything else on my site so feel free to do whatever you like with it.

 

ImageProcessor

- Total time to develop 24 hours.

Note: There seems to be a weird problem with the conversion from 16bit color to 32bit color. It works fine on my computers at home but gives problems on my computer at work. I'll post an update when I know what's wrong.

Solution: It seems that there's a difference in the loaders between Paint.NET and Photoshop. The code available on the site creates 16bit TGA files that can be opened properly by Paint.net. To open them properly in Photoshop the RGB order in the Color16 struct needs to be inverted. I confirmed using Quicktime picture viewer that Photoshop has the correct loader and I've updated the code to write the correct format.

Update: New version available.  Fixed File Iterator to be properly recursive.  Added Folder Iterator as separate class. Added simple testing system used for File/Folder Iterators.  Fixed the debugging of lib, some classes could not be expanded because they were only included in the Precompiled Header of the lib and not in the exe project (bug in visual studio).