You’re Not My Type!
C++ is a strongly typed language. This means that each data element in a C++ program has a specific internal format defined by the programmer. The C++ programmer can define the element size of every integer, be it 8-bit, 16-bit, 32-bit, 64-bit, and whether or not the integer can be signed or unsigned. Other loosely typed languages don’t allow the programmer to have such fine-tune control. While the number 6 is a signed integer of a size determined by the language compiler (usually 32-bit for most current desktop compilers), in other languages it is just a number.
Strongly typed languages allow the programmer to fine-tune the performance and data footprint properties of the application. As an example, let’s compare the data footprint of a 640×480 RGB image. If the color components are represented by 8-bit integers, and each pixel is represented by 3 color components, red, green and blue in this case, then that image will take up 640 x 480 x 3 x 1 = 921,600 bytes of data. If we use 16-bit integers to represent the color components, then the data footprint becomes 1,843,200 bytes. If we use the default integer size of 32-bits, the footprint becomes 3,686,400 bytes. 1 megabyte of RAM vs 4 megabytes is a sizable difference.
While most image file formats support 8-bit color components, some high-performance libraries and custom formats can support 16-bit color components. Furthermore, some colorspaces are better represented with floating point color components instead of integer values. The smallest floating point data type in C++ is a 32-bit quantity. While these different color component data types allow a greater precision in image manipulation, it also adverse affects the image’s data footprint.
While such fine tuning is the hallmark of the C++ programmer (and other strongly typed languages as well), it also raises some programming headaches. For instance, with three different types of color components, how do we write color component algorithms when all data elements have to be distinctly typed? Or, assuming we want to write a function called ConvertColorspace which would take a pointer to an array of color components, how do we write it? The straightforward version:
bool ConvertColorspace(uint8*)
suffers from not supporting the other data types. We could do the following:
bool ConvertColorspace(uint8*)
bool ConvertColorspace(uint16*)
bool ConvertColorspace(float*)
but this requires us to write and maintain parallel algorithms. Any bug fix discovered in one would need to be fixed in the other two. C++ does support code templates, such as:
template <class T> bool ConvertColorspace(T*)
where a C++ programmer can write and maintain one set of algorithms that can operate on different data types. However, while templates are great at defining generic data constructs that can operate on any data, such as stacks and queues, we not only want to limit the algorithms to the three color component data types, but we want to make sure that all three get defined. Furthermore, templates are essentially code macros. How and where they get instantiated is determined by the compiler, which makes it somewhat difficult to define shared library entry points. This mechanism takes some of the fine tuning control away from the programmer and puts it in the hands of the compiler.
My current solution is to define a new data type called ColorComponentPtr. Tucked inside the ColorComponentPtr is the following construct:
uint16 type;
union { uint8* c8; uint16* c16, float* cr; }
This construct ensures that there is only one source of color component based algorithms and gives the programmer full control over the fine tuning aspect of the code. Coupled with well-written constructors, it’s not a perfect solution, but a usable one.