Loading JPEG and GIF pictures

 

Download demo source code - 95 Kb
Download C++ source code - 3 Kb

 

This article provides source code in order to read and display JPEG and GIF pictures using no third-party library at all. Both MFC and non-MFC source code is provided as to fit the largest requirements.

Why such a thing in first place? Simple, I am used to have a dependency on a GIF read/write third-party library and a JPEG read/write third-party library whenever I have to use non-flat *.BMP files. Regardless the GIF licensing issues, which have ended up in the united states since june 2003, I have always relied on third-parties because of the lack of such thing along with the WIN32 API. Wrong was I. OLE/COM provides an interface I have even played with last year, and the MSDN documentation is kept so out-of-sync that it doesn't even mention what the IPicture interface really supports.

So, while MSDN limits the capabilities of the IPicture interface to BMP, ICO, CUR and WMF pics, actually this interface supports non-animated GIF as well as JPEG pics. In addition, the interface manages the rendering by passing it a device context.

Igor, someone I have met in codeproject forums, was kind enough to show me a link[^] to an existing MFC sample implementation for it, basically a single class called CPicture. So I decided that, because my needs are such that I don't want any MFC in my code, I have rewritten the CPicture class with raw WIN32/GDI calls. And that's what I am showing here.

In the remainder of this article, I provide the code for the WIN32/GDI implementation, then a sample code to use it. This is followed by the reproduced MFC implementation from PaulDiLascia, and then again a sample showing how to use it.

A pic needs to be inserted in the .rc file. How it works is as follows : open the .rc editor, righ-click on Resources, and choose Import. In the open dialog, select the *.* file types in order to show .gif and .jpeg files, then choose one. At insertion time, VisualStudio prompts for a category name since obviously those file extensions are unsupportedi in version 6.0. Enter IMAGE, and then enter a unique id to reference the pic. Please note that the source code explicitely references the IMAGE category. Make sure to get the code sync with it.

 

 

Implementing it with WIN32/GDI

WIN32/GDI declaration and implementation



Picture.h
-------------------CUT HERE----------------------

#pragma once




//////////////////
// Picture object--encapsulates IPicture
//
class CPicture 
{

public:
	CPicture();
	~CPicture();

	// Load frm various sosurces
	BOOL Load(HINSTANCE hInst, UINT nIDRes);
	BOOL Load(LPCTSTR pszPathName);
	BOOL Load(IStream* pstm);

	// render to device context
	BOOL Render(HDC dc, RECT* rc, LPCRECT prcMFBounds=NULL) const;

	SIZE GetImageSize(HDC dc=NULL) const;

	operator IPicture*() {
		return m_spIPicture;
	}

	void GetHIMETRICSize(OLE_XSIZE_HIMETRIC& cx, OLE_YSIZE_HIMETRIC& cy) const {
		cx = cy = 0;
		const_cast<CPicture*>(this)->m_hr = m_spIPicture->get_Width(&cx);
		const_cast<CPicture*>(this)->m_hr = m_spIPicture->get_Height(&cy);
	}

	void Free() {
		if (m_spIPicture) {
			m_spIPicture.Release();
		}
	}

protected:

	void SetHIMETRICtoDP(HDC hdc, SIZE* sz) const;

	CComQIPtr<IPicture>m_spIPicture;  // ATL smart pointer to IPicture
	HRESULT m_hr;  // last error code
};


Picture.cpp
-------------------CUT HERE----------------------


#include "stdafx.h"
#include <atlbase.h>
#include "Picture.h"


#define HIMETRIC_INCH   2540    // HIMETRIC units per inch


////////////////////////////////////////////////////////////////
// CPicture implementation
//

CPicture::CPicture()
{
}

CPicture::~CPicture()
{
}

//////////////////
// Load from resource. Looks for "IMAGE" type.
//
BOOL CPicture::Load(HINSTANCE hInst, UINT nIDRes)
{
    // find resource in resource file
    HRSRC hRsrc = ::FindResource(hInst, MAKEINTRESOURCE(nIDRes), "IMAGE"); // type
    if ( !hRsrc )
        return FALSE;

    // load resource into memory
    DWORD len = ::SizeofResource(hInst, hRsrc);
    HGLOBAL hResData = ::LoadResource(hInst, hRsrc);
    if ( !hResData )
        return FALSE;

    HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD, len);
    if ( !hGlobal )
    {
        ::FreeResource(hResData);
        return FALSE;
    }

    char* pDest = reinterpret_cast<char*> ( ::GlobalLock(hGlobal) );
    char* pSrc = reinterpret_cast<char*> ( ::LockResource(hResData) );
    if (!pSrc || !pDest)
    {
        ::GlobalFree(hGlobal);
        ::FreeResource(hResData);
        return FALSE;
    }

    ::CopyMemory(pDest,pSrc,len);
    ::FreeResource(hResData);
    ::GlobalUnlock(hGlobal);


    // don't delete memory on object's release
    IStream* pStream = NULL;
    if ( ::CreateStreamOnHGlobal(hGlobal,FALSE,&pStream) != S_OK )
    {
        ::GlobalFree(hGlobal);
        return FALSE;
    }

    // create memory file and load it
    BOOL bRet = Load(pStream);

    ::GlobalFree(hGlobal);

    return bRet;
}

//////////////////
// Load from path name.
//
BOOL CPicture::Load(LPCTSTR pszPathName)
{
    HANDLE hFile = ::CreateFile(pszPathName, 
                                FILE_READ_DATA,
                                FILE_SHARE_READ,
                                NULL, 
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL);
    if ( !hFile )
        return FALSE;

    DWORD len = ::GetFileSize( hFile, NULL); // only 32-bit of the actual file size is retained
    if (len == 0)
        return FALSE;

    HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD, len);
    if ( !hGlobal )
    {
        ::CloseHandle(hFile);
        return FALSE;
    }

    char* lpBuffer = reinterpret_cast<char*> ( ::GlobalLock(hGlobal) );
    DWORD dwBytesRead = 0;

    while ( ::ReadFile(hFile, lpBuffer, 4096, &dwBytesRead, NULL) )
    {
        lpBuffer += dwBytesRead;
        if (dwBytesRead == 0)
            break;
        dwBytesRead = 0;
    }

    ::CloseHandle(hFile);

	
    ::GlobalUnlock(hGlobal);


    // don't delete memory on object's release
    IStream* pStream = NULL;
    if ( ::CreateStreamOnHGlobal(hGlobal,FALSE,&pStream) != S_OK )
    {
        ::GlobalFree(hGlobal);
        return FALSE;
    }

    // create memory file and load it
    BOOL bRet = Load(pStream);

    ::GlobalFree(hGlobal);

    return bRet;
}

//////////////////
// Load from stream (IStream). This is the one that really does it: call
// OleLoadPicture to do the work.
//
BOOL CPicture::Load(IStream* pstm)
{
    Free();

    HRESULT hr = OleLoadPicture(pstm, 0, FALSE,
                                IID_IPicture, (void**)&m_spIPicture);

    return hr == S_OK;
}

//////////////////
// Render to device context. Covert to HIMETRIC for IPicture.
//
// prcMFBounds : NULL if dc is not a metafile dc
//
BOOL CPicture::Render(HDC dc, RECT* rc, LPCRECT prcMFBounds) const
{

    if ( !rc || (rc->right == rc->left && rc->bottom == rc->top) ) 
    {
          SIZE sz = GetImageSize(dc);
          rc->right = sz.cx;
          rc->bottom = sz.cy;
    }

    long hmWidth,hmHeight; // HIMETRIC units
    GetHIMETRICSize(hmWidth, hmHeight);

    m_spIPicture->Render(dc, 
                        rc->left, rc->top, 
                        rc->right - rc->left, rc->bottom - rc->top,
                        0, hmHeight, hmWidth, -hmHeight, prcMFBounds);

    return TRUE;
}

//////////////////
// Get image size in pixels. Converts from HIMETRIC to device coords.
//
SIZE CPicture::GetImageSize(HDC dc) const
{
    SIZE sz = {0,0};

    if (!m_spIPicture)
         return sz;
	
    LONG hmWidth, hmHeight; // HIMETRIC units
    m_spIPicture->get_Width(&hmWidth);
    m_spIPicture->get_Height(&hmHeight);

    sz.cx = hmWidth;
    sz.cy = hmHeight;

    if ( dc == NULL ) 
    {
        HDC dcscreen = ::GetWindowDC(NULL);

        SetHIMETRICtoDP(dcscreen,&sz); // convert to pixels
    } 
    else 
    {
        SetHIMETRICtoDP(dc,&sz);
    }
    return sz;
}


void CPicture::SetHIMETRICtoDP(HDC hdc, SIZE* sz) const
{
    int nMapMode;
    if ( (nMapMode = ::GetMapMode(hdc)) < MM_ISOTROPIC && nMapMode != MM_TEXT)
    {
        // when using a constrained map mode, map against physical inch
		
        ::SetMapMode(hdc,MM_HIMETRIC);
        POINT pt;
        pt.x = sz->cx;
        pt.y = sz->cy;
        ::LPtoDP(hdc,&pt,1);
        sz->cx = pt.x;
        sz->cy = pt.y;
        ::SetMapMode(hdc, nMapMode);
    }
    else
    {
        // map against logical inch for non-constrained mapping modes
        int cxPerInch, cyPerInch;
        cxPerInch = ::GetDeviceCaps(hdc,LOGPIXELSX);
        cyPerInch = ::GetDeviceCaps(hdc,LOGPIXELSY);
        sz->cx = MulDiv(sz->cx, cxPerInch, HIMETRIC_INCH);
        sz->cy = MulDiv(sz->cy, cyPerInch, HIMETRIC_INCH);
    }

    POINT pt;
    pt.x = sz->cx;
    pt.y = sz->cy;
    ::DPtoLP(hdc,&pt,1);
    sz->cx = pt.x;
    sz->cy = pt.y;

}

Using the WIN32/GDI code


PictureActiveX.h
-------------------CUT HERE----------------------

class CPictureActiveX
{
  ...
  CPicture picture;

  // read the picture
  void Read(UINT nResID);

  // that's where CPictureActiveX draws the picture
  HRESULT OnDraw(ATL_DRAWINFO& di);
};



PictureActiveX.cpp
-------------------CUT HERE----------------------

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>
#include <atlctl.h>

// the two following lines of code may be pasted and used somewhere else instead
CComModule _Module;
_Module.Init(ObjectMap, hInstance, &LIBID_VUMETERLib);


  // read the picture
void CPictureActiveX::Read(UINT nResID)
{
  picture.Load(_Module.GetResourceInstance(),nResID);
}


// that's where CPictureActiveX draws the picture
HRESULT CPictureActiveX::OnDraw(ATL_DRAWINFO& di)
{
  HDC hdc = di.hdcDraw;
  RECT& rc = *(RECT*)di.prcBounds;

  picture.Render(hdc, &rc);


  ...
  return S_OK;
}

 

 

Implementing it with MFC

MFC implementation


Picture.h
-------------------CUT HERE----------------------

////////////////////////////////////////////////////////////////
// MSDN Magazine -- October 2001
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 for Windows 98 and probably Windows 2000 too.
// Set tabsize = 3 in your editor.
//
#pragma once
#include <atlbase.h>

//////////////////
// Picture object--encapsulates IPicture
//
class CPicture {
public:
	CPicture();
	~CPicture();

	// Load frm various sosurces
	BOOL Load(UINT nIDRes);
	BOOL Load(LPCTSTR pszPathName);
	BOOL Load(CFile& file);
	BOOL Load(CArchive& ar);
	BOOL Load(IStream* pstm);

	// render to device context
	BOOL Render(CDC* pDC, CRect rc=CRect(0,0,0,0),
		LPCRECT prcMFBounds=NULL) const;

	CSize GetImageSize(CDC* pDC=NULL) const;

	operator IPicture*() {
		return m_spIPicture;
	}

	void GetHIMETRICSize(OLE_XSIZE_HIMETRIC& cx, OLE_YSIZE_HIMETRIC& cy) const {
		cx = cy = 0;
		const_cast<CPicture*>(this)->m_hr = m_spIPicture->get_Width(&cx);
		ASSERT(SUCCEEDED(m_hr));
		const_cast<CPicture*>(this)->m_hr = m_spIPicture->get_Height(&cy);
		ASSERT(SUCCEEDED(m_hr));
	}

	void Free() {
		if (m_spIPicture) {
			m_spIPicture.Release();
		}
	}

protected:
	CComQIPtr<IPicture>m_spIPicture; // ATL smart pointer to IPicture
	HRESULT m_hr;  // last error code
};



Picture.cpp
-------------------CUT HERE----------------------
#include "stdafx.h"
#include "Picture.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

////////////////////////////////////////////////////////////////
// CPicture implementation
//

CPicture::CPicture()
{
}

CPicture::~CPicture()
{
}

//////////////////
// Load from resource. Looks for "IMAGE" type.
//
BOOL CPicture::Load(UINT nIDRes)
{
	// find resource in resource file
	HINSTANCE hInst = AfxGetResourceHandle();
	HRSRC hRsrc = ::FindResource(hInst,
		MAKEINTRESOURCE(nIDRes),
		"IMAGE"); // type
	if (!hRsrc)
		return FALSE;

	// load resource into memory
	DWORD len = SizeofResource(hInst, hRsrc);
	BYTE* lpRsrc = (BYTE*)LoadResource(hInst, hRsrc);
	if (!lpRsrc)
		return FALSE;

	// create memory file and load it
	CMemFile file(lpRsrc, len);
	BOOL bRet = Load(file);
	FreeResource(hRsrc);

	return bRet;
}

//////////////////
// Load from path name.
//
BOOL CPicture::Load(LPCTSTR pszPathName)
{
	CFile file;
	if (!file.Open(pszPathName, CFile::modeRead|CFile::shareDenyWrite))
		return FALSE;
	BOOL bRet = Load(file);
	file.Close();
	return bRet;
}

//////////////////
// Load from CFile
//
BOOL CPicture::Load(CFile& file)
{
	CArchive ar(&file, CArchive::load | CArchive::bNoFlushOnDelete);
	return Load(ar);
}

//////////////////
// Load from archive--create stream and load from stream.
//
BOOL CPicture::Load(CArchive& ar)
{
	CArchiveStream arcstream(&ar);
	return Load((IStream*)&arcstream);
}

//////////////////
// Load from stream (IStream). This is the one that really does it: call
// OleLoadPicture to do the work.
//
BOOL CPicture::Load(IStream* pstm)
{
	Free();
	HRESULT hr = OleLoadPicture(pstm, 0, FALSE,
		IID_IPicture, (void**)&m_spIPicture);
	ASSERT(SUCCEEDED(hr) && m_spIPicture);	
	return TRUE;
}

//////////////////
// Render to device context. Covert to HIMETRIC for IPicture.
//
BOOL CPicture::Render(CDC* pDC, CRect rc, LPCRECT prcMFBounds) const
{
	ASSERT(pDC);

	if (rc.IsRectNull()) {
		CSize sz = GetImageSize(pDC);
		rc.right = sz.cx;
		rc.bottom = sz.cy;
	}
	long hmWidth,hmHeight; // HIMETRIC units
	GetHIMETRICSize(hmWidth, hmHeight);
	m_spIPicture->Render(*pDC, rc.left, rc.top, rc.Width(), rc.Height(),
		0, hmHeight, hmWidth, -hmHeight, prcMFBounds);

	return TRUE;
}

//////////////////
// Get image size in pixels. Converts from HIMETRIC to device coords.
//
CSize CPicture::GetImageSize(CDC* pDC) const
{
	if (!m_spIPicture)
		return CSize(0,0);
	
	LONG hmWidth, hmHeight; // HIMETRIC units
	m_spIPicture->get_Width(&hmWidth);
	m_spIPicture->get_Height(&hmHeight);
	CSize sz(hmWidth,hmHeight);
	if (pDC==NULL) {
		CWindowDC dc(NULL);
		dc.HIMETRICtoDP(&sz); // convert to pixels
	} else {
		pDC->HIMETRICtoDP(&sz);
	}
	return sz;
}

Using the MFC code

Once the source code is inserted, it's possible to use the code in a typical doc/view model with this code :


class CDocument
{
 ...
 CPicture m_pict; // picture loader instance in some class


 void Load(UINT nResId)
 {
   m_pict.Load(nResId);
 }

 CPicture* GetPicture()
 {
   return &m_pict;
 }

 ...
};


class CView
{
 ...
   
 void OnDraw(CDC* pDC)
 {
   CDocument* pDoc = GetDocument();
   ASSERT_VALID(pDoc);
   CPicture* ppic = pDoc->GetPicture();
   ASSERT(ppic);
   if (*ppic) {
     CRect rc;
     GetImageRect(rc);
     ppic->Render(pDC,rc);
   }
 }

 ...
};

 

For those interested in supporting animated GIF sequences as well, there is an outstanding article[^] on codeproject to do just this.

 

 

Enjoy!
Stephane Rodriguez- September 26, 2003.

 


Home
Blog