|

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):
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:
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.
ImageCodecInfo imageCodecInfo = GetEncoderInfo("image/tiff");
System.Drawing.Imaging.Encoder encoder =
System.Drawing.Imaging.Encoder.Compression;
EncoderParameters encoderParameters = new EncoderParameters(1);
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:
- Copy the image bytes for the original RGB image into a byte array.
- Create a new, bitonal image with the same dimensions as the source image.
- Create a
byte array of the necessary size to contain the bits for the bitonal image.
- 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.
- 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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 33 (Total in Forum: 33) (Refresh) | FirstPrevNext |
|
 |
|
|
 |
|
|
1. Code below draws any bitmap correctly, including 1bpp bitmap:
Graphics g = pictBox.CreateGraphics(); Bitmap bitmap = new Bitmap("..."); e.Graphics.DrawImage(bitmap); 2. To edit 1bpp bitmap, create new 1bpp Bitmap with the same size, select black pixels by using BitmapData.Scan0 and pointers, and apply new bitmap by using AND operator to the first bitmap. Then, Bitmap.Save() works fine.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Dear sir, thanks for the great code! I have been merging multiple tiff images in to one with this code. But when I try to print the result document of say 5 mb, it takes 150mb in the spooler. what to do?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm glad you are finding the code useful. I'm afraid I can't be of much help with the printing. I suspect that when the image is sent to the print spooler, it is getting uncompressed to the full resolution of the printer, perhaps as an RGB image. A 600 dpi RGB image, at an 8.5" x 11" size will take up a lot of memory.
For example:
((8.5" x 600dpi) x (11" x 600dpi)) * 3 bytes per pixel = 100,980,000 bytes (or about 100 meg)
One possible solution that comes to mind is seeing if you can drop down the printer resolution to 300 dpi if you don't need to print at a high resolution.
Another solution might be to reduce the size of the bitmap itself if you don't need a high resolution.
I'm not sure which problem is affecting you, these are just my thoughts and hopefully useful advice.
Best Regards, Michael McCLoskey
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Dear Michael McCLoskey Sir, Thanks for the information. The origional files are with 200dpi and the new file comes with 96dpi, but the size is almost 12 times the original files total size. And still facing the same problem with printing. The print spool size goies to 152mb. Thanks,
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have placed simple a C# 2005 command-line project on my website. It's an easy way to fax dynamic reports.
Link to the blog page.
If you have FaxComEx.dll, you can simply provide the URL, FAX number and FAX Server name.
If you don't, you can provide a URL, and stdout will provide the generated TIFF name.
I do some basic random dithering that doesn't eat up too many cycles - good enough for my project!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Very cool stuff! I'm very happy to see my code put to good use. Thanks for the credit. I might have call to use that dithering code you added at some point. I hope you don't mind.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
When I use Bitonal code output image resolution is 120 X 120 dpi instead of 300 x 300 dpi and also size of image is 27.50" and 21.33" instead of 11.00" x 8.5". Please advice me what change I need in code to get mention result. Thanks for nice code.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The Bitmap class has a SetResolution method that can be used to configure the resolution. You could try calling that with (300,300) immediately before saving the image to set the correct resolution. I think the code tries to maintain the resolution of the source image. Are you sure the source image is a 300 dpi image? Either way, calling SetResolution prior to saving the image should do trick.
You're welcome for the code. I'm glad you can use it.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Thank you very very much for your help to correct size & resolution. I add one line before saving image like., bitonalBitmap.SetResolution(300, 300) and it works great ,it save new image with 8.5" x 11.0" & 300 x 300 dpi. Actually I don't know C# so I convert code from C# to VB2005. Thanks again.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This works great for me with one page documents, but it appears to cut off multiple page TIFF documents to just the first page. Am I doing something wrong or is this not supported?
Thanks
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
I didn't really consider multipage TIFF documents when writing this article, but I believe the technique should be adaptable to working with them. I'd recommend opening the source TIFF, iterating each page and making any necessary changes to each page, and then saving the results to a new destination TIFF.
To save a multipage TIFF, you have to do something like this for the first page:
Encoder encoder = Encoder.SaveFlag; EncoderParameters encoderParameters = new EncoderParameters(1); EncoderParameter encoderParameter = new EncoderParameter(encoder,(long)EncoderValue.MultiFrame); encoderParameters.Param[0] = encoderParameter; currentImage.Save(filename, imageCodecInfo, encoderParameters);
And then do a SaveAdd for subsequent pages, saving each additional bitmap to the file:
encoderParameter = new EncoderParameter(encoder,(long)EncoderValue.FrameDimensionPage); encoderParameters.Param[0] = encoderParameter; currentImage.SaveAdd(additionalImage, encoderParameters);
When you're all done writing pages, you close the multipage TIFF like this:
encoderParameter = new EncoderParameter(encoder, (long)EncoderValue.Flush); encoderParameters.Param[0] = encoderParameter; currentImage.SaveAdd(encoderParameters);
To determine the number of pages in the source TIFF you use GetFrameCount with a parameter of FrameDimension.Page:
pageCount = bitmap.GetFrameCount(FrameDimension.Page);
To set the active page in the source TIFF you use SelectActiveFrame thusly:
bitmap.SelectActiveFrame(FrameDimension.Page, pageIndex);
This guy has some good tips and code for working with multipage TIFF's
http://www.bobpowell.net/
I hope this helps you out in some way.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
the code works excellent and very fast on files up to a certain image size, after that memory/buffer problems start to show up, I tested on a 36x48" 200 dpi TIF, that is 9600x7200 pixels (I have to work with engineering drawings, and not letter size sheets) - that size just makes it into reasonable buffer size and processes fine in ~10 secs. at 400dpi - 36x48" (thats 14400x19200 pixels) the required buffer image size gets so large the byte array fails to initialize, also the graphics, draw image unscaled method fails with out of memory. The display of these files fails as well, which I dont really care much about, howevere it would be nice to optimize this code so at least the operation can be done in memory - no display required, here is my suggestions; I hope the author or someone else can optimize the code for this -check the image size and process the image in chunks, rather then all at once, ( or use multiple buffers if memory permits ) - use bitblt or iterate through the bitmap to draw the original bitmap onto the rgb converted version, because the drawunscaled method inside converttorgb fails with large images
any better tips, lets hear them ? thanks in advance
|
| Sign In·View Thread·PermaLink | 1.67/5 (3 votes) |
|
|
|
 |
|
|
I've been wracking my brain on ways to deal with the issue you mention and think it might be possible to come up with a solution, but it would not be trivial to implement. Assuming you can load the 14400 x 19200 image bitonal image successfully, it should be possible to operate on it in parts. The technique would require writing two additional methods. One method would create an editable, 32 bit RGB bitmap from a portion of the larger image and a separate method would replace that portion of the bitonal image with the modified 32 bit RGB bitmap. Assuming you were not modifying a large section of the image at one time, this should work. If you need to have an editable 32 bit version of the entire image at one time to modify, then I don't know of any alternatives to having enough RAM available to accommodate the huge bitmap (something like 1.1 Gig of RAM in this case).
Would this type of solution work for what you are trying to do?
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
thanks for the response, I have worked around my problem successfully, (using the techniques in your article) ultimately what I was after was reading 2 bitonal tif files into memory and creating a third file, this time in colour that has colour pixels set based on the identical or different pixels of the bitonal tif's - and do this all in a reasonable amount of time for a 14400x19200 sized file (if you attempt to use the .net getpixel/setpixel methods you are in for a long wait - it takes forever) - well the memory footprint of a 32bit RGB bitmap is brutal, I didnt need aRGB colour scheme anyways so instead of creating a 32bit GDI+ bitmap I have created an 8bit indexed TIF in a byte array and modified the pixels directly in the byte array (this gives a max of 256 possible colours - choosen from a palette of 24bit) - plenty for what I needed to do. The code after a bit of profiling allows me to compare 2 TIF files in under 10 secs and creates a colour output file at high quality. I still would like to find a way to buffer a 24bit or 32bit image in parts in memory and be able to work with it, just curiousity and for possible future projects - it certainly is a challenging task
|
| Sign In·View Thread·PermaLink | 3.00/5 (4 votes) |
|
|
|
 |
|
|
Dear Cinamon!
I'am sitting at home and trying to do some similar job. My TIFF is a 1pbb Tiff with 50400 x 63157 pixel size. The data i want to integrated into this file are represented within another tiff file, also 1pbb Tiff with 50400 x 63157 pixel size.
In fact i want to load both files and write the "black" pixels of both into a new file. It sounds that you did such a job succesfully - can you please provide me with some further details of your code pimps, so that i can go on with it.
thx a lot in advance Oliver
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Are you able to even load one of these big boys into a .NET bitmap object? If my calculations are correct, then each of these images will eat up about 380 meg of memory. To do a merge on them would require at least twice that amount of memory. If you can load one of them into memory and have a couple gigs of memory on the machine, then it might be feasible and I could probably offer up some code to make it happen. In this case though, you wouldn't want to convert them to any sort of color representation because that would definitely bring any computer to its knees. You'd want to load each source image into memory and create a method that locks blocks of the image data in each image, gets the data into byte arrays and OR's the data together, then writes the data back out into one of the images.
A different approach might be to try to manipulate the image bytes on disk. If you were able to save the images as uncompressed TIFF's, then the image bytes in the files should be laid out linearly and combining two images of the same size should just be a matter of walking the bytes and OR'ing the image data together. You'd have to be able determine the offset into the TIFF files where the actual image data resides so you don't overwrite the header or any non-image data in the files. This method might be tricky, but it would definitely be more memory friendly and might actually be faster than the first method.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I'm trying to load a tif image using Image.FromFile and I am getting an out of memory exception.
An unhandled exception of type 'System.OutOfMemoryException' occurred in system.drawing.dll
Additional information: Out of memory.
I tried loading it using a stream, and I am getting an Invalid parameter exception.
I need to somehow convert thi tif image to a valid jpeg or jpg format.
Has anyone faced this issue before? Can I get some code to solve this problem?
I will be unable to use any libraries that 3rd parties are providing and will have to write my own code.
Thanks in advance.
|
| Sign In·View Thread·PermaLink | 1.50/5 (2 votes) |
|
|
|
 |
|
|
There are limits on the size of a bitmap that may be loaded in memory. While some third party products support loading and operating on parts of an image, the .NET framework methods only support loading the entire image into memory. If you are working with large images, particularly color images, then you need to make sure your system can handle it. You can get a rough idea of the memory required to load an image by multiplying the width in pixels by the height in pixels and multiplying the result by one of the following factors to determine the bytes of memory required to load the image:
For 32 bit images -> Memory in bytes = width * height * 4 For 24 bit images -> Memory in bytes = width * height * 3 For 16 bit images -> Memory in bytes = width * height * 2 For 8 bit (256 color/grayscale images) -> Memory in bytes = width * height * 1 For 1 bit (Black and White) images -> Memory in bytes = width * height * 0.125
It seems like you are trying to load an image too large for the available memory in your system. Out of memory conditions can also occur if you are performing multiple operations with bitmaps and don't call Dispose on each bitmap object when you are done with it, so please be mindful of that too. I've had that cause me problems on multiple occasions.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
If I have the 'Out of memory' exception and I want to minimize the possibility of it to happen, what should I do:
a) Add more RAM? b) Adjust my virtual memory settings? c) Anything other than above?
Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Working with bitmaps is always memory intensive. When I've run into out of memory conditions, most of the time it's been because I processing a bunch of images in a loop of some sort and failed to dispose of each bitmap when I was done with it. Bitmap implement IDisposable, so you should call Dispose on the each bitmap when you are done with it to proactively release the memory it is using.
If you're working with single, extremely large images, then there's no substitute for having lots of RAM. The general formula for figuring out how much RAM you need for a single image is to multiply its width in pixels by its height and then multiply that by the number if bytes per pixel, which is as follows:
Bitonal - 1/8 (0.125) bytes per pixel 16 Color - 1/2 (0.5) bytes per pixel 256 Color - 1 byte per pixel 65k Color - (16 bits / 2 bytes per pixel) 16M Color - 24 bits / 3 bytes per pixel 16M Color with Alpha - 32 bits / 4 bytes per pixel
So, for instance, an 8.5" x 11", 300 DPI bitonal images, works out as follows:
2550 pixels wide * 3300 pixels high * 0.125 bytes per pixel = 1,051,875 bytes (about a meg)
The same image converted to 32 bit color would be
2550 * 3300 * 4 bytes per pixel = 33,660,000 bytes (32 meg!)
So, as you can see, when you start converting bitonal images to color for manipulation, they get much bigger in RAM, so it's very important to release that memory as soon as possible after using the bitmap.
I hope this helps.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The OutOfMemoryException on Image.FromFile is also thrown if the tiff-image has an invalid pixel-format. This behavior is quite strange, but we have to live with it.
|
| Sign In·View Thread·PermaLink | 4.00/5 (1 vote) |
|
|
|
 |
|
|
Just wondering if ConvertToBitonal should Dispose source when source != original?
i.e. when source is created on line 29:
source = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Yes, I believe you are correct. Since, in this case, source is created and utilized completely within the context of the procedure, it should be properly disposed of, which the procedure fails to do. Great catch! I will endeavor to update the sample code accordingly.
Michael A. McCloskey
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The example source code has been updated to include the missing Dispose. Thanks for the find and for taking the time to comment!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|