BITMAP optimization

Started by
8 comments, last by GameDev.net 18 years, 4 months ago
Hey Guys, if someone can help me, I'll be thankful. Well, I have a program which transfer the bitmap (DIB Section) from a MFC's CImage object to an array of integers, which is my pixels array. Here's the code: for (unsigned int y = 0; y < height; y++) { int line = y * width; for (unsigned int x = 0; x < width; x++) { // Extract color information for pixel and build an equivalent Java pixel // for storage in the Java-based integer array. // The CImage::GetPixel return an pixel in the following format: 0xAARRGGBB // But java.awt.Image needs a image in the follow format: 0xAABBGGRR // Where: // A = alpha, R=red, G=green, B=blue, // And the 'or' operator (|) is used to make the image opaque: A=0xFF // This code is very inefficient, try other approaches COLORREF pixel = (COLORREF)cImage.GetPixel(x,y); pixel = 0xff000000 | RGB(GetBValue(pixel), GetGValue(pixel), GetRValue(pixel)); // Store the pixel in the array at the appropriate index. pixelsArray[line + x] = (jint) pixel; } } As you can see, I get pixel by pixel the image from CImage object and put it in my pixel array. This code is extremelly slow and is consuming too much time and processing of my application. Here's the deal, I need performance in this specific code! First of all, my objective was make that code work, now I need to improve that! The most important thing is that I have to make a swith from RGB to BGR pixel format, because the object which will receive the filled array interpret the bytes/pixels in that format. Thanks
guilherme
Advertisement
Hello,

1) you can use either [code][/code] or [source lang="cpp"][/source] tags to write code that will be easier to read.

2) If you know how to gain access to the internal pointer of your CImage object, you can use this pointer to read your pixels. To be able to correctly read your bitmap data, you need to know:
  • the pointer to the beginning of the data
  • the height and the width of the image
  • the pitch (ie the distance in bytes from the beginning of one line to the beginning of the next line).

The pitch is equal to
pitch = ((width+3)&(~3))*(bpp)

bpp is bytes per pixel. The address of pixel (x, y) is then
byte *pixeladdr = imagebegin + y * pitch + x * bpp;

Of course, you can use the same kind of addressing that you are using with your java image buffer:
byte *linebegin = imagebegin + y;byte *p = linebegin;for (int x=0; x<width; x++) [  p = linebegin + (x * bpp);  // or even simple and faster, since you avoid the multiply:  // p += bpp; (for each iteration)}

The only problem for me is that I don't know the CImage lib very well (I did my own when I needed one) thus I don't know how to get the image pointer from your CImage instance :P

3) concerning your java image it can help you a lot if you define a union type like this:
union ArgbPixel{  unsigned int argb;  struct   {    unsigned char a, r, g, b;  }};

Your code can be greatly simplified:
  ArgbPixel *pixelArray;  ArgbPixel pixel;  // ...  pixel.argb = cImage.GetPixel(x,y);  std::swap(pixel.r, pixel.g);  pixelArray[line+x].argb = pixel.argb;


HTH,
I would recommend removing the GetPixel() function call if possible, That is probably your bottleneck.
eg:

void CopyImage( jint* pixelsArray, CImage& cImage )
{
int width( cImage.GetWidth() ), height( cImage.GetHeight() );

void CopyPixel( jint& out, const COLORREF& in )
{
// intel format
// in : 0xAARRGGBB
unsigned char* pucIn= (unsigned char*)( &in );
// out : 0xAABBGGRR
unsigned char* pucOut= (unsigned char*)( &out );
// Copy
pucOut[0] =pucIn[2]; //x )
{ // GetPixel (slowly but surely)
in =cImage.GetPixel( x, y );
// copy pixel
CopyPixel( *pOut, in );
// next pixel
++pOut;
++x;
}
}
}
}

I would recommend removing the GetPixel() function call if possible, That is probably your bottleneck.
eg:

void CopyImage( jint* pixelsArray, CImage& cImage ){	int width( cImage.GetWidth() ), height( cImage.GetHeight() );	void CopyPixel( jint& out, const COLORREF& in )	{		// intel format		// in : 0xAARRGGBB		unsigned char* pucIn= (unsigned char*)( &in );		// out : 0xAABBGGRR		unsigned char* pucOut= (unsigned char*)( &out );		// Copy		pucOut[0] =pucIn[2]; //< red		pucOut[1] =pucIn[1]; //< green		pucOut[2] =pucIn[0]; //< blue		// fake the alpha		pucOut[3] =0xFF;//pucIn[3];	};	// shortcut for true color images	int BitsPerPixel( cImage.GetBPP() );	if( 32 ==BitsPerPixel || 24 ==BitsPerPixel )	{	// assumes 32bit BMP is standard 8bit component bitfield style.		// convert BitsPerPixel to BytesPerPixel.		BitsPerPixel /=8;		// copy image pixel per pixel		COLORREF* pIn;		jint* pOut;		unsigned int x, y( height );		while( y-- )		{	// setup to copy a row			pIn =(COLORREF*)( cImage.GetPixelAddress( 0, y ) );			pOut =&pixelsArray[y *width];			x =width;			while( x-- )			{	// copy pixel				CopyPixel( *pOut, *pIn );				// next pixel				++pOut;				pIn =(COLORREF*)( &((char*)pIn)[BitsPerPixel] );			}		}	}	else	{	// safe but slow version for other bmp types		COLORREF in;		jint* pOut;		unsigned int x, y( height );		while( y-- )		{	// setup to copy a row			pOut =&pixelsArray[y *width];			x =0;			while( width >x )			{	// GetPixel (slowly but surely)				in =cImage.GetPixel( x, y );				// copy pixel				CopyPixel( *pOut, in );				// next pixel				++pOut;				++x;			}		}	}}

Thanks guys, your codes helped me a lot.

The code is at least ten times faster, my benchmark indicates a gain ratio of 95 percent.
guilherme
Another thing, someone knows how could I avoid the following multiplication:

line = y * width;

where width is constant through the loop and y variable?

(x << 5) evaluates to the same value as (x * 32), but is very faster! But it only works on number of power of two, like 2, 4, 8, 16, 32, 64, etc. How could I do the same with numbers like 320 and 240, which aren't (at all) multiples of two.

Another thing, anyone here already work with JAI or know something related?
guilherme
Yep, do not multiply. I couldn't be bothered to improve that.
To move the multiply out of the loop realise the following:

y =height;
while( y-- )
{ // calc line
line =y*width;
}

is equal to:

y =height;
line =height*width;
while( y-- )
{ // calc line
line -=width;
}

But you should not be saving much moving that out of the row loop. Concentrate on the inner per pixel loop.
Ok, you're right, although I gained about 100 ms doing that, every impovement is worth.

My code became this:

unsigned int javaLine = 0;unsigned int cppLine = 0;for (unsigned int y = 0; y < height; y++) {  for (unsigned int x = 0; x < width; x++) {    int index = cppLine + (x << shiftBPP); // = cppLine + (x * bytesPerPixel)    // Notice that I switch from RGB to BGR through index changing    unsigned int pixel = 0xff000000 	| (bitmap[index+2] << 16)					| (bitmap[index+1] << 8) 					| bitmap[index];    pixelsArray[javaLine + x] = (jint) pixel;  }  javaLine += width;  cppLine += pitch;}
guilherme
Maybe I did a mistake by specifying that cppLine is unsigned, because my pitch could be negative, but It worked anyway.

Do you see anithing that can be improved in that code?
guilherme
You are not doing any conversion (done here)...
I assume shiftBPP is bytes per pixel...

unsigned char *pPixOut = (unsigned char*)&pixelsArray[0];unsigned char *pRowIn = (unsigned char*)&bitmap[0], *pPixIn;int x;while( height-- ) {  // setup row  pPixIn =pRowIn;  x =width;  while( x-- ) {    // copy pixel, switching from 8bit ARGB to ABGR (intel)    *(pPixOut++) =pPixIn[2];    *(pPixOut++) =pPixIn[1];    *(pPixOut++) =pPixIn[0];    *(pPixOut++) =0xFF;//pPixIn[3];    // next pixel    pPixIn +=shiftBPP;  }  // next row  pRowIn +=pitch;}

This topic is closed to new replies.

Advertisement