Objective Toolkit : Chapter 9 Image Classes : Using the Image Classes
Using the Image Classes
Typically, an image class loads the image from a file, renders the image on the display with the StretchDIBits() API, performs any number of image processing manipulations on the image, and then saves the modified image to a new file. The sections that follow explain how to perform each of these actions with the SECImage family of classes.
SECImage is serializable, so if you include an SECImage instance in your document, it is serialized with the rest of your data.
To read an image from a file
The standard method for reading images is LoadImage(). LoadImage() is a virtual function in SECImage that is implemented by each format-specific derived image class. The SECImage derivative classes read image data from a format-specific image file and translate it to the intermediate format used by the parent SECImage class. When an image is loaded, the m_pPalette member of SECImage is created, which creates a palette for the loaded image based on the loaded color map.
The following code loads a .PCX file.
 
// . . .
SECPcx pcx;
if (pcx.LoadImage(“check.pcx”) == FALSE)
ASSERT(1); //LoadImage FAILED!
//Now we have an image loaded, and it can be displayed
To view GIF/TIFF images
Two Objective Toolkit classes use LZW compression: SECGIF and SECTiff. These classes contain stub routines where the LZW compression algorithms belong. As a result, if you attempt to load or save images of GIF or TIFF format, an error occurs at run time.
NOTE >> GIF is a popular format for graphics to be viewed in Web browsers, while TIFF (tag-based image file format) is a digital data format compatible with a variety of scanners, faxes, and other image-processing applications.
We offer a GIF/TIFF Unlock Pack (lzwcode.zip) that allows you to replace the stubbed classes with the source code of the algorithm.
To display an image
Once an image has been successfully created or loaded from an image file, you can display it on any device context (DC) by treating the data contained by SECImage as a device independent bitmap (DIB). A DIB is typically rendered to a DC via the StretchDIBits() API. Objective Toolkit encapsulates the StretchDIBits() call through its own StretchDIBits() method.
The advantage of using the Objective Toolkit encapsulated StretchDIBits() is that it increases the resolution of the image you’re manipulating to a resolution greater than the one available on your DC. In this case, Objective Toolkit automatically displays the image correctly. For example, if you attempted to display a 24-bits-per-pixel image on an 8-bits-per-pixel display in the WIN32 environment, Objective Toolkit would make a call to the CreateHalftone() palette API, which creates a palette with the closest matching color values to the image in memory. The approximated palette contains the standard colors for the image. In the 16-bit environment, Objective Toolkit uses its own internal quantize routine to perform an operation similar to CreateHalftone().
The following code displays an image.
void CImageView::OnDraw(CDC* pDC)
{
SECImage * pImage = pDoc->GetImage();
CPalette *pOldPalette;
 
// If a palette has been created for the image, select it.
if (pImage->m_pPalette)
pOldPalette =
pDC->SelectPalette(pImage->m_pPalette, TRUE);
// Call encapsulated StretchDIBits API
pImage->StretchDIBits(pDC,
0,0, pDoc->GetDocSize().cx,
pDoc->GetDocSize().cy, 0,0,
pImage->m_dwWidth,
pImage->m_dwHeight,
pImage->m_lpSrcBits,
pImage->m_lpBMI, DIB_RGB_COLORS,
SRCCOPY );
 
// Restore the palette
if (pImage->m_pPalette)
pDC->SelectPalette(pOldPalette, TRUE);
}
To convert an image
The SECImage family of classes allows you to convert from one image format to another easily. For example, if you wanted to convert an GIF image to an JPEG image, you would add a few lines of code using the Objective Toolkit image classes.
Image format conversion is performed using the ConvertImage() method. Given a source image and a destination image, ConvertImage() can use image instances interchangeably. For example, a .PCX image can be loaded into memory through an instance of the SECPcx class and then converted with an instance of any other derived image class. Unlike CopyImage(), ConvertImage() does not duplicate image data. Once an image is converted, you can safely delete the source image class instance.
The following code demonstrates how to convert an SECPcx image into an SECGif image:
void TestConversion(SECPcx *pSrc)
{
// Create destination instance
SECGif *pDest = new SECGif();
pDest->ConvertImage(pSrc);
 
// Now pDest is a valid image, while pSrc
// contains no data...
pDest->SaveImage(“convert.gif”);
 
// Convert back to validate the source image again.
pSrc->ConvertImage(pDest);
 
// delete the destination, will not affect
// pSrc data members at all
delete(pDest);
}
To copy an image
SECImage supplies a CopyImage() routine that allows you to create a duplicate of a source image. You can use this routine to make a copy of an image before allowing your user to change it. Then, you can allow the user to revert to the original image.
CopyImage() behaves the same way the ConvertImage() method does, except it does not alter the source image so the user can use it after the copy is performed.
The following code copies a TIFF image to a JPEG image:
 
void TestCopy(SECTiff *pSrc)
{
// create the destination image
SECJpeg *pJpeg = new SECJpeg();
if (!pJpeg)
return;
 
if (!pJpeg->CopyImage(pSrc))
{
delete pJpeg;
return;
}
 
// Now you have two independent copies of the
// same image in 2 different formats
// destruction of object will destroy the
// copied image data, while pSrc is left intact.
delete pJpeg;
}
NOTE >> Images often require a large amount of memory. Creating a copy does not perform any compression or savings in memory. In effect, it requires twice the amount of memory.
To manipulate an image
Once you create an image and load it into memory, you can manipulate the image through a number of SECImage methods.
For example, you can flip and rotate the image. You can flip an image vertically or horizontally by calling the FlipVert() or FlipHorz() methods. You can rotate images 90 degrees counter-clockwise by calling the Rotate90() method.
ContrastImage() accepts a signed integer to modify the contrast of the image. A positive value increases the sharpness of the image and a negative value decreases its sharpness.
CropImage() accepts coordinates of a clipping rectangle used to crop the image currently loaded in memory. Coordinates are passed in as the left, top, right and bottom positions. If positions are passed that extend beyond the maximum reach of the current image, the clipping coordinates are limited to the rightmost and bottommost positions of the image.
The following code demonstrates how to use the image manipulation methods.
 
void Manipulate(SECPcx *pSrc)
{
// First rotate it
pSrc->Rotate90();
 
// Next Flip it on the horizontal axis
pSrc->FlipHorz();
 
// Flip again on the vertical axis
pSrc->FlipVert();
 
// Dull the image by decreasing contrast
pSrc->ContrastImage(-1);
 
// Then crop the image to a hypothetical region
pSrc->CropImage(50, 50, 100, 100);
}
To write an image to a file
SECImage and derivatives allow you to save an image to a file via the SaveImage() method. SaveImage() succeeds only when a legitimate image is loaded in the image class. The following code demonstrates how to save a .PCX image to a file named new.pcx.
 
if (pcx.SaveImage(“new.pcx”) == FALSE)
ASSERT(1); //SaveImage failed!
To convert to a CBitmap object
Using Objective Toolkit, you can create a device-specific bitmap from an SECImage instance. To do this, ensure that then image is at the same resolution as the device. Otherwise, the CBitmap is created for an incorrect display type. You can create a CBitmap object with the MakeBitmap() method, which returns a pointer to a new CBitmap object. MakeBitmap() accepts a pointer to the current device context as an argument (CDC pointer).
To convert from a CBitmap object
When you perform GDI drawing functions to a device context (CDC object), you need to convert the image of the bitmap selected in the device context to the SECImage format. To do so, use the CreateFromBitmap() method, which accepts CBitmap and CDC pointers as arguments. The created SECImage instance contains the same dimensions and depth as the CBitmap itself. If the CDC is a memory device context, ensure that you clear the bitmap with a GDI call before making the call to CreateFromBitmap().
To create from a CDC object
This is a two-step process:
1. Create a CBitmap object from your CDC.
2. Instantiate an SECImage object and call SECImage::CreateFromBitmap().
You can create a CBitmap with the following code:
 
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc);
 
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
 
CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
 
// Todo: draw to the memory dc here...
 
dcMem.SelectObject(pOldBitmap);
NOTE >> SECImage::CreateFromBitmap() takes a pointer to a CBitmap object and to a CDC object to copy the image data from the CBitmap.
To load an image from a resource
The SECImage base class does not support direct loading of image data from a resource; however, you can load images indirectly. After you have imported your image file as binary data in the resource editor, do the following to load it:
1. Obtain a pointer to the resource data.
2. Attach the data to a CMemFile object.
3. Instantiate an SECImage-derived object and call LoadImage() using the CMemFile.
The following section of code demonstrates this:
 
// The image is stored as a resource in file format.
 
HINSTANCE hinst = AfxGetInstanceHandle();
HRSRC hRes = FindResource(hinst, szResNavn, szResType);
if (hRes == NULL)
{
TRACE2("Couldn't find restype %s resource %s!\n",
szResType,szResNavn);
return FALSE;
}
 
// need the pointer to the image data and it's length
DWORD len = SizeofResource(hinst, hRes);
BYTE* lpImage = (BYTE*)LoadResource(hinst, hRes);
ASSERT(lpRes);
 
// CMemFile is CFile-derived, and will soon be used to
// to load the image using SECImage::LoadImage
CMemFile* pImgMemFile = new CMemFile();
SECJpeg* pJpeg; \\ let's assume I know the image will be JPEG
 
// Attach the image data to a CMemFile, which will allocate space.
// ImageBufLen is the size of the image buffer
pImgMemFile->Attach(lpImage, lImageBufLen);
 
// now use the CMemFile to load into the SECJpeg object
// for manipulation or display
pJpeg = new SECJpeg();
if (!pJpeg->LoadImage(pImgMemFile))
{
TRACE0("Couldn't LoadImage");
return FALSE;
}
 
// can delete the CMemFile now since LoadImage allocated its own
// space
delete pImgMemFile;
pImgMemFile = NULL;
 
FreeResource((HANDLE)lpRes);
return(str);
 
. . .
To stream image data
Although the SECImage class does not directly support streaming, you can save SECImage to a CMemFile and then stream the data from CMemFile.
 
// The image is stored in the database in file format. It has
// been retrieved here to a buffer called pImageInBuffer;
 
LPBYTE lpImage = pImageInBuffer;
CMemFile* pImgMemFile = new CMemFile(); \\ CFile derived class
SECJpeg* pJpeg; \\ let's assume I know the image will be JPEG
 
// Attach the image data to a CMemFile, which will allocate space.
// ImageBufLen is the size of the image buffer
pImgMemFile->Attach(lpImage, lImageBufLen);
 
// now use the CMemFile to load into the SECJpeg object
// for manipulation or display
pJpeg = new SECJpeg();
if (!pJpeg->LoadImage(pImgMemFile))
error("Couldn't LoadImage");
 
// can delete the CMemFile now since LoadImage allocated its
// own space
delete pImgMemFile;
pImgMemFile = NULL;
 
// display the image in the pJpeg and or whatever manipulations
// are required to it
...
 
// now save it back to a CMemFile
pImgMemFile = new CMemFile;
if (!pJpeg->SaveImage(pImgMemFile))
error("Couldn't SaveImage");
 
// can now get the image back to the database via the lpImage ptr
// this will store the image length in lImageBufLen and the
// image itself
// into lpImage
lImageBufLen = pImgMemFile->GetLength();
lpImage = pImageMemFile->Detach();
 
// probably don't want to delete the pImageMemFile until you've
// copied lpImage to the database or at lease to some other buffer
// since Detach() just returns a pointer to CMemFile's buffer.
Ensure that you set the nGrowBytes for the CMemFile. The default is 1024.