ThumbNailer ATL tutorial

ThumbNailer Visual C++ Image Processing plug-in Tutorial

For anyone with C/C++ programming experience, writing an Image Processing plug-in for S.A. ThumbNailer using Visual C++ 5 or 6 should be very easy. Follow these steps in the to generate a basic skeleton plug-in :



Create the Project

Create a new ATL COM AppWizard Project. For our example, we'll call it TNImagePlugin. Use the default ATL project settings. You can use MFC if you wish, though it will add at least 200K to the size of your plug-in. This example will not use MFC.

Create the Plug-in Object

Using the Class View window, create a New ATL Object. Choose SimpleObject, then Next. This will bring you to the ATL Object Wizard Properties page.

For the Short Name, enter the name of the project : TNImagePlugin. It is very important that the C++ Short Name for this object is the same as your project name!

For the .h and .cpp files, you will have to change the defaults. I usually add a "c" to the beginning of the file name, but you can change them however you like; the important thing is that you change the defaults!

Change the ProgID for this class so that the text after the "." is "TNImgAutomation". This is also very important. This is how S.A. ThumbNailer will know that your plug-in supports the proper interface.

Fix the .RGS file
Ignore this if you are using VC6 or higher

Open your .RGS file. It should be TNImagePlugin.rgs, in this example. We have to fix a VC5 bug. The first few lines of the file will read :

  TNImagePlugin.TNImgAutomation.1 = s 'TNImagePlugin Class'
  {
     CLSID = s '{whatever...}'
  }
  TNImagePlugin.TNImgAutomation = s 'TNImagePlugin Class'
  {
     CurVer = s 'TNImagePlugin.TNImgAutomation.1'
  }

Make a copy of the line beginning "CLSID = ". Paste it above the line beginning "CurVer = ". It should now look like something this :

  TNImagePlugin.TNImgAutomation.1 = s 'TNImagePlugin Class'
  {
     CLSID = s '{whatever...}'
  }
  TNImagePlugin.TNImgAutomation = s 'TNImagePlugin Class'
  {
     CLSID = s '{whatever...}'
     CurVer = s 'TNImagePlugin.TNImgAutomation.1'
  }


Done! (sortof)

Now, you have a valid S.A. ThumbNailer Image Processing plug-in that will do absolutely nothing.

Add the Basic Function Methods

We can now add some basic functionality to your plug-in.

There are 8 basic methods you should support:

  STDMETHODIMP GetPluginName(BSTR *txt, long * retval);
  STDMETHODIMP GetPluginDescription(BSTR *txt, long * retval);
  STDMETHODIMP GetPluginVersion(BSTR *txt, long * retval);
  STDMETHODIMP GetPluginCopyRightString(BSTR *txt, long * retval);
  STDMETHODIMP PluginHelpAvailable(long * retval);
  STDMETHODIMP PluginSettingsAvailable(long * retval);
  STDMETHODIMP PluginShowHelp(long * retval);
  STDMETHODIMP PluginShowSettings(long * retval);
These methods are supported by all ThumbNailer plugin types.

While these are not strictly required, they will make life easier for users of your plug-in.

GetPluginName

We'll start at the beginning. Using the Class View again, locate your ITNImagePlugin class. Right click on it and select Add Method. You should now see the Add Method To Interface dialog. In Method Name, enter:

   GetPluginName

For Parameters , enter :

  [in] BSTR *Name, [out, retval] long *retval

The retval parameter will be returned to S.A. ThumbNailer by the plug-in.

You must include this in all plug-in methods :

[out, retval] long *retval

To return a value from a function, set retval. You should always return S_OK from a plug-in function.

Press OK.

Open the .cpp file for your class. If you are following along, this will be cTNImagePlugin.cpp. Find the GetPluginName method. It should look like this :

  STDMETHODIMP CTNImagePlugin::GetPluginName(BSTR * Name /* [in] */ , 
                                             long * retval /* [out, retval] */)
  {
     return S_OK;
  }

Right now, this method does not do anything useful. We can change that by adding a few lines to the body of the method :

  STDMETHODIMP CTNImagePlugin::GetPluginName(BSTR * Name /* [in] */ , 
                                             long *retval /* [out, retval] */)
  {
     // CComBSTR is a class that works on BSTRs - BASIC Strings
     CComBSTR t;

     // set some text
     t= _T("TNImagePlugin");

     // copy to the output param
     *Name = t.Copy();

     // 1 = OK, 0 = not OK
     *retval = 1;

     return S_OK;
  }

Follow the same steps for the functions GetPluginDescription, GetPluginVersion and GetPluginCopyRightString.

Help and Settings

The two other basic methods you should implement are PluginHelpAvailable and PluginSettingsAvailable . These two functions tell S.A. ThumbNailer if this plug-in supports Help and/or Settings dialogs. Follow the same steps as above, but, because these functions take no input parameters, for the Parameters field in the Create Method dialog, only enter :

  [out, retval] long *retval

For now, add this line to the bodies of these functions :

  *retval = 0;

This will tell S.A. ThumbNailer to ignore the Help and Settings dialogs for your plugin. (Return 1, if you have a help or settings dialogs).

If you want to implement a Help dialog, you should add the PluginShowHelp method and have your plugin display a help dialog, a help file, etc..

If you want to implement a Settings dialog, you will add the PluginShowSettings method, and implement whatever settings interface your plug-in needs.

Note: You should not store settings in your plug-in object - it will be created and destroyed many times during the course of use. Instead, save settings to the Registry or an external file.

Image Processing Plug-in Methods

These methods are specific to ThumbNailer image processing plugins.

  STDMETHODIMP StartProcessing(long * retval);   
  STDMETHODIMP StopProcessing(long * retval);
  
  STDMETHODIMP SetThumbNailerProfileName(BSTR Profile, long * retval);
  STDMETHODIMP SetCurrentSourcePath(BSTR Path, long * retval);
  STDMETHODIMP SetCurrentThumbnailPath(BSTR Path, long * retval);
  
  STDMETHODIMP SkipImage(long * retval);

  STDMETHODIMP ProcessImage(long MemoryHandle, long Width, long Height, 
                            long Context, long * retval);

  STDMETHODIMP ProcessImage2(long *pMemoryHandle, long *Width, 
                             long *Height, long Context, long * retval);

  • StartProcessing is called when the program starts processing. You can use this call to perform any initilization you need. StopProcessing is called when all processing is finished.

  • SetThumbNailerProfileName tells the plug-in the name of the profile that is being used for this batch. This information may or may not be of interest to your plug-in. It is called at the start of the batch. If you don't need this, you don't have to implement it.

  • SetCurrentSourcePath tells your plug-in the name of the current source image file. SetCurrentThumbnailPath tells your plug-in the name of the current output file. These methods are called after the source image has been read and the output image name has been determined. If you don't need this, you don't have to implement it.

  • SkipImage is called after SetCurrentSourcePath and SetCurrentThumbnailPath. If you return a value of 1, the current image will not be processed further.

  • ProcessImage is where you do the actual image processing step. It receives a handle to a chunk of image data (24-bits, RGB, top-down) along with the width and height. Plugins will modify this image data. ProcessImage cannot modify the input image size. If you need to change the image size, you must use ProcessImage2.

  • ProcessImage2 this is similar to ProcessImage. But, this function requires you to return a new image buffer; this means you can resize the image. If it will not need to modify the size of the image, your plugin should implement ProcessImage.

ProcessImage

ProcessImage is the main processing function for this type of plug-in. It allows you to directly modify the image data.

The parameters for ProcessImage are :

long MemoryHandle an HGLOBAL cast as a long. This is a handle to the image data.
long Width width of image, in pixels
long Height height of image, in pixels
long Context identifies the current position in the image processing chain.
...and don't forget the long *retVal...

Context is one of :
Value Meaning (Image State)
0 raw image as read from file
1 image has been alpha blended and resized to the final output size
2 image overlay has been applied, sharpening and LUT have been applied
3 text overlay has been applied, image has been matted and beveled.
The image is ready to be written

Notes :

  • In all cases, the image is formatted as RGB data, 3 BYTEs per pixel, top-down. To get a BYTE pointer to the image data, use GlobalLock. When you are done with the pointer, call GlobalUnlock.
  • You cannot change the image size with ProcessImage (you can, however, with ProcessImage2).
  • Never call GlobalFree with MemoryHandle.
  • The image size will change between context 0 and 1.
  • It is not necessary to handle all possible contexts.
  • If you would like to signal an error, return a value of 0 in retVal. This will cause S.A. ThumbNailer to stop and report an error of "Plug-in Cancel".

A simple implementation of ProcessImage might look like this :

  // make all images photo-negative by negating the 
  // pixels at the final processing step
  STDMETHODIMP CImgPlugIn::ProcessImage(long MemoryHandle, 
                                        long Width, long Height, 
                                        long Context, long * retval)
  {
    // this shouldn't happen, but it's good to be safe
    if (MemoryHandle==NULL)
    {
      // this is an error . 
      // retval = 0 will stop thumbnailer
      *retval = 0;
      return S_OK;
    }

    // we'll only process on the last step
    if (Context!=3)
    {
      // not an error, we just don't care
      *retval = 1;
      return S_OK;
    }

    // get a BYTE pointer to the RGB image data
    BYTE *pMem = (BYTE *)GlobalLock((HGLOBAL)MemoryHandle);

    if (pMem!=NULL)
    {
      // do this to all 3 components of all pixels
      // each pixel is 3 BYTEs (Red, Green and Blue)
      for (DWORD i =0; i < (DWORD)Width * (DWORD)Height * 3; i++)
      {
        // make this component photo-negative
        *(pMem) = 255 - *(pMem);

        // next
        pMem++;
      }
      
      // this is important. unlock the memory handle
      GlobalUnlock((HGLOBAL)MemoryHandle);

      // everything worked
      *retval = 1;
    }
    else
    {
      // this is an error
      // retval = 0 will stop thumbnailer
      *retval = 0;
    }

    return S_OK;
  }

ProcessImage2

ProcessImage2 is a method that lets you return a new image to the caller. As with ProcessImage, the caller will provide you with an input image. But, unlike ProcessImage, you are responsible for allocating and returning a new image. ProcessImage2 is only supported on ThumbNailer v6.1.2.0 and higher.

The parameters for ProcessImage2 are :

long *pMemoryHandle a pointer to an HGLOBAL cast as a long. When the function is called, this is a handle to the input image data. When you return, you must set this to reference the new image you have allocated. (you must return a new buffer, you can't reuse the input buffer!)
long * pWidth width of image, in pixels, on input. Set this to the new width on output.
long * pHeight height of image, in pixels, on input. Set this to the new height on output.
long Context identifies the current position in the image processing chain (same values as in ProcessImage).
...and don't forget the long *retVal...

Notes :

  • In all cases, the image is formatted as RGB data, 3 BYTEs per pixel, top-down. To get a BYTE pointer to the image data, use GlobalLock. When you are done with the pointer, call GlobalUnlock. You are not required to free the input image; the caller will take care of that.
  • The image size will change between context 0 and 1.
  • It is not necessary to handle all possible contexts.
  • If you would like to signal an error, return a value of 0 in retVal. This will cause S.A. ThumbNailer to stop and report an error of "Plug-in Cancel".

A simple implementation of ProcessImage2 might look like this :

// make all images photo-negative by negating the pixels at 
// the final processing step
STDMETHODIMP CImgPlugIn::ProcessImage2(long *pMemoryHandle, 
                                       long * pWidth, long *pHeight, 
                                       long Context, long * retval)
{
    // this shouldn't happen, but it's good to be safe
    if ((pMemoryHandle==NULL)  || (pWidth==NULL) || (pHeight==NULL))
    {
      // this is an error . 
      // retval = 0 will stop thumbnailer
      *retval = 0;
      return S_OK;
    }

    if (*pMemoryHandle==NULL)
    {
      // this is an error, too.
      *retval = 0;
      return S_OK;
    }

    // we'll only process on the last step
    if (Context!=3)
    {
      // not an error, we just don't care
      *retval = 1;
      return S_OK;
    }

    // get a BYTE pointer to the RGB image data
    BYTE *pMem = (BYTE *)GlobalLock((HGLOBAL)*pMemoryHandle);

    if (pMem!=NULL)
    {
      // allocate an output image
      long newWidth = 100;
      long newHeight = 100;
      HGLOBAL hNew = GlobalAlloc(GPTR, newWidth * newHeight * 3);

      .... 
      do something with the input image data to fill up the output image.

      this step is left as an exercise.
      ....
  
      // unlock the input memory handle
      GlobalUnlock((HGLOBAL)*pMemoryHandle);

      // set the output variables
      *pMemoryHandle = hNew;
      *pWidth = newWidth;
      *pHeight = newHeight;

      // everything worked
      *retval = 1;
    }
    else
    {
      // this is an error
      // retval = 0 will stop thumbnailer
      *retval = 0;
    }

    return S_OK;
}


Register and Use it


Next, you need to place the plug-in DLL into the "plugins" folder. This must be a sub-folder of the folder where your ThumbNailer (or SASheet) .EXE lives.

Strictly speaking, it is only necessary that you have a file with the same name as your DLL in the plugins folder. When S.A. ThumbNailer looks for plugins, it scans for all DLLs in the plugins folder, adds ".TNImgAutomation" (or ".TNAutomation" for HTML plug-ins) to each DLL's name to create a ProgID. Then, S.A. ThumbNailer tries to create a plug-in with that ProgID by using COleDispatchDriver::CreateDispatch with that ProgID. S.A. ThumbNailer never really tries to use a plug-in DLL directly. It only needs a way to generate ProgIDs so that Windows can create the object.

Admittedly, this is a hack, but given that there is no reliable way to get a list from Windows, of the components that implement a given interface, this is a reasonable way to do it.

The final step is to register your plug-in DLL using RegSvr32. The easiest way to register the DLL is to use the button on S.A. ThumbNailer's plug-in dialog. This will call RegSvr32 with the path to your DLL. Registering the DLL adds entries into the system registry that will allow S.A. ThumbNailer to find and use your plug-in.

Summary


The operation of a S.A. ThumbNailer Image Processing plug-in is divided into two areas. The basic methods provide the user with information about the plug-in's name, description and capabilities - GetPluginName, PluginHelpAvailable, etc.. The real work is done by the ProcessImage method. ProcessImage will receive a handle to the current image memory at four points in S.A. ThumbNailer's main image processing loop.


Copyright © 2008, Smaller Animals Software, Inc.

Smaller Animals News

ThumbNailer 9.0

  • The latest version of ThumbNailer is out!

    ImgSource 4.0

  • ImgSource 4.0 now has x64 support!

    New Product!!!!

    Introducing ClickPic! This little program lets you quickly resize, rotate and convert images, all from within Windows Explorer. Just right-click on an image file!
  • ThumbNailer news

    Latest version: 9.0.4.4

    Frequently Asked Questions
    Template Tutorial

    Plugin news

    • New v8 Plugin - TNContrastMask - Automatically brings out shadow and highlight details.
    • Updated Plugin - TNRAW - Added support for nearly ten more cameras, including the Canon EOS 20D.