|
|
Comments and Discussions
|
|
 |

|
Great article. However, in special cases where OnPaint() is called multiple times, it would be wise to check if the image has already been loaded, otherwise it will be loaded again.
|
|
|
|

|
Hi Joe,
With a little help from a friend I got your code to work, and it loads my PNGS nicely on Windows 7.
When I run my exe on Xp it crashes. However I can run the same code running from files without a problem.
Do you know of any reason why it could crash on xp?
Asimov
|
|
|
|

|
I haven't a clue, especially since it was developed on XP.
See if you can trace into the calls. I wonder if there is an issue with the resource(s).
|
|
|
|

|
Hi Joe,
Well I used xp at work you see so I haven't got it at home. Think I will have to install a virtual xp for test purposes.
However I am glad you told me that it was developed on Xp so in effect it should work fine.
I can't see any problems with my resource file on windows 7 though.
Thanks for the reply. Will let you know if I get it to work.
It is hard to find the problem because I use windows 7, but I wanted it to work on xp too you see.
Asimov
|
|
|
|
|
|

|
Is it necessary to use Bitmap? Can I change it to CImage?
What the advantage of using Bitmap?
|
|
|
|

|
As explained in the article, I used Bitmap so there was no dependency on MFC/ATL.
|
|
|
|

|
I see. Thank you for this article.
It's very helpful for me.
|
|
|
|

|
First of all, thanks a lot for you great submission - it really helps and the code is neat, I like it
I've made a small addition to CGdiPlusBitmap, though:
Gdiplus::Bitmap* operator->() const { ASSERT(m_pBitmap); return m_pBitmap; }
- just to make intellisense aware of the underlying Gdiplus::Bitmap* pointer.
modified on Wednesday, August 10, 2011 6:49 PM
|
|
|
|

|
@Joe: Very good article.
Couple of minor tidbits... If you add:
bool LoadFile(LPCTSTR pName) { return CGDIPlusBitmap::Load (pName); }
At the end of class CGdiPlusBitmapResource : public CGdiPlusBitmap class, you can load up files and take advantage of the Empty() when destructing.
Also, if you want to prevent your files from being locked until you end the app, change the code in CGdiPlusBitmap::Load(LPCTSTR pFile) to:
bool Load(LPCTSTR pFile)
{
Empty();
Gdiplus::Bitmap* pTemp = Gdiplus::Bitmap::FromFile(pFile);
bool bGood = false;
if (pTemp->GetLastStatus() == Gdiplus::Ok) {
m_pImage = new Gdiplus::Bitmap( pTemp->GetWidth(), pTemp->GetHeight());
if (m_pImage) {
Gdiplus::Graphics gTmp(m_pImage);
gTmp.DrawImage( pTemp, 0, 0, pTemp->GetWidth(), pTemp->GetHeight());
bGood = true;
}
}
delete pTemp;
return bGood;
}
|
|
|
|

|
Thank you. Your suggestion is good. In one program, I actually turn the Gdiplus bitmap into an HBITMAP with a dib section. (Not for locking reasons, but so it integrates easily with other code, so the Gdiplus stuff is isolated into a single DLL and so I could more easily port it to CE 6, which has an alternate to Gdiplus.)
|
|
|
|

|
Thank You !
I was looking for a way to display transparent PNGs from resources with GDI+ in my applications.
I just have to implement this to my WTL code and it's gonna be OK.
All the best,
Philippe Marechal
|
|
|
|

|
Thank you. Hope it goes well.
|
|
|
|

|
To get it working with VS2010 put #include <sdkddkver.h> on the line after #pragma once in stdafx.h.
modified on Friday, February 11, 2011 4:53 AM
|
|
|
|

|
Thanks. I'll have to remember that (and probably won't) if we move from VS 2008.
|
|
|
|

|
It should work ok on VS2008, so you could put it in now
|
|
|
|

|
This article is great and I've gotten the code to work (in VS 2010), and this question may be a little off the topic, but: Once I've loaded a PNG from the resource file: CGdiPlusBitmapResource* pBitmap = new CGdiPlusBitmapResource; if (pBitmap->Load(IDB_PNG , _T("PNG"))) How do I display that image, pBitmap, with a transparent background color? For example: say I want all black (RGB(0,0,0)) pixels or magenta (RGB(255,0,255)) pixels to be transparent so the background shows through, is there a way to display pBitmap (a png) so that a certain pixel displays transparent?
|
|
|
|

|
You must first create a PNG with a transparent/alpha layer. Several editors will let you do this. Paint Shop Pro lets you declare a color transparent during a save. A graphic artist can modify an image to have blending (to minimize jaggies.)
|
|
|
|

|
But is there any programming that needs to be done, as with a HBITMAP? (Creating XOR mask and ANDing it the original).
|
|
|
|
|

|
This is interesting. That image from Wikimedia Commons that you linked works fine, yet PNG images I created using GIMP fail to load for some reason. GIMP must be creating non-standard PNG files, or else there is some setting that I need to change when I export to PNG or something.
Update: If I open the file in Paint.NET and 'Save As' and explicitly specify to save as a 32-bit image then it loads fine. Still not sure where GIMP is messing up though.
--
http://www.coruscant.co.uk/
modified 17-Jul-12 8:18am.
|
|
|
|

|
Hi
I am displaying the png image as a splash screen before opening the main dialog box(before the dlg.domodel() call) and close it after the dialog box initialization. But the problem is that image get disapear if any other window get on top before the main dialog box initialize. I want to redraw the splash screen(png image) and put it on top of all windows till it get disapear. One more thing the image still appears even after i delete the image object and close the application. How should i refresh the screen to delete the splash screen.
|
|
|
|

|
You've run into a classic problem with writing a splash screen: you've created it too early and don't have a message loop. A splash screen shouldn't be put up first, before everything, but as soon as you have a functioning windows application with a main message loop. You then display something while loading/setting up the rest of the data and/or resources so the user knows things are working. There are several articles here on the subject.
|
|
|
|

|
This code has one essential flaw. Do not call:
::GlobalFree(m_hBuffer);
before returning. GDI+ continues using that memeory. Yes, I know... This is called Microsoft Instead, remove that line and change CreateStreamOnHGlobal to this:
if (::CreateStreamOnHGlobal(m_hBuffer, TRUE, &pStream) == S_OK)
that will do auto-release when the object is released.
|
|
|
|

|
::GlobalFree(m_hBuffer) is called only on error and I've never had an issue. I've had problems using TRUE in CreateStreamOnHGlobal which is why I didn't use it.
If you have a link for documentation on this, please post it.
|
|
|
|

|
http://msdn.microsoft.com/en-us/library/aa378980(VS.85).aspx[^]
fDeleteOnRelease [in]
A value that indicates whether the underlying handle for this stream object should be automatically freed when the stream object is released. If set to FALSE, the caller must free the hGlobal after the final release. If set to TRUE, the final release will automatically free the hGlobal parameter.
The way your code appears in CGdiPlusBitmapResource::Load above, ::GlobalFree(m_hBuffer) is called every time after the m_pBitmap is created. Also contrary to what you said I ran into a problem too when I was freeing the global memory used for the stream. The way GDI+ operates is that it does not maintain the global memory object it was passed to, which may lead to unpredictable results. For instance, freeing that memory worked under Windows 7, but the same code produced no errors but the result was a blank image under Vista.
One other way would be not to call ::GlobalFree(m_hBuffer) and use FALSE for fDeleteOnRelease in CreateStreamOnHGlobal but then a caller must also maintain the pointer to m_hBuffer and release it when it's not needed. This can lead to potential memory leak, thus calling CreateStreamOnHGlobal with fDeleteOnRelease set to TRUE would be your bext bet (well, unless you encapsulate it into a separate class and have RAII handle all the releases.)
|
|
|
|

|
Simply linking to documentation that I've already read isn't any help. Show something else to support your claim that "GDI+ continues using that memory" when the call to Gdiplus::Bitmap::FromStream(pStream) fails. Nothing you've stated supports that and completely ignores the actual code:
if (m_pBitmap)
{
if (m_pBitmap->GetLastStatus() == Gdiplus::Ok) <big>return true;</big>
Note the emphasis and comments. If the bitmap was created successfully, it returns true and does NOT unlock and free the global memory.
Also note that in the article, I clearly state "Before discussing the code itself, there is a caveat with GDI+ that must be addressed. With JPG, some TIFF and other formats, the original image information must be available at all times."
This code works. It is in production now. I just spent the morning working on one of the many programs that use it.
|
|
|
|
|

|
Well done Joe,
This is an optimal solution to resolve the issue. It would have been nice if MSDN would have indicated that there is an issue with formats other than BMP when loading a resource. Of course while working on a solution and while going over yours I also found that one can go about solving this problem by storing PNG etc from resource into a temporary file and then calling the Bitmap::FromFile(..). I implemented it and it works, though then I accepted your solution to be the optimal and chose your approach. Well done!
Arik
|
|
|
|

|
Howdo I get the attributes reequired for a png loaded from the resource?
I have a programwhich uses the setcolor and image attributes in drawing the picture input froma file. I want it work for a resource picture.
I can readit and use only simple drawimage with 2 parameters.I want to resize the image but idonot know how to get the size property from the
bitmap supplied by the class.
..
|
|
|
|

|
Use the GetWidth() and GetHeight() methods. (They are part of the GdiPlus Image class.)
|
|
|
|

|
Perhaps you are confused by the way I am wrapping the Gdiplus::Bitmap class. I modified the LoadPNG method to be as follows and the GetWidth() and GetHeight() both work, but do look confusing (this actually led me to modify the code even more, which I'll post soon, but the following illustrates the basic solution):
void CChildView::OnLoadPng()
{
CGdiPlusBitmapResource* pBitmap = new CGdiPlusBitmapResource;
if (pBitmap->Load(_T("IDB_BAY"), _T("PNG")))
{
CClientDC dc(this);
Gdiplus::Graphics graphics(dc);
graphics.DrawImage(*pBitmap, 0, 0, pBitmap->m_pBitmap->GetWidth(), pBitmap->m_pBitmap->GetHeight());
int y = pBitmap->m_pBitmap->GetHeight() + 10;
CSize size = dc.GetTextExtent(_T("M"), 1);
TCHAR buffer[128];
dc.TextOut(5, y, buffer, wsprintf(buffer, _T("Width: %u"), pBitmap->m_pBitmap->GetWidth()));
dc.TextOut(5, y + size.cy, buffer, wsprintf(buffer, _T("Height: %u"), pBitmap->m_pBitmap->GetHeight()));
}
else
{
AfxMessageBox("Failure loading image");
}
delete pBitmap;
}
And yes, GDI+ is poorly designed, but very capable, procedural library with a set of thin C++ classes to help simplify using it.
|
|
|
|
|

|
thanks a lot.I located what was happening....A event handler was trying to draw before its turn.
I will get a nice small picture, with transparency effects on the program window.
|
|
|
|

|
I have some bmp pics in res file and also import them into my project.
how can I call these bmp pics to fill a rectangle.
using GDI+.
and give a sample better
|
|
|
|
|

|
Isn't that the whole point of using a .PNG?
Best Regards,
Steve
|
|
|
|

|
Is this code works with ATL projects.
Requirement is IE toolbar to have a PNG icons instead of ICO.Now It works fine with ICO.I need to make it work for PNG.Any Help??
|
|
|
|

|
I can think of no reason this wouldn't work with ATL.
Anyone who thinks he has a better idea of what's good for people than people do is a swine.
- P.J. O'Rourke
|
|
|
|

|
great work. thanks.
can i save as 8 bit PNG file ? how ?
Today is a gift, that's why we call it present
|
|
|
|

|
Not sure. I've only used GDI+ to load and display images. For programs that need to manipulate them, I've had access to LeadTools. GDI+ has an Image::Save method, but I've never used it. If you're converting from one bit-per-pixel count to a lower one, you'll need to use some sort of dithering.
Anyone who thinks he has a better idea of what's good for people than people do is a swine.
- P.J. O'Rourke
|
|
|
|

|
In case you wonder (like I did) if it might be simpler to use CImage along similar lines and avoid GDI+
It indeed works using CImage::Load(IStream*) and the image is displayed.
The only catch is that the transparency (alpha channel) info from PNG image is either lost or (most probably) just ignored in CImage. Some articles recommend manually 'pre-multiplying' color values by alpha =)
Gdiplus::Bitmap works fine.
P.S. I wonder if it might be a reason why IE did not handle transparent PNGs until recently
|
|
|
|

|
Hi,
Nice job I use it on my app with PNG on a dialog: It works fine !
But I notice something when the image is loaded, the size was not the same as the original file, and blured as a redimensionned picture.
After a small investigation, I have found that my pictures made with Gimp were scaled in 72 DPI (Dot Per Inch) instead of 96 DPI in native. I scaled them back in 96 DPI, now everything looks find.
I didn't try in other format than PNG, and I didn't notice that anybody report a problem about this.
Anyway, this can be usefull for anyone that meet this kind of behaviour with images.
Bye.
|
|
|
|

|
First of all thank you very much for this code... I am trying to change the functionality in order to do something when the button is double clicked and to move the button or the dialog that contains the button when the left button is released, do you think is possible? could you give me some clues?
Thanks in advance
|
|
|
|

|
Hello, another more question, when the mouse is over the image the image is resized, it’s mean I see two images with different size, do you know which the problem could be?
Thank you very much
|
|
|
|

|
look like geneous code,very good
|
|
|
|

|
Instead of having to copy the resource data to global memory in order to use CreateStreamOnHGlobal, I wrote a simple COM class to allow the image to be loaded directly from the resource data.
////////////////////////////////////////////////////////////////////////////////
//
class MEMORY_STREAM
//
////////////////////////////////////////////////////////////////////////////////
:
IStream
{
ULONG m_ReferenceCount;
const PUCHAR m_Memory;
ULARGE_INTEGER m_Offset;
ULARGE_INTEGER m_Size;
public:
MEMORY_STREAM
(
const void* Memory,
DWORD Size
);
// IUnknown
STDMETHODIMP QueryInterface
(
REFIID InterfaceId,
void** Object
);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// ISequentialStream
STDMETHODIMP Read
(
void* Buffer,
ULONG ReadBytes,
ULONG* BytesRead
);
STDMETHODIMP Write
(
const void* Buffer,
ULONG WriteBytes,
ULONG* BytesWritten
);
// IStream
STDMETHODIMP Seek
(
LARGE_INTEGER Move,
DWORD Origin,
ULARGE_INTEGER* NewPosition
);
STDMETHODIMP SetSize
(
ULARGE_INTEGER NewSize
);
STDMETHODIMP CopyTo
(
IStream* Stream,
ULARGE_INTEGER CopyBytes,
ULARGE_INTEGER* BytesRead,
ULARGE_INTEGER* BytesWritten
);
STDMETHODIMP Commit
(
DWORD Flags
);
STDMETHODIMP Revert();
STDMETHODIMP LockRegion
(
ULARGE_INTEGER Offset,
ULARGE_INTEGER Size,
DWORD Type
);
STDMETHODIMP UnlockRegion
(
ULARGE_INTEGER Offset,
ULARGE_INTEGER Size,
DWORD Type
);
STDMETHODIMP Stat
(
STATSTG* Stats,
DWORD Flag
);
STDMETHODIMP Clone
(
IStream** Stream
);
};
////////////////////////////////////////////////////////////////////////////////
//
// MEMORY_STREAM Implementation
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODCALLTYPE MEMORY_STREAM::MEMORY_STREAM
(
const void* Image,
DWORD Size
)
//
////////////////////////////////////////////////////////////////////////////////
:
m_ReferenceCount(0),
m_Memory((PUCHAR)Image)
{
m_Offset.QuadPart = 0;
m_Size.QuadPart = Size;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP_(ULONG) MEMORY_STREAM::AddRef()
//
////////////////////////////////////////////////////////////////////////////////
{
return ++m_ReferenceCount;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP_(ULONG) MEMORY_STREAM::Release()
//
////////////////////////////////////////////////////////////////////////////////
{
ULONG ReferenceCount(--m_ReferenceCount);
if (m_ReferenceCount == 0)
{
delete this;
}
return ReferenceCount;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::QueryInterface
(
REFIID InterfaceId,
void** Object
)
//
////////////////////////////////////////////////////////////////////////////////
{
while (true)
{
if (IsEqualGUID(InterfaceId, __uuidof(IUnknown)))
{
*Object = static_cast<IUnknown*>(this);
break;
}
if (IsEqualGUID(InterfaceId, __uuidof(IStream)))
{
*Object = static_cast<IStream*>(this);
break;
}
*Object = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::Read
(
void* Buffer,
ULONG ReadBytes,
ULONG* BytesRead
)
//
////////////////////////////////////////////////////////////////////////////////
{
ULONG Length;
HRESULT Result;
if (m_Offset.QuadPart + ReadBytes > m_Size.QuadPart)
{
Length = (ULONG)(m_Size.QuadPart - m_Offset.QuadPart);
Result = S_FALSE;
}
else
{
Length = ReadBytes;
Result = S_OK;
}
CopyMemory(Buffer, m_Memory + (ULONG_PTR)m_Offset.QuadPart, Length);
m_Offset.QuadPart += Length;
if (BytesRead)
{
*BytesRead = Length;
}
return Result;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::Write
(
const void* Buffer,
ULONG WriteBytes,
ULONG* BytesWritten
)
//
////////////////////////////////////////////////////////////////////////////////
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::Seek
(
LARGE_INTEGER Move,
DWORD Origin,
ULARGE_INTEGER* NewPosition
)
//
////////////////////////////////////////////////////////////////////////////////
{
switch (Origin)
{
case STREAM_SEEK_SET:
m_Offset.QuadPart = Move.QuadPart;
break;
case STREAM_SEEK_CUR:
if (m_Offset.QuadPart + Move.QuadPart > m_Size.QuadPart)
{
return STG_E_INVALIDFUNCTION;
}
m_Offset.QuadPart += Move.QuadPart;
break;
case STREAM_SEEK_END:
if ((ULONGLONG)Move.QuadPart > m_Offset.QuadPart)
{
return STG_E_INVALIDFUNCTION;
}
m_Offset.QuadPart = m_Size.QuadPart - Move.QuadPart;
break;
default:
return STG_E_INVALIDFUNCTION;
}
if (NewPosition)
{
*NewPosition = m_Offset;
}
return S_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::SetSize
(
ULARGE_INTEGER NewSize
)
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::CopyTo
(
IStream* Stream,
ULARGE_INTEGER CopyBytes,
ULARGE_INTEGER* BytesRead,
ULARGE_INTEGER* BytesWritten
)
//
////////////////////////////////////////////////////////////////////////////////
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::Commit
(
DWORD Flags
)
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::Revert()
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::LockRegion
(
ULARGE_INTEGER Offset,
ULARGE_INTEGER Size,
DWORD Type
)
//
////////////////////////////////////////////////////////////////////////////////
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::UnlockRegion
(
ULARGE_INTEGER Offset,
ULARGE_INTEGER Size,
DWORD Type
)
//
////////////////////////////////////////////////////////////////////////////////
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::Stat
(
STATSTG* Stats,
DWORD Flags
)
//
////////////////////////////////////////////////////////////////////////////////
{
ZeroMemory(Stats, sizeof(*Stats));
if ((Flags & STATFLAG_NONAME) != STATFLAG_NONAME)
{
Stats->pwcsName = (LPOLESTR)CoTaskMemAlloc(sizeof(OLECHAR));
if (Stats->pwcsName)
{
*Stats->pwcsName = 0;
}
}
Stats->type = STGTY_STREAM;
Stats->cbSize = m_Size;
return S_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP MEMORY_STREAM::Clone
(
IStream** Stream
)
//
////////////////////////////////////////////////////////////////////////////////
{
return E_NOTIMPL;
}
////////////////////////////////////////////////////////////////////////////////
//
HRESULT CreateStreamOnResource
(
HMODULE Instance,
PCTSTR Type,
PCTSTR Name,
IStream** Stream
)
//
////////////////////////////////////////////////////////////////////////////////
{
HRSRC ResourceHandle = FindResource(Instance, Name, Type);
if (ResourceHandle)
{
DWORD Size = ::SizeofResource(Instance, ResourceHandle);
if (Size)
{
HGLOBAL Resource = LoadResource(Instance, ResourceHandle);
if (Resource)
{
const void* Data = LockResource(Resource);
MEMORY_STREAM* MemoryStream = new MEMORY_STREAM(Data, Size);
if (!MemoryStream)
{
return E_OUTOFMEMORY;
}
return MemoryStream->QueryInterface
(
__uuidof(IStream),
(void**)Stream
);
}
}
}
return HRESULT_FROM_WIN32(GetLastError());
}
You can then use the class like this to create a GDI+ bitmap:
IStream* Stream;
Bitmap* bitmap(NULL);
if
(
CreateStreamOnResource
(
Instance,
_T("PNG"),
MAKEINTRESOURCE(IDI_IMAGE),
&Stream
) == S_OK
)
{
bitmap = Gdiplus::Bitmap::FromStream(Stream);
Stream->Release();
}
Nick Acquaviva
|
|
|
|

|
I like it.
This could be an article.
Anyone who thinks he has a better idea of what's good for people than people do is a swine.
- P.J. O'Rourke
|
|
|
|

|
What about SHCreateMemStream from Shlwapi.h ?
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
|
A class to facilitate loading JPG and PNG files from resources using GDI+
| Type | Article |
| Licence | CPOL |
| First Posted | 20 Jan 2003 |
| Views | 466,719 |
| Downloads | 8,964 |
| Bookmarked | 136 times |
|
|