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:
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 :
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.