Click here to Skip to main content
15,883,705 members
Articles / Desktop Programming / MFC
Article

A simple image preview class using GDI+

Rate me:
Please Sign up or sign in to vote.
4.50/5 (29 votes)
5 Mar 2004CPOL4 min read 139.1K   5.6K   47   30
Using GDI+ in conjunction with an owner-draw static window

Introduction

In a project I'm working on I needed the ability to show various image formats in a preview window. I wanted to be able to handle any of the common image formats, such as Bitmaps, JPEGs, GIFs and PNGs. After a bit of searching around and not finding anything that did precisely what I wanted I decided to roll my own.

Basics

The task breaks down into two sub-tasks. The first is to load the image from some source such as a file and to decode it into a format that Windows can handle. Then comes the easy part, rendering the image onto the display.

Task 1 made easy

The usual approach to loading an image from a file is to find some library (commercial or open source) that handles the image format you're interested in and stitch it into your program. I've done this before and I'm sure most of us have at some time or other. The frequency with which such a task arises is attested to by the popularity of this[^] article (CxImage by Davide Pizzolato).

Whilst browsing through CodeProject and mulling over the prospect of yet again trying to use an external library to handle the images I found this[^] (Starting with GDI+ by Christian Graus). After reading this article and then moving on to the MSDN documentation for GDI+ I realised this was the perfect solution to task 1. (A mention in Mike Dunn's C++ FAQ didn't hurt either).

Task 2

This is much easier. The best way I've found is to derive a class from the CStatic class, make it ownerdraw and render the bitmap into the static control.

Putting it all together

C++
class CImagePreviewStatic : public CStatic
{
    DECLARE_DYNAMIC(CImagePreviewStatic)
public:
                    CImagePreviewStatic();
    virtual         ~CImagePreviewStatic();

    virtual BOOL    Create();
    virtual void    DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

    void            SetFilename(LPCTSTR szFilename);

protected:
    WCHAR           m_wsFilename[_MAX_PATH];
    Image           *m_img;       //  GDI+ object
    Graphics        *m_graphics;  //  GDI+ object

    DECLARE_MESSAGE_MAP()
};
The constructor NULLs the Image and Graphics pointers (both these objects are GDI+ objects). The destructor deletes those pointers. The GDI+ objects get created in the Create() function:
C++
BOOL CImagePreviewStatic::Create()
{
    if (GetSafeHwnd() != HWND(NULL))
    {
        m_img = new Image(m_wsFilename);
        m_graphics = new Graphics(GetSafeHwnd());
        return TRUE;
    }

    return FALSE;
}
Pretty simple code. The m_img pointer is initialised with an Image object created using the filename of the image you want to load. The m_graphics pointer is initialised with a Graphics object which is associated with the CStatics underlying window.

Note that the Image constructor requires a Unicode string. In ANSI builds the SetFilename() function converts the ANSI filename into a Unicode string using some helper macros.

C++
void CImagePreviewStatic::SetFilename(LPCTSTR szFilename)
{
#ifndef _UNICODE
    USES_CONVERSION;
#endif

    ASSERT(szFilename);
    ASSERT(AfxIsValidString(szFilename));

    TRACE("%s\n", szFilename);

#ifndef _UNICODE
    wcscpy(m_wsFilename, A2W(szFilename));
#else
    wcscpy(m_wsFilename, szFilename);
#endif

    delete m_img;
    m_img = new Image(m_wsFilename, FALSE);
    Invalidate();
}
Once we've done the string conversion (if required) we delete the existing Image pointer and create a new one using the new filename. Then we Invalidate() the window and let Windows send us a paint message some time in the future.

When Windows gets around to asking us to redraw the ownerdraw logic kicks in.

C++
void CImagePreviewStatic::DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/)
{
    Unit  units;
    CRect rect;

    if (m_img != NULL)
    {
        GetClientRect(&rect);

        RectF destRect(REAL(rect.left), 
                       REAL(rect.top), 
                       REAL(rect.Width()),
                       REAL(rect.Height())),
              srcRect;
        m_img->GetBounds(&srcRect, &units);
        m_graphics->DrawImage(m_img, destRect, srcRect.X, srcRect.Y, srcRect.Width, 
                              srcRect.Height, UnitPixel, NULL);
    }
}
What this code does is get the bounding rectangle of the underlying Window and creates a RectF object specifying the same coordinates. (The appended F means that each element is a REAL rather than an int). Then we get the bounds of the image itself and call the DrawImage() function to draw the image on the Window. The specific DrawImage() overload I used scales the image into the drawing rectangle. Pretty simple code!

Using the code

You probably want to include these three lines at the end of your stdafx.h file.
C++
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
These lines include the header file for GDI+ and set the Gdiplus namespace. This will save some typing since you don't then have to prefix every GDI+ reference with Gdiplus::. The third line inserts a reference to the gdiplus.lib library into whichever object file contains it; this saves you having to explicitly add the library to your project workspace.

Next, your application must initialise the GDI+ library before using any other GDI+ functions, and shut it down before exiting. That's done by this code somewhere in your application. CMyApp::InitInstance() is a good place.

C++
.
.
.

// Initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

.
.
.
m_gdiplusToken is an unsigned long used by GDI+ later when you want to shut it down. Your CMyApp::ExitInstance() would contain:
C++
GdiplusShutdown(m_gdiplusToken);
Once you've initialised GDI+ you're good to go with all the rest of the GDI+ functionality.

To use CImagePreviewStatic you need to add a static window to your dialogs or views. Bind the window to a CImagePreviewStatic object. Make sure the static window has the SS_OWNERDRAW style set. Once you've bound the window to the CImagePreviewStatic object call the Create() function and then set the image filename you want to preview. For example:

C++
class CImageDlg : public CDialog
{
    DECLARE_DYNAMIC(CImageDlg)

    enum adviseMessages
    {
        adviseUpdatePreview,
    };
public:
                    CImageDlg();
    virtual         ~CImageDlg();

    virtual void    DoDataExchange(CDataExchange *pDX);

protected:
    CImagePreviewStatic m_preview;


    DECLARE_MESSAGE_MAP()
    virtual BOOL    OnInitDialog();

public:
};
In the DoDataExchange() function we use DDX to bind the m_preview member to a static control on our dialog template.
c+
void CImageDlg::DoDataExchange(CDataExchange *pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_IMAGEPREVIEW, m_preview);
}
Then, in our OnInitDialog() we do:
C++
BOOL CImageDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    m_preview.Create();
    return TRUE;
}
Once this has been done our CImagePreviewStatic control is initialised and ready to display images. All we have to do is call the SetFilename() function and the ownerdraw plumbing in Windows does all the rest.

The demo program is the minimal implementation necessary to demonstrate the control. It has a hardwired image name and it expects to find this image in the programs current directory.

History

March 6, 2004 - Initial Version.

License

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


Written By
United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.

I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.

Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.

Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.

I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.

Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions

 
Generalnew Image Pin
Member 1029952326-Sep-13 5:00
Member 1029952326-Sep-13 5:00 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey18-Feb-12 0:10
professionalManoj Kumar Choubey18-Feb-12 0:10 
GeneralThumbnail Pin
john563220-Feb-10 21:18
john563220-Feb-10 21:18 
GeneralMy vote of 5 Pin
Dirk van Boxel7-Feb-10 3:47
Dirk van Boxel7-Feb-10 3:47 
GeneralSimple improvement, for image to keep original aspect ratio - code included [modified] Pin
islobodan6-Jan-09 2:47
islobodan6-Jan-09 2:47 
GeneralPainting issue while using scrollbar and drawimage.... Pin
Member 461586722-Feb-08 0:40
Member 461586722-Feb-08 0:40 
GeneralFor no of images Pin
Shah Satish17-Jan-07 1:45
Shah Satish17-Jan-07 1:45 
GeneralAn error when compiling Pin
vnpatriot718-Apr-06 2:03
vnpatriot718-Apr-06 2:03 
GeneralStatic Window does not call the CustomDraw in VC6 Pin
sdancer7523-Jan-06 20:34
sdancer7523-Jan-06 20:34 
GeneralThank you Sir Pin
Fransiscusherry24-Oct-05 18:13
Fransiscusherry24-Oct-05 18:13 
GeneralSaving the rotated image Pin
Alex Evans12-Jan-05 19:34
Alex Evans12-Jan-05 19:34 
GeneralThank You! Pin
jhorstkamp7-Jan-05 6:56
jhorstkamp7-Jan-05 6:56 
GeneralHelp required Pin
tcss11-Nov-04 0:27
tcss11-Nov-04 0:27 
GeneralRe: Help required (solved) Pin
tcss11-Nov-04 0:57
tcss11-Nov-04 0:57 
GeneralLicensing question Pin
erichf14-Oct-04 11:02
erichf14-Oct-04 11:02 
GeneralRe: Licensing question Pin
Rob Manderson14-Oct-04 15:02
protectorRob Manderson14-Oct-04 15:02 
GeneralRe: Licensing question Pin
erichf14-Oct-04 15:23
erichf14-Oct-04 15:23 
Generaldrawing in print context Pin
darthmaul1-Apr-04 1:35
darthmaul1-Apr-04 1:35 
GeneralEasy to use and works great! Pin
CodeProjectSQ10-Mar-04 16:14
CodeProjectSQ10-Mar-04 16:14 
GeneralRe: Easy to use and works great! Pin
Rob Manderson10-Mar-04 21:07
protectorRob Manderson10-Mar-04 21:07 
Generalsome tips for MFC user/develper Pin
beavis7-Mar-04 22:14
beavis7-Mar-04 22:14 
GeneralAnd I see my downvoter has finally voted! Pin
Rob Manderson7-Mar-04 16:24
protectorRob Manderson7-Mar-04 16:24 
GeneralRe: And I see my downvoter has finally voted! Pin
Prakash Nadar10-Mar-04 13:34
Prakash Nadar10-Mar-04 13:34 
Downvoter?? If you are thinking of me, I didnt vote at all Smile | :) .
I dont vote or articles simply because i dont belive in it.
Some article may be very good for one guy, and may be absulutly useless to another.
So does the success of an article depends on the ratio of ppl who liked the article or not ?
I belive that out of 100 ppl 1 votes 5 and rest votes 1 its still a successfull article. coz it helped some one.

As for this this article, it is not usefull for me at this time, may be i will use it in the future when the need arises and this satisfies my requirement.
Then should vote 1 now and 5 later? Not possible currently here in Codeproject rite.

I didnt say that urs is a bad article, I just said why the repetition of the same type of article. Smile | :)



MSN Messenger.
prakashnadar@msn.com
GeneralRe: And I see my downvoter has finally voted! Pin
Rob Manderson10-Mar-04 13:43
protectorRob Manderson10-Mar-04 13:43 
GeneralRe: And I see my downvoter has finally voted! Pin
Prakash Nadar10-Mar-04 16:36
Prakash Nadar10-Mar-04 16:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.