C++ Q&A;: Displaying a JPG in your MFC Application

MSDN Magazine

Displaying a JPG in your MFC Application
Paul DiLascia
Download the code for this article:CQA0110.exe (202KB)
Browse the code for this article at Code Center: ImgView

Q

In Visual Basic, I can display a JPG or GIF file by creating a picture control, but how can I display a JPG in my MFC app?

Many Readers


A

Good question! Sometimes programmers who use Visual Basic® seem to have it so easy. Just plop a picture control in your form and awaaay you go...while C++ programmers have to fret and strut. What are we supposed to do, write our own JPG decompression functions?
      Of course not! In fact, C/C++ programmers can use the very same (almost) picture control their Visual Basic counterparts use. I kid you not. The Visual Basic picture control is based on a system COM class, IPicture (see Figure 1). IPicture manages a picture object and its properties. Picture objects provide an abstraction for bitmaps. Windows® provides a standard implementation that knows how to handle BMP, JPG, and GIF bitmaps. All you need to do is instantiate IPicture and call Render. Instead of calling CoCreateInstance the normal way, you call a special function called OleLoadPicture.

  IStream* pstm = // needs a stream
IPicture* pIPicture;
hr = OleLoadPicture(pstm, 0, FALSE,
IID_IPicture, (void**)&pIPicture);

 

OleLoadPicture loads the picture from the stream and creates a new IPicture object you can use to display the picture.

  rc = // rect to display in
// convert rc to HIMETRIC
spIPicture->Render(pDC, rc);

 

      IPicture does all the nasties to figure out whether the image is a Windows bitmap, JPEG, or GIF file—it even does icons and metafiles! Naturally, the details are a little tricky, so I wrote a little demo program called ImgView (see Figure 2) to put it all together.

Figure 2 ImgView
Figure 2 ImgView

ImgView is a typical MFC doc/view app that uses a class I wrote, CPicture (see Figure 3), to encapsulate IPicture. CPicture maps cumbersome COM-style parameters into types more friendly to MFC programmers. For example, CPicture lets you load a picture directly from a file name, CFile, or CArchive, instead of dealing with streams; and CPicture::Render does all the yucky HIMETRIC coordinate conversions that IPicture wants—so you don't have to. CPicture even has a Load function to load an image from your resource data, so all you have to do to display a resource picture is write:

     CPicture pic(ID_MYPIC); // load pic
CRect rc(0,0,0,0); // use default rc
pic.Render(pDC, rc); // display it

 

      What could be easier? CPicture::Render takes a rectangle, the one you want to display your picture in. IPicture stretches the image appropriately. If you pass an empty rect, CPicture uses the image's native size—no stretching. As for the image itself, CPicture looks for a resource type called "IMAGE", so you have to code your RC file like so:

     IDR_MYPIC IMAGE MOVEABLE PURE "res\\MyPic.jpg"
  

 

      Overall, CPicture is pretty brainless. It holds an ATL CComQIPtr<IPicture> smart pointer to the IPicture interface, which the various Load functions initialize by calling OleLoadPicture. CPicture provides the usual wrapper functions to call the underlying IPicture. CPicture encapsulates only those IPicture methods I needed to write ImgView; this being because I am such a lazy programmer. If you ever need to call IPicture::get_Handle or some other rare IPicture method, you'll have to add the wrapper yourself, sorry. At least the code is trivial.
      By the way, I should point out that after writing CPicture, I discovered a little-known MFC class called CPictureHolder that does much of the same thing. You can find it in afxctl.h.
      As I mentioned earlier, ImgView is a typical MFC doc/view application with classes CPictureDoc and CPictureView for doc and view. Figure 4 shows the view. CPictureDoc is trivial; it uses CPicture to hold the picture—

  class CPictureDoc : public CDocument {
protected:
CPicture m_pict; // the picture
};

 

and CPictureDoc::Serialize calls CPicture::Load to read the picture from the archive MFC sets up.

  void CPictureDoc::Serialize(CArchive& ar)
{
if (ar.IsLoading()) {
m_pict.Load(ar);
}
}

 

      Just for fun, CPictureDoc::OnNewDocument loads a pretty NASA photo from the program's resource data. To display the pictures, CPictureView::OnDraw calls CPicture::Render.

  void CPictureView::OnDraw(CDC* pDC)
{
CPictureDoc* pDoc = GetDocument();
CPicture* ppic = pDoc->GetPicture();
CRect rc;
GetImageRect(rc);
ppic->Render(pDC,rc);
}

 

GetImageRect is a CPictureView function that retrieves the proper image rectangle, depending on the current ImgView zoom ratio. (ImgView lets you view the image at 25, 33, 50, 75, or 100 percent, or "to fit".) GetImageRect calls CPicture::GetImageSize to get the true image size, then scales appropriately.
      The rest of CPictureView is typical CScrollView stuff, with code to initialize the view and set scroll sizes, handle commands, and so on. The only funny business with IPicture is that, as I suggested earlier, IPicture::Render likes its coordinates in HIMETRIC units, whereas vanilla MFC apps use the default MM_TEXT mapping mode. Not to worry, CPicture::Render and CPicture::GetImageSize do the magic conversions, so you don't have to overtax your poor noggin worrying about such mundane and weary trivia.
      CPictureView has one message handler worth mentioning: OnEraseBkgnd. This is required to draw the blank area in case the image is smaller than the view's client area (see Figure 5). OnEraseBkgnd creates a clipping rectangle equal to the image rectangle, then fills the client rectangle with black. Clipping avoids flicker when you size the window—FillRect doesn't draw inside the clipped rectangle. This is standard Windows Graphics 101.

Figure 5 OnEraseBkgnd Fills the Clipped Image
Figure 5 OnEraseBkgnd Fills the Clipped Image

      IPicture/CPicture really makes displaying images easy. It even takes care of palette realization and all that horrible stuff. You can throw away all your old DIB-drawing code that loads palettes, BitBlts, StretchBlts, and so on—IPicture is the way to go. If you're not using IPicture to display images, start doing so now!
      Well, all that was so easy I decided to write another class just for fun. CPictureView is fine and dandy if you want to write an image viewer, but what if you want to add a picture to a dialog or some other window? For that, I wrote another class, CPictureCtrl (see Figure 6). CPictureCtrl lets you plop a picture in any dialog or window, as a child control. For example,

  class CAboutDialog : public CDialog {
protected:
CPictureCtrl m_wndPict;
virtual BOOL OnInitDialog();
};
BOOL CAboutDialog::OnInitDialog()
{
m_wndPict.SubclassDlgItem(IDC_MYIMAGE,this);
return CDialog::OnInitDialog();
}

 

      This assumes you have a static control in your dialog, with ID=IDC_IMAGE and an IMAGE resource with the same ID. I derived CPictureCtrl from my ubiquitous CStaticLink, so you can specify a URL hyperlink if you like (or just create a string resource with the same ID as the control and picture). If you specify a URL, clicking the mouse on the picture will launch your browser to that link. Amazing. CPicture holds a CPicture object and overrides WM_PAINT to call CPicture::Render instead of the normal static control thing. For more details, download the source from the link at the top of this article—and use it with my blessing!

Send questions and comments for Paul to cppqa@microsoft.com.

Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++(Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or https://www.dilascia.com.


From the October 2001 issue of MSDN Magazine.