Introduction
This article describes a simple way to do basic rendering of various types of image files
into a view window.
It uses a rendering method described in the MSDN article Q218972, LOADPIC.EXE.
I ran across this sample application purely by accident. After reviewing it, I re-wrote
the guts of the functionality for use in an MFC document-view environment.
The re-write consists of three functions:
DrawImage()
, which draws the image contained in an image file.
GetImageSize()
, which returns the height and width of the image in the image file.
LoadPictureFile()
, which loads the image from disk and readies it for display via OleLoadPicture()
.
Header Definitions:
BOOL DrawImage(CDC* pDC,
CString csFile,
CPoint ptCenter,
HWND hWnd,
CRect rectImage);
DrawImage()
will render the image whose pathname
is passed in in csFile into the area of
the rectangle in rectImage.
rectImage must be in device units.
Important: These functions assume a DC mapping
mode other than MM_TEXT
. That is, negative Y is downward.
If you are going to use MM_TEXT
, you will need to change the
signs of the height variables in the Render() function call.
BOOL GetImageSize(CDC* pDC,
LPCTSTR szFile,
int* pnHeight,
int* pnWidth);
GetImageSize()
returns the height and width of
the image in szFile. These values are in device units for the DC
passed in to the function.
It is good to use this function to fetch the true size of the
image, in case you want to let the user reset the image display.
BOOL LoadPictureFile(LPCTSTR szFile,
LPPICTURE* pgpPicture);
LoadPictureFile()
loads the image bytes in szFile into pgpPicture.
Includes:
The LOADPIC project has a reference to olectl.h in its stdafx.h file.
However, I am using these functions in an MFC
application with no special includes.
MSDN does not mention any need for OleLoadPicture
.
Source:
BOOL DrawImage(CDC* pDC,
CString csFile,
CPoint ptCenter,
HWND hWnd,
CRect rectImage)
{
if (pDC == NULL || csFile.IsEmpty() || hWnd == NULL)
return FALSE;
LPPICTURE gpPicture = NULL;
if (LoadPictureFile((LPCTSTR)csFile,&gpPicture))
{
long hmWidth = 0;
long hmHeight = 0;
gpPicture->get_Width (&hmWidth);
gpPicture->get_Height(&hmHeight);
CRect rectI = rectImage;
rectI.NormalizeRect();
int nWidth = rectI.Width();
int nHeight= rectI.Height();
CPoint ptUL(ptCenter.x-(nWidth /2),
ptCenter.y+(nHeight/2));
RECT rc;
GetClientRect(hWnd, &rc);
HRESULT hrP = NULL;
hrP =
gpPicture->Render(pDC->m_hDC,
ptUL.x,
ptUL.y,
nWidth,
-nHeight,
0,
hmHeight,
hmWidth,
-hmHeight,
&rc);
gpPicture->Release();
if (SUCCEEDED(hrP))
return TRUE;
}
return FALSE;
}
BOOL GetImageSize(CDC* pDC,
LPCTSTR szFile,
int* pnHeight,
int* pnWidth)
{
LPPICTURE gpPicture = NULL;
if (!LoadPictureFile(szFile,&gpPicture))
return FALSE;
long hmWidth = 0;
long hmHeight = 0;
gpPicture->get_Width (&hmWidth);
gpPicture->get_Height(&hmHeight);
int nPixX = pDC->GetDeviceCaps(LOGPIXELSX);
int nPixY = pDC->GetDeviceCaps(LOGPIXELSY);
if (pDC->IsPrinting())
{
nPixX = 96;
nPixY = 96;
}
*pnWidth = MulDiv(hmWidth, nPixX, HIMETRIC_INCH);
*pnHeight = MulDiv(hmHeight, nPixY, HIMETRIC_INCH);
return TRUE;
}
BOOL LoadPictureFile(LPCTSTR szFile,
LPPICTURE* pgpPicture)
{
HANDLE hFile = CreateFile(szFile,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
AfxMessageBox ("Could not read file");
return FALSE;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
if (dwFileSize == (DWORD)-1)
{
CloseHandle(hFile);
AfxMessageBox ("File seems to be empty");
return FALSE;
}
LPVOID pvData = NULL;
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
if (hGlobal == NULL)
{
CloseHandle(hFile);
AfxMessageBox ("Could not allocate memory for image");
return FALSE;
}
pvData = GlobalLock(hGlobal);
if (pvData == NULL)
{
GlobalUnlock(hGlobal);
CloseHandle(hFile);
AfxMessageBox ("Could not lock memory for image");
return FALSE;
}
DWORD dwBytesRead = 0;
BOOL bRead = ReadFile(hFile,
pvData,
dwFileSize,
&dwBytesRead,
NULL);
GlobalUnlock(hGlobal);
CloseHandle(hFile);
if (!bRead)
{
AfxMessageBox ("Could not read file");
return FALSE;
}
LPSTREAM pstm = NULL;
HRESULT hr = CreateStreamOnHGlobal(hGlobal,
TRUE,
&pstm);
if (!(SUCCEEDED(hr)))
{
AfxMessageBox ("CreateStreamOnHGlobal() failed");
if (pstm != NULL)
pstm->Release();
return FALSE;
}
else if (pstm == NULL)
{
AfxMessageBox ("CreateStreamOnHGlobal() failed");
return FALSE;
}
if (*pgpPicture)
(*pgpPicture)->Release();
hr = ::OleLoadPicture(pstm,
dwFileSize,
FALSE,
IID_IPicture,
(LPVOID *)&(*pgpPicture));
if (!(SUCCEEDED(hr)))
{
pstm->Release();
AfxMessageBox("Could not load image (hr failure)");
return FALSE;
}
else if (*pgpPicture == NULL)
{
pstm->Release();
AfxMessageBox("Could not load image (pgpPicture failure)");
return FALSE;
}
pstm->Release();
return TRUE;
}
Example of Use:
The picture above shows use of these functions
in a map-making program I have been working on.
Here is how the program uses them:
Document Class:
First a handler for the "Map" menu allows a user to navigate
to the desired image file and open it. This simply saves off the
file path for use later, and sets the program into
"rendering images" mode: Wherever the user clicks,
a stroke "image" object will be created to display the image.
The CStroke
class handles this (remember Scribble?).
View Class:
Once the program is in "rendering images" mode, the view class
handles the left mouse button click.
Here is the code block from OnLButtonDown()
:
void CGameMaprView::OnLButtonDown(UINT nFlags, CPoint point)
{
CGameMaprDoc* pDoc = GetDocument();
CRect rectTemp;
CClientDC dc(this);
OnPrepareDC(&dc);
dc.DPtoLP(&point);
...
else if (pDoc->DrawType() == DRAW_IMAGE_FILE)
{
m_pStrokeCur = pDoc->NewStroke(FALSE,
&dc,
point.x,
point.y,
pDoc->BitmapResourceName());
if (m_pStrokeCur != NULL)
{
m_pStrokeCur->DrawStroke(&dc,FALSE,this);
pDoc->RedrawArea(m_pStrokeCur->m_rectBounding);
}
}
...
return;
}
Document Class (again):
Back in the document class, NewStroke()
creates a new stroke,
and gets the original image size for the initial bounding box:
CStroke* CGameMaprDoc::NewStroke(BOOL bPasting,
CDC *pDC,
int nX,
int nY,
CString csBmp)
{
...
else if (m_eDrawType == DRAW_IMAGE_FILE)
{
eType = S_TYPE_IMAGE_FILE;
}
pStrokeItem = new CStroke( ... );
...
else if (m_eDrawType == DRAW_IMAGE_FILE &&
!csBmp.IsEmpty())
{
int nH = 0;
int nW = 0;
if (GetImageSize(pDC,(LPCTSTR)csBmp,&nH,&nW))
{
pStrokeItem->m_csBitmapResource = csBmp;
pStrokeItem->m_rectBounding.SetRect(nX-(nW/2),
nY+(nH/2),
nX+(nW/2),
nY-(nH/2));
}
else
{
AfxMessageBox("Could not create image object");
delete pStrokeItem;
return NULL;
}
}
...
}
Stroke Class:
In the
CStroke
class, the
DrawStroke()
function handles drawing
of the image:
BOOL CStroke::DrawStroke(CDC* pDC,
BOOL bDirectDisplay,
CGameMaprView* pView)
{
...
else if (StrokeType() == S_TYPE_IMAGE_FILE)
{
DrawImage(pDC,
m_csBitmapResource,
m_rectBounding.CenterPoint(),
pView->m_hWnd,
m_rectBounding);
}
...
}
The stroke class also has an "update properties" method that allows
a user to specify a different height and width for the image.
Printing Issues:
For some reason, articles dealing with images (especially bitmaps)
never talk about printing issues to consider.
The biggest one is the aspect ratio:
LOGPIXELSX
and LOGPIXELSY
, to a printer's Device Context (DC)
mean the number of Dots Per Inch (DPI).
On a 600 DPI printer, these values will
be returned as 600.
If you are not careful, this will have "interesting" results when you print!
The second issue is transparency. Hewlett-Packard printers,
for example,
do not seem to recognize the BitBlt
ROP codes properly,
making it impossible to render an image transparently by the normally-shown methods.
(This may have been fixed on later models - HP never responded
when I tried to contact them about it.)
This limitation also exists in the above functions:
You will not be able to print a transparent image
(such as the knight in the image sample above) using OleLoadImage()
.
Non-transparent printing seems to work fine.
To do transparent printing, you would need to
turn the image into a bitmap, and then
print it using regions of color rather than bit-blitting with
the "true mask" method.
Failures to Load:
LOADPIC states that it will open Enhanced metafiles and Windows metafiles
(EMF and WMF formats).
I successfully loaded two EMF's;
however, when I tried to load c:\windows\system\spbanner.wmf,
I got:
"Could not load image (hr failure)". Word 97 would not
load this file properly either.
If desired, a reader could add further analysis code to
list the detailed cause of such failures.
Conclusion:
I have found these functions very useful;
it is my hope that other readers will also find
them useful for rendering various types of images.