Link to home
Create AccountLog in
Avatar of andy06
andy06Flag for Germany

asked on

Converting Bitmap in 2D Array

Hi everyone ,
I'm doing image processing for the first time and I'm working with VC++ 6.0 under WinXP.
I would like to create a function which loads an 8 bit grayscale bitmap image and convert it
into a 2D array where each element represents one of the image's
pixel intensities for further purposes e.g. to calculate the fft.I saw quite similar
examples on this homepage but none of them returns an 2D array.
My code is attached in Code Snippet
The problem is that I would like to have the values of the array returned and I don't know how it should look like inside LRESULT CALLBACK WndProc function(I'm using Win32 API).And I'm also getting error message because of following declarations:
BYTE  tempScanLine[WIDTH * 1]  // error C2057:it should be a constant expression
                                                    //error C2466 : can't reserved an array of size 0
                                                 //  error C2133: 'tempScanLine' : unknown parameter
const double grayscale[WIDTH*HEIGHT]; //  error C2133: 'grayscale' : unknown parameter
I thought of writing grayscale=new double [WIDTH*HEIGHT] but then I have to give the memory free
and I can't return the array anymore right?
Thank you
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{	
static HBITMAP hbitmap;
int WIDTH, HEIGHT;
switch (message)
     {
   case WM_PAINT:
				
HANDLE hObj = LoadImage(NULL,"image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
HDC hdc = CreateCompatibleDC(NULL);
SelectObject(hdc, hObj);
BITMAPINFO bInfo;
bInfo.bmiHeader.biSize = sizeof(bInfo.bmiHeader);
bInfo.bmiHeader.biWidth = WIDTH;
bInfo.bmiHeader.biHeight = HEIGHT;
bInfo.bmiHeader.biPlanes = 1;
bInfo.bmiHeader.biBitCount = 8; 
bInfo.bmiHeader.biCompression = BI_RGB;
BYTE tempScanLine[WIDTH ];
BYTE info[HEIGHT][WIDTH][1];
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(BITMAPINFO));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
int count = HEIGHT - 1;
const double grayscale[WIDTH*HEIGHT];
for(int j = 0; j < HEIGHT; ++j)
{
      // find the width of the object
      GetDIBits(hdc, (HBITMAP) hObj, j, 1, NULL, &bi, DIB_RGB_COLORS);
 
      // store the color values in a 3D array of bytes
      GetDIBits(hdc, (HBITMAP) hObj, j, 1, tempScanLine, &bInfo, DIB_RGB_COLORS);
      for(int z = 0; z < bi.bmiHeader.biWidth; ++z)
      {
            info[count][z][0] = tempScanLine[(z * 1) + 2];    
            info[count][z][1] = tempScanLine[(z * 1) + 1];     
            info[count][z][2] = tempScanLine[(z * 1) + 0];                   
          //Transform to gray values
            grayscale[z]=info[count][z][0]*0.299 + info[count][z][1]*0.587+info[count][z][2]*0.114;
      }
     --count;
}

Open in new window

Avatar of abosultan
abosultan
Flag of France image

I suggest you to use opencv library since you work on VC and first time on image processing; it can help you if you like to continue in this field
have a look at Open cv
http://www.site.uottawa.ca/%7Elaganier/tutorial/opencv%2Bdirectshow/cvision.htm
I post a code tested in borland c++, so some few changes need te be altered.
there you see some dynamic array declaration , instead you have to modify by pointer(dynamic allocation) as a dynamic array;

typedef Graphics:: TBitmap TTBitmap; 
typedef DynamicArray<byte> TTByteArray;
typedef DynamicArray<TTByteArray> TImageArray;
/***********
 
TImageArray GetArrayOfImage(int BytePerPixel, TTBitmap  *pBitmap)
{
     Byte  *ptr;
     TImageArray Result;
    try
     {
      int h = pBitmap->Height;
      int w = (pBitmap->Width)*BytePerPixel;
      Result.set_length(h);
      for  ( int  y = 0; y < h; y++)
      {
        Result[y].set_length(w);
        ptr = (byte *)pBitmap->ScanLine[y];
         for  ( int  x = 0; x < w; x++)
                        Result[y][x] = ptr[x];
      }
    }
     catch  (...)
    {
      ShowMessage("Could not load or alter bitmap");
    }
    return Result;
}

Open in new window

Avatar of andy06

ASKER

As I am working with Microsoft VC++ 6.0, what should be appropriate to replace TTBitmap  , TTByteArray with?Do I have to define a Class for Bitmap?If I have to use pointer in order to store the pixels I would have to use pointer auf pointer right? How is the function set_length() define?what about ScanLine?I can't see the definition for it anywhere. Where in the above code should be the file loaded and opened?
I downloaded the class Cximage on the following website:
http://www.codeproject.com/KB/graphics/cximage.aspx
Do you have an idea ow I could use that class in order to store the pixel in an 2D array?
>> what should be appropriate to replace TTBitmap  
I'm not so expert in VC, but instead of Tbitmap, you can see HBITMAP, CBitmapclass in Vc++
> TTByteArray
TTByteArray is dynamic 1D array of bytes, you can use instead a dynamic allocation by pointers
for exemple
BYTE *buffBitmap = new BYTE[];

>>function set_length()
since I had used dynamic array, this function set a new size to the array in each step of adding an element in it;
>> ScanLine[];
is a function to indicate (set pointer) to a row, which in turn has his own pixels(columns)
Avatar of DanRollins
First of all, don't put something like that in your WM_PAINT handler.

Here is how I'd proceed in experimenting with and learning about these issues:

1) Create new dialog-based MFC application.
2) Add a smallish gray-scale image to your resources (IDB_BITMAP1)
3) Add a picture control to your dialog box, Set properties:
       ID: IDC_MyBmpCtrl           <<--- important: not IDC_STATIC
       Type: bitmap
       Image: IDB_BITMAP1
    The image is now automatically displayed for you!
4) Click on the bitmap in the dialog and press Ctrl+W (class wizard)
5) Click Member Variables; select IDC_MyBmpCtrl
6) Click [Add Variable]
     Member name: m_ctlMyBmp
    Category: Control
    Variable TYpe: CStatic
7) Add a button to the dialog (IDC_BUTTON1)
8) Double click it (add dlg member function OnButton1()
    Put this text there:
      MessageBox("You rang?");

After that, you now can start experimenting.  The image is automatically loaded and displayed.  When you click a button, some specific code will be executed.  You can obtain the HBITMAP my using:
     HBITMAP hBmp= m_ctlMyBmp.GetBitmap()
You can use the various CBitmap functions by:
    CBitmap* pcBmp= CBitmap::FromHandle( hBmp );
and then...
   CSize cs= pcBmp->GetBitMapDimension().
... and so forth.

When you get to this point, and have read up on CBitmap class object, let me know, and we'll take it to the next step.

-- Dan
Avatar of andy06

ASKER

Hi,
I've never used MFC before and it took some time to reach until step 4.At Step four  I was asked to create a new Class!(I named the Class MyBitmap).the MessageBox should be then in the function "void MyBitmap::OnButton1" right?Where should I write the following statements?
HBITMAP hBmp= m_ctlMyBmp.GetBitmap();
CBitmap* pcBmp= CBitmap::FromHandle( hBmp );
CSize cs= pcBmp->GetBitMapDimension()?
Thank you


You should not have needed to create a new class object -- only a new member function for the CDialog-derived object that is the main (only) window your new program has.

Assuming that your project name is MyTestApp...
The OnButton1 function will be a member of CMyTestAppDlg (the ClassWizard will place it into the MyTestAppDlg.cpp source file.
Avatar of andy06

ASKER

I've seen where my mistake was.I was asked to create a new class
because I created a new dialog for the picture instead of using
the Dialog which was already generated by VC++.The function OnButton1()
is now at the right place.no error by compiling but the bitmap is not displayed at all.I'm having only
a blank page.....
What about
HBITMAP hBmp= m_ctlMyBmp.GetBitmap();
CBitmap* pcBmp= CBitmap::FromHandle( hBmp );
CSize cs= pcBmp->GetBitMapDimension()?
Should they be put in (I renamed my project MyTestApp) MyTestAppView.cpp?
thank you
>> I'm having only
a blank page.....

This leads me to think that perhaps you did not create a Dialog-based application.  That option is on the first page presented by the AppWizard.  Revised detail for step #1:

1a) menu:  File / New...  
1b) Select Projects tab
1c) Select MFC AppWizard [exe]   / Project name: MyTestApp / [OK]
1d) Select Dialog based [Finished]

(Your empty dialog is displayed.)
Avatar of andy06

ASKER

You are right.I hadn't created a Dialog-based application but a SDI application.
Now my 8bpp grayscale image is automatically loaded and displayed.  By clicking on the button1 a message is also displayed.  
How could I now store the pixel intensities in an array where each element represents one of the image's pixel intensities?
Thank you
OK, here's the next step.  Put this code into the OnButton1 handler:

      HBITMAP hBmp= (HBITMAP)LoadImage(NULL,"c:\\temp\\test.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

      m_ctlMyBmp.SetBitmap( hBmp );

      CBitmap* pcBmp= CBitmap::FromHandle( hBmp );

      BITMAP rBmp;
      int n= pcBmp->GetBitmap( &rBmp );

But use a filename of a bitmap you want to work with.  I suggest you make a small one, say 10x10 or 32x32 for testing purposes.  First run the revised code:  You should see the desired bitmap appear in the control.

Next, put a breakpoint on the last line.   Single-step it.  Now use the debugger to examine the filled-in values in rBmp (a BITMAP structure).  It will provide the dimensions of the newly-loaded bitmap and other information.  Report what it says for
    rBmp.bmBitsPixel
and
    rBmp.bmPlanes

This is important for the next step, because some "grayscale" images are really full-color images.
Avatar of andy06

ASKER

I loaded for testing purposes a colored 32x32 bitmap and a grayscale 26x24 bitmap.For the 32x32 bitmap I become as information:
rBmp.bmWidth=32
rBmp.bmHeight=32
rBmp.bmBitsPixel=32
rBmp.bmPlanes=1
and  for the grayscale 26x24 bitmap I'm having:
rBmp.bmBitsPixel=32
rBmp.bmPlanes=1
I also tested all other "grayscale" bitmap(with dimensions 256x256) I had and it happened that they are all 32Bpp Bitmap with rBmp.bmPlanes=1.
that means I thought I was handling with 8Bpp grayscale image and in fact they weren't right?
thank you
Avatar of andy06

ASKER

As I'm not working with 8Bpp bitmap how does the next step look like?
thank you
The simplest, and easiest-to-understand tool that is available to you is:

    GetPixel(x,y)

It returns a COLORREF value that represents the intensity of each of the Red, Green, Blue (RGB) components of the single pixel at the designated position.

In order to access that function, the bitmap must be selected into a "DC" (Device Context).  Because we have set up a test scenario in which the bitmap is already being displayed, this is easy.

Your next step is to experiment with this as follows:
1) Use the code (in the attachment, below) in your OnButton1 handler (or add an OnButton2 handler... whatever)

2) Click the button to see the result.
    An all-white pixel will be (255,255,255) and an all-black one will be (0,0,0).  
    In a color bitmap, an all-red pixel will be (255,0,0) and so forth,

3) Open Paintbrush or other bitmap editor and change the color of the pixel in the top left corner (x=0, y=0).  Save the bitmap and click the button in your dialog box.  You should see the new result -- both visibly in the displayed bitmap (that one pixel will change color) and in the message box.

4) You can now experiment with accessing other pixels.  

5) If you want, you can modify the bitmap.  Add another button and use CDC::SetPixel() For instance:

void CD39Dlg::OnButton4()
{
      CDC* pCDC= m_ctlMyBmp.GetWindowDC();

      pCDC->SetPixel(0,0, RGB(255,0,0) );
      pCDC->SetPixel(1,0, RGB(255,0,0) );
      pCDC->SetPixel(0,1, RGB(255,0,0) );
      pCDC->SetPixel(1,1, RGB(255,0,0) );
}

When you click that (newly-added button) the four pixels in the top/left corner will turn red.

If you have a grayscale bitmap, then your RGB values will be all the same... e.g.,
   (0,0,0)= black;
   (32,32,32)= very light gray,
   (127,127,127) is medium gray,
   (255,255,255)= pure black,
etc.

=-=-=-=-=-=-=-=-=-=
If you have a particular (smallish) gray-scale bitmap that you want to experiment with (and about which you have questions), you can attach it to your next comment, and I'll refer to it in subsequent comments of my own.

-- Dan
void CD39Dlg::OnButton3() 
{
	HBITMAP hBmp= (HBITMAP)LoadImage(NULL,"c:\\temp\\test.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
 
	m_ctlMyBmp.SetBitmap( hBmp );
 
	CDC* pCDC= m_ctlMyBmp.GetWindowDC();  // get access to DC-related fns
	
	COLORREF clrRGB= pCDC->GetPixel(0,0); // <<--- grab the dot in the top, left corner
	int nRed=   GetRValue( clrRGB );
	int nGreen= GetGValue( clrRGB );
	int nBlue=  GetBValue( clrRGB );
 
	CString s; s.Format( "Red=%d, Blue=%d, Green=%d", nRed, nBlue, nGreen );
	MessageBox( s );
}

Open in new window

Avatar of andy06

ASKER

The step 1) until step 5) work all  fine. Thank you very much for the detailed explanations.
In the MSDN website the function GetPixel is defined like this:
COLORREF GetPixel(
  HDC hdc,   // handle to device context
  int nXPos,  // x-coordinate of pixel
  int nYPos  // y-coordinate of pixel
)
Why did you use only two arguments instead of three when calling GetPixel?
Was it possible in order to retrieve the red, green and blue value of a pixel to use the RGBQUAD structure?
Thank you
>>Why did you use only two arguments instead of three when calling GetPixel?
I'm not sure If I could answer like DanRollins, but anyway I found another representation in MSDN
http://msdn2.microsoft.com/en-us/library/system.drawing.bitmap.getpixel(vs.71).aspx

it may be pertinent

The ::GetPixel() API function
     http://msdn2.microsoft.com/en-us/library/ms532282.aspx
requires an HDC parameter.  But you will recall that I used the syntax:

    pCDC->GetPixel(0,0);

which uses the similar function that is a member of the CDC class object.  That object already knows the HDC, and it uses it when it calls the underlying API function.

>> Was it possible in order to retrieve the red, green and blue value of a pixel to use the RGBQUAD structure?

I'm not certain what you mean by this.  
The RGBQUAD and COLORREF structures are functionally similar ways to looks at a 32-bit value that contains the RGB information used to describe the color of a pixel.
Avatar of andy06

ASKER

Ok it appears clearly to me now.
I've attached two bitmaps that I'm working with.
How does the next step looks like?
Thank you
EditCopy.bmp
cube.bmp
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
Avatar of andy06

ASKER

I have a question at line 24 of your code.What happens when RGB are not all the same?do I have to include a  if-clause?
Isit possible to use a function of my own on abPixels[x][y]= GetRValue( clrRGB )?(line 24)  
The code depends on the fact that a dialog-based application has been used.It is possible to read each pixel of the image without displaying it by using MFC of course?
thank you
Avatar of andy06

ASKER

clearly answers and thank you for having so much patience
"...all the same" refers to the fact that it is a grayscale image.  
That means that when in memory, for use by a color device (the screen) the R,G, and B values will always be equal.  That provides 256 levels of whiteness.  Consider that if the values were not all the same --  say there was more red than green -- then the image would contain non-gray pixels.

As to the fact that there is a dialog box involved...
The only reason for that is to make it easy to see the results of experiments.  The functionaity would be the same if you never display the image.  For instance, the only relevant difference is that the above code uses:

    m_ctlMyBmp.SetBitmap( hBmp );

so that you can physically *see* the bitmap.  It is perfectly valid to make changes to the pixel data without ever displaying the image anywhere.  The fact that a dialog box is used is not part of that equation.  You could just as easily create an off-screen DC and manipulate the pixels there.

Thanks for the points and the grade :-)