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
*
* 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
}
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
*
* 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)
if (bmiHeader->biBitCount == 24)
//Calculate the number of bytes per row in the BITMAP (to handle buffer space)
if (bytesPerRow % 4 != 0) { bytesPerRow = bytesPerRow + (4 - bytesPerRow%4); }
//Copy all pixel data, row by row
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;
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
if (bmiHeader->biBitCount == 32)
{ //ignore opacity unless this is an RGBA image
mPix->opacity = bmpPix[3];
}
mPix++;
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
Note: Only a member of this blog may post a comment.