This page provides an overview of the internal design of paintlib. It is mainly intended for people who need to extend or change the library, for instance by adding custom data sources, custom bitmap classes or new decoders.
To decode a file, 3 objects interact: The decoder, the bitmap object, and a data source. In these objects, the work is divided between the base class and a derived class in most cases. As an UML class diagram, paintlib looks like this:
Decoder Implementation
The decoder base class CPicDecoder handles trace levels and provides a standard interface for errors. It also provides routines which read 16- and 32- bit integers from the data source in the correct byte order. The main decoding routines MakeBmpFromFile and MakeBmp are in this class. They set up the data source and call DoDecode(), a method which is implemented in each derived class. Most derived classes are decoders for a specific file format. The exception is CAnyPicDecoder, which will be explained later.
The DoDecode()-routines handle the actual decoding of the data. In the case of CPNGDecoder, CTIFFDecoder and CJPEGDecoder, they just call the appropriate routines in the other libraries. In other format-specific decoder classes, the work is done in the class itself, calling base class routines when necessary. The data is placed into the bitmap by calling GetLineArray() and directly writing the lines into the memory regions returned.
CAnyPicDecoder creates a format-specific decoder of each known type on construction. DoDecode() is not implemented in this class. Instead, MakeBmp() is overridden. It auto-detects the file type and calls the MakeBmp() method of the decoder for the file format.
If you wish to implement your own decoder, you can use CTGADecoder as a model. TGA is a simple file format and the code is easy to read. To add auto-detection, you need to add a new decoder member to CAnyPicDecoder and modify the autodetection code in this class. You also need to handle errors like the existing decoders do (via CPicDecoder::raiseError()). In particular, if you're interfacing to an existing third-party decoder, you will probably need to handle their errors and call raiseError() whenever the decoder signals an error.
Custom Data Sources
The base class CDataSource handles data which is already in memory. CFileSource simply opens a file and makes it available as a memory block. If memory-mapped files are available, a mapping is created. If not, all data is read into memory on open. CResourceSource does the same for windows resources. A new data source class needs to read the data into memory on open and free the memory when Close() is called.
Custom Bitmap Classes
You can derive custom bitmap classes from CBmp to define your own storage format for bitmap data. If you're porting paintlib to a different OS, it makes sense to define a new bitmap class which stores data in a format that is native to the OS. There are several pure virtual functions in CBmp which must be defined in a derived class. In addition, there are member variables which must be set correctly. Member pointers must always point to a valid memory location or be NULL.
To change the byte ordering within a 32-bit Pixel, you need to change the RGBA_RED, RGBA_GREEN, RGBA_BLUE, and RGBA_ALPHA constants. See the CBmp reference for more information on these constants.
Bitmap creation is a two-step process. The first step is the constructor call, and the second step is the call to Create() or the assignment operator. During construction, a derived class creates a small empty bitmap. (This can't be done in the base class because calling virtual functions in a constructor is not allowed. Ask why in comp.lang.c++.moderated and you'll get a lot of good answers ;-). During Create(), the object is initialized to contain a bitmap of a specified size. This two-step process is necessary because constructors have no simple way of signalling failure - for instance due to low memory conditions - to the calling program. It also makes it possible to call Create() without knowing to which subclass of CBmp the bitmap actually belongs. Create() and CreateCopy() are implemented in CBmp itself and call several pure virtual functions that must be implemented:
void CXxxBmp::internalCreate ( LONG Width, LONG Height, WORD BitsPerPixel, BOOL bAlphaChannel )
Create an empty bitmap with allocated but uninitialized bits (m_pBits). If necessary, this function also needs to allocate a color table (m_pClrTab). m_pClrTab must be NULL if no color table is allocated. It must call the base class method initLocals() to initialize the member variables. The routine can assume that no memory for members is allocated before it is called.
void CXxxBmp::initLineArray ()
Allocate and initialize the array of pointers to lines (m_pLineArray). This array contains one pointer per line in the bitmap and is returned by GetLineArray().
void CXxxBmp::freeMembers ()
Free all memory allocated. You should also set the pointers to the memory regions to NULL.
In addition, two public functions must be implemented:
long CXxxBmp::GetMemUsed ()
Return the total amount of memory used by the object in bytes.
long CXxxBmp::GetBytesPerLine ()
Return the number of bytes needed per line, including pad bytes if necessary.
Bitmap classes are meant to be used in a lot of different situations. Use ASSERT statements wherever you can to provide parameter validation.