Click here to Skip to main content
15,887,351 members
Articles / Programming Languages / C#
Article

Fast Drawing of Non-32bpp Images with System.Drawing

Rate me:
Please Sign up or sign in to vote.
4.80/5 (21 votes)
5 Jun 20074 min read 89.4K   1.7K   52   20
Avoid the unnecessary pixel format conversion that normally happens when drawing a portion of an image to the screen with System.Drawing

Introduction

It is a well-known fact that when drawing images to the screen that are a different pixel format than the screen's pixel format, format conversion must be performed. GDI+ provides the CachedBitmap class to facilitate easy caching of a converted version of a bitmap. However, this functionality is not exposed in .NET, so normally bitmaps that do not match the screen format are converted on each drawing call.

The project

At first glance, it would seem that it shouldn't be too performance-degrading, even for large images, to just draw the area of the bitmap that is exposed or invalidated using Graphics.DrawImage(). However, a further performance examination reveals that the speed of Graphics.DrawImage() is always related to the size of the whole bitmap, not the size of the area being drawn. If you tell GDI+ to draw even a 1-pixel-by-1-pixel section of a bitmap that is not in the same pixel format as the screen, it will convert the entire bitmap to the screen pixel format before completing the drawing call!

This is clearly unacceptable, especially since digital camera images are typically 24bpp. So, I set out to write a routine that would create a new bitmap that is the size of the area that needs to be drawn. It would then copy the correct section of the source bitmap into that area and draw that to the screen. This would give much better performance.

However, I also wanted to see if I could make GDI+ convert only the relevant area of the bitmap directly, rather than having the overhead of the extra copying routine. I realized that with my <a href="/KB/graphics/pointerlessimageproc.asp">EditableBitmap</a> class, I could create an EditableBitmap as a view on another EditableBitmap's bits. Then I could just report to GDI+ that the "stride" is the size of the source bitmap's stride. So, there would be a distinct GDI+ bitmap information class that GDI+ could work with, but there would not be any more memory overhead related to the bits for an extra bitmap. There would also not be a performance reduction, however minor, associated with copying bits over to a copied bitmap section with each drawing call. I added a method called CreateView(), which took a rectangle that specified the bounds of the view:

C#
public EditableBitmap CreateView(Rectangle viewArea)
{
    if(disposed)
        throw new ObjectDisposedException("this");
    return new EditableBitmap(this, viewArea);
}

It delegates the "magic" to a protected constructor:

C#
protected EditableBitmap(EditableBitmap source, Rectangle viewArea)
{
    owner=source;
    pixelFormatSize=source.pixelFormatSize;
    byteArray=source.byteArray;
    byteArray.AddReference();
    stride = source.stride;
    
    try
    {
        startOffset=source.startOffset+(stride*viewArea.Y)+
            (viewArea.X*pixelFormatSize);
        bitmap = new Bitmap(viewArea.Width, viewArea.Height, 
            stride, source.Bitmap.PixelFormat, 
            (IntPtr)(((int)byteArray.bitPtr)+startOffset));
    }
    finally
    {   
        if(bitmap==null)
            byteArray.ReleaseReference();
    }
}

The constructor copies all of the properties from the source bitmap that will be the same. It stores a reference to the bitmap that it is a view on in the Owner property. It then calculates a byte offset from the start of the owner's byte array to the first pixel of the view bitmap. It then creates a GDI+ Bitmap object, passing in the offset byte pointer and the view's width and height. So far, so good! We have a bitmap object that tricks GDI+ into thinking that it is a standalone bitmap, when it really points to part of a larger bitmap.

However, we still have to worry about those pesky resource management issues. The EditableBitmap class depends on a pinned byte array to store the pixel data. With bitmap views, that byte array is shared between multiple bitmaps. We want the views to still be operational when the root bitmap is disposed and definitely vice versa, as well. So, we have to make it so that the bit array is only destroyed when there are no more EditableBitmap instances that use it. To do this, I added a new class, SharedPinnedByteArray, that manages the byte array and keeps a reference count. When the reference count reaches zero or it is finalized, it unpins the byte array.

The end result is a much faster rendering time for large, non-screen-format bitmaps. The demo included demonstrates the speed difference. When you run it, it will ask you to choose a bitmap using a file dialog. Choose a large bitmap. Try scrolling around, especially dragging the scrollbar thumbs. You will most likely see "tearing:" areas that paint slowly enough that you can easily see the missing area. Now click on the bitmap display area so that the title bar of the form says "New Method." Try scrolling again and it should be very smooth.

History

  • 5 June, 2007 -- Article edited and posted to the main CodeProject.com article base
  • 15 November, 2006 -- Original version posted

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
My main goal as a developer is to improve the way software is designed, and how it interacts with the user. I like designing software best, but I also like coding and documentation. I especially like to work with user interfaces and graphics.

I have extensive knowledge of the .NET Framework, and like to delve into its internals. I specialize in working with VG.net and MyXaml. I also like to work with ASP.NET, AJAX, and DHTML.

Comments and Discussions

 
PraiseMy vote of 5 Pin
EzequielKees5-Jul-17 1:00
EzequielKees5-Jul-17 1:00 
QuestionUse EditableBitmap in Visual Basic 2010 Pin
h. de boer16-Oct-11 9:50
h. de boer16-Oct-11 9:50 
GeneralRotation [modified] Pin
Scalee1-Feb-10 10:45
Scalee1-Feb-10 10:45 
GeneralRe: Rotation Pin
Scalee2-Feb-10 12:25
Scalee2-Feb-10 12:25 
GeneralDrawImageUnscaled(Bitmap, Point) Pin
Martijn van Dorp20-Jul-09 6:39
Martijn van Dorp20-Jul-09 6:39 
GeneralCloning Pin
swight9-Apr-09 15:20
swight9-Apr-09 15:20 
GeneralRe: Cloning Pin
J. Dunlap16-Apr-09 8:50
J. Dunlap16-Apr-09 8:50 
GeneralRe: Cloning Pin
swight19-Apr-09 14:45
swight19-Apr-09 14:45 
GeneralBig thanks! Pin
Froggr8-Apr-09 5:15
Froggr8-Apr-09 5:15 
Questionmain image and regions of interest Pin
alleyes26-Feb-09 2:32
professionalalleyes26-Feb-09 2:32 
AnswerRe: main image and regions of interest Pin
Froggr8-Apr-09 5:00
Froggr8-Apr-09 5:00 
GeneralRe: main image and regions of interest Pin
alleyes8-Apr-09 5:04
professionalalleyes8-Apr-09 5:04 
GeneralUsed your EditableBitmap class in my codeproject Pin
Berend Engelbrecht29-Jun-08 23:34
Berend Engelbrecht29-Jun-08 23:34 
QuestionRe: Used your EditableBitmap class in my codeproject Pin
alleyes26-Feb-09 3:01
professionalalleyes26-Feb-09 3:01 
AnswerRe: Used your EditableBitmap class in my codeproject Pin
Berend Engelbrecht26-Feb-09 6:59
Berend Engelbrecht26-Feb-09 6:59 
QuestionRe: Used your EditableBitmap class in my codeproject Pin
alleyes26-Feb-09 8:07
professionalalleyes26-Feb-09 8:07 
AnswerRe: Used your EditableBitmap class in my codeproject Pin
Berend Engelbrecht26-Feb-09 9:07
Berend Engelbrecht26-Feb-09 9:07 
Output resolution in viewport rectangle
Justin's code is fine for that. Output resolution is the same as input resolution, no details are lost. What he made is a "viewport" algorithm that works faster than the standard API in Windows, but otherwise has the same functionality.

Resolution and detail in DeepZoom
Silverlight Deepzoom is nice if your image is large and you want to be able to view an overview of the full image and also quickly zoom in to the maximum detail. You can zoom using the scroll wheel of the mouse - the algorithm for zooming and rendering the visible part of the image is similar to what is used in the satellite images of Google Maps.

However, DeepZoom has some disadvantages that makes it less suitable for your purpose:

1. Although pixel resolution is internally preserved, the DeepZoom viewer uses a similar rendering algorithm to the Windows "Picture and Fax viewer". The viewer introduces some blurring when rendering the image, even if the source image is sharp.

2. The tiles can only be saved as jpeg or png. I think neither of these formats is the best choice for 16 bpp grayscale images. Probably the best for you (depending on the subject of your images) would be jpeg with a very low compression ratio (i.e., close to 100% quality factor), if you would go that route.

Google code, a possible alternative?
If Microsoft Silverlight DeepZoom is not for you, perhaps you can look at what google code has to offer. I am not sure what is the release state of the API/code for this, but recently they published an experiment with high resolution images using the rendering technology of google maps:
Prado museum showcase in google maps
pradomuseum.googlecode.com

I have done some google maps coding and the viewer side of it is not difficult - you only need a few lines of javascript code to embed a google map in your own web page. You can get a free unlock key here to use the google maps API in your web page.

However, Google does not store just any image in Google maps format, the Prado stuff is just an experiment, not production code. I would imagine that it is not really possible to get Google to store your hires images in their datacenters. With Silverlight you do not have that problem, you can use your own webserver to store silverlight code and data without needing an external partner.

Even if the Prado showcase were production code, there could be copyright issues that would make it unwise for you to upload your hires images to Google's cloud. Google's usual disclaimer is not very respectful of other people's copyright ...
GeneralRe: Used your EditableBitmap class in my codeproject Pin
alleyes26-Feb-09 9:27
professionalalleyes26-Feb-09 9:27 
Generalexercises in c# Pin
Mr.PoorEnglish9-Jan-08 10:40
Mr.PoorEnglish9-Jan-08 10:40 
GeneralGood work, but... Pin
Adam Byrne16-Nov-06 5:36
Adam Byrne16-Nov-06 5:36 

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.