|

Updates
Aug 23, 2003 - Fixed transparent drawing bug
Introduction
After I wrote my CFileEditCtrl edit control, I got a request to place an image on the browse button, and then another request to make the control flat and hot to the mouse. That was becoming quite a task, searching on this site and www.codeguru.com for drawing code that would draw images, either bitmaps or icons, either normally or transparently, grey-scaled or disabled, possibly stretched or at least centered on the button. So after finding some code that solved one bit or another (but not quite the way I wanted), I put them together, filled in the pieces that were missing and came up with the CPJAImage class. While the reason I wrote the class was for the browse button, I thought it was a shame not to share it with you as yet another separate image rendering class.
Acknowledgments
Jean-Edouard Lachand-Robert for his DitherBlt() function.
Paul Reynolds for the "True Mask" transparency function.
Zafir Anjum and Guy Gascoigne for their greyscale algorithm.
Using the CPJAImage class
The first step is to include the PJAImage.h and PJAImage.cpp files in your project. Then after you have loaded or created your bitmap or icon object in the normal fashion, select it into the CPJAImage object using the SetImage() function. CBitmap Bitmap;
Bitmap.LoadBitmap(IDB_BITMAP1);
CPJAImage PJAImage;
PJAImage.SetImage(Bitmap, PJAI_BITMAP);
Then when you are ready to draw the image, use the DrawImage() function to draw the image onto the given device context. PJAImage.DrawImage(pDC, left, top, width, height, DrawFlags);
The drawing flags you specify in the DrawImage() function determine how the image is drawn.
User functions
CPJAImage::CPJAImage()
The class constructor creates an empty CPJAImage object.
CPJAImage::~CPJAImage()
The class destructor destroys the image handle
void CPJAImage::DrawImage(CDC *pToDC, int x, int y, int w, int h, DWORD DrawFlags = 0)
The DrawImage() does the actual drawing.
Parameters
pToDC |
A pointer to the device context the image is to be drawn on. |
x |
The left side of the bounding rectangle where the image is to be drawn |
y |
The top of the bounding rectangle |
w |
The width of the bounding rectangle |
h |
The height of the bounding rectangle |
DrawFlags |
The flags used to control the drawing |
Flags used in the DrawFlags parameter
|
PJAI_CENTERED
|
Draws the image centered on the supplied rectangle. If the image is larger than the rectangle, it will be clipped. This flag can not be used with the PJAI_STRETCHED flag. |
PJAI_STRETCHED |
Draws the image so that it fills the supplied rectangle. Bitmaps are stretched using the StretchBlt() API function. Icons are stretched using the DrawIconEx() API function. This flag can not be used with the PJAI_CENTERED flag. |
PJAI_DISABLED |
Draws the image as disabled (3D Monochrome). This flag can not be used with the PJAI_GRAYSCALE flag. |
PJAI_GRAYSCALE |
Draws the image in grayscale. This flag can not be used with the PJAI_DISABLED flag. |
PJAI_TRANSPARENT |
Draws the image transparently. If the transparent color is not specified with the SetTransparentColor() function, the color at the top left corner (GetPixel(0, 0)) of the image is used as the transparent color. This flag has no effect if the image is an icon, as icons are transparent by default. |
CSize CPJAImage::GetSize()
Returns a CSize object that contains the size of the image currently contained in the CPJAImage.
BOOL CPJAImage::SetImage(HANDLE hImage, DWORD Flags)
Selects the supplied image into the CPJAImage object. Returns TRUE if the supplied image was successfully selected, FALSE if not.
Parameters
hImage |
The HANDLE of the image, can be a HBITMAP (DIB or DDB) or a HICON |
Flags |
Specifies the image flags. |
Flags used by the SetImage() function
PJAI_BITMAP |
The given handle is an HBITMAP |
PJAI_ICON |
The given handle is a HICON |
PJAI_AUTODELETE |
The given handle will be deleted and the memory freed when a new image is set or the CPJAImage object is deleted. If this flag is not set, the user of this class is responsible for freeing the image handle when it is no longer needed |
COLORREF CPJAImage::SetTransparentColor(COLORREF clr = CLR_DEFAULT)
Sets the color to be used as the transparent color for the bitmap image. Returns the previous transparent color. If CLR_DEFAULT (0xFF000000) is set as the transparent color, the color of the pixel at the top left corner ( GetPixel (0, 0) ) is used as the transparent color.
Parameters
clr |
The transparent color |
Demonstration Application
Be sure to check out the demo app to see the CPJAImage class in action. While the demo has a preloaded image in it, you can drag 'n drop your favorite picture files ( *.ico, *.bmp, *.jpg, or *.gif ) onto the demo app.
To change the transparent color, click the Set Color button, and then use the mouse to select the color from the picture displayed.
Enjoy!
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 42 (Total in Forum: 42) (Refresh) | FirstPrevNext |
|
 |
|
|
I have now used this for the 2nd time and once again it performed magnificantly. A really useful and reliable class. Thanks.
Mike Dear NYT - the fact is, the founding fathers hung traitors. Vincent Reynolds: My opposition is as enlightened as your support, jackass. dennisd45: My view of the world is slightly more nuanced dennisd45 (the NAMBLA supporter) wrote: I know exactly what it means. So shut up you mother killing baby raper.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
PJ,
When I run your demo app on my system instead of your image having a transparent background it has red cross-hatched lines. (when i click the "transparent" button of course)
Do you have any idea what could be causing this?
im using VS2003.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
No bug. If you look in CPJAImage_demoDlg::OnPaint() you will see I fill the background of the picture with a red cross hatch brush. I just did not update the image included with the article the last time I updated the code.
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
"Obviously ??? You're definitely a superstar!!!" - mYkel - 21 Jun '04
"There's not enough blatant self-congratulatory backslapping in the world today..." - HumblePie - 21 Jun '05
Within you lies the power for good - Use it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
sorry to bug you PJ - its a great class, but I have a question. I notice your example uses a Picture control to provide the co-ordinates/CRect/DC to draw the bitmap onto (Ive never done much with drawing, so I hope Im using the correct words) ..
I did this using a Picture control, and it works well - the only issue is the Picture control has a border I cant get rid of - ideally I'd like to use a Static for the 'placeholder' and get its DC .. but I seem to get unhandled exceptions when I replace the Picture control with a Static control ..
Is there anything obvious about using a Static that I should be aware of ? - or perhaps I made a simpler mistake - I guess I'll go over it again and see what I can find ...
[Edited after a play in my lunch-break] I think Ive found the answer - I think its the 'Frame' option I have set for the CPicture Property - I'll get to test it tonight ..... sorry if Ive messed you around
Thanks anyway, Garth
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You can use a static control for the drawing. Just derive a class from CStatic and overide it's OnPaint member function. I used a frame control in the demo because I wanted to keep all the drawing code in the dialog class. It saved me from having to pass all the drawing info from the button clicks to a seperate CStatic class.
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
"Obviously ??? You're definitely a superstar!!!" - mYkel - 21 Jun '04
"There's not enough blatant self-congratulatory backslapping in the world today..." - HumblePie - 21 Jun '05
Within you lies the power for good - Use it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
allmost 6 !!
thanks you vary much.
I have no imidiate work for your class, but i have been able to isolate the very much needed code to create this: HICON CreateGrayscaleIcon(HICON hIcon);
thanks a million.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You are very welcome
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
"Obviously ??? You're definitely a superstar!!!" - mYkel - 21 Jun '04
"There's not enough blatant self-congratulatory backslapping in the world today..." - HumblePie - 21 Jun '05
Within you lies the power for good - Use it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi PJ,
in short I want to convert a 32bpp (RGBA) icon to grayscale while runtime without losing the alpha channel. If I drop such an icon into your test app and convert it to grayscale the alpha channel gets lost.
Taking a short look into your code shows that you make at last a RGB copy operation of the grayed pixels. The alpha channel is not set and so the formerly transparent parts become opaque.
Is there a way to convert 32bpp icons to grayscale without losing the alpha channel AND without using GDI+? I have code for doing this with GDI+ but I cannot use it yet in my project.
Thanks in advance!
Regards, mykel
If they give you lined paper, write the other way!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
After some nightly debugging I happily fixed the problem 
The following part of CPJAImage::GrayScale() causes the problem:
int nGray = Gray(GetBValue(*dst), GetGValue(*dst), GetRValue(*dst)); *dst = (DWORD)RGB(nGray, nGray, nGray); // *** this kills the alpha channel *** //
The RGB macro fills bit 0-24 with (BYTE)nGray. Bit 25-32 are filled with zeros and afterwards copied back into dst. So at this point the formerly alpha channel byte gets zero which is opaque.
Here is my fix:
Add the following macro at the top of pjaimage.h:
#define RGBA(r,g,b,a) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16))|(((DWORD)(BYTE)(a))<<24)) This macro is similar to the RGB macro but it also accepts an alpha channel byte.
So everything is fine if we do:
int nGray = Gray(GetBValue(*dst), GetGValue(*dst), GetRValue(*dst)); BYTE* pAlpha = ((BYTE*)dst)+3; *dst = (DWORD)RGBA(nGray, nGray, nGray, *pAlpha);
That's all. Grayscaling a 32bpp icon doesn't kill the alpha channel anymore. Hope you like it!
Regards, mykel
If they give you lined paper, write the other way!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks Mykel.
Having others contribute bug fixes to bugs that the authors miss is part of what makes CP so great.
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
"Obviously ??? You're definitely a superstar!!!" mYkel - 21 Jun '04
Within you lies the power for good - Use it! Honoured as one of The Most Helpful Members of 2004
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Mykel, not that it really matters, but I changed the code to*dst = (*dst & 0xff000000) | ((DWORD)RGB(nGray, nGray, nGray) & 0x00ffffff); Saves having to add the RGBA macro to the file.
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
"Obviously ??? You're definitely a superstar!!!" mYkel - 21 Jun '04
Within you lies the power for good - Use it! Honoured as one of The Most Helpful Members of 2004
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi PJ,
sounds good to me.
I also forgot to thank you for another nice class! 
Have a nice day, mykel
If they give you lined paper, write the other way!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
First all thanks for the class. I found it quite handy drawing images transparently. I am using it in our latest project at work, but have come across a problem.
There seems to be some confusion (or my lack of understand) over the use w and h parameter of the DrawImage function, which lead to what I suspect to be the weird artifacts generated of drawing two images on the same DC.
What I have tried to do is draw two transparent images adjacently on my view. I have read that there was fix to a similar (or maybe the same) problem in 2003. I have in my code something like this...
CPJAImage img1; CPJAImage img2;
img1.SetImage(...); img2.SetImage(...);
// Note I treat w and h as logical offset of x and y respectively not as the width and height of the rect.
CSize img1Size = img1.GetSize(); CSize img2Size = img2.GetSize();
CPoint offset(10,10); // For example
img1.DrawImage(pDC, offset.x, offset.y, offset.x + img1Size.cx, offset.y + img1Size.cy, CPJA_TRANSPARENT); img2.DrawImage(pDC, offset.x + img1Size.cx, offset.y + img1Size.cy, offset.x + img1Size.cx + img2Size.cx, offset.y + img1Size.cy + img2Size.cy, CPJA_TRANSPARENT);
// This produces strange artifacts when my view is resized. // So I changed the code in these places to make it work. // In the DrawImage function.
//old BackGroundBitmap.CreateCompatibleBitmap(pDC, w, h); ... BackGroundDC.BitBlt(0, 0, w, h, pDC, x, y, SRCCOPY); ... TransparentDC.BitBlt(0, 0, w, h, &BackGroundDC, left, top, SRCCOPY);
//new changes BackGroundBitmap.CreateCompatibleBitmap(pDC, width, height); ... BackGroundDC.BitBlt(0, 0, width, height, pDC, x, y, SRCCOPY); ... TransparentDC.BitBlt(0, 0, width, height, &BackGroundDC, 0, 0, SRCCOPY);
Im not sure how these changes adversely affects the rest of the code. I have also added my own function to load a picture by a file name. I can post it if anyone wants it.
Thanks, Samson
Coding Monkey
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Sawjai wrote: TransparentDC.BitBlt(0, 0, width, height, &BackGroundDC, 0, 0, SRCCOPY);
This seems to be the only fix that is necessary as the painting is clipped to the supplied rectancle if width and height are bigger than w and h.
Thanks for the bug fix
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
"Obviously ??? You're definitely a superstar!!!" mYkel - 21 Jun '04
Within you lies the power for good - Use it! Honoured as one of The Most Helpful Members of 2004
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi PJ Arends,
Oppps, I had a mistake in my DrawImage parameters that I posted.  I will update it now.
Cheers, Samson
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I am new to VC++ (recently converted from Borland C++ Builder).
Does any one any suggestion/code example on how to make CPJAImage a Drag & Drop Control in VC++ 7.1? I want to replace Image control with CPJAImage. I want to load image during design time (Image property) and also a transparent property.
Len Richter
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
CPJAImage is not a windows control (It is not a wrapper around an HWND), so I would say just call CPJAImage::DrawImage() from your regular control's WM_PAINT handler. Or you can do as I did in the demo app, which does support drag 'n drop.
Sonork 100.11743 Chicken Little
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
Within you lies the power for good - Use it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I did try using Onpaint but I am missing something because I am getting a black rectangle (ie. like iti si not coping the BMP image from the Image control). Here is my code. Do you see anything I am missing?
private: System::Void ImagePaint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) {CPJAImage PJAImage; PictureBox *pPictureBox = dynamic_cast(sender);
// Get handle to device context. HDC hDC = (HDC)e->Graphics->GetHdc().ToInt32(); CDC* pDC = CDC::FromHandle(hDC); COLORREF clr = pDC->GetPixel(CPoint(0,0)); PJAImage.SetImage(hDC, PJAI_BITMAP); PJAImage.SetTransparentColour(clr);//RGB(0,0,0));
/ CDC DC; DC.CreateCompatibleDC(pDC); int save = DC.SaveDC(); SelectObject(background); CBitmap bmp; bmp.CreateCompatibleBitmap(pDC, pPictureBox->Image->Width, pPictureBox->Image->Height); DC.SelectObject(bmp); PJAImage.DrawImage(&DC, 0, 0, pPictureBox->Image->Width, pPictureBox->Image->Height, PJAI_TRANSPARENT | PJAI_STRETCHED); pDC->BitBlt(0, 0, pPictureBox->Image->Width, pPictureBox->Image->Height, &DC, 0, 0, SRCCOPY); DC.RestoreDC(save); DC.DeleteDC(); // Release handle to device context. e->Graphics->ReleaseHdc(hDC); //ReleaseDC(pDC); }
Len
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Len2020 wrote: HDC hDC = (HDC)e->Graphics->GetHdc().ToInt32();
Len2020 wrote: PJAImage.SetImage(hDC, PJAI_BITMAP);
Here is your problem, SetImage requires a HBITMAP or HICON as the first parameter, you are passing in a HDC as a HBITMAP.
Sonork 100.11743 Chicken Little
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
Within you lies the power for good - Use it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
PIA WROTE: Here is your problem, SetImage requires a HBITMAP or HICON as the first parameter, you are passing in a HDC as a HBITMAP.
I still can not get this to work due to my lack on knowledge between GDI and GDI++. I have searched on the web to get an understanding on DC and HBITMAP by it is still fuzzy to me. The problem is the handle HBITMAP and trying to get it. I did use HBITMAP operator but I am still get a black box only. I am still missing something obvious. Could you by chance correct my code. I would really appreciate it.
private: System::Void ImagePaint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) {CPJAImage PJAImage; PictureBox *pPictureBox = dynamic_cast(sender);
// Get handle to device context. HDC hDC = (HDC)e->Graphics->GetHdc().ToInt32(); CDC *pCDCImage = CDC::FromHandle(hDC); // Create an in-memory DC compatible with the display DC we're using to paint. CDC cdcMemory; cdcMemory.CreateCompatibleDC(pCDCImage); int save = cdcMemory.SaveDC(); COLORREF clr = cdcMemory.GetPixel(CPoint(0,0)); CBitmap bmp; bmp.CreateCompatibleBitmap(&cdcMemory, pPictureBox->Image->Width, pPictureBox->Image->Height); // Select Bitmap into the new Memory DC. cdcMemory.SelectObject(bmp);
PJAImage.SetImage(bmp.operator HBITMAP(), PJAI_BITMAP); //PJAImage.SetTransparentColour(clr);//RGB(0,0,0)); PJAImage.DrawImage(&cdcMemory, 0, 0, pPictureBox->Image->Width, pPictureBox->Image->Height, PJAI_TRANSPARENT | PJAI_STRETCHED); // Copy the bits from the in-memory DC into the on-screen DC to actually do the painting. pCDCImage->BitBlt(0, 0, pPictureBox->Image->Width, pPictureBox->Image->Height, &cdcMemory, 0, 0, SRCCOPY); cdcMemory.RestoreDC(save); cdcMemory.DeleteDC(); // Release handle to device context. e->Graphics->ReleaseHdc(hDC); }
Regards, Len Richter
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Len2020 wrote: private: System::Void ImagePaint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) {CPJAImage PJAImage; PictureBox *pPictureBox = dynamic_cast(sender);
// Get handle to device context. HDC hDC = (HDC)e->Graphics->GetHdc().ToInt32(); CDC *pCDCImage = CDC::FromHandle(hDC);
// Create an in-memory DC compatible with the display DC we're using to paint. CDC cdcMemory; cdcMemory.CreateCompatibleDC(pCDCImage); int save = cdcMemory.SaveDC();
OK, everything looks good up to this point. But the cdcMemory device context will only have a 1 x 1 pixel monochrome bitmap selected into at this point.
Len2020 wrote: COLORREF clr = cdcMemory.GetPixel(CPoint(0,0));
Because cdcMemory contains a monochrome bitmap, clr will be either black or white (probably black).
Len2020 wrote: CBitmap bmp; bmp.CreateCompatibleBitmap(&cdcMemory, pPictureBox->Image->Width, pPictureBox->Image->Height);
I would have used the original pCDCImage here instead of the cdcMemory as the cdcMemory is currently monochrome.bmp.CreateCompatibleBitmap(pCDCImage, ... Len2020 wrote: // Select Bitmap into the new Memory DC. cdcMemory.SelectObject(bmp);
PJAImage.SetImage(bmp.operator HBITMAP(), PJAI_BITMAP);
Your PJAImage object now contains a monchrome bitmap that is initialized with random data, probably just a black box.
Len2020 wrote: //PJAImage.SetTransparentColour(clr);//RGB(0,0,0)); PJAImage.DrawImage(&cdcMemory, 0, 0, pPictureBox->Image->Width, pPictureBox->Image->Height, PJAI_TRANSPARENT | PJAI_STRETCHED);
Again, the cdcMemory still contains only a 1 x 1 pixel monochrome bitmap as it's drawing surface.
Len2020 wrote: // Copy the bits from the in-memory DC into the on-screen DC to actually do the painting. pCDCImage->BitBlt(0, 0, pPictureBox->Image->Width, pPictureBox->Image->Height, &cdcMemory, 0, 0, SRCCOPY); cdcMemory.RestoreDC(save); cdcMemory.DeleteDC(); // Release handle to device context. e->Graphics->ReleaseHdc(hDC); }
You are seeing a black box because that is what you are telling it to do.
That being said, I am wondering why you are trying to use this class in a .NET app? While it will certainly work, I would think you would be better off using the .NET runtime.
I do not know how you are initializing the Image property of the PictureBox object that you are using here, but System.Drawing.Image is the base class for System.Drawing.Bitmap, and the Bitmap class has a method MakeTransparent(Color) that you could use.private: System::Void ImagePaint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { PictureBox *pPictureBox = dynamic_cast< PictureBox * >(sender); Bitmap *pBitmap = dynamic_cast< Bitmap * >(pPictureBox->Image);
pBitmap->MakeTransparent(pBitmap->GetPixel(0, 0));
e->Graphics->DrawImage(pBitmap, 0, 0); }
Note: I do not do .NET, so this code is totally untested, but you should get the idea
Sonork 100.11743 Chicken Little
"You're obviously a superstar." - Christian Graus about me - 12 Feb '03
Within you lies the power for good - Use it!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Pete.
The problem in using the .Net class example is the PictureBox. It does not support true transparent. What I mean is the Image is transprent but you still show the PictureBox control. Boralnd had a PictureBox but also just a Image control and basically that is what I want. In further thinking about it using OnPaint and your control will not work either because the PictureBox will still be shown. I can not find a way to make the the PictureBox transparent also. I tried Form->TransprentKey and set Control->BackColor but that also make the form underneath transparent and therfeore see the desktop. Man, I though M$ would had a simple drag & drop ImageControl. I do not know how to create my own because still newbie to VS 2003. Do you have and further suggestions?
Len
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|