GDI+ again makes our lives easier, facilitating the management of multi-frame images. In this article, I’ll present a subclass from GpImage (the image class from _gdiplus.vcx) that wraps some methods from gdiplus.dll that are not present in the original wrapper class that came with VFP9.
The procedures below make use of some internal functions of GpImage that deal with the EncoderParameters and GUIDs. I tried to keep the formats the most likely to what Walter Nicholls gifted us in _gdiplus.vcx.
Methods added to GPImage
As you can see at the links above, in MSDN we can find good information about all these functions.
Multi-frame TIFF images
According to Bob Powell, “A multiple page TIFF is effectively a bunch of images stored one-after-the-other in a single file. The files may be of different formats and even have different physical sizes. These individual images, or frames, may be accessed by selecting the frame to be viewed and drawing it as you would any other image. The process of creating such a file is controlled by using a single image as a master frame and adding other images to it in sequence.” The code below creates a multi-frame TIFF image with 4 images, and is based on the example from MSDN at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/usingGDIPlus/usingimageencodersanddecoders/creatingandsavingamultipleframeimage.asp
First of all, it loads from the disk the four images in GpImage objects (loMulti and loimage2-4). To save the first page, call the SavetoFile method of the GpImage class. To save subsequent pages, call the SaveAdd method of the Image class.
As the individual pages are added to the multi Image object, they are also added to the disk file Multiframe.tif. Note that the code calls Save (not SaveAdd) to save the first page. The first argument passed to the Save method is the name of the disk file that will eventually contain several frames. The second argument passed to the Save method specifies the CLSIDEncoder, (CLSID of the encoder to use, or a MIME type – e.g. "image/tiff"). The third argument is the encoder that will be used to convert the data in the multi Image object to the format (in this case TIFF) required by the disk file. The Guid member of that EncoderParameter object is set to EncoderSaveFlag. The Value member of the EncoderParameter parameter must contain the value EncoderValueMultiFrame ("SAVEFLAG=18").
That same encoder is used automatically by all subsequent calls to the SaveAdd method of the multi-frame object. The code saves the second, third, and fourth pages by calling the SaveAdd method of the multi-frame image object. The first argument passed to the SaveAdd method is an Image object. The image object is added to the multi-frame image object and is also added to the Multiframe.tif disk file. The second argument passed to the SaveAdd method is the EncoderParameters. The SaveFlag Encoder Parameter now contains the value EncoderValueFrameDimensionPage ("SAVEFLAG=23").
When the last image is added, it is good to pass another Encoder Parameter to tell gdiplus that the encoder object is to be closed, passing SaveFlag Encoder Parameter with the value EncoderValueFlush ("SAVEFLAG=20").
*!* PROGRAM : UT_TIFF_CreateMultiFrame *!* AUTHOR : CESAR CHALOM *!* Example to show how to create MultiFrame TIFFs #INCLUDE GDIPLUS.H #DEFINE EncoderValueMultiFrame 18 *!* Specifies multiple-frame encoding. #DEFINE EncoderValueFlush 20 *!* Specifies that the encoder object is to be closed. #DEFINE EncoderValueFrameDimensionPage 23 *!* For a TIFF image, specifies the page frame dimension SET PROCEDURE TO _myGdiplus.prg ADDITIVE LOCAL lcSource, loMulti, loImage2, loImage3, loImage4 lcSource = GETPICT() loMulti = CREATEOBJECT("myGpImage") loMulti.CreateFromFile(lcSource) lcSource = GETPICT() loImage2 = CREATEOBJECT("myGpImage") loImage2.CreateFromFile(lcSource) lcSource = GETPICT() loImage3 = CREATEOBJECT("myGpImage") loImage3.CreateFromFile(lcSource) lcSource = GETPICT() loImage4 = CREATEOBJECT("myGpImage") loImage4.CreateFromFile(lcSource) *!* Create the file that will receive all images *!* For multiframe images, the 3rd parameter of "SavetoFile", rvEncoderParams, *!* MUST be passed as "SAVEFLAG=18" ,EncoderValueMultiFrame, to inform gdiplus.dll *!* that the just created file needs to be prepared to store more than one image loMulti.SaveToFile("c:\MultiFrame.tif","image/tiff","SAVEFLAG=18") && EncoderValueMultiFrame *!* The next images are stored using SaveAddImage, and rvEncoderParams MUST be *!* "SAVEFLAG=23" ,EncoderValueFrameDimensionPage loMulti.SaveAdd(loImage2,"SAVEFLAG=23") && EncoderValueFrameDimensionPage loMulti.SaveAdd(loImage3,"SAVEFLAG=23") && EncoderValueFrameDimensionPage *!* In the last image we can inform gdiplus that it can close the file, sending *!* "SAVEFLAG=23,SAVEFLAG=20" to add another image and close the file loMulti.SaveAdd(loImage4,"SAVEFLAG=23,SAVEFLAG=20") && EncoderValueFrameDimensionPage, EncoderValueFlusH RETURN
Animated GIFs
Unfortunately, it is not possible to create Multi-frame or Animated GIFS using GdiPlus.dll version 1 image encoders. The SaveAdd method which can be used to add frames to a multi-frame TIFF image does not work with the GIF encoder.
Anyway, some interesting information from the GIF frames can be retrieved using the MetadataTag Properties (see my last article in the Universal Thread Magazine entitled “Saving and retrieving metadata information from your pictures with GDI+” from the April 2006 issue):
Run the code below and select any animated GIF to see the properties:
LOCAL lcSource, lcInfo, lnFrameDelay, lnLoopCount lcSource = GETPICT("gif") LOCAL loImage AS GpImage OF ffc/_gdiplus.vcx loImage = NEWOBJECT("GpImage", HOME() + "ffc/_gdiplus.vcx") loImage.CreateFromFile(lcSource) lnFrameDelay = loImage.GetPropertyItem(20736) lnLoopCount = loImage.GetPropertyItem(20737) lcInfo = "Image : " + lcSource + chr(13) + chr(13) + ; "Frame Delay : " + transform(lnFrameDelay) + chr(13) +; "Loop Count : " + transform(lnLoopCount) MESSAGEBOX(lcInfo,64,"GIF Properties")
Extracting frames from GIF’s and TIFF’s
For this task gdiplus brings these two functions: GetFrameCount and SelectActiveFrame. After loading the multi-frame image, we can get the number of frames using GetFrameCount, then select each frame with SelectActiveFrame and save every image in a separate file using the original SaveToFile function from GpImage. The first step is to identify whether the file is a TIFF or a GIF, because both GetFrameCount and SelectActiveFrame need to receive as a parameter the address of a globally unique identifier (GUID) that specifies the dimension in which the frames were previously added to the multiple-frame file. The GUIDS can be one of these:
#DEFINE FrameDimensionPage "{7462DC86-6180-4C7E-8E3F-EE7333A7A483}" #DEFINE FrameDimensionTime "{6AEDBD6D-3FB5-418A-83A6-7F45229DC872}"
The following example retrieves the individual frames from any multiple-frame TIFF or animated GIF files, saving each page to a separate TIFF or GIF disk file. The first step is to load the multi-frame Image, which can be a GIF or a TIFF file. Then we use JUSTEXT(lcSource) to know the file extension of image loaded – this will be the parameter to be sent to GetFrameCount and SelectActiveFrame. The next step is to use GetFrameCount to get the number of frames. After that just create a loop starting from 0 to the number of frames – 1, because the first frame has always its index number zero, and select each frame with SelectActiveFrame, and save each frame using SavetoFile. Run the code below and select any animated GIF or Multi-frame TIFF and see the results:
*!* PROGRAM : UT_GetAllFrames.prg *!* AUTHOR : CESAR CHALOM *!* Example to show how to extract images from any Multi-framed TIFF or Animated GIF *!* GUID for a GIF image #DEFINE FrameDimensionTime "{6AEDBD6D-3FB5-418A-83A6-7F45229DC872}" *!* GUID for a TIFF image #DEFINE FrameDimensionPage "{7462DC86-6180-4C7E-8E3F-EE7333A7A483}" SET PROCEDURE TO _myGdiplus.prg ADDITIVE LOCAL lcSource, lcDestFrame, loImage, lnImages, n lcSource = GETPICT("gif;tif") lcDestFrame = JUSTPATH(lcSource) + "\_" + JUSTSTEM(lcSource) + "_Frame" lcExt = JUSTEXT(lcSource) loImage = CREATEOBJECT("myGpImage") loImage.CreateFromFile(lcSource) lnImages = loImage.GetFrameCount(lcExt) *!* The parameter could be also *!* lnImages = loImage.GetFrameCount(FrameDimensionPage) MESSAGEBOX("Images : " + TRANSFORM(lnImages),64,"GDI+ GetFrameCount()") FOR n = 0 TO lnImages - 1 loImage.SelectActiveFrame(lcExt, n) *!* The parameter could be also *!* loImage.SelectActiveFrame(FrameDimensionPage, n) loImage.SaveToFile(lcDestFrame + TRANSFORM(n) + "." + lcExt,; IIF(UPPER(lcExt)= "GIF", "image/gif", "image/tiff")) ENDFOR
or
The two animated GIF’s above were taken from http://www.gifs.net that offers many cool animated GIF images for free.
lcDestMerge = JUSTPATH(lcSource) + "\_" + JUSTSTEM(lcSource) + "_Merge" lnWidth = 0 lnHeight = 0 FOR n = 0 TO lnImages - 1 loImage.SelectActiveFrame(lcExt, n) *!* The parameter could be also *!* loImage.SelectActiveFrame(FrameDimensionPage, n) loImage.SaveToFile(lcDestFrame + TRANSFORM(n) + "." + lcExt, ; IIF(UPPER(lcExt)= "GIF", "image/gif", "image/tiff")) lnWidth = lnWidth + + loImage.ImageWidth lnHeight = lnHeight + + loImage.ImageHeight ENDFOR *!* Create Big Image that will contain all Images Merged one above the other LOCAL loMergeBitmapV AS GpBitmap OF ffc/_gdiplus.vcx loMergeBitmapV = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx") LOCAL loGraphV AS GpGraphics OF HOME() + ffc/_gdiplus.vcx loGraphV = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx") loMergeBitmapV.Create(loImage.ImageWidth, lnHeight) loGraphV.CreateFromImage(loMergeBitmapV) lnHeight = 0 FOR n = 0 TO lnImages - 1 loImage.SelectActiveFrame(lcExt, n) loGraphV.DrawImageAt(loImage, 0, lnHeight) lnHeight = lnHeight + loImage.ImageHeight ENDFOR loMergeBitmapV.SaveToFile(lcDestMerge + "V" + "." + lcExt, ; IIF(UPPER(lcExt)= "GIF", "image/gif", "image/tiff")) *!* Create Big Image that will contain all Images Merged side by side LOCAL loMergeBitmapH AS GpBitmap OF ffc/_gdiplus.vcx loMergeBitmapH = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx") LOCAL loGraphH AS GpGraphics OF HOME() + ffc/_gdiplus.vcx loGraphH = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx") loMergeBitmapH.Create(lnWidth, loImage.ImageHeight) loGraphH.CreateFromImage(loMergeBitmapH) lnWidth = 0 FOR n = 0 TO lnImages - 1 loImage.SelectActiveFrame(lcExt, n) loGraphH.DrawImageAt(loImage, lnWidth, 0) lnWidth = lnWidth + loImage.ImageWidth ENDFOR loMergeBitmapH.SaveToFile(lcDestMerge + "H" + "." + lcExt, ; IIF(UPPER(lcExt)= "GIF", "image/gif", "image/tiff")) RETURN
Editing or adding images to TIFF images
Unfortunately, Multiple-Frame TIFF images cannot be directly edited. It is not possible to open a TIFF, insert or remove a frame and save it again in the same file. But it is possible to open the image file, extract all images, saving each frame as a separate .TIF file or create individual image objects that will store each image. Then manipulate or edit any extracted image and finally create a new TIFF multiple-frame image in the desired sort order, adding or removing frames.
The program below loads a multiple-frame image, then checks if it is a multi-frame image with at least four images. If yes, it will create a new TIFF image, adding the second and fourth frames to it. Note that the first index image is always zero, so we need to pass the integers 1 and 3 as parameters to SelectActiveFrame method, to select the correct image. Another important point is that because we are adding images from another multi-frame image, the SaveAdd method does not need to receive the image to be pasted as a parameter.
This example is adapted from the example provided by MSDN in http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusReference/Classes/ImageClass/ImageMethods/ImageSaveAddMethods/SaveAdd.asp
“The following example creates an Image object based on a TIFF file that has four frames. The code calls the Image::SelectActiveFrame method to navigate to the second frame in the page dimension of that Image object. (The page dimension is the only dimension in this case.) Then the code calls the Save method to save the second frame to a new file named TwoFrames.tif. The code calls the Image::SelectActiveFrame method again to navigate to the fourth frame of the Image object. Then the code calls the Image::SaveAdd method to add the fourth frame to TwoFrames.tif. The code calls the Image::SaveAdd method a second time to close TwoFrames.tif, and then draws the two frames that were saved in that file.”
*!* PROGRAM : UT_TIFF_MultiFrame2 *!* AUTHOR : CESAR CHALOM *!* Example to show how to create Multi-frame TIFFs #DEFINE EncoderValueMultiFrame 18 *!* Specifies multiple-frame encoding. #DEFINE EncoderValueFlush 20 *!* Specifies that the encoder object is to be closed. #DEFINE EncoderValueFrameDimensionPage 23 *!* For a TIFF image, specifies the page frame dimension SET PROCEDURE TO _myGdiPlus.prg ADDITIVE LOCAL lcSource lcSource = GETPICT("TIF") LOCAL loImage AS GpImage OF _MyGdiplus loImage = CREATEOBJECT("myGpImage") loImage.CreateFromFile(lcSource) lnImages = loImage.GetFrameCount("TIFF") *!* The parameter could be also *!* lnImages = loImage.GetFrameCount(FrameDimensionPage) IF lnImages < 4 MESSAGEBOX("Images : " + TRANSFORM(lnImages) + CHR(13) + ; "Please run this prg again, selecting a TIFF image with at least 4 frames !",; 64,"Incorrect TIFF Image") ENDIF loImage.SelectActiveFrame("TIFF", 1) && this is the 2nd frame loImage.SaveToFile("c:\TwoFrames.tif","image/tiff","SAVEFLAG=18") && EncoderValueMultiFrame loImage.SelectActiveFrame("TIFF", 3) && this is the 4th frame loImage.SaveAdd("SAVEFLAG=23") && EncoderValueFrameDimensionPage loImage.SaveAdd("SAVEFLAG=20") && EncoderValueFrameDimensionPage,EncoderValueFlusH RETURN
Multi-dimension images
A multiple-dimension or multiple-resolution image is an image that contains more than one copy of an image at different resolutions. This kind of image is not that usual, some digital cameras generate the original picture and also another in a smaller size and resolution. They can be manipulated as well, using GetFrameDimensionsCount and GetFrameDimensionsList methods from gdiplus. They do exactly what their names suggest.
GetFrameDimensionsCount returns the number of frame dimensions in this Image object, and GetFrameDimensionsList returns an array containing the identifiers for the frame dimensions of this Image object.
The example below is adapted from the example provided by MSDN in http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusReference/Classes/ImageClass/ImageMethods/GetFrameDimensionsCount.asp
“The code calls the GetFrameDimensionsCount method to find out how many frame dimensions the Image object has. Each of those frame dimensions is identified by a globally unique identifier (GUID), and the call to Image::GetFrameDimensionsList retrieves those GUIDs. The first GUID is at index 1 in the DimensionIDList array.”
*!* PROGRAM : UT_FrameDimensions.prg *!* AUTHOR : CESAR CHALOM *!* Example to show how to get FrameDimensions Information *!* Code adapted from MSDN #DEFINE EncoderValueMultiFrame 18 *!* Specifies multiple-frame encoding. #DEFINE EncoderValueFlush 20 *!* Specifies that the encoder object is to be closed. #DEFINE EncoderValueFrameDimensionPage 23 *!* For a TIFF image, specifies the page frame dimension SET PROCEDURE TO _myGdiPlus.prg ADDITIVE LOCAL lcSource, lnDimensions lcSource = GETPICT() LOCAL loImage AS GpImage OF _MyGdiplus loImage = CREATEOBJECT("myGpImage") loImage.CreateFromFile(lcSource) lnDimensions = loImage.GetFrameDimensionsCount() lcDimensions = "" DIMENSION raDimensionsIDList(1) nCount = loImage.GetFrameDimensionsList(@raDimensionsIDList) FOR n = 1 TO nCount lnProp = raDimensionsIDList(n) lcDimensions = lcDimensions + TRANSFORM(lnProp) + CHR(13) ENDFOR MESSAGEBOX("Image dimensions : " + TRANSFORM(lnDimensions) + CHR(13) + ; CHR(13) + "The dimensions ID are : " + CHR(13) + lcDimensions ; ,64,"Dimensions") RETURN
Conclusion
As I have showed here, that’s not difficult to deal with multiple-framed images. Just follow the correct steps and you will get the desired results. In the future, I hope the new versions of gdiplus.dll will bring new features, including a full support to creating animated GIF’s. In the attached file, there are all codes from this article, including the subclass _mygdiplus.prg, that contains all methods discussed in this article, and also the new methods that deal with PropertyTags from my last article. There are still many functions that were not added to the original _gdiplus.vcx. In the future, I hope to be adding as many as possible methods to make it more complete.
Some related links
MSDN - EncoderValue Enumerated Types
MSDN - Copying Individual Frames from a Multiple-Frame Image
MSDN - Creating and Saving a Multiple-Frame Image
MSDN – FrameDimensions
Free GIF images: http://www.gifs.net
Bob Powell’ls GDI+ FAQs: http://www.bobpowell.net/faqmain.htm
Source code