Home > Articles > Programming > Windows Programming

  • Print
  • + Share This
This chapter is from the book

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 scheme—say, 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 name—such 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


AliceBlue
AntiqueWhite
Aqua
Aquamarine
Azure
Beige
Bisque
Black
BlanchedAlmond
Blue
BlueViolet
Brown
BurlyWood
CadetBlue
Chartreuse
Chocolate
Coral
CornflowerBlue
Cornsilk
Crimson
Cyan
DarkBlue
DarkCyan
DarkGoldenrod
DarkGray
DarkGreen
DarkKhaki
DarkMagenta
DarkOliveGreen
DarkOrange
DarkOrchid
DarkRed
DarkSalmon
DarkSeaGreen
DarkSlateBlue
DarkSlateGray
DarkTurquoise
DarkViolet
DeepPink
DeepSkyBlue
DimGray
DodgerBlue
Firebrick
FloralWhite
ForestGreen
Fuchsia
Gainsboro
GhostWhite


Gold
Goldenrod
Gray
Green
GreenYellow
Honeydew
HotPink
IndianRed
Indigo
Ivory
Khaki
Lavender
LavenderBlush
LawnGreen
LemonChiffon
LightBlue
LightCoral
LightCyan
LightGoldenrodYellow
LightGray
LightGreen
LightPink
LightSalmon
LightSeaGreen
LightSkyBlue
LightSlateGray
LightSteelBlue
LightYellow
Lime
LimeGreen
Linen
Magenta
Maroon
MediumAquamarine
MediumBlue
MediumOrchid
MediumPurple
MediumSeaGreen
MediumSlateBlue
MediumSpringGreen
MediumTurquoise
MediumVioletRed
MidnightBlue
MintCream
MistyRose
Moccasin
NavajoWhite


Navy
OldLace
Olive
OliveDrab
Orange
OrangeRed
Orchid
PaleGoldenrod
PaleGreen
PaleTurquoise
PaleVioletRed
PapayaWhip
PeachPuff
Peru
Pink
Plum
PowderBlue
Purple
Red
RosyBrown
RoyalBlue
SaddleBrown
Salmon

SandyBrown
SeaGreen
SeaShell
Sienna
Silver
SkyBlue
SlateBlue
SlateGray
Snow
SpringGreen
SteelBlue
Tan
Teal
Thistle
Tomato
Transparent
Turquoise
Violet
Wheat
White
WhiteSmoke
Yellow
YellowGreen

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 components—red, green, and blue—that 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 parameter—Color. 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. Some—like Red—are, 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 room—both 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 easily—and correctly—handle 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 elements—including dialog boxes and menus—are 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 CE–powered 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.

15fig02.gifFigure 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, or—as we show in the ShowBitmap sample program—a 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 idea—even 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 created—it 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 JPEG—the compression scheme created for photographs—was 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

15fig03.gifFigure 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.

15fig04.jpgFigure 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

15fig05.gifFigure 15.5 ShowBitmap displaying a bitmap from a resource

  • + Share This
  • 🔖 Save To Your Account