Click here to Skip to main content
15,881,204 members
Articles / Multimedia / GDI+

Loading JPG & PNG resources using GDI+

Rate me:
Please Sign up or sign in to vote.
4.84/5 (76 votes)
6 Sep 2004CPOL4 min read 870.9K   20.2K   157  
A class to facilitate loading JPG and PNG files from resources using GDI+
<style type="text/css">
<!--
	button.toolbar {
		border: 1px;
		border-style: solid;
		background-color: white;
		border-color: white;
		font-family: Verdana, Arial, Tahoma, "MS Sans Serif", sans-serif;
		font-size: 9pt;
	}
	div.toolbar {
		background-color: white;
		border-color: #FF9900;
		padding: 1px 2px 2px 2px;
		}
-->
</style>

<script language=javascript>
<!--
function InsertText(strInsertText) 
{
	if (Submitted) return false;
	if (strInsertText <= 0) return false;
	var objTextArea = document.SubmitForm.ArticleText;
	if (objTextArea)
	{
		if (document.selection && document.selection.createRange)
		{
			objTextArea.focus();
			var objSelectedTextRange = document.selection.createRange();
			var strSelectedText = objSelectedTextRange.text;
			objSelectedTextRange.text = strInsertText + strSelectedText;
		}
		else
		{
			objTextArea.value += strInsertText;
			objTextArea.focus();
		}
	}
	return false;
}

function QuoteText(author)
{
	var txt = "";
	if (document.getSelection) txt = document.getSelection();
	else if (document.selection && document.selection.createRange) txt = document.selection.createRange().text;
	else return;

	var bUseHTML = true;	//(document.MessageForm.Format && !document.MessageForm.Format.checked);
		
	if (txt != "") 
	{
		var Quote = "";
		
		var objTextArea = document.SubmitForm.ArticleText;
		if (objTextArea && objTextArea.value != "")	Quote += "\n";	
		
		if (bUseHTML) Quote += "<small><b>";
		Quote += (author + " wrote:");
		if (bUseHTML) Quote += "</b></small>\n<i>";
		else Quote += "\n";
		Quote += txt;
		if (bUseHTML) Quote += "</i>";
		Quote += "\n\n";
		
	
		InsertText(Quote);
	
	}
	else
		alert('You did not select any text to quote');
}

//-->
</script>

<script language="JavaScript1.2" type="text/javascript">
<!--
// Based on the work of Jason Jystad. Endless kudos and thanks Jason.

function ToggleWrapSelection(strTag) 
{
	if (Submitted) return false;
	if (strTag.length <= 0) return false;
	
	var StartTag = "<" + strTag + ">";
	var EndTag   = "</" + strTag + ">";
	
	var objTextArea = document.SubmitForm.ArticleText;
	if (objTextArea)
	{
		if (document.selection && document.selection.createRange)
		{
			objTextArea.focus();
			var objSelectedTextRange = document.selection.createRange();
			var strSelectedText = objSelectedTextRange.text;
			var nStrtLen = StartTag.length;
			var nEndLen  = EndTag.length;
			var nSelLen  = strSelectedText.length;
			
			if (strSelectedText.substr(0, nStrtLen) == StartTag &&
			    strSelectedText.substr(nSelLen-nEndLen) == EndTag)
			{
				objSelectedTextRange.text = strSelectedText.substr(nStrtLen, nSelLen-nStrtLen-nEndLen);
			}
			else
				objSelectedTextRange.text = StartTag + strSelectedText + EndTag;
				
			if (strSelectedText.length == 0)
			{
				objSelectedTextRange.move("character", -(strTag.length + 3));
				objSelectedTextRange.select();
			}
			objTextArea.focus();
		}
		else
		{
			var strAppendText = StartTag + EndTag;
			objTextArea.value += strAppendText;
			objTextArea.focus();
		}
	}
	return false;
}

function InLinkErAte(NewWindow) 
{
	if (Submitted) return false;
	var objTextArea = document.SubmitForm.ArticleText;
	if (objTextArea)
	{
		if (document.selection && document.selection.createRange)
		{
			objTextArea.focus();
			var objSelectedTextRange = document.selection.createRange();
			var strSelectedText = objSelectedTextRange.text;
			var objRegEx = new RegExp("(ht|f)tps?:\/\/");
			var strProtocol = "";
			if (strSelectedText.length == 0)
			{
				if (NewWindow) 
				{
					alert("Please select a URL within the message text to convert");
					return false;
				}
			}
			else
			{
				if (!objRegEx.test(strSelectedText)) strProtocol = "http://";
			}
			
			var URI = strProtocol + strSelectedText;

			var offset = 0;
			if (NewWindow)
			{
				objSelectedTextRange.text = "<a href=\"" + URI + "\">" + strSelectedText
											+ "</a>[<a target=_blank title='New Window' href=\"" 
											+ URI + "\">^</a>]";
				offset = 8 + URI.length + 47;
			}
			else
			{
				objSelectedTextRange.text = "<a href=\"" + URI + "\">"
											+ strSelectedText + "</a>";
				offset = 4;
			}
       
			if (strSelectedText.length == 0)
			{
				objSelectedTextRange.move("character", -offset);
				objSelectedTextRange.select();
			}
		}
		else
		{
			var strAppendText = "<a href=\"\"" + strTarget + "></a>";
			objTextArea.value += strAppendText;
			objTextArea.focus();
		}
	}
	return false;
}

function ToggleHTML() 
{
	if (Submitted) return false;
	var objTextArea = document.SubmitForm.ArticleText;
	if (objTextArea)
	{
		if (document.selection && document.selection.createRange)
		{
			objTextArea.focus();
			var objSelectedTextRange = document.selection.createRange();
			var strNewText = objSelectedTextRange.text;
			strNewText = strNewText.replace(/&/g, "&amp;");
			strNewText = strNewText.replace(/>/g, "&gt;");
			strNewText = strNewText.replace(/</g, "&lt;");
			objSelectedTextRange.text = strNewText;
		}
		else
		{
			alert("This function is presently only compatible with IE4+ on Win32 platforms.");
		}
	}
	return false;
}    

function InitToolbar(objITToolbar) 
{
	if (!objITToolbar) { return false; }
	
	var intITCounter = 0;
	var nButtons = objITToolbar.childNodes.length;
	var objITButton;
	var intITTotalWidth = 0;
	
	for (intITCounter = 0; intITCounter < nButtons; intITCounter++) 
	{
		objITButton = objITToolbar.childNodes[intITCounter];
		if (objITButton.type == "button" || objITButton.type == "submit")
		{
			intITTotalWidth += objITButton.offsetWidth;
			
			objITButton.onmouseover = function(){
				if (true || this.name != "pinable" || (this.name == "pinable" && this.style.borderStyle != "inset")) {
					this.style.borderWidths = "1px, 1px, 1px, 1px";
					this.style.borderColor = "#ff9900";
					this.style.backgroundColor = "#ffcc99";
					this.style.borderStyle = "outset";
				}
			}
			objITButton.onmouseout = function(){
				if (this.name != "pinable" || (this.name == "pinable" && this.style.borderStyle != "inset")) {
					this.style.borderWidths = "1px, 1px, 1px, 1px";
					this.style.borderColor = "white";
					this.style.backgroundColor = "white";
					this.style.borderStyle = "solid";
				}
			}
			objITButton.onmousedown = function(){
				this.style.borderWidths = "1px, 1px, 1px, 1px";
				this.style.borderColor = "#ff9900";
				if (this.name == "pinable" && this.style.borderStyle == "inset") {
					this.style.borderStyle = "outset";
				}
				else {
					this.style.borderStyle = "inset";
				}
			}
			objITButton.onmouseup = function(){
				if (this.name != "pinable" || (this.name == "pinable" && this.style.borderStyle != "inset")) {
					this.style.borderWidths = "1px, 1px, 1px, 1px";
					this.style.borderColor = "#ff9900";
					this.style.borderStyle = "outset";
				}
				//window.focus();
			}
		}
	}
	//objITToolbar.style.width = intITTotalWidth + 20;
}
-->
</script>
<!-- HTML for article "Loading JPG & PNG resources using GDI+" by Joe Woodbury
     URL: http://www.codeproject.com/cs/media/cgdiplusbitmap.asp

     Article content copyright Joe Woodbury
     All formatting, additions and alterations Copyright � CodeProject, 2003
-->
<!----------------------------- Ignore ----------------------------->
<link rel="stylesheet" type=text/css href="http://www.codeproject.com/styles/global.css">
<p><b>Please choose 'View Source' in your browser to view the HTML, or  
File | Save to save this file to your hard drive for editing.</b></p>
<hr size=1 noshade>
<!----------------------------- Ignore ----------------------------->


<!----------------------------- Article Starts ----------------------------->


<ul class=download>
<li><a href="cgdiplusbitmap/cgdiplusbitmap_demo.zip">Download demo project - 122 Kb </a></li>
<li><a href="cgdiplusbitmap/cgdiplusbitmap_src.zip">Download source - 1 Kb</a></li>
</ul>

<h2>Introduction</h2>

<p>Recently, I needed to display some JPG and PNG files. I had an old
copy of LeadTools and the open source libraries for both formats, but wanted my executable
to be as small as possible. So I decided to give GDI+ a try. I quickly found that
I'm not a big fan of GDI+--I find it poorly designed and very quirky--but it worked
well for my purposes until I discovered, to my horror, that GDI+ cannot load JPG or
PNG files stored as resources!</p>

<p>Like, I'm sure, other developers facing this issue, I disbelieved the
documentation and tried <code>Bitmap::FromResource</code> to no avail. While perusing the
Bitmap methods available, I ran across <code>Bitmap::FromStream</code>.</p>

<p>After a bit of testing and several errors, due mostly to the horrible GDI+
documentation, I came up with working code. After a night of rest, I decided
to encapsulate the code in a simple class to ensure memory got freed. The result
were two classes: <code> CGdiPlusBitmap</code> and <code>CGdiPlusBitmapResource</code>.</p>

<h2>The Gotcha</h2>

<p>Before discussing the code itself, there is a caveat with GDI+ that must be addressed.
In GDI+, the original image information must be available at all times--it does not double
buffer the data. In other words, if you open a bitmap using <code>Bitmap::FromFile</code>,
you cannot delete or otherwise change that file while the image is open. This same
restriction applies to the <code>CGdiPlusBitmapResource</code>.</p>

<p>In my second iteration of the code, I was creating the Bitmap using <code>Bitmap::FromStream</code>
and then freeing the global memory. Everything appeared fine until I attempted to draw
the bitmap, whereupon I got an generic error. Remembering the caveat about <code>Bitmap::FromFile</code>
I realized I needed to retain the allocated global memory, thus the desire for a class
that automatically frees that memory.</p>

<h2>The Class</h2>

<p>For kicks, I created two classes, with the base class being a very simple encapsulation
of Bitmap. I suppose that if I ever had the patience and desire, I could extend that class.
(Some may be curious why I didn't simply subclass the ATL class <code>CImage</code>. It's because the
<code>CGdiPlusBitmapResource</code> has to be used in code that doesn't use MFC or ATL. However, the code
is so simple, it could easily be modified to use <code> CImage</code> as the base class.)</p>

<p>I'm not even going to bother going over the <code> CGdiPlusBitmap</code> class except to say that it
has a single, public, data member <code>Bitmap* m_pBitmap</code>. (In the class I prefaced
the GDI+ objects with the <code> Gdiplus</code> namespace in case the developer doesn't want to declare
<code>using namespace Gdiplus;</code>.)</p>

<p>The <code> CGdiPlusBitmapResource</code> class has several constructors and several overloaded
<code>Load</code> functions. The overloaded functions simply allow lazy programmers,
like myself, to not have to type <code>MAKEINTRESOURCE</code>. The main <code>Load</code>
function takes the resource name and type as strings and is the key to the class. This
code follows in its entirety:</p>

<pre>
inline
bool CGdiPlusBitmapResource::Load(LPCTSTR pName, LPCTSTR pType, HMODULE hInst)
{
    Empty();

    HRSRC hResource = ::FindResource(hInst, pName, pType);
    if (!hResource)
        return false;
    
    DWORD imageSize = ::SizeofResource(hInst, hResource);
    if (!imageSize)
        return false;

    const void* pResourceData = ::LockResource(::LoadResource(hInst, hResource));
    if (!pResourceData)
        return false;

    m_hBuffer  = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
    if (!m_hBuffer)
        return false;

    void* pBuffer = ::GlobalLock(m_hBuffer);
    if (!pBuffer)
    {
        ::GlobalFree(m_hBuffer);
        m_hBuffer = NULL;
        return false;
    }

    CopyMemory(pBuffer, pResourceData, imageSize);

    IStream* pStream = NULL;
    if (::CreateStreamOnHGlobal(m_hBuffer, TRUE, &amp;pStream) == S_OK)
    {
        m_pBitmap = Gdiplus::Bitmap::FromStream(pStream);
	    pStream-&gt;Release();
    }

    return m_pBitmap &amp;&amp; m_pBitmap-&gt;GetLastStatus() == Gdiplus::Ok;
}</pre>

<p>I find the code very self explanatory, though those that know the return
value of <code>::LoadResource</code> is an <code> HGLOBAL</code> may find the apparent
double copy using <code>CopyMemory</code> confusing. In brief
<code>CreateStreamOnHGlobal</code> requires a <code> HGLOBAL</code> handle
allocated by <code> GlobalAlloc</code> using the <code> GMEM_MOVEABLE</code> flag.</p>

<h2>The Demo</h2>

<p>The demo was thrown together with Visual Studio .NET 2002 (VC++ 7.0) and
was tested under Windows XP. If you want to compile it with VC++ 6.0, you
can convert the project using a utility here on CodeProject.</p>

<p>The demo allows you to load resampled JPG or PNG files (For the curious I
took both photographs in Oahu, Hawaii for, and while filming content of, a
multimedia product. One is of Laie Bay, the other is a sunset viewed from Waikiki.)</p>

<h2>Disclaimer</h2>

<p>I am not a GDI+ expert, nor am I a big fan. Please don't ask me questions
about it, though if you have any suggestions or improvements, please let me know.</p>





<!----------------------------- Article Ends ----------------------------->

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Joe is one of those software engineers with a film degree. His first paid programming job (you think film is a good way to make a living?) was writing games for Apple II's using 6502 assembly. He soon moved to 80x86 assembly, C, C++ (for a long time), C# and then back to C++ with occasional dabbling in C#, Python and other vile languages.

He first wrote software for Windows 3.0 in 1990. Save for some work in Linux, DOS and a mercifully brief foray into OS/2, he has concentrated on designing and writing software for all versions and types of Windows except RT.

Comments and Discussions