Raster Graphics
We define raster graphics as those functions that operate on an array of pixels. The simplest raster operation is to fill a rectangle with a single color. On a display screen, this is one of the most common operations. If you study any window on any display screen, you are likely to see that the background is a single color, often white or sometimes gray. You can use three methods in the Graphics class to fill a rectangular area:
-
Clear: fills a window with a specified color
-
FillRectangle: fills a specified rectangle using a brush
-
FillRegion: fills a specified region using a brush
The Clear method accepts a single parameter, a structure of type Color. [17] The other two methods accept a Brush [18] object as the parameter that identifies the color to use to fill the area. Before we can fill an area with any of these functions, then, we need to know how to define colors and how to create brushes.
Specifying Colors
The most basic type of drawing attribute is color, yet few drawing methods accept colors directly as parameters. Most drawing methods require other drawing attributes that have a built-in color. For example, the color used for filling areas is the color defined within a brush object. When drawing lines, the line color is the color that is part of a pen. Brushes are also used to specify text color. So even though color parameters are not directly used as parameters to methods, they are indirectly specified through a pen or brush.
There are three ways to specify a color in a .NET Compact Framework program:
-
With a system color
-
With a named color
-
With an RGB value
System colors are a set of colors used to draw the elements of the user interface. The use of system colors helps create consistency between different programs. For example, a given system might be set up with black text on a white background, or it could be set up with the opposite, white text on a black background. System colors are made available as properties of the SystemColors [19] class. Available system colors are listed in Table 15.7 in the System Colors subsection.
Table 15.7. System Colors in the .NET Compact Framework
Color |
Description |
---|---|
ActiveBorder |
Border color of a window when the window is active |
ActiveCaption |
Color of the background in the caption when a window is active |
ActiveCaptionText |
Color of the text in the caption when a window is active |
AppWorkspace |
Color of the unused area in an MDI [a] application |
Control |
Background color for a three-dimensional control |
ControlDark |
Color of the middle of the shadow for a three-dimensional control |
ControlDarkDark |
Color of the darkest shadow for a three-dimensional control |
ControlLight |
Color of the lightest element in a three-dimensional control |
ControlLightLight |
Color of the lightest edge for a three-dimensional control |
ControlText |
Color for drawing text in controls |
Desktop |
Color of the desktop background |
GrayText |
Color for drawing grayed text (e.g., for disabled controls) |
Highlight |
Background color of highlighted areas for menus, ListBox controls, and TextBox controls |
HighlightText |
Text color for highlighted text |
HotTrack |
Color of hot-tracked items |
InactiveBorder |
Border color of a top-level window when the window is inactive |
InactiveCaption |
Color of the background in the caption when a window is inactive |
InactiveCaptionText |
Color of the text in the caption when a window is inactive |
Info |
Background color of a tool tip |
InfoText |
Text color of a tool tip |
Menu |
Menu background color |
MenuText |
Menu text color |
ScrollBar |
Background color of a scroll bar |
Window |
Background color of a window |
WindowFrame |
Color of a window border |
WindowText |
Color of text in a window |
Named colors provide access to colors that use human-readable names like Red, Green, Blue, White, and Black. There are also a large number of colors with less common names like SeaShell and PeachPuff. Whether or not you like all the names of the colors you encounter, they provide a way to specify colors that is easy to remember. Color names are made available as Shared properties of the Color structure.
When you specify a color using an RGB value, you specify an amount of red, an amount of green, and an amount of blue. Each is defined with a byte, meaning that values can range from 0 to 255. It is sometimes helpful to remember that RGB is a video-oriented color scheme often used for display screens and televisions. When the energy for all three colors is 0, the color you see is black; when all the energy for all three colors is at 100% (255), the resulting color is white.
System Colors
System colors let you connect your program's graphical output to current system settings. This allows a program to blend in with the current system configuration. On some platforms, users can change system colors from the system control panel (such as on desktop version of Microsoft Windows). Other platforms, like the Pocket PC, do not provide the user with an easy way to modify system color settings. A custom embedded smart device could easily be created with a unique system color schemesay, to match corporate logo colors or to meet unique environmental requirements such as usage in low-light conditions or in sunlight. For all of these cases, the safest approach to selecting text colors involves using system colors.
System colors are available as read-only properties in the SystemColors class, the contents of which are summarized in Table 15.7. If you study this table, you may notice that several entries have the word Text in the namesuch as ControlText and WindowText. There are, after all, many uses of text in the user interface. When specifying the color for drawing text, these system colors provide your best choice.
In some cases, there is a pair of system color names: one with and one without the word Text in the name (e.g., Control and ControlText, Window and WindowText). One color in the pair defines a system color for text, and the other defines the color of the background. For example, when drawing in a form or dialog box, use the Window color for the background and WindowText for the text color. When you create a custom control, use the Control color for the control's background area and ControlText for the color of text drawn in a control. In the following code, the background is filled with the default window background color.
Private Sub FormMain_Paint( _ ByVal sender As Object, _ ByVal e As PaintEventArgs) Handles MyBase.Paint Dim g As Graphics = e.Graphics g.Clear(SystemColors.Window) End Sub
Named Colors
The System.Drawing.Color class defines 142 named colors as read-only properties. The names include old favorites like Red, Green, Blue, Cyan, Magenta, Yellow, Brown, and Black. It also includes some new colors like AliceBlue, AntiqueWhite, Aqua, and Aquamarine. With names like Chocolate, Honeydew, and PapayaWhip, you may get hungry just picking a color. The named colors appear in Table 15.8.
Table 15.8. Named Colors in the .NET Compact Framework
|
|
|
The following code draws in the background of a window with the color PapayaWhip.
Private Sub FormMain_Paint( _ ByVal sender As Object, _ ByVal e As PaintEventArgs) Handles MyBase.Paint Dim g As Graphics = e.Graphics g.Clear(Color.PapayaWhip) End Sub
Colors from RGB Values
The third approach that the .NET Compact Framework supports for specifying colors is to specify the three componentsred, green, and bluethat make up a color. These three components are packed together into a 32-bit integer with one byte for each. The range for each component is from 0 to 255 (FF in hexadecimal). Table 15.9 summarizes color triplet values for common colors.
Table 15.9. Color Triplets for Common Colors
Color Name |
RGB Triplet (Decimal) |
RGB Triplet (Hexadecimal) |
---|---|---|
Black |
(0, 0, 0) |
(0, 0, 0) |
White |
(255, 255, 255) |
(&HFF, &HFF, &HFF) |
Red |
(255, 0, 0) |
(&HFF, 0, 0) |
Green |
(0, 255, 0) |
(0, &HFF, 0) |
Blue |
(0, 0, 255) |
(0, 0, &HFF) |
Cyan |
(0, 255, 255) |
(0, &HFF, &HFF) |
Magenta |
(255, 0, 255) |
(&HFF, 0, &HFF) |
Yellow |
(255, 255, 0) |
(&HFF, &HFF, 0) |
Dark Gray |
(68, 68, 68) |
(&H44, &H44, &H44) |
Medium Gray |
(128, 128, 128) |
(&H80, &H80, &H80) |
Light Gray |
(204, 204, 204) |
(&HCC, &HCC, &HCC) |
To create a color from an RGB triplet, use the Color.FromArgb method. There are two overloaded versions for this Shared method. We find the following one easier to use.
ByVal red As Integer, _ ByVal green As Integer, _ ByVal blue As Integer) As Color
When you read the online documentation for this method, you see a reference to a fourth element in a color, the alpha value. The .NET Compact Framework does not support this, so you can safely ignore it. (In the desktop .NET Framework, the alpha value defines the transparency of a color, where a value of 0 is entirely transparent and 255 is entirely opaque. In a .NET Compact Framework program, all colors have an alpha value of 255, which means that all colors are 100% opaque.
Knowing Black from White
We give programmers in our training classes the following tip to help remember the correct RGB for black (0, 0, 0) and white (255, 255, 255). The RGB color encoding is a light-based scheme, which in a computer CRT is often used to correlate the power to apply to the electron guns in the monitor. Turn the power off, which causes the power to go to zero, and you see black. When the power to the red, green, and blue is turned up all the way, you get white.
In Table 15.9, notice the different shades of gray. By studying the color triplets, you can observe what makes the color gray: equal parts of red, green, and blue.
The following code draws the window background using a light gray color.
Private Sub FormMain_Paint( _ ByVal sender As Object, _ ByVal e As PaintEventArgs) Handles MyBase.Paint Dim g As Graphics = e.Graphics g.Clear(Color.FromArgb(204,204,204)) End Sub
Creating Brushes
A brush specifies the color and pattern to use for area-filling methods, such as FillRectangle. The .NET Compact Framework does not support patterns in brushes, however, so a brush just specifies the color when filling areas. Brushes also specify the color to use when drawing text. The second parameter to the DrawString method, for example, is a brush.
The desktop .NET Framework supports five different kinds of brushes, including solid brushes, bitmap brushes, and hatch brushes. Windows CE supports solid brushes and bitmap brushes but not hatch brushes. And in the .NET Compact Framework, things are even simpler: only solid brushes are supported, by the SolidBrush [20] class. This class has a single constructor, which takes a single parameterColor. The SolidBrush constructor is defined as follows.
Public Sub New( _ ByVal color As Color)
With one constructor, it is natural to assume that there is one way to create a solid brush. But because there are three ways to define a color, there are three ways to create a brush:
-
Using the system colors
-
Using a named color
-
Using an RGB value
The following subsections discuss each of these briefly.
Creating Brushes with System Colors
The following code creates a brush from a system color. This brush is suitable for drawing text within a program's main form or in a dialog box.
Dim brText As Brush = New SolidBrush(SystemColors.WindowText)
The resulting brush provides the same color used by the operating system to draw text. You are not required to select this color, but in doing so you help ensure that your application fits into the color scheme established by the user.
There might be reasons to design your own color scheme. For example, when dealing with financial figures you might display positive numbers in black and display negative numbers in red. Or perhaps when displaying certain types of documents you could highlight keywords in different colors, in the same way that Visual Studio .NET highlights language keywords in blue. To handle these situations, you need to specify the brush color with one of the two other color-defining schemes: using either named colors or RGB colors.
Creating Brushes with Named Colors
Here are examples of creating brushes using named colors.
Dim brRed As Brush Dim brPaleGreen As Brush Dim brLightBlue As Brush brRed = New SolidBrush(Color.Red) brPaleGreen = New SolidBrush(Color.PaleGreen) brLightBlue = New SolidBrush(Color.LightBlue)
You might wonder where these color names come from. Somelike Redare, of course, names for common colors. But when you read through the list of names, you see colors like AliceBlue, GhostWhite, and WhiteSmoke. The colors are sometimes called HTML Color Names because the more exotic names were first supported as color names in HTML by various browsers. Officially, however, HTML 4.0 includes only 16 color names, not the 140+ names defined in the Color structure.
Creating Brushes with RGB Values
To create a brush using an RGB value, call the FromArgb method in the Color class and pass the return value to the SolidBrush constructor. This method accepts three integer parameters, one each for red, green, and blue. Here is how to create three brushes from RGB triplets.
Dim brRed As Brush = New SolidBrush(Color.FromArgb(255, 0, 0)) Dim brGreen As Brush = New SolidBrush(Color.FromArgb(0, 255, 0)) Dim brBlue As Brush = New SolidBrush(Color.FromArgb(0, 0, 255))
Creating Bitmaps
A bitmap is a two-dimensional array of pixels with a fixed height and a fixed width. Bitmaps have many uses. One is to hold scanned images, such as a company logo. Photographs are stored as bitmaps, commonly in the highly compressed format of JPEG [21] files. Bitmaps can be used to create interesting effects on a display screen, such as smooth scrolling and seamless animation.
Bitmaps are often used to store complex images that a program can easily draw in one or more locations by making a single method call. As useful as this approach can be, it is important to always remember that bitmaps require a lot of roomboth in memory and in the file system. If you plan to include any bitmaps with your program, give some thought to the format of those bitmaps. We address this issue later in this chapter.
Bitmaps are sometimes referred to as off-screen-bitmaps because of the important role bitmaps have historically played in supporting display screen graphics. The Bitmaps on the Desktop sidebar discusses how bitmaps are used on desktop versions of Windows to support various user interface objects. That same support does not exist in Windows CE because of memory constraints. But bitmaps are still available to Windows CE programs for all of their other uses.
In our programming classes, we observe that programmers often get confused when first starting to work with bitmaps. The confusion seems to come from not grasping that bitmaps are inherently off-screen. Or it may arise from an understanding that display screens are supported by memory-mapped video devices and that the memory occupied by a bitmap must somehow be related to the memory used by the display adapter. After creating a bitmap and drawing into a bitmap, some programmers expect that bitmaps are going to appear somewhere on the screen. That does not happen, however, because bitmaps appear on a display screen only when your program explicitly causes them to appear.
Bitmaps on the Desktop
On desktop versions of Windows, bitmaps support the quick appearance and disappearance of menus, dialog boxes, and various other user interface elements. For example, before a menu appears on the screen, a snapshot is taken of the area to be covered by the menu. When the menu disappears, the bitmap is used to redraw the affected part of the screen. This technique helps make the elements of the user interface appear and disappear very quickly. This technique is not employed in Windows CE because of the tight memory restrictions of mobile and embedded systems. But your program could use bitmaps in other ways to support the display of your program's user interface.
Bitmaps: Drawing Surface or Drawing Object?
Bitmaps play two roles in every graphic library built for Microsoft Windows: (1) as drawing surfaces and (2) as drawing objects used to draw onto other surfaces. This is another reason why bitmaps can at first seem confusing for some programmers.
A bitmap is a drawing surface like other drawing surfaces. We say this because a program can obtain a Graphics object for a bitmap and then use the methods of that object to draw onto the surface of the bitmap. All of the drawing methods in the Graphics object are supported for bitmaps, including text, raster, and vector drawing methods.
The second role played by bitmaps is that of a drawing object. Like other drawing objects, such as pens, brushes, and fonts, a bitmap holds a pattern that can be applied to a drawing surface. Each drawing object has its particular uses, and each produces a different effect, as determined by the various drawing methods. The .NET Compact Framework supports four overloads for the bitmap drawing method, which is named DrawImage.
An example might clarify what we mean by each of these two roles. Using a Graphics object, a program can draw onto a bitmap by calling drawing methods. One such drawing method is DrawImage, which draws a bitmap onto a drawing surface. A program can call the DrawImage method to draw one bitmap (the drawing object) onto the surface of another bitmap (the drawing surface).
To push the example one step further, a bitmap can be both the drawing surface and also the drawing object. You could do this by using the DrawImage method to draw onto a bitmap while using the bitmap itself as the image source. This may sound like a snake eating its own tail, a seemingly impossible operation. It is possible, however, because it involves copying a rectangular array of pixels from one part of a bitmap to another part. The work required for this type of bitmap handling is well understood and has been part of Windows display drivers for more than a decade. The bitmap drawing code in Windows CE can easilyand correctlyhandle cases where, for example, source and destination rectangles overlap. This describes what happens, for example, when a user picks up and moves a window.
The Bitmap Class
The .NET Compact Framework supports in-memory bitmaps with the Bitmap [22] class. This class is derived from the Image class, which is a common base class for the Bitmap class and the Metafile class. As we mentioned earlier in this chapter, metafiles are not supported in the .NET Compact Framework. But because the .NET Compact Framework maintains consistency with the desktop .NET Framework, our bitmap drawing method is called DrawImage (instead of, for example, DrawBitmap). On the desktop, where metafiles are supported, the DrawImage method draws both bitmaps and metafiles.
The dimensions of a bitmap are available through two properties of the Bitmap object: Height and Width. The dimensions are also available through the Size property, which provides a convenient package for height and width. These are read-only properties because an image cannot change size once it has been created.
On the desktop, the Bitmap class supports 12 constructors, while in the .NET Compact Framework there are only 4 constructors. You can create a bitmap these ways:
-
By opening an image file
-
By reading an image from a stream
-
By starting from an existing bitmap
-
By specifying the width and height for an empty bitmap
Table 15.10 maps the constructors to six common sources you might use to create a bitmap.
Table 15.10. Sources for Bitmaps and Associated Bitmap Class Constructors
Source |
Constructor Parameters |
Comments |
---|---|---|
An external image file |
(String) |
Provides the path to the bitmap in the file system |
A portion of a file |
(Stream) |
Uses the FileStream class to open the file and move the seek position to the first byte of the bitmap |
Data in memory |
(Stream) |
Uses the MemoryStream class to assemble the bitmap bits as a byte array |
A resource |
(Stream) |
Reads bitmap data from a managed resource created as an untyped manifest resource |
An existing bitmap |
(Image) |
Copies an existing bitmap |
An empty bitmap |
(int,int) |
Specifies the width and height of the empty bitmap |
Creating an Empty Bitmap
One way to create a bitmap is to specify the desired dimensions of the bitmap to the following constructor in the Bitmap class.
Public Sub New( _ ByVal width As Integer, _ ByVal height As Integer)
This constructor creates a bitmap in program memory with the specified size. This is the quickest and easiest way to create a bitmap, but the empty (i.e., all-black) image means that you must draw into the bitmap before displaying its contents. You might call this a scratch space or double-buffer bitmap because it provides an off-screen drawing surface for doodling, just like scratch paper. The term double-buffer refers to a technique of creating smooth graphic effects by doing complex drawing off-screen and sending the resulting output to the display screen with a single, fast drawing operation. Let's use a bitmap created with this constructor.
After creating the bitmap itself, a program typically obtains a Graphics object for the bitmap. As we mentioned earlier in this chapter, we need a Graphics object for any type of drawing. We obtain a Graphics object for the bitmap by calling the FromImage method of the Bitmap class. Before drawing anything else in the bitmap, it makes sense to first erase the bitmap's background.
We need to think about cleanup. This is a subject that can often be ignored in managed code, but not when working with resource-intensive objects like bitmaps. So, when done working with a bitmap, your program must use the Dispose method to clean up two objects: the bitmap itself and the Graphics object. The code in Listing 15.1 shows the whole life cycle of our created bitmap: The code creates a bitmap, erases the bitmap's background, draws the bitmap to the display screen, and then cleans up the two objects that were created.
Example 15.1. Dynamic Bitmap Creation
Private Sub CreateAndDraw( _ ByVal x As Integer, ByVal y As Integer) ' Create a bitmap and a Graphics object for the bitmap. Dim bmpNew As Bitmap = New Bitmap(100,100) Dim gbmp As Graphics = Graphics.FromImage(bmpNew) ' Clear the bitmap background. gbmp.Clear(Color.LightGray) ' Get a Graphics object for the form. Dim g As Graphics = CreateGraphics() ' Copy the bitmap to the window at (x,y) location. g.DrawImage(bmpNew, x, y) ' Clean up when we are done. g.Dispose() gbmp.Dispose() bmpNew.Dispose() End Sub
Creating a Bitmap from an External File
Another way to create a bitmap is by specifying the path to an image file. This is accomplished with a constructor that accepts a single parameter, a string with the path to the candidate file. This second Bitmap class constructor is defined as follows.
Public Sub New( _ ByVal filename As String)
This method has two important requirements. One is that there must be enough memory to accommodate the bitmap. If there is not, the call fails. A second requirement is that the specified file must have an image in a format that the constructor understands. We have been able to create bitmaps from the following file types:
-
Bitmap files (.bmp) with 1, 4, 8, or 24 bits per pixel
-
JPEG (.jpg) files
-
GIF (.gif) files
-
PNG (.png) files
Among the unsupported graphic file formats are TIFF (.tif) files.
This constructor throws an exception if the file name provided is not a recognized format or if it encounters other problems when attempting to open the file or create the bitmap. For that reason, it makes sense to wrap this constructor in a try. . catch block. Listing 15.2 provides an example of calling this constructor, with a file name provided by the user in a File Open dialog box.
Example 15.2. Creating a Bitmap with a File Name
Try bmpNew = New Bitmap(strFileName) Catch MessageBox.Show("Cannot create bitmap from " + _ "File: " + strFileName) End Try
Creating a Bitmap from a Resource
When a program needs a bitmap for its normal operation, it makes sense to package the bitmap as a resource. Resources are read-only data objects that are bound into a program's executable file [23] at program build time. The benefit of binding a bitmap to an executable file is that it is always available and cannot be accidentally deleted by a user.
Resources have been a part of Windows programming from the very first version of Windows. In a native-mode program, resources are used for bitmaps and icons and also to hold the definitions of dialog boxes and menus. In managed-code programs, resources are still used for bitmaps and icons, although some program elementsincluding dialog boxes and menusare not defined in a resource but instead are defined using code in the InitializeComponent method by the Designer.
Where Do Resources Live?
Because memory is scarce on a Windows CEpowered device, it helps to know when and how memory gets used. When a resource gets added to a module, the resource occupies space in the module's file but uses no program memory until the resource is explicitly opened and used. This is true for both native resources and managed resources.
While resources are used in both native code and managed code, native resources can be used only from native mode code, and managed resources can be used only from managed code. The only exception is the program icon for a managed-code program, which is defined as a native icon. In managed code, there are two types of resources: typed resources and untyped resources.
Typed Resources
We like to use typed resources to hold literal strings, which aid in the localization of programs. To access typed resources, a program creates an instance of a ResourceManager [24] class and then makes calls to methods like GetObject and GetString. We provided an example of using typed resources for literal strings in Chapter 3, in the sample project named StringResources.
Typed resources are defined using XML in files that have an extension of .resx. In a typed resource, an XML attribute provides type information, as shown in this example.
<data name="dlgFileOpen.Location" type="System.Drawing.Point, System.CF.Drawing, Version=7.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <value>125, 17 </value> </data>
The Designer makes extensive use of typed resources. For each form created in the Designer, there is an associated file used to store a variety of details about the form. The Visual Studio .NET Solution Explorer does not normally display resource files, but you can make them appear by clicking the Show All Files icon.
For example, bitmaps in the image collection of an ImageList control on a form are stored as typed resources in the typed resource file of the form that contains the control. The bitmaps themselves are serialized into the XML and are not stored in their original, binary form. While a programmer could convert bitmap files into XML resources, we prefer to avoid this extra step and use untyped resources when we add bitmap resources to a project.
Untyped Resources
Untyped resources are also known as manifest resources because they are made available through an assembly's manifest (or table of contents). As the name implies, an untyped resource contains no type information and is made available as a raw stream of bytes. It does have a name, however, created by combining the default project namespace with the file name that contained the original resource data. You must know this name because you use the name to retrieve the resource. If you have trouble figuring out the resource name, the ildasm.exe utility can help. Open the program file and then click on the manifest. Listing 15.3 shows three bitmap resource names in a fragment from the manifest for the ShowBitmap sample program presented later in this chapter.
Example 15.3. Three Bitmap Resource Names from the ShowBitmap Manifest
.mresource public ShowBitmap.SPADE.BMP { } .mresource public ShowBitmap.CUP.BMP { } .mresource public ShowBitmap.HEART.BMP { }
Visual Studio .NET creates an untyped resource from an external file when you add the file to a project and assign a build action of Embedded Resource. The default build action for bitmap files, Content, allows the bitmap file to be downloaded with a program, but as a separate file and not as an embedded resource. Figure 15.2 shows the Visual Studio .NET settings to turn the file CUP.BMP into an embedded bitmap resource. The name of the resource is ShowBitmap.CUP.BMP, which we need to know to access the resource from our code.
Figure 15.2 The settings to turn CUP.BMP into an embedded bitmap resource
You can access an embedded resource by calling a method in the Assembly class named GetManifestResourceStream. [25] As suggested by the method name, the return value is a Stream object; more precisely, you are provided a MemoryStream [26] object. You can use all of the elements associated with a Stream-derived class (including the ability to query the resource length, which is the same as the resource input file) to seek a location in the stream and to read bytes (the CanSeek and CanRead properties are both set to True). In keeping with the read-only nature of Windows resources, you cannot write to a resource stream [27] (CanWrite returns False).
The code fragment in Listing 15.4 shows two methods from the ShowBitmap sample program. These methods are helper routines to handle the initialization and cleanup of resource-based bitmaps. The LoadBitmapResource method creates a bitmap from a resource; the DisposeBitmap method provides the cleanup.
What Can Go into an Untyped Resource?
This chapter provides an example of putting a bitmap into an untyped resource. But this is not the only type of resource you can create. You can put any custom data into untyped resources, which can then be used to access the data at runtime. When you request an untyped resource, you are provided with a Stream object that you can use as you wish. You might, for example, read a resource into an array of bytes and then parse those bytes in whatever way your application needs. Such resources can be any read-only data that your program needs: tax tables, sports scores, oras we show in the ShowBitmap sample programa set of bitmaps.
A benefit of using custom resources is that we have access to data we need at runtime. But when we are not using that data, it does not occupy scarce program memory. This makes custom resources a useful tool in our toolkit for building memory-wise programs.
Example 15.4. Creating Bitmaps from Untyped Manifest Resources
Private Function _ LoadBitmapResource(ByVal strName As String) As Bitmap Dim assembly As Assembly = Assembly.GetExecutingAssembly() Dim strRes As String = "ShowBitmap." + strName Dim stream As Stream = _ assembly.GetManifestResourceStream(strRes) Dim bmp As Bitmap = Nothing Try bmp = New Bitmap(stream) End Try stream.Close() Return bmp End Function Private Sub DisposeBitmap(ByRef bmp As Bitmap) If Not bmp Is Nothing Then bmp.Dispose() End If bmp = Nothing End Sub
The LoadBitmapResource method creates a bitmap by opening a resource stream and uses data read from that stream to create a bitmap. This method gets a reference to the program's assembly by calling a Shared method in the Assembly class named GetExecutingAssembly. After creating a bitmap, the stream can be closed. Once a bitmap has been created, it is self-contained and needs no external data. That is why we can close the stream once the Bitmap object has been created.
The DisposeBitmap method deletes the bitmap to free up its associated memory. It does this by calling the Dispose method for a Bitmap object. There are only a few situations in which it is mandatory to call the Dispose method. [28] Sometimes, however, it is still a good ideaeven if it is not, strictly speaking, required. Bitmaps can be large, so we suggest you consider explicitly deleting bitmaps, as we have done in our sample. Among the factors to consider are the size of your bitmaps and the number of bitmaps. We suggest that you explicitly delete bitmaps when you have either many small bitmaps or a few large bitmaps.
We call our two methods using code like the following.
Private Sub mitemResourceCup_Click( _ ByVal sender As Object, ByVal e As EventArgs) DisposeBitmap( bmpDraw) bmpDraw = LoadBitmapResource("CUP.BMP") Invalidate() End Sub
After cleaning up the old bitmap, we create a new bitmap and request a Paint event by calling the Invalidate method. Next, we discuss image file size, and how to save memory by changing the format you use for your images.
Image File Sizes
Bitmaps can occupy a lot of memory, which can create problems in a memory-scarce environment like Windows CE. When placing bitmaps in resources, we recommend that you test different formats and use the smallest one. To provide a starting point, we conducted some tests with three 100 × 100 pixel images stored in different formats. Table 15.11 summarizes our results, which provide the size in bytes for each image file.
Table 15.11. Size Comparison for Three 100 x 100 Images in Various Image File Formats
Format |
Bits per Pixel |
Size of Single-Color Image (Bytes) |
Size of Multicolor Image with Regular Data (Bytes) |
Size of Multicolor Image with Irregular Data (Bytes) |
---|---|---|---|---|
Monochrome DIB |
1 |
1,662 |
1,662 |
1,662 |
16-color DIB |
4 |
5,318 |
5,318 |
5,318 |
256-color DIB |
8 |
11,078 |
11,078 |
11,078 |
True-color DIB |
24 |
30,054 |
30,054 |
30,054 |
GIF |
8 |
964 |
3,102 |
7,493 |
PNG |
8 |
999 |
616 |
5,973 |
JPEG |
24 |
823 |
3,642 |
5,024 |
Four formats are uncompressed and three are compressed. The first four entries in the table are for DIB files. This well-known format is thoroughly documented in the MSDN Library and is the format that Visual Studio .NET provides for creating bitmap images. Notice that the size of these images is the same for a given number of bits per pixel. This reflects the fact that DIB files are uncompressed.
The last three formats are the compressed formats: GIF, PNG, and JPEG. To make sense of these formats, we must discuss the contents of the three images. The single-color image was a solid black rectangle. Each of the three compressed formats easily beat any of the uncompressed formats for the single-color image. The reason is that compressed formats look for a pattern and use that information to store details of the pattern. A single color is a pretty easy pattern to recognize and compress.
The second column, the multicolor image with regular data, shows the results for an image created with a solid background and vertical stripes. We used vertical stripes in an attempt to thwart the compression because run-length encoding of horizontal scan lines is an obvious type of compression. We were surprised (and pleased) to find that PNG compression was able to see through the fog we so carefully createdit created the smallest image in the table.
The third column, the multicolor image with irregular data, shows the sizes for images created with very random data. For this test, we copied text (.NET Compact Framework source code) into an image file. (We never want our work to be called "random," but we wanted an irregular image to push the envelope for the three compression formats.) The result was more like a photograph than any of the other images, which is why JPEGthe compression scheme created for photographswas able to provide the best compression. It provided the smallest file size with the least loss of information (the monochrome image was smaller, but the image was lost).
To summarize, the two compression schemes that created the smallest image files were PNG (for regular data) and JPEG (for irregular data). One problem is that Visual Studio .NET does not support either of these formats. But Microsoft Paint (mspaint.exe) supports both, so we recommend that you make sure your images have been compressed as much as possible prior to embedding your images as resources.
Drawing Bitmaps
The Graphics class supports four overloaded versions of the bitmap drawing method, DrawImage. These alternatives support the following types of bitmap drawing:
-
Drawing the entire bitmap at the original image size
-
Drawing part of a bitmap at the original image size
-
Drawing part of a bitmap with a change to the image size
-
Drawing part of a bitmap with a change to the image size and with transparency
We discuss these four methods in the sections that follow.
Drawing the Entire Bitmap at the Original Image Size
The simplest version of the DrawImage method copies an entire bitmap onto a device surface with no change in the image size, as shown here.
Overloads Public Sub DrawImage( _ ByVal image As Image, _ ByVal x As Integer, _ ByVal y As Integer)
Listing 15.5 shows an example of calling this method in a Paint event handler.
Example 15.5. Drawing an Entire Bitmap at the Original Size
Private Sub FormMain_Paint( _ ByVal sender As Object, _ ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics Dim x As Integer = 10 Dim y As Integer = 10 g.DrawImage(bmpDraw, x, y) End Sub
Drawing Part of a Bitmap at the Original Image Size
While we sometimes want to draw an entire bitmap, there are also times when we only want to see a portion of a bitmap. The second version of the DrawImage method provides the support we need to do just that, as shown on the next page.
Overloads Public Sub DrawImage( _ ByVal image As Image, _ ByVal x As Integer, _ ByVal y As Integer, _ ByVal srcRect As Rectangle, _ ByVal srcUnit As GraphicsUnit)
This version of the DrawImage method has five parameters, while the earlier one has only three. One of the extra parameters is useful, and the second is not so useful. The fourth parameter, srcRect, is the useful one, which identifies the rectangular area in the source bitmap that we wish to copy to the destination surface.
The fifth parameter, srcUnit, can be set to only one valid value in the .NET Compact Framework: GraphicsUnit.Pixel. On the desktop, the presence of this parameter gives the caller the freedom to select a convenient unit of measure for the source rectangle (e.g., inches or millimeters). But the .NET Compact Framework supports only pixel drawing units, which is why this parameter is not so useful in the context of a .NET Compact Framework program. The srcUnit parameter is present because of the high level of compatibility between the desktop .NET Framework and the .NET Compact Framework. As such, it represents a small price to pay for the convenience of allowing smart-device code to have binary compatibility with the desktop runtime.
Drawing Part of a Bitmap with a Change to the Image Size
The third overloaded version of the DrawImage method allows a portion of a bitmap to be selected for drawing, and that portion can be stretched (or shrunk) to match a specified size on the destination surface. Of course, nothing requires the image to change size: If the width and height of the destination rectangle is the same as the width and height of the source rectangle, no size change occurs. This version of the DrawImage method is defined as shown here.
Overloads Public Sub DrawImage( _ ByVal image As Image, _ ByVal destRect As Rectangle, _ ByVal srcRect As Rectangle, _ ByVal srcUnit As GraphicsUnit)
Drawing Part of a Bitmap with a Change to the Image Size and with Transparency
The final version of the DrawImage method adds a new feature to the drawing of bitmaps. It enables transparency while drawing a bitmap. In some ways, this feature breaks our definition of raster graphics. You might recall that we refer to raster graphics as those operations that operate on arrays of pixels. Implicit in this definition is that all operations are rectangular.
The ability to draw a raster operation and touch only a nonrectangular set of pixels on a drawing surface is, therefore, something of a heresy (like having nonrectangular windows on a display screen or a late-night coding session without ordering large quantities of unhealthy food). We hope that readers can accept this change with little loss of sleep. We certainly are happy to break the shackles that have previously limited almost all raster graphics to the boring world of rectangular arrays of pixels. This amazing new feature is available through the following version of the DrawImage method.
Overloads Public Sub DrawImage( _ ByVal image As Image, _ ByVal destRect As Rectangle, _ ByVal srcX As Integer, _ ByVal srcY As Integer, _ ByVal srcWidth As Integer, _ ByVal srcHeight As Integer, _ ByVal srcUnit As GraphicsUnit, _ ByVal imageAttr As ImageAttributes)
With its eight parameters, this version of the DrawImage method is the most complicated one that the .NET Compact Framework supports. Perhaps it is appropriate that this version matches the other versions in capabilities: It can draw an entire bitmap at its original size, draw a portion of a bitmap at its original size, and draw a portion of a bitmap at a different size.
What makes this version different is the final parameter, a reference to an ImageAttributes object. On the desktop, this class supports a variety of color adjustments that can be applied when drawing a bitmap onto a surface. The .NET Compact Framework version is much simpler, with what amounts to a single property: a color key. The color key defines the range of colors that represent transparent portions of an image. In other words, any color that matches the color key is a color that is not copied by the call to the DrawImage method. The color key settings are controlled through two methods: SetColorKey defines the transparency range, and ClearColorKey disables the transparency range.
Figure 15.3 shows an example of transparency at work. A 100 × 100 bitmap is first drawn without transparency at the window origin. That same bitmap is then drawn three times, using the version of the DrawImage method that supports transparency. The color key is set to light gray, which corresponds to the color outside the ellipse (the interior of the ellipse is set to yellow). Listing 15.6 shows the code, a handler for a MouseDown event, which we used to create the example.
Example 15.6. Event Handler That Draws a Bitmap with Transparency
Dim bFirstTime As Boolean = True Private Sub FormMain_MouseDown( _ ByVal sender As Object, _ ByVal e As MouseEventArgs) Handles MyBase.MouseDown #If False Then CreateAndDraw(e.X, e.Y) #End If ' Get a Graphics object for the form. Dim g As Graphics = CreateGraphics() ' Create a bitmap and a Graphics object for the bitmap. Dim bmpNew As Bitmap = New Bitmap(100, 100) Dim gbmp As Graphics = Graphics.FromImage(bmpNew) ' Clear the bitmap background. gbmp.Clear(Color.LightGray) ' Create some drawing objects. Dim penBlack As Pen = New Pen(Color.Black) Dim brBlack As Brush = New SolidBrush(Color.Black) Dim brYellow As Brush = New SolidBrush(Color.Yellow) ' Draw onto the bitmap. gbmp.FillEllipse(brYellow, 0, 0, 98, 98) gbmp.DrawEllipse(penBlack, 0, 0, 98, 98) gbmp.DrawString("At " + e.X.ToString() + "," + _ e.Y.ToString(), Font, brBlack, 40, 40) ' Copy the bitmap to the window at the MouseDown location. If (bFirstTime) Then ' Copy without transparency. g.DrawImage(bmpNew, e.X, e.Y) bFirstTime = False Else ' Copy the bitmap using transparency. Dim rectDest As Rectangle = New Rectangle(e.X, e.Y, _ 100, 100) Dim imgatt As ImageAttributes = New ImageAttributes imgatt.SetColorKey(Color.LightGray, Color.LightGray) g.DrawImage(bmpNew, rectDest, 0, 0, 99, 99, _ GraphicsUnit.Pixel, imgatt) End If ' Clean up when we are done. g.Dispose() gbmp.Dispose() bmpNew.Dispose() End Sub
Figure 15.3 Four calls to the DrawImage method, three with transparency enabled
A Sample Program: ShowBitmap
Our bitmap drawing sample program shows several features of bitmaps that we have been discussing. This program can open files and create a bitmap. Several formats are supported, including the standard Windows DIB (.bmp) files and also a few compressed image file formats such as GIF (.gif) files, JPEG (.jpg) files, and PNG (.png) files. Figure 15.4 shows the ShowBitmap program with a JPEG image of Chandler (the office beagle at The Paul Yao Company). This image is drawn scaled to 50%, an effect made possible by selecting the appropriate version of the DrawImage method.
Figure 15.4 ShowBitmap displaying a JPEG file scaled to 50%
Our sample program contains a set of bitmap files that are bound to the program files as embedded resources (see Listing 15.7). As with all types of resources, the resource data does not get loaded into memory until we explicitly load the resource. In this program, we load the resource when the user selects an item on the program's resource menu. Figure 15.5 shows the bitmap resource that was read from a resource identified as ShowBitmap.CUP.BMP, drawn at 400% of its original size.
Example 15.7. Source Code for ShowBitmap.vb
Imports System.Reflection ' Needed for Assembly Imports System.IO ' Needed for Stream Imports System.Drawing.Imaging ' Needed for ImageAttributes ' ... Private bmpDraw As Bitmap Dim bFirstTime As Boolean = True Dim bResource As Boolean = False Dim strResName As String Private Sub FormMain_MouseDown( _ ByVal sender As Object, _ ByVal e As MouseEventArgs) Handles MyBase.MouseDown #If False Then CreateAndDraw(e.X, e.Y) #End If ' Get a Graphics object for the form. Dim g As Graphics = CreateGraphics() ' Create a bitmap and a Graphics object for the bitmap. Dim bmpNew As Bitmap = New Bitmap(100, 100) Dim gbmp As Graphics = Graphics.FromImage(bmpNew) ' Clear the bitmap background. gbmp.Clear(Color.LightGray) ' Create some drawing objects. Dim penBlack As Pen = New Pen(Color.Black) Dim brBlack As Brush = New SolidBrush(Color.Black) Dim brYellow As Brush = New SolidBrush(Color.Yellow) ' Draw onto the bitmap. gbmp.FillEllipse(brYellow, 0, 0, 98, 98) gbmp.DrawEllipse(penBlack, 0, 0, 98, 98) gbmp.DrawString("At " + e.X.ToString() + "," + _ e.Y.ToString(), Font, brBlack, 40, 40) ' Copy the bitmap to the window at the MouseDown location. If (bFirstTime) Then ' Copy without transparency. g.DrawImage(bmpNew, e.X, e.Y) bFirstTime = False Else ' Copy the bitmap using transparency. Dim rectDest As Rectangle = New Rectangle(e.X, e.Y, _ 100, 100) Dim imgatt As ImageAttributes = New ImageAttributes imgatt.SetColorKey(Color.LightGray, Color.LightGray) g.DrawImage(bmpNew, rectDest, 0, 0, 99, 99, _ GraphicsUnit.Pixel, imgatt) End If ' Clean up when we are done. g.Dispose() gbmp.Dispose() bmpNew.Dispose() End Sub Private Sub FormMain_Paint( _ ByVal sender As Object, _ ByVal e As PaintEventArgs) Handles MyBase.Paint Dim g As Graphics = e.Graphics Dim sinX As Single = 10.0F Dim sinY As Single = 10.0F Dim szfText As SizeF = g.MeasureString("X", Font) Dim cyLine As Single = szfText.Height Dim brText As Brush = New SolidBrush(SystemColors.WindowText) If Not bmpDraw Is Nothing Then If (bResource) Then g.DrawString("Resource: " + strResName, _ Font, brText, sinX, sinY) Else g.DrawString("File: " + dlgFileOpen.FileName, _ Font, brText, sinX, sinY) End If sinY += cyLine g.DrawString("Bitmap Height = " + _ bmpDraw.Height.ToString(), _ Font, brText, sinX, sinY) sinY += cyLine g.DrawString("Bitmap Width = " + _ bmpDraw.Width.ToString(), _ Font, brText, sinX, sinY) sinY += cyLine sinY += cyLine If mitemScale100.Checked Then g.DrawImage(bmpDraw, CInt(sinX), CInt(sinY)) Else Dim rectSrc As Rectangle = New Rectangle(0, 0, _ bmpDraw.Width, bmpDraw.Height) Dim xScaled As Integer = 0 Dim yScaled As Integer = 0 If mitemScale50.Checked Then xScaled = bmpDraw.Width / 2 yScaled = bmpDraw.Height / 2 ElseIf mitemScale200.Checked Then xScaled = bmpDraw.Width * 2 yScaled = bmpDraw.Height * 2 ElseIf mitemScale400.Checked Then xScaled = bmpDraw.Width * 4 yScaled = bmpDraw.Height * 4 End If Dim rectDest As Rectangle = New Rectangle(CInt(sinX), _ CInt(sinY), xScaled, yScaled) g.DrawImage(bmpDraw, rectDest, rectSrc, _ GraphicsUnit.Pixel) End If Else g.DrawString("File: None", Font, brText, sinX, sinY) End If End Sub Private Sub mitemFileOpen_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles mitemFileOpen.Click dlgFileOpen.Filter = "Bitmap (*.bmp)|*.bmp|" + _ "Picture (*.jpg)|*.jpg|" + _ "PNG Files (*.png)|*.png|" + _ "TIF Files (*.tif)|*.tif|" + _ "GIF Files (*.gif)|*.gif |" + _ "All Files (*.*)|*.*" If dlgFileOpen.ShowDialog() = DialogResult.OK Then Dim bmpNew As Bitmap = Nothing Try bmpNew = New Bitmap(dlgFileOpen.FileName) bResource = False Catch MessageBox.Show("Cannot create bitmap from " + _ "File: " + dlgFileOpen.FileName) Return End Try DisposeBitmap(bmpDraw) bmpDraw = bmpNew Invalidate() End If End Sub Private Sub mitemScale_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mitemScale50.Click, mitemScale100.Click, _ mitemScale200.Click, mitemScale400.Click ' Clear the checkmark on related items. mitemScale50.Checked = False mitemScale100.Checked = False mitemScale200.Checked = False mitemScale400.Checked = False ' Set the checkmark on the selected menu item. CType(sender, MenuItem).Checked = True ' Request paint to redraw the bitmap. Invalidate() End Sub Private Sub mitemResourceCup_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mitemResourceCup.Click DisposeBitmap(bmpDraw) bmpDraw = LoadBitmapResource("CUP.BMP") Invalidate() End Sub Private Sub mitemResourceBell_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mitemResourceBell.Click DisposeBitmap(bmpDraw) bmpDraw = LoadBitmapResource("BELL.BMP") Invalidate() End Sub Private Sub mitemResourceSpade_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mitemResourceSpade.Click DisposeBitmap(bmpDraw) bmpDraw = LoadBitmapResource("SPADE.BMP") Invalidate() End Sub Private Sub mitemResourceHeart_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mitemResourceHeart.Click DisposeBitmap(bmpDraw) bmpDraw = LoadBitmapResource("HEART.BMP") Invalidate() End Sub Private Sub mitemResourceDiamond_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mitemResourceDiamond.Click DisposeBitmap(bmpDraw) bmpDraw = LoadBitmapResource("DIAMOND.BMP") Invalidate() End Sub Private Sub mitemResourceClub_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles mitemResourceClub.Click DisposeBitmap(bmpDraw) bmpDraw = LoadBitmapResource("CLUB.BMP") Invalidate() End Sub Private Function LoadBitmapResource( _ ByVal strName As String) As Bitmap Dim [assembly] As System.Reflection.Assembly = _ System.Reflection.Assembly.GetExecutingAssembly() Dim strRes As String = "ShowBitmap." + strName Dim [stream] As Stream = _ [assembly].GetManifestResourceStream(strRes) Dim bmp As Bitmap = Nothing Try bmp = New Bitmap([stream]) strResName = strRes bResource = True Catch End Try [stream].Close() Return bmp End Function Private Sub DisposeBitmap(ByRef bmp As Bitmap) If Not bmp Is Nothing Then bmp.Dispose() End If bmp = Nothing End Sub ' Simplest possible bitmap: create a bitmap, clear the ' bitmap background, and draw the bitmap to the display screen. Private Sub CreateAndDraw( _ ByVal x As Integer, ByVal y As Integer) ' Create a bitmap and a Graphics object for the bitmap. Dim bmpNew As Bitmap = New Bitmap(100, 100) Dim gbmp As Graphics = Graphics.FromImage(bmpNew) ' Clear the bitmap background. gbmp.Clear(Color.LightGray) ' Get a Graphics object for the form. Dim g As Graphics = CreateGraphics() ' Copy the bitmap to the window at (x,y) location. g.DrawImage(bmpNew, x, y) ' Clean up when we are done. g.Dispose() gbmp.Dispose() bmpNew.Dispose() End Sub
Figure 15.5 ShowBitmap displaying a bitmap from a resource