Click here to Skip to main content
15,902,938 members
Articles / Multimedia / GDI+

Bitonal (TIFF) Image Converter for .NET

Rate me:
Please Sign up or sign in to vote.
4.82/5 (45 votes)
16 Feb 2010CPOL5 min read 439.1K   7.5K   116   92
How to add bitonal image editing support to your applications
Sample Image - BitonalImageConverter.gif

Introduction

The .NET framework provides rich support for generating and manipulating bitmap images, but it lacks one significant feature that is imperative for image processing -- the ability to modify and then save modified bitonal (i.e., black and white or one-bit per pixel) images. Bitonal images are commonly used in document management and document imaging applications for scanned documents. Bitonal images are most commonly stored in the TIFF (Tagged Image File Format) file format with a CCITT Group IV compression algorithm.

The Problem

The .NET Framework supports loading and displaying bitonal images, but that's where the support ends. All drawing in .NET requires a Graphics object, but a Graphics object cannot be created from a bitonal image. Go ahead and try it now if you don't believe me. I'll wait...

Here's some code that demonstrates the issue (assuming Bitonal-In.tif is a bitonal image):

C#
Bitmap originalBitmap = new Bitmap(@"Bitonal-In.tif");
Graphics g2 = Graphics.FromImage(originalBitmap);

This code will generate an "A Graphics object cannot be created from an image that has an indexed pixel format." exception, thereby thwarting our desire to modify our bitonal image. I know, I couldn't believe it either.

The Solution (Almost)

We can work around the previous exception by converting the bitonal image into an RGB (Red/Green/Blue) bitmap for modification. The Converter class included with this article contains a static method, ConvertToRGB, for doing so. The code for this method is as follows:

C#
public static Bitmap ConvertToRGB(Bitmap original)
{
    Bitmap newImage = new Bitmap(original.Width, original.Height, 
                                 PixelFormat.Format32bppArgb);
    newImage.SetResolution(original.HorizontalResolution, 
                           original.VerticalResolution);
    Graphics g = Graphics.FromImage(newImage);
    g.DrawImageUnscaled(original, 0, 0);
    g.Dispose();
    return newImage;
}

This gives us a bitmap we can use to create a Graphics object and modify the image, so all is well with the world once again. Well... almost, but not quite.

A New, But Different Problem

We can now happily modify and display our image all day long (albeit with the larger memory footprint of a 32 bit per pixel RGB image), but we are thwarted once again if we wish to save our modified image back to disk in a bitonal format. The following code will generate a "Parameter is not valid." exception when attempting to save our 32 bit RGB image back to a bitonal format.

C#
// Get an ImageCodecInfo object that represents the TIFF codec.
ImageCodecInfo imageCodecInfo = GetEncoderInfo("image/tiff");
System.Drawing.Imaging.Encoder encoder = 
       System.Drawing.Imaging.Encoder.Compression;
EncoderParameters encoderParameters = new EncoderParameters(1);

// Save the bitmap as a TIFF file with group IV compression.
EncoderParameter encoderParameter = new EncoderParameter(encoder, 
                                    (long)EncoderValue.CompressionCCITT4);
encoderParameters.Param[0] = encoderParameter;
bitonalBitmap.Save(@"Bitonal-Out.tif", imageCodecInfo, encoderParameters);

The problem arises as a result of the .NET framework's inability to encode an RGB image into a bitonal file format. It is the primary intent of this article to address this issue.

The Solution (I Really Mean It This Time)

While the .NET framework does indeed support saving bitonal images, it provides no means for converting an RGB image into a bitonal image, which is the crux of the problem. We can't use the same method used to go from bitonal to RGB because we can't create a new bitonal image and get a Graphics object to draw on it. We must resort to something completely different -- direct image byte manipulation (Aaahh!!! Did he just say that !??).

While it is beyond the scope of this article to dig into the memory structure of bitmaps, I will mention briefly the task at hand. 32-bit RGB bitmaps use four bytes of memory for each pixel (picture element) in the bitmap. One byte each is used for the Red-ness, Green-ness, and Blue-ness of the pixel, and one byte is used to represent the Alpha (or transparency) of the pixel. The RGB value of 255-255-255 represents white, and a value of 0-0-0 represents black. Bitonal images, on the other hand, use a single bit to represent each pixel in the image, and eight pixels are packed into each byte of memory used to represent the image.

The BitmapData class in .NET provides a LockBits method, which gives us direct access to the image bytes for a bitmap. We can use this method to retrieve the image bytes for an existing image into a byte[], modify the image bytes, and then write the image bytes back to the bitmap, thus modifying the bitmap. To convert an RGB bitmap into a bitonal bitmap, we proceed as follows:

  1. Copy the image bytes for the original RGB image into a byte array.
  2. Create a new, bitonal image with the same dimensions as the source image.
  3. Create a byte array of the necessary size to contain the bits for the bitonal image.
  4. Walk the pixels in the source data, and set the appropriate bit in the destination data if the sum of the red, green, and blue values exceeds a certain threshold.
  5. Copy the destination byte array back to the new bitonal bitmap.

Comments

While searching for a solution to this problem, I came across other snippets to do this type of conversion, but they all suffered from the same problem: They were S....L....O....W..... The method provided with this article performs the conversion of a typical 300 DPI (Dot Per Inch) image on my machine (a 3 gigahertz P4) in about 100 milliseconds. While a lot of C# imaging applications resort to pointer arithmetic and unsafe code blocks, the code in this article is hereby deemed Completely Safe and does not need to resort to such medieval methods.

Room For Improvement

The RGB to bitonal conversion method provided with this article performs a threshold type conversion of the image in which a destination pixel is either black or white depending on the brightness of the pixel in the source image. One beneficial improvement to the code would be the addition of half-toning or dithering algorithms to produce a higher quality output from color images. The method provided with this article was written for use in document imaging applications in which the source image is already a bitonal image, so this was not a necessity for my purposes, but I see how it could prove useful if this code was to be used to produce bitonal images from color images with a wider variety of source colors.

Sample Project

The sample project included with this project is a Windows Forms application that utilizes the Converter class to convert an existing bitonal image to RGB, draw some text on it, then save it back to disk in bitonal format. The project contains the minimal amount of code necessary to demonstrate the technique.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I have been a "true blue" Microsoft developer since the earliest DOS days. I have coded in Turbo-C for DOS, VB 2->6, VB.NET, and C#. I enjoy graphics coding when I have the time, but tend to earn my living doing data layer and middle tier stuff. I love it all and only regret there's not enough time in the day to know everything about everything.

I am currently working as a software architect in Atlanta, Georgia and when I'm not coding, I'm pondering or reading anything that helps increase my understanding of the meaning of life and the nature of the universe, or at least my understanding of the .NET framework class library.

Comments and Discussions

 
AnswerRe: Missing a Dispose? Pin
Michael A. McCloskey16-Apr-07 12:11
Michael A. McCloskey16-Apr-07 12:11 
GeneralRe: Missing a Dispose? Pin
michielschaeverbeke3-Jul-07 1:57
michielschaeverbeke3-Jul-07 1:57 
GeneralRe: Missing a Dispose? Pin
Michael A. McCloskey3-Jul-07 3:34
Michael A. McCloskey3-Jul-07 3:34 
GeneralEXCELLENT! Pin
SLIMDOG5-Jan-07 6:42
SLIMDOG5-Jan-07 6:42 
GeneralThanks, Man Pin
orro1019-Dec-06 5:01
orro1019-Dec-06 5:01 
GeneralThank You! This Code Saved My Tool Pin
erikfarley18-Dec-06 21:15
erikfarley18-Dec-06 21:15 
GeneralHelp for some question!Please Pin
anders ling23-Aug-06 15:52
anders ling23-Aug-06 15:52 
GeneralRe: Help for some question!Please Pin
anders ling30-Aug-06 14:40
anders ling30-Aug-06 14:40 
Thank u very much for your greate help!!!!



Yes,what u say that:. " The resolution can be changed prior to saving the bitmap to a TIFF, ..." it work!!!


After i read the RFC2301,RFC2306,TIFF6 Sepection,i use the "bmp.SetPropertyItem(PropertyItem pt);" to


set the TIF Tags after the Converter.ConvertToBitonal(bmp) method by u!

the tags i set are the required fileds in rfc2306,such as

TIFFSetField(image, TIFFTAG_IMAGEWIDTH, 1728);//256
TIFFSetField(image, TIFFTAG_IMAGELENGTH, 144);//257
TIFFSetField(image, TIFFTAG_BITSPERSAMPLE, 1);//258
TIFFSetField(image, TIFFTAG_SAMPLESPERPIXEL, 1);//277
TIFFSetField(image, TIFFTAG_ROWSPERSTRIP, 144);//278

TIFFSetField(image, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4);//259,4
TIFFSetField(image, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);//262,0
TIFFSetField(image, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);//266,1
TIFFSetField(image, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);//284,1

TIFFSetField(image, TIFFTAG_XRESOLUTION, 200.0);//282
TIFFSetField(image, TIFFTAG_YRESOLUTION, 196.0);//283
TIFFSetField(image, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);//296,2

and it somtings works with Fax service!!!!

we should DO REMEMBER THAT THE G4,G3 FAX used by Fax Service must have a 1728 pix width!!!!!!!


but still i have a problem,the NewSubFileType 0xEF or the SubFileType 0xFF Tag does not effect!!!


i read the tags of the fax received by Fax Servcie,and found that the 0xEF Filed is 3,but the Filed created by .NE FrameWork is always 0??????


it is unbelivebale!!!!!








----------------------------------------
> Date: Wed, 30 Aug 2006 13:02:56 +0000
> From: mmccloskey@speedfactory.net
> To: lingxiaowen@hotmail.com
> Subject: Bitonal Image Converter Reply
>
> Greetings,
> I tried many time to reply to your question on CodeProject.com, but
> their ASP page kept timing out when I tried to post my reply. I
> decided to try to email you directly. Here is my reply:
>
> The tags written into the destination TIFF in this sample are
> controlled by the TIFF encoder in the .NET framework and are not
> something the code has much control over. The Converter class sets the
> destination image size and resolution to that of the source image. The
> resolution can be changed prior to saving the bitmap to a TIFF, by
> using the SetResolution method of the bitmap object as follows:
>
> bitonalBitmap.SetResolution(300.0f, 300.0f);
>
> This will set the DPI of the destination image. If you need an image
> with a different image width or height, then one option would be to
> create a new bitmap of the size you need and then render the original
> image onto it using the drawing functions built into .NET, much as is
> done with the ConvertToBitonal method in the converter class.
>
> I don't know why the MS Fax server doesn't like the image. I don't
> presently have the service set up, so I can't check it out. If you
> have an image that you know does work with the fax service, then I
> would try reading it in and just writing it back out without doing any
> conversions to see if the group IV TIFF's produced by .NET are
> imcompatible with the Fax service.
>
> It could just be that the Fax service requires a standard fax
> resolution (200 dpi ?). If so, then setting the resolution using the
> SetResolution method might solve your problem.
>
> The test image I included with the project was a 96 DPI image. In
> retrospect, I probably should have used a 300 DPI image to better
> represent a typical resolution.
>
> I hope this helps.
>
> ----------------------------------------------------------------
> Speedfactory Web-Mail http://www.speedfactory.net/
>

GeneralRe: Help for some question!Please Pin
ErikEckhardt15-Jun-10 16:40
ErikEckhardt15-Jun-10 16:40 
Generalthere is a problem!!!!! Pin
anders ling23-Aug-06 15:50
anders ling23-Aug-06 15:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.