Graphics

In this section, we'll compare the GDI and GDI+ graphics APIs, and point out a few situations in which it's worthwhile dropping down to GDI for your graphics. In this section we're confining the discussion to static graphics - in other words, how to create a good still appearance for your form. If your application features animated graphics, then you'll most likely need to use DirectX - which I don't discuss in this book.

There's a common misconception around that GDI+ has got something to do with the .NET Framework. It's an understandable misconception because the two technologies of .NET and GDI+ did arrive at about the same time, and Microsoft's publicity for Windows Forms has made a big play of the fact that you can use GDI+ for drawing operations. In fact, however, GDI+ is a completely unmanaged API. It is not some kind of managed wrapper around GDI, but is rather a completely independent API, implemented in gdiplus.dll, and existing side-by-side with GDI as an alternative means of providing high-level access to the display facilities offered by your graphics card. It is quite possible to instantiate the GDI+ classes and invoke their methods from unmanaged C++ code. The .NET Framework classes in the System.Drawing namespace are simply managed wrappers around the equivalent classes in gdiplus.dll. And these managed wrappers are very thin - in most cases the .NET classes have the same names and expose exactly the same methods as the equivalent unmanaged classes. This has the rather nice incidental benefit that once you've learned how to use GDI+ in Windows Forms applications, you can directly transfer that knowledge into calling the native GDI+ classes from unmanaged C++, if you so wish.

GDI or GDI+?

One question that occasionally arises in Windows Forms applications is the question of whether you should ever use GDI for your drawing operations. In almost all cases, the answer is that you're better off with GDI+. There are, however, two cases in which you may wish to drop back to use native GDI.

  1. For functional reasons - to access features not available in GDI+. The GDI and the GDI+ libraries are not completely equivalent in functionality. There are some features that are supported in GDI+ but not in GDI (for example, gradient brushes). And there are other features that are supported in GDI but not in GDI+, for example Blitting from the screen into memory. If you want to do something like that, then you can't use GDI+. Period. Another feature which is strictly GDI-only is support for different raster operations when Blitting. By the way, if you're not familiar with the term, Blt (short for bit block transfer) is the process of copying a rectangular image from one location to another. It's basically the same as calling Graphics.DrawImage() in GDI+.

  2. For performance reasons. Now don't get me wrong here. I am not saying that GDI is faster than GDI+. Indeed, Microsoft actually claims the reverse: that GDI+ offers performance improvements relative to GDI. In practice, however, the situation is more complex. Which API gives the better performance will depend on what you are doing and what hardware you are using. In the vast majority of cases, there is little or no performance improvement to be gained from dropping to GDI. However, using GDI may be worth it if your application is to be run on older hardware. There are also a few cases in which you may be able to architect your drawing routines better, and with more efficient algorithms using GDI, because GDI gives you a finer degree of control over the graphics drawing objects.

Performance

GDI and GDI+ are internally implemented very differently. Microsoft hasn't given away much about their internal designs, but we can say that GDI+ is designed much more around today's graphics cards whereas GDI was designed around the graphics cards of yesterday. That means that GDI+ may give better performance on new hardware, with GDI giving better performance on old hardware.

In more detail, GDI+ tends to be based on the assumption that you have a reasonably new graphics card and that you have quite a bit of memory available. For example, GDI+ defaults to working with images that have 32-bit color depth, and will tend to internally process images in memory on this assumption, even if the results are sent to the graphics card and displayed using a lower color depth. Incidentally, this isn't just a GDI+ issue. When DirectX 8.0 was released in 2001, it came with redesigned interfaces and objects - the implementation of the new objects being based on the same 32-bit assumptions! That's fine with modern hardware: modern graphics cards are increasingly being built with the hardware intrinsically designed to favor 32-bit color. However, GDI makes no such assumptions, which is why you may find that if your application is intended to run on older computers with more restricted memory and older graphics cards, you may get better performance with GDI. It's very hard to lay down guidelines here, but my own experience suggests that as an extremely approximate rough rule of thumb, you might expect GDI+ not to give very good results on hardware built prior to about 1999-2000. I should stress, however, that because almost every computer system is different, this suggestion is very rough. In general, if you are concerned about performance, my advice would be to start using GDI+, because that is the quickest way to get your application up and running. If you do find, on testing your application on machines at the bottom range of specs typically used by your clients, that there are performance issues with the graphics, then try to identify the code responsible for the bottlenecks and consider swapping this code to GDI (or to DirectX).

One other point to bear in mind is that, although GDI+ assumes you have a powerful graphics card from the point of view of Blitting 32-bit pixel depth images, you don't need to worry about the graphics card having any advanced facilities beyond basic Blitting. Many of the new features of GDI+, such as the gradient brushes, are actually implemented by software, and so don't require on any intrinsic graphics card support. There's essentially no danger of GDI+ failing because of some feature not being available on a particular graphics card. It's only if you are using DirectX that you may need to start worrying about details of the video card.

Screenshot Example

The final example of this chapter, BltFromScreen, illustrates the use of GDI to achieve something that cannot be done using GDI+: Blitting from the screen. This example displays a form with a couple of controls on it, and has a menu option to take a screenshot of its own client area. If the user clicks on this menu option, the program saves the screenshot in a file called Screenshot.bmp.

The form when running looks like this:

click to expand

As you can see, it just has a couple of random controls on it - for our purposes it doesn't really matter what the controls are, since they are only there to provide something more interesting than a blank form for the screenshot. I've also added a Paint event handler to display the string Hello.

 private void Form1_Paint(object sender,                          System.Windows.Forms.PaintEventArgs e) {    e.Graphics.DrawString("Hello", new Font("Ariel", 12, FontStyle.Bold),                          Brushes.Indigo, new Point(10, 10)); } 

Now here's the event handler for the menu command:

 private void menuFileGetScreenshot_Click(object sender, System.EventArgs e) {    Refresh();    GrabScreenshot(); } 

When the user clicks on the File menu then selects Get Screenshot, we first refresh the form and then call a method called GrabScreenshot(), which will actually take the screenshot. Why do we refresh the form first? The menu items in the File menu will be obscuring part of the client area while the menu is up. They are actually removed from the screen before the code in the menu command handler is executed, but the WM_PAINT message isn't sent to the form to get the area where the menu was repainted until after the command handler is executed. So if we don't call Refresh() first to repaint the form, we'll end up with a screenshot that's blank in the area where the menu was.

The GrabScreenshot() method is where the interesting action happens. But before we can see the code, we need to define the imported unmanaged GDI functions we are going to be using:

 [DllImport("gdi32.dll")] static extern int BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth,                          int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc,                          int swRop); [DllImport("gdi32.dll")] static extern IntPtr CreateCompatibleDC(IntPtr hdc); [DllImport("gdi32.dll")] static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth,                                             int nHeight); [DllImport("gdi32.dll")] static extern IntPtr SelectObject (IntPtr hdc, IntPtr hgdiobj); [DllImport("gdi32.dll")] static extern int DeleteObject(IntPtr hgdiobj); const int SRCCOPY = 0xcc0020; 

I don't want to get sidetracked into explaining the details of GDI too much - that's not the purpose of this chapter. For the benefit of anyone who hasn't used GDI before, here's a quick indication of how these functions work.

First we need to understand that where GDI+ has objects like images and graphics objects, GDI uses handles. It works in the same way as the Windows handles that exist beneath managed forms and controls. So where GDI+ has a Graphics object that is used for drawing and for storing all the information about a drawing surface, GDI has a handle to a device context (or hdc for short). Where GDI+ has an Image object (from which the Bitmap class is derived), GDI has a handle to a bitmap (or hbitmap). Where GDI scores over GDI+ is that it allows you to create any of these objects independently of any other object - GDI+ has all sorts of restrictions in this regard.

In GDI, an image and a device context exist independently of each other, and you need to specifically select an image into a device context before you can perform BitBlt operations, since BitBlt works from one device context to another. Now with that background:

  • BitBlt() copies a bitmap between device contexts.

  • CreateCompatibleDC() creates a memory-based device context. This is something that has no direct equivalent in GDI+, and is the thing that gives GDI so much more flexibility: You can think of it as like a GDI+ Graphics object, but it's not connected to the screen or to any specific device. It exists only in the computer's memory, and allows you to do manipulations of images in memory.

  • CreateCompatibleBitmap() creates a bitmap - it's GDI's equivalent of new Bitmap().

  • SelectObject() selects a bitmap (or other graphics objects such as pens and brushes) into a device context so they can be used for drawing operations.

  • DeleteObject() cleans up the memory and resources associated with the handle it is passed. It's the approximate equivalent of the IDisposable.Dispose() method.

  • SRCCOPY is a constant that indicates how a bitmap should be copied when it is being Blitted here. In GDI+, when you call DrawImage(), all you can do is have a straight copy, though with the option of color-keying a transparent color. GDI is much more flexible here - it has a huge number of so-called raster operations, which determine how each pixel in the final image should be generated, so when you invoke BitBlt() you have to specify the algorithm - it's the last parameter to BitBlt().SRCCOPY indicates the simplest raster operation - each pixel is simply copied straight over, just as in Graphics.DrawImage(). Other possibilities include reversing the color, or performing bitwise operations such as And or Or between the source pixel and whatever the previous value of the pixel was in the destination device context - some of these can lead to quite intriguing visual effects.

Now for the code:

 private void GrabScreenshot() {    int width = this.ClientSize.Width;    int height = this.ClientSize.Height;    Graphics screen = this.CreateGraphics();    Intptr hdcScreen = screen.GetHdc();    IntPtr hdcMemory = CreateCompatibleDC(hdcScreen);    IntPtr hBitmap = CreateCompatibleBitmap(hdcScreen, width, height);    IntPtr hOldBitmap = SelectObject(hdcMemory, hBitmap);    int result = BitBlt(hdcMemory, 0, 0, width, height, hdcScreen, 0, 0,                        SRCCOPY);    Image screenShot = Image.FromHbitmap(hBitmap);    screenShot.Save("Screenshot.bmp", ImageFormat.Bmp);    SelectObject(hdcMemory, holdBitmap);    screen.ReleaseHdc(hdcScreen);    DeleteObject(hdcMemory);    DeleteObject(hBitmap);    MessageBox.Show("Screenshot saved in Screenshot.bmp"); } 

In this code we start off by caching the dimensions of the client area as we'll be using these values a fair bit. We then get a device context for the screen - the easiest way to do that is to stick with GDI+ and use the Graphics.GetHdc() method.

Getting a memory device context is not possible with GDI+, so we use the GDI CreateCompatibleDC() method. Passing in the screen device context as a parameter here ensures that the memory device context will be compatible with the screen when it comes to things like pixel color depth. We also need to create a blank bitmap that will be big enough to hold the screenshot and attach it to the memory DC - that's what the CreateCompatibleBitmap() and SelectObject() commands are about. Having done all that preparation we can copy the screen into bitmap associated with the in-memory device context using the BitBlt() function.

At this point, we now have a bitmap ready to save to a file. GDI+ offers far superior facilities to GDI when it comes to loading and saving images, so it'd be nice to go back to GDI+ here. The Image.FromHbitmap() method instantiates a GDI+ Image object that contains the image from the GDI bitmap - so we use this method then call Image.Save(). With that we are done - the remaining lines of code are there simply to clean up all the resources we've been using. If you download and run the example, bear in mind that the screenshot taken is only a screenshot of the form's client area - so the title bar and borders, etc. aren't included in the generated bitmap.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net