paintlib - C++ - Tutorial

You'll find a short tutorial on how to include paintlib functions in your programs on this page. If you haven't gotten paintlib and the sample programs compiled yet, read the installing page first.

[Note for MSVC users: The place most people seem to get stuck in first is in the project settings. The best thing to do when you get obscure compiler and linker errors is probably to compare the project settings of your project with the settings in the sample projects. In particular, make sure paintlib and your program are being built with the same MFC version (static or dynamic? multi- or single-threaded?) and use the same alignment settings.]

Decoding an Image

In the simplest case, what you need to do to use paintlib is this:

    #include "anydec.h"
    #include "winbmp.h"

    // Create a decoder.
    PLAnyPicDecoder Decoder;

    // Create a bitmap object.
    PLWinBmp Bmp;

    // Tell the decoder to fill
    // the bitmap object with data.
    Decoder.MakeBmpFromFile ("foo.jpg", &Bmp);

    // Do something with the bitmap.
    Bmp.Draw (pdc, 0, 0);

These four lines are enough to load a bitmap in any of the supported formats from disk and draw it to a windows device context. The MakeBmpFromFile() call auto-detects the file type and gives you a 32 bpp bitmap containing the image stored in the file - including alpha channel if there is one. (The example is for windows because there's an easy "Do something with the bitmap" step in this case. The fragment also crashes if an error occurs while decoding. I'll get to the rest in a minute.)

Of course, you can use one decoder to decode several bitmap objects. It makes sense to create a decoder during program initialization and use it until the program terminates. The decoder class is not thread-safe, so if several threads need to decode images, it's best to create a decoder in each thread.

When you create the bitmap object, you're also selecting the storage format for the bitmap. Let me explain this. The decoder takes a PLBmp as parameter to MakeBmpFromFile. Because PLBmp is an abstract base class, there can be no objects of the class. It exists mainly to define an interface for derived classes. Whenever it needs to store data, it asks the bitmap object for the address to place it. So if you create a PLWinBmp and pass it to MakeBmpFromFile, the storage format is a windows DIB (Device Independent Bitmap). We could also have passed a PLAnyBmp and stored the data in an OS-independent format. There's a third bitmap class - PLDIBSection - for use when you want to use a windows DIB section and if you're using DirectX, you can use PLDDrawBmp to load bitmaps directly into DirectDraw surfaces.

Needless to say that you can also define your own bitmap storage formats by deriving from PLBmp. (If all this doesn't make sense, ignore what I've just said. Just remember that you can safely pass PLWinBmps, PLAnyBmps, or objects of any other PLBmp-derived class to MakeBmpFromFile() and the bitmap data will arrive in the object in the format defined by the class. ;-)

Manipulating bitmaps

Once you've decoded the bitmaps, you'll want to do something with them. If you're coding for windows and using PLWinBmp, you can do whatever DIBs (Device Independent Bitmaps) are capable of. PLWinBmp defines methods to draw the bitmap to a device context and to copy it to the clipboard. You can also load PLWinBmps from resources. The class PLDIBSection derives from PLWinBmp and has the ability to return a GDI bitmap handle in addition to the features of PLWinBmp. This means you can use GDI drawing functions to change the contents of a PLDIBSection.

To manipulate the data in a PLBmp, paintlib uses filter classes::

      PLFilterFill Filter (color);
      Filter.ApplyInPlace (pBmp);   // Replace with Apply(pSrc, pDst) if you want to
                                    // keep the original bitmap.

or:

      pBmp->ApplyFilter (CFilterFill (color));

Using filters allows you to do things like dynamically creating arrays of filters that you then apply to many bitmaps in succession etc. It also allows for simple undo/redo mechanisms: Keep all the filters you've applied to a bitmap in an array. When you need to undo, just reapply all but the last filter to the original image. (The test class PLTester takes advantage of this design at several points. Have a look.)

If you're interested in manipulating the data yourself, this is what you can do:

    // Assuming a 32 bpp bitmap.

    PLPixel32 ** pLineArray;
    int     x,y;
    PLPixel32  * pLine;

    // Get the addresses of the bitmap lines
    pLineArray = pBmp->GetLineArray32();

    // Iterate through the lines
    for (y=0; y<pBmp->GetHeight(); y++)
    {
      pLine = pLineArray[y];

      // Iterate through the pixels
      for (x=0; x<pBmp->GetWidth(); x++)
      {
        // Set the red intensity to maximum.
        // (replace this with what you really
        // want to do...)
        pLine[x].SetR(0xFFh);
      }
    }

pBmp points to an object of a PLBmp-derived class. pBmp->GetLineArray32() returns an array of pointers to the bitmap lines. Once you have the array, you can iterate through the lines at maximum speed. The class PLPixel32 allows basic operations on pixels like setting and getting components, testing for equality and assigning one pixel to another. There are similar classes for 24 and 8 bpp pixels and corresponding GetLineArray() functions. The order of the components of each pixel in 32 and 24 bpp mode is defined by the constants PL_RGBA_BLUE, PL_RGBA_GREEN, PL_RGBA_RED, and PL_RGBA_ALPHA. 32 bpp bitmaps may or may not have valid data in the alpha channel. You can check this by calling pBmp->HasAlpha() and change the value by calling SetAlphaChannel() or SetHasAlpha(). You can copy bitmaps and convert them to different bit depths by calling CreateCopy() or the assignment operator. In 8 bpp mode, the color of each pixel is determined by the palette of the bitmap. The functions SetPalette(), SetGrayPalette() and SetPaletteEntry() allow you to set the color table.

There are several additional functions to get information about the bitmap: GetWidth(), GetHeight(), GetBitsPerPixel(), GetBytesPerLine(), GetMemUsed(), GetNumColors() and GetResolution(). The resolution (in dpi) can also be set using SetResolution() - this doesn't resample the image, it just allows programs to be smart about how big the image is supposed to be displayed or printed.

Note that implementing a filter isn't much more work than manipulating the data directly. What you need to do is create a new class derived from PLFilter, place the actual manipulation code in Apply() and code a constructor. The constructor takes any parameters that your algorithm needs and places them in member variables that Apply() can use. In general, the destructor will be empty.

Saving an image

Easy:

    PLJPEGEncoder Encoder;
    Encoder.MakeFileFromBmp(sFName, pBmp);

PLJPEGEncoder also has some functions for setting image quality that you can call before calling MakeFileFromBmp().

You can also use a PLPNGEncoder if you want to save in png format, a PLBMPEncoder to save in windows bmp format or a PLTIFFEncoder if you want to save in tiff format. PLTIFFEncoderEx provides advanced support for writing your own tiff tags.

Error Handling and Debugging

If you defined _DEBUG when you built the library, it was compiled to contain trace statements and asserts to help you debugging. In addition, debug info is included and optimizations are disabled. Trace statements give diagnostic output to a suitable medium. For MSVC, output is sent either to the MSVC debug log or to a file. Other systems send output to stderr or a file. The amount of information output and the destination can be changed by calling PLAnyPicDecoder::SetTraceConfig (Level, pszFName).

In addition to enabling trace output, defining _DEBUG also enables internal sanity checks and parameter validation via asserts. If an assertion fails, the program will stop execution and print out information about the file and line number causing the failure. There are comments at these places in the code to help you sort things out.

Errors which you can potentially recover from are handled by the C++ exception mechanism. Here is sample code including error-handling:

    try
    {
      Decoder.MakeBmpFromFile ("foo.jpg",
                               &Bmp);
    }
    catch (PLTextException e)
    {
      cerr << "Error " << e.GetCode() << " decoding foo.jpg: " << e << endl;
    }

If no error occurs, this code just amounts to one call to MakeBmpFromFile(). If something goes wrong, execution jumps to the catch block. In this block, you have a variable of type PLTextException available which provides you with an error code and a descriptive error string. The error codes are explained in the reference section.

Windows-specific Stuff

If you've got a bitmap handle and a corresponding device context and you need a PLWinBmp, do this:

    PLWinBmp * pBmp;
    pBmp->Create (Width, Height, bpp, FALSE);
    GetDIBits( hdc, hBmp, 0, (WORD) pBmp->GetHeight(),
               pBmp->GetBits(), pBmp->GetBMI(),
               DIB_RGB_COLORS);

Loading a picture (in any format) from a resource is also possible. Just place a resource of type RCDATA into the .rc file of the program and load the resource as a picture by calling

    MakeBmpFromResource(IDR_PICTURE, &Bmp)

The line in the .rc file should look like this:

    IDR_PICTURE  RCDATA  DISCARDABLE  "picture.jpg"

You will also need to define IDR_PICTURE in resource.h (or wherever resource IDs are defined in your project). Under MSVC, adding a user-defined resource to a project is a bit weird. What you need to do is:

1. Insert the file "res\projektname.rc2" in "View/Resource Includes".
2. Insert the resource reference in projectname.rc2 (the file already exists).
3. Add the resource ID using "View/Resource Symbols" to resource.h.

For a real-life example of all this, see the sample programs.