Sunday, January 9, 2011

Converting an uncompressed BITMAP to a Magick++ Image

One problem that you may encounter while working with the Magick++ API (an API for both GraphicsMagick and ImageMagick) is trying to convert an uncompressed Windows BITMAP to a Magick++ image.


1. Simple approach: read()
The easiest approach is to use the Magick++ read() function, which is an abstraction of the lower-level ConstituteImage function (part of the MagickCore API).
Here is the code:


#include "Magick++.h"
#include <assert.h>
//! Transfers the given /uncompressed/ Windows BITMAP to a Magick++ Image
/*!
* bmiHeader specifies information about the bitmap image (if necessary with color table entries at end)
* bPixels contains the pixel buffer of the bitmap.
* mImage is a pointer to the Magick++ image that will get the bitmap data
*
* A Magick++ exception may be thrown
*
* By Amil Khanzada: http://amilkhanzada.blogspot.com/2011/01/copying-uncompressed-bitmap-to-magick.html
*/
void BitmapToMagick(const BITMAPINFOHEADER* bmiHeader, const BYTE* bPixels, Magick::Image* magickImage)
{
      assert(bmiHeader->biCompression == BI_RGB); //the bitmap MUST be uncompressed for this function to work

      if (bmiHeader->biBitCount == 24)
      {    //This is a BGR bitmap (RGB24). Note that abs() is used because 'bmiHeader->biHeight' may be negative
          magickImage->read(bmiHeader->biWidth, abs(bmiHeader->biHeight), "BGR", Magick::CharPixel, bPixels);
      }
      else if (bmiHeader->biBitCount == 32)
      {   //This is a BGRA bitmap (RGB32)
          magickImage->read(bmiHeader->biWidth, abs(bmiHeader->biHeight), "BGRA", Magick::CharPixel, bPixels);
      }

      if (bmiHeader->biHeight > 0)
      {   //If the BITMAP is bottom-up (the usual scenario), we must flip the image
          magickImage->flip();
      }
}

Inefficiencies of the simple approach
While being quite simple, there are two parts of this function that can be optimized.
The first and most obvious inefficiency is that, in most cases (most BITMAPs I have encountered in these situations are bottom-up), we need to flip the image at the end. Digging into the Magick++ code, I found this flip() allocates a new image with the flipped contents and replaces the current image with this new image. This re-allocation alone is an inefficiency (memcpy() and free() are called somewhere in the Magick++ code).
The second, less obvious, inefficiency is that the call to read() implicitly causes a new Magick++ image to be allocated in memory behind the scenes, which will then replace magickImage in memory. In many cases this will be necessary. However, if magickImage is an image with the same dimensions as the given BITMAP, we can actually eliminate this extra re-allocation.
In total, the approach above requires 2 extra memory allocation operations and 2 extra memory de-allocation operations.

2. Efficient approach: manually filling the pixel buffer
To remove the need for these extra memory allocations, we can manually access the pixel buffer of the Magick++ image. To eliminate first inefficiency, we simply copy the rows in reverse order if necessary. To eliminate the second inefficiency, we can overwrite the pixels of the old image.

Here is the code for RGB24 BITMAPs (3 bytes per pixel):

#include "Magick++.h"
#include <assert.h>
#include "omp.h"
//! Transfers the given /uncompressed/, /BGR/ Windows BITMAP to a Magick++ Image
/*!
* bmiHeader specifies information about the bitmap image (if necessary with color table entries at end)
* bPixels contains the pixel buffer of the bitmap. For maximal efficiency, it is assumed that no other process modifies these pixels while this function is running
* mImage is a pointer to the Magick++ image that will get the bitmap data. It is assumed that no other process can modify the image while this function is running
*
*
* A Magick++ exception may be thrown
*
* By Amil Khanzada: http://amilkhanzada.blogspot.com/2011/01/copying-uncompressed-bitmap-to-magick.html
*/
void BitmapToMagick(const BITMAPINFOHEADER* bmiHeader, const BYTE* restrict pixels, Magick::Image* magickImage)
{
      assert(bmiHeader->biCompression == BI_RGB); //must be uncompressed
      //Ensure that the Magick++ image is already allocated with the same dimensions as the BITMAP
      assert(bmiHeader->biWidth == magickImage->columns());
      assert(abs(bmiHeader->biHeight) == magickImage->rows());

      //Prepare the image so that we can modify the pixels directly
      magickImage->modifyImage();
        if (bmiHeader->biBitCount == 24)
           magickImage->type(MagickLib::TrueColorType);
       else
           magickImage->type(MagickLib::TrueColorMatteType);

      //Calculate the number of bytes per row in the BITMAP (to handle buffer space)
      register unsigned int bytesPerRow = bmiHeader->biWidth * bmiHeader->biBitCount/8;
      if (bytesPerRow % 4 != 0) { bytesPerRow = bytesPerRow + (4 - bytesPerRow%4); }

      //Copy all pixel data, row by row
      #pragma omp parallel for //this is parallelizable with OpenMP
      for (int r = 0; r < int(magickImage->rows()); r++)
      {
            //Get the start of the Magick pixel buffer /for this row/
            register MagickLib::PixelPacket* restrict mPix = magickImage->setPixels(0, r, magickImage->columns(), 1);
            //The start of the BITMAP pixel buffer /for this row/ (if it is a bottom-up DIB, we reverse the order)
            int bRowNum = (bmiHeader->biHeight > 0) ? (magickImage->rows() - r - 1) : r;
            register const BYTE* restrict bmpPix = pixels + bRowNum*bytesPerRow;

            for (unsigned int c = 0; c < magickImage->columns(); c++)
            {      //Copy a row of BITMAP pixels to a row of Magick++ pixels
                  //Copy over blue, green, and red (pixels are stored as BGR in the bitmap)
                  mPix->blue = bmpPix[0];
                  mPix->green = bmpPix[1];
                  mPix->red = bmpPix[2];
                        if (bmiHeader->biBitCount == 32)
                        {     //ignore opacity unless this is an RGBA image
                       mPix->opacity = bmpPix[3];
                        }

                  mPix++;
                  bmpPix += bmiHeader->biBitCount/8; //the number of bytes per pixel
            }

           //Ensure that the pixels are updated in the image
           magickImage->syncPixels();
      }

      return;
}

Some things to note:
-I used the register and restrict keywords to help the compiler optimize the code
-I used OpenMP's #pragma omp for directive so that multiple cores, if available can assist with the computation. Unfortunately, as this operation is mostly memory-bound, the speedup is very limited, but noticeable (I got around a 5% speed up on my dual-core processor). You may want to eliminate this directive to prevent your application from possibly "hogging" the CPU and slowing down other applications
-bytesPerRow is needed to handle the buffer space used by BITMAPs


3. Specialized RGB32 approach
There is actually another optimization we can make if we are copying RGB32 BITMAPs using the Q8 version of GraphicsMagick (or ImageMagick). It turns out that the PixelPacket structure used to store a pixel's data in Magick++ Q8 version is actually identical to the structure used to store a pixel in a BITMAP. Furthermore, for both RGBA BITMAPs and Magick++ images, rows will never include any buffer space, so we can perform a direct memory copy. In other words, the complete memory layout of the BITMAP and Magick++ image are identical.
Without further ado, here is the code:


void BitmapBGRAToMagick(const BITMAPINFOHEADER* bmih, const BYTE* restrict bPixels, Magick::Image* mImage)
{
     assert(bmih->biCompression == BI_RGB); //must be uncompressed
     assert(bmih->biBitCount == 32); //BGRA uses 4 bytes per pixel


     if (! (mImage->columns() == unsigned int(bmih->biWidth) &&
            mImage->rows() == unsigned int(abs(bmih->biHeight))))
     { //The Magick image is not identical in dimensions, so we are forced to allocate a new image
     Magick::Color blank(0, 0, 0); //a blank color
     *mImage = Magick::Image(Magick::Geometry(bmih->biWidth, abs(bmih->biHeight)), blank);
     }

     //Prepare the image so that we can modify the pixels directly
     mImage->modifyImage();
     mImage->type(MagickLib::TrueColorMatteType); //set the type to true color with opacity
     //Get a pointer to the pixel buffer
     MagickLib::PixelPacket* restrict mPixels = mImage->setPixels(0, 0,      bmih->biWidth, bmih->biHeight);
     //Simply copy the BITMAP's data into the pixel buffer (the structures are identical!)
     memcpy((void*)mPixels, (const void*)bPixels, bmih->biSizeImage);

     //Ensure that the pixels are updated in the image
     mImage->syncPixels();

     return;
}

4. Compressed BITMAPs
If you are interested in transferring compressed BITMAPs to Magick++ objects, please see this guide.


By the way, feel free to incorporate these code snippets into your applications (I release them to the public domain). If you have any questions, feel free to post a comment.

Special thanks to Bob Friesenhahn for his guidance when I was struggling with this problem.

No comments:

Post a Comment