Solved

help creating monochrome bitmap from existing bitmap

Posted on 2007-12-04
27
730 Views
Last Modified: 2010-04-21
Hi

I'm trying to wrtie a method that will highlight an images. i.e. set al the pixels of a set rgb to be black and all others to be white. I then want to return this as HBITMAP.

its almost working but i can't work out where i'm going wrong and hope you can help me.

i create the bitmap like so

BITMAPFILEHEADER* bfh = (BITMAPFILEHEADER *) malloc(sizeof(BITMAPFILEHEADER));
bfh->bfType='B'+('M'<<8);
bfh->bfOffBits= sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) + 8; //offset to bitmap pixel data + 8 is for the two colour bytes
bfh->bfSize= paddedSize + bfh->bfOffBits;
bfh->bfReserved1=0;
bfh->bfReserved2=0;

BITMAPINFOHEADER* Bmi      = (BITMAPINFOHEADER *) malloc(sizeof(BITMAPINFOHEADER));
Bmi->biSize                  = sizeof(BITMAPINFOHEADER);
Bmi->biWidth                  = WorkBmp.bmWidth; //WorkBmp is the orginal BITMAP
Bmi->biHeight            = WorkBmp.bmHeight;
Bmi->biPlanes            = 1;
Bmi->biBitCount            = 1;
Bmi->biCompression            = BI_RGB;  //BI_RGB=0
Bmi->biSizeImage            = paddedSize;
Bmi->biXPelsPerMeter      = 0;
Bmi->biYPelsPerMeter      = 0;
Bmi->biClrUsed      = 0;
Bmi->biClrImportant      = 0;

where paddedSize

int paddedSize = WorkBmp.bmHeight * ( WorkBmp.bmWidth/8 + (4- (WorkBmp.bmWidth/8%4)));
            
RGBQUAD colors[2];
   colors[0].rgbBlue = colors[0].rgbGreen = colors[0].rgbRed = 0;
   colors[1].rgbBlue = colors[1].rgbGreen = colors[1].rgbRed = 255;
   colors[0].rgbReserved = colors[1].rgbReserved = 0;

HDC hdc = GetDC(NULL);
LPBITMAPINFO bi = (LPBITMAPINFO) malloc(sizeof(LPBITMAPINFO));
bi->bmiHeader = *Bmi;
bi->bmiColors[0] = colors[0];
bi->bmiColors[1] = colors[1];

HBITMAP hBitmap = ::CreateDIBSection(hdc, (LPBITMAPINFO)bi, DIB_RGB_COLORS, (LPVOID *)pBmiSrc, NULL, 0);
ReleaseDC(NULL, hdc);

to write the image to a file i use

CBitmap* cBitmap = CBitmap::FromHandle(highlightedBitmap);

CPalette cPal;
HANDLE bHandle = DDBToDIB(*cBitmap, BI_RGB ,&cPal);
BOOL writtenOK = WriteDIB(outstr, bHandle);      

where DDBToDIB and WriteDIB were pulled from the web and i have tesed them and they have worked for me in the past.

however the problem is it simpy creates black images if i write it to a file using the returned HBITMAP. I know that pBmiSrc, the bit array, is correct as i have tried manually adding its contents into a hex editor and the bitmap is then appears as it should.
0
Comment
Question by:flynny
  • 12
  • 11
  • 2
  • +2
27 Comments
 
LVL 30

Expert Comment

by:Zoppo
ID: 20402283
Hi flynny,

maybe the problem comes from how you allocate the BITMAPINFO:

> LPBITMAPINFO bi = (LPBITMAPINFO) malloc(sizeof(LPBITMAPINFO));

First you use 'sizeof(LPBITMAPINFO)' which is for sure wrong since this just allocates the size of a pointer (4 bytes) ... further I think you'll have to allocate in addition the memory for the colortable, so maybe somehow like this should work:

> LPBITMAPINFO bi = (LPBITMAPINFO) malloc(sizeof( BITMAPINFO ) + 2 * sizeof( RGBQUAD ) );

Hope that helps,

ZOPPO
0
 
LVL 19

Expert Comment

by:alb66
ID: 20402413
I think that "hBitmap" is already a DIB, so you can pass it directly to WriteDIB() without using CBitmap and DDBToDIB:


HBITMAP hBitmap = ::CreateDIBSection(hdc, (LPBITMAPINFO)bi, DIB_RGB_COLORS, (LPVOID *)pBmiSrc, NULL, 0);
BOOL writtenOK = WriteDIB(outstr, hBitmap );      
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20402662
Maybe the following link is helpful:

http://support.microsoft.com/kb/77282/en-us

Regards, Alex
0
 

Author Comment

by:flynny
ID: 20403356
hi guys thanks for the replies.

yes i think the problem looks like it could be in the LPBITMAPINFO as if i do the following it writes the bitmap ok

    FILE *p = fopen( "c:\\test12345.bmp", "wb" );
    fwrite( bfh, sizeof(BITMAPFILEHEADER), 1, p );
    fwrite( Bmi, sizeof(BITMAPINFOHEADER), 1, p );
    fwrite( &colors[0],4, 1, p );
    fwrite( &colors[1],4, 1, p );
    fwrite( pBmiSrc,Bmi->biSizeImage, 1, p );
    fclose( p );

like you said alb66 i tried passing the hbitmap to theWrteDIB method and it doesnt appear to be writing the hbitmap

i create and pass as follows

BOOL writtenOK = WriteDIB("c:\\test12345.bmp", hBitmap );

and it seems to fail here
int nColors = 1 << lpbi->biBitCount;

i assume because its not been able to cast hDIB to a LPBITMAPINFOHEADER? any ideas why it does this?




BOOL CBitmapOCR::WriteDIB( LPTSTR szFile, HANDLE hDIB)

{

	BITMAPFILEHEADER	hdr;

	LPBITMAPINFOHEADER	lpbi;
 

	if (!hDIB)

		return FALSE;
 

	CFile file;

	if( !file.Open( szFile, CFile::modeWrite|CFile::modeCreate) )

		return FALSE;
 

	lpbi = (LPBITMAPINFOHEADER)hDIB;

	int nColors = 1 << lpbi->biBitCount;
 

	// Fill in the fields of the file header 

	hdr.bfType		= ((WORD) ('M' << 8) | 'B');	// is always "BM"

	hdr.bfSize		= GlobalSize (hDIB) + sizeof( hdr );
 

	hdr.bfReserved1 	= 0;

	hdr.bfReserved2 	= 0;

	hdr.bfOffBits		= (DWORD) (sizeof( hdr ) + lpbi->biSize +

						nColors * sizeof(RGBQUAD));
 

	// Write the file header 

	file.Write( &hdr, sizeof(hdr) );
 

	// Write the DIB header and the bits 

	file.Write( lpbi, GlobalSize(hDIB) );
 

	return TRUE;

}

Open in new window

0
 
LVL 20

Expert Comment

by:ikework
ID: 20404512
> and it seems to fail here
what do you mean with it fails? does it crash?
0
 

Author Comment

by:flynny
ID: 20410082
yes it crashes after

lpbi = (LPBITMAPINFOHEADER)hDIB;

once i try and access any variable in the LPBITMAPINFOHEADER. I read somewhere that i will need to convert the HBitmap to a handle by globalalloc-ing the memory. I tried this but it didn't seem to work using the following

HBITMAP hBitmap = ::CreateDIBSection(hdc, (LPBITMAPINFO)bi, DIB_RGB_COLORS, (LPVOID *)&pBmiSrc, NULL, 0);
HGLOBAL memPtr = GlobalAlloc(GMEM_FIXED, sizeof(hBitmap)); //pointer to mem block

HBITMAP tmpBitmap;
if( memPtr && (tmpBitmap = (HBITMAP)GlobalLock(memPtr)) != NULL )
      tmpBitmap = hBitmap;

BOOL writtenOK = WriteDIB("c:\\test12345.bmp", tmpBitmap);

this just did exactly the same thing.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20410174
>>>> lpbi = (LPBITMAPINFOHEADER)hDIB;

The hDIB is a handle to a bitmap. It is some kind of 'id' used from Windows to identify its resources. Though most likely there is a association from the handle to some structure, the handle itself is not a pointer and therefore it crashes.  

Normally, you have a HBITMAP handle to a DDB (a device-dependent-bitmap where the device is the screen) and you call GetDIBits to get the bits from that bitmap.

You should check the link I gave you above. There is full source code for exactly the task you asked for.
0
 

Author Comment

by:flynny
ID: 20412676
hi itsme, sorry i did mention the link in my first reply but unfortunately the ie crashed and so had to retype and in my rush forgot to readd about this. ;)

The difference between the link and what i want to do is that the link will simply convert the image to monochrome and i have no control upon which pixel/s will be black and which will be white (for example in theory i woulld like to be able to specify the colour white to be highlighted.)

However using the CreateBitmap method seemed to solve the problem of creating the bitmap however its still working a little strangely.

i now create the bitmap like so

HBITMAP hbmMono = ::CreateBitmap(WorkBmp.bmWidth, WorkBmp.bmHeight, 1, 1, pBmiSrc);

where

int paddedSize = WorkBmp.bmHeight * ( WorkBmp.bmWidth/8 + (4- (WorkBmp.bmWidth/8%4)));
BYTE* pBmiSrc = new BYTE[ paddedSize ];

and send this to the method included below. however it seems to only be pulling back half of the byte array?

instead of outputting  

424D62000000000000003E0000002800000005000000090000000100010000000000240000000000000000000000000000000000000000000000FFFFFF00880000007000000070000000700000007000000070000000700000007000000088000000

i'm outputting

424D62000000000000003E0000002800000005000000090000000100010000000000240000000000000000000000000000000000000000000000FFFFFF00880000007000000070000000700000007000CDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD

any ideas why this could be?

many thanks for all the help,

Matt.


bool CBitmapOCR::WriteToFile(HBITMAP bmp, LPTSTR path)

{

	BITMAP WorkBmp;

	if(GetObject(bmp, sizeof(WorkBmp), &WorkBmp) == 0)

	{

		CString dwErr = ::GetLastError();

		logMsg(dwErr); //simply prints to a log file in error			

	}
 

	int paddedSize = WorkBmp.bmHeight * ( WorkBmp.bmWidth/8 + (4- (WorkBmp.bmWidth/8%4)));
 

	BITMAPFILEHEADER* bfh = (BITMAPFILEHEADER *) malloc(sizeof(BITMAPFILEHEADER));

	bfh->bfType='B'+('M'<<8); 

	bfh->bfOffBits= sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) + 8; //offset to bitmap pixel data + 8 is for the two colour bytes

	bfh->bfSize= paddedSize + bfh->bfOffBits;

	bfh->bfReserved1=0; 

	bfh->bfReserved2=0; 
 

	//next the bitmap info

	BITMAPINFOHEADER* Bmi	= (BITMAPINFOHEADER *) malloc(sizeof(BITMAPINFOHEADER));

	Bmi->biSize				= sizeof(BITMAPINFOHEADER);

	Bmi->biWidth			= WorkBmp.bmWidth;

	Bmi->biHeight			= WorkBmp.bmHeight;

	Bmi->biPlanes			= 1;//WorkBmp.bmPlanes;

	Bmi->biBitCount			= 1;//WorkBmp.bmBitsPixel; 

	Bmi->biCompression		= BI_RGB;  //BI_RGB=0

	Bmi->biSizeImage		= paddedSize; //WorkBmp.bmHeight * WorkBmp.bmWidth /8; //should ust be ths as only one bit er pixel

	Bmi->biXPelsPerMeter	= 0;

	Bmi->biYPelsPerMeter	= 0;

	Bmi->biClrUsed			= 0;

	Bmi->biClrImportant		= 0;

		

	RGBQUAD colors[2];

    colors[0].rgbBlue = colors[0].rgbGreen = colors[0].rgbRed = 0;

    colors[1].rgbBlue = colors[1].rgbGreen = colors[1].rgbRed = 255;

    colors[0].rgbReserved = colors[1].rgbReserved = 0;
 

	BYTE* pBits = new BYTE[ paddedSize ];

	GetBitmapBits(bmp,WorkBmp.bmWidth * WorkBmp.bmHeight,pBits);
 

	FILE *p = fopen( path, "wb" );

    fwrite( bfh, sizeof(BITMAPFILEHEADER), 1, p );

    fwrite( Bmi, sizeof(BITMAPINFOHEADER), 1, p );

    fwrite( &colors[0],4, 1, p );

    fwrite( &colors[1],4, 1, p );

    fwrite( pBits,Bmi->biSizeImage, 1, p );

    fclose( p );
 

	return true;

}

Open in new window

0
 
LVL 20

Expert Comment

by:ikework
ID: 20412946
hi matt,

the calculation of the padded rowsize does not work properly. if you put a width of 160 the result should be 20, but your formula gives 24

WorkBmp.bmWidth: 160

(WorkBmp.bmWidth/8 + (4- (WorkBmp.bmWidth/8%4))); -> result 24 but should be 20


ike
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20413223
>>>> (4- (WorkBmp.bmWidth/8%4)));
You should add some more parantheses. I don't know in which order the operators / and % were evaluated. It maybe done in that way you expect it ... or not.

>>>> GetBitmapBits(bmp,WorkBmp.bmWidth * WorkBmp.bmHeight,pBits);
The GetBitmapBits will copy the bits of the colored bitmap to a buffer (pBits) which was sized for a monochrome bitmap. Note, when changing the Bmi struct  members you do not change any of the 'internal' resources associated to the 'bmp' handle. But that handle (and width and height) was the only info passed to the GetBitmapBits.

You should size the pBits buffer to bmWidth * bmHeight * sizeof(RGBQUAD) and get the colors for the colored bitmap. Then, you could evaluate the RGBQUAD items, and set the bits of a second buffer for the monochrome bitmap (sized to 'paddedSize') like the following:

      for (int i = 0; i < WorkBmp.bmWidth * WorkBmp.bmHeight; ++i)
      {
            RGBQUAD rgbq = *(((RGBQUAD*)pBits) + i);  // get ith item of RGBQUAD array
            // check color whether it should be black or white
            bool black =  (rgbq.rgbRed > 178 && ... );
            if (black)
            {
                  pMonoBits[i/8] |= 1<<(i%8);   // set corresponding bit
            }
      }

After that you could store the pMonoBits together with the bitmap header you already filled correctly.

Regards, Alex



0
 

Author Comment

by:flynny
ID: 20418751
Hi itsme and ikework

ikework, thanks for pointing that out i've replaced this  to the following now;

double bytesPerPixel = WorkBmp.bmBitsPixel/8.000;
int dActualWidth = (int) ((WorkBmp.bmWidth * bytesPerPixel) + 0.999f); //round up to narest byte

while(dActualWidth%4 != 0)
   dActualWidth++;

itsme, thanks for that. yes this is the approach trying to take however the problem i mentioned prviously is still happening. (I have noticed though that when i tested i on a 19 x 9 image it worked ok)

so i have my highlighting method working now the problem seems to be i'm not getting enough bitmap bits when trying to write to the file (i printing out the contents of the byte array before creatring the bitmap in the highlight method and this is correct.)

i've changed my GetBitmapBit cal to be

int paddedSize = WorkBmp.bmHeight * dActualWidth;

BYTE* pBits = new BYTE[paddedSize];
GetBitmapBits(bmp, paddedSize, pBits);

i create the HBITMAP bmp as follows

HBITMAP hbmMono = ::CreateBitmap((WORD)WorkBmp.bmWidth, (WORD)WorkBmp.bmHeight, 1, 1, pBmiSrc); //pBmiSrc is correct as i have printed the contents out

am i reading the incorrect amount of bytes?
0
 

Author Comment

by:flynny
ID: 20418899
hi guys,

i have noticed something strange, as an experiment, due to the fact it seemed to only be sending half the row, i tried the following

HBITMAP hbmMono = ::CreateBitmap((WORD)WorkBmp.bmWidth, (WORD)WorkBmp.bmHeight*2, 1, 1, pBmiSrc);

then when i was writing to the file i did the following

int paddedSize = WorkBmp.bmHeight/2 * dActualWidth;

set the BITMAPHEADERINFO bmHeight to be divided by 2 also. and this is working? why i dont know. any ideas?
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20419033
>>>> when i tested i on a 19 x 9 image
Do you really need to have bitmaps with 'odd' width and height. If your sizes were a multiple of 8 (what is not much for a paper device), you can spare using doubles for calculating bits.

>>>> double bytesPerPixel = WorkBmp.bmBitsPixel/8.000;
the following is equivalent:

     int bytesPerPixel = (WorkBmp.bmBitsPixel + 7)/8;

It rounds up to the next multiple of 8.

>>>> int paddedSize = WorkBmp.bmHeight * dActualWidth;
That makes paddedSize the number of bytes in case the dActualWidth was calulated correctly. But I have doubts ...

>>>> double bytesPerPixel = WorkBmp.bmBitsPixel/8.000;
>>>> int dActualWidth = (int) ((WorkBmp.bmWidth * bytesPerPixel) + 0.999f); //round up to narest byte
>>>> while(dActualWidth%4 != 0)
>>>>   dActualWidth++;
That is bad code.

Take 'monochrome' bitmap where bmWidth = 19 and bytesPerPixel is 0.125. Then dActualWidth = 2 and will be padded up to 4.

Take RGB bitmap, where bmWidth = 19 and bytesPerPixel is 4. Then dActualWidth = 76 and was not padded up.

I don't know whether the results were that you expected but the following would give the same results.

   int iActualWidth = ((WorkBmp.bmBitsPixel * WorkBmp.bmWidth) + 7) / 8;
   iActualWidth += 4 - (iActualWidth%4);

>>>> am i reading the incorrect amount of bytes?
I have problems to see what is the source bitmap and what is the target bitmap. I assumed the passed bmp handle was the handle to a colored bitmap. If so, the GetBitmapBits will return (width * height) * bitsPerPixel  bits, which may be padded up to a multiple of 32 bits. Until here there is *not* the sligthest reason to mixup bits and bytes. If you rounded up to a multiple of 32 (once!), you easily can divide by 8 (no roundings) and get the number of bytes the buffer needs to be allocated (and what was returned by GetBitmapBits hopefully). The only variable which may make trouble in the above calculation is the bitsPerPixel as I can't say from your code that you 'retrieve' it somewhere. You would get the *correct* bitsPerPixel for a valid HBITMAP handle if you would call the GetDiBits function with a NULL argument for the lpBits. Then, GetDiBits only fills the BITMAPINFOHEADER structure where the biBitCount member gives the requested information. In your code I can't see how you retrieved the WorkBmp.bmBitsPixel  (I saw that you set bmi.biBitCount fix to 1)  but that is the essential part.

Can you give some samples where you tell waht you got for 'bmBitsPixel', width and height, paddedSize, and where you tell why you think, that the requested buffer doesn't get all bits. Note, you easily could check that by not creating a monochrome bitmap but creating a copy of the existing bitmap. If you don't get the correct number of bits, that copy would be incomplete. If that works ok, the issue is when you finally 'store' the bits of the monochrome bitmap (where I actually didn't see any code til now).


 
0
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20419061
>>>> iActualWidth += 4 - (iActualWidth%4);
Correction:
      iActualWidth = (( iActualWidth + 3)/4) * 4;
0
 

Author Comment

by:flynny
ID: 20419176
hi its me thanks for your reply.

>>Do you really need to have bitmaps with 'odd' width and height. If your sizes were a multiple of 8 (what is not much for a paper device), you can spare using doubles for calculating bits.
potentially the bitmap could be any size so i will need to use doubles

>>     int bytesPerPixel = (WorkBmp.bmBitsPixel + 7)/8;
great thank for that.

>>That is bad code.
ok. i see. Just to check then because the reason i moved away from this way was due the tohe poiint the ikework made.

take iActualWidth = 4 (where the actual width is of modulo or 160 as in ikeworks example)
iActualWidth += 4 - (iActualWidth%4);
= 8

will i need to pad like this?

>>I have problems to see what is the source bitmap and what is the target bitmap.
sorry. basically i want to have a method HBITMAP highlightimage(HBITMAP, int,int,int) which will take a sent HBITMAP create a new highlighted monochrome image and then return it. so.. the passed HBITMAP will be the colour bitmap, and the returned HBITMAP will be the monochrome, highlighted one.

I then have another method BOOL WriteToFile(HBITMAP, path) which will write the sent HBITMAP to the specified path.

>>Can you give some samples where you tell waht you got for 'bmBitsPixel', width and height, paddedSize, and where you tell why you think, that the requested buffer doesn't get all bits.

ok, i'll do some test and post the results.

thanks again for all your help.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20419338
>>>> iActualWidth += 4 - (iActualWidth%4);
That was wrong. Look at my correction above.
0
 

Author Comment

by:flynny
ID: 20420301
hi its me sorry, maybe i'm reading it wrong but would this work?

>>>iActualWidth = (( iActualWidth + 3)/4) * 4;
e.g. for 4
4+3 = 7
and the division and multplying would cancel each other out?

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20421690
>>>> and the division and multplying would cancel each other out?
No, it is an integer divison which would round to the next lower integer, so

  ((0 + 3)/4) * 4 = 0 * 4 = 0
  ((1 + 3)/4) * 4 = 1 * 4 = 4
  ((2 + 3)/4) * 4 = 1 * 4 = 4
  ((3 + 3)/4) * 4 = 1 * 4 = 4
  ((4 + 3)/4) * 4 = 1 * 4 = 4
  ((5 + 3)/4) * 4 = 2 * 4 = 8
  ((6 + 3)/4) * 4 = 2 * 4 = 8
  ((7 + 3)/4) * 4 = 2 * 4 = 8
  ((8 + 3)/4) * 4 = 2 * 4 = 8
  ((9 + 3)/4) * 4 = 3 * 4 = 12

the whole thing always rounds up to the next multiple of 4.


0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20421788
As told above you better should calculate the bits, round up to a  multiple of 32 and get the bytes then.

Microsoft samples do that by using the following macro:

// How wide, in bytes, would this many bits be, DWORD aligned?

#define WIDTHBYTES(bits)      ((((bits) + 31)>>5)<<2)


The above takes the number of bits, adds 31, divides by 32  (a right shift of 5 is the same as dividing by 2^5==32) and multiplies the result by 4 (a left-shift of 2 is the same as multiplying with 2^2 == 4).

0
 

Author Comment

by:flynny
ID: 20426475
sorry about that itsme, didnt see the integer division. i've been testing the method on 24bit bitmaps and (using paint) and it doesnt work for any bitmap i pass it. for example in the following bitmap

424D9A00000000000000360000002800000006000000050000000100180000000000640000000000000000000000000000000000000000000000000000000000000000FF0000FF000000FFFFFF00000000000000000000FF0000FF000000FFFFFFFFFFFF00000000000000FF0000FF000000FFFFFFFFFFFFFFFFFF00000000FF0000FF000000FFFFFFFFFFFFFFFFFFFFFFFF00FF0000FF000000

i get the following output from iterating through the byte array

ffffffffffffffffffffffff00ff0000ff00ffffffffffffffffff00000000ff0000ff00ffffffffffff00000000000000ff0000ff00ffffff00000000000000000000ff0000ff0000000000000000000000000000ff0000ff0000000000000000000000

i'll do some more tests on different bitmaps, but any ideas here?
HBITMAP CBitmapOCR::HighlightImage(HBITMAP hBmp, int rVal, int gVal, int bVal)

{

	BITMAP WorkBmp;

	if(GetObject(hBmp, sizeof(WorkBmp), &WorkBmp) == 0)

	{

		CString dwErr = ::GetLastError();

		logMsg(dwErr);			

	}
 

	//pad so multiple of 4

	int bytesPerPixel = ((WorkBmp.bmBitsPixel+7)/8);

	int dActualWidth = (( (WorkBmp.bmWidth * bytesPerPixel) + 3)/4) * 4;
 

	//calculate the mono bitmap width

	int dActualMonoWidth = (( (WorkBmp.bmWidth * 0.125) + 3)/4) * 4;	

	int paddedSize = WorkBmp.bmHeight * dActualMonoWidth;

	BYTE* pBmiSrc = new BYTE[ paddedSize ];
 

	BYTE* bmBits = (BYTE*)GlobalAlloc(GPTR,WorkBmp.bmWidthBytes*WorkBmp.bmHeight);

	GetBitmapBits(hBmp,dActualWidth*WorkBmp.bmHeight,bmBits);//was bmwidthbytes
 

	int x;

	for(x=0; x<dActualWidth*WorkBmp.bmHeight;x++)

	{ //simply logs out to file 

		ocrMsg.Format(":Original Byte:%x: count[%d]", 	bmBits[x],x);

		logMsg(ocrMsg);

	}

Open in new window

0
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 500 total points
ID: 20426583
>>>> int bytesPerPixel = ((WorkBmp.bmBitsPixel+7)/8);
That only works for bmBitsPixel is a multiple of 8. To make it work generally you have to calculate the bits for the total bitmap, round up to next multiple of 32bits (DWORD) by adding +31 and make integer division by 32:

           (allBitsOfBitmap + 31) / 32

That is the number of DWORDs. By multiplying with 4 you get the bytes of the bitmap.

>>>> int dActualMonoWidth = (( (WorkBmp.bmWidth * 0.125) + 3)/4) * 4;

That goes wrong cause (WorkBmp.bmWidth * 0.125) is a double. Then all consequent calculations are calculations with double as well and you don't get integer rounding.

    int dActualMonoWidth = (( (WorkBmp.bmWidth * 1) + 31)/32) * 4;

>>>> for(x=0; x<dActualWidth*WorkBmp.bmHeight;x++)
The loop iterates bytes what hardly makes sense for bitmaps. You need to loop RGBQUAD (32bit) words for a colored bitmap where WorkBmp.bmBitsPixel == 32. If WorkBmp.bmBitsPixel == 24, you should have RGB values (3 bytes) for each pixel,

32 bits:

       for(x=0; x< (iActualWidth*WorkBmp.bmHeight)/4;x++)
       {
      ocrMsg.Format(":Original Byte:%x: count[%d]",       ((DWORD*)bmBits)[x],x);
      logMsg(ocrMsg);
       }

24 bits:

       for(x=0; x< (iActualWidth*WorkBmp.bmHeight)/3;x+=3)
       {
      ostringstream oss;
                oss << hex << setw(2) << setfilled('0') << bmBits[x] << ' '
                       << hex << setw(2) << setfilled('0') << bmBits[x+1] << ' '
                       << hex << setw(2) << setfilled('0') << bmBits[x+2] << "|";
      logMsg(oss.str().c_str());
       }


0
 

Author Comment

by:flynny
ID: 20456172
hi itsme, thanks for the reply and your help you've been brilliant.

ok i will be wanting to process both 24 and 32 bit bitmap so i will have to make the method general rather than just for 32bit.

>>           (allBitsOfBitmap + 31) / 32
so to check, would to following work generically to calculate the actualwidht of a bitmap line?

(((width*bitsperpixel)+31)/32) * 4; // i.e. using th WIDTHBYTES macro?

so for a 5x5 bitmap for example

for 24bit;

(((5 * 24) + 31) /32) * 4 =  120 + 31 / 32 *4 = 151 /32 *4 = 4 * 4 = 16

for 32 bit

(((5 * 32) + 31) /32) * 4 =  160 + 31 / 32 *4 = 191 /32 *4 = 5 * 4 = 20








0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20456274
>>>> for 24bit;
>>>> (((5 * 24) + 31) /32) * 4 =  120 + 31 / 32 *4 = 151 /32 *4 = 4 * 4 = 16

>>>> for 32 bit
>>>> (((5 * 32) + 31) /32) * 4 =  160 + 31 / 32 *4 = 191 /32 *4 = 5 * 4 = 20

Yes, for 24 bits you have 3 bytes per pixel, so it is 15 bytes padded up to the next multiple of 4 --> 16

Yes, for 32 bits you have 4 bytes per pixel, so it is 20 bytes what is already a multiple of 4 --> 20
0
 

Author Comment

by:flynny
ID: 20456349
great thanks itsme, ok so now to access each individual pixel in the byte array i will need to do the following? . e.g. to just prin out each pixels rgb value

int bytesPerPixel = ((WorkBmp.bmBitsPixel+7)/8); //will give 4 for 32bit and 3 for 24bit
int dActualWidth = (((WorkBmp.bmWidth*WorkBmp.bmBitsPixel)+31)/32) * 4;

for(y=0;y<WorkBmp.bmHeight;y++)
{
      for(x=0;x<WorkBmp.bmWidth;x++)
      {
            r = *((BYTE*)WorkBmp.bmBits + (y * dActualWidth) + (x * bytesPerPixel) + 2);
            g = *((BYTE*)WorkBmp.bmBits + (y * dActualWidth) + (x * bytesPerPixel) + 1);
            b = *((BYTE*)WorkBmp.bmBits + (y * dActualWidth) + (x * bytesPerPixel) + 0);

            ocrMsg.Format("TEST [x%d,y%d] r%d,g%d,b%d",x,y,r,g,b); //basically just outputs text to log file
            logMsg(ocrMsg);
      }
}
0
 
LVL 39

Assisted Solution

by:itsmeandnobodyelse
itsmeandnobodyelse earned 500 total points
ID: 20456508
>>>> r = *((BYTE*)WorkBmp.bmBits + (y * dActualWidth) + (x * bytesPerPixel) + 2);
Actually, I don't like castings, especially if they are nor needed. WorkBmp.bmBits is a byte array. If you add bytes to a byte pointer it keeps a byte pointer and casting is not needed.

    r = *(WorkBmp.bmBits + (y * dActualWidth) + (x * bytesPerPixel) + 2);

Note, with castings you change pointer arithmetics (i. e. adding 4 to a int* pointer would step 4 ints), what isn't a issue when casting to BYTE* and adding bytes, but nevertheless is always error-prone. So, you might consider loosing some nanoseconds and have
         
         int offset = (y * dActualWidth) + (x * bytesPerPixel);
         r = WorkBmp.bmBits[offset   + 2];
         g = WorkBmp.bmBits[offset  + 1];
         b = WorkBmp.bmBits[offset  + 0];


I actually don't know whether a pixel is stored as 'rgb' or as 'bgr'. You now have 'bgr' as 'b' has offset 0. In case of 32 bits we have RGBQUAD what has blue, green, red (and reserved) as well, so you maybe right.
0
 

Author Comment

by:flynny
ID: 20464135
thanks for that itsme,

it working perfectly. ;)
0
 

Author Closing Comment

by:flynny
ID: 31412561
Itsme broke the question down with detailed examples, helping to understand the process clearly and easiely. many thanks
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, wh…
What is C++ STL?: STL stands for Standard Template Library and is a part of standard C++ libraries. It contains many useful data structures (containers) and algorithms, which can spare you a lot of the time. Today we will look at the STL Vector. …
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now