If you're into image processing using ASP.NET you're probably well aware of what GDI+ is: in case you're not, here it is in few words: a class-based API to handle graphics and formatted text, interacting with device drivers on behalf of applications. The good thing about GDI+ is... well, not much. As a matter of fact, it kinda sucks in a number of ways: poor performances, bad documentation, lots of memory/compatibility issues and so on.
However, if you had the misfortune of working with it at least once, you probably know that the worst thing about GDI+ is the nasty and opaque error messages it often gives when things go wrong.
I'm talking about this:
A generic error occurred in GDI+.
which basically means: we were too lazy to write some better return code, so just live with that and do your best to figure it out.
When working with GDI+, you will have the pleasure to be struck by this dreadful error in a number of different ways: if you're a seasoned developer, you know what it feels like; if you happen to be a newcomer, you will learn to hate it soon enough. It basically is the Rickroll of error messages, to say the least.
The Issue
However, if you've stumbled upon this post, it probably means that you had the same issue I experienced a couple weeks ago when trying to deal with multi-page TIFF files. I tried to open the file and cycling through the frames/pages using the SelectActiveFrame method, following the official MSDN guidelines in the following way:
1 2 3 4 5 6 7 |
var img = Bitmap.FromFile(filename); var pages = img.GetFrameCount(FrameDimension.Page); for (int i = 0; i < pages; i++) { img.SelectActiveFrame(FrameDimension.Page, i); // do something with img } |
This is a pretty standard way to deal with multi-page (or multi-frame) image files: as we can see, it all depends on the GetFrameCount and SelectActiveFrame GDI+ methods: ok, we might say that the second is utterly horrible for the way it works (internal pointers = bad), but at least it does the job... Except it doesn't. Despite the web is full of examples identical to this, I couldn't get it working with a single multi-page TIFF file - and I had a whole lot of them. The issue was with the SelectActiveFrame method, which - after working properly the first frame - was costantly raising a Generic GDI+ Error when dealing with each subsequent frame/page... basically crashing the application on the first "page two".
I honestly still don't know the reason for that... not that I could understand anything from the infamous error message! Luckily enough, instead of getting mad for the rickroll I was receiving, I managed to find a great workaround.
The Fix
Since the problem was evidently in the SelectActiveFrame method, I looked around for a way to replace it with something better. I eventually managed to find out a great alternative hidden in the System.Windows.Media.Imaging namespace, which is included in the PresentationCore.dll library: in order to use that you'll have to add this library to your application References folder (Right-Click > Add Reference > Framework).
As soon as you do that, you can use the powerful TiffBitmapDecoder class, which can be used to split a multi-page TIFF file into a collection of frames:
1 2 |
TiffBitmapDecoder decoder = new TiffBitmapDecoder(msTemp, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); int totFrames = decoder.Frames.Count; |
These frames are of BitmapSource type, which can be converted into Bitmap in the following way:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static Bitmap BitmapFromSource(BitmapSource bitmapsource) { Bitmap bitmap; using (var outStream = new MemoryStream()) { BitmapEncoder enc = new BmpBitmapEncoder(); enc.Frames.Add(BitmapFrame.Create(bitmapsource)); enc.Save(outStream); bitmap = new Bitmap(outStream); } return bitmap; } |
And that's it! Here's the full code I used to put all the frames/pages of the multi-page TIFF file in a Bitmap list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
List<System.Drawing.Bitmap> bmpLst = new List<System.Drawing.Bitmap>(); using (var msTemp = new MemoryStream(data)) { TiffBitmapDecoder decoder = new TiffBitmapDecoder(msTemp, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); int totFrames = decoder.Frames.Count; for (int i = 0; i < totFrames; ++i) { // Create bitmap to hold the single frame System.Drawing.Bitmap bmpSingleFrame = BitmapFromSource(decoder.Frames[i]); // add the frame (as a bitmap) to the bitmap list bmpLst.Add(bmpSingleFrame); } } |
In my scenario I had the multi-page TIFF file in a byte array, which pointed me towards using a
MemoryStream: if you need to fetch it from the filesystem, you can use
System.IO.File.ReadAllBytes(filename)to convert it into a
byte[], a
FileStreaminstance or a number of alternative ways. It's worth noting that you will also need the BitmapFromSource helper method above.
That's it for now: happy coding!