ImageHandler: A Component to Copy an HDC Area to a JPEG File

Environment: Windows 98/NT

Introduction

This article grew out of my dissatisfaction with the "Copy a DIB to a JPEG File" article I wrote a few years ago. The code presented in that article had a few errors, and was not well formatted on the web page, due to my inexperience in posting articles.

The main problem with the article was that it required users to download and build the libJPEG project from the Independent JPEG Group. This project is very tricky to build and use, and has led to a steady stream of "Help me!" e-mails.

The obvious solution was to build a component. This component would allow a generalized way to create a JPEG file from the contents of a view window. Once built, users would not have to worry about building their own copy of jpeg.lib.

Thus ImageHandler was born.

Acknowledgements

Imagehandler could not have been written without much existing code from other sources:

  • Zafir Anjum and other CodeGuru adminiatrators and contributors
  • The Independent JPEG Group for creating libJPEG and making it available for use
  • Jeff Prosise's excellent book Programming Windows 95 with MFC.
  • Jeff Prosise and his excellent book Programming Windows 95 with MFC.
  • Jonathan Bates's very instructive book Creating Lightweight Components with ATL*

(*Although, since ImageHandler shamelessly uses MFC, it might not be what Bates would consider "lightweight".)

Paintlib (libJPEG)

ImageHandler uses the 6b of 27-Mar-1998 version of libJPEG.

This article does not include libJPEG or the jpeg.lib library file. If you wish to modify the ImageHandler project to create more interfaces, you will need to get the latest version of libJPEG from the paintlib web site and build it to create your own jpeg.lib.

Building jpeg.lib is not a trivial process (which is what led to me creating this component in the first place). However, many other developers have done so successfully. If you have questions about paintlib itself or problems building it, please direct your inquiries to the paintlib authors.

When I built jpeg.lib I followed this process:

  1. I downloaded libJPEG and de-compressed it
  2. I read the Windows sections of the build instructions
  3. I tried to build the project and watched the build fail
  4. I re-read the build instructions carefully
  5. I made corrections, rebuilt the libJPEG project, and it has worked ever since

The ImageHandler Component

ImageHandler was built as an ATL in-process server. It uses MFC as a static library; you will not need MFC on your machine to run ImageHandler. It is about 164 KB in size (abbout 100 KB of this is due to static linkage of MFC).

ImageHandler has one interface class, IRectToJpeg. IRectToJpeg exposes one one interface, HdcToJpeg. The picture below shows the interface class as it appears in the project workspace:



Click here for larger image

Registering the Component

To register the component, first download and unzip the "executables" self-extracting archive below. This file contains a copy of regsvr32.exe, and a bat file (RegTheDll.bat) which executes regsvr32.exe against ImageHandler.dll to register it.

Imagehandler will not be useable until you run the bat file and register it on your machine.

The HdcToJpg() Interface

HdcToJpg() is defined as follows:


virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE HdcToJpeg( 
    /* [in] */ long lngHDC,    //HDC of the target window
    /* [in] */ int nTLX,       //Rectangle top-left x-coord
    /* [in] */ int nTLY,       //Rectangle top-left y-coord
    /* [in] */ int nBRX,       //Rectangle bottom-right x-coord
    /* [in] */ int nBRY,       //Rectangle bottom-right y-coord
    /* [in] */ int nQuality,   //Quality factor for the JPEG (1-100)
    /* [in] */ BSTR bstrPath,  //Pathname to the JPEG file to be created
    /* [out] */ BSTR __RPC_FAR *pbstrMsg, //Message string to be 
                                          //   returned, may be NULL
    /* [retval][out] */ long __RPC_FAR *lngResult) = 0; //Result of call

The coordinates may be positive or negative. They should be in Logical units appropriate to the mapping mode of the HDC.

The quality factor must be in the range 1-100.

HdcToJpg() will return a message to the caller, unless pbstrMsg is set to NULL.

HdcToJpg() may return these messages:

  • HDC is invalid:
    The HDC parameter was NULL.
  • nQuality value is invalid (must be 1-100):
    The quality value was out of range.
  • JPEG file path is empty:
    The path value was empty.
  • Could not create CBitmap from rectangle:
    Additional string at end of one of the other error messages.
  • Could not create JPEG from CBitmap:
    Additional string at end of one of the other error messages.
  • <file> created OK:
    The JPEG file was created, no errors detected.
  • Could not get internal bitmap data:
    HdcToJpg() was unable to create internal bitmap information. This could be because the HDC is invalid, or because there was not enough memory available.
  • Could not create internal bitmap data:
    Same meaning as above.
  • Could not create DIB from DDB:
    HdcToJpg() probably could not get enough memory to turn its internal DDB into a DIB.
  • Failed to create compatible dc:
    CreateCompatibleDC() failed when creating the required memory DC. This is probably due to the rectangle being too large.
  • Failed to create compatible bitmap:
    CreateCompatibleBitmap() failed when creating the required memory DC. This is probably because the rectangle is too big.
  • Failed to do bitblt into memory DC:
    The StretchBlt() call to copy into the memory DC failed.

Return Values:

HdcToJpg() returns a result of either S_OK (0) or S_FALSE (1).

Sanity Checking:

HdcToJpg() does some basic sanity checks on the input parameters, such as making sure pointers are not NULL.

However, it does not check the rectangle coordinates. It is the caller's responsibility to make sure they make sense for the Device Context in question.

It does not check the path, other than to make sure that it is not empty. It is the callers's responsibility to make sure the pathname and file name are correct, and that the correct file extension is present (usually .jpg).

Creating the ImageHandler Interface using the Type Library

When I created the client project (the C++ driver whose snippet code appears below) I decided to do it the easy (lazy) way, and use the Imagehandler type library. This file is included in the executables download as Imagehandler.tlb.

To create the automation class IRectToJpeg:

  1. Open Class Wizard
  2. Click on the "Automation" tab.
  3. Click on the "Add Class..." button and pick the "From a type library..." option.
  4. In the "Import from Type Library" dialog, navigate to the ImageHandler.tlb file and choose it.
  5. In the "Confirm Classes" dialog, just keep the defaults given.

C++ Client Code

The example below shows a code snippet that creates an instance of the component and creates a JPEG file from a Device Context rectangle.

The snippet is part of the view class of an MDI or SDI application. A red rectangle has been drawn into the upper left corner of the view window, and this rectangle (plus some white space around it) will be turned into the JPEG file "redsquare.jpg".

If you wanted to code up a complete path, the string would look something like this:

CString csPath = "C:\\pictures\\jpegs\\redsquare.jpg";

The rectangle covers the area (0,0) to (200,-200). The mapping mode in use is MM_LOENGLISH, so this rectangle will grab the upper left corner of the view window, making a square 200 logical units on a side.

Since no other path information is given, the file will be created in the same directory where the MDI application executes.

Notice that the sample code draws into a memory DC; this way if the user makes the view window small, the rectangle will still be drawn and generated correctly.


//Header file for the wizard-created automation class
#include "ImageHandler.h"

void CImageHandlerDriverView::OnViewSavetojpegfile() 
{
  CLSID clsRectToJpeg;

  COleException COleE;

  CoInitialize(NULL);

  if ( FAILED( CLSIDFromProgID(L"ImageHandler.RectToJpeg",
                               &clsRectToJpeg)))
  {
    AfxMessageBox("Cannot find ImageHandler",
                   MB_OK | MB_ICONSTOP );
  }

  else
  {
    //This class was created from Class Wizard Automation 
    //    using the tlib.
    IRectToJpeg jpm;

    if (jpm.CreateDispatch(clsRectToJpeg,&COleE))
    {
        CClientDC dc(this);

        CDC memDC;

        CBitmap bmp;

        bmp.CreateCompatibleBitmap(&dc,600,600);
 
        memDC.CreateCompatibleDC(&dc);
        memDC.SelectObject(&bmp);
        memDC.SetMapMode(MM_LOENGLISH);

        //Make a white background and a red rect
        memDC.FillSolidRect(CRect(0,0,600,-600),RGB(255,255,255));
        memDC.FillSolidRect(CRect(25,-25,125,-125),RGB(255,0,0));

        HRESULT hr = S_OK;

        CString csMsg = " ";

        BSTR bstrMsg = SysAllocString(
                            (unsigned short *)(LPCTSTR)csMsg);

        TRY
        {
            //The component returns either S_OK or S_FALSE
            hr = jpm.HdcToJpeg(
            (long)(memDC.m_hDC), //HDC
            0,                   //Top Left x-coord
            0,                   //Top left y-coord
            200,                 //Bot right x-coord
           -200,                 //Bot right y-coord
            100,                 //Quality (1-100)
           "redsquare.jpg",      //Pathname to file
            &bstrMsg);     //Holds any message from component

            csMsg = bstrMsg;
        }

        CATCH_ALL(ce)
        {
            TCHAR szCause[255];

            ce->GetErrorMessage(szCause, 255);

            csMsg = "Exception thrown by HdcToJpg(): ";
            csMsg += (LPCTSTR)szCause;

            hr = S_FALSE;
        }
        END_CATCH_ALL

        SysFreeString(bstrMsg);

        memDC.DeleteDC();

        if (hr == S_OK)
          csMsg += ", result = S_OK";
        else
          csMsg += ", result = S_FALSE";

        AfxMessageBox(csMsg);
      }
  }
  
  CoUninitialize();
}

Calling the Interface from Visual Basic

To use the component from Visual Basic is even easier, as would be expected.

The snippet below executes as an event when a command button on a form is pushed. It grabs the top left corner of the form and copies it into a JPEG file called "tlcorner.jpg". Since no path information is given, the JPEG will be created in the same directory as the project executes from.

No memory DC is in use here, so if you make the form small, results are unpredictable...


'===================================================
'Grab the top left corner of the owning form
'(100 pixels X 100 pixels)
'and store it as a JPEG file
'===================================================
Private Sub cmdSaveJpeg_Click()
  Dim Ih As Object
  Dim nQuality As Integer
  Dim strPath As String
  Dim strMsg As String
  Dim lngResult As Long 

  strPath = "tlcorner.jpg"
  nQuality = 100 

  Set Ih = CreateObject("ImageHandler.RectToJpeg") 

  If IsObject(Ih) Then
    lngResult = Ih.HdcToJpeg(Me.hDC, _
                             0, _
                             0, _
                             100, _
                             100, _
                             nQuality, _
                             strPath, _
                             strMsg)
    MsgBox (strMsg + ", result = " + Str$(lngResult))

  Else
    MsgBox ("Could not create 'ImageHandler.RectToJpeg' object")
  End If
End Sub

Testing

ImageHandler has been tested under Win98 and Windows NT 4.0. It has been tested under 4 video modes: 256-color, 65536 hi-color, 24-bit true color, and 32-bit true color.

I set up a loop to create 1000 small jpeg files using ImageHandler, and watched system performance as the loop executed. There did not seem to be any resource leaks.

Imagehandler was built with MFC statically linked; therefore, the target machine does not need the MFC DLL in order for ImageHandler to execute.

Limitations

  1. The largest rectangle ImageHandler will process is about 2000 logical units on a side. If the rectangle is bigger than this, ImageHandler is unable to get enough contiguous memory to create its internal bitmap structures.
  2. The Imagehandler code is not "const safe".
  3. ImageHandler allocates and frees its own memory, and furthermore is not thread-safe. It will not be safe to use a given ImageHandler instance in a multi-threading situation.

Downloads (self-extracting archives)

There are two download files. Each is a self-extracting archive.

The first contains the Imagehandler project files. It will be useful for examining the code to produce the JPEG, but you will not be able to build the project unless you get a jpeg.lib built, and have the necessary libJPEG header files available.

The second contains the executables associated with this article:

  • ImageHandler.dll (160 KB)
  • ImageHandler.tlb
  • Regsvr32.exe
  • RegTheDll.bat, a batch file to perform registration using regsvr32.exe. Once you have downloaded and unzipped the archive, just execute the bat file to register the component.
  • ImageHandlerDriver.exe, the C++ driver application. To create the JPEG file, start the application, then choose the "View->Save to JPEG file..." menu option.
  • Project1.exe, the VB driver application. To create the JPEG file, start the application, then click on the "Save TL Corner of Form" button.
ImageHandler project - 73 Kb
ImageHandler DLL and Driver Executables - 286 Kb


Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Live Event Date: November 13, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT APIs can be a great source of competitive advantage. The practice of exposing backend services as APIs has become pervasive, however their use varies widely across companies and industries. Some companies leverage APIs to create internal, operational and development efficiencies, while others use them to drive ancillary revenue channels. Many companies successfully support both public and private programs from the same API by varying levels …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds