I have a MFC application built with VS2008, that I would like to use
it for image processing.
At this moment I use CImage instance to modify some colors using the
functions GetPixel()/SetPixel() and the image gets displayed
accordingly.
I would like to get the image's matrix (same dimensions as the
original, same format, uncompressed, like .bmp, .png) , make some
changes in it and then attach it to a new CImage instance in order to
save it with a different name, everything without being necessary to
display the image.
I noticed that CImage has the GetBits() function that retrieves the
array of bytes, but did not see any function to set the array in one
shot (without SetPixel()).
My question is if this can be done. If CImage cannot set the matrix,
then what class should I use instead.
Some code would help me to understand better.
Thanks a lot,
George
The usual way of manipulating bitmaps is not with SetPixel() [which is slow]
but by selecting them into a memory DC and drawing on them.
Here's an example of how to create a copy of whatever bitmap is currently
selected in hDC. I'm assuming the size he size you want is
(nWidth,nHeight). The essential operations are:
HDC hMemDC = ::CreateCompatibleDC( hDC );
HBITMAP hBitmap = ::CreateCompatibleBitmap( hDC, nWidth, nHeight );
HBITMAP hOldBmp = (HBITMAP)::SelectObject( hMemDC, hBitmap );
::BitBlt( hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY );
//*****************************
if( hOldBmp!=NULL ) ::SelectObject( hMemDC, hOldBmp );
::DeleteDC( hMemDC );
// hBitmap is now a handle to a copy of the bitmap
// and it isn't now selected in any DC
MFC classes don't really make it any easier, but you can use CDC, CBitmap,
etc if you prefer.
At the point marked ***** you can do more drawing on the DC to modify the
bitmap.
Dave
-- David Webber
Mozart Music Software
http://www.mozart.co.uk
For discussion and support see
http://www.mozart.co.uk/mozartists/mailinglist.htm
As already pointed out, Get/SetPixel is horrendously slow. I use them for modifying 16x16
bitmaps but wouldn't want to use it on anything much larger.
joe
Joseph M. Newcomer [MVP]
email: newc...@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
Thank you very much for your answers.
I still try to find a solution.
I noticed that CBitmap cannot load regular images (C:\image.bmp), it
can only deal with project resources and I would not try to
dinamically load/release resources.
What I intend to do is to:
- load an image (not from resources)
- get its bitmap array or vector (liniarized array)
(ex. an image with width = 100, height = 50, # of channels (RGB) = 3,
would produce a vector with 100 * 50 * 3 bytes )
- not display the image (to avoid collecting/setting the pixels values
from/to the device context, using GetPixel)
- modify some elements in the bitmap array
- save the the bitmap array as a new image, same extension (C:
\newimage.bmp)
So pretty much like in a batch processing, in background.
I am not sure what class is more appropriate to do this, I used so far
CImage's GetPixel/SetPixel just for experiments, and would need a
faster solution for the steps above.
Could you please provide me with some code that works, I am in a big
rush
Many thanks, again, for your help
George
P.S.
For an image with width = 358, height = 144, # of channels (RGB) = 3
that would produce a vector of 358 * 144 * 3 = 154656 bytes (I used
GetPixel()...),
CImage.GetBits() returns a char* vector of 1074 bytes ( I used
strlen()), that would be equal to (width = 358) multiplied by (# of
channels (RGB) = 3). I try to figure out how to retrieve the whole
elements (154656 bytes). I did not find any example in my search.
> email: newco...@flounder.com
> Web:http://www.flounder.com
> MVP Tips:http://www.flounder.com/mvp_tips.htm- Hide quoted text -
>
> - Show quoted text -
> - load an image (not from resources)
>...
For many years loading and saving bitmap files was a black art. To master
it you had to study Microsoft's documentation of the file format.
But CImage, since it has turned up, means you don't have to.
I don't actually load bitmaps myself, but I do save them to files, and here
is the essential code which does that. I start with an hBitmap (which I
have drawn using a memory DC and the usual GDI tools). Then to save the
bitmap in a file:
===============
CImage image;
image.Attach( hBitmap );
HRESULT hResult = image.Save( pszFilename, clsidEncoder );
BOOL bResult = (hResult==E_FAIL)? FALSE: TRUE;
image.Detach();
==============
You'll have to check the docs for clsidEncoder: it depends on whether you're
saving BMP, GIF, JPG, PNG, TIF, ...
Now I'm sure that you can also use CImage::Load() to read files with no more
difficulty.
The point is that you can use classes like CBitmap and CImage to encapsulate
the bitmap, but when it comes to defining the actual picture, the HBITMAP is
the thing which GDI uses, and you (just about always) draw it using a device
context and the GDI drawing functions. CBitmap is essentially a thin
wrapper around an HBITMAP and doesn't give you much more functionality than
the original Win32 API functions for bitmaps. But CImage was designed to
fill a gap and do things which weren't so easy (like loading and saving
bitmap files).
The secret is to think of an HBITMAP, CBitmap or CImage as an 'entity' and
not go worrying about the format of the bits unless you *really* have to.
For my part, if I never have to see another
BITMAPINFO, BITMAPINFOHEADER, or RGBQUAD again, I'll be happy.
> P.S.
> For an image with width = 358, height = 144, # of channels (RGB) = 3
> that would produce a vector of 358 * 144 * 3 = 154656 bytes (I used
> GetPixel()...),
> CImage.GetBits() returns a char* vector of 1074 bytes ( I used
> strlen()), that would be equal to (width = 358) multiplied by (# of
> channels (RGB) = 3). I try to figure out how to retrieve the whole
> elements (154656 bytes). I did not find any example in my search.
GetBits *does* return a pointer to the image plane. Your problem
is that strlen is for null terminated strings, so it stops counting
at the first null byte (which happened to be 1074 bytes into your
image array).
Here's an example of an invert function that I wrote for 8 bit
greyscale images (Note it only works for 8 bit images because it
doesn't test GetBPP() but it's only research code). Note the
important gotcha is that the width of the image and the width of
the array don't always match (the array is aligned on a 4 byte
boundary), which is why you need GetWidth() and GetPitch().
void CMyImage::Invert()
{
int i, j, hei, wid, stride;
BYTE *pBits = (BYTE *)GetBits();
BYTE *pRow;
hei = GetHeight();
wid = GetWidth();
stride = GetPitch();
for (j=0; j<hei; j++)
{
pRow = pBits+j*stride;
for (i=0; i<wid; i++)
pRow[i] = 255-pRow[i];
}
}
Cheers
mark-r
****
I'm actually surprised it gave you as may as 1074 bytes. The ecoding is in RGB triples,
so the first instance of any value for which R, G or B had 0 would terminate strlen. In
fact, the str functions have no relevance to this array of values. It is not a string, so
applying strlen to it makes no sense.
If Microsoft had programmers with two functioning neurons, the value returned would have
been declared either a void * or a BYTE (unsigned char) * array. 'char' is a *completley
useless* representation of values, because it means if you fetch a value that is 128 units
(or greater) it will be treated as a negative number! But Microsoft seems to have never
internalized the concept of "unsigned char" (or unsigned value in general; some APIs
return a UINT which is "-1 if there is an error", which would make readers either roll on
the floor laughing hysterically at such a clueless newbie error, or weeping because of the
complete screwup) and keeps insisting on using 'char' in completely inappropriate
contexts; this is one such example. So note that if you have a value RGB(255,255,255)
(white) it will end up producing the wrong result if you do
char * pixels;
...set pixels to point to bitmap
for(int i = 0; i < number_of_pixels; i+= 3)
{
int R = pixels[i];
int G = pixels[i+1];
int B = pixels[i+2]
because the compiler will *know*, by the (grotesquely ERRONEOUS) declaration of pixels
that these are signed 8-bit quantities, and will report that R is -1, G is -1 and B is -1
(0xFF, sign-extended to int). So if you then do arithmetic on these, you may not get the
results you want (by great good fortune, 2's complement arithmetic *may* do what you want,
but I wouldn't trust it).
I think the order is RGB as I showed above, but I'm trusting memory here so I may have it
backwards.
It is your responsibility to figure out how many pixels there are, and do the arithmetic
to compute the number_of_pixels value shown above. But strlen will not, cannot, and never
will be useful in this endeavor.
joe
****
email: newc...@flounder.com
> If Microsoft had programmers with two functioning neurons, the value returned would have
> been declared either a void * or a BYTE (unsigned char) * array. 'char' is a *completley
Well, in this instance, Microsoft must have got one of their trained
chimps on the case, because GetBits() *does* return a void *.
> char * pixels;
> ...set pixels to point to bitmap
> for(int i = 0; i< number_of_pixels; i+= 3)
> {
> int R = pixels[i];
> int G = pixels[i+1];
> int B = pixels[i+2]
As an aside, you shouldn't use a single for loop to scan over the
whole image plane because the rows may be padded to DWORD boundaries,
IOW there may be more than number_of_pixels*3 bytes in the image array.
Better to do
for(j = 0; j < height; j++)
{
for (i = 0; i< width; i++)
{
R = pixels[j*pitch + i*3];
G = pixels[j*pitch + i*3 + 1];
B = pixels[j*pitch + i*3 + 2];
where pitch is the number of bytes per row, found from the GetPitch()
function.
> It is your responsibility to figure out how many pixels there are, and do the arithmetic
> to compute the number_of_pixels value shown above.
number_of_pixels = image.GetWidth() * image.GetHeight();
number_of_bytes = image.GetPitch() * image.GetHeight();
Cheers
mark-r
>On 20/04/2011 03:19, Joseph M. Newcomer wrote:
>
>> If Microsoft had programmers with two functioning neurons, the value returned would have
>> been declared either a void * or a BYTE (unsigned char) * array. 'char' is a *completley
>
>
>Well, in this instance, Microsoft must have got one of their trained
>chimps on the case, because GetBits() *does* return a void *.
>
>> char * pixels;
>> ...set pixels to point to bitmap
>> for(int i = 0; i< number_of_pixels; i+= 3)
>> {
>> int R = pixels[i];
>> int G = pixels[i+1];
>> int B = pixels[i+2]
****
They finally got one right! Actually, I didn't check the API, because I've seen this
error so often...
****
>
>As an aside, you shouldn't use a single for loop to scan over the
>whole image plane because the rows may be padded to DWORD boundaries,
>IOW there may be more than number_of_pixels*3 bytes in the image array.
>Better to do
>for(j = 0; j < height; j++)
>{
> for (i = 0; i< width; i++)
> {
> R = pixels[j*pitch + i*3];
> G = pixels[j*pitch + i*3 + 1];
> B = pixels[j*pitch + i*3 + 2];
>
****
I wrote oversimplified code because I didn't want to write out all the required tests for
bit planes, etc. (which I have done, and it is long and tedious for bitmaps)
****
>where pitch is the number of bytes per row, found from the GetPitch()
>function.
>
>
>> It is your responsibility to figure out how many pixels there are, and do the arithmetic
>> to compute the number_of_pixels value shown above.
>
>number_of_pixels = image.GetWidth() * image.GetHeight();
>number_of_bytes = image.GetPitch() * image.GetHeight();
****
Note that GetPitch returns a signed number, negative if the bitmap is a DIB. This adds a
bit of complexity, for example
number_of_bytes = abs(image.GetPitch()) * image.GetHeight();
And the loop is more complex; for example, it has to deal with the fact that it might be
indexing off the end going down instead of the beginning going up; for example, on the
first iteration, i==0 and j==0 so if the pitch is -3 (a DIB) the loop shown would index
0*-3 + 0*3 == 0
that is, 0 bytes from the address in pixels, which is the highest address in the array, so
the next two computations, of G and B, will be off the end of the array.
I realized after I hit 'send' that I should have indicated this was for a DDB (Device
Dependent Bitmap).
Also, rows are usually padded to dword boundaries, a fact I had forgotten (but not every
pixel is aligned to a DWORD boundary), making it even messier. At least, when I'm
iterating over bitmaps NOT from CImage (which is the only time I've done it), so I'm not
sure what CImage returns.
As I said, tedious coding.
joe
****
>
>Cheers
>
>mark-r
> Note that GetPitch returns a signed number, negative if the bitmap is a DIB. This adds a
> bit of complexity, for example
>
> number_of_bytes = abs(image.GetPitch()) * image.GetHeight();
Ah, yes, I'd forgotten about that. I remember that's why I always
use a row pointer when iterating over an image plane.
BYTE *pBits = (BYTE *)image.GetBits();
BYTE *pRow;
for (j=0; j<height; j++)
{
pRow = pBits + j*image.GetPitch();
for (i=0; i<width; i++)
{
R = pRow[i*3];
G = pRow[i*3+1]
etc...
The important thing is that if the DIB is top-down, so GetPitch()
returns +ve, then the pointer from GetBits points to the *first*
row in memory. If the DIB is bottom-up, so GetPitch() returns -ve,
then then the pointer from GetBits points to the *last* row in
memory. So in both cases, the above code correctly steps through
the array; in the first case it starts at a low address and counts
up, in the second case it starts at a high address and counts
down. In both cases it starts at the pixel notionally defined as
(0,0) and steps sequentially through to pixel (width-1, height-1).
Note you decide whether you want a top-down or bottom-up DIB at
create time, by specifying a positive or negative height when
calling CImage::Create or CImage::CreateEx (or using the
orientation parameter if you call CImage::Attach).
Cheers
mark-r