Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Multiframe Images with GDI+
Cesar Chalom, May 1, 2006
Certain Image types permit to save multiple images into a single file. It is possible to save several images to a single TIFF (Tagged Image File Format) file. GIF Image files also permit multi-frames, creating animated images, but is not totally supported by GDI+ version 1.
Summary
Certain Image types permit to save multiple images into a single file. It is possible to save several images to a single TIFF (Tagged Image File Format) file. GIF Image files also permit multi-frames, creating animated images, but is not totally supported by GDI+ version 1.
Description
Certain image types permit to save multiple images into a single file. It is possible to save several images to a single TIFF (Tagged Image File Format) file. GIF Image files also permit multi-frames, creating animated images, but is not totally supported by GDI+ version 1.

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

Method Description
SaveAdd() Adds the information in the specified Image to this Image. The specified EncoderParameters determine how the new information is incorporated into the existing image. This method can receive one or two parameters, depending on the needs.
SaveAdd(toImage,rvEncoderParams) Adds a frame to a file or stream specified in a previous call to the SaveToFile
toImage: Image object that holds the frame to be added.
rvEncoderParams: optional, parameters required by the image encoder used in the save-add operation. Either a comma-separated string in the form "param1=value1,param2=value2", or a two-column array (passed by reference) of encoder parameters and values. If not passed, default is SAVEFLAG=EncoderValueFrameDimensionPage
MSDN Help
SaveAdd(rvEncoderParams) Adds a frame to a file or stream specified in a previous call to the method.
Saves selected frames from a multiple-frame image to another multiple-frame image.
rvEncoderParams : optional, parameters required by the image encoder used in the save-add operation. Either a comma-separated string in the form "param1=value1,param2=value2", or a two-column array (passed by reference) of encoder parameters and values. If not passed, default is SAVEFLAG=EncoderValueFrameDimensionPage
MSDN Help
SelectActiveFrame(tcGUID,tnFrame) Selects the frame specified by the dimension and index.
tcGUID: Either a GUID that specifies the frame dimension or string containing the type of Image, like "TIFF" or "GIF"
tnFrame: Integer that specifies the index of the frame within the specified frame dimension.
MSDN Help
GetFrameCount(tcGuid) Selects the frame specified by the dimension and index.
Returns the number of frames of the specified dimension.
tcGUID: Either a GUID that specifies the frame dimension or string containing the type of Image, like "TIFF" or "GIF"
MSDN Help
GetFrameDimensionsCount(tcGuid) Returns the number of frame dimensions in this Image object.
Returns information about multiple-frame images, which come in two styles: multiple page and multiple resolution.
MSDN Help
GetFrameDimensionsList(raDimensionsIDList) Gets the identifiers for the frame dimensions of this Image object.
Each of those frame dimensions is identified by a GUID, and the call to GetFrameDimensionsList retrieves those GUIDs. The first GUID is at index 0 in the raDimensionsIDList array.
MSDN Help

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

ID Item Description
20736 FrameDelay Time delay between 2 frames in an animated GIF image
20737 LoopCount Number of times to display the animation

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}" 
Where FrameDimensionPage is used for TIFF’s and FrameDimensionTime is used for GIF’s. To make things easier, both GetFrameCount and SelectActiveFrame accept also “GIF” and “TIFF” as parameters, so you don’t have to worry defining the GUID constants.

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
Adding the piece of code below to the previous code will create one big bitmap that will hold all images one besides the other, like in this case:

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
The output generated will be :

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

Cesar Chalom
Cesar is an independent software developer. He is an active member of brazilian Visual FoxPro groups.
More articles from this author
Cesar Chalom, June 1, 2006
Almost any application makes use of charts to display information to their users. Graphs are used for many different reasons, and can be found all over. We see them in the newspapers, magazines, and on television because they help us to communicate information. One of the commonest types of charts i...
Cesar Chalom, April 1, 2006
Did you know that all the JPEGs from your digital camera contain a lot of extra information? We can easily retrieve some interesting information such as: Title, Equipment Make, Camera Model, Shutter Speed, Lens Aperture, Flash Mode, Date of Picture, and much more! These metadata "tags" are stored in...
Cesar Chalom, August 1, 2006
Following previous articles on the topic, this one describes how to create some cool effects on images with the help of color matrices. Sometimes we may need to make some adjustments in the colors of an image, like to change Saturation, Contrast, Brightness, Hue, convert to Grayscales, to increase o...
Cesar Chalom, October 1, 2006
In the first part of this article, Cesar showed how we can apply some interesting effects to images with the use of color matrices and the new GdiPlus-X library. In this article, Cesar continues by showing more options to make use of some interesting applications of color matrices. This articles in...
Cesar Chalom, September 1, 2006
As a continuation of an article published in the Universal Thread Magazine May 2006 issue, entitled "Multiframe Images with GDI+", Cesar Chalom describes in this article how to manipulate TIFF images using the new GDIPlus-X library from VFP-X project. This article also shows how to create TIFFs usin...