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

Using TIFFs with the new GDI+ classes
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...
Summary
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 using all compressions supported.
Description
As a continuation of my article from May 2006 published in UTMAG, entitled "Multiframe Images with GDI+" now I will show how to manipulate TIFF images using the new GDIPlus-X library from VFP-X project. If you are not familiar with TIFFs or multiframe images, I strongly recommend you to read that article first in order to completely understand the steps that I will take. As an addiction to what was presented before, in this article I will also show how to create TIFFs using all compressions supported.

IMPORTANT:

All samples shown in this article use the new GDIPlus-X library, that is still in ALPHA version, but is already stable and reliable to do the majority of GDI+ tasks. So, before you start, please download the latest stable release from Codeplex: http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX

Creating multiframe TIFFS

The steps are exactly the same that were presented in the previous article:

  1. Load the images to GDI+
  2. Save the first image to disk using the "Save" command, using the encoder "SaveFlag", with the parameter "MultiFrame".
  3. Save the next images using the "SaveAdd" method, again using the encoder "SaveFlag", but using the parameter "FrameDimensionPage"
  4. When finished adding the images to the file, call "SaveAdd" for one last time, sending the "SaveFlag" encoder with "Flush" to Specify that the encoder object is to be closed.
There is no need to reinvent the wheel, because MSDN provides many good examples. The code below was converted to GDIPlusX for VFP from .NET Help: http://msdn2.microsoft.com/en-us/library/system.drawing.imaging.encoder.saveflag.aspx
* Converted to GDIPlusX for VFP from .NET help:
* http://msdn2.microsoft.com/en-us/library/system.drawing.imaging.encoder.saveflag.aspx

* The code saves three images in a single, multiple-frame TIFF file.

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

WITH _SCREEN.System.Drawing

	LOCAL multif AS xfcBitmap
	LOCAL page2 AS xfcBitmap
	LOCAL page3 AS xfcBitmap
	LOCAL myImageCodecInfo AS xfcImageCodecInfo
	LOCAL myEncoder AS xfcEncoder
	LOCAL myEncoderParameter AS xfcEncoderParameter
	LOCAL myEncoderParameters AS xfcEncoderParameters

	&& Create three Bitmap objects.
	multif = .Bitmap.New(GETPICT())
	page2  = .Bitmap.New(GETPICT())
	page3  = .Bitmap.New(GETPICT())
	
	&& Get an ImageCodecInfo object that represents the TIFF codec.
	myImageCodecInfo = GetEncoderInfo("image/tiff")
	
	&& Create an Encoder object based on the GUID
	&& for the SaveFlag parameter category.

	myEncoder = .Imaging.Encoder.SaveFlag

	&& Create an EncoderParameters object.
	&& An EncoderParameters object has an array of EncoderParameter
	&& objects. In this case, there is only one
	&& EncoderParameter object in the array.
	myEncoderParameters = .Imaging.EncoderParameters.New(1);
	
	*!* Save the first page (frame).
	myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder,;
         .Imaging.EncoderValue.MultiFrame)

	myEncoderParameters.Param[1] = myEncoderParameter
	multif.Save("c:\Multiframe.tif", myImageCodecInfo, myEncoderParameters)

	*!* Save the second page (frame).
	myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder,;
         .Imaging.EncoderValue.FrameDimensionPage)
	myEncoderParameters.Param[1] = myEncoderParameter
	multif.SaveAdd(page2, myEncoderParameters)

	*!* Save the third page (frame).
	myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder,;
         .Imaging.EncoderValue.FrameDimensionPage)
	myEncoderParameters.Param[1] = myEncoderParameter
	multif.SaveAdd(page3, myEncoderParameters)

	*!* Close the multiple-frame file.
	myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder,;
         .Imaging.EncoderValue.Flush)
	myEncoderParameters.Param[1] = myEncoderParameter
	multif.SaveAdd(myEncoderParameters)
ENDWITH
RETURN

FUNCTION GetEncoderInfo(mimeType AS String) AS xfcImageCodecInfo 
	LOCAL j
	LOCAL encoders AS Collection
	encoders = _SCREEN.System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders()
	
	FOR j = 1 TO encoders.Count
		IF encoders[j].MimeType = mimeType
			RETURN encoders[j]
		ENDIF
	ENDFOR
ENDFUNC
You might have noticed that the code above used another procedure, "GetEncoderInfo" to obtain the information that represents the TIFF codec. That is just one of the possible ways to obtain the codec. I used that to preserve the original sample provided by MSDN.

We can obtain the same result, erasing the "GetEncoderInfo" procedure, and substituting

myImageCodecInfo = GetEncoderInfo("image/tiff")
for
myImageCodecInfo = .Imaging.ImageFormat.Tiff

Extracting frames from TIFFS

This is also very simple.

  1. Load the multiframe image to GDI+
  2. Retrieve the number of frames with "GetFrameCount()"
  3. Loop using "SelectActiveFrame" to select each of the frames
  4. Call the "Save" method to save each of he frames separately.
** HOWTO: Extract frames from a Multiframe TIFF

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

WITH _SCREEN.System.Drawing

* Load the multiframe TIFF to GDI+
LOCAL loMultif AS xfcBitmap
loMultif = .Bitmap.New(GETPICT("TIF"))
LOCAL lnFrames, n, lcFrameFileName
lnFrames = loMultif.GetFrameCount()
IF lnFrames > 1
   FOR n = 0 TO lnFrames - 1
      loMultif.SelectActiveFrame(.Imaging.FrameDimension.Page, n)       
      lcFrameFileName = "c:\Frame" + TRANSFORM(n + 1) + ".Tif"
      loMultif.Save(lcFrameFileName, .Imaging.ImageFormat.Bmp)
   ENDFOR
ELSE
   MESSAGEBOX("The selected file is not a Multiframe TIFF")
ENDIF 

ENDWITH 
RETURN
If you want to extract the images from an animated GIF, apply the same code, but pass ".Imaging.FrameDimensionTime" instead of "FrameDimensionPage" to "SelectActiveFrame"

Setting the TIFF compression

MSDN (http://msdn2.microsoft.com/en-us/library/system.drawing.imaging.encodervalue.aspx) describes the various compression types supported by GDI+:

Compression type Description
CompressionCCITT3 Specifies the CCITT3 compression scheme. Can be passed to the TIFF encoder as a parameter that belongs to the compression category.
CompressionCCITT4 Specifies the CCITT4 compression scheme. Can be passed to the TIFF encoder as a parameter that belongs to the compression category.
CompressionLZW Specifies the LZW compression scheme. Can be passed to the TIFF encoder as a parameter that belongs to the Compression category.
CompressionNone Specifies no compression. Can be passed to the TIFF encoder as a parameter that belongs to the compression category.
CompressionRle Specifies the RLE compression scheme. Can be passed to the TIFF encoder as a parameter that belongs to the compression category.

Convert to monochrome

CCITT3, CCITT4 and RLE compression types are normally used for faxing purposes. But this compression will only work for monochromatic (one bit-per-pixel) images. So, before attempting to apply this kind of conversion we need to successfully convert the original image to one bit per pixel.

Bob Powell shows in his "GDI+ Faqs" an interesting but slow routine to convert images to 1bpp: http://www.bobpowell.net/onebit.htm

But GDI (the good and old one) provides some functions (CopyImage and LoadImage) to do that in a flash. Many thanks to Mike Gagnon, who showed the right path to accomplish that. So, the developers team of GDIPlus-X agreed to add this important feature to the library, as a new Method added to the Image and Bitmap classes: GetMonochrome().

Steps to get the monochrome 1bpp version of any image using the GDI+X classes

  1. Load Image to Gdiplus
  2. Call the Image.GetMonochrome to obtain a new object that will contain the 1bpp image
  3. Save normally as Bmp
** HOWTO: Get a Monochromatic 1 bit per pixel image

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

WITH _SCREEN.System.Drawing

	* Create a Bitmap object based on a BMP file.
	LOCAL loOriginalBmp AS xfcBitmap
    loOriginalBmp = .Bitmap.New(GETPICT())
    
    LOCAL loMonoChrBmp as xfcBitmap
    loMonoChrBmp = loOriginalBmp.GetMonochrome()
    
	* Dispose the original Bitmap because we don't need it any more
	loOriginalBmp = NULL
	
	* Save the created Monochromatic Bmp
	loMonochrBmp.Save("c:\Monochromatic.bmp", .Imaging.ImageFormat.Bmp)

ENDWITH 

RETURN
Here’s the Level Extreme original logo converted to monochrome:

Now we can use the routine below to save any image in all kinds of compression. For compression types NONE and LZW the original the image is used. For RLE, CCITT3/4, a monochromatic copy of the selected image is compressed.

** HOWTO: Set Tiff Compression
** http://msdn2.microsoft.com/en-us/library/system.drawing.imaging.encoder.compression.aspx

** The following example creates a Bitmap object from a BMP file.
** The code saves the image as a TIFF file with LZW compression.

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

WITH _SCREEN.System.Drawing

	LOCAL loMyBitmap AS xfcBitmap
	LOCAL myEncoder AS xfcEncoder
	LOCAL myEncoderParameters AS xfcEncoderParameters

	* Create a Bitmap object based on a BMP file.
    loMyBitmap = .Bitmap.New(GETPICT())
        
	* Create an Encoder object based on the GUID
     * for the Compression parameter category.
    myEncoder = .Imaging.Encoder.Compression

	* Create an EncoderParameters object.
    * An EncoderParameters object has an array of EncoderParameter objects.
    * In this case, there is only one EncoderParameter object in the array.
	myEncoderParameters = .Imaging.EncoderParameters.New(1)
        
	* Save the bitmap as a TIFF file with LZW compression.
    myEncoderParameters.Param(1) = .Imaging.EncoderParameter.New(myEncoder,;
     .Imaging.EncoderValue.CompressionLZW)
    loMyBitmap.Save("c:\ImageLZW.tif", .Imaging.ImageFormat.Tiff, myEncoderParameters)

	* Save the bitmap as a TIFF file with NONE compression.
    myEncoderParameters.Param(1) = .Imaging.EncoderParameter.New(myEncoder,;
     .Imaging.EncoderValue.CompressionNone)
    loMyBitmap.Save("c:\ImageNONE.tif", .Imaging.ImageFormat.Tiff, myEncoderParameters)



	* Convert the original Bitmap to Monochrome
    LOCAL loMonoChrBmp as xfcBitmap
    loMonoChrBmp = loMyBitmap.GetMonochrome()
    
	* Dispose the original Bitmap because we don't need it any more
	loMyBitmap = NULL
	
	* Go on with the compressions using the monochromatic version of the selected image

	* Save the bitmap as a TIFF file with RLE compression.
    myEncoderParameters.Param(1) = .Imaging.EncoderParameter.New(myEncoder,;
     .Imaging.EncoderValue.CompressionRle)
    loMonoChrBmp.Save("c:\ImageRLE.tif", .Imaging.ImageFormat.Tiff, myEncoderParameters)

	* Save the bitmap as a TIFF file with CCITT3 compression.
    myEncoderParameters.Param(1) = .Imaging.EncoderParameter.New(myEncoder,;
     .Imaging.EncoderValue.CompressionCCITT3)
    loMonoChrBmp.Save("c:\ImageCCITT3.tif", .Imaging.ImageFormat.Tiff, myEncoderParameters)

	* Save the bitmap as a TIFF file with CCITT4 compression.
    myEncoderParameters.Param(1) = .Imaging.EncoderParameter.New(myEncoder,;
     .Imaging.EncoderValue.CompressionCCITT4)
    loMonoChrBmp.Save("c:\ImageCCITT4.tif", .Imaging.ImageFormat.Tiff, myEncoderParameters)

ENDWITH 

RETURN

Multiframe TIFFS with compression

GDI+ by default uses the LZW lossless compression whenever asked to save as TIFF. According to Wayne Fulton, from www.scantips.com, "Lossless means there is no quality loss due to compression. Lossless guarantees that you can always read back exactly what you thought you saved, bit-for-bit identical, without data corruption". So, unless we specify, GDI+ will always use LZW compression on TIFFS.

To create a multiframe TIFF with compression different from LZW, we need to send some additional encoder parameters to Gdi+, even if the original picture is already a TIFF with another compression. The methods "Save" and "SaveAdd" permit us to send more than one encoder parameter at a time. Then, all we need to do is to create another encoder object based on the GUID for the Compression parameter category, and set its EncoderValue to the desired compression, eg CompressionCCITT4.

The sample below will ask for any three images. To ensure that it will be able to save using compression CCITT4, it will first convert all the selected images to monochrome. Then it will create a multiframe TIFF image file containing all selected images in the compression CCITT4.

** The following example loads three Bitmap objects
** Converts all bitmaps to Monochrome to be compatible with RLE, CCITT3/4 compression
** The code saves all three images in a single, multiple-frame TIFF
** file, using compression format CCITT4 

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 

WITH _SCREEN.System.Drawing 

LOCAL loBmp AS xfcBitmap
LOCAL loMultif AS xfcBitmap
LOCAL loPage2 AS xfcBitmap
LOCAL loPage3 AS xfcBitmap
LOCAL myEncoder AS xfcEncoder
LOCAL myCompEncoder AS xfcEncoder
LOCAL myEncoderParameter AS xfcEncoderParameter
LOCAL myCompEncoderParameter AS xfcEncoderParameter 

LOCAL myEncoderParameters AS xfcEncoderParameters 

*!* Create three Bitmap objects.
*!* 1st we load the original Bitmap, 
*!* and then get its monochrome version
loBmp = .Bitmap.New(GETPICT())
loMultif = loBmp.GetMonochrome() 
loBmp = .Bitmap.New(GETPICT())
loPage2 = loBmp.GetMonochrome() 

loBmp = .Bitmap.New(GETPICT())
loPage3 = loBmp.GetMonochrome() 

*!* Release the original Bitmap because we'll not use it any more
loBmp = NULL 

&& Create Encoder object based on the GUID for the SaveFlag parameter category.
myEncoder = .Imaging.Encoder.SaveFlag 

&& Create Encoder object based on the GUID for the Compression parameter category.
myCompEncoder = .Imaging.Encoder.Compression 

&& Create an EncoderParameters object.
&& EncoderParameters object has an array of EncoderParameter objects
&& In this case, there'll be 2 EncoderParameter objects in the array.
myEncoderParameters = .Imaging.EncoderParameters.New(2)
*!* Save the first page (frame).
myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder, ;
   .Imaging.EncoderValue.MultiFrame)
myCompEncoderParameter =.Imaging.EncoderParameter.New(myCompEncoder, ;
   .Imaging.EncoderValue.CompressionCCITT4)

myEncoderParameters.Param[1] = myEncoderParameter
myEncoderParameters.Param[2] = myCompEncoderParameter 

loMultif.Save("c:\NewMultiframeCompress.tif", ;
   .Imaging.ImageFormat.Tiff, myEncoderParameters) 

*!* Save the second page (frame).
*!* This time we will only change the 1st parameter to "FrameDimensionPage"
*!* The 2nd parameter that sets the compression will remain the same 
myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder, ;
   .Imaging.EncoderValue.FrameDimensionPage)
myEncoderParameters.Param[1] = myEncoderParameter
loMultif.SaveAdd(loPage2, myEncoderParameters) 

*!* Save the third page (frame).
*!* This time we don't need to make any change to the encoder paramenters
*!* We'll keep using the same parameters used when we added the 2nd frame
loMultif.SaveAdd(loPage3, myEncoderParameters) 

*!* Close the multiple-frame file.
*!* This time we call the "Flush" parameter to close the file.
*!* We don't need the 2nd parameter any more, so change it to NULL
myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder, ;
   .Imaging.EncoderValue.Flush)
myEncoderParameters.Param[1] = myEncoderParameter
myEncoderParameters.Param[2] = NULL
loMultif.SaveAdd(myEncoderParameters) 

ENDWITH
RETURN 
The main part of the previous code is below:
&& Create Encoder object based on the GUID for the SaveFlag parameter category.
myEncoder = .Imaging.Encoder.SaveFlag 

&& Create Encoder object based on the GUID for the Compression parameter category.
myCompEncoder = .Imaging.Encoder.Compression 

&& Create an EncoderParameters object.
&& EncoderParameters object has an array of EncoderParameter objects
&& In this case, there'll be 2 EncoderParameter objects in the array.
myEncoderParameters = .Imaging.EncoderParameters.New(2)
*!* Save the first page (frame).
myEncoderParameter = .Imaging.EncoderParameter.New(myEncoder, ;
   .Imaging.EncoderValue.MultiFrame)
myCompEncoderParameter =.Imaging.EncoderParameter.New(myCompEncoder, ;
   .Imaging.EncoderValue.CompressionCCITT4)

myEncoderParameters.Param[1] = myEncoderParameter
myEncoderParameters.Param[2] = myCompEncoderParameter 
Note that the object MyEncoderParameters contains a property "Params" that is an array of parameters.

Conclusion

GdiPlus-X project is still in ALPHA version. If you are interested to help in the project, coding, testing making suggestions, in Codeplex you will find the Discussions and Issue Tracker sections. There's still a lot to be tested, as Bo said: "We welcome any feedback. Please let us know if you find any bugs in the classes that need to be addressed. If you are interested in helping with the development of this library, documentation or being an official tester, please contact the project manager Bo Durban at gdiplusx@moxiedata.com."

The reference manual for these libraries is still under development. To help you get started, in the "readme.htm" file you'll find a trick using intellisense to display a list of classes and members with their descriptions while writing code. Bo also included a preliminary reference document that list the classes included with the library and their current status. It also includes a brief description of each class with links to the equivalent .NET class in the MSDN library.

If you find any difficulty to work with the classes, or want to see GDI+ doing some specific task, feel free to ask at the codeplex message boards.

Related links

GdiPlus-X at CodePlex
http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX

How to convert a bitmap file to monochrome format
http://www.news2news.com/vfp/?example=493&function=123

Bob Powell 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, 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.
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...