GDI+ Graphics Programming Guide
GDI+ Graphics Programming Guide
[ Team LiB ]
&"87%"
class="v1"
height="17">Table
of Contents
Graphics Programming with GDI+
By Mahesh Chand
"This is the most comprehensive book about graphics programming using GDI+ so far. This book will be a
very useful handbook for everyone who does graphics programming for Windows."
-Min Liu, Software Design Engineer of GDI+, Microsoft Corporation
Graphics Programming with GDI+ is the .NET developer's guide to writing graphics applications for
Windows and the Web. Through the use of detailed examples it provides experienced programmers with a
deep understanding of the entire GDI+ API defined in the .NET Framework class library.
The book begins with an introduction to GDI+ and the basics of graphics programming in Windows. The core
of the book is a hands-on guide to practical topics, including how to use Windows Forms and optimize GDI+
performance. Chapters demonstrate how to develop real-world tools such as GDI+Painter, GDI+Editor,
ImageViewer, and ImageAnimator. The author provides extensive reusable sample code in C"docText">Key
topics include:
Graphics Programming in GDI+ is the most in-depth treatment available on writing effective graphics
applications for the .NET Framework.
1
2
[ Team LiB ]
[ Team LiB ]
&"87%"
class="v1"
height="17">Table
of Contents
Graphics Programming with GDI+
By Mahesh Chand
Copyright
Praise for Graphics Programming with GDI+
Microsoft .NET Development Series
Figures
Tables
Acknowledgments
Introduction
Who Is This Book For?
Prerequisites
What's in This Book That I Won't See in Other Books?
Chapter Organization
Example Source Code
Exception and Error Handling in the Samples
SUMMARY
Chapter 1. GDI+: The Next-Generation Graphics Interface
Section 1.1. Understanding GDI+
Section 1.2. Exploring GDI+ Functionality
Section 1.3. GDI+ from a GDI Perspective
Section 1.4. GDI+ Namespaces and Classes in .NET
Summary
Chapter 2. Your First GDI+ Application
Section 2.1. Drawing Surfaces
Section 2.2. The Coordinate System
Section 2.3. Tutorial: Your First GDI+ Application
Section 2.4. Some Basic GDI+ Objects
SUMMARY
Chapter 3. The Graphics Class
2
3
3
4
4
5
Copyright
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and Addison-Wesley was aware of a trademark
claim, the designations have been printed with initial capital letters or in all capitals.
The .NET logo is either a registered trademark or trademark of Microsoft Corporation in the United States
and/or other countries and is used under license from Microsoft.
The author and publisher have taken care in the preparation of this book, but make no expressed or implied
warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for
incidental or consequential damages in connection with or arising out of the use of the information or
programs contained herein.
The publisher offers discounts on this book when ordered in quantity for bulk purchases and special sales. For
more information, please contact:
International Sales
(317) 581-3793
international@[Link]
[Link]
Chand, Mahesh
Graphics programming with GDI+ / Mahesh Chand.
p. cm.
ISBN 0-321-16077-0 (alk. paper)
1. Computer graphics. 2. User interfaces (Computer systems) I. Title
5
6
T385.C4515 2003
006.6 dc22
2003057705
Copyright &"docText">All rights reserved. No part of this publication may be reproduced, stored in a
retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying,
recording, or otherwise, without the prior consent of the publisher. Printed in the United States of America.
Published simultaneously in Canada.
For information on obtaining permission for use of material from this work, please submit a written request
to:
1 2 3 4 5 6 7 8 9 10 CRS 0706050403
Dedication
To Mel and Neel
[ Team LiB ]
[ Team LiB ]
Praise for
"This is the most comprehensive book about graphics programming using GDI+ so far. A lot
of useful sample code inside this book reveals that Mr. Chand apparently has done a fair
amount of research on GDI+. This book will be a very useful handbook for everyone who
does graphics programming for Windows."
"Graphics Programming with GDI+ explores and exploits a wonderful range of GDI+
programming concepts, techniques, and applications for programmers of beginner to
intermediate abilities. Being a prolific contributor to the Internet community of developers,
Mahesh Chand is offering what seems to be a natural extension of what he does best sharing
his programming skills with other talented programmers. Each chapter compels to the next."
"Mahesh does a very good job getting .NET developers up to speed using the GDI+ features
supported in the .NET Framework. There is good coverage of graphics fundamentals that
helps the reader better understand the concepts of graphics programming with GDI+, and
there are some excellent sample applications that demonstrate the graphics topics covered to
6
7
"Graphics Programming with GDI+ is a comprehensive reference for anyone who wants to
leverage this technology. It presents a clear discussion of the topics in such a manner that is
comprehensible to the beginner, but sufficiently in-depth to challenge seasoned
programmers."
[ Team LiB ]
[ Team LiB ]
The Microsoft .NET Development Series is supported and developed by the leaders and experts of Microsoft
development technologies including Microsoft architects and DevelopMentor instructors. The books in this
series provide a core resource of information and understanding every developer needs in order to write
effective applications and managed code. Learn from the leaders how to maximize your use of the .NET
Framework and its programming languages.
Don Box with Chris Sells, Essential .NET Volume 1: The Common Language Runtime, 0-201-73411-7
Anders Hejlsberg, Scott Wiltamuth, Peter Golde, C"docText">Alex Homer, Dave Sussman, Mark Fussell, A
First Look at [Link] and [Link] v. 2.0, 0-321-22839-1
Alex Homer, Dave Sussman, Rob Howard, A First Look at [Link] v. 2.0, 0-321-22896-0
Microsoft Common Language Runtime Team, The Common Language Runtime Annotated Reference and
Specification, 0-321-15493-2
Microsoft .NET Framework Class Libraries Team, The .NET Framework CLI Standard Class Library
Annotated Reference, 0-321-15489-4
Microsoft Visual C# Development Team, The C# Annotated Reference and Specification, 0-321-15491-6
James S. Miller and Susann Ragsdale, The Common Language Infrastructure Annotated Standard,
0-321-15493-2
7
8
Fritz Onion, Essential [Link] with Examples in Visual Basic .NET, 0-201-76039-8
Ted Pattison and Dr. Joe Hummel, Building Applications and Components with Visual Basic .NET,
0-201-73495-8
Chris Sells and Justin Gehtland, Windows Forms Programming in Visual Basic .NET, 0-321-12519-3
Damien Watkins, Mark Hammond, Brad Abrams, Programming in the .NET Environment, 0-201-77018-0
Shawn Wildermuth, Pragmatic [Link]: Data Access for the Internet World, 0-201-74568-2
[Link]/msdotnetseries/
[ Team LiB ]
[ Team LiB ]
Figures
[*]
A color version of this figure is available on the Addison-Wesley Web site at
[Link]/titles/0321160770.
[ Team LiB ]
[ Team LiB ]
Tables
[ Team LiB ]
[ Team LiB ]
Acknowledgments
First of all, I would like to thank a great team at Addison-Wesley, including Stephane Thomas, John D. Ruley,
Michael Mullen, Stephanie Hiebert, and Tyrrell Albaugh, all of whom were very helpful from time to time.
Technical reviewers played a vital role in improving the technical aspects of this book. Their comments and
suggestions made me think from various different programming perspectives. I would like to thank technical
reviewers Charles Parker, Min Liu, Gilles Khouzam, Jason Hattingh, Chris Garrett, Jeffery Galinovsky,
Darrin Bishop, and Deborah Bechtold.
I would also like to thank John O'Donnell for his contribution to the printing chapter of the book (Chapter 11).
[ Team LiB ]
[ Team LiB ]
Introduction
By introducing the .NET Framework to the programming world, Microsoft has changed the perspective and
vision of programming and programmers. Unlike previous programming environments, the .NET Framework
is designed with the future of software development in mind. Besides introducing the new
C"docText">Languages, tools, and utilities aside, the .NET Framework library is the real power of the .NET
8
9
Framework. It's an object-oriented class library that defines an interface to interact with various programming
technologies. Any programming language that is designed to work with the .NET Framework can access the
library, which makes a programmer's life easier because the methods and properties defined in the library are
the same, regardless of the language.
Each class defined in the .NET Framework library belongs to a particular namespace a logical unit that is
used to separate a particular programming interface from others. For example, the [Link]
namespace defines classes that are used for Windows Forms development. [Link] and its
subnamespaces define classes that are used for database development ([Link]).
GDI+ is the next-generation graphics device interface, defined in [Link] and its subnamespaces.
This book focuses on how to write graphical Windows and Web applications using GDI+ and C# for the
Microsoft .NET Framework.
[ Team LiB ]
[ Team LiB ]
• How GDI+ works, and where it is defined in the .NET Framework library
• How to draw text, lines, curves, rectangles, ellipses, and other graphics shapes in GDI+
• How to fill rectangles, ellipses, and other closed curves with different colors, styles, and textures
• Painting and drawing in .NET
• Viewing and manipulating images
• How Windows Forms and Web Forms are related to drawing
• How to write Web-based graphics applications
• Printing in .NET
• Transforming graphics objects, colors, and images
• Interactive color blending and transparent colors
• Using GDI in .NET applications
• Precautions to take when writing GDI+ applications
• Optimizing the performance of GDI+ applications
[ Team LiB ]
[ Team LiB ]
Prerequisites
There are some things you should know before beginning this book:
• Language: This book is written in C"docText">Framework: I used Visual Studio .NET to develop and
test the samples in this book. Knowledge of Visual Studio .NET and basics of the .NET Framework is
a requirement.
• Basics of graphics programming: A basic understanding of graphics programming is a plus but is not
mandatory.
• GDI programming experience: Experience with GDI programming is a plus but is not mandatory.
[ Team LiB ]
[ Team LiB ]
9
10
Chapter Organization
Before we start, let's take a quick tour of this book. It has 15 chapters and one appendix. Here's a brief
introduction:
GDI+ is a new and improved version of GDI. This chapter introduces the GDI+ library, its advantages over
previous versions, new features and additions to the library, and how it is related to the .NET Framework.
In the .NET Framework Library, GDI+ functionality is defined in the [Link] namespace and its
subnamespaces. This chapter discusses the contents of these namespaces. After finishing this chapter, you will
understand which functionality is defined where and when to which namespace.
The Graphics class plays a major role in GDI+. Whenever you need to draw a graphics object, you must use
the Graphics class. This chapter discusses Graphics class methods and properties, and how to use them.
After completing this chapter, you'll have a pretty good idea how to draw and fill various graphics objects.
Brushes and pens are used to fill and draw graphics objects. GDI+ provides many classes for working with
brushes and pens. This chapter describes how to work with them.
This chapter discusses the color-, font-, and text-related classes provided by the .NET Framework class library
in more detail.
10
11
Rectangles and regions can be very useful and very tricky. This chapter covers them in detail.
The .NET Framework divides GDI+ functionality between two namespaces: [Link] and
[Link]. This chapter covers the basic imaging-related functionality defined in the
[Link] namespace.
This chapter discusses more imaging functionality, including the [Link] namespace and
how to work with metafiles in the .NET Framework. We will also see how to maintain the quality and
rendering speed of images in GDI+.
This chapter discusses advanced two-dimensional graphics programming using GDI+. Advanced 2D
techniques and tools include blending, matrices, graphics paths, and gradient brushes.
This chapter examines GDI+ transformation. Transformation can be applied not only to graphics shapes,
curves, and images, but also to image colors.
GDI+ can also be used in Web applications. This chapter discusses how to use GDI+ in Web applications with
[Link].
This chapter concentrates on GDI+ best practices and GDI+-related tips and tricks to improve the quality and
performance of drawing.
This chapter demonstrates how GDI can be used with GDI+ in managed applications.
11
12
In this chapter we have some fun with GDI+. Among the topics in this chapter are designing interactive GUI
applications, creating shaped forms, and adding custom text in images.
[ Team LiB ]
[ Team LiB ]
[ Team LiB ]
[ Team LiB ]
SUMMARY
This introduction explained the book's organization and answered basic questions about the book. In Chapter
1, you will learn the basics of GDI+. Topics we will cover include
• What is GDI+, and why it is a better programming interface than its predecessors?
• How is GDI+ designed and used in the .NET Framework?
• What are the major advantages of GDI+ over GDI?
• How do you write your first graphics application in .NET using GDI+?
• What are some of the basic graphics concepts?
[ Team LiB ]
[ Team LiB ]
12
13
that interact with graphics devices such as monitors, printers, or files, you will have to use GDI+.
This chapter will introduce GDI+. First we will discuss the theoretical aspects of GDI+, which you should
know before starting to write a graphics application.
After reading this chapter, you should understand the following topics:
• What GDI+ is
• How GDI+ is defined
• How to use GDI+ in your applications
• What's new in GDI+
• What the major programming differences between GDI and GDI+ are
• Which major namespaces and classes in the .NET Framework library expose the functionality of
GDI+
[ Team LiB ]
[ Team LiB ]
1.1.1 Definition
GDI+ is a library that provides an interface that allows programmers to write Windows and Web graphics
applications that interact with graphical devices such as printers, monitors, or files.
All graphical user interface (GUI) applications interact with a hardware device (a monitor, printer, or
scanner), that can represent the data in a human-readable form. However, there is no direct communication
between a program and a device; otherwise, you would have to write user interface code for each and every
device with which your program interacts!
To avoid this monumental task, a third component sits between the program and device. It converts and passes
data sent by the program to the device and vice versa. This component is the GDI+ library. Typing a simple
"Hello World" on the console, drawing a line or a rectangle, and printing a form are examples in which a
program sends data to GDI+, which converts it for use by a hardware device. Figure 1.1 illustrates this
process.
Now let's see how GDI+ works. Suppose your program draws a line. A line is displayed as a set of pixels
drawn in sequence from the starting location to the ending location. To draw a line on a monitor, the monitor
needs to know where to draw the pixels. Instead of telling the monitor to draw pixels, your program calls the
DrawLine method of GDI+, and GDI+ draws the line from point A to point B. GDI+ reads the point A and
13
14
point B locations, converts them to a sequence of pixels, and tells the monitor to display the sequence of
pixels.
GDI+ allows you to write device-independent managed applications and is designed to provide high
performance, ease of use, and multilingual support.
The previous section defined GDI+. But how is it implemented? GDI+ is a set of C++ classes that are located
in a class library called [Link]. [Link] is a built-in component of the Microsoft Windows XP
and Windows Server 2003 operating systems.
Tip
You can use GDI+ on Windows operating systems other than XP. You just need to install GDI+ on the
computer, which means that [Link] must be copied to the system directory. Installing the .NET SDK,
Visual Studio .NET, or .NET redistributable copies [Link] automatically.
Comparing GDI+ to GDI, as we do later in this chapter, is a natural way to introduce GDI+. Note, however,
that prior knowledge of GDI is not a prerequisite for learning GDI+ or using this book. This book is about
GDI+ development in the .NET Framework, which provides new classes and a new way to write graphics
applications. Prior experience with GDI will aid your understanding of the basic concepts, but it is not
necessary.
GDI Interoperability
You can use GDI in managed applications with GDI+. GDI interoperability allows you to use GDI
functionality in managed applications with GDI+, but you need to take some precautions. We will
discuss GDI interoperability in Chapter 14.
The previous section said that the GDI+ library is a set of C++ classes that can be used from both managed
and unmanaged code. Before we discuss how GDI+ is represented in the .NET Framework library, let's
review the concepts of managed and unmanaged code.
Code written in the Microsoft .NET development environment is divided into two categories: managed and
unmanaged. In brief, code written in the .NET framework that is being managed by the common language
runtime (CLR) is called managed code. Code that is not being managed by the CLR is called unmanaged
code.
Managed code enjoys many rich features provided by the CLR, including automatic memory management and
garbage collection, cross-language integration, language independence, rich exception handling, improved
security, debugging and profiling, versioning, and deployment. With the help of a garbage collector (GC), the
14
15
CLR automatically manages the life cycle of objects. When the GC finds that an object has not been used after
a certain amount of time, the CLR frees resources associated with that object automatically and removes the
object from the memory. You can also control the life cycle of objects programmatically.
You can write both managed and unmanaged applications using Microsoft Visual Studio .NET. You can use
Visual C++ 7.0 to write unmanaged code in Visual Studio .NET. Managed Extensions to C++ (MC++) is the
way to write C++ managed code. Code written using C# and Visual Basic .NET is managed code.
GDI+ exposes its functionality for both managed and unmanaged code. As noted earlier, GDI+ is a set of
unmanaged C++ classes. Programmers targeting unmanaged code can use these C++ classes to write their
graphics applications.
Note
This book targets only managed code development. Unmanaged GDI+ development will not be discussed.
The .NET Framework library provides managed classes that are a nice wrapper around GDI+ C++ classes.
The GDI+ managed classes provided by the .NET Framework library are defined in the [Link]
and [Link] assemblies. Figure 1.2 shows a conceptual diagram of the communication
between managed Windows and Web applications and display devices through managed GDI+. As the
diagram shows, the managed GDI+ classes defined in the [Link] namespace and its subnamespace
are a wrapper around the GDI+ C++ classes defined in the [Link] unmanaged library.
The managed GDI+ classes provided in the .NET Framework library are defined in the [Link]
namespace and its five subnamespaces: [Link], [Link].Drawing2D,
[Link], [Link], and [Link]. We will discuss these
namespaces and their classes in more detail in Section 1.4.
In brief,
• GDI+ is a component that sits between an application and graphical devices. It converts data into a
form compatible with a graphical device, which presents the data in human-readable form.
• GDI+ is implemented as a set of C++ classes that can be used from unmanaged code.
15
16
• In the .NET Framework library, GDI+ classes are exposed through [Link] (and its
subnamespaces), which provides a managed class wrapper around the GDI+ C++ classes.
In this book we will be using GDI+ through the namespaces provided by the .NET Framework library. If you
want to learn more about GDI+ C++ classes, search for GDI+ references on MSDN. On the GDI+ references
page (go to [Link] expand Graphics and Multimedia, and then click on GDI+),
you can find all GDI+ classes, functions, constants, enumerations, and structures.
GDI+ provides significant improvements over its predecessor, GDI. In this section we will take a quick look
at these improvements.
GDI+ provides some nice features for 2D vector graphics. One of the many nice features is support for
floating point coordinates. For example, the PointF, SizeF, and RectangleF classes represent a floating
point, size, and rectangle, respectively. Other objects that use Point, Size, and Rectangle objects also have
overloaded methods that can use the PointF, SizeF, and RectangleF objects.
The alpha component, which represents the opacity of a color, is a new addition to the Color structure. Alpha
blending, anti-aliasing, and color blending are other new additions to the library. We will discuss these topics
in more detail in Chapters 5 and 9.
Texture and gradient brushes are another new addition. Some other additions to the basic primitives are
compound lines, cardinal splines, scalable regions, inset pens, high-quality filtering and scaling, and many
new line styles and line cap options.
Imaging is another area where GDI developers will find many new additions in GDI+. Some of the additions
are native support for image file formats such as .jpeg, .png, .gif, .bmp, .tiff, .exif, and .icon; support
for encoding and decoding raster formats; native image processing support; brightness, contrast, and color
balance; and support for transformations, including rotation and cropping.
In color management, support for sRGB, ICM2, and sRGB64 is a new addition. Typography support includes
the ClearType, texture, and gradient-filled texts, as well as support for Unicode and Windows 2000 scripts.
[ Team LiB ]
[ Team LiB ]
1. 2D vector graphics
2. Imaging
3. Typography
4. Printing
5. Design
Vector graphics concerns the drawing of shapes that can be specified by sets of points on a coordinate system.
Such shapes are called primitives; examples include lines, curves, rectangles, and paths. In managed GDI+, a
16
17
class object or structure represents a graphics primitive. Each class or structure provides members that can be
used to get and set a primitive's properties. For example, the Point structure provides X and Y properties that
represent the x- and y-coordinate values of a point. The Point structure also provides methods, including
Ceiling, Round, and Truncate. We will discuss these methods in more detail in Chapter 2.
In the .NET Framework library, 2D vector programming is divided into two categories: general and advanced.
General 2D vector graphics programming functionality is defined in the [Link] namespace;
advanced functionality is defined in the [Link].Drawing2D namespace.
The major 2D vector programming classes defined in the [Link] namespace are Pen, Pens, Brush
(and Brush-derived classes), Brushes, Font (and Font-related classes), Point, Rectangle, and Size. We will
discuss these classes and their members in more detail in other chapters according to how they are
categorized.
The [Link].Drawing2D namespace provides blending, color blending, graphics paths, custom line
caps, hatch and linear gradient brushes, and matrices. We will discuss these classes and their members in more
detail in Chapter 9.
1.2.2 Imaging
Imaging involves viewing and manipulating images. In managed GDI+, imaging functionality is divided into
two categories: basic and advanced. The basic functionality is defined in the Image class, which also serves as
the base class of the Bitmap and Metafile classes. The Image class provides members to load, create, and
save images.
The Bitmap and Metafile classes define functionality for displaying, manipulating, and saving bitmaps and
metafiles. Chapters 7 and 8 cover imaging functionality in more detail.
1.2.3 Typography
Typography refers to the design and appearance of text. GDI+ provides classes to create and use fonts. Some
of the font-related classes are Font, FontFamily, and FontConverter. GDI+ also provides classes to read all
installed fonts on a system. You can also add custom fonts to the font collection. We will cover the
capabilities of GDI+ with respect to fonts and typography in Chapter 5.
1.2.4 Printing
GDI+ provides easy-to-use classes that encapsulate Windows printing functionality. The printing classes
defined in the .NET Framework class library provide access to and control over available printers, printer
sources, paper and paper sources, pages, printer resolution, and so on. GDI+ printing functionality is defined
in the [Link] namespace. Chapter 11 is dedicated to printing functionality.
1.2.5 Design
The GDI+ class library also provides classes that extend design-time user interface (UI) logic and drawing
functionality. These classes are defined in the [Link] namespace. Examples of extended UI
functionality include creating custom toolbox items, type-specific value editors, and type converters.
[ Team LiB ]
[ Team LiB ]
17
18
We have already mentioned the first and major difference between the two versions: Whereas GDI+ exposes
its functionality as both unmanaged and managed classes (through the [Link] namespace), GDI is
unmanaged only. Besides this major difference, some of the important changes in GDI+ are as follows:
As a GDI programmer, you must be familiar with the device context. A device context is a structure that
stores information about a particular display device, such as a printer or monitor. This structure specifies how
the graphics objects will be drawn on the output device. The device context also stores information about the
properties of graphics objects, such as the quality of rendering and so on. To draw an object on a device, first
an application needs to get a handle to the device context (HDC), which is used by GDI to send information to
the device.
In GDI+, the concept of device context and handle to the device context is replaced by the Graphics object.
The Graphics class provides methods and properties to draw various graphics objects; these methods and
properties are very easy to use compared to the earlier device context based programming model.
Suppose that you need to draw a line from point (20, 20) to point (200, 200). In GDI, first an application
creates an HDC using the BeginPaint function, which takes a window handle and a PAINTSTRUCT structure.
Alternatively, you can call the GetDC function. To draw a line, the application must create a pen object and
draw a line using this pen. An application can obtain a pen object by making a call to the CreatePen function,
which returns a handle to the pen.
Before starting to draw, the application needs to call the SelectObject function, which takes the device
context and pen handle as arguments. Now the application can draw any graphics object. The application calls
the EndPaint function to end the drawing process. For example, the code snippet in Listing 1.1 draws a line
using the MoveToEx and LineTo functions.
18
19
hPen = CreatePen(PS_SOLID, 5,
RGB(255, 255, 0));
SelectObject(handle, hPen);
MoveToEx(handle, 20, 20, NULL);
LineTo(handle, 200, 200);
EndPaint(hWnd, &pstruct);
..................
.....................
}
}
Now let's see the same example in GDI+: First you need a Graphics object associated with a form, which is
usually available on the form's Form_Paint event or OnPaint method. Once you've got the Graphics object
associated with a form, you can call its draw and fill methods to draw and fill various graphics objects, such as
lines, rectangles, and curves. For example, the code written in Listing 1.2 is the form's paint method. As this
code shows, first we get a Graphics object associated with the form by using [Link].
After that we create a Pen object and pass it as an argument to the DrawLine method. The DrawLine method
takes a Pen object and the starting and ending points of a line, and draws a line on the form. Notice also in
Listing 1.2 that there is no MoveTo call.
Note
There are other ways to get a Graphics object in your application. We will look at these options in more
detail in Chapter 3.
If you compare Listings 1.1 and 1.2, it's easy to see that the GDI+ model is more flexible, easier to use, and
more object-oriented. GDI provides functions to draw graphics objects; GDI+ provides objects. Each graphics
primitive is an object. For example, in GDI+, a pen is represented by a Pen object, as opposed to the HPEN
structure in GDI.
In GDI, first you select a brush, path, image, or font and pass this object a device context. Then you use the
device context handle to draw a graphics object, which means all the objects drawn using that device context
will have the same effects.
Unlike GDI, GDI+ provides an object-independent model, which means that pens, brushes, images, or fonts
can be created and used independently and can be changed at any time. In addition, an application can even
use different pens to draw different graphics objects on the same form, which is not true in the case of a
device context.
19
20
GDI+ methods provide many overloaded forms to provide more flexibility to developers. For example, the
DrawRectangle method has three overloaded forms:
These forms allow developers to draw a rectangle from a rectangle object, four integer values, or floating
point values. The DrawRectangle method draws a rectangle specified by a coordinate pair, a width, and a
height. The DrawImage method, used to draw images, has no fewer than 30 overloaded forms. We will discuss
these methods in more detail and see them in action in Chapter 3.
Drawing and filling are analogous to writing and painting. When you write, you use a pen to "draw" symbols
made up of lines and curves. Painting means you take a brush, dip it into a color, and fill in areas with the
color.
In GDI, both actions (fill and draw) are done in one step. For example, consider drawing and filling a
rectangle. First an application creates a pen and a brush and calls SelectObject to select that pen and brush.
Then the application calls the Rectangle method, which draws and fills the rectangle. Listing 1.3 shows a
code snippet that draws and fills a rectangle.
In GDI+, the Graphics class provides separate draw and fill methods. For example, the DrawRectangle
method takes a Pen object and draws an outline of a rectangle, and the FillRectangle method takes a Brush
object and fills the rectangle with the specified brush, as Listing 1.4 shows.
Graphics g = [Link];
Pen pn = new Pen([Link], 3);
HatchBrush htchBrush = new HatchBrush([Link],
[Link], [Link]);
[Link](pn, 50, 50, 100, 100);
[Link](htchBrush, 20, 20, 200, 200);
We will discuss the draw and fill methods in more detail in Chapter 4.
Regions are another area where a GDI developer may find minor changes in GDI+. GDI provides several
functions for creating elliptical, round, and polygonal regions. As a GDI programmer, you are probably
familiar with the CreateRectRgn, CreateEllipticRgn, CreateRoundRectRgn, CreatePolygonRgn, and
CreatePolyPolygonRgn functions.
20
21
In GDI+, the Region class represents a region. The Region class constructor takes an argument of type
GraphicsPath, which can have a polygon, a circle, or an ellipse to create a polygonal, round, or elliptical
region, respectively. We will discuss regions in more depth in Chapter 6.
[ Team LiB ]
[ Team LiB ]
Note
The .NET Framework class library is also referred as the .NET runtime
class library or base class library (BCL).
Note
If you are already aware of the .NET Framework library's GDI+ objects
and class hierarchy, you may want to skip the rest of this chapter.
21
22
1.4.1 The [Link] Namespace
22
23
Provides members that convert
fonts from one type to another.
FontFamily Defines a group of typefaces
having a similar basic design and
certain variations in styles.
Graphics A key class that encapsulates
drawing surfaces. Among many
other things, the Graphics class
provides members to draw and fill
graphical objects.
Icon Represents a Windows icon. The
Icon class provides members to
define the size, width, and height
of an icon.
IconConverter Provides members to convert an
Icon object from one type to
another.
Image Provides members to define the
size, height, width, and format of
an image. The Image class also
provides methods to create Image
objects from a file, a window
handle, or a stream; and to save,
rotate, and flip images. It is an
abstract base class, and its
functionality is used through its
derived classes: Bitmap, Icon, and
Metafile.
ImageAnimator Provides methods to start and stop
animation, and to update frames
for an image that has time-based
frames.
ImageConverter Provides members to convert
Image objects from one type to
another.
ImageFormatConverter Defines members that can be used
to convert images from one format
to another.
Pen Defines a pen with a specified
color and width. A pen is used to
draw graphical objects such as a
line, a rectangle, a curve, or an
ellipse.
Pens Provides static members for all the
standard colors. For example,
[Link] represents a red pen.
PointConverter Defines members that can be used
to convert Point objects from one
type to another.
RectangleConverter Defines members that can be used
to convert Rectangle objects from
one type to another.
Region Represents a region in GDI+,
which describes the interior of a
23
24
graphics shape.
SizeConverter Defines members that can be used
to convert size from one type to
another.
SolidBrush Inherited from the Brush class.
This class defines a solid brush of
a single color.
StringFormat Provides members to define text
format, including alignment,
trimming and line spacing, display
manipulations, and OpenType
features.
SystemBrushes Defines static properties. Each
property is a SolidBrush object
with a Windows display element
such as Highlight,
HighlightText, or
ActiveBorder.
SystemColors Defines static properties of a
Color structure.
SystemIcons Defines static properties for
Windows systemwide icons.
SystemPens Defines static properties. Each
property is a Pen object with the
color of a Windows display
element and a width of 1.
TextureBrush Inherited from the Brush class.
This class defines a brush that has
an image as its texture.
ToolboxBitmapAttribute Defines the images associated with
a specified component.
As its name suggests, the [Link] namespace provides additional functionality to develop
design-time controls such as custom toolbox items, graphics editors, and type converters. The classes of the
[Link] namespace are described briefly in Table 1.2.
Besides the classes discussed in Table 1.2, the [Link] namespace also defines a few
interfaces, delegates, and enumerations. Table 1.3 lists the interfaces defined in this namespace.
Besides the classes discussed in Table 1.4, the [Link].Drawing2D namespace provides dozens of
enumerations. We will discuss these enumerations when we use them in examples in later chapters.
24
25
1.4.4 The [Link] Namespace
Table 1.5 briefly describes the classes of the [Link] namespace. These classes and their
use are discussed in more detail in Chapter 8.
The [Link] namespace defines printing-related classes and types in GDI+. Before an
application uses classes from this namespace, it must include the namespace.
Table 1.6 briefly discusses the classes provided by the [Link] namespace. These classes
and their use are discussed in more detail in Chapter 11.
The [Link] namespace contains only a few classes related to advanced GDI+ typography
functionality. Before an application uses classes from this namespace, it must include the namespace. Table
1.7 describes these classes; they will be discussed in more detail in Chapter 5.
Class
Description
BitmapEditor
CategoryNameCollection
Collection of categories.
FontEditor
ImageEditor
PaintValueEventArgs
PropertyValueUIItem
25
26
ToolboxComponentsCreatedEventArgs
Provides data for the ComponentsCreated event, which occurs when components are added to the toolbox.
ToolboxComponentsCreatingEventArgs
Provides data for the ComponentsCreating event, which occurs when components are added to the toolbox.
ToolboxItem
ToolboxItemCollection
UITypeEditor
Interface
Description
IPropertyValueUIService
IToolboxService
IToolboxUser
Tests the toolbox for toolbox item support capabilities and selects the current tool.
Class
Description
AdjustableArrowCap
Represents an adjustable arrow-shaped line cap. Provides members to define the properties to fill, and to set
the height and width of an arrow cap.
Blend
Gradient blends are used to provide smoothness and shading to the interiors of shapes. A blend pattern
contains factor and pattern arrays, which define the position and percentage of color of the starting and ending
colors. The Blend class defines a blend pattern, which uses LinearGradientBrush to fill the shapes. The
Factors and Positions properties represent the array of blend factors and array of positions for the gradient,
26
27
respectively.
ColorBlend
Defines color blending in multicolor gradients. The Color and Position properties represent the color array
and position array, respectively.
CustomLineCap
GraphicsContainer
GraphicsPath
In GDI+, a path is a series of connected lines and curves. This class provides properties to define the path's fill
mode and other properties. This class also defines methods to add graphics shapes to a path. For instance, the
AddArc and AddCurve methods add an arc and a curve, respectively, to the path. Wrap, Transform, Reverse,
and Reset are some of the associated methods.
GraphicsPathIterator
A path can contain subpaths. This class provides the ability to find the number of subpaths and iterate through
them. Count and SubpathCount return the number of points and the number of subpaths in a path,
respectively.
GraphicsState
HatchBrush
Hatch brushes are brushes with a hatch style, a foreground color, and a background color. This class
represents a hatch brush in GDI+.
LinearGradientBrush
Matrix
Encapsulates a 3x3 matrix that represents a geometric transformation. This class defines methods for
inverting, multiplying, resetting, rotating, scaling, shearing, and translating matrices.
PathData
Contains the data in the form of points and types that makes up a path. The Points property of the class
represents an array of points, and the Types property represents the types of the points in a path.
PathGradientBrush
Represents a brush with a graphics path. PathGradientBrush contains methods and properties for blending,
wrapping, scaling, and transformation. This class encapsulates a Brush object that fills the interior of a
27
28
RegionData
Represents the data stored by a Region object. The Data property of this class represents the data in the form
of an array of bytes.
Class
Description
BitmapData
Often we don't want to load and refresh all data of a bitmap because rendering each pixel is not only a slow
process, but also consumes system resources. With the help of the BitmapData class and its LockBits and
UnlockBits methods, we can lock the required data of a bitmap in memory and work with that instead of
working with all the data.
ColorMap
Defines a map for converting colors. ColorMap is used by the ImageAttributes class.
ColorMatrix
Defines a 5x5 matrix that contains coordinates for the ARGB space. ColorMatrix is used by the
ImageAttributes class.
ColorPalette
Defines an array of colors that make up a color palette. ColorPalette is used by the ImageAttributes class.
Encoder
Represents an encoder, which represents a globally unique identifier (GUID) that identifies the category of an
image encoder parameter. Encoder is used by the EncoderParameter class.
EncoderParameter
An encoder parameter, which sets values for a particular category of an image. This class is used in the Save
method with the help of EncoderParameters.
EncoderParameters
FrameDimension
ImageAttributes
Contains information about how image colors are manipulated during rendering (for more information, see
Chapter 7).
28
29
ImageCodecInfo
ImageFormat
Metafile
Defines a graphic metafile, which contains graphics operations in the form of records that can be recorded
(constructed) and played back (displayed).
MetafileHeader
MetaHeader
PropertyItem
WmfPlaceableFileHeader
Class
Description
Margins
Specifies the margins of a printed page. The Bottom, Left, Right, and Top properties are used to get and set
the bottom, left, right, and top margins, respectively, of a page in hundredths of an inch.
MarginsConverter
PageSettings
Specifies settings of a page, including properties such as Bounds, Color, Landscape, Margins, PaperSize,
PaperSource, PrinterResolution, and PrinterSettings.
PaperSize
Specifies the paper size. Its properties include Height, Width, PaperName, and Kind. The Kind property is the
type of paper, represented by the PaperKind enumeration, which has members that represent A3, envelopes,
sheets, ledgers, and so on.
29
30
PaperSource
Specifies the paper tray from which the printer gets paper, with properties Kind and SourceName. SourceName
is a type of PaperSource enumeration, which defines members based on the Kind property.
PreviewPageInfo
Provides print preview information for a single page. The Image property returns the image of the printed
page, and the PhysicalSize property returns the size of the printed page in 1/1000 inch.
PreviewPrintController
Displays a document on a screen as a series of images for each page. The UseAntiAlias property gets and
sets the anti-aliasing when displaying the print preview.
PrintController
Controls how a document is printed. The class provides four methods: OnStartPage, OnStartPrint,
OnEndPage, and OnEndPrint.
PrintDocument
Starts the printing process. Creates an instance of this class, sets the printing properties that describe how to
print, and calls the Print method to start the process.
PrinterResolution
Provides properties to return a printer resolution. The Kind, X, and Y properties return the printer resolution,
horizontal resolution in dots per inch (dpi), and vertical printer resolution in dpi, respectively.
PrinterSettings
Provides methods and properties for setting how a document is printed, including the printer that prints it.
Some of the common properties are MinimumPage, MaximumPage, Copies, MaximumCopies, PrinterName, and
so on.
[Link]
[Link]
[Link]
PrinterUnitConvert
Specifies a series of conversion methods that are useful when interoperating with the Win32 printing
application program interface (API).
PrintEventArgs
30
31
PrintingPermission
PrintingPermissionAttribute
PrintPageEventArgs
QueryPageSettingsEventArgs
StandardPrintController
Class
Description
FontCollection
Abstract base class for installed and private font collections. It provides a method to get a list of the font
families contained in the collection. Two derived classes from the FontCollection class are
InstalledFontCollection and PrivateFontCollection.
InstalledFontCollection
PrivateFontCollection
Represents a collection of font families built from font files that are provided by the client application.
[ Team LiB ]
[ Team LiB ]
Summary
GDI+ is an improved version of Microsoft's graphics device interface (GDI) API. In this chapter we learned
how GDI+ is designed for use in both managed and unmanaged code. [Link] and its helper
namespaces defined in the .NET Framework library provide a managed class wrapper to write managed GDI+
applications. We also learned the basics and definition of GDI+ and what major improvements are offered by
GDI+ in comparison to GDI. At the end of this chapter, we took a quick look at the [Link]
namespace and its subnamespaces, and classes defined in these namespaces.
Now that you've learned the basics of GDI+, the next step is to write a fully functional graphics application. In
Chapter 2 you will learn how to write your first graphics application using GDI+ in a step-by-step tutorial
31
32
format.
[ Team LiB ]
[ Team LiB ]
To write a graphics application, a good understanding of drawing surfaces and coordinate systems is
necessary. We will begin by discussing these concepts and how they are represented in GDI+. Then you'll
learn step-by-step how to write a graphics application in the .NET Framework using GDI+. We will cover the
following topics:
At the end of this chapter we will discuss some basic graphics structures and their members. These structures
are used in examples throughout this book and include the following:
• Color
• Point and PointF
• Rectangle and RectangleF
• Size and SizeF
[ Team LiB ]
[ Team LiB ]
1. The canvas is the space on which objects will be drawn. For example, in a Windows application, a
Windows Form is a canvas.
2. A brush or a pen represents the texture, color, and width of the objects to be drawn on the canvas.
3. The process describes how objects are drawn on the canvas.
To draw graphics objects you need to have a pen or a brush, which defines the texture, color, and width of the
drawing. For example, if you draw a line or a rectangle, you need to create a pen with a color and width.
The process component of the drawing application includes making a call to draw the line or rectangle on the
form.
Each drawing surface has four common properties: width, height, resolution, and color depth.
• The width and height properties of a surface determine the size of the surface, and they are specified
by the number of pixels horizontally and vertically, respectively.
32
33
• The resolution property of a surface is a measurement of the output quality of graphics objects or
images in dots per inch (dpi). For example, a resolution of 72 dpi means that 1 inch of the surface
holds 72 horizontal and 72 vertical pixels. For monitors and LCDs, the resolution is frequently
specified in terms of the total number of pixels horizontally and vertically rather than a pixel density.
Thus a monitor resolution of 1280x1024 means that the screen of the monitor can hold 1,280
horizontal pixels and 1,024 vertical pixels.
• The color depth of a surface is the number of colors used to represent each pixel.
Definition: Pixel
A pixel is the smallest element that participates in the drawing process to display graphics objects or images
on the screen. The pixel density is often represented by a value in dots per inch (dpi).
The quality of a pixel is directly proportional to the color depth. The Color structure represents a color in
GDI+. It has four components: alpha, red, green, and blue. The RGB (red-green-blue) components of a color
represent the number of possible colors (see Figure 2.1). Each component in RGB has 256 (28) color
combinations. Hence all three components of GDI+ color represent 256x256x256 possible colors. The alpha
component determines the transparency of the color, which affects how the color mixes with other colors.
To see the proper colors defined in the GDI+ color structure, a drawing surface must support at least a 24-bit
color system (for the RGB components of a color structure), which means that each pixel of the surface must
be able to hold 24 bits (8 bits each for the R, G, and B components, as noted already). Surfaces with less than
24 bits per pixel may not display graphics objects and images exactly as defined in a drawing application. We
will discuss colors in more detail in Chapter 5.
Note
The color depth of a surface is different from the color depth of a particular display device, such as a monitor
or a printer. Most monitors can support over a million colors, and some printers may support only black and
white.
GDI+ provides three types of drawing surfaces: forms, printers, and bitmaps.
When you write a Windows application that draws something on a form, the form acts as a drawing surface
and supports all the properties required by a drawing surface.
33
34
When you print from an application, the printer acts as a drawing surface. You can set a printer's resolution
and color depth, as well as the height and width of the paper. We will discuss printer-related functionality in
Chapter 11.
When you create images in memory and save them as a bitmap, the bitmap functions as a drawing surface.
You can set the image width, height, resolution, and color depth properties. Bitmap surfaces are commonly
used for writing graphics Web applications. Drawing works a little differently in Web applications. For
example, if you want to draw a line and a rectangle in a Web page using GDI+, you need to create an image,
use this image as a surface for the line and rectangle objects, set its surface-related properties, and then send
the image to the browser. We will discuss Web graphics applications in more detail in Chapter 12.
[ Team LiB ]
[ Team LiB ]
The Cartesian coordinate system (shown in Figure 2.2) divides a two-dimensional plane into four regions, also
called quadrants, and two axes: x and y. The x-axis is represented by a horizontal line and the y-axis by a
vertical line. An ordered pair of x and y positions defines a point in a plane. The origin of the plane is a point
with x = 0 and y = 0 values, and the quadrants divide the plane relative to the origin.
To find out which point falls in which quadrant, we compare the point's x- and y-positions relative to the
origin:
34
35
A point with positive x and y values will fall in quadrant I. A point with +y and x values will fall in quadrant
II. A point with x and y values will fall in quadrant III, and a point with +x and y values will fall in
quadrant IV. For example, a point at coordinates (2, 3) will fall in quadrant IV, and a point at coordinates
( 3, 2) will fall in quadrant II.
Unlike the Cartesian coordinate system, the default GDI+ coordinate system starts with the origin in the upper
left corner. The default x-axis points to the right, and the y-axis points down. As Figure 2.3 shows, the upper
left corner starts with points x = 0 and y = 0. Points to the left of x = 0 are negative values in the x-direction,
and points above y = 0 are negative values in the y-direction.
Because the default GDI+ coordinate system starts with (x = 0, y = 0) in the upper left corner of the screen, by
default you can see only the points that have positive x and y values. Objects with either x or y values will
not be visible on the screen. However, you can apply transformations to move objects with negative values
into the visible area.
GDI+ provides three types of coordinate systems: world coordinates, page coordinates, and device
coordinates.
1. The coordinate system used in an application is called world coordinates. Suppose that your
application draws a line from point A (0, 0) to point B (120, 80), as shown in Figure 2.4. If you don't
apply any transformation, the line will be displayed at the right location. Now suppose you want to
draw a line from point A ( 40, 50) to point B ( 10, 20). The line drawn using these two points will
not be displayed on the screen because the GDI+ coordinate system starts at point (0, 0). However,
you can transform the coordinates such that ( 40, 50) is the starting point at the top left corner of the
surface.
Figure 2.4. Drawing a line from point (0, 0) to point (120, 80)
35
36
2. The new coordinate system is called page coordinates. The process of converting world coordinates to
page coordinates is called the world transformation.
3. You can also control the actual size of graphics objects. For example, if you want to draw a line in
inches instead of pixels, you can simply draw a line from point A (1, 1) to point B (1, 2), thereby
creating a line that is 1 inch long. The new coordinates are called device coordinates. The process of
converting page coordinates to device coordinates is called the page transformation.
We will discuss coordinate systems and transformation in more detail in Chapter 10.
[ Team LiB ]
[ Team LiB ]
The first step of this tutorial is to create a Windows application using Visual Studio .NET.
1. Open Visual Studio .NET, select File | New | Project, and then choose Visual
C"docEmphStrong">Project Types and Windows Application under Templates, as shown in Figure
2.5.
36
37
Figure 2.5. Creating a Windows application
Note
Clicking the OK button creates a Windows application with a form and opens the Form Designer, in which
you can build Windows applications.
As mentioned in Chapter 1, GDI+ functionality resides in the [Link] namespace and is defined
in the [Link] namespace. Hence the [Link] namespace must be included in the
application. Visual Studio .NET automatically adds a reference to this namespace, which you can see in the
beginning of the class. If the namespace is not defined there, you must add a reference manually. To add a
reference to the GDI+ library, you use the Add Reference dialog.
37
38
4. Click the OK button to add the [Link] namespace reference to your project.
5. Go to the Solution Explorer window and expand the References node. The [Link]
namespace is listed there Figure 2.7).
Note
Visual Studio .NET version 1.0 (or later) automatically adds a reference to the [Link]
library. In that case, you may not need to add a reference to the library.
6. After adding a reference to [Link], you must import [Link] and other
related namespaces, depending on the classes your application will use. For now, we will import the
[Link] and [Link].Drawing2D namespaces. We add the following two lines to
the top of our class:
using [Link];
using [Link].Drawing2D;
38
39
You can also qualify a namespace reference by directly adding it as a prefix of the class. For example, if you
don't want to use the using statements defined here, you can define a class as follows:
[Link] g = [Link];
Note
If you create a Windows application using [Link], only the line using [Link].Drawing2D needs
to be written because using [Link] will already be there.
After adding a GDI+ library reference to the project, the next step is to decide on a drawing surface. In a
Windows application, a form is a drawing surface. Every form has a Graphics object associated with it,
which provides the drawing functionality.
In the .NET Framework, the Graphics class represents a GDI+ Graphics object, which defines methods and
properties to draw and fill graphics objects. Whenever an application needs to draw anything, it must go
through the Graphics object.
Caution
There is no way to create a Graphics object using the new operator. For example, if you write the following
code, you will get a compiler error:
There are several ways to obtain a Graphics object associated with a form. Three of them are described in the
following sections.
You can get a Graphics object corresponding to a form using the PaintEventArgs property of the form's
paint event. For example, the following code gets a Graphics object from PaintEventArgs:
You can add the form's paint event handler using the Properties window. As Figure 2.8 shows, we add
Form1_Paint (the default name) as the paint event handler.
39
40
Figure 2.8. Adding the Form_Paint event handler
Tip
Double-clicking in the paint event drop-down menu in the Properties window also adds the event handler.
Another way to get a Graphics object associated with a form is to override the OnPaint method of the form,
which uses PaintEventArgs in a manner similar to the Form1_Paint event. The following code snippet
overrides the OnPaint method of a form:
Sometimes you don't want to use the OnPaint method. For example, you might want to draw something on a
button or a menu click event handler. The Form class provides the CreateGraphics method, which returns a
Graphics object. The following code snippet creates a Graphics object using the CreateGraphics method
and calls a method of the Graphics class:
Graphics g = [Link]();
[Link]([Link]);
[Link]();
40
41
As this snippet shows, we call the Clear method of the Graphics class, which sets the background color of
the surface as the background color of the form.
Caution
When you create a Graphics object using the CreateGraphics method, you must dispose of that object
explicitly by calling the Dispose method to release the resources associated with it.
You can also use the FromImage, FromHwnd, and FromHdc static methods of the Graphics class to create
Graphics objects from images, window handles, and window handles to device contexts, respectively. We
will discuss these methods in more detail in Chapter 3 (Section [Link] ).
The following code creates a Bitmap object and calls the static FromImage method, using a Bitmap object as
an input parameter, which returns a Graphics object.
Bitmap bmp =
new Bitmap(600,400,PixelFormat.Format32bppArgb);
Graphics g = [Link](bmp);
The following code creates a Graphics object from a window handle. In this example, this refers to a
Windows Form. You can even pass [Link] if your form is Form1.
Graphics g = [Link]([Link]);
Once you have a Graphics object, the next step is to decide what you're going to draw on the surface. You
may need one or more of the three objects: pen, brush, or image. In this chapter we will concentrate on pens
and brushes only. Images are discussed in Chapters 7 and 8.
In GDI+ the Pen and Brush classes represent a pen and a brush, respectively. The abstract Brush class
functionality is accessed through its derived classes: SolidBrush and HatchBrush, among others. Pens are
used when you need to draw lines, rectangles, and curve boundaries. Brushes are used when you need to fill
graphics objects. Chapter 4 discusses pens and brushes in detail.
The Pen class constructor takes as arguments the color and width of the pen. The following code creates a red
pen with a width of 3 pixels and a black pen with a width of 1 pixel. The Pens class provides static members,
each of which represents a pen with a particular color.
41
42
The SolidBrush class represents a solid brush in GDI+. This class's constructor takes a color as an argument.
The following code creates a green solid brush.
Once you have the surface, pens, and/or brushes, you can draw lines, shapes, curves, or images. The Graphics
class provides draw and fill methods to draw and fill graphics shapes, curves, or images. For example, the
FillRectangle method draws a rectangle with a filled color, and DrawRectangle draws the boundary of a
rectangle with the specified pen. Draw methods take a pen as an argument, and fill methods take a brush.
We override the OnPaint method and write the code in Listing 2.1 on this method. As Listing 2.1 shows, we
first set the smoothing mode of the Graphics object by setting its SmoothingMode property. The
SmoothingMode enumeration is defined in the [Link].Advanced2D namespace and is used to set the
quality of a graphics object. In our code, we set the smoothing mode to anti-aliasing. We will discuss this in
more detail in Chapters 8 and 9.
After that we create a rectangle, two pens, and a solid brush. In the next code snippet, we call the
DrawRectangle, FillEllipse, and DrawLine methods. The DrawRectangle method draws the boundaries of
a rectangle, the FillEllipse method fills an ellipse with the specified brush, and the DrawLine method draws
a line using the specified pen. Chapter 3 will discuss the fill and draw methods in more detail.
When you are done using objects, you must release them. In the .NET Framework library, most objects
provide a Dispose method, which can be used to dispose of an object. The Dispose method makes sure that
42
43
all resources allocated for an object are released.
The following code snippet creates Pen and SolidBrush objects as redPen and greenBrush, respectively:
When you are done with these objects, call the Dispose method to release the resources allocated with them.
For example, the following code snippet disposes of the redPen and greenBrush objects:
[Link]();
[Link]();
Now we will Dispose of the previously created objects using the Dispose method to the objects we created in
Listing 2.1, as shown in Listing 2.2. (Boldface lines are the new lines added to the listing.)
Disposing of Objects
In the .NET Framework, the garbage collector is responsible for managing resources associated
with an object. When you dispose of an object, the garbage collector collects the object right away
43
44
and frees all the resources associated with that object. If you don't dispose of an object, the
garbage collector will keep track of the objects, and if an object is not used for a certain amount of
time, it will dispose of it automatically.
It is always best programming practice to dispose of any objects that you create explicitly (using
the new operator).
The final step in creating an application is to build and run it. To do this, in Visual Studio .NET you can
simply select Debug | Start (F5) or Debug | Start Without Debugging (Ctrl+F5).
The output of the application looks like Figure 2.9. The application draws a line, a rectangle, and some
ellipses with different colors.
Congratulations! You have finished the first step toward becoming a GDI+ expert. Now you can write simple
graphics applications in Visual Studio .NET.
[ Team LiB ]
[ Team LiB ]
44
45
You may have noticed that we used the Color structure in our previous example.
The Color structure represents a GDI+ ARGB (alpha-red-green-blue) color.
This class contains a static property for almost every possible color. For
example, [Link] and [Link] represent black and red, respectively.
Besides these static properties, this structure has the additional properties
defined in Table 2.1.
Method
Description
FromArgb
Creates a Color structure from the four 8-bit ARGB component (alpha-red-green-blue) values.
FromKnownColor
45
46
FromName
GetBrightness
GetHue
GetSaturation
ToArgb
ToKnownColor
In GDI+, the Point structure represents an ordered pair of integer x- and y-coordinates that define a point in a
two-dimensional plane. The Point structure's constructor initializes a new instance of the Point structure.
The Point constructor has three overloaded forms that allow you to create a Point object from an integer, a
Size object, or two integers as follows:
1. public Point(int);
2. public Point(Size);
3. public Point(int, int);
The following code snippet creates Point objects using all three forms of the constructor:
The PointF structure is similar to the Point structure, but it uses floating point values instead of integers.
Unlike the Point structure, PointF has only one constructor, which takes two floating point values as x- and
y-coordinates.
Both the Point and the PointF structures define three properties: IsEmpty, X, and Y. The IsEmpty property
returns true if a point is empty, which means that both X and Y values are zero; otherwise it returns false.
The X and Y properties return the x- and y-coordinates of a point, respectively. The Empty static field of the
46
47
Point structure creates a new point with X and Y values set to zero.
Listing 2.3 creates a point with zero X and Y values using [Link] and assigns new coordinate values
using the X and Y properties. This example creates a Graphics object using the [Link] method
and returns the graphics surface for a form. The [Link] method creates a Graphics object from
a window handle, which we pass as [Link]. The DrawLine method draws a line starting from the first
point to the second point using the defined pen. You can test this code on a button or a menu click event
handler.
Figure 2.10 shows the output of Listing 2.3. The program draws a line from point 1 to point 2. The "Point"
text in this figure is a menu item.
Like the Point structure, PointF can also use Empty, X, and Y properties, as shown in Listing 2.4. You can test
this code on a button or a menu click event handler.
47
48
newPoint.Y = 200.0F;
// Create a Graphics object from the
// current form's handle
Graphics g = [Link]([Link]);
// Create a new pen with color blue
// and width = 4
Pen pn = new Pen([Link], 4);
// Draw a line from point pt to
// new point
[Link](pn, pt, newPoint);
// Dispose of Pen and Graphics objects
[Link]();
[Link]();
Figure 2.11 shows the output of Listing 2.4. It is identical to Figure 2.10.
The Point structure also defines methods to convert from PointF to Point. The Ceiling method of the
Point structure converts a PointF object to a Point object by rounding off the values of the PointF object to
the next higher integer values. The Round method converts a PointF object to Point by rounding floating
values to the nearest integer values. The Truncate method converts a PointF object to Point by truncating
the floating values to integers. Listing 2.5 shows how to use the Ceiling, Round, and Truncate methods. You
can test this code on a button or a menu click event handler.
Listing 2.5 Using the Ceiling, Round, and Truncate methods of Point
The Point structure also defines addition, equality, inequality, subtraction, Point-to-Size, and
Point-to-PointF conversion operators. Listing 2.6 shows how to add and subtract a Size object from a Point
object, convert from Point to PointF, and convert from a Point object to a Size object. You can test this
code on a button or a menu click event handler.
48
49
Listing 2.6 Some Point and PointF conversions
The Rectangle and RectangleF structures represent a rectangle in GDI+. A Rectangle structure stores the
top left corner and height and width of a rectangular region. You can create a Rectangle object from Point
and Size objects or by using four integer values as starting and ending coordinates of the rectangle.
The Rectangle and RectangleF structures provide properties that can be used to get the height, width, and
position of the rectangle. Table 2.3 describes the properties of the Rectangle and RectangleF structures.
Property
Description
Bottom
49
50
Height
IsEmpty
Returns true if all of the rectangle's values (starting point, height, and width) are zero; otherwise returns
false.
Left
Location
Right
Size
Top
Width
Listing 2.8 uses three different methods to create three Rectangle objects. The first method creates a
Rectangle object by using a Point and a Size. The second and third methods create a Rectangle by using
four integer values as the starting x- and y-coordinates and the width and height of the rectangle. After
creating the rectangles, the program creates pen and brush objects using the Pen and SolidBrush classes and
calls the fill and draw methods of Graphics to draw and fill the rectangles. Finally, we dispose of the objects.
You can test this code on a button or a menu click event handler.
50
51
// Create a Point object
Point pt = new Point(80, 80);
// Create a Size object
Size sz = new Size(100, 100);
// Create a rectangle from Point and Size
Rectangle rect1 = new Rectangle(pt, sz);
// Create a rectangle from integers
Rectangle rect2 =
new Rectangle(x, y, width, height);
// Create a rectangle from direct integers
Rectangle rect3 =
new Rectangle(10, 10, 180, 180);
// Create pens and brushes
Pen redPen = new Pen([Link], 2);
SolidBrush greenBrush =
new SolidBrush([Link]);
SolidBrush blueBrush =
new SolidBrush([Link]);
// Draw and fill rectangles
[Link](redPen, rect3);
[Link](blueBrush, rect2);
[Link](greenBrush, rect1);
// Dispose of the objects
[Link]();
[Link]();
[Link]();
[Link]();
Figure 2.12 shows the output from Listing 2.8: three different rectangles.
You can create a RectangleF object in a similar way. The only difference is that RectangleF takes floating
point arguments instead of integers, SizeF instead of Size, and PointF instead of Point. Listing 2.9 creates
RectangleF objects from SizeF, PointF, Size, and Point objects. You can test this code on a button or a
menu click event handler.
51
52
PointF pt = new PointF(80.0f, 80.0f);
// Create a SizeF object
SizeF sz = new SizeF(100.0f, 100.0f);
// Create a rectangle from PointF and SizeF
RectangleF rect1 = new RectangleF(pt, sz);
// Create a rectangle from integers
RectangleF rect2 =
new RectangleF(x, y, width, height);
// Create a rectangle from direct integers
RectangleF rect3 =
new RectangleF(10.0f, 10.0f, 180.0f, 180.0f);
// Create pens and brushes
Pen redPen = new Pen([Link], 2);
SolidBrush greenBrush =
new SolidBrush([Link]);
SolidBrush blueBrush =
new SolidBrush([Link]);
// Draw and fill rectangles
[Link](redPen, rect3.X, rect3.Y,
[Link], [Link]);
[Link](blueBrush, rect2);
[Link](greenBrush, rect1);
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
Figure 2.13 shows the output from Listing 2.9: three different rectangles, as in Figure 2.12.
Method
Description
Ceiling
Converts a RectangleF object to a Rectangle object by rounding the RectangleF values to the next higher
integer values.
Contains
52
53
Determines if the specified point is contained within the rectangular region of a rectangle.
FromLTRB
Inflate
Intersect
IntersectsWith
Offset
Round
Converts a RectangleF object to a Rectangle object by rounding the RectangleF values to the nearest
integer values.
Truncate
Union
Like the Point and PointF structures, Rectangle and RectangleF define Ceiling, Round, and Truncate
methods. These methods are described in Table 2.4. Listing 2.10 shows how to use these methods.
Listing 2.10 Using the Round, Truncate, Union, Inflate, Ceiling, and Intersect methods of Rectangle
53
54
// Create a Size object
Size inflateSize = new Size(0, 40);
// Inflate rectangle
[Link](inflateSize);
// Draw new rectangle
[Link]([Link], isectRect);
// Set Rectangle properties
rect4 = [Link];
[Link] = new Point(50, 50);
rect4.X = 30;
rect4.Y = 40;
// Union two rectangles
Rectangle unionRect =
[Link](rect4, rect5);
// Draw new rectangle
[Link]([Link], unionRect);
// Dispose of the Graphics object
[Link]();
Figure 2.14. Using the Round, Truncate, Union, Inflate, Ceiling, and Intersect methods of Rectangle
The Size and SizeF structures represent the size of a rectangular area. Like Point/PointF and
Rectangle/RectangleF, Size and SizeF also each have an Empty static field, which creates a Size object
with zero height and zero width. The only difference between Size and SizeF is that Size uses integer values
and SizeF uses floating point values.
You can create Size and SizeF objects by passing the width and height of the Point and PointF objects as
constructor arguments, respectively. Listing 2.11 shows different ways to create Size and SizeF objects.
54
55
The Height and Width properties represent the height and width, respectively, of the area represented by the
Size and SizeF structures. The IsEmpty property returns true if Size has zero height and zero width;
otherwise it returns false.
Like the Point/PointF and Rectangle/RectangleF structures, Size and SizeF have Ceiling, Truncate, and
Round static methods. Each method can convert a SizeF object to a Size object: the Ceiling method, by
rounding the values of the Size structure to the next higher integer values; the Round method, by rounding the
values of the Size structure to the nearest integer values; and the Truncate method, by truncating the values
to the next lower integer values.
Listing 2.12 shows the use of the Ceiling, Round and Truncate methods. You can test this code on a button
or a menu click event handler.
Listing 2.12 Using the Ceiling, Round, and Truncate methods of Size and SizeF
[ Team LiB ]
[ Team LiB ]
SUMMARY
Before you write a graphics application, a basic understanding of drawing surfaces and coordinate systems is
a must. This chapter began with the basics of the drawing surfaces and the coordinate system, describing how
drawing surfaces and coordinate systems are represented in GDI+ and how the GDI+ coordinate system
differs from other coordinate systems.
Before using any GDI+-related classes defined in the .NET Framework library, you must reference
[Link] and its subnamespaces. In this chapter you learned how to add references to the GDI+
library and how to import the GDI+-related namespaces into your application. After adding a reference to the
GDI+ library and namespaces to the application, the next step is to get the Graphics object. There are several
ways to get a Graphics object in an application. This chapter discussed three different ways, and then showed
how to use the Graphics class methods to draw and fill lines, rectangles, and ellipses. You also learned to
dispose of objects when you're finished with them.
Finally, we covered some basic GDI+ structures including Color, Rectangle, RectangleF, Point, PointF,
Size, and SizeF describing their members and how to use them in your applications.
You should now be able to write simple graphics applications using GDI+.
Chapter 3 is all about the Graphics class and will demonstrate how quickly you can write real-world
applications. By the end of Chapter 3, you will be able to write your own 2D paint application similar to
Microsoft's PaintBrush, using your newly acquired GDI+ skills.
[ Team LiB ]
[ Team LiB ]
55
56
Specifically, in this chapter we will discuss the methods and properties of the Graphics class, and how to use
them in real-world applications, including line charts, pie charts, and our GDI+Painter application.
GDI+Painter is similar to the PaintBrush application, which allows you to draw simple graphics objects such
as lines, rectangles, and circles and save the images as bitmaps.
[ Team LiB ]
[ Team LiB ]
56
57
[ Team LiB ]
[ Team LiB ]
The draw methods of the Graphics class are used to draw lines, curves, and
outer boundaries of closed curves and images. Table 3.2 lists the draw
methods of the Graphics class.
The DrawLine method draws a line beween two points specified by a pair of
coordinates. DrawLines draws a series of lines using an array of points.
57
58
Draws a Bézier curve defined
by four Point structures.
DrawBeziers Draws a series of Bézier
splines from an array of Point
structures.
DrawClosedCurve Draws a closed cardinal spline
defined by an array of Point
structures.
DrawCurve Draws a cardinal spline
through a specified array of
Point structures.
DrawEllipse Draws an ellipse defined by a
bounding rectangle specified
by a pair of coordinates, a
height, and a width.
DrawIcon Draws an image represented by
the specified Icon object at the
specified coordinates.
DrawIconUnstretched Draws an image represented by
the specified Icon object
without scaling the image.
DrawImage Draws the specified Image
object at the specified location
and with the original size.
DrawImageUnscaled Draws the specified Image
object with its original size at
the location specified by a
coordinate pair.
DrawLine Draws a line connecting two
points specified by coordinate
pairs.
DrawLines Draws a series of line
segments that connect an array
of Point structures.
DrawPath Draws a GraphicsPath object.
DrawPie Draws a pie shape specified by
a coordinate pair, a width, a
height, and two radial lines.
DrawPolygon Draws a polygon defined by an
array of Point structures.
DrawRectangle Draws a rectangle specified by
a coordinate pair, a width, and
a height.
DrawRectangles Draws a series of rectangles
specified by an array of
Rectangle structures.
DrawString Draws the specified text string
at the specified location using
the specified Brush and Font
objects.
DrawLine has four overloaded methods. The first argument of all DrawLine methods is a Pen object, with
texture, color, and width attributes. The rest of the arguments vary. You can use two points with integer or
floating point values, or you can pass four integer or floating point values directly:
58
59
To draw a line, an application first creates a Pen object, which defines the color and width. The following line
of code creates a red pen with a width of 1:
Listing 3.1 shows how to use the different overloaded methods. We create four pens with different colors and
widths. After that we call DrawLine with different values including integer, floating point, and Point
structures to draw four different lines. Three of them start at point (20, 20).
59
60
[Link]();
}
The output from Listing 3.1 is shown in Figure 3.1. We've drawn four lines starting at point (20, 20).
Sometimes we need to draw multiple connected straight line segments. One way to do this is to call the
DrawLine method multiple times.
The Graphics class also provides the DrawLines method, which can be used to draw multiple connected
lines. This method has two overloaded forms. One takes an array of Point structure objects, and the other
takes an array of PointF structure objects:
To draw lines using DrawLines, an application first creates a Pen object, then creates an array of points, and
then calls DrawLines. The code in Listing 3.2 draws three line segments.
PointF[] ptsArray =
{
new PointF( 20.0F, 20.0F),
new PointF( 20.0F, 200.0F),
new PointF(200.0F, 200.0F),
new PointF(20.0F, 20.0F)
};
[Link](redPen, ptsArray);
60
61
The next basic drawing object is a rectangle. When you draw a rectangle through your applications, you need
to specify only the starting point, height, and width of the rectangle. GDI+ takes care of the rest.
The Graphics class provides the DrawRectangle method, which draws a rectangle specified by a starting
point, a width, and a height. The Graphics class also provides the DrawRectangles method, which draws a
series of rectangles specified by an array of Rectangle structures.
DrawRectangle has three overloaded methods. An application can use a Rectangle structure or coordinates
of integer or float types to draw a rectangle:
To draw a rectangle, an application first creates a pen and a rectangle (location, width, and height), and then it
calls DrawRectangle. Listing 3.3 draws rectangles using the different overloaded forms of DrawRectangle.
61
62
Figure 3.3 shows the output from Listing 3.3.
The DrawRectangles method draws a series of rectangles using a single-pen. It is useful when you need to
draw multiple rectangles using the same pen (if you need to draw multiple rectangles using different pens, you
must use multiple calls to DrawRectangle). A single call to DrawRectangles is faster than multiple
DrawRectangle calls. DrawRectangles takes two parameters a pen and an array of Rectangle or
RectangleF structures as shown in Listing 3.4.
Figure 3.4 shows the output from Listing 3.4. As you can see, it's easy to draw multiple rectangles using the
DrawRectangles method.
62
63
[Link] Drawing Ellipses and Circles
An ellipse is a circular boundary within a rectangle, where each opposite point has the same distance from a
fixed point, called the center of the ellipse. An ellipse within a square is called a circle. Figure 3.5 shows an
ellipse with its height, width, and center indicated.
To draw an ellipse, you need to specify the outer rectangle. GDI+ takes care of the rest. DrawEllipse draws
an ellipse defined by a rectangle specified by a pair of coordinates, a height, and a width (an ellipse with equal
height and width is a circle). DrawEllipse has four overloaded methods:
To draw an ellipse, an application creates a pen and four coordinates (or a rectangle), and then calls
DrawEllipse. Listing 3.5 draws ellipses with different options.
63
64
This section briefly discusses the drawing of text. Chapter 5 covers this topic in more detail.
The DrawString method draws a text string on a graphics surface. It has many overloaded forms. DrawString
takes arguments that identify the text, font, brush, starting location, and string format.
where string is the text that you want to draw, Font and Brush are the font and brushes used to draw the text,
and PointF is the starting point of the text.
Listing 3.6 uses the DrawString method to draw "Hello GDI+ World!" on a form.
Note
You might notice in Listing 3.6 that we create Font, SolidBrush, and Point objects directly as parameters of
the DrawString method. This method of creating objects means that we can't dispose of these objects, so
some cleanup is left for the garbage collector.
64
65
Figure 3.7. Drawing text
Now let's see another example of drawing text this time using the StringFormat class, which defines the text
format. Using StringFormat, you can set flags, alignment, trimming, and other options for the text. (Chapter
5 discusses this functionality in more detail.) Listing 3.7 shows different ways to draw text on a graphics
surface. In this example the FormatFlags property is set to [Link], which
draws vertical text.
65
66
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
}
As promised, the examples in this book not only show the use of GDI+, but also encourage you to use GDI+
practices in real-world applications, We will create one more real-world application, a line chart application.
In this example we will use all the functionality we have discussed so far. Our line chart application will draw
lines when a user clicks on a form.
We create a Windows application and add a check box and a button. Then we change the Text properties of
the button and the check box to call them Clear All and Rectangle, respectively. Then we add code to draw
two lines and some numbers (using the DrawString method). The initial screen of the line chart application
looks like Figure 3.9.
66
67
When you click on the form, the application draws a line. The first line starts from the bottom left corner,
where the values of our x- and y-axes are both 0. After a few clicks, the chart looks like Figure 3.10. Every
time you click on the form, the application draws a line from the previous point to the current point and draws
a small ellipse representing the current point.
The Clear All button removes the lines and initializes the first point to (0, 0). Now if you check the Rectangle
box and click on the form, the chart looks like Figure 3.11. When you click the left mouse button for the first
time, the application draws a line from point (0, 0) to the point where you clicked the button.
67
68
Now let's see the code. First we declare starting and ending points. These points will be used to draw a line
when you click the left mouse button. The default values of both points are shown in the following code
fragment, which represents position (0, 0) on the screen:
The next step is to draw vertical and horizontal axis lines with index numbers. We do this on the form's paint
event handler with the help of the DrawString method. Listing 3.8 provides code for the form-paint event
handler. As the listing shows, we simply draw a vertical line, a horizontal line, and the marks on these lines.
68
69
// Drawing vertical strings
StringFormat vertStrFormat = new StringFormat();
[Link] =
[Link];
[Link]("-",horzFont,horzBrush,
50, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
70, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
90, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
110, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
130, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
150, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
170, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
190, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
210, 212, vertStrFormat);
[Link]("-",horzFont,horzBrush,
230, 212, vertStrFormat);
// y-axis drawing
[Link]("100-",vertFont,vertBrush, 20,20);
[Link]("90 -",vertFont,vertBrush, 25,40);
[Link]("80 -",vertFont,vertBrush, 25,60);
[Link]("70 -",vertFont,vertBrush, 25,80);
[Link]("60 -",vertFont,vertBrush, 25,100);
[Link]("50 -",vertFont,vertBrush, 25,120);
[Link]("40 -",vertFont,vertBrush, 25,140);
[Link]("30 -",vertFont,vertBrush, 25,160);
[Link]("20 -",vertFont,vertBrush, 25,180);
[Link]("10 -",vertFont,vertBrush, 25,200);
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
}
Note
The idea in Listing 3.8 is to show an extensive use of the DrawString method. Alternatively and preferably,
you could replace DrawString with the DrawLine and/or DrawLines method.
Now on the mouse-down event handler, we draw a line from the starting point (0, 0) to the first mouse click.
We store the mouse click position as the starting point for the next line. When we click again, the new line
will be drawn from the current starting position to the point where the mouse was clicked. Listing 3.9 shows
the mouse-down click event handler. We create a new Graphics object using the CreateGraphics method.
After that we create two Pen objects. We store the previous point as the starting point and the current point as
the ending point. The X and Y properties of MouseEventArgs return the x- and y-values of the point where the
mouse was clicked.
Now we check to see if the Rectangle check box is checked. If so, we draw a rectangle to mark the connecting
point of the two lines. If not, we draw an ellipse as the connecting point.
69
70
Listing 3.9 The mouse-down event handler
The Clear All button removes all the lines by invalidating the form's client area and sets the starting and
ending points back to their initial values. Code for the Clear All button click event handler is given in Listing
3.10.
An arc is a portion of an ellipse. For example, Figure 3.12 shows an ellipse that has six arcs. An arc is defined
by a bounding rectangle (just as an ellipse), a start angle, and a sweep angle. The start angle is an angle in
degrees measured clockwise from the x-axis to the starting point of the arc. The sweep angle is an angle in
degrees measured clockwise from the startAngle parameter to the ending point of the arc. So an arc is the
portion of the perimeter of the ellipse between the start angle and the start angle plus the sweep angle.
70
71
Figure 3.12. Arcs in an ellipse
The DrawArc method draws an arc on a graphics surface. DrawArc takes a pen, a pair of coordinates, a width,
and a height. There are many DrawArc overloaded methods. An application can use a Rectangle or
RectangleF object and integer or float coordinates:
The Pen object determines the color, width, and style of the arc; Rectangle or RectangleF represents the
bounding rectangle; and the last two parameters are the start angle and sweep angle.
To draw an arc, the application creates Pen and Rectangle objects and defines start and sweep angles. Then it
calls the DrawArc method.
Let's create an application that will draw an arc to match the values of the start and sweep angles. We create a
Windows application, adding add two text boxes and a button control. The final form looks like Figure 3.13.
We define two floating variables on the class level to store the start and sweep angles:
Now let's draw an arc on the form's paint event handler. Listing 3.11 draws an arc. We first create a pen and a
rectangle, and we use them in the DrawArc method with start and sweep angles.
71
72
Listing 3.11 The paint event handler
Now we add code for the Reset Angles button. Listing 3.12 simply sets the start and sweep angles by reading
values from the text boxes and calls the Invalidate method, which forces GDI+ to call the form's paint event
handler.
Figure 3.14. The default arc, with start angle of 45 degrees and sweep angle of 90 degrees
Now let's change the start and sweep angles to 90 and 180 degrees, respectively, and click the Reset Angles
button. The new output looks like Figure 3.15.
Figure 3.15. An arc with start angle of 90 degrees and sweep angle of 180 degrees
72
73
Let's change angles one more time. This time our start angle will be 180 degrees, and the sweep angle will be
360 degrees. The new output looks like Figure 3.16.
Figure 3.16. An arc with start angle of 180 degrees and sweep angle of 360 degree
A curve is a sequence of adjoining points with a tension. The tension of a curve provides its smoothness and
removes corners. A cardinal spline is a sequence of multiple joined curves. Basically, in a curve there is no
straight line between two points. To illustrate, Figure 3.17 shows two curves.
There are two types of curves: open and closed. A closed curve is a curve whose starting point is the ending
point. A curve that is not a closed curve is called an open curve. In Figure 3.18 the first curve is an open
73
74
Programmatically, a curve is an array of connected points with a tension. A curve has a starting point and an
ending point. Between these two points can be many intermediate points. The Graphics class provides two
methods for drawing curves: DrawCurve and DrawClosedCurve. The DrawCurve method draws a curve
specified by an array of Point structures. The DrawClosedCurve draws a closed curve specified by an array of
Point structures. Both DrawCurve and DrawClosedCurve have overloaded methods.
To test the DrawCurve methods, we create a Windows application and add Listing 3.13 to the form's paint
event handler. It creates an array of points and draws a curve using the DrawCurve method.
74
75
// Draw curve
[Link](bluePen, ptsArray);
// Dispose of object
[Link]();
}
Note
Here the tension parameter determines the shape of the curve. If the value of tension is 0.0F, the method
draws a straight line between the points. The value of tension should vary between 0.0F and 1.0F.
Now let's update the example in Listing 3.13. We add a text box, a label, and a button to the form. We change
the properties of these controls, and the form looks like Figure 3.20.
75
76
Now we will update our sample code to draw a curve using the tension value entered in the text box. We add
a float type variable, tension, at the class level:
Then we update the form's paint event handler as shown in Listing 3.14. We provide tension as the third
argument to the DrawCurve method.
Now we add code for the Apply button, which simply reads the text box's value and sets it as the tension, as in
Listing 3.15.
76
77
}
If you enter "0.0" in the text box and hit Apply, the output looks like Figure 3.21, and if you enter the value
"1.0" in the text box and hit Apply, the output looks like Figure 3.22.
You can also add an offset and specify a number of segments for the curve:
The offset specifies the number of elements to skip in the array of points. The first element after the skipped
elements in the array of points becomes the starting point of the curve.
77
78
The numberOfSegments property specifies the number of segments, after the starting point, to draw in the
curve. It must be at least 1. The offset plus the number of segments must be less than the number of elements
in the array of the points.
The following method skips the first element of the array of points and starts drawing a curve from the second
point in the array, stopping after three segments:
int offset = 1;
int segments = 3;
[Link](bluePen, ptsArray,
offset, segments);
The final version of DrawCurve takes a pen, points array, offset, number of segments, and tension:
Here's an example:
int offset = 1;
int segments = 3;
[Link](bluePen, ptsArray,
offset, segments, tension);
As stated earlier, a closed curve is a curve whose starting and ending points are the same. The Graphics class
provides the DrawClosedCurve method to draw closed curves. It has the following overloaded forms:
The simplest form of DrawClosedCurve takes two parameters: a pen and an array of points. Listing 3.16
creates an array of points and a pen and calls the DrawClosedCurve method.
// Create a pen
Pen bluePen = new Pen([Link], 1);
78
79
// Create an array of points
PointF pt1 = new PointF( 40.0F, 50.0F);
PointF pt2 = new PointF(50.0F, 75.0F);
PointF pt3 = new PointF(100.0F, 115.0F);
PointF pt4 = new PointF(200.0F, 180.0F);
PointF pt5 = new PointF(200.0F, 90.0F);
PointF[] ptsArray =
{
pt1, pt2, pt3, pt4, pt5
};
// Draw curve
[Link](bluePen, ptsArray);
// Dispose of object
[Link]();
}
Figure 3.23 shows the output from Listing 3.16. The result is a closed curve.
The second form of DrawClosedCurve takes as arguments the tension of the curve and FillMode. We have
already discussed tension. FillMode specifies how the interior of a closed path is filled and clipped. The
FillMode enumeration represents the fill mode of graphics objects. It has two modes: Alternate (the default
mode) and Winding.
To determine the interiors of a closed curve in the Alternate mode, draw a line from any
arbitrary start point in the path to some point obviously outside the path. If the line crosses an
odd number of path segments, the starting point is inside the closed region and is therefore
part of the fill or clipping area. An even number of crossings means that the point is not in an
area to be filled or clipped. An open figure is filled or clipped by using a line to connect the
last point to the first point of the figure.
The Winding mode considers the direction of the path segments at each intersection. It adds
one for every clockwise intersection, and subtracts one for every counterclockwise
intersection. If the result is nonzero, the point is considered inside the fill or clip area. A zero
count means that the point lies outside the fill or clip area.
We will clarify these definitions with examples in the discussion of paths in Chapter 9.
Listing 3.17 uses DrawClosedCurve to draw a closed curve with a tension and fill mode.
79
80
Listing 3.17 Drawing a closed curve with a tension and fill mode
// Draw curve
float tension = 0.5F;
[Link](bluePen, ptsArray,
tension, [Link]);
The Bézier curve, developed by Pierre Bézier in the 1960s for CAD/CAM operations, has become one of the
most used curves in drawing. A Bézier curve is defined by four points: two endpoints and two control points.
Figure 3.24 shows an example of a Bézier curve in which A and B are the starting and ending points and C
and D are two control points.
The Graphics class provides the DrawBezier and DrawBeziers methods for drawing Bézier curves.
DrawBezier draws a Bézier curve defined by four points: the starting point, two control points, and the ending
point of the curve. The following example draws a Bézier curve with starting point (30, 20), ending point
(140, 50), and control points (80, 60) and (120, 18).
DrawBeziers draws a series of Bézier curves from an array of Point structures. To draw multiple beziers, you
need 3x + 1 points, where x is the number of Bézier segments.
Listing 3.18 draws Bézier curves using both DrawBezier and DrawBeziers.
80
81
PointF p7 = new PointF(200.0F, 200.0F);
PointF[] ptsArray =
{
p1, p2, p3, p4, p5, p6, p7
};
// Draw a Bézier
[Link](bluePen, 30, 20,
80, 60, 120, 180, 140, 50);
// Draw Béziers
[Link](redPen, ptsArray);
// Dispose of objects
[Link]();
[Link]();
}
A polygon is a closed shape with three or more straight sides. Examples of polygons include triangles and
rectangles.
The Graphics class provides a DrawPolygon method to draw polygons. DrawPolygon draws a polygon
defined by an array of points. It takes two arguments: a pen and an array of Point or PointF strucures.
To draw a polygon, an application first creates a pen and an array of points and then calls the DrawPolygon
method with these parameters. Listing 3.19 draws a polygon with five points.
81
82
PointF p5 = new PointF(200.0F, 150.0F);
PointF[] ptsArray =
{
p1, p2, p3, p4, p5
};
// Draw polygon
[Link](greenPen,ptsArray);
// Dispose of objects
[Link]();
[Link]();
}
The DrawIcon and DrawIconUnstretched methods are used to draw icons. DrawIcon draws an image
represented by a specified object at the specified coordinates stretching the image to fit, if necessary.
DrawIconUnstretched draws an image represented by an Icon object without scaling the image.
DrawIcon and DrawIconUnstretched take two arguments: an Icon object and upper left corner coordinates of
a rectangle. To draw an icon using these methods, an application first creates an icon and either a Rectangle
object or coordinates to the upper left corner at which to draw the icon.
An Icon object represents a Windows icon. An application creates an Icon object using its constructor, which
takes arguments of string, Icon, Stream, and Type. Table 3.3 describes the properties of the Icon class.
Property
Description
Handle
82
83
Height
Size
Width
Method
Description
Clone
Save
ToBitmap
Listing 3.20 draws icons. The application first creates two Icon objects, then creates a Rectangle object and
calls DrawIcon and DrawIconUnstretched.
83
84
A graphics path is a combination of multiple graphics shapes. For example, the graphics path in Figure 3.28 is
a combination of lines, an ellipse, and a rectangle.
The GraphicsPath class represents graphics paths. It provides methods to add graphics objects. For example,
the AddLine, AddRectangle, AddEllipse, AddArc, AddPolygon, AddCurve, and AddBezier methods add a
line, a rectangle, an ellipse, an arc, a polygon, a curve, and a Bézier curve, respectively.
GraphicsPath is defined in the [Link].Drawing2D namespace. You must import this namespace
using the following line:
using [Link].Drawing2D;
The Graphics class provides a DrawPath method, which draws a graphics path. It takes two arguments: Pen
and GraphicsPath.
To draw a graphics path, first we create a GraphicsPath object, then we add graphics shapes to the path by
calling its Add methods, and finally we call DrawPath. For example, the following code creates a graphics
path, adds an ellipse to the path, and draws it.
84
85
Let's add more shapes to the graph. Listing 3.21 creates a graphics path; adds some lines, an ellipse, and a
rectangle; and draws the path.
A pie is a slice of an ellipse. A pie shape also consists of two radial lines that intersect with the endpoints of
the arc. Figure 3.30 shows an ellipse with four pie shapes.
85
86
Figure 3.30. Four pie shapes of an ellipse
The Graphics class provides the DrawPie method, which draws a pie shape defined by an arc of an ellipse.
The DrawPie method takes a Pen object, a Rectangle or RectangleF object, and two radial angles.
Let's create an application that draws pie shapes. We create a Windows application and add two text boxes
and a button control to the form. The final form looks like Figure 3.31.
The Draw Pie button will draw a pie shape based on the values entered in the Start Angle and Sweep Angle
text boxes. Listing 3.22 shows the code for the Draw Pie button click event handler.
86
87
[Link]();
[Link]();
}
Now let's run the pie shape drawing application and enter values for the start and sweep angles. Figure 3.32
shows a pie for start and sweep angles of 0.0 and 90 degrees, respectively.
Figure 3.32. A pie shape with start angle of 0 degrees and sweep angle of 90 degrees
Figure 3.33 shows a pie for start and sweep angles of 45.0 and 180.0 degrees, respectively.
Figure 3.33. A pie shape with start angle of 45 degrees and sweep angle of 180 degrees
Figure 3.34 shows a pie for start and sweep angles of 90.0 and 45.0 degrees, respectively.
Figure 3.34. A pie shape with start angle of 90 degrees and sweep angle of 45 degrees
87
88
Note
The Graphics class also provides functionality for drawing images, using DrawImage and
DrawImageUnscaled. DrawImage draws an Image object with a specified size, and DrawImageUnscaled draws
an Image object without scaling it. The DrawImage method has many overloaded forms.
Note
Here we discuss simple images. Chapters 7 and 8 discuss the Image class, its members, and imaging-related
functionality in detail.
An application creates an Image object by calling the Image class's static FromFile method, which takes a file
name as an argument. After that you create the coordinates of a rectangle in which to draw the image and call
DrawImage. Listing 3.23 draws an image on the surface with a size of ClientRectangle.
try
{
// Create an image from a file
Image newImage =
[Link]("[Link]");
// Draw image
[Link](newImage,
[Link]);
[Link]();
88
89
}
catch (Exception ex)
{
[Link]([Link]());
}
}
So far we have seen only the draw methods of the Graphics class. As we discussed earlier, pens are used to
draw the outer boundary of graphics shapes, and brushes are used to fill the interior of graphics shapes. In this
section we will cover the Fill methods of the Graphics class. You can fill only certain graphics shapes;
hence there are only a few Fill methods available in the Graphics class. Table 3.5 lists them.
FillClosedCurve fills the interior of a closed curve. The first parameter of FillClosedCurve is a brush. It
can be a solid brush, a hatch brush, or a gradient brush. Brushes are discussed in more detail in Chapter 4. The
second parameter is an array of points. The third and fourth parameters are optional. The third parameter is a
fill mode, which is represented by the FillMode enumeration. The fourth and last optional parameter is the
tension of the curve, which we discussed in Section [Link].
The FillMode enumeration specifies the way the interior of a closed path is filled. It has two modes: alternate
or winding. The values for alternate and winding are Alternate and Winding, respectively. The default mode
is Alternate. The fill mode matters only if the curve intersects itself (see Section [Link]).
To fill a closed curve using FillClosedCurve, an application first creates a Brush object and an array of
points for the curve. The application can then set the fill mode and tension (which is optional) and call
FillClosedCurve.
Listing 3.24 creates an array of PointF structures and a SolidBrush object, and calls FillClosedCurve.
89
90
Listing 3.24 Using FillClosedCurve to fill a closed curve
Method
Description
FillClosedCurve
Fills the interior of a closed cardinal spline curve defined by an array of Point structures.
FillEllipse
Fills the interior of an ellipse defined by a bounding rectangle specified by a pair of coordinates, a width, and
a height.
FillPath
FillPie
Fills the interior of a pie section defined by an ellipse specified by a pair of coordinates, a width, a height, and
two radial lines.
FillPolygon
Fills the interior of a polygon defined by an array of points specified by Point structures.
FillRectangle
Fills the interior of a rectangle specified by a pair of coordinates, a width, and a height.
FillRectangles
90
91
FillRegion
FillEllipse fills the interior of an ellipse. It takes a Brush object and rectangle coordinates.
To fill an ellipse using FillEllipse, an application creates a Brush and a rectangle and calls FillEllipse.
Listing 3.25 creates three brushes and calls FillEllipse to fill an ellipse with a brush.
91
92
Figure 3.37 shows the output from Listing 3.25.
FillPath fills the interior of a graphics path. To do this, an application creates Brush and GraphicsPath
objects and then calls FillPath, which takes a brush and a graphics path as arguments. Listing 3.26 creates
GraphicsPath and SolidBrush objects and calls FillPath.
Figure 3.38 shows the output from Listing 3.26. As the figure shows, the fill method fills all the covered areas
of a graphics path.
92
93
Figure 3.38. Filling a graphics path
FillPie fills a pie section with a specified brush. It takes four parameters: a brush, the rectangle of the ellipse,
and the start and sweep angles. The following code calls FillPie.
[Link](new SolidBrush([Link]),
0.0F, 0.0F, 100, 60, 0.0F, 90.0F);
We will discuss the FillPie method in the pie chart application in Section 3.4.
FillPolygon fills a polygon with the specified brush. It takes three parameters: a brush, an array of points,
and a fill mode. The FillMode enumeration defines the fill mode of the interior of the path. It provides two fill
modes: Alternate and Winding. The default mode is Alternate.
In our application we will use a hatch brush. So far we have seen only a solid brush. A solid brush is a brush
with one color only. A hatch brush is a brush with a hatch style and two colors. These colors work together to
support the hatch style. The HatchBrush class represents a hatch brush. We will discuss hatch brushes in more
detail in Chapter 4.
The code in Listing 3.27 uses FillPolygon to fill a polygon using the Winding mode.
Graphics g = [Link] ;
// Create a solid brush
SolidBrush greenBrush =
new SolidBrush([Link]);
// Create points for polygon
PointF p1 = new PointF(40.0F, 50.0F);
PointF p2 = new PointF(60.0F, 70.0F);
PointF p3 = new PointF(80.0F, 34.0F);
PointF p4 = new PointF(120.0F, 180.0F);
93
94
PointF p5 = new PointF(200.0F, 150.0F);
PointF[] ptsArray =
{
p1, p2, p3, p4, p5
};
// Draw polygon
[Link](greenBrush, ptsArray);
// Dispose of object
[Link]();
Figure 3.39 shows the output from Listing 3.27. As you can see, the fill method fills all the areas of a polygon.
FillRectangle fills a rectangle with a brush. This method takes a brush and a rectangle as arguments.
FillRectangles fills a specified series of rectangles with a brush, and it takes a brush and an array of
rectangles. These methods also have overloaded forms with additional options. For instance, if you're using a
HatchStyle brush, you can specify background and foreground colors. Chapter 4 discusses FillRectangle
and its options in more detail.
Note
The source code in Listing 3.28 uses FillRectangle to fill two rectangles. One rectangle is filled with a hatch
brush, the other with a solid brush.
94
95
([Link],
[Link], [Link]),
rect);
[Link](blueBrush,
new Rectangle(150, 20, 50, 100));
// Dispose of object
[Link]();
}
FillRegion fills a specified region with a brush. This method takes a brush and a region as input parameters.
Listing 3.29 creates a Region object from a rectangle and calls FillRegion to fill the region.
Note
The Graphics class provides more than just draw and fill methods. Miscellaneous methods are defined in
Table 3.6. Some of these methods are discussed in more detail later.
95
96
[Link] The Clear Method
The Clear method clears the entire drawing surface and fills it with the specified background color. It takes
one argument, of type Color. To clear a form, an application passes the form's background color. The
following code snippet uses the Clear method to clear a form.
[Link] g = [Link]();
[Link]([Link]);
[Link]();
Method
Description
AddMetafileComment
Clear
Clears the entire drawing surface and fills it with the specified background color.
ExcludeClip
Updates the clip region to exclude the area specified by a Rectangle structure.
Flush
Forces execution of all pending graphics operations and returns immediately without waiting for the
operations to finish.
FromHdc
FromHwnd
FromImage
GetHalftonePalette
GetHdc
96
97
GetNearestColor
IntersectClip
Updates the clip region of a Graphics object to the intersection of the current clip region and a Rectangle
structure.
IsVisible
MeasureCharacterRanges
Returns an array of Region objects, each of which bounds a range of character positions within a string.
MeasureString
MultiplyTransform
ReleaseHdc
Releases a device context handle obtained by a previous call to the GetHdc method.
ResetClip
ResetTransform
Restore
Restores the state of a Graphics object to the state represented by a GraphicsState object. Takes
GraphicsState as input, removes the information block from the stack, and restores the Graphics object to
the state it was in when it was saved.
RotateTransform
Save
Saves the information block of a Graphics object. The information block stores the state of the Graphics
object. The Save method returns a GraphicsState object that identifies the information block.
ScaleTransform
SetClip
97
98
TransformPoints
Transforms an array of points from one coordinate space to another using the current world and page
transformations.
TranslateClip
Translates the clipping region by specified amounts in the horizontal and vertical directions.
TranslateTransform
MeasureString measures a string when it is drawn with a Font object and returns the size of the string as a
SizeF object. You can use SizeF to find out the height and width of string.
MeasureString can also be used to find the total number of characters and lines in a string. It has seven
overloaded methods. It takes two required parameters: the string and font to measure. Optional parameters
you can pass include the width of the string in pixels, maximum layout area of the text, string format, and
combinations of these parameters.
Note
Listing 3.30 uses the MeasureString method to measure a string's height and width and draws a rectangle and
a circle around the string. This example also shows how to find the total number of lines and characters of a
string.
Graphics g = [Link]([Link]);
[Link]([Link]);
98
99
out nChars, out nLines);
stringDetails = "Height: "+[Link]()
+ ", Width: "+[Link]()
+ ", Lines: "+[Link]()
+ ", Chars: "+[Link]();
[Link]("Second string details: "+ stringDetails);
As we discussed earlier, an application can use Graphics class members to get a Graphics object. The
Graphics class provides three methods to create a Graphics object: FromHwnd, FromHdc, and FromImage.
FromImage takes an Image object as input parameter and returns a Graphics object. We will discuss
FromImage in more detail in Chapters 7 and 8. The following code snippet creates a Graphics object from an
Image object. Once a Graphics object has been created, you can call its members.
Note
Make sure you call the Dispose method of the Graphics object when you're finished with it.
99
100
FromHdc creates a Graphics object from a window handle to a device context. The following code snippet
shows an example in which FromHdc takes one parameter, of type IntPtr.
Note
You need to call the ReleaseHdc method to release resources allocated by a window handle to a device
context, and also make sure you call the Dispose method of the Graphics object when you're finished with it.
FromHwnd returns a Graphics object for a form. The following method takes a window handle.
Graphics g = [Link]([Link]);
To draw on a form, an application can pass this handle. Once an application has a Graphics object, it can call
any Graphics class method to draw graphics objects.
[ Team LiB ]
[ Team LiB ]
The program is a Windows Forms application and looks like Figure 3.42. It has three draw buttons (line,
ellipse, and rectangle) and two fill buttons (rectangle and ellipse). The Save Image button allows you to save
the image.
100
101
Click on a button and the program draws the selected item on the form. Here's how it works:
// Variables
private Bitmap bitmap = null;
private Bitmap curBitmap = null;
private bool dragMode = false;
private int drawIndex = 1;
private int curX, curY, x, y;
private int diffX, diffY;
private Graphics curGraphics;
private Pen curPen;
private SolidBrush curBrush;
private Size fullSize;
Note
The next step is to initialize objects. On the form-load event handler, we create a bitmap and a Graphics
object from the bitmap, which represents the entire form. We set its background color to the form's
background color by calling the [Link] method. We also create a Pen object and a Brush object
when the form loads. Listing 3.31 gives the form-load event handler code.
101
102
[Link]);
// Create a Graphics object from Bitmap
curGraphics = [Link](bitmap);
// Set background color as form's color
[Link]([Link]);
// Create a new pen and brush as
// default pen and brush
curPen = new Pen([Link]);
curBrush = new SolidBrush([Link]);
}
When we click on a button, we find out which button was selected and save it in the drawIndex variable.
Listing 3.32 gives code for the button click event handler for all buttons.
When we start drawing on the form, we save the starting point on the mouse-down events and the ending
point on the mouse-up events (see Listing 3.33). From these two points we can determine the area of the
rectangle we're trying to draw. We use this rectangle in draw and fill methods.
On a mouse-move event, we calculate the difference between the ending and starting points that are used to
draw the rectangle. Notice also that on mouse down we set dragMode to true, and on mouse up we set
dragMode to false. On the basis of the area covered by user selection, we draw or fill objects on mouse up,
which gives the user a visible drawing effect. You will also see the RefreshFormBackground method, which
we will discuss shortly.
102
103
[Link] e)
{
// Find out the ending point of
// the rectangle and calculate the
// difference between starting and ending
// points to find out the height and width
// of the rectangle
x = e.X;
y = e.Y;
diffX = e.X - curX;
diffY = e.Y - curY;
// If dragMode is true, call refresh
// to force the window to repaint
if (dragMode)
{
[Link]();
}
}
103
104
Now we add code to the form's paint event handler, which draws and fills the object. Listing 3.34 gives the
code for the OnPaint method.
Graphics g = [Link];
// If dragMode is true, draw the selected
// graphics shape
if (dragMode)
{
switch (drawIndex)
{
case 1:
{
[Link](curPen, curX, curY, x, y);
break;
}
case 2:
{
[Link](curPen,
curX, curY, diffX, diffY);
break;
}
case 3:
{
[Link](curPen,
curX, curY, diffX, diffY);
break;
}
case 4:
{
[Link](curBrush,
curX, curY, diffX, diffY);
break;
}
case 5:
{
[Link](curBrush,
curX, curY, diffX, diffY);
break;
}
}
}
}
Here's a little trick. You may have noticed that we used the RefreshFormBackground method. This method
sets the current drawing as the background of the form. Listing 3.35 gives code for the method.
The Save Image button allows us to save the image by simply calling the Save method of Bitmap. The Save
method takes a file name and format. We use SaveFileDialog to select the file name. Listing 3.36 gives code
104
105
In the end we release all objects, which we can do on the form-closed event (see Listing 3.37).
In Chapter 4 we will add functionality to select different pens and brushes to draw and fill graphics shapes.
[ Team LiB ]
[ Team LiB ]
First we create a Windows application and add four buttons, a text box, and a list box control. We change the
text and names of the text box, and our final form looks like Figure 3.43. In the Enter Share text box we will
enter a number to represent the share of total items. For example, add five values in the share box: 10, 20, 30,
40, 50. The total is 150. The percentage of the share with value 10 is 10/150.
105
106
Listing 3.38 adds variables. You may notice the structure sliceData, which has two public variables: share
and clr. The share variable represents the share of a slice, and clr is its color.
// User-defined variables
private Rectangle rect =
new Rectangle(250, 150, 200, 200);
public ArrayList sliceList = new ArrayList();
struct sliceData
{
public int share;
public Color clr;
};
private Color curClr = [Link];
int shareTotal = 0;
The Select Color button allows us to select the color for a share. As Listing 3.39 shows, we use ColorDialog
to select a color.
The Add Slice button adds the data to an array to be added to the list for calculation. As Listing 3.40 shows,
all data is added to an array. This code also adds the entered data to the ListBox control.
106
107
shareTotal += slice;
sliceData dt;
[Link] = curClr;
[Link] = slice;
[Link](dt);
[Link](
"Share:"+[Link]()+" ," + [Link]() );
}
The Draw Chart and Fill Chart button clicks are used to draw the outer boundary and fill the chart,
respectively. These buttons call the DrawPieChart method with a Boolean variable, as shown in Listing 3.41.
Listing 3.41 The Draw Pie and Fill Pie button click handlers
The DrawPieChart method actually draws the pie chart, as shown in Listing 3.42. Depending on which
button Fill Chart or Draw Chart was clicked, we call FillPie or DrawPie, respectively. We also read each
sliceData variable of the array and calculate the percentage of a share in the entire chart, represented by an
angle.
Let's see this application in action. We add shares 10, 20, 30, 40, and 50 with different colors. The Draw Chart
button click draws a pie chart, with the output shown in Figure 3.44.
107
108
The Fill Chart button fills the chart, with the output shown in Figure 3.45.
[ Team LiB ]
[ Team LiB ]
SUMMARY
In this chapter we have seen that the Graphics object plays a major role in drawing and represents a canvas to
draw graphics curves, shapes, and images.
We started this chapter by discussing the Graphics class properties. Then we discussed various Graphics
class methods, which are divided into three categories: draw, fill, and miscellaneous. We saw how to use the
draw methods to draw lines, rectangles, ellipses, curves, images, paths, and other graphics objects. We also
108
109
discussed differences between the draw and fill methods and how to use the fill methods to fill rectangles,
ellipses, curves, and graphics paths. We then discussed miscellaneous methods, covering the Clear,
MeasureString, FromImage, FromHdc, and FromHwnd methods.
This chapter also presented a couple of real-world applications, showing how to write an application to draw
line and pie charts. We also used various methods and properties of the Graphics class to write a
PaintBrush-like application, GDI+Painter. Using this application, you can draw lines, rectangles, and ellipses
and save the resulting image as a bitmap file.
Having completed this chapter, you should have a good understanding of the Graphics class, its methods and
properties, and how to use those methods and properties to write real-world applications.
Pens and brushes are two of the most frequently used objects in the graphics world. In this chapter we
discussed pens and brushes briefly. Chapter 4 is dedicated to pens and brushes. You will learn how to create
different kinds of pens and brushes to write interactive graphics applications. At the end of Chapter 4 we will
add different pen and brush options to GDI+Painter, making it more interactive.
[ Team LiB ]
[ Team LiB ]
We begin by discussing brushes, brush types, their methods and properties, and how to create and use them in
GDI+.
GDI+ provides the Pen and Pens classes to represent pens. In this chapter we will discuss how to create
different kinds of pens using the Pen class and its properties, and how to use the Pen class methods. We will
also discuss how to add line caps, dash caps, line dash styles, and line cap styles. In Sections 4.3 and 4.4 we
will discuss the transformation of pens and brushes.
The SystemPens and SystemBrushes classes represent the system pens and brushes, respectively. In Section
4.5 we will discuss how to use these classes to work with system pens and brushes.
At the end of this chapter we will add color, pen, and brush options to the GDI+Painter application that we
created in Chapter 3.
[ Team LiB ]
[ Team LiB ]
109
110
namespace; and the HatchBrush and GradientBrush classes are defined in the
[Link].Drawing2D namespace.
Before using brushes, obviously you must include the corresponding namespace to
your application. Alternatively, you can use the namespace as a prefix to the class;
for example, [Link] represents the Brush class if you do not
wish to include the [Link] namespace in your application.
The code snippet in Listing 4.1 creates a red SolidBrush object and uses it to
draw a rectangle. This code is written on a form's paint event handler. The first
line gets the Graphics object of the form, and the second line creates a brush
using the SolidBrush class, which later is used to fill a rectangle. The last line
disposes of the SolidBrush object.
Graphics g = [Link];
SolidBrush redBrush = new SolidBrush([Link]);
Rectangle rect = new Rectangle(150, 80, 200, 140);
[Link](redBrush, rect);
[Link]();
In the .NET Framework library, the Brush class is an abstract base class, which
means you cannot create an instance of it without using its derived classes. All
usable classes are inherited from the abstract Brush class. Figure 4.1 shows all the
Brush-derived classes that can be used in your GDI+ applications.
Applications generally call fill methods of the appropriate Graphics class, which
in turn use brushes to fill GDI+ objects (such as an ellipse, an arc, or a polygon)
with a certain kind of brush. GDI+ provides four different kinds of brushes: solid,
hatch, texture, and gradient. Figure 4.2 shows the brush types and their classes.
110
111
The Brushes class is a sealed class (it cannot be inherited). Brushes provides
more than 140 static members (properties), and each of these members represents
a brush with a particular color (including all the standard colors). For instance, the
[Link], [Link], and [Link] members represent Brush
objects with the colors pink, red, and green, respectively.
A solid brush is a brush that fills an area with a single solid color. We create a
SolidBrush object by calling its constructor and passing a Color structure as the
only parameter. The Color structure represents a color. It has a static property for
every possible color. For example, [Link] represents the color red. The code
snippet in Listing 4.2 creates three SolidBrush objects with three different colors:
red, green, and blue.
SolidBrush has only one property of interest: Color, which represents the color
of the brush.
Listing 4.3 uses red, green, and blue solid brushes and fills an ellipse, a pie, and a
rectangle using the FillEllipse, FillPie, and FillRectangle methods of the
Graphics class, respectively.
Graphics g = [Link];
// Create three SolidBrush objects
// using the colors red, green, and blue
SolidBrush redBrush = new SolidBrush([Link]);
SolidBrush greenBrush = new SolidBrush([Link]);
SolidBrush blueBrush = new SolidBrush([Link]);
// Fill ellipse using red brush
111
112
[Link](redBrush, 20, 40, 100, 120);
// Fill rectangle using blue brush
Rectangle rect = new Rectangle(150, 80, 200, 140);
[Link](blueBrush, rect);
// Fill pie using green brush
[Link](greenBrush,
40, 20, 200, 40, 0.0f, 60.0f );
// Dispose of objects
[Link]();
[Link]();
[Link]();
}
The output of Listing 4.3 draws an ellipse, a rectangle, and a pie, as Figure 4.3
shows.
Hatch brushes are brushes with a hatch style, a foreground color, and a
background color. Hatches are a combination of rectangle lines and the area
between the lines. The foreground color defines the color of lines; the background
color defines the color between lines.
The HatchBrush class constructor takes HatchStyle as its first parameter and
Color as the second parameter. Second and third Color parameters represent the
foreground and background colors. The following code snippet shows the
constructor signatures:
Note
112
113
The following code creates a hatch brush with a dashed-vertical hatch style, blue
background, and red foreground:
We can use this hatch brush to fill graphics objects such as rectangles or ellipses.
For example, the following code line fills an ellipse using hBrush1:
HatchStyle returns the hatch brush style of type HatchStyle enumeration, whose
members are described in Table 4.1.
Let's create a Windows application that looks like Figure 4.4. The combo box will
list some of the available hatch styles. The Pick... buttons let you select
background and foreground colors of the hatch brush, and the Apply Style button
creates a hatch brush based on the selection and uses it to draw a rectangle.
113
114
First we add one HatchStyle-type and two Color-type class-level variables that
represent the current selected hatch style, foreground, and background color of a
hatch brush, respectively. These variables are defined as follows:
114
115
DottedDiamond Forward diagonal and
backward diagonal lines,
each of which is composed
of dots that cross.
DottedGrid Horizontal and vertical
lines, each of which is
composed of dots that
cross.
ForwardDiagonal A pattern of lines on a
diagonal from upper left to
lower right.
Horizontal A pattern of horizontal
lines.
HorizontalBrick A hatch with the
appearance of horizontally
layered bricks.
LargeCheckerBoard A hatch with the
appearance of a
checker-board with squares
that are twice the size of
SmallCheckerBoard.
LargeConfetti A hatch with the
appearance of confetti that
is composed of larger
pieces than
SmallConfetti.
LargeGrid Horizontal and vertical
lines that cross and are
spaced 50 percent farther
apart than in Cross.
LightDownwardDiagonal Diagonal lines that slant to
the right from top points to
bottom points.
LightHorizontal Horizontal lines that are
spaced 50 percent closer
together than Horizontal
lines.
LightUpwardDiagonal Diagonal lines that slant to
the left from top points to
bottom points and are
spaced 50 percent closer
together than
BackwardDiagonal lines.
LightVertical Vertical lines that are
spaced 50 percent closer
together than Vertical
lines.
Max Hatch style SolidDiamond.
Min Hatch style Horizontal.
NarrowHorizontal Horizontal lines that are
spaced 75 percent closer
together than Horizontal
lines (or 25 percent closer
together than
115
116
LightHorizontal lines).
NarrowVertical Vertical lines that are
spaced 75 percent closer
together than Vertical
lines (or 25 percent closer
together than
LightVertical lines).
OutlinedDiamond Forward diagonal and
backward diagonal lines
that cross.
PercentXX Percent hatch. The "XX"
number after "Percent"
represents the ratio of
foreground color to
background color as
XX:100. The values of XX
are 05, 10, 20, 25, 30, 40,
50, 60, 70, 75, 80, and 90.
Plaid A hatch with the
appearance of a plaid
material.
Shingle A hatch with the
appearance of diagonally
layered shingles that slant
to the right from top points
to bottom points.
SmallCheckerBoard A hatch with the
appearance of a
checkerboard.
SmallConfetti A hatch with the
appearance of confetti.
SmallGrid Horizontal and vertical
lines that cross and are
spaced 50 percent closer
together than Cross lines.
SolidDiamond A hatch with the
appearance of a
checkerboard placed
diagonally.
Sphere A hatch with the
appearance of spheres laid
adjacent to one another.
Trellis A hatch with the
appearance of a trellis.
Vertical A pattern of vertical lines.
Wave Horizontal lines that are
composed of tildes.
Weave A hatch with the
appearance of a woven
material.
WideDownwardDiagonal Diagonal lines that slant to
the right from top points to
bottom points, have the
same spacing as in
116
117
ForwardDiagonal, and are
triple the width of
ForwardDiagonal lines.
WideUpwardDiagonal Diagonal lines that slant to
the left from top points to
bottom points, have the
same spacing as in
BackwardDiagonal, and
are triple the width of
BackwardDiagonal lines.
ZigZag Horizontal lines that are
composed of zigzags.
On the form's load event handler (see Listing 4.4), we fill the combo box with different hatch styles and set
the background color properties of our two text boxes to the current colors.
The FillHatchStyles method adds different styles to the combo box (see Listing 4.5). We have added only a
few styles; many more are available (see Table 4.1).
117
118
The Pick... buttons in our combo box (see Figure 4.4) call the ColorDialog method and save the selected
foreground and background colors, respectively. These methods also set the background color of the
respective text boxes, as Listing 4.6 shows.
The last step is to apply the selected styles and colors, create a hatch brush, and use this brush to draw a
rectangle. This is all done on the Apply Style button click event handler, which is shown in Listing 4.7. As
you can see from this listing, first we create a HatchStyle object based on the user selection in the combo
box. Then we create a HatchBrush object using the hatch style, background, and foreground colors. After that
we simply fill a rectangle with the hatch brush.
118
119
break;
case "HorizontalBrick":
style = [Link];
break;
case "LightDownwardDiagonal":
style = [Link];
break;
case "LightUpwardDiagonal":
style = [Link];
break;
default:
break;
}
// Create a hatch brush with selected
// hatch style and colors
HatchBrush brush =
new HatchBrush(style, forClr, backClr);
// Fill rectangle
[Link](brush, 50, 100, 200, 200);
// Dispose of objects
[Link]();
[Link]();
}
If you compile and run the application and then click the Apply Style button, the default rectangle looks like
Figure 4.5.
Let's select LightDownwardDiagonal for the hatch style, change the foreground and background colors, and
click the Apply Style button. Now the output looks like Figure 4.6.
119
120
Let's change the hatch style and colors one more time. This time we pick DiagonalCross as our hatch style.
Now the output looks like Figure 4.7.
Texture brushes allow us to use an image as a brush and fill GDI+ objects with the brush. Texture brushes are
useful when you need to fill a graphics object with images in a pattern such as tile. In this section we will
discuss how to create and use texture brushes in GDI+.
In the .NET Framework library, the TextureBrush class represents a texture brush. Table 4.2 describes the
properties of the TextureBrush class.
Let's create an application using texture brushes. We create a Windows application. We also add a context
menu to the form, along with five context menu items. The final form looks like Figure 4.8.
120
121
Figure 4.8. The texture brush application
Property
Description
Image
Transform
Represents a Matrix object that defines a local geometric transformation for the image.
WrapMode
Represents a WrapMode enumeration that indicates the wrap mode for a texture brush.
Note
The WrapMode enumeration represents the wrap mode for a texture brush. It has five members: Clamp, Tile,
TileFlipX, TileFlipY, and TileFlipXY. These members are described later, in Table 4.7.
The next step is to create a texture brush from an image and fill a rectangle with that brush. We create an
Image object on the form's load event handler from the file [Link], which is used to create a
TextureBrush object. On the form's paint event handler, we call the FillRectangle method to fill the
121
122
rectangle with the texture. Listing 4.8 shows the form's load and paint event handler. Note that our rectangle is
the ClientRectangle of the form.
Note
Now we can add event handlers for the context menu items as shown in Listing 4.9. As you can see from this
code, we simply set the WrapMode property of the texture brush.
122
123
private void TileFlipXY_Click(object sender,
[Link] e)
{
[Link] = [Link];
[Link]();
}
Finally, we need to load the context menu on the right mouse click event handler. As Listing 4.10 shows, we
simply set the ContextMenu property of the form.
Now let's run the application. Figure 4.9 shows default (tiled) output from the program. The entire client
rectangle is filled with the texture.
If we right-click on the form and select the Clamp menu item, we get Figure 4.10.
123
124
Now let's select the TileFlipY option, which generates Figure 4.11. You can try other options on your own!
Linear gradient brushes allow you to blend two colors together, generating an indefinite range of shades. The
Blend class defines a custom falloff for the gradient.
Note
Chapter 9 discusses the Blend class and alpha blending in more detail.
In a gradient, we begin with a starting color and shift to an ending color, with gradual blending in the space
between them. In addition to the starting and ending colors, we can specify the direction of the gradient. For
example, Figure 4.12 starts with green in the left bottom corner and ends with red in the top right corner. (You
may not notice these colors exactly in a black-and-white image.)
124
125
Figure 4.12. A color gradient
You can also specify a range for pattern repetition. For example, you can specify that the gradient will occur
from point (0, 0) to point (20, 20) and after that will repeat the same pattern, as in Figure 4.13.
The LinearGradientBrush class has eight forms of overloaded constructors. Each constructor takes a starting
point, an ending point, and two gradient colors. The orientation and linear gradient mode are optional.
The following code snippet creates a linear gradient brush using the colors red and green:
Here the mode parameter is represented by the LinearGradientMode enumeration, which specifies the
direction of a linear gradient. The members of the LinearGradientMode enumeration are described in Table
4.3.
Now let's look at the properties and methods of the LinearGradient-Brush class, which are defined in Tables
4.4 and 4.5, respectively.
125
126
Note
Now let's create an application that uses linear gradient brushes. We create a Windows application, add three
label controls, a combo box, two text boxes, four buttons, and two check boxes. We also change the Text
property and other properties of these controls. The final form looks like Figure 4.14.
The combo box will list the linear gradient modes. The Pick... buttons allow the user to pick starting and
ending colors for the gradient process. The Other Rectangle check box uses a rectangle to specify the range of
the gradient. We will discuss the Gamma Correction and Properties options later in this section.
Member
Description
BackwardDiagonal
ForwardDiagonal
Horizontal
126
127
Vertical
Property
Description
Blend
Represents the Blend object that specifies gradient position and factors.
GammaCorrection
InterpolationColors
LinearColors
Rectangle
Returns a rectangle that defines the starting and ending points of a gradient.
Transform
WrapMode
Method
Description
MultiplyTransform
ResetTransform
RotateTransform
127
128
Rotates the transformation.
ScaleTransform
SetSigmaBellShape
TranslateTransform
After defining the variables, we add the code from Listing 4.11 on the form's load event handler. As the code
shows, we add all gradient modes on the AddGradientMode method. We also set the default background color
of text boxes.
Next we add code for the Pick... buttons, which allow the user to provide color selections for the starting and
ending colors. We also set the color of relative text boxes, as shown in Listing 4.12.
128
129
ColorDialog clrDlg = new ColorDialog();
if ([Link]() == [Link])
{
// Save color as foreground color,
// and fill text box with this color
startColor = [Link];
[Link] = startColor;
}
}
The last step is to write code for the Apply Settings button. This button reads various settings, including the
selected gradient mode in the combo box, the starting and ending colors, another rectangle, and gamma
correction. As Listing 4.13 shows, the code creates a linear gradient brush using a rectangle, two colors, and
the gradient mode selection. After creating the brush, it calls the FillRectangle method.
129
130
(rect1, startColor, endColor, mode);
}
else
{
lgBrush = new LinearGradientBrush
(rect, startColor, endColor, mode);
}
// Gamma correction check box is checked
if([Link])
{
[Link] = true;
}
// Fill rectangle
[Link](lgBrush, rect);
// Dispose of objects
if(lgBrush != null)
[Link]();
[Link]();
}
When you run the application, the result looks like Figure 4.15.
To generate a different output, let's change the linear gradient mode to Vertical. We'll also change the
colors, with the results shown in Figure 4.16.
130
131
Let's change the colors and gradient mode again, this time selecting the Other Rectangle check box. This
option sets a range of the gradient. If the output is out of range, the gradient repeats itself. The new output
looks like Figure 4.17.
Gamma Correction
Gamma correction is a process that controls the brightness of images and graphics objects. Some
graphics objects that are not properly corrected after color processing can look too dark or
bleached out. Gamma correction helps correct this problem by managing the ratio of red, green,
and blue components.
131
132
You can also use the LinearGradientBrush class properties and methods to change brush properties
programmatically. Listing 4.14 creates a linear gradient brush from two points (starting point and ending
point), and sets the LinearColors and GammaCorrection properties. The correction provides more uniform
intensity in the gradient. We write this code on the Properties button click event handler.
Figure 4.18 shows the output from the Properties button click.
132
133
[Link] Path Gradient Brushes
A graphics path is a collection of lines and curves. In GDI+, the PathGradientBrush object fills a graphics
paths with a gradient. Like LinearGradientBrush, PathGradientBrush is a combination of two colors, but
instead of starting with one color and ending with another, PathGradientBrush starts from the center of a
graphics path and ends at the outside boundary of the path. In between, you can apply blend factors, positions,
and style effects using the PathGradientBrush class members.
We will discuss PathGradientBrush, its properties, and its methods in more detail in Chapter 9 (Section 9.5).
Property
Description
Blend
A Blend object specifies the positions and factors that define a custom falloff point for a gradient. The Blend
property takes a Blend object.
CenterColor
CenterPoint
FocusScales
InterpolationColors
A ColorBlend object defines a multicolor linear gradient, and this property can be used to set a ColorBlend
object for the brush.
Rectangle
Represents a bounding rectangle for the brush. Outside of this boundary, the brush pattern repeats itself.
SurroundColors
133
134
Transform
WrapMode
Defines how a texture or gradient is tiled when it is larger than the area being filled, using a WrapMode
enumeration.
Member
Description
Clamp
Tile
TileFlipX
TileFlipXY
Reverses the texture or gradient horizontally and vertically and then tiles it.
TileFlipY
[ Team LiB ]
[ Team LiB ]
In this section we will discuss how to create and use pens in GDI+; the Pen and
Pens classes; and how to create dash styles, cap styles, and line styles for pens.
134
135
The Pen class represents a pen in GDI+. Using the Pen class constructor, an
application can create a Pen object from a Brush or Color object with a specified
width for the pen.
Listing 4.15 creates pens using Brush and Color objects with and without a
specified width.
Listing 4.15 Using the Pen class constructor to create Pen objects
135
136
The Pens class has static properties for all standard colors, which return
appropriately colored Pen objects. The following code snippet creates three Pen
objects using the Pens class.
The Pen class provides properties to set brush, color, and width programmatically
after a Pen object is created. Table 4.8 describes the properties of the Pen class.
A pen can draw solid lines, filled lines, texture, and even gradient lines all
depending on the brush you use to create the pen. For example, if you use a texture
brush to create a pen and then use this pen to create lines, the lines will be texture
lines.
The only way to set a pen's type is to create a brush and use that brush to create the
pen. The PenType property of the Pen class represents the type of the pen's lines.
This property is represented by the PenType enumeration.
Note
136
137
4.2.4 Pens Example
Now let's create a sample application. In Listing 4.16 we create three pens from
three different brushes: a solid brush, a texture brush, and a linear gradient brush.
After that we create three pens from these brushes, and then we read the type of
each pen and display the types in a message box.
137
138
corner.
PenType The style of lines of a
pen. This property is
represented by the
PenType enumeration.
Transform The geometric
transformation of a pen.
Width The width of a pen.
Property
Description
Clone
MultiplyTransform
ResetTransform
RotateTransform
ScaleTransform
SetLineCap
Sets the values that determine the style of cap used to end lines drawn by a pen.
TranslateTransform
Member
Description
HatchFill
A hatch fill
LinearGradient
138
139
A linear gradient fill
PathGradient
SolidColor
A solid fill
TextureFill
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
}
139
140
Figure 4.20. Displaying pen types
The alignment of a pen represents its position respective to a line. The PenAlignment enumeration specifies
the alignment of a pen meaning the center point of the pen width relative to the line. Table 4.11 describes the
members of the PenAlignment enumeration.
To see alignment in action, let's create a sample application. We create a Windows application, and add a
combo box, three labels, two buttons, and a numeric up-down control. We change the control properties, and
the final form looks like Figure 4.21.
140
141
Table 4.11. PenAlignment members
Member
Description
Center
Inset
Left
Outset
Right
The Pen Alignment combo box lists the alignments of a pen. Pen Width represents the width of the pen, and
Pen Color lets you pick the color of the pen. The Pen Color button click event handler simply sets the color of
the pen and stores the selected color in a Color type variable at the class level, as shown in Listing 4.17.
Listing 4.18 (on the form's load event handler) loads all alignments to the combo box.
}
private void AddPenAlignments()
{
141
142
// Add pen alignment
[Link]([Link]);
[Link] =
[Link]();
[Link]([Link]);
[Link]([Link]);
[Link]([Link]);
[Link]([Link]);
}
Finally, in Listing 4.19 we write code for the Draw Graphics button click event handler. We set the Width and
Color properties of the pen after reading values from the form's controls. Then we look for the current
alignment set by the user in the combo box and set the Alignment property of the pen. In the end, we use this
pen to draw a rectangle. We also fill one more rectangle with a linear gradient brush.
Figure 4.22 shows the output from Listing 4.19. The pen width is 10 and alignment is center.
142
143
Figure 4.22. Drawing with center pen alignment
Pens offer more options than what we have seen so far. A line's caps are the starting and ending points of the
line. For example, you may have seen lines with arrows and circles. Figure 4.24 shows some lines with their
cap and dash styles.
143
144
Using Pen properties and methods, you can draw lines with cap and dash styles. Here we will discuss line cap
and line dash styles only briefly (for more details, see Chapter 9).
Note
We can divide line caps into two types: anchor and nonanchor. The width of an anchor cap is bigger than the
width of the line; the width of a nonanchor cap is the same as the width of the line.
The LineCap property of the Pen class represents the cap style used at the beginning and ending of lines
drawn by the pen. You can determine the current cap style of a line by calling the GetLineCap method, which
returns a LineCap enumeration. You can also apply a line cap style using the SetLineCap method. This
method takes an argument of LineCap enumeration type. Table 4.12 describes the members of the LineCap
enumeration.
The SetLineCap method takes the line cap style for the beginning, ending, and dash cap of the line. The first
and second parameters of SetLineCap are of type LineCap. The third parameter is of type DashCap
enumeration.
Member
Description
AnchorMask
ArrowAnchor
Custom
DiamondAnchor
Flat
144
145
NoAnchor
No anchor
Round
RoundAnchor
Square
SquareAnchor
Triangle
Member
Description
Flat
Round
A circular cap
Triangle
A triangular cap
The DashCap enumeration specifies the type of graphics shape used on both ends of each dash in a dashed
line. Table 4.13 describes the members of the DashCap enumeration.
The DashStyle enumeration specifies the style of a dashed line drawn by the pen. Table 4.14 describes the
members of the DashStyle enumeration.
Member
Description
Custom
145
146
Dash
DashDot
DashDotDot
Dot
Solid
A solid line
Listing 4.20 shows how to use various styles and properties of the Pen class to draw different kinds of dashed
lines with different kinds of starting and ending caps. We use the DashStyle, SetLineCap, StartCap, and
EndCap members of the Pen class to set the line dash style, line cap style, start cap style, and end cap style,
respectively.
146
147
// takes care of it for you.
[Link]();
[Link]();
[Link]();
}
4.2.7 Drawing Other Graphics Shapes by Applying Cap and Dashed Line Styles
In the previous section we saw how to draw lines using cap and dash styles. But these styles are not limited to
lines only. You can draw other graphics shapes, such as rectangles, ellipses, and curves, using the line cap and
dash styles.
As in the previous section, here we will create a pen, set its line cap and line dash styles, and use it but this
time, drawing graphics shapes, rather than simple lines.
Listing 4.21 creates several pens and uses them to draw an arc, Bézier curve, rectangle, and ellipse with the
help of the DrawArc, DrawBezier, DrawRectangle, and DrawEllipse methods of the Graphics class (see
Chapter 3 for details).
147
148
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = 3.4f;
[Link]([Link],
[Link], [Link]);
// Draw objects
[Link](redPen, 10.0F, 10.0F, 50,
100, 45.0F, 90.0F);
[Link](bluePen, 60, 80, 140, 50);
[Link](blackPen, 20.0F, 30.0F,
100.0F, 200.0F, 40.0F, 400.0F,
100.0F, 200.0F);
[Link](redPen, 50, 50, 200, 100 );
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
}
Figure 4.26 shows the output of Listing 4.21. All of the elements drawn have line cap and dash styles.
[ Team LiB ]
[ Team LiB ]
The Pen class provides methods for transformation and rotation. The RotateTransform method rotates a
transformation by an angle. This method takes a rotation angle of type float. The second argument,
MatrixOrder, is an optional parameter that provides an order for matrix transformation operations. The
MatrixOrder enumeration defines the matrix order, which has two members: Append and Prepend. The
matrix order is the order in which a matrix is multiplied with other matrices.
148
149
The difference between Append and Prepend is the order of the operation. For example, if two operations are
participating in a process, the second operation will be performed after the first when the matrix order is
Append; when the order is Prepend, the second operation will be performed before the first.
The MultiplyTransform method multiplies a transformation matrix by a pen. Its first argument is a Matrix
object, and the optional second argument is the matrix order of type MatrixOrder enumeration.
Note
The TranslateTransform method of the Pen class translates a transformation by the specified dimension.
This method takes two float type values for translation in x and y, and an optional third parameter of type
MatrixOrder.
Listing 4.22 uses the ScaleTransform and RotateTransform methods to apply rotation on pens and
rectangles.
Figure 4.27 shows the output from Listing 4.22. The first ellipse and rectangle are drawn normally. The
second ellipse and rectangle are drawn after rotation and scaling have been applied to their pens.
149
150
Chapter 10 discusses rotation, scaling, and other transformation methods in more detail.
Note
You need to reference the [Link].Drawing2D namespace in order to run the code in the listings of
this section because the Matrix class and the MatrixOrder enumeration are defined in this namespace.
[ Team LiB ]
[ Team LiB ]
150
151
ResetTransform Resets the Transform property of a
texture to identity.
RotateTransform Rotates the local geometric
transformation of a texture brush by the
specified amount.
ScaleTransform Scales the local geometric transformation
of a texture brush by the specified
amount.
TranslateTransform Translates the local geometric
transformation of a texture brush by the
specified dimensions in the specified
order.
Listing 4.23 uses the Translate, MultiplyTransform, ScaleTransform, and RotateTransform methods of
the Pen class to apply rotation on pens, and draws a line and rectangles.
Figure 4.28 shows the output from Listing 4.23, with the original image on the left and the transformed image
on the right.
151
152
A transformation on a linear gradient brush is a transformation of the colors of the brush. The
LinearGradientBrush class provides all common transformation methods and Transform properties. Listing
4.24 shows how to use transformation in linear gradient brushes.
Figure 4.29 shows the output from Listing 4.24. The second rectangle results from various transformation
operations, and the first rectangle is a result of a call to ResetTransform.
152
153
Figure 4.29. Transformation in linear gradient brushes
PathGradientBrush provides similar mechanisms to transform path gradient brushes. As Listing 4.25 shows,
we create a PathGradientBrush object and set its CenterColor and SurroundColors properties. Then we
create a Matrix object and call its methods to apply various transformation operations, such as translation,
rotation, scaling, and shearing, and we apply the Matrix object to the PathGradientBrush object by calling
its MultiplyTransform method.
Figure 4.30 shows the output from Listing 4.25. The original rectangle started at point (10, 10) with height
and width 200 each, but after various transformation methods have been applied, the output rectangle is totally
different.
153
154
Figure 4.30. Transformation in path gradient brushes
[ Team LiB ]
[ Team LiB ]
There are two ways to create system pens and brushes. First, you can create pens and
brushes using the SystemColors class. SystemColors represents the system colors in
GDI+, providing static properties for system colors, such as ActiveBorder and
ControlText. The second way to create system pens and brushes uses the SystemPens
and SystemBrushes classes.
For performance reasons, it is a good idea to use the SystemPens and SystemBrushes
classes rather than creating pens and brushes by using the SystemColors class.
The SystemPens class represents a pen created with the system colors. This class has a
static property for each system color that represents the system pen with that particular
color. Table 4.16 lists the properties of the SystemPens class.
The SystemPens class also provides a method FromSystemColor that creates a Pen
object from a Color structure. To create a system pen, we pass a SystemColors object.
The following code shows how to use the FromSystemColor method:
154
155
Table 4.16. SystemPens properties
Property Description
ActiveCaptionText Pen with active
window's title bar
color
Control Pen with control
color
ControlDark Pen with the shadow
color of a 3D
element.
ControlDarkDark Pen with the dark
shadow color of a 3D
element.
ControlLight Pen with the light
color of a 3D
element.
ControlLightLight Pen with the
highlight color of a
3D element.
ControlText Pen with the control
text color
GrayText Pen with disabled
color
Highlight Pen with highlighting
HighlightText Pen with highlighted
text color
InactiveCaptionText Pen with inactive title
bar color
InfoText Pen with the color of
the text of a ToolTip
MenuText Pen with the color of
a menu's text
WindowFrame Pen with the color of
a window frame
WindowText Pen with the color of
the text in the client
area of a window
Pen pn = [Link](
[Link]);
The SystemBrushes class represents a Brush object using the system colors. All properties of SystemBrushes
are static read-only properties. Table 4.17 describes these properties.
Property
Description
155
156
ActiveBorder
ActiveCaption
Brush object with the background color of the active window's title bar
ActiveCaptionText
Brush object with the color of the text in the active window's title bar
AppWorkspace
Control
ControlDark
ControlDarkDark
ControlLight
ControlLightLight
ControlText
Desktop
Highlight
HighlightText
HotTrack
InactiveBorder
156
157
InactiveCaption
Brush object with the color of the background of an inactive window's title bar
Info
Menu
ScrollBar
Window
Brush object with the color of the background in the client area of a window
WindowText
Brush object with the color of the text in the client area of a window
Note
The MSDN documentation states that the SystemBrushes properties return a SolidBrush object, but that
statement is not quite accurate. These properties return a Brush object that must be cast to a SolidBrush
object. If you run the code without casting them, the compiler throws an error.
The SystemBrushes class also provides a FromSystemColor method, which creates a Brush object from a
specified system color. The following code shows how to use the FromSystemColor method:
SolidBrush brush =
(SolidBrush)[Link]
([Link]);
Listing 4.26 uses SystemBrushes and SystemPens objects to draw two lines and a rectangle.
157
158
Listing 4.26 Using the SystemBrushes and SystemPens classes
[ Team LiB ]
[ Team LiB ]
Figure 4.32 shows the modified version of GDI+Painter without any objects..
158
159
Figure 4.32. GDI+Painter with pen and brush support
Transparency is a component of the color in GDI+. In the .NET Framework library, the Color structure
represents a color. It has four components: alpha (A), red (R), green (G), and blue (B). The alpha component
of the Color structure represents the transparency of a color. The alpha component values vary from 0 to 255,
where 0 is fully transparent and 255 is fully opaque. To create a transparent brush or pen, we create a color
using the alpha value and use the color to create a pen or a brush. We will discuss colors and alpha
transparency in more detail in Chapter 5 (ARGB is the focus of Section 5.2).
The following code snippet shows how to create a color with transparency. We use the same method to add
transparency to our application.
In our modified version of GDI+Painter, the width selector numeric up-down control allows you to select the
width of the pen. A pen is used when we draw the outlines of graphics shapes. A brush is used when we draw
filled graphics shapes.
The Pen color and Brush color buttons launch ColorDialog, which lets us select a color and set the color of
the button itself, which later is used by the program when creating a Pen or Brush object. Listing 4.27 shows
the code for these two button click event handlers. This code also sets the background color of the respective
buttons to set the current selected color of our brush and pen.
159
160
}
private void BrushSettings_Click(object sender,
[Link] e)
{
ColorDialog colorDlg = new ColorDialog();
[Link]();
[Link] = [Link];
}
When we draw a graphics shape, we set the color, width, and transparency of the pen and brush according to
the selection. The last two changes in our revised version of GDI+Painter are on the mouse-up event handler
and the form's paint event handler, respectively.
The modified mouse-up event handler is shown in Listing 4.28. In it, we use the color buttons to create our
current pen and brush from the selected colors.
diffX = x - curX;
diffY = y - curY;
switch (drawIndex)
{
case 1:
{
// Draw a line
[Link](curPen,
curX, curY, x, y);
break;
}
case 2:
{
// Draw an ellipse
[Link](curPen,
curX, curY, diffX, diffY);
break;
}
case 3:
{
// Draw a rectangle
[Link](curPen,
curX, curY, diffX, diffY);
break;
}
case 4:
{
// Fill rectangle
[Link](curBrush,
curX, curY, diffX, diffY);
break;
160
161
}
case 5:
{
// Fill ellipse
[Link](curBrush,
curX, curY, diffX, diffY);
break;
}
}
// Refresh
RefreshFormBackground();
// Set dragMode to false
dragMode = false;
}
The same procedure is applied to the form's paint event handler, shown in Listing 4.29. This code sets the
Color and Width properties of our pen and the Color property of our brush according to the current values.
Graphics g = [Link];
// If dragMode is true, draw selected
// graphics shape
if (dragMode)
{
switch (drawIndex)
{
case 1:
{
[Link](curPen, curX, curY, x, y);
break;
}
case 2:
{
[Link](curPen,
curX, curY, diffX, diffY);
break;
}
case 3:
{
[Link](curPen,
curX, curY, diffX, diffY);
break;
}
case 4:
{
161
162
[Link](curBrush,
curX, curY, diffX, diffY);
break;
}
case 5:
{
[Link](curBrush,
curX, curY, diffX, diffY);
break;
}
}
}
}
If you run the revised GDI+Painter application, you can set the colors of the brush and the pen, the pen's
width, and the transparency of both the pen and the brush. Figure 4.33 shows lines, rectangles, and ellipses
drawn with different sizes and transparency.
You can improve the functionality of the GDI+Painter application (or your own applications) even more: As
we have discussed in our examples, you can add a brush selection feature. You can allow users to select a
brush type, style, and other properties. If users pick a gradient brush, they can select colors. You can also
allow users to select cap and line styles. For solid brushes, users should be able to pick a color; for texture
brushes, they should be able to pick an image; and for hatch and gradient brushes, they should be able to pick
styles, background, foreground, and other color properties. You can even add transformation and other
options all of which we've discussed in this chapter.
On the basis of this example, you can write your own graphics tool library with support for many more
options than the standard Windows PaintBrush application!
[ Team LiB ]
[ Team LiB ]
162
163
SUMMARY
In this chapter we learned how to work with pens and brushes by using classes from the GDI+ .NET
Framework class library. The chapter began by showing how to represent various kinds of brushes in GDI+.
We learned the classes for the different brushes and how to use their properties and methods.
After covering brushes, the discussion moved on to pens and how to represent them using GDI+ classes. We
learned pen-related classes and their properties and methods, and how to add various styles to pens, such as
cap, line, and dash styles. We also discussed system pens and brushes, and how to use GDI+ classes to
represent and use system pens and brushes.
At the end of the chapter we added options for pens and brushes to the GDI+Painter application. You should
now have a pretty good idea of how to use pens and brushes in your own applications.
After pens and brushes, the next most frequently used graphics objects are text, fonts, and colors. We will
discuss these in Chapter 5.
[ Team LiB ]
[ Team LiB ]
• Basics of colors, fonts, and text and how they are represented in Windows
• Namespaces, classes, and other objects provided by the .NET Framework library to work with colors,
fonts, and text
• System fonts, colors, brushes, and pens
• Color conversions and translations
• System and private font collections
• Formatting text using hinting, tab stops, and other methods
• Setting the quality and performance of text rendering
• Writing a simple text editor application
• Text transformation operations such as scaling, rotation, and translation
• Advanced typography
[ Team LiB ]
[ Team LiB ]
Graphics g = [Link];
163
164
If an application executes code from a button or menu click event handler or elsewhere, you will need to
create a Graphics object using CreateGraphics or another method (see Chapter 3 for details) and call the
Dispose method to dispose of objects when you're finished with them. The following code snippet gives an
example:
Graphics g = [Link]();
Note
To test code from this chapter, we will create a Windows application with code written on the menu item click
event handlers.
[ Team LiB ]
[ Team LiB ]
It's hard for human beings as perceptual entities to describe and represent colors. Color
spaces provide a common frame of reference that helps represent colors. A color space
contains components called color channels. For example, RGB space is a
three-dimensional space with red, green, and blue color channels. To limit our discussion,
we will cover the RGB (red-green-blue), HSV (hue-saturation-value), and HLS
(hue-lightness-saturation) color spaces.
The RGB color space is the most commonly used namespace in computer programming
because it closely matches the structure of most display hardware which commonly
includes separate red, green, and blue subpixel structures. It can be thought of as a cube
in which length indicates the intensity of red, width indicates the intensity of green, and
164
165
height indicates the intensity of blue. The corner indicated by (0, 0, 0) is black, and the
opposite corner (255, 255, 255) is white. Every other color available is represented
somewhere between those corners.
The HSV, sometimes called HSB (hue-saturation-brightness), and HLS color spaces can
be thought of as single and double cones. The hue component represents the position on
the cone as an angular measurement. The 0-, 120-, and 240-degree values of hue
represent the colors red, green, and blue, respectively.
The saturation component describes the color intensity. A saturation value of 0 means
gray (colorless), and the maximum value of saturation indicates pure color and brightness
for the values specified by the hue and value components.
The value, or brightness, component represents the brightness of the color. A value of 0
indicates the color black (no brightness), and a maximum value indicates that the color is
brightest (closest to white).
The Color structure provided by the .NET Framework library is based on the RGB color
space. In Section 5.2.2 we will discuss how to use it in our applications.
The Color structure represents ARGB colors in GDI+. This class has a static member
property for almost every possible color. For example, [Link] and [Link]
represent the colors black and red, respectively. Besides these static properties, this
structure includes read-only properties A, R, G, and B that represent the alpha, red, green,
and blue components, respectively.
The IsEmpty property checks whether a Color structure has been initialized (if not, there
is no color). The KnownColor enumeration contains more than 300 colors, and each color
is represented by its name. For example, Blue and Black members represent the colors
blue and black, respectively. KnownColor also defines color combinations, such as
LimeGreen and LightBlue. You can also find system colors such as ActiveBorder,
ActiveCaption, Control, ControlText, Highlight, and InactiveBorder, using the
IsSystemColor enumeration. The Name property represents the name of the color, which
is a read-only property. The Transparent property is a static property that represents a
transparent color.
The Color structure also provides some methods. The FromArgb method creates a color
from the four ARGB components. This method has different overloaded forms with
which an application can create a Color object from an alpha value only; from an alpha
value with a Color object only; from three values (red, green, and blue); and from all
four values (alpha, red, green, and blue).
The FromKnownColor and FromName methods create a Color object from a predefined
color or from the name of a predefined color, respectively. The FromKnownColor method
takes only one argument, of KnownColor enumeration. The FromName method takes one
argument of string type as the color name. All members defined in the KnownColor
enumeration are valid names for this method.
Note
165
166
All three "from" methods (FromArgb, FromKnownColor, and FromName) are static.
Listing 5.1 illustrates different ways to create Color objects and use them in an
application to draw various graphics objects, including a filled ellipse with a red brush, a
filled rectangle with a blue brush, and a line with a green pen. The application first
creates four Color objects via the FromArgb, FromName, FromKnownColor, and Empty
methods. The FromArgb method creates a translucent pure red Color object, using
parameters 120, 255, 0, and 0. The FromName method creates a Color object from the
string "Blue". The FromKnownColor method creates a color object from the known color
Green.
Listing 5.1 Using the methods and properties of the Color structure
166
167
Figure 5.1. Creating colors using different methods
Figure 5.2 shows the output from Listing 5.2. The values of hue, saturation, and
brightness in this particular color are 330, 1, and 0.3921569, respectively.
167
168
The SystemColors class represents the Windows system colors; it provides 26 read-only
properties, each of which returns a Color object. Table 5.1 lists the properties of the
SystemColors class.
The following code snippet uses the SystemColors class to set colors of a few Windows
controls. In this code we set the background colors of a text box, a radio button, and a
button to inactive border, active caption, and control dark system colors, respectively.
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
If you're wondering whether you can create a brush or a pen from the SystemColors
class to fill and draw shapes, curves, and text, the answer is, absolutely. The following
code snippet uses SystemColors to create SolidBrush and Pen objects. This code creates
a solid brush and a pen from active caption system and highlight text system colors,
respectively.
168
169
color
ControlDark 3D control shadow
color
ControlDarkDark 3D control dark
shadow color
ControlLight 3D control highlight
color
ControlLightLight 3D control light
highlight color
ControlText Text color of
controls
Desktop Windows desktop
color
GrayText Disabled text color
Highlight Highlighted text
background color
HighlightText Highlighted text
color
HotTrack Hot track color
InactiveBorder Inactive window
border color
InactiveCaption Inactive window
caption bar color
InactiveCaptionText Inactive window
caption bar text
color
Info ToolTip background
color
InfoText ToolTip text color
Menu Menu background
color
MenuText Menu text color
ScrollBar Background color of
scroll bars
Window Background color of
window
WindowFrame Thin window frame
color
WindowText Window text color
SolidBrush brush =
new SolidBrush([Link]);
Pen pn = new Pen([Link]);
For performance reasons, GDI+ provides SystemPens and SystemBrushes classes, which should be used
instead of creating a brush or pen from the SystemColors class. For example, the following method is
advisable for creating system brushes and pens. This code snippet creates a solid brush and a pen from active
caption and highlight text system colors, respectively.
SolidBrush brush1 =
(SolidBrush)[Link]
([Link]);
169
170
Pen pn1 = [Link]
([Link]);
Listing 5.3 uses the SystemBrushes and SystemPens classes to create a SolidBrush object and three Pen
objects, which are used later to draw and fill graphics objects. The solid brush is created from the active
caption system color, and the three pens are created from highlight text, control light light, and control dark
system colors, respectively. Later the brush and pens are used to draw two lines, a rectangle, and an ellipse.
Figure 5.3 shows the output from Listing 5.3. System colors were used to draw two lines, an ellipse, and a
rectangle.
Note
When you create pens using SystemPens, you cannot modify the width or other properties of the pen. The
code will compile but will throw an unhandled exception when executed. If you create a pen using
170
171
SystemColors, however, you can modify its width like this:
[Link] = 4;
The ColorConverter class is used to convert colors from one data type to another. This class is inherited
from the TypeConverter class, which defines the functionality for conversion of types and accessing values
and properties of types. The TypeConverter class serves as a base class for many conversion classes, and
ColorConverter and FontConverter are two of them. We will discuss FontConverter in more detail later in
this chapter. Some of the common methods of the TypeConverter class (which are available in the
ColorConverter class) are described in Table 5.2.
Method
Description
CanConvertFrom
Takes a type as a parameter and returns true if the converter can convert an object to the type of the
converter; otherwise returns false.
CanConvertTo
Takes a type as a parameter and returns true if the converter can convert an object to a given type; otherwise
returns false.
ConvertFrom
Converts an object to the type of the converter and returns the converted object.
ConvertTo
GetStandardValues
Returns a collection of standard values (collection type) for the data type for which this type converter is
designed.
GetStandardValuesSupported
Listing 5.4 uses the ColorConverter class methods to convert colors. We store a color in a string and call the
ConvertFromString method, which returns the Color object. Later we will use the Color objects to create
two brushes that we will use to fill a rectangle and an ellipse.
171
172
Listing 5.4 Using the ColorConverter class to convert colors
The ColorTranslator class provides methods to translate colors to and from HTML, OLE, and Win32 color
values. These methods are useful when you're using legacy color structures that pre-date the .NET
Framework. For example, you may have legacy code that gives the HTML color representation of a color.
Table 5.3 describes the methods of the ColorTranslator class. All of the methods are static.
Listing 5.5 uses the ColorTranslator class to translate colors from Win32 and HTML colors. Later these
colors will be used to create brushes.
172
173
// Dispose of objects
[Link]();
[Link]();
[Link]();
}
Method
Description
FromHtml
FromOle
FromWin32
ToHtml
ToOle
ToWin32
In a manner similar to the "from" methods just discussed, you can translate a Color structure into Win32,
HTML, and OLE values using the ToWin32, ToHtml, and ToOle methods, respectively.
Note
You can also transform colors using transformation methods. Some of the transformation methods are for
scaling, translating, rotating, and shearing. We cover this functionality in Chapter 10.
[ Team LiB ]
[ Team LiB ]
173
174
Windows supports two types of fonts: GDI fonts and device fonts. Device fonts are native to
output devices such as a monitor or a printer. GDI fonts are stored in files on your
system normally in the Windows\Fonts directory. Each font has its own file. For example, Arial,
Arial Black, Arial Bold, Arial Italic, Arial Black Italic, Arial Bold Italic, Arial Narrow, Arial
Narrow Bold Italic, and Arial Narrow Italic are different fonts in the Arial font family, and each
one has its own file (see Figure 5.5).
GDI fonts can be further divided into four major categories: raster, stroke, TrueType, and
OpenType. The raster and stroke fonts are the oldest way to display text (they pre-date Windows
3.1!). Raster fonts (also known as bitmap fonts) store each character in pixel format. Each raster
font is designed for a specific aspect ratio and character size, which are generally not scalable to
other sizes. The main advantage of raster fonts is high performance because rendering a raster
font usually just requires copying it to video memory. Raster fonts support boldface, italics,
underlining, and strikethrough formatting.
Stroke fonts (also known as vector fonts) are defined as a series of lines and dots in much the
same way that characters are drawn with a pen plotter. Stroke fonts are thus quite scalable (they
can be increased or decreased to any size), and they can be used with output devices of any
resolution. Examples of stroke fonts include Modern, Roman, and Script. Like raster fonts, stroke
fonts support boldface, italics, underlining, and strikethrough formatting.
174
175
Next we come to TrueType fonts, which were developed by Apple and Microsoft and are
supported by many manufacturers. TrueType fonts are also called outline fonts because the
individual characters are defined by filled outlines of straight lines and curves. Altering the
coordinates that define the outlines provides great scalability. The original 13 TrueType fonts
were
1. Courier New
2. Courier New Bold
3. Courier New Italic
4. Courier New Bold Italic
5. Times New Roman
6. Times New Roman Bold
7. Times New Roman Italic
8. Times New Roman Bold Italic
9. Arial
10. Arial Bold
11. Arial Italic
12. Arial Bold Italic
13. Symbol
Adobe and Microsoft announced yet another format in 1997, called OpenType. It is a
combination of TrueType and the Type 1 outline format of Adobe's page-description language.
Windows 2000 installs 82 fonts, including TrueType fonts, OpenType fonts, and other types. The
TrueType fonts are represented by a "T" icon, and OpenType fonts are represented by an "O"
icon in Windows Explorer, as shown in Figure 5.6.
The file extension of both TrueType and OpenType fonts is .ttf. If you double-click on the
Verdana OpenType font file, it displays the information shown in Figure 5.7.
175
176
Figure 5.7. An OpenType font
The Arial Black Italic TrueType font file, on the other hand, looks like Figure 5.8.
In 1998, Microsoft announced a new display technology called ClearType. ClearType increases
the readability and smoothness of text on existing LCDs (liquid crystal displays), such as laptop
screens, flat-screen monitors, and Pocket PC screens. In normal displays, a pixel has only two
states: on and off. ClearType technology adds additional information to a pixel besides the on and
off states. With ClearType, the words on the display device look almost as sharp and clear as
those on the printed page.
Note
176
177
[Link] Attributes or Styles
In typography, the combination of a typeface name (sometimes referred to as a face name) and a
point size (sometimes referred to as the em size) represents a font. A typeface name is a
combination of a font family and the font style (also referred to as font attributes). Each typeface
belongs to a font family such as Times New Roman, Arial, or Courier. The Courier family, for
example, includes the typefaces Courier New, Courier New Bold, and Courier New Italic.
Generally, when we talk about a font, we are referring to more than just one component. A
typical font is a combination of three components: font family, font style, and font size. Figure
5.9 shows the components of a typical font.
A complete example of a font is "Times New Roman, size 10, Bold|Italic". Here the font family is
Times New Roman, the size is 10-point, and the style is both bold and italic.
The size of a font is expressed in points, where a point is usually 1/72 (0.013888) inch. The
measurement of the size of a font is a little confusing because characters have different heights. If
all alphabetic characters had the same height, it would be easier to calculate the size of a font. For
example, consider the characters b and q. Technically they have the same height (or size), but
they are situated in different locations along a straight line. In other words, the character's size
may not be the same as the point size, also called em size. The font size is related to the line
spacing. We will discuss line spacing in more detail in Section 5.3.4.
Before we use fonts and draw text, let's see what classes GDI+ provides related to text and fonts,
and how to use them.
Typography Namespaces
In the .NET framework library, two namespaces define the font-related functionality:
[Link] and [Link]. The [Link] namespace
contains general typography functionality, and [Link] contains
advanced typography functionality. Before using any of the typography-related classes
177
178
in your application, you must include the appropriate namespace. We will discuss
advanced typography in Section 5.6.
The Font class provides functionality for fonts, including methods and properties to define
functionalities such as font style, size, name, and conversions. Before we discuss the Font class,
we will introduce the FontStyle enumeration and the FontFamily class, which we will use to
create Font objects.
The FontStyle enumeration defines the common styles of a font. The members of FontStyle are
described in Table 5.4.
The FontFamily class provides methods and properties to work with font families. Table 5.5
describes the properties of the FontFamily class.
Property
Description
Families
Returns an array of all the font families associated with the current graphics context.
GenericMonospace
GenericSansSerif
GenericSerif
Name
178
179
Returns the name of a font family.
Note
The GetFamilies method of the FontCollection class returns all families, as we will discuss in Section 5.6.
Table 5.6 introduces some new terms, including base line, ascent, and descent. Let's see what they mean.
Figure 5.10 shows a typical font in Windows. As you can see, although the letters b and q are the same size,
their starting points and ending points (top and bottom locations) are different. The total height of a
font including ascent, descent, and extra space is called the line spacing. Ascent is the height above the base
line, and descent is the height below the base line. As Figure 5.10 shows, two characters may have different
positions along the base line. For some fonts, the extra value is 0, but for others it is not.
Method
Description
GetCellAscent
GetCellDescent
GetEmHeight
Returns the height, in font design units, of the em square for the specified style.
GetFamilies
Returns an array that contains all font families available for a graphics object. This method takes an argument
of Graphics type.
GetLineSpacing
Returns the amount of space between two consecutive lines of text for a font family.
GetName
179
180
Returns the name, in the specified language, of a font family.
IsStyleAvailable
Before applying a style to a font, you may want to know whether the font family in question supports that
style. This method returns true if a font style is available. For example, the following code snippet checks
whether or not the Arial font family supports italics:
For some fonts, line spacing is the sum of ascent and descent. Listing 5.6 creates a new font; uses get methods
to get the values of line spacing, ascent, and descent; and calculates the extra space by subtracting ascent and
descent from the line space. The following list identifies the get methods of a FontFamily object:
In addition to these get methods, the Font class provides GetHeight, which returns the height of a Font
object.
As Listing 5.6 shows, we use GetLineSpacing, GetLineAscent, GetLineDescent, and GetEmHeight to get
line spacing, ascent, descent, and font height, respectively, and then we display the output in a message box.
Listing 5.6 Getting line spacing, ascent, descent, and font height
180
181
", EM Height:"+ [Link]() ;
[Link]([Link]());
// Dispose of objects
[Link]();
[Link]();
}
Figure 5.11 shows the output from Listing 5.6. We get cell height, line spacing, ascent, descent, free (extra)
space, and em height.
Figure 5.11. Getting line spacing, ascent, descent, free (extra) space, and height of a font
You can define the unit of measure of a font when you construct a Font object. The Font class constructor
takes an argument of type GraphicsUnit enumeration, which specifies the unit of measure of a font. The
default unit of measure for fonts is the point (1/72 inch). You can get the current unit of a font by using the
Unit property of the Font class. The following code snippet returns the current unit of the font:
Member
Unit of Measure
Display
1/75 inch
Document
1/300 inch
Inch
1 inch
Millimeter
1 millimeter
Pixel
181
182
1 pixel
Point
1/72 inch
World
The Font class combines a font and methods and properties to define functionalities such as font style, size,
name, and conversions. Table 5.8 describes the properties of the Font class.
The following code creates a Font object of font family Arial with size 16 and uses the Font class properties
to find out the details of the Font object.
The Font class provides three static methods: FromHdc, FromHfont, and FromLogFont. These methods create a
Font object from a window handle to a device context, a window handle, and a GDI LOGFONT structure,
respectively. The GetHeight method returns the height of a Font object. The ToHfont and ToLogFont
methods convert a Font object to a window handler and a GDI LOGFONT structure, respectively.
Property
Description
Bold
FontFamily
Every font belongs to a font family. This property returns the FontFamily object associated with a Font
object.
GdiCharSet
GdiVerticalFont
182
183
Returns true if a font is derived from a GDI vertical font; otherwise returns false.
Height
Italic
Name
Size
SizeInPoints
Strikeout
Style
Underline
Unit
In the following example, you must import the GDI library by adding the following code at the beginning of
your class before using any GDI fonts, because we will be using GetStockObject:
[[Link]("[Link]")]
private static extern IntPtr GetStockObject(int fnObj);
Listing 5.7 creates a font from a GDI handle and draws a string on the form. The FromHfont method creates a
Font object from a GDI handle.
183
184
SolidBrush brush = new SolidBrush([Link]);
// Get a handle
IntPtr hFont = GetStockObject(0);
// Create a font from the handle
Font hfontFont = [Link](hFont);
// Draw text
[Link]("GDI HFONT", hfontFont,
brush, 20, 20);
// Dispose of objects
[Link]();
[Link]();
[Link]();
}
A Font object belongs to the FontFamily class, so before we construct a Font object, we need to construct a
FontFamily object. The following code snippet creates two FontFamily objects, belonging to the Verdana
and Arial font families, respectively.
The Font class provides more than a dozen overloaded constructors, which allow an application to construct a
Font object in different ways, either from string names of a font family and size or from a FontFamily object
with font style and optional GraphicsUnit values.
The simplest way to create a Font object is to pass the font family name as the first argument and the point
size as the second argument of the Font constructor. The following code snippet creates a Times New Roman
12-point font:
The following code snippet creates three fonts in different styles belonging to the Verdana, Tahoma, and Arial
font families, respectively:
184
185
// Create font families
FontFamily verdanaFamily = new FontFamily("Verdana");
FontFamily arialFamily = new FontFamily("Arial");
// Construct Font objects
Font verdanaFont = new Font( verdanaFamily, 14,
[Link], [Link]);
Font tahomaFont = new Font( new FontFamily("Tahoma"), 10,
[Link]|[Link], [Link]);
Font arialFont = new Font(arialFamily, 16, [Link],
[Link]);
Font tnwFont = new Font("Times New Roman", 12);
Note
As the code example here shows, you can use the FontStyle and GraphicsUnit enumerations to define the
style and units of a font, respectively.
If you don't want to create and use a FontFamily object in constructing a font, you can pass the font family
name and size directly when you create a new Font object. The following code snippet creates three fonts
from the Verdana, Arial, and Tahoma font families, respectively, with different sizes and styles:
[ Team LiB ]
[ Team LiB ]
Listing 5.8 creates three different fonts and draws text on a form using the DrawString
method. Each DrawString method uses a different color and font to draw the string.
185
186
new Font( arialFamily, 16, [Link]);
Font tahomaFont = new Font( "Tahoma", 24,
[Link]|[Link]);
// Create Brush and other objects
PointF pointF = new PointF(30, 10);
SolidBrush solidBrush =
new SolidBrush([Link](255, 0, 0, 255));
// Draw text using DrawString
[Link]("Drawing Text", verdanaFont,
new SolidBrush([Link]), new PointF(20,20) );
[Link]("Drawing Text", arialFont,
new SolidBrush([Link]), new PointF(20, 50) );
[Link]("Drawing Text", tahomaFont,
new SolidBrush([Link]), new PointF(20, 80) );
// Dispose of objects
[Link]();
[Link]();
}
Figure 5.13 shows the output from Listing 5.8. The first text is 10-point Verdana; the second,
14-point Arial Bold; and the third, 24-point Tahoma Italic.
Note
See Chapter 3 (Section [Link]) for more overloaded forms of the DrawString method.
The DrawString method can also be used to draw formatted text. To format text, the .NET
Framework library provides the StringFormat class, which can be passed as a parameter of
the DrawString methods. The StringFormat class provides members to set alignment, line
spacing, digit substitution, trimming, and tab stops. These classes are defined in the
[Link] namespace.
The Alignment and Trimming properties of the StringFormat class are used to set and get
alignment and trimming of text. The Alignment property takes a value of type
StringAlignment enumeration, and the Trimming property takes a value of type
186
187
StringTrimming enumeration.
The LineAlignment property represents the line alignment of text, which also takes a value of
type StringAlignment enumeration.
The StringAlignment enumeration specifies the alignment of a text string. Table 5.9 describes
the members of the StringAlignment enumeration.
The StringTrimming enumeration specifies how to trim characters from a string that does not
completely fit into a layout shape. Table 5.10 describes the members of the StringTrimming
enumeration.
Listing 5.9 uses Alignment and Trimming properties to align and trim text strings and draws
the text to a form. We use two StringFormat objects: strFormat1 and strFormat2. For
strFormat1, we set the alignment to Center, line alignment to Center, and trimming to
EllipsisCharacter. For strFormat2, we set the alignment to Far, string alignment to Near,
and trimming to Character. Then we use strFormat1 and strFormat2 as parameters of the
DrawString method to apply a string format to the text.
Member
Description
Character
EllipsisCharacter
Text is trimmed to the nearest character, and an ellipsis is inserted at the end of a trimmed line.
EllipsisPath
EllipsisWord
187
188
Text is trimmed to the nearest word, and an ellipsis is inserted at the end of a trimmed line.
None
No trimming.
Word
Figure 5.14 shows the output from Listing 5.9. Text inside the rectangle is trimmed to fit.
188
189
Along with the properties discussed in the preceding section, the StringFormat class provides some methods.
The GetTabStops and SetTabStops methods can be used to get and set tab stops, respectively. Each of these
methods takes two arguments: firstTabOffset and tabStops. The first parameter, firstTabOffset, is a
float value that represents the number of spaces between the beginning of a line of text and the first tab
stop. The second parameter, tabStops, is an array of float values that represents the number of spaces
between tabs.
An application can use the SetTabStops method to generate tabular output on a graphics surface. For
example, Listing 5.10 uses SetTabStops to generate a tabular data report. In this example we create a
StringFormat object and set its tab stops using the SetTabStops method, and then we call the DrawString
method.
In Listing 5.10 we create a table that lists the grades of a student in tabular format. The table has four
columns: ID, Math, Physics, and Chemistry. These columns list the grades obtained by a student. As the
listing shows, we create a StringFormat object and set the tab stops using the SetTabStops method.
Listing 5.10 Using tab stops to draw tabular data on a graphics surface
189
190
SolidBrush redBrush = new SolidBrush([Link]);
// Create a rectangle
Rectangle rect = new Rectangle(10, 50, 350, 250);
// Create a StringFormat object
StringFormat strFormat = new StringFormat();
// Set tab stops of string format
[Link](5, new float[]
{80, 100, 80, 80});
// Draw string
[Link]("Student Grades Table",
tahomaFont,
blackBrush, new Rectangle
(10, 10, 300, 100));
[Link]("=============",
tahomaFont, blackBrush,
new Rectangle(10, 23, 300, 100));
// Draw string with tab stops
[Link](text, verdanaFont,
redBrush, rect, strFormat);
// Dispose of GDI+ objects
[Link]();
[Link]();
[Link]();
[Link]();
}
Figure 5.15 shows the output from Listing 5.10. It's easy to present text data in a tabular form by simply using
the StringFormat class and its properties.
The FormatFlags property is useful when an application needs to draw text strings in different layouts such
as drawing vertical text. FormatFlags takes a value of the StringFormatFlags enumeration. Table 5.11
describes the members of the StringFormatFlags enumeration.
Note
An application can apply more than one StringFormatFlags member by using bitwise combinations.
As Listing 5.11 shows, our sample code draws two strings. One string is drawn from right to left, and the
other is vertical. Using FormatFlags is pretty simple. An application creates a StringFormat object, sets its
FormatFlags property, and then uses the StringFormat object in the DrawString method. Note that an
application can use more than one instance of FormatFlags for the same StringFormat object.
190
191
Table 5.11. StringFormatFlags members
Member
Description
DirectionRightToLeft
Draws text right to left in a given rectangle using the DrawString method.
DirectionVertical
Draws vertical text in a given rectangle using the DrawString method. The default alignment is left (use the
Alignment property to change the text alignment).
DisplayFormatControl
Causes control characters such as the paragraph mark to be shown in the output with a representative glyph
(e.g., ¶).
FitBlackBox
Specifies that no part of any glyph will overhang the bounding rectangle.
LineLimit
Specifies that only complete lines will be laid out in the formatting rectangle.
MeasureTrailingSpaces
By default, the boundary rectangle returned by the MeasureString method excludes any space at the end of
each line. Set this flag to include that space in the measurement.
NoClip
By default, clipping is on, which means that any text outside of the formatting rectangle is not displayed.
NoClip disables clipping.
NoFontFallback
By default, if the specified font is not found, an alternative font will be used. NoFontFallback disables that
option and displays an open square for the missing character(s).
NoWrap
191
192
StringFormat strFormat2 = new StringFormat();
// Set format flags of StringFormat objects
// with direction right to left
[Link] =
[Link];
// Set direction vertical
[Link] =
[Link];
// Set alignment
[Link] = [Link];
// Draw rectangle
[Link](new Pen([Link]), rect);
string str =
"Horizontal Text: This is horizontal "
+ "text inside a rectangle";
// Draw strings
[Link](str,
new Font("Verdana", 10, [Link]),
new SolidBrush([Link]),
rect, strFormat1);
[Link]("Vertical: Text String",
new Font("Arial", 14),
new SolidBrush([Link]),
rect, strFormat2);
// Dispose of GDI+ objects
[Link]();
}
Figure 5.16 shows the output from Listing 5.11. One text string is drawn from right to left (aligned right) in
the drawing rectangle, and the other text string is drawn vertically on the left-hand side. An application can
even use Alignment, Trimming, and other properties to align and trim text.
Note
192
193
The SetDigitSubstitution method can be used to substitute digits in a string on the basis of a user's local
area. SetDigitSubstitution takes a parameter of the StringDigitSubstitute enumeration, the members
of which are described in Table 5.12.
Member
Description
National
Provides substitution digits based on the national language of the user's locale.
None
Disables substitutions.
Traditional
User
[ Team LiB ]
[ Team LiB ]
Note
193
194
The TextRenderingHint property takes a value of type TextRenderingHint
enumeration. The members of the TextRenderingHint enumeration are described
in Table 5.13.
Listing 5.12 uses the TextRenderingHint property to draw text with different
options. This code draws four different text strings using different text rendering
hint options.
194
195
Characters are anti-aliased
with hinting.
AntiAliasGridFit offers
good quality and high
performance.
ClearTypeGridFit Characters are drawn by a
ClearType bitmap with
hinting. This is the
highest-quality setting, with
slow performance. It takes
advantage of ClearType
font features, if available.
SingleBitPerPixel Characters are drawn with
each glyph's bitmap.
Hinting is not used.
SingleBitPerPixelGridFit Characters are drawn with
each glyph's bitmap.
Hinting is used to improve
character appearance on
stems and curvature.
SystemDefault Characters are drawn with
each glyph's bitmap, with
the system's default
rendering hint.
Figure 5.17 shows the output from Listing 5.12. Different TextRenderingHint options result in text with
higher or lower quality. (How clearly this shows up will vary on different displays and it may be hard to see
in print.)
[ Team LiB ]
[ Team LiB ]
195
196
The InstalledFontCollection class represents all the fonts installed on the system. The Families property
returns a collection of all font families available on the system.
Note
Before using any of the [Link] namespace classes, an application must add a reference to the
namespace with the "using" directive:
using [Link];
As stated in the previous section, the InstalledFontCollection class represents all available font families
on a system. The Families property returns an array of FontFamily type.
Listing 5.13 returns all available fonts on a system. To test this application, add a combo box to a form and
write this code on the form-load event handler or a button or menu click event handler. using
[Link] Before executing this code, an application must add the following line:
using [Link]
196
197
5.6.2 Private Font Collection
The PrivateFontCollection class is used to create a private collection of fonts, for use only by your
application. A private collection may include the fonts available on a system, as well as fonts that are not
installed on the system. Such a collection is useful when you want to use third-party fonts. The AddFontFile
method is used to add a font file to the collection. The AddMemoryFont method reads fonts from system
memory and adds them to the collection. The IsStyleAvailable method, which takes a FontStyle
enumeration value, indicates whether a style is available.
Normally all system fonts are installed in your Windows\Fonts directory. On our test machine, all fonts are
installed in the directory C:\WinNT\Fonts. You can also browse and add fonts from other locations to a
private font collection by passing the full path of the font file in the AddFontFile method. For example, the
following code snippet adds four fonts to a private font collection.
In this code we add four fonts to the private font collection. Verdana is available on all machines. The other
three fonts can be downloaded from [Link] (click Enter on site's home page to access
naviagation area).
You can even add styles to an existing font. In Listing 5.14 we add four fonts to the private font collection
with the AddFontFile method. Then we see if these font families have different styles. If not, we add new
styles to the font families and draw text using the new fonts. In the end, we print out the font name on the
form.
197
198
fontName = fontFamilies[j].Name;
if(fontFamilies[j].IsStyleAvailable(
[Link]) &&
fontFamilies[j].IsStyleAvailable(
[Link]) &&
fontFamilies[j].IsStyleAvailable(
[Link]) &&
fontFamilies[j].IsStyleAvailable(
[Link]) )
{
// Create a font from the font name
Font newFont = new Font(fontName,
20, [Link] | [Link]
|[Link], [Link]);
// Draw string using the current font
[Link](fontName, newFont,
new SolidBrush([Link]), pointF);
// Set location
pointF.Y += [Link];
}
}
// Dispose of object
[Link]();
}
Note
You may need to change the directory path in Listing 5.14 to match your machine.
To test Listing 5.14, create a Windows application and insert the sample code on the form-paint, a button
click, or a menu click event handler, and run the application. Figure 5.18 shows the ouput of the application.
All the available fonts in the private font collection are listed.
[ Team LiB ]
[ Team LiB ]
198
199
First we create a Windows application and add some controls to the form. As Figure 5.19 shows, we add two
label controls and set their Text properties to Available Fonts and Size, respectively. Then we add a combo
box, a NumericUpDown control, and two button controls with the Text properties set to Color and Apply,
respectively. We will use the combo box control to display all installed fonts, the NumericUpDown control to
set the size of text, and the Color button to set the text color. We also add a RichTextBox control to the form
and size it appropriately.
using [Link];
We also add two private variables of types Color and int, respectively, as follows:
Finally, we double-click on the form and insert the code from Listing 5.15 on the form-load event handler,
thereby setting the NumericUpDown control's Value property to 10 and adding all installed fonts to the combo
box control.
199
200
[Link] = 10;
// Create InstalledFontCollection object
InstalledFontCollection
sysFontCollection =
new InstalledFontCollection();
// Get the array of FontFamily objects
FontFamily[] fontFamilies =
[Link];
// Read all font familes and
// add to the combo box
foreach (FontFamily ff in fontFamilies)
{
[Link]([Link]);
}
[Link] = fontFamilies[0].Name;
}
The Color button click event handler simply calls ColorDialog, which allows the user to pick the text color
(see Listing 5.16).
The Apply button reads the selected font name from the combo box and the size from the NumericUpDown
control. Then it creates a Font object using the font family name and size. Finally, we set the ForeColor and
Font properties of the RichTextBox control (see Listing 5.17).
By extending this simple application and the RichTextBox features, you can develop a complete text editor
with features that include open and save, find, change font styles, and so on. We'll leave this to you as an
exercise!
[ Team LiB ]
[ Team LiB ]
200
201
Transformation using Graphics class methods and properties is pretty simple. The Graphics class provides
the methods ScaleTransform, RotateTransform, TranslateTransform and others.
Note
See Chapter 10 for detailed information about transformations and how to use various transformation
techniques.
Let's look at a simple yet useful example of text transformation. First we draw some text on a form using the
code in Listing 5.18.
Graphics g = [Link];
string str = "Colors, fonts, and text are common elements "+
"of graphics programming. In this chapter, you learned " +
" about the colors, fonts, and text representations in the "+
".NET Framework class library. You learned how to create "+
"these elements and use them in GDI+.";
[Link](str, new Font("Verdana", 10),
new SolidBrush([Link]), new Rectangle(50,20,200,300) );
Figure 5.20 shows the output of Listing 5.18. The text is drawn normally.
Now let's scale the text using the ScaleTransform method by writing the following line before the
DrawString method call. Scaling changes the text size by application of a scaling factor. For example, the
following code line doubles the size of text. This code must be added before the DrawString method call:
[Link](2, 1);
201
202
Now our text on the form looks like Figure 5.21. It is scaled to twice the regular size.
Now let's rotate the text, which we can do by calling the RotateTransform method, which takes a rotation
angle. We rotate the text 45 degrees by adding the following line before the DrawString method call:
[Link](45.0f,
[Link]);
Now the text on the form looks like Figure 5.22. It is rotated from its previous position.
Finally, let's call TranslateTransform, which takes two values related to the x- and y-axes. We add the
following line after RotateTransform:
[Link](-20, -70);
and our final form looks like Figure 5.23. The text has been moved (or "translated") from its previous
position.
202
203
Figure 5.23. Using TranslateTransform to translate text
[ Team LiB ]
[ Team LiB ]
SUMMARY
We started this chapter by discussing the basics of colors, fonts, and text-related functionality and classes
defined in the .NET Framework. In the colors section, we covered how to use the Color class and its
members, including system colors. We also discussed color spaces, and how to translate colors from one to
another.
In the fonts section, we discussed how to use the Font class and related classes to create various types of fonts
with different sizes and colors. We also discussed how to control the font families, including system and
private font collections, and use them in our application.
The text section covered some uses of fonts and strings. We discussed how to format text, including aligning,
tab stops, trimming, and hinting. We also discussed how to improve the quality and speed of text rendering by
using various settings. Then we created a text editor illustrating how to use color-, font-, and text-related
functionality in a real-world application.
At the end of the chapter we discussed some text transformation techniques, including scaling, rotation, and
translation of text from one position to another.
Chapter 3 mentioned rectangles and regions only briefly, but regions and rectangles play a major role in
application development and rendering performance. In Chapter 6 we will discuss rectangles and regions in
detail.
[ Team LiB ]
[ Team LiB ]
• .NET Framework objects that work with rectangles and regions and their members
203
204
• Graphics class members that work with rectangles and regions
• Writing applications using objects
• The Rectangle structure and its members
• The Region class and its members
• Invalidating and clipping regions
• Examples of real-world applications using regions and rectangles
A rectangle has three properties: starting point, height, and width. Figure 6.1 shows these properties where the
starting point is the top left.
Suppose you wanted to draw a rectangle from point (1, 2) with height 7 and width 6. The final rectangle
would look like Figure 6.2.
Figure 6.2. A rectangle with starting point (1, 2), height 7, and width 6
The filled rectangle occupies the entire area within the range of its height and width.
[ Team LiB ]
[ Team LiB ]
A Rectangle object stores the top left corner and height and width of a rectangular region. In this section we
will see how to create and use the Rectangle structure.
There are several ways to create a Rectangle object. For example, you can create a Rectangle object from
four integer values representing the starting point and size of the rectangle, or from Point and Size structures.
204
205
Listing 6.1 creates Rectangle objects from Size, Point, and direct values. As this code shows, a Rectangle
constructor can take a Point and a Size object or, alternatively, the starting point (as separate variables x and
y), width, and height.
int x = 20;
int y = 30;
int height = 30;
int width = 30;
// Create a starting point
Point pt = new Point(10, 10);
// Create a size
Size sz = new Size(60, 40);
// Create a rectangle from a point
// and a size
Rectangle rect1 = new Rectangle(pt, sz);
Rectangle rect2 =
new Rectangle(x, y, width, height);
You can also create a RectangleF object in several ways: from four floating point numbers with the starting
and ending points and height and width of the rectangle, or from a point and a size. RectangleF is a mirror of
Rectangle, including properties and methods. The only difference is that RectangleF takes floating point
values. For example, instead of Size and Point, RectangleF uses SizeF and PointF. Listing 6.2 creates
RectangleF objects in two different ways.
The Rectangle structure provides properties that include Bottom, Top, Left, Right, Height, Width, IsEmpty,
Location, Size, X, and Y. Listing 6.3 creates two rectangles (rect1 and rect2), reads these properties, and
displays their values in a message box.
205
206
// Create a rectangle from floating points
RectangleF rect2 =
new RectangleF(40.2f, 40.6f, 100.5f, 100.0f);
// If rectangle is empty,
// set its Location, Width, and Height
// properties
if ([Link])
{
[Link] = pt;
[Link] = [Link];
[Link] = [Link];
}
// Read properties and display
string str =
"Location:"+ [Link]();
str += "X:"+[Link]() + "\n";
str += "Y:"+ [Link]() + "\n";
str += "Left:"+ [Link]() + "\n";
str += "Right:"+ [Link]() + "\n";
str += "Top:"+ [Link]() + "\n";
str += "Bottom:"+ [Link]();
[Link](str);
}
As we discussed in Chapter 2, the Rectangle structure provides methods that include Round, Truncate,
Inflate, Ceiling, Intersect, and Union.
• The Round method converts a RectangleF object to a Rectangle object by rounding off the values of
RectangleF to the nearest integer.
• The Truncate method converts a RectangleF object to a Rectangle object by truncating the values
of RectangleF.
• The Inflate method creates a rectangle inflated by the specified amount.
• The Ceiling method converts a RectangleF object to a Rectangle object by rounding to the next
higher integer values.
• The Intersect method replaces a rectangle by its intersection with a supplied rectangle.
• The Union method gets a rectangle that contains the union of two rectangles.
Listing 6.4 shows how to use the Round, Truncate, Inflate, Ceiling, Intersect, and Union methods.
206
207
Rectangle isectRect =
[Link](rect3, rect5);
// Fill rectangle
[Link](
new SolidBrush([Link]), isectRect);
// Inflate a rectangle
Size inflateSize = new Size(0, 40);
[Link](inflateSize);
// Draw rectangle
[Link]([Link], isectRect);
// Empty rectangle and set its properties
rect4 = [Link];
[Link] = new Point(50, 50);
rect4.X = 30;
rect4.Y = 40;
// Union rectangles
Rectangle unionRect =
[Link](rect4, rect5);
// Draw rectangle
[Link]([Link], unionRect);
// Displose of objects
[Link]();
}
The Contains method is used to determine whether a rectangle or point is inside the current rectangle. If a
point is inside the current rectangle, the Contains method returns true; otherwise it returns false. One of
the common uses of Contains is to find out if a mouse button was clicked inside a rectangle.
To see proper use of the Contains method, let's create a Windows application and draw a rectangle on the
form. Whether the user clicks inside or outside of the rectangle, we will have the application generate an
appropriate message.
207
208
Rectangle bigRect = new Rectangle(50, 50, 100, 100);
Then we use the form's paint event handler because we want to render graphics whenever the form needs to
refresh. The form's paint event handler code looks like this:
Our last step is to determine whether the user clicked inside the rectangle. We track the user's mouse-down
event and write code for the left mouse button click event handler. The MouseEventArgs enumeration
provides members to find out which mouse button is clicked. The MouseButtons enumeration has members
that include Left, Middle, None, Right, Xbutton1, and Xbutton2, which represent the mouse buttons.
We check to see if the mouse button clicked was the left button, then create a rectangle, and (if the mouse
button was clicked) generate a message. Listing 6.5 shows the code for this process.
When you run the application and click on the rectangle, the output looks like Figure 6.4.
208
209
The Contains method also allows us to find out whether a rectangle fits inside another rectangle. Listing 6.6
checks whether smallRect is within bigRect.
In the .NET Framework library, the Region class object represents a region. If you have
ever developed a complex .NET graphics application that requires a lot of rendering,
you may have used this object a lot.
The Region class provides five overloaded forms. Using these forms, you can construct
a Region object from a Rectangle, RectangleF, GraphicsPath, or RegionData object,
or with no parameters. The following code snippet creates Region objects in different
ways using different arguments.
The Region class has no properties. After constructing a region, an application can use
the Graphics class's FillRegion method to fill the region.
209
210
Table 6.1 describes the methods of the Region class briefly. They are discussed in detail
in Section 6.2.2 through 6.2.4
We saw the Region class methods in Table 6.1. Now let's use these methods in our
applications.
Figure 6.5 shows the output from Listing 6.7. Our code updates a portion of rgn1 that
doesn't intersect with rgn2. It is useful when you need to update only a specific part of a
region. For example, suppose you're writing a shooting game application and your
program updates the targets only after gunfire. In this scenario you need to update only
the target region, not the entire form.
210
211
211
212
Updates a region to
the intersection of
itself with another
region.
IsEmpty Returns true if a
region is empty;
otherwise returns
false.
IsInfinite Returns true if a
region has an infinite
interior; otherwise
returns false.
IsVisible Returns true if the
specified rectangle is
contained within a
region.
MakeEmpty Marks a region as
empty.
MakeInfinite Marks a region as
infinite.
Transform Applies the
transformation matrix
to the region.
Translate Offsets the
coordinates of a
region by the
specified amount.
Union Updates a region to
the union of itself
and the given
graphics path.
Xor Updates a region to
the union minus the
intersection of itself
with the given
graphics path.
The Exclude method updates the part of a region that does not interact with the specified region or rectangle.
Like Complement, Exclude takes an argument of type Rectangle, RectangleF, GraphicsPath, or Region and
updates the region. Listing 6.8 creates two Region objects and draws rectangles with different pens, then calls
Exclude.
Figure 6.6 shows the output from Listing 6.8. Only the excluded part of the region is updated.
212
213
Figure 6.6. Excluding regions
[Link](rgn2);
with
[Link](rgn2);
produces Figure 6.7, which updates the union of both regions (or rectangles). Like Exclude and Complement,
the Union method can take Rectangle, RectangleF, GraphicsPath, or Region as an argument.
The Xor method updates the union of both regions (or rectangles) except the intersection area of the rectangle
itself. Replacing Exclude with Xor, as shown in Listing 6.9, generates Figure 6.8.
213
214
The Intersect method is the reverse of Xor. It updates only the intersection region of two regions or
rectangles. For example, if you replace line
[Link](rgn2);
[Link](rgn2);
214
215
The IsEmpty method takes a Graphics object as an argument and returns true if a region is empty. Otherwise
it returns false. IsInfinite returns true if a region is infinite (otherwise it returns false), and it takes a
Graphics object as the only argument.
The MakeEmpty and MakeInfinite methods make a region empty or infinite, respectively. An infinite region
completely covers the area of a control.
The GetBounds method returns the bounds of a region. This method also takes a Graphics object as an
argument.
The code in Listing 6.10 uses these methods. It makes rgn2 infinite and fills it with a red pen, which fills the
entire form with red.
Listing 6.10 Using GetBounds and other methods of the Region class
if (! [Link](g))
[Link]();
// Get bounds of the infinite region
RectangleF rect = [Link](g);
// Display
[Link]([Link]());
// Fill the region
[Link]([Link], rgn2);
// Dispose of object
[Link]();
An infinite region's starting coordinates are negative numbers, and its height and width are large positive
numbers, as Figure 6.10 shows. Using FillRegion on an infinite region fills the entire form.
215
216
Figure 6.10. Bounds of an infinite region
[ Team LiB ]
[ Team LiB ]
Figure 6.11 shows output from Listing 6.11. The small rectangle and small region are not
updated.
216
217
SetClip sets the clipping region of a Graphics object. This method has many overloaded
forms and takes parameters of type Rectangle, RectangleF, Region, GraphicsPath, and
Graphics with or without the CombineMode enumeration. The CombineMode enumeration
defines how different clipping regions can be combined (see Table 6.2).
The ResetClip method resets the clipping region to infinity. Listing 6.12 uses the SetClip,
ResetClip, and IntersectClip methods.
217
218
region being
removed from the
new region.
Exclude The existing
region is replaced
by the result of
the new region
being removed
from the existing
region.
Intersect Two clipping
regions are
combined, and the
result is their
intersection.
Replace One clipping
region replaces
the other.
Union Two clipping
regions are
combined, and the
result is their
union.
Xor Two clipping
regions are
combined, and the
result is their
union minus their
intersection.
Note
218
219
TranslateClip translates the clipping region as specified. Listing 6.13 uses the TranslateClip method to
translate a region by 20 and 30 points.
[ Team LiB ]
[ Team LiB ]
219
220
[Link](brush, region);
[Link](rect2);
[Link](rect2);
220
221
[Link](rect2);
[ Team LiB ]
[ Team LiB ]
Figure 6.18 shows a typical rectangular form. As you can see, the title bar area usually contains the title of the
form, as well as minimize, maximize, and close buttons. This is the nonclient area; the rest of the form is the
client area. Graphics objects can be drawn only in the client area. The combination of both client and
nonclient areas is the default region of a form.
221
222
What exactly is a region? A region is a collection of pixels that represents part of a control. GDI+ is
responsible only for drawing the region associated with a window (a form or control). The default region of a
window includes both client and nonclient areas, so GDI+ draws the entire window.
However, you can force the operating system to display only part of a window. This is where regions are
useful.
This section will show you the importance of regions and how you can use them in real-world applications.
Have you ever thought about writing nonrectangular forms or controls? How about writing circular,
triangular, or polygonal forms, buttons, labels, or text boxes? Our example is a Windows application in which
the user can select the shape of the form. The user will have options to change the default rectangular form to
a circular, triangular, or polygonal form. You will also learn how to create nonrectangular controls such as
buttons.
How can we write nonrectangular forms and controls? GDI+ draws only the regions associated with a form or
a control. But setting a nonrectangular region should do the trick. This is what we will do in our application.
One of the nonrectangular forms of the final application might look like Figure 6.19. As you can see, this
technique can be used to build cool-looking Windows applications.
222
223
6.5.2 Coding
In Windows Forms, every control, including a form, is derived from the Control class. The Region property
of the control class represents the region of control. If you set the Region property of a control, only the area
covered by that region will be visible to the user. Section [Link] through [Link] describe the steps involved in
writing code for nonrectangular shapes.
We create a Windows application, put three controls on the form, and change the Text properties of the
buttons. We also add a context menu and four menu items, as Figure 6.20 shows. In addition, we add menu
and button click event handlers.
Now we add a class to the project. Our class name is Shape, as Listing 6.15 shows. We add two methods to
this class: GetPolyRegion and GetRectRegion. Both of these methods return a Region object. The
GetPolyRegion method takes an array of Point objects as its only argument. We create a graphics path from
the points by calling AddPolygon. After that we create a region from the path and return it. See Chapters 3 and
9 for more about the GraphicsPath class. Similarly, we create a region from a rectangle in the
223
224
GetRectRegion method.
Now we load the context menu on the right mouse click of the form. In Listing 6.16, we set the ContextMenu
property of the form as the context menu control.
Now we call GetRectRegion and GetPolyRegion from the context menu click event handlers to get the
region for a rectangle or a polygon. After getting a Region object corresponding to a rectangle or a polygon,
we just need to set the Region property of the form. Listing 6.17 shows the code for the context menu click
event handlers.
224
225
Listing 6.17 Menu item click event handlers
The code in Listing 6.18 for the Close menu item simply closes the form.
225
226
[Link] Step 5: Display Nonrectangular Shapes
Using similar methods, you can set the Region property of controls such as Button or TextBox to display
nonrectangular shapes. If you don't want to use the Shape class, you can directly set the Region property of a
control. Listing 6.19 sets the Region properties of three buttons. We write this code on the form's load event
handler.
originalSize = [Link];
// Create a Region object from the path
GraphicsPath path1 =
new GraphicsPath([Link]);
[Link](new Rectangle(30, 30,
[Link] -60, [Link]-60));
[Link] = new Region(path1);
GraphicsPath path2 =
new GraphicsPath([Link]);
[Link](new Rectangle(30, 30,
[Link] -60, [Link]-60));
[Link] = new Region(path2);
GraphicsPath path3 =
new GraphicsPath([Link]);
[Link](new Rectangle(20, 20,
[Link] -40, [Link]-40));
[Link] = new Region(path3);
The last step is to run the application and right-click on the form. Figure 6.21 shows the result of selecting the
Circle menu option.
Figure 6.22 shows the result of selecting the Triangle menu option.
226
227
Using this technique, you can build Windows forms and controls of virtually any shape.
[ Team LiB ]
[ Team LiB ]
SUMMARY
In this chapter we discussed some common uses of rectangles and regions. You learned several ways to create
Rectangle and RectangleF objects, and how to use the Round, Truncate, Union, Inflate, Ceiling, and
Intersect methods in your applications. After that you saw an example of a hit test. Then we discussed the
Region class and its members, and how to use Complement, Union, Exclude, Xor, and other methods of the
Region class. We also saw a sample of clipping regions. At the end of this chapter we saw an interesting
sample application that uses regions to create nonrectangular forms and controls.
Imaging is a vital part of graphics. GDI+ provides rich imaging functionality. We will cover this functionality
in Chapter 7.
[ Team LiB ]
[ Team LiB ]
227
228
As we said earlier, the graphics-related functionality in the .NET Framework class library is defined in the
[Link] namespace and its helper namespaces. The imaging functionality is divided into two
categories by separation into two namespaces. Basic imaging functionality is defined in the [Link]
namespace; advanced imaging functionality is defined in the [Link] namespace. This
chapter covers the former; Chapter 8 will focus on the latter.
[ Team LiB ]
[ Team LiB ]
A raster image (also called bitmap) is a collection of one or more pixels. Each pixel of the image
can be controlled individually, which means that each pixel of the image can have a different
color or shade. In a raster image that contains a line and a rectangle, the line and rectangle are
each a sequence of pixels. Raster images require higher resolutions and anti-aliasing for a smooth
appearance and are best suited for photographs and images with shading.
Vector images can be transformed from one state to another without any loss of data.
Transforming raster images, however, may cause data loss or reduce the quality of images. For
example, in the zoomed raster image shown in Figure 7.1, the outer boundary of the image is
blurry.
In the zoomed vector image of Figure 7.2, however, the outer boundary of the image is sharper.
228
229
A bitmap is usually stored in an array of bits that specify the color of each pixel in a rectangular
array of pixels. The bitmap's height and width are measured in pixels. The number of bits per
pixel specifies the number of colors that can be assigned to that pixel, according to the equation
Nc=2Bp
where
For example, if Bp = 8, then Nc = 28 = 256 colors. If Bp = 24, then Nc = 224 = 16,777,216 colors.
Table 7.1 shows the number of bits and number of possible colors that can be assigned to a pixel.
Bitmaps with 1 bit per pixel are called monochrome images. Monochrome images generally store
two colors: black and white.
• BMP
• GIF
• JPEG
• EXIF
• PNG
• TIFF
[Link] BMP
Table 7.1. Number of bits and possible number of colors per pixel
Bits Colors
1 21 = 2
2 22 = 4
4 24 = 16
229
230
8 28 = 256
16 216 = 65,536
24 224 =
16,777,216
[Link] GIF
Graphics Interchange Format (GIF) is a common format for images that appear on Web pages. GIF uses
Lempel-Ziv-Welch (LZW) compression to minimize file size. No information is lost in the compression
process; a decompressed image is exactly the same as the original. GIF files can use a maximum of 8 bits per
pixel, so they are limited to 256 colors.
[Link] JPEG
Joint Photographic Experts Group (JPEG) is another popular format used on Web pages. JPEG can store 24
bits per pixel, so it is capable of displaying more than 16 million colors. Some information is lost during JPEG
conversion, but it usually doesn't affect the perceived quality of the image. JPEG is not a file format; it is a
compression scheme. JPEG File Interchange Format (JFIF) is a file format commonly used for storing and
transferring images that have been compressed according to the JPEG scheme.
[Link] EXIF
Exchangeable Image File (EXIF) is a file format used by digital cameras. It was originally developed by the
Japan Electronic Industry Development Association. The EXIF file contains an image compressed according
to the JPEG specification.
[Link] PNG
Portable Network Graphics (PNG) format provides the advantages of the GIF format but supports greater
color depth. PNG files can store colors with 8, 24, 32, or 48 bits per pixel, and grayscales with 1, 2, 4, 8, or 16
bits per pixel. PNG also supports alpha channel, so it's a suitable format for storing images that support a high
number of colors with transparency.
[Link] TIFF
Tag Image File Format (TIFF or TIF) can store images with arbitrary color depth, using a variety of
compression algorithms. The TIFF format can be extended as needed by the approval and addition of new
tags. This format is used by engineers when they need to add information in the image itself.
Almost all image file formats can also store metadata related to the image, such as scanner manufacturer, host
computer, type of compression, orientation, samples per pixel, and so on.
[ Team LiB ]
[ Team LiB ]
230
231
work with raster images, and the Metafile class provides functionality to work with
vector images. Both classes are inherited from the Image class. In this chapter we will
discuss the Image and Bitmap classes and their members. The Metafile class will be
discussed in Chapter 8.
We'll start this discussion with the Image class, which is defined in the
[Link] namespace. Understanding this class is important because we will
be using its members in our samples throughout this chapter and the next.
The Image class is an abstract base class for the Bitmap, Metafile, and Icon classes.
Some common Image class properties (all read-only) are described in Table 7.2.
Besides the properties discussed in Table 7.2, the Image class provides methods,
which are described in Table 7.3.
Now we will write an application that will use some of the properties and methods of
the Image class. You will learn how to open, view, manipulate, and save images. The
application is a simple image viewer.
231
232
an image.
PropertyItems Returns an array of
PropertyItem objects
for an image.
RawFormat Returns the format of an
image.
Size Returns the width and
height of an image.
VerticalResolution Returns the vertical
resolution, in pixels per
inch, of an image.
To begin:
1. Use Visual Studio .NET to create a Windows application project called ImageViewer.
2. Add a MainMenu control and some menu items to the form.
3. Change the text of the menu items to File, Open File, Save File, and Exit, and the name of these menu
items to FileMenu, OpenFileMenu, SaveFileMenu, and ExitMenu, respectively. The final form looks
like Figure 7.3.
4. Write menu click event handlers for the OpenFileMenu, SaveFileMenu, and ExitMenu items by
simply double-clicking on them.
The OpenFileMenu click event handler will allow us to browse and select one image and display it, the
SaveFileMenu click event handler will save the image as a new file name, and the ExitMenu click event
handler will simply close the application.
Before we write code for these menu event handlers, let's see how to create an Image object from a file and
how to display it using the DrawImage method of the Graphics class.
Method
Description
232
233
Creates an Image object from a file, a window handle, and a stream, respectively.
GetBounds
GetEncoderParameterList
GetFrameCount
Returns the total number of frames available in an image. Some images include multiple frames. Each frame
is a separate layer with different properties. For example, an animated GIF can have multiple frames with
different text and other properties.
GetPixelFormatSize
GetPropertyItem
GetThumbnailImage
IsAlphaPixelFormat
Returns true if the pixel format for an Image object contains alpha information.
IsCanonicalPixelFormat
IsExtendedPixelFormat
RemovePropertyItem
RotateFlip
Save
SaveAdd
233
234
Takes one parameter of type EncoderParameters that defines parameters required by the image encoder that
is used by the saveadd operation.
SelectActiveFrame
Selects a frame specified by the dimension and index. The first parameter of this method is the frame
dimension, which can be used to identify an image by its time, resolution, or page number. The second
parameter is the frame index of the active frame. Calling this method causes all changes made to the previous
frame to be discarded.
SetPropertyItem
The Image class provides three static methods to create an Image object: FromFile, FromHbitmap, and
FromStream.
For example, in the following line, FromFile constructs an Image object. Here curFileName is a string
variable that holds the file name:
We will see how to create Image objects from streams and bitmaps in later chapters.
After creating an Image object, you'll want to view the image. GDI+ and Windows Forms offer many ways to
view images. You can use a Form, PictureBox, or Button control as a container to view images. In most of
our samples, we will draw an image on a graphics surface (a form).
Tip
You can also use a picture box to view images. The PictureBox control is easy to use, but using a form as a
viewer provides more control and flexibility. For instance, use a PictureBox control when you do not need to
manipulate or resize images. If you need to manipulate images using operations such as zooming in and
zooming out, scaling, and skewing, use a Form object as the container because it is easy to change the size of
Form. Later in this chapter you will see how to use a picture box to draw images.
As we saw in Chapter 3, the DrawImage method of the Graphics class is used to draw an image. It has 30
overloaded forms. The simplest form of DrawImage takes an Image object and the starting point where it will
be drawn. You can also specify the area of a rectangle in which the image will be drawn. GraphicsUnit and
234
235
ImageAttributes are optional parameters, which we will discuss later in this chapter.
The following code snippet creates an Image object from a file, and draws the image using the DrawImage
method. The starting point of the image is (10, 10). You can put this code on the form's paint event handler.
Graphics g = [Link];
Image curImage = [Link](curFileName);
[Link](curImage, 10, 10);
The following code will fit an image into a rectangle that starts at point (10, 10) and has a width of 100 and a
height of 100.
Graphics g = [Link];
Image curImage = [Link](curFileName);
Rectangle rect = new Rectangle(20, 20, 100, 100);
[Link](curImage, rect);
If you want to fill the entire form with an image, you can use the ClientRectangle property of the form as
the default rectangle.
Graphics g = [Link];
Image curImage = [Link](curFileName);
[Link](curImage, [Link]);
Before we write code for the menu items event handler, we define string and Image type variables in the
application scope. Add the following at the beginning of the class:
// User-defined variables
private string curFileName = null;
private Image curImage = null;
Listing 7.1 shows the code for the OpenFileMenu click event handler. We use OpenFileDialog to browse
images and save the file name in the string variable after the user selects a file. Thus we create an Image
object from the selected file by using [Link]. We also call Invalidate, which forces the form to
repaint and call the paint event handler, where we will be viewing the image.
235
236
"Meta Files(*.emf;*.wmf;*.png)|*.emf;*.wmf;*.png";
[Link] = "ImageViewer: Open Image File";
[Link] = true;
// If OK, selected
if([Link]() == [Link])
{
// Read current selected file name
curFileName = [Link];
// Create the Image object using
// [Link]
try
{
curImage = [Link](curFileName);
}
catch(Exception exp)
{
[Link]([Link]);
}
}
// Repaint the form, which forces the paint
// event handler
Invalidate();
}
Now we write the [Link] method on the form's paint event handler. You can write a paint
event handler from the Properties window of the form by double-clicking on the paint event available in the
events list. Listing 7.2 shows our code, which simply calls DrawImage, using the default rectangle coordinates
as AutoScrollPosition, and the image's width and height.
Now we're ready to view images. Compile and run the application, use the Open File menu item to select an
image file, and the program will view it. In Figure 7.4, we open a file called [Link].
236
237
Clicking the Open button brings up the file for viewing, as shown in Figure 7.5.
Now we move to the Save File menu item. It allows you to save images in different file formats.
The Image class provides the Save method, which is used to save images to a specified format. The Save
method takes a file name (as string type) or a stream (a Stream object), and a specified format of type
ImageFormat class. Table 7.4 describes the properties of the ImageFormat class.
Note
The Emf and Wmf properties in the ImageFormat enumeration do not save a real metafile, but save the bitmap
as one metafile record. It will still be a bitmap.
237
238
Table 7.4. ImageFormat properties
Property
Description
Bmp
Emf
Specifies EMF (Enhanced Metafile Format). We will discuss this format in Chapter 8.
Exif
Gif
Guid
Icon
Jpeg
MemoryBmp
Png
Tiff
Wmf
Specifies WMF (Windows Metafile Format). We will discuss this format in Chapter 8.
Now we add code for the SaveFileMenu click event handler, as shown in Listing 7.3. We use
SaveFileDialog, which lets us specify the file name and saves an image using the format specified in the
dialog. We read the extension of the file name entered by the user, and on that basis we pass the ImageFormat
property in the Save method.
Note
238
239
The ImageFormat enumeration is defined in the [Link] namespace. Don't forget to add a
reference to this namespace in your application.
Now we write code for the ExitMenu click event handler. This menu simply closes the application. Hence we
call the [Link] method on this event handler, as shown in Listing 7.4.
239
240
[Link]();
}
Table 7.2 listed the Image class properties. Now we will read and display the properties of an image. We add a
Properties menu item to the main menu and write the code in Listing 7.5 as this menu click event handler. We
read the size, format, resolution, and pixel format of an image.
[ Team LiB ]
[ Team LiB ]
240
241
7.3.1 Creating a Thumbnail of an Image
A thumbnail is a small representation of an image. The Image class provides a method called
GetThumbnailImage, which is used to create a thumbnail. This method's first two parameters are
the width and height of the thumbnail image. The third parameter is
[Link], which is not used in GDI+ version 1.0 but must be passed in
for compatibility. The fourth parameter must be of type [Link]. This parameter is not used
in the current version. If both the width and height parameters are 0, GDI+ will return the
embedded thumbnail if there is one in the image; otherwise a system-defined size is used. For
most JPEG images from digital cameras, it is better to pass both zeros in for both parameters to
get the embedded thumbnail.
To test the thumbnail code, we add a menu named Options to the MainMenu control, as well as a
Create Thumbnail menu item. We add Create Thumbnail as a submenu item or on a button click
event handler, as Listing 7.6 shows. We create an [Link] parameter,
and then we call GetThumbnailImage with one-fourth the width and height of the original size,
followed by the DrawImage method.
Now we run the application and open [Link]. If we click the Create Thumbnail menu item,
the new thumbnail image looks like Figure 7.7.
241
242
Rotating and flipping are common operations in many imaging programs. Rotation rotates an
image at an angle that is a multiple of 90. Flipping reflects an image on an axis.
The RotateFlip method allows us to rotate and flip images. The value of RotateFlip is of type
RotateFlipType enumeration, which defines the direction of rotation and flipping. The members
of the RotateFlipType enumeration (listed in Table 7.5) are easy to understand.
To rotate and/or flip an image, call RotateFlip and pass in any of the values in Table 7.5. The
following code snippets show different rotation and flip options.
• Rotating 90 degrees:
[Link](RotateFlipType.Rotate90FlipNone);
• Rotating 180 degrees:
[Link](RotateFlipType.Rotate180FlipNone);
• Rotating 270 degrees:
[Link](RotateFlipType.Rotate270FlipNone);
• Flipping on the x-axis only, with no rotation:
[Link]([Link]);
• Flipping on the y-axis only, with no rotation:
[Link]([Link]);
• Flipping on the x- and y-axes, with no rotation:
[Link]([Link]);
• Rotating 180 degrees and flipping on the x-axis:
[Link](RotateFlipType.Rotate180FlipX);
Now let's add rotate and flip options to the ImageViewer application.
We add four submenus to the Options menu Rotate, Flip, Fit, and Zoom. We will cover the
Rotate and Flip options in this section, and Fit and Zoom in Sections 7.3.4 and 7.3.5,
respectively.
242
243
Table 7.5. RotateFlipType members
Member Description
Rotate180FlipNone 180-degree
rotation
without
flipping
Rotate180FlipX 180-degree
rotation with
a horizontal
flip
Rotate180FlipXY 180-degree
rotation with
horizontal
and vertical
flips
Rotate180FlipY 180-degree
rotation with
a vertical
flip
Rotate270FlipNone 270-degree
rotation
without
flipping
Rotate270FlipX 270-degree
rotation with
a horizontal
flip
Rotate270FlipXY 270-degree
rotation with
horizontal
and vertical
flips
Rotate270FlipY 270-degree
rotation with
a vertical
flip
Rotate90FlipNone 90-degree
rotation
without
flipping
Rotate90FlipX 90-degree
rotation with
a horizontal
flip
Rotate90FlipXY 90-degree
rotation with
horizontal
and vertical
flips
Rotate90FlipY 90-degree
rotation with
a vertical
flip
RotateNoneFlipNone No rotation
and no
243
244
flipping
RotateNoneFlipX No rotation,
with a
horizontal
flip
RotateNoneFlipXY No rotation,
with
horizontal
and vertical
flips
RotateNoneFlipY No rotation,
with a
vertical flip
We add three items to the Rotate submenu: 90, 180, and 270 (see Figure 7.8). These items rotate an image 90,
180, and 270 degrees, respectively. You can add as many items as you want. You can even allow users to
enter an arbitrary angle.
Now we add three items to the Flip submenu: FlipX, FlipY, and FlipXY (see Figure 7.9). These items flip an
image about the x-, y-, and xy-axes, respectively. You can add more items if you wish.
Within our program we give the menu items meaningful names. For example, the 90, 180, and 270 menu
items are represented by Rotate90Menu, Rotate180Menu, and Rotate270Menu, respectively. And we use
FlipXMenu, FlipYMenu, and FlipXYMenu to represent the FlipX, FlipY, and FlipXY menu items, respectively.
The next step is to write code for the menu item event handlers. To add them, we simply double-click on the
menu items. The code for the Rotate menu items is given in Listing 7.7. We check whether the Image object
has been created and then call RotateFlip with the appropriate value. We also call Invalidate to redraw the
image with the new settings.
// Rotate 90 degrees
private void Rotate90Menu_Click(object sender,
[Link] e)
{
244
245
if(curImage != null)
{
[Link](
RotateFlipType.Rotate90FlipNone);
Invalidate();
}
}
// Rotate 180 degrees
private void Rotate180Menu_Click(object sender,
[Link] e)
{
if(curImage != null)
{
[Link](
RotateFlipType.Rotate180FlipNone);
Invalidate();
}
}
// Rotate 270 degrees
private void Rotate270Menu_Click(object sender,
[Link] e)
{
if(curImage != null)
{
[Link](
RotateFlipType.Rotate270FlipNone);
Invalidate();
}
}
Now let's run and test the application. We open an image, and it looks like Figure 7.10.
245
246
Figure 7.11. The image of Figure 7.10, rotated 90 degrees
246
247
We also add code for the Flip menu item click event handlers, as shown in Listing 7.8. We simply call
RotateFlip with an appropriate value.
// Flip X
private void FlipXMenu_Click(object sender,
[Link] e)
{
if(curImage != null)
{
[Link](
[Link]);
Invalidate();
}
}
// Flip Y
private void FlipYMenu_Click(object sender,
[Link] e)
{
if(curImage != null)
{
[Link](
[Link]);
Invalidate();
}
}
// Flip X and Y both
private void FlipXYMenu_Click(object sender,
[Link] e)
{
if(curImage != null)
{
[Link](
[Link]);
Invalidate();
}
}
Now if we flip the image shown in Figure 7.10, we can see the difference. The FlipX option generates the
image shown in Figure 7.14.
247
248
Figure 7.14. The image of Figure 7.10, flipped in the-direction
Figure 7.16. The image of Figure 7.10, flipped in both the-and the-directions
248
249
An application that manipulates images often needs to fit them within the height and/or width of a drawing
surface. A fit-width option sets the width of an image to the width of the surface (a form or a control), and a
fit-height option sets the height of an image to the height of the surface. The fit-all option sets both the height
and the width of an image to the height and width of the surface.
Let's add fit options to our ImageViewer application. We add four menu items to the Fit submenu: Fit Height,
Fit Width, Fit Original, and Fit All, which will fit the height, width, original size of the image, and both height
and width, respectively (see Figure 7.17).
To implement the fit options, we need to add Rectangle and Size variables at the application level, as
follows:
249
250
We will use curRect to store the current rectangle of the image and originalSize for the original size of the
image.
Now we need to modify the OpenFileMenu click event handler. The new code is given in Listing 7.9. We
activate autoscrolling by setting the AutoScroll and AutoScrollMinSize properties of the form to true. We
create a rectangle from the current size of the image. We also save the current size of the image by setting the
Width and Height properties of originalSize.
The paint event handler must also be modified. The new code is given in Listing 7.10. We use the curRect
rectangle to view the image.
250
251
if(curImage != null)
{
// Draw image using the DrawImage method
[Link](curImage, new Rectangle
([Link].X,
[Link].Y,
(int)([Link]),
(int)([Link])));
}
}
The last step is to add event handler code for the Fit Height, Fit Width, Fit Original, and Fit All menu options,
as shown in Listing 7.11. For the Fit Width option, we set the width of the current rectangle to the width of the
form; for the Fit Height option, we set the height of the current rectangle to the height of the form; for the Fit
All option, we set both the height and width of the current rectangle to the height and width of the form; and
for Fit Original, we set the current rectangle's height and width to the height and width of the original file
saved as originalSize.
Now we compile and run the application, and we view an image. The original image looks like Figure 7.18.
251
252
Figure 7.18. An image in ImageViewer
The Fit Width option generates the image shown in Figure 7.19.
The Fit Height option generates the image shown in Figure 7.20.
Aspect Ratio
To see an image correctly, you may want to maintain its aspect ratio (the ratio of height to width).
To do so, you need to modify the code so that when you select Fit Width or Fit Height, the width
and the height are changed according to the original ratio.
252
253
The Fit Original option generates the image shown in Figure 7.21.
The Fit All option generates the image shown in Figure 7.22.
Before we finish our ImageViewer application, let's add one more option: zooming.
Adding zoom-in and zoom-out features requires only one operation: multiplying the height and width of the
image by a zoom factor. The zoom factor is the ratio of the current size of the image to the desired new size of
the image. For example, suppose that we want to zoom in an image by 200 percent. We must multiply the
current size of the image by 200 percent, or 2 (200/100 = 2 times). If we want to zoom out an image by 25
percent, we need to multiply the size of the image by 25 percent, or 0.25 (25/100 = 0.25 times).
Now let's add the zoom features to our application. As is typically done, we add five items to the Zoom
submenu: 25, 50, 100, 200, and 500 (see Figure 7.23). In our code we use Zoom25, Zoom50, Zoom100, Zoom200,
and Zoom500, respectively, to represent these menu items, and we add the appropriate menu item click event
handlers by double-clicking on the menu items.
253
254
Now we add a double variable that represents the zoom factor. The default zoom factor is 1.0. We add the
following line to the class at the application level:
Next we modify the OpenFileMenu click event handler slightly. We change the AutoScrollMinSize property
as follows:
We multiply the image height and width by the zoom factor to represent an image with an appropriate zoom
setting.
The next step is to modify the paint event handler. Here we need to multiply the height and width of the image
by the zoom factor. The new DrawImage method, shown here, calls the paint event handler of Listing 7.10:
The last step is to add Zoom menu item click event handlers and calculate the zoom factor. Listing 7.12 shows
the code for the Zoom menu item click event handlers. We calculate the zoom factor by dividing the zoom
value by 100. We also call the Invalidate method to repaint the image with the new zoom setting.
254
255
Invalidate();
}
}
private void Zoom50_Click(object sender,
[Link] e)
{
if(curImage != null)
{
curZoom = (double)50/100;
Invalidate();
}
}
private void Zoom100_Click(object sender,
[Link] e)
{
if(curImage != null)
{
curZoom = (double)100/100;
Invalidate();
}
}
private void Zoom200_Click(object sender,
[Link] e)
{
if(curImage != null)
{
curZoom = (double)200/100;
Invalidate();
}
}
private void Zoom500_Click(object sender,
[Link] e)
{
if(curImage != null)
{
curZoom = (double)500/100;
Invalidate();
}
}
Using the method just described, we can zoom an image in and out to any percentage. Let's run the application
and open an image. Our original image looks like Figure 7.24.
255
256
The Zoom | 200 option generates the image shown in Figure 7.27.
Figure 7.27. The image of Figure 7.24 with 200 percent zoom
The Zoom | 500 option generates the image shown in Figure 7.28.
Figure 7.28. The image of Figure 7.24 with 500 percent zoom
256
257
Congratulations! You have successfully written an image viewer application that can be used for various
purposes. Now we will discuss some additional imaging options.
[ Team LiB ]
[ Team LiB ]
You can create animated images by using graphics tools such as Macromedia Fireworks or CorelDRAW, but
GDI+ doesn't support the creation of animated images. When you create animated images, you must specify
the order of frames and the time interval between them.
The GDI+ library provides the ImageAnimator class to deal with animated file formats using time-based
frames. At this time, GDI+ supports only multiframe GIFs and TIFFs. ImageAnimator has four static
methods: Animate, CanAnimate, StopAnimate, and UpdateFrames.
1. The Animate method displays a framed image as an animation. This method takes parameters of type
Image and EventHandler. Image is the image you want to animate. The event is triggered when the
currently displayed frame is changed.
2. The CanAnimate method returns true when an image has timebased frames.
3. The StopAnimate method terminates an animation. It takes parameters of type Image and
EventHandler.
4. The UpdateFrames method will move to the next frame and render it the next time the image is
drawn.
Now let's write an application that will play animated images. We create a Windows application and add a
MainMenu control and two button controls to the form. We also add two menu items: Open File and Exit. We
change the text and names of the menu items and button controls as shown in Figure 7.30.
257
258
Figure 7.30. An image animation example
The Open File menu item allows us to browse images, and the Exit menu item closes the form. Listing 7.13
gives the code for the click event handlers for these two menu items.
Listing 7.13 The Open File and Exit menu item click event handlers
Now we rename the two buttons Start Animation and Stop Animation, respectively, and write click event
handlers by double-clicking on them. The code for the StartAnimationBtn event handler is given in Listing
7.14. We create an Image object by calling FromImage, which takes an image file as its only argument. Then
we use the CanAnimate method to check if the image can be animated. If it can, we call Animate, which plays
the animation.
258
259
Listing 7.14 The StartAnimationBtn click event handler
On the StopAnimationBtn click event handler, we check whether there is an Image object, and we call
StopAnimate to stop the animation as shown in Listing 7.15.
Now we add OnPaint and OnFrameChanged methods to the application. The code for these methods is given
in Listing 7.16. In the OnPaint method, we call the UpdateFrames method of ImageAnimator and then call
DrawImage to draw the image. In the OnFrameChanged method, we repaint the form by calling Invalidate.
Now compile and run the application. You can browse animated images on your system or download the files
from online and select a file. The Start Animation button will start playing the animation. The Stop Animation
button will stop the animation.
Figure 7.31 shows the first frame of the animation sample provided with this book (download code from
online).
259
260
[ Team LiB ]
[ Team LiB ]
The Bitmap class provides about a dozen overloaded forms of the constructors. You can create a Bitmap
object from a bitmap file, or from Image, Stream, string, or Type objects. When you create a Bitmap object,
260
261
you can also specify the size of the bitmap, the resolution of the Graphics object, and the pixel format of the
bitmap.
The code snippet in Listing 7.17 creates Bitmap objects from an Image and file name with or without the size
of the Bitmap included.
Besides the constructor, the Bitmap class provides two static methods FromHicon and FromResource which
can be used to create a Bitmap object from a window handle to an icon and from a Windows resource (.res
file), respectively.
Viewing a bitmap using the Bitmap class is similar to viewing an image. After constructing a Bitmap object,
you just pass it as a parameter to DrawImage. The following code snippet creates a Bitmap object from a file
and views the bitmap by calling the DrawImage method of a Graphics object associated with a form. You can
write this code on a menu or a button click event handler.
Graphics g = [Link]();
Bitmap bitmap = new Bitmap("[Link]");
[Link](bitmap, 20, 20);
[Link]();
The Bitmap class doesn't define any properties beyond those defined in the Image class. However, Bitmap
does provide additional methods. Among them are FromHicon, FromResource, GetHbitmap, GetHicon,
GetPixel, LockBits, MakeTransparent, SetPixel, SetResolution, and UnlockBits.
The FromHicon and FromResource methods create a Bitmap object from a window handle to an icon and
from a Windows resource, respectively. The GetHbitmap and GetHicon methods create a Windows HBITMAP
structure and a window handle to an icon.
The GetPixel and SetPixel methods get and set the color of the specified pixel of an image. These methods
are useful when an application needs to blur images, change the color of specific pixels, change the contrast of
pixels, and so on. You can blur an image by reducing the color depth of pixels. We will use GetPixel and
SetPixel in examples in this chapter and the next.
The following line of code returns the color of a pixel at positions x = 10 and y = 10:
261
262
The following code snippet uses SetPixel to change all pixels between point (50, 50) and point (60, 60) to
red:
SetResolution sets the resolution of a bitmap. This method takes two parameters of type float, which
represent the horizontal resolution and vertical resolution in dots per inch.
MakeTransparent makes the default color transparent to a bitmap. This method takes either no arguments or a
single argument of type Color:
or
[Link](curColor);
To test the methods and properties of Bitmap, we create a Windows application and add Open File and Exit
menu items as in the previous examples. Then we add controls for a group box, text boxes, a button, a check
box, and some labels. The final form looks like Figure 7.33. We can set the resolution and transparency of the
bitmap from here.
262
263
// Variables
private Bitmap curBitmap;
private float imgHeight;
private float imgWidth;
private string curFileName;
As usual, we browse images on the Open File menu item click event handler and close the form on the Exit
menu item click event handler. We also create a Bitmap object from the selected file and store the height and
width of the image, as Listing 7.18 shows.
Listing 7.18 The Open File and Exit menu item event handlers
263
264
Now we write code on the paint event handler to view the bitmap (see Listing 7.19).
The code for the Apply Settings button click event handler is given in Listing 7.20. It reads values for
horizontal and vertical resolution from two text boxes and sets values for a bitmap using the SetResolution
method. It also uses the MakeTransparent and SetPixel methods.
If we run the application and click the Apply Settings button (see Figure 7.34), a small red rectangle appears,
showing that the color of that part of the image has been changed to red.
264
265
The LockBits and UnlockBits methods are used to lock and unlock a bitmap into system memory. LockBits
takes three parameters of type Rectangle, ImageLockMode enumeration, and PixelFormat enumeration and
returns an object of type BitmapData. The rectangle is the portion of the bitmap that will be locked in system
memory.
ImageLockMode provides the access level on the data. Its members include ReadOnly, ReadWrite,
UserInputBuffer, and WriteOnly. The PixelFormat enumeration defines the format of color data for each
pixel.
Note
[ Team LiB ]
[ Team LiB ]
An application can create an Icon object from a stream, string, icon, icon file, or type by using the Icon class
constructors with the size of the icon as an optional parameter. The Icon class provides four read-only
properties Handle, Height, Size, and Width which return a window handle to the icon, height, size, and
width of an icon, respectively.
Listing 7.21 creates an Icon object from an icon file and sets the icon of a form using the Form class's Icon
property.
265
266
Listing 7.21 Creating an icon and setting a form's Icon property
The FromHandle method of the Icon class creates an Icon object from a window handle to an icon (HICON).
The Save method saves an Icon object to a stream, and the ToBitmap method converts an Icon object to a
Bitmap object. Listing 7.22 creates a Bitmap object from an Icon object using ToBitmap and draws the
bitmap using DrawImage.
Figure 7.35 shows the output from Listings 7.21 and 7.22.
Sometimes you will need to convert a Bitmap object into an Icon object. The following code snippet shows
how to do this:
266
267
Icon curIcon;
curIcon = [Link]([Link]());
[ Team LiB ]
[ Team LiB ]
The DrawImage method has about two dozen overloaded forms one of which lets us provide the destination
points for an image. The original image will be drawn after its coordinates are mapped to the destination
points a process called skewing. We will see an example in a moment. First let's examine the necessary form
of DrawImage.
To translate an image from its original coordinates to the mapped coordinates, an application needs to create
an array of new coordinates and call DrawImage, passing this array as the second parameter. For example, the
following code snippet creates an array of points and passes it to the DrawImage method.
Point[] pts =
{
new Point(X0, Y0),
new Point(X1, Y1),
new Point(X2, Y2)
};
[Link](curImage, pts);
Now let's create a Windows application and add a MainMenu control with an Open File menu item. Let's also
add a button to the form. Our final form will look like Figure 7.36.
267
268
The complete code is given in Listing 7.23. The Open File menu item click event handler opens an image and
creates a Bitmap object from the selected file. The paint event handler views the image. If skewImage is true,
the paint event handler calls the DrawImage method with an array of points. The Skew Image button click
event handler simply sets skewImage to true.
268
269
if(curBitmap != null)
{
if(skewImage)
{
[Link](curBitmap, pts);
}
else
{
[Link](curBitmap, 0, 0);
}
}
// Dispose of object
[Link]();
}
If you run the application and open an image, the normal view looks like Figure 7.37.
If you click Skew Image, the new output looks like Figure 7.38.
269
270
[ Team LiB ]
[ Team LiB ]
An application must create transparent pens and brushes to draw transparent graphics objects. An application
can use the [Link] method to specify the ratio of all four components in a color. For example, the
following code snippet creates a fully opaque green pen and brush.
Pen solidPen =
new Pen([Link](255, 0, 255, 0), 10);
SolidBrush solidColorBrush =
new SolidBrush([Link](255, 0, 255, 0));
Pen transPen =
new Pen([Link](128, 0, 255, 0), 10);
SolidBrush semiTransBrush =
new SolidBrush([Link](60, 0, 255, 0));
Listing 7.24 views an image and draws lines and a rectangle with different transparencies.
270
271
Listing 7.24 Drawing transparent graphics objects
[ Team LiB ]
[ Team LiB ]
271
272
Drawing transparent images is different from drawing transparent graphics objects such as lines, rectangles, or
ellipses. To draw transparent graphics objects, we simply create a transparent color and use this color when
we create a pen or a brush.
Drawing transparent images is controlled by the color matrix (represented by the ColorMatrix class), which
defines the transparency of the image. Acolor matrix is applied to an image when we call DrawImage. The
DrawImage method takes an argument of type ImageAttributes. The SetColorMatrix method of
ImageAttributes sets a color matrix to the ImageAttributes type. Passing ImageAttributes to DrawImage
applies the color matrix to the image. Chapter 8 discusses this process in more detail.
As usual, we create a Windows application. In this application we will draw a large image, and a small image
on top of the large image. To make this application more interesting, we add a transparency control to the
application so that we can adjust the transparency of the top image. The final form looks like Figure 7.40.
Now let's add a TrackBar control to the form. We set the Maximum and Minimum properties of TrackBar to 10
and 0, respectively. Then we write a TrackBar control scroll event so that when we scroll the track bar, it can
manage the transparency of the image.
Note
We have defined a float type variable in the class as follows: float tpVal = 1.0f;
Now we convert the TrackBar value to a floating value so that we can use it in the ColorMatrix class to set
the color of the image, as Listing 7.25 shows. The ColorMatrix class constructor takes an array, which
272
273
contains the values of matrix items. The Item property of this class represents a cell of the matrix and can be
used to get and set cell values. Besides the Item property, the ColorMatrix class provides 25 MatrixXY
properties, which represent items of the matrix at row (x + 1) and column (y + 1). MatrixXY properties can be
used to get and set an item's value. See Chapter 10 (Section 10.7.1) for more details.
We will now view both images on the form's paint event, as Listing 7.26 shows. We create an Image object
and view the first image. Then we create a ColorMatrix object with transparency and set it with the
ImageAttribute property. Later we attach the ImageAttribute property to the second image when we draw
it using the DrawImage method.
[ Team LiB ]
[ Team LiB ]
273
274
To view an image in a PictureBox control, we simply create an Image object using any of
the Image class methods and set the [Link] property to that image.
Listing 7.27 views an image in a picture box. To test this code, create a Windows
application, add a PictureBox control to the form by dragging it from the toolbox, and
add code to the form-load event handler.
274
275
CenterImage The image is
displayed in the
center of the
picture box.
Normal The image is
placed in the upper
left corner of the
picture box and
clipped if it is
larger than the
control.
StretchImage The image is
stretched or shrunk
to fit the size of the
picture box.
[ Team LiB ]
[ Team LiB ]
To make our program even more interesting, we will determine the size of the saved image at runtime. Create
a Windows application and add two text boxes, two tables, and a button control to the form. The text boxes
are used to specify the height and width of the saved image, and the button is used to save the image with the
new size, as shown in Figure 7.42.
275
276
Then we create and view the image at the form's paint event handler, as shown in Listing 7.28.
On the Save Image button click, we ask the user to specify a file name and we call the Save method of the
Image class, which saves an image in the given format. As Listing 7.29 shows, we also read the size of the
new image from textBox1 and textBox2 and specify the size when we create a new Bitmap object from the
existing image.
Now we save an image with a width of 200 and a height of 200. The results are shown in Figure 7.43.
Figure 7.43. New image, with width of 200 and height of 200
276
277
[ Team LiB ]
[ Team LiB ]
SUMMARY
GDI+ provides a significant improvement in imaging over GDI. In this chapter we discussed the basic
imaging capabilities of GDI+, as defined in the [Link] namespace. We focused mainly on the
Image and Bitmap classes, and by now you should understand how to use the .NET Framework to work with
images. We saw how to open, view, save, and manipulate images. We also saw some interesting functionality,
including creating thumbnail images, rotating and flipping, zooming in and out, skewing and stretching, and
animation.
In addition, we covered some advanced imaging features, including drawing transparent images and setting
bitmap resolution and color. Throughout this chapter, we developed a real-world application that you can use
in your programming career.
Imaging functionality doesn't end here. Advanced imaging functionality, which is defined in the
[Link] namespace, will be the focus of Chapter 8. Some of the topics yet to be discussed
are bitmaps, metafiles, color maps, encoding and decoding images, and details of the color matrix.
[ Team LiB ]
[ Team LiB ]
277
278
• Converting metafiles
[ Team LiB ]
[ Team LiB ]
Why might you want to lock bitmap pixels? Rendering (painting) bitmaps and images is a
resource-consuming operation, and it is one of the most frequently performed graphics
operations. Suppose you want to change the color or intensity level of a bitmap. You could
always loop though the bitmap pixel by pixel and use SetPixel to modify its properties, but
that is a huge time- and resource-consuming operation.
Note
The code used in this chapter uses classes defined in the [Link]
namespace, so be sure to add a reference to this namespace in your applications.
A better option would be to use LockBits and UnlockBits. These methods allow you to
control any part of the bitmap by specifying a range of pixels, eliminating the need to loop
through each pixel of the bitmap.
To use this option, first call LockBits, which returns the BitmapData object. BitmapData
specifies the attributes of a bitmap. Before we examine the members of the BitmapData
class, let's take a look at the LockBits and UnlockBits methods. The LockBits method is
defined as follows:
UnlockBits takes a single parameter of type BitmapData, which was returned by LockBits.
This method is defined as follows:
The ImageLockMode enumeration used in LockBits provides the access level to the data.
Table 8.1 describes the members of ImageLockMode.
278
279
The pixel format defines the number of bits of memory associated with one pixel of data, as
well as the order of the color components within a single pixel. Generally the number of bits
per pixel is directly proportional to the quality of the image because the pixel can store more
colors.
To demonstrate the use of LockBits and UnlockBits, we will change the pixels of a bitmap using the
GetPixel and SetPixel methods. As we discussed in Chapter 7, an application can use GetPixel and
SetPixel to get and set the colors of each pixel of a bitmap. To set a bitmap color to grayscale or other colors,
an application reads the current color using GetPixel, calculates the grayscale value, and calls SetPixel to
apply the new color.
In the following code snippet we read the color of a pixel; calculate the grayscale value by applying a formula
to the red, green, and blue components; and call SetPixel to set the pixel's new grayscale color.
Listing 8.1 draws an image with its original color settings and later redraws it in grayscale. The Width and
Height properties of the Bitmap class are used to loop through each pixel of the bitmap, and SetPixel is used
to set the pixel's color to grayscale.
279
280
Table 8.2. PixelFormat members
Member
Description
Alpha
The pixel data contains alpha values that are not premultiplied.
DontCare
Format1bppIndexed
1 bit per pixel, using indexed color. The color table therefore has two colors in it.
Format4bppIndexed
Format8bppIndexed
Format16bppArgb1555
16 bits per pixel, giving 32,768 colors; 5 bits each are used for red, green, and blue, and 1 bit is used for alpha.
Format16bppGrayScale
Format16bppRgb555
16 bits per pixel; 5 bits each are used for red, green, and blue. The last bit is not used.
Format16bppRgb565
16 bits per pixel; 5 bits are used for red, 6 bits for green, and 5 bits for blue.
Format24bppRgb
24 bits per pixel; 8 bits each are used for red, green, and blue.
Format32bppArgb
32 bits per pixel; 8 bits each are used for alpha, red, green, and blue. This is the default GDI+ color
combination.
Format32bppPArgb
32 bits per pixel; 8 bits each are used for alpha, red, green, and blue. The red, green, and blue components are
premultiplied according to the alpha component.
Format32bppRgb
280
281
32 bits per pixel; 8 bits each are used for red, green, and blue. The last 8 bits are not used.
Format48bppRgb
48 bits per pixel; 16 bits each are used for red, green, and blue.
Format64bppArgb
64 bits per pixel; 16 bits each are used for alpha, red, green, and blue.
Format64bppPArgb
64 bits per pixel; 16 bits each are used for alpha, red, green, and blue. The red, green, and blue components
are premultiplied according to the alpha component.
Gdi
GDI colors.
Indexed
Color-indexed values, which are an index to colors in the system color table, as opposed to individual color
values.
Max
PAlpha
Undefined
281
282
[Link](curBitmap, 0, 0, [Link],
[Link]);
// Dispose of object
[Link]();
In the previous section we discussed how to set the pixel format of a bitmap by reading pixels one by one.
You can also set the pixel format by using the BitmapData class and its members.
The BitmapData object specifies the attributes of a bitmap, including size, pixel format, starting address of the
pixel data in memory, and length of each scan line (stride). These properties are described in Table 8.3. All of
the properties have both get and set types.
Now let's set the color of pixels in a bitmap by using LockBits and UnlockBits. This approach is faster than
using the SetPixel method. Listing 8.2 uses LockBits and UnlockBits to set a bitmap pixel format. First we
create an Image object from a file, followed by a Bitmap object from the Image object. Then we call
LockBits, which returns a BitmapData object. Next we call PixelFormat to set the pixel format. You can use
any of the PixelFormat enumeration values. Finally, we call UnlockBits to unlock the locked bits. Notice
that the lockedRect rectangle in the LockBits method is the size of the bitmap.
Listing 8.2 Using LockBits and UnlockBits to set the grayscale of a bitmap
Property
Description
Height
PixelFormat
Scan0
282
283
Stride
Width
Figure 8.1 shows the output from Listing 8.2. The entire bitmap is grayscale.
If a bitmap is huge and we want to change the format of only a few pixels, LockBits and UnlockBits really
help. Using these methods, we can lock and render only the part of a bitmap we want to work on instead of
rendering the entire bitmap. Suppose we want to change the pixel format of only the section of the bitmap
starting at point (50, 50) and ending at point (200, 200). We simply change the rectangle passed to LockBits.
Listing 8.3 locks only that portion of the image specified by a rectangle.
283
284
[Link](bmpData);
// Draw image with new pixel format
[Link](curImage, 0, 0,
[Link], [Link]);
}
Figure 8.2 shows the output from Listing 8.3. You may not see any difference between this illustration and
Figure 8.1, but if you run the sample code yourself, you will notice that the color of only a small rectangle in
the image is changed.
[ Team LiB ]
[ Team LiB ]
The Metafile class provides functionality to work with different metafile formats
including Windows Metafile Format (WMF), Enhanced Metafile Format (EMF), and
an extension to Enhanced Metafile Format (EMF+). The Metafile class provides
about 40 overloaded forms of its constructor.
284
285
Loading and viewing a metafile is similar to viewing a bitmap. An application can load
a metafile from a stream, string, or IntPtr instance with different formats and
locations. The simplest way to load and view a metafile is to pass the file name in the
Metafile constructor and call DrawImage.
The Metafile class is derived from the Image class and has no methods and properties
besides those inherited from the Image class.
285
286
8.2.1 Metafile Class Method
The Metafile object can create a metafile programmatically. Three simple steps are
required to create a metafile:
Now let's create a metafile programmatically. In Listing 8.5 we use GetHdc to get the
handle to a device context (HDC), and we use this handle to create a metafile called
[Link]. After creating the metafile, we use the FillRectangle, FillEllipse,
and DrawString methods to add a rectangle, an ellipse, and a string, respectively.
Calling these methods adds records describing the respective objects to the metafile.
Finally, we release the objects.
286
287
// Draw an ellipse on the Metafile object
LinearGradientBrush lgBrush =
new LinearGradientBrush(
rect, [Link], [Link], 45.0f);
[Link](lgBrush, rect);
// Draw text on the Metafile object
rect.Y += 110;
[Link]("MetaFile Sample",
new Font("Verdana", 20),
lgBrush, 200, 200,
[Link]);
// Release objects
[Link](hdc);
[Link]();
[Link]();
}
Running the code in Listing 8.5 will create a new metafile in your application's folder.
Figure 8.4 shows the image described by the metafile.
As mentioned earlier, after creating a metafile, you can view it as you would any other
image, using the DrawImage method of the Graphics class.
Tip
Using the same approach, you can easily create a metafile editor similar to
GDI+Painter, in which you can draw graphics objects and save them as metafiles. You
can even change the GDI+Painter application code to do so.
287
288
Using enhanced metafiles, you can add personalized data to a metafile as defined in the
MSDN documentation:
There are three types of EMFs: EMF only, EMF+ dual, and EMF+ only. The EmfType
enumeration is used to find out the type of EMF programmatically. This enumeration
provides three members: EmfOnly, EmfPlusDual, and EmfPlusOnly. The EmfOnly and
EmfPlusDual types of records can be played by both GDI and GDI+; EmfPlusOnly
types of records can be played only by GDI+.
You can use the Metafile object constructors to specify the type of EMF you want to
create. The following code creates an EMF+ dual metafile:
Metafile curMetafile =
new Metafile(hdc, [Link],
"[Link]");
The EnumerateMetafile method can be used to read and play back records of a
metafile one by one. Each record is sent to [Link], which
is used to read the data for a record. This method has many overloaded forms.
GDI/GDI+ Record
Each metafile record describes a command that is capable of drawing, filling,
or changing the graphics state of a surface. For example, clearing a graphics
object, drawing a rectangle, filling an ellipse, creating a graphics container,
and ending a graphics container are all examples of records. After creating a
288
289
Listing 8.6 reads records from a metafile and displays data for these records
individually. In the EnumMetaCB callback, we check whether the record type is
FillEllipse, FillRects, DrawEllipse, or DrawRects and display the corresponding
data.
289
290
Figure 8.5 shows the output from Listing 8.6. Our program displays the record type,
flag, data size, and data. The record in this example contains only FillRectangle
methods. If more records are used to create a metafile, you will see messages for the
various record types.
A metafile header contains attributes such as type, size, and version of a metafile. It is
represented by the MetafileHeader class. GetMetafileHeader returns a metafile
header and has many overloaded methods.
The MetafileHeader class has the eight methods listed in Table 8.4.
290
291
metafile is in the
Windows placeable
WMF format.
Properties of the MetafileHeader class represent various attributes of metafiles, including size, version, and
type, as Table 8.5 shows. All of these properties are read-only.
Reading metafile attributes is simple: Create a Metafile object, get its header attributes using
GetMetafileHeader, and display the value of these attributes in a message box. Listing 8.7 reads metafile
header attributes, including type, bounds, size, and version.
Property
Description
Bounds
DpiX
Gets the horizontal resolution, in dots per inch, of a metafile in the form of a rectangle.
DpiY
Gets the vertical resolution, in dots per inch, of a metafile in the form of a rectangle.
EmfPlusHeaderSize
LogicalDpiX
LogicalDpiY
291
292
MetafileSize
Type
Version
WmfHeader
[ Team LiB ]
[ Team LiB ]
A color remap table is used to convert the existing colors of an image to new colors by
applying a color mapping to them. The ColorMap class represents a color remap table. It
defines the mapping between existing colors and the new colors to which they will be
converted. When the map is applied to an image, any pixel of the old color is converted to the
new color.
The ColorMap class has only two properties NewColor and OldColor both of type Color.
OldColor represents an existing color, and NewColor represents the new color to which the
existing color will be converted.
292
293
Note
Each ColorMap object maps a single color. To map multiple colors, you must create multiple
ColorMap objects.
To see ColorMap in action, we create a Windows application and add a MainMenu control to
the form. We also add three menu items to the main menu and use their menu item click event
handlers to test our code.
Listing 8.8 gives code for the ColorMap menu click event handler. As usual, we create
Graphics and Image objects. We will map the red, yellow, and blue colors to green, navy, and
aqua, respectively. We create three ColorMap objects and a ColorMap array from these objects,
and we set their OldColor and NewColor properties to the desired colors. Then we create an
ImageAttributes object and apply the ColorMap array to it by calling the SetRemapTable
method. After that the ImageAttributes object is used as a parameter of DrawImage.
293
294
[Link]();
}
Figure 8.7 shows the output from Listing 8.8. The original image is on the left; the image on
the right shows remapped colors. On your system you will notice that the red, yellow, and blue
colors are converted to green, navy, and aqua.
The ColorMatrix class defines a 5x5 matrix that contains coordinates for the ARGB (alpha,
red, green, and blue) space (from 0,0 to 4,4). The Item property of this class represents a cell
of the matrix and can be used to get and set cell values. Besides the Item property, the
ColorMatrix class provides 25 MatrixXY properties, which represent items of the matrix at the
xth row and yth column. The MatrixXY properties can be used to get and set item values.
You can use an array of points to initialize a ColorMatrix object, or you can assign values
directly to the ColorMatrix properties. The following code snippet creates an array of points
that is used as an argument to the ColorMatrix constructor, and then sets the values of
Matrix34 and Matrix11.
float[][] ptsArray ={
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 0.5f, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix clrMatrix = new ColorMatrix(ptsArray);
if( clrMatrix.Matrix34 <= 0.5) //3rd row, 4th col
{
clrMatrix.Matrix34 = 0.8f;
clrMatrix.Matrix11 = 0.3f; //1st row, 1st col
}
The SetColorMatrix method of the ImageAttributes class uses a color matrix. We will see
how to use a color matrix in your applications in the sample applications that follow. Chapter
10 discusses ColorMatrix in more detail.
A color palette defines an array of colors that make up a color palette. The colors in the palette
are limited to 32-bit ARGB colors (8 bits each for the alpha, red, green, and blue components).
294
295
The color palette can be used to increase the color intensity without increasing the number of
colors used. This process creates a halftone, and it offers increased contrast at a cost of
decreased resolution.
The ColorPalette class defines an array of colors that make up a color palette. This class has
only two properties: Entries and Flags. The Entries property returns an array of colors, and
the Flags property represents how the color information is interpreted. Table 8.6 lists valid
values for the Flags property.
[ Team LiB ]
[ Team LiB ]
Sometimes we need to fill a graphics shape with a texture that's smaller or larger than the
graphics shape. The wrap mode represented by the WrapMode enumeration specifies how a
texture is tiled when it is larger or smaller than the area being filled. The members of the
WrapMode enumeration are described in Table 8.7.
SetWrapMode is used to set the wrap mode of a texture or gradient. This method takes three
parameters: a wrap mode (WrapMode), a color (Color), and a clamp (Boolean). The last two
parameters are optional. If the clamp value is true, the texture will be clamped to the image
boundary; otherwise there is no clamping.
Listing 8.9 uses this method. First we create an ImageAttributes object and set the wrap
mode using SetWrapMode. Then we create an Image object using FromFile, followed by a
295
296
call to DrawImage with an argument of the ImageAttributes object. DrawImage draws an
image on the form, rendered using the colors defined by ImageAttributes.
Figure 8.8 shows the output from Listing 8.9. If the image is smaller than the surface, images are wrapped.
296
297
Note
The WrapMode enumeration is defined in the [Link].Drawing2D namespace. Don't forget to add the
namespace reference to the project.
The SetGamma method sets the gamma value, which represents the brightness of a graphics shape, for all
graphics objects, including images, brushes, and pens. Gamma values range from 0.1 to 5.0 (normally 0.1 to
2.2), with 0.1 being the brightest and 5.0 the darkest.
This method takes a floating type parameter as gamma value and a second optional parameter of the
ColorAdjustType enumeration type. Using the ColorAdjustType enumeration from the Imaging namespace,
you can even specify which GDI+ objects use this color adjustment. For example, if you want to apply
gamma values on text only, you can do so using [Link], which is described in Table 8.8.
The following code snippet sets the gamma value of ImageAttributes.
Now you can use this ImageAttributes object as a parameter of the DrawImage method.
A color matrix represents how colors are represented in an Image object. As we saw in Section 8.3.2, the
ColorMatrix object represents a color matrix. SetColorMatrix applies a color matrix to an image. This
method takes a parameter of the ColorMatrix class, with two optional parameters of ColorMatrixFlag and
ColorAdjustType enumerations.
297
298
Table 8.8. ColorAdjustType members
Member
Description
Any
Reserved
Bitmap
Brush
Count
Default
For all objects that do no have their own color adjustment information
Pen
Text
Often we don't want all graphics objects to be affected by a color adjustment. Suppose we have some graphics
shapes, an image, and some text, and we want only the image to be affected by the color adjustment specified
by the SetColorMatrix method. The ColorAdjustType enumeration allows us to specify which graphics
objects use the color adjustment information. Table 8.8 describes the members of the ColorAdjustType
enumeration.
ColorMatrixFlag specifies the types of images and colors that will be affected by the color adjustment
settings. The ColorMatrixFlag enumeration has three members: AltGrays, Default, and SkipGrays.
AltGrays is not available for use except by the .NET Framework internally, so basically ColorMatrixFlag
provides the option of affecting gray colors or not. The Default value means that all colors will be affected;
SkipGrays means that gray shades will not be affected. (You may want to skip some of the gray shades that
are used when you're smoothing images.)
In Listing 8.10 we create ColorMatrix and ImageAttributes objects. Then we call SetColorMatrix to add a
color matrix to [Link] takes ColorMatrix as its first
argument.
298
299
[Link]([Link]);
Rectangle rect = new Rectangle(20, 20, 200, 100);
Bitmap bitmap = new Bitmap("[Link]");
// Create an array of matrix points
float[][] ptsArray =
{
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 0.5f, 0},
new float[] {0, 0, 0, 0, 1}
};
// Create a color matrix
ColorMatrix clrMatrix = new ColorMatrix(ptsArray);
// Set ColorMatrix properties
if( clrMatrix.Matrix34 <= 0.5)
{
clrMatrix.Matrix34 = 0.8f;
clrMatrix.Matrix11 = 0.3f;
}
// Create image attributes
ImageAttributes imgAttributes = new ImageAttributes();
// Set color matrix
[Link](clrMatrix,
[Link],
[Link]);
[Link]([Link], rect);
rect.Y += 120;
[Link]([Link], rect);
// Draw image
[Link](bitmap,
new Rectangle(0, 0, [Link], [Link]),
0, 0, [Link], [Link],
[Link], imgAttributes);
// Dispose of object
[Link]();
}
Figure 8.9 shows the output from Listing 8.10. A rectangle and a circle are drawn, and then an image with
lower color resolution, as specified by ImageAttributes.
299
300
8.4.4 The SetNoOp and SetColorKey Methods
The SetNoOp method sets the NoOp correction value for Graphics objects. When NoOp is set, no adjustments to
the color will be made during the rendering process.
SetColorKey sets the low and high color values for graphics objects and shapes. The SetColorKey method
takes a parameter of type ColorAdjustType enumeration (see Table 8.8) that specifies the type of the graphics
objects and shapes to be affected by SetColorKey.
Listing 8.11 applies gamma effect and sets color key values using the SetColorKey method.
//[Link]([Link]);
300
301
the output will look like Figure 8.11. Using SetNoOp cancels all image attribute effects.
The SetThreshold method sets the transparency range (threshold) for a specified category. This method takes
one parameter representing a threshold value ranging between 0.0 and 1.0, and an optional second parameter
of type ColorAdjustType. The value of the threshold specifies a cutoff point for each component of color. For
example, suppose that the threshold is set to 0.8 and the value of the red component is 240. Because the value
of the red component (240) is greater than 0.8, the red component will be changed to 255 (full intensity).
[Link](0.8f, [Link]);
We have already discussed how the SetRemapTable method sets a remap table to the specified ColorMap
object. The OldColor and NewColor properties of ColorMap represent old and new colors, respectively.
SetBrushRemapTable converts only the colors of brushes. The ColorMap class also provides both OldColor
and NewColor properties.
Listing 8.12 creates a ColorMap object, sets its OldColor and NewColor properties, and then calls
SetBrushRemapTable with the ColorMap object.
301
302
The ImageAttributes class provides a "clear" method for almost every set method we have discussed in this
section. The clear methods take either no parameter or an optional parameter of ColorAdjustType
enumeration. These clear methods are listed in Table 8.9.
Method
Description
ClearBrushRemapTable
ClearColorKey
Clears color key values for the graphics objects specified by the ColorAdjustType enumeration.
ClearColorMatrix
ClearGamma
Clears gamma effect for the graphics objects specified by the ColorAdjustType enumeration.
ClearNoOp
ClearOutputChannel
Clears output channel selection for graphics objects specified by the ColorAdjustType enumeration.
ClearOutputChannelColorProfile
Clears output channel selection and color profile file for graphics objects specified by the ColorAdjustType
enumeration.
ClearRemapTable
Clears color remap table for graphics objects specified by the ColorAdjustType enumeration.
ClearThreshold
Clears threshold value for graphics objects specified by the ColorAdjustType enumeration.
Suppose that we wanted to clear the color key values for all graphics objects. We would use the
ClearColorKey method as follows:
[Link]([Link]);
302
303
[ Team LiB ]
[ Team LiB ]
[Link](fileName, [Link]);
In fact, the Save method does much more than just save an image in different formats. An
overloaded Save method can take an argument of type EncoderParameters, which
represents an encoder. An encoder is responsible for converting a file from one format to
another, and a decoder reverses it. The encoder is responsible for saving an image to a
format defined by codec parameters.
The SaveAdd method has two overloaded forms. The first form adds a frame to the file or
stream specified in a previous call to the Save method. This method can be used to save
selected frames from a multiple-frame image to another multiple-frame image.
The second form, which takes two parameters (Image and EncoderParameters) adds a
frame to the file or stream specified in a previous call to the Save method.
303
304
clear this up, look at Figure 8.12, which shows how the different elements relate to each
other.
As you can see, the Save method of the Image class consumes EncoderParameters, which
is a collection of type EncoderParameter. An EncoderParameter object represents an
encoder. We use the Encoder property to attach an Encoder object to the
EncoderParameter object.
An Encoder object encapsulates a globally unique identifier (GUID) that identifies the
category of an image encoder parameter represented by EncoderParameter. This Encoder
object is attached to an EncoderParameter object through its Encoder property.
An Encoder object is created by use of the Encoder class constructor, which takes one
parameter of type Guid.
The Encoder class provides one property, Guid, and a set of static fields, which represent the
encoder properties. The Guid property of the Encoder class returns a GUID attached to an
encoder. Table 8.10 describes the fields.
Suppose that you want to save a JPEG file to a TIFF file with 24-bit compression. In Listing
8.13 we first create an EncoderParameters object. Then we create an array of
ImageCodecInfo objects, which provide members to retrieve information about installed
image codecs, including the codec name, MIME type, format, version, and signature. The
properties of the ImageCodecInfo class are listed in Table 8.12. All of these properties have
both get and set types.
304
305
Table 8.10. Encoder fields
Field Description
ChrominanceTable Specifies
chrominance
table as the
parameter
category.
ColorDepth Specifies color
depth as the
parameter
category.
Compression Specifies
compression as
the parameter
category.
LuminanceTable Specifies
luminance table
as the parameter
category.
Quality Specifies quality
as the parameter
category.
RenderMethod Specifies
rendering
method as the
parameter
category.
SaveFlag Specifies save
flag as the
parameter
category.
ScanMethod Specifies scan
method as the
parameter
category.
Transformation Specifies
transformation as
the parameter
category.
Version Specifies version
as the parameter
category.
Property
Description
Encoder
Represents an encoder associated with this encoder parameter. Both get and set types.
NumberOfValues
305
306
Returns the number of elements in the array of values stored in an encoder parameter.
Type
ValueType
GDI+ provides several built-in image encoders and decoders. The ImageCodecInfo class provides two static
methods: GetImageEncoders and GetImageDecoders, which return the built-in GDI+ image encoders and
decoders in an array of ImageCodecInfo objects.
MIME Types
MIME stands for "Multipurpose Internet Mail Extensions." It is a standard way of classifying file types on
the Internet. By specifying a MIME type, applications can easily identify the type of file and can extract
more information and attributes about a file. Here are some useful links to Web resources that provide
information about MIME types:
[Link]
[Link]
[Link]
Property
Description
Clsid
Returns the Guid structure that contains a GUID identifying a specific codec.
CodecName
DllName
Returns a string containing the path name of the codec's DLL. If there is no DLL, returns null.
FilenameExtension
Returns a string containing the file name extension(s) used by the codec. The extensions are separated by
semicolons.
Flags
306
307
FormatDescription
FormatID
Returns a Guid structure containing a GUID that identifies the codec's format.
MimeType
Returns a string containing the codec's Multipurpose Internet Mail Extensions (MIME) type.
SignatureMasks
SignaturePatterns
Version
In Listing 8.13, after creating an EncoderParameters object, we use the Encoder and EncoderParameter
objects to create three encoder parameters. These encoder parameters are responsible for changing image
color depth, compression, and transformation. We use the Encoder class and set its ColorDepth property.
Later the Encoder object is used as an argument to EncoderParameter, which subsequently is added to
EncoderParameters. Then we also set the Transformation and Compression properties to CompressionLZW
and TransformRotation180, respectively.
When we are done adding EncoderParameter objects to EncoderParameters, we call the Save method of
Bitmap with the EncoderParameters object. Our sample saves the bitmap to a TIFF file with 24 color depth,
and LZW compression.
307
308
encoderParam = new EncoderParameter(encoder, 24L);
[Link][0] = encoderParam;
// Set compression mode to LZW
encoder = [Link];
encoderParam = new EncoderParameter(encoder,
(long)[Link]);
[Link][1] = encoderParam;
// Set transformation to 180 degrees
encoder = [Link];
encoderParam = new EncoderParameter(encoder,
(long)EncoderValue.TransformRotate180);
[Link][2] = encoderParam;
// Save file as a TIFF file
[Link]("[Link]", imgCodecInfo,
encoderParams);
// Dispose of object
[Link]();
}
8.5.3 Retrieving Information from Digital Images or Tagged Data of TIFF Files
The PropertyItems property of the Image class returns an array of PropertyItem objects, which describe the
attributes of an image. Each instance of PropertyItem has four properties Id, Len, Type, and Value which
represent the identifier, length, type, and value of the property, respectively.
One common use of PropertyItem is to read the tagged data of TIFF files or the information from the JPEG
images taken from a digital camera. Listing 8.14 opens a JPEG file and uses the [Link]
property to get an array of PropertyItem objects. After that we make a loop and read all property item IDs
and values.
You can add this code to a button or a menu click event handler. Don't forget to add a reference to the
[Link] namespace.
Graphics g = [Link]();
[Link]([Link]);
Image curImage = [Link]("[Link]");
// Return an array of property items using
// Image's PropertyItems property
PropertyItem [] imgProperties = [Link];
// Total items
string str = [Link]();
[Link]("Properties "+str);
// Read items and display in a message box
for (int i=0; i< [Link]; i++)
{
str = [Link];
str = "Id :"+imgProperties[i].[Link]();
str += " ,Value:"
+[Link](imgProperties[i].Value);
[Link](str);
}
// Dispose of object
[Link]();
Saving a bitmap as a PNG file or any another format is simple if we use ImageCodecInfo settings. We create
an ImageCodecInfo object with MIME type image/png and use it as the second argument to the Save method
308
309
Listing 8.15 will save [Link] to [Link]. You can save a file to other formats by changing the
MIME type.
[ Team LiB ]
[ Team LiB ]
SUMMARY
This chapter covered more advanced imaging concepts. We discussed the [Link]
namespace classes, their members, and how to use them. At the beginning of the chapter you learned how to
set grayscale images using SetPixel, LockBits, and UnlockBits. In the same section we discussed how to
set the color of a bitmap.
In the section covering the Metafile class and related functionality, you learned the metafile types supported
by GDI+, how to create new metafiles, and how to read and enumerate existing metafiles. We also saw how to
read metafile header information.
The Graphics class provides methods to set the attributes of images. We covered how to set the colors and
other attributes of images using the color map table, color matrix, and color palette. In this section we saw
some real-world applications, such as drawing transparent images, wrapping images, and setting gamma
values of images.
This chapter also discussed how to use the Encoder, EncoderParameter, EncoderParameters, and
ImageCodecInfo classes and their members to encode images. We discussed some real-world scenarios in
which you may want to change the color depth and compression of images. We also learned how to read
tagged data from TIFF files and how to convert among different image formats.
309
310
[ Team LiB ]
[ Team LiB ]
Note
Before using any class discussed in this chapter, an application should reference
the [Link].Drawing2D namespace by adding the following line:
using [Link].Drawing2D
Apart from blending, gradient brushes, graphics containers, graphics paths, and
matrix-related classes, the [Link].Drawing2D namespace provides
many enumerations. Some of the enumerations we have discussed in previous
chapters; the rest will be covered in this chapter.
310
311
A graphics path, which
contains a series of connected
lines and curves.
GraphicsPathIterator A graphics path can have
many subpaths. This class
provides a way to iterate
through them.
GraphicsState Graphics object state, which
is returned by the
BeginContainer method.
HatchBrush A hatch brush. Discussed in
Chapter 4.
LinearGradientBrush Linear gradient brush.
Discussed in Chapter 4.
Matrix A 3x3 affine matrix that
represents a geometric
transformation.
PathData Contains the graphical data of
a graphics path.
PathGradientBrush A brush that fills a graphics
path with a gradient.
RegionData Data of a region.
[ Team LiB ]
[ Team LiB ]
A line has three parts: the line body, starting cap, and ending cap. The line starts with a
starting cap and ends with an ending cap. The part that connects these two caps is the line
body. The caps and body of a line can have different styles. Figure 9.1 shows two lines with
different starting and ending cap and body styles.
Figure 9.1. Lines with different starting cap, ending cap, and dash styles
The ends of a line can have different caps. Table 9.2 shows some of the available line cap
styles.
A line body can have its own style, called the dash style. Figure 9.2 shows four different
dash styles.
311
312
Figure 9.2. Line dash style
Each line dash style can also have its own cap style, which is called a line dash cap. Figure
9.3 shows three different line dash caps.
The Pen object specifies the line caps and line styles being used to draw lines. To create a line with caps and
styles, we create a Pen object, set its line cap and line style properties (or methods) and use the Pen object to
draw the lines.
Table 9.3 lists the members of the Pen class that can be used to set line caps and line styles.
There is no direct way to apply line caps and line styles to a line. We must go through the Pen object. As we
covered in previous chapters, to draw a line we must have a Pen object specifying the color and width of the
pen used when we call the DrawLine method of the Graphics class. The Pen object also provides members for
attaching line caps and line styles to a pen. After we attach line caps and styles to a pen, we use this pen to
draw lines.
In Listing 9.1 we create a Pen object with a specified color and width. Then we set the line caps using the
StartCap and EndCap properties of the Pen class, followed by the DashStyle and DashOffset properties.
After that we call DrawLine and dispose of the objects.
Table 9.3. Pen Class members for setting line caps and styles
Member
Description
312
313
StartCap
Property that gets or sets the cap style used at the beginning of the line. Takes a LineCap enumeration
member.
EndCap
Property that gets or sets the cap style used at the end of the line. Takes a LineCap enumeration member.
CustomStartCap
Property that gets or sets a custom cap to use at the beginning of the line. Takes a CustomLineCap object.
CustomEndCap
Property that gets or sets a custom cap to use at the ending of the line. Takes a CustomLineCap object.
DashCap
Property that gets or sets the cap style used at the end of the dashes that make up a dashed line. Takes a
DashCap enumeration, which has only three members: Flat, Round, and Triangle.
DashOffset
Property that gets and sets the dash offset that is, the distance from the start of a line to the beginning of a
dash pattern.
DashPattern
Property that specifies the length of each dash and space in a dash pattern. Takes an array of floating values.
The first element of this array sets the length of a dash, the second element sets the length of a space, the third
element sets the length of a dash, and so on.
DashStyle
Dash lines can have their own styles. This property gets and sets dash line styles, which are represented by the
DashStyle enumeration. The DashStyle enumeration has six members Custom, Dash, DashDot, DashDotDot,
Dot, and Solid that represent lines consisting of a custom pattern, dashes, a dash-dot repeating pattern, a
dash-dot-dot repeating pattern, dots, and a solid line, respectively.
SetLineCap
Method that sets the values of all three parts (the starting line cap, ending line cap, and dash style) of a line.
Graphics g = [Link]();
[Link]([Link]);
// Create a pen
Pen blackPen = new Pen([Link], 10);
// Set the line caps and line styles
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = 40;
[Link](blackPen, 20, 10, 200, 10);
// Dispose of objects
[Link]();
[Link]();
313
314
We will cover line caps and styles in more detail in Sections 9.1.3 through 9.1.5.
In the previous sections we discussed the LineCap, DashStyle, and DashCap enumerations, which represent
the line cap, line dash style, and dash cap, respectively. Now we will write an application and use these
enumerations.
We create a Windows application and a MainMenu control with three menu items on the form. We call these
menu items GetCapStyle, LineDashStyle, and LineDashCap, respectively, and write menu click event
handlers by double-clicking on them. On the GetCapStyle menu item click event handler, we will read
different line caps and generate output using these line caps; on the LineDashStyle menu item click event
handler, we will generate lines with different dash styles; and on the LineDashCap menu item click event
handler, we will generate output with different line dash caps.
The GetCapStyle menu item click event handler is shown in Listing 9.2. We create a pen and set the starting
and ending caps using the StartCap and EndCap properties of the Pen object, and then we draw a line.
314
315
}
The output of Listing 9.2 looks like Figure 9.4, in which the lines have different caps.
The LineDashStyle menu item click event handler code is given in Listing 9.3. We create a pen and set the
dash style and dash offset values using the DashStyle and DashOffset properties of the Pen object, and then
we draw lines.
Figure 9.5 shows the output from Listing 9.3. The lines have different dash styles.
315
316
The GetCapStyle menu item click event handler code is given in Listing 9.4. We create a pen and set the dash
cap styles using the DashCap property of the Pen object.
Figure 9.6 shows the output from Listing 9.4. The lines have different dash caps: triangular, flat, and round,
respectively.
So far we have applied line caps and line styles only to lines, but these effects can also be applied to other
objects, including curves, rectangles, and ellipses. However, some of these objects impose limitations. For
example, rectangles, ellipses, and closed curves do not have starting and ending caps, so the StartCap and
EndCap properties of a pen will not affect them.
Let's add one more menu item to MainMenu, called OtherObjects. The code for its menu item click event
handler is given in Listing 9.5. We create three pens with different colors and widths; set their line cap, dash
style, and dash cap properties; and draw a rectangle, an ellipse, and a curve.
316
317
Listing 9.5 Drawing other objects using line caps, dash styles, and dash caps
Figure 9.7 shows the output from Listing 9.5. Each graphics object rectangle, ellipse, and curve has a
different style.
Figure 9.7. A rectangle, an ellipse, and a curve with different line styles
317
318
Sometimes we need to use custom caps. Figure 9.8 shows a line with customized caps of different sizes.
The CustomLineCap and AdjustableArrowCap classes provide functionality to draw custom line caps.
CustomLineCap allows us to define custom caps, which can be attached to a pen then an application can use
the pen to draw graphics objects.
The CustomLineCap class constructor takes two parameters of type GraphicsPath. The first parameter
defines the fill path, which identifies the fill for the custom cap. The second parameter defines the stroke path,
which defines the outline of the custom cap. The fill path and stroke path parameters cannot be used at the
same time.
To create a CustomLineCap object, first we create a GraphicsPath object and add items to the path such as a
line, ellipse, or rectangle using any of the add methods. Then we pass the GraphicsPath object as an
argument to CustomLineCap. The following code snippet shows how to create a CustomLineCap object:
Once we have a CustomLineCap object, we can set the CustomStartCap and CustomEndCap properties of the
pen to apply custom line caps. We will see a full working example of custom line caps in a moment.
318
319
[Link] Line Joins
A line join defines how lines and curves are joined in a graphics path. The LineJoin enumeration represents a
line join. Its members are described in Table 9.5.
We can set the line join of a pen using its LineJoin property. To see the line joins, we create a Windows
application and add a group box, four radio buttons, and a button to the form. The final form looks like Figure
9.9.
Property
Description
BaseCap
BaseInset
StrokeJoin
How lines and curves in the path that will be stroked are joined. LineJoin enumeration type.
WidthScale
Width scale of custom line cap. A WidthScale value of 2 means that the cap will be double the pen size that is
drawing the line cap.
Member
Description
319
320
Bevel
Miter
MiterClipped
Round
When we select different line join types and hit the Apply LineJoin button, the application draws lines with
different joins.
The code for the Apply LineJoin button click event handler and DrawJoinedLines method is given in Listing
9.6. As the listing shows, the Apply LineJoin button click event handler calls the DrawJoinedLines method
with a LineJoin value determined by the current selection.
320
321
Point[] pts =
{
new Point(150, 20),
new Point(50, 20),
new Point(80, 60),
new Point(50, 150),
new Point(150, 150)
};
// Create a rectangle using lines
Point[] pts1 =
{
new Point(200, 20),
new Point(300, 20),
new Point(300, 120),
new Point(200, 120),
new Point(200, 20)
};
// Draw lines
[Link](redPen, pts);
[Link](redPen, pts1);
// Dispose of object
[Link]();
}
Now if we run the code, the Bevel line join output looks like Figure 9.10.
321
322
We have already seen how to use the StartCap and EndCap properties of a Pen object to set the starting and
ending caps of lines. We have also seen how to use the StartCustomCap and EndCustomCap properties to set
customized starting and ending caps.
To understand caps better, take a look at Figure 9.13. The rectangle A is a line cap. The starting cap is
triangular, and the ending cap is round.
The GetStrokeCaps and SetStrokeCaps methods of the CustomLineCap class can also be used to get and set
the starting and ending caps of a custom cap. The SetStrokeCaps method takes two arguments of type
LineCap enumeration and sets the caps for the starting and ending points of lines. Listing 9.7 creates custom
line caps and sets them using the SetStrokeCaps method. After creating custom line caps, we create a pen
and set its CustomStartCap and CustomEndCap properties, which use the pen to draw a line.
322
323
new Point(3, -3)
};
GraphicsPath path = new GraphicsPath();
[Link](points);
// Create a custom line cap from the path
CustomLineCap cap =
new CustomLineCap(null, path);
// Set the starting and ending caps of the custom cap
[Link]([Link], [Link]);
// Create a Pen object and set its starting and ending
// caps
Pen redPen = new Pen([Link], 15);
[Link] = cap;
[Link] = cap;
[Link] = [Link];
// Draw the line
[Link](redPen,
new Point(100, 100),
new Point(400, 100));
// Dispose of object
[Link]();
}
Adjustable arrow caps allow you to set the size of the cap's base cap, height, width, and joins. The
AdjustableArrowCap class, which is inherited from the CustomLineCap class, represents an adjustable
arrow-shaped line cap.
The AdjustableArrowCap class constructor takes three parameters: the width of the arrow as a floating value,
the height of the arrow as a floating value, and a Boolean value (optional) that, if true, indicates that the
arrow cap is filled.
float w = 2;
float h = 5;
bool fill = false;
AdjustableArrowCap myArrow =
new AdjustableArrowCap(w, h, fill);
323
324
Besides having CustomLineCap methods and properties, AdjustableArrowCap provides four properties:
Filled, Height, Width, and MiddleInset. The Height and Width properties represent the height and the
width, respectively, of an arrow cap. The Filled property indicates whether an arrow cap is filled. The
MiddleInset property represents the distance between the outline of the arrow cap and the fill.
Now let's add an AdjustableArrowCap option to our application. We add one menu item to the form, along
with a menu item click event handler, as shown in Listing 9.8. We create two AdjustableArrowCap objects
and set their BaseCap, BaseInset, StrokeJoin, and WidthScale properties. Then we create a black Pen
object with a width of 15 and set the CustomStartCap and CustomEndCap properties of the pen as
AdjustableArrowCap objects. Finally, we use this pen to draw a line with DrawLine.
Figure 9.15 shows the output from Listing 9.8. The end caps have different sizes.
[ Team LiB ]
[ Team LiB ]
324
325
A graphics path is a set of connected lines, curves, and other simple graphics objects, including
rectangles, ellipses, and text. A path works as a single graphics object, so an effect applied to
the graphics path will be applied to all the components of the path. For example, if a graphics
path contains a line, a rectangle, and an ellipse and we draw the path using a red pen, all three
components (line, rectangle, and ellipse) of the graphics path will be drawn with the red pen.
To create and use a graphics path, we create a GraphicsPath object and add its components by
using add methods. For example, you can use the AddLine, AddRectangle, and AddEllipse
methods to add a line, a rectangle, and an ellipse, respectively, to the graphics path. After
adding components to a path, you can use DrawPath or FillPath to draw and fill it.
By default, all graphics shapes of a path are connected to one another and treated as a single
entity with a collection of points and point types. But by using StartFigure and CloseFigure,
an application can draw more than one image.
The GraphicsPath class represents a graphics path in the .NET Framework library. It provides
six overloaded constructors, which take as arguments a fill mode, array of points, and array of
bytes (an array of PathPointTypes enumerations that defines the type of each corresponding
point in the point array) to construct a GraphicsPath object. The following code snippet uses
different overloaded constructors to create GraphicsPath objects.
In this function, pts represents an array of Point structures, and types represents an array of
bytes, which takes the PathPointType enumeration types, defined as follows:
byte[] types = {
(byte)[Link],
(byte)[Link],
(byte)[Link] };
The GraphicsPath object includes an array of points and an array of types. Point types that
make up shapes include starting points, ending points, and Bézier curve points. The
PathPointType enumeration defines the type of a point in a graphics path. The members of the
PathPointType enumeration are described in Table 9.6.
325
326
Now let's create a simple graphics path. Listing 9.9 gives the code for a simple graphics path
with a line, a rectangle, and an ellipse. To test this code, create a Windows application, add a
reference to the [Link].Advanced2D namespace, and add the code on the form's
load, or a button, or a menu item click event handler. The code creates a graphics path using
GraphicsPath; adds two lines, a rectangle, and an ellipse using AddLine, AddRectangle, and
AddEllipse, respectively; and draws the path using a red pen.
326
327
marking the
points.
PathTypeMask Mask point,
which allows
us to show or
hide points.
Start Starting point
of a graphics
path.
Figure 9.16 shows the output from Listing 9.9: two lines, a rectangle, and an ellipse.
You can also fill a path with FillPath. If you replace the DrawPath line in Listing 9.9 with:
the code will generate a new figure that looks like Figure 9.17.
Note
In a graphics path, all lines and curves are connected, even though you don't connect them explicitly. Objects
like rectangles and circles may not be connected (unless you connect them explicitly) but they are still part of
the path.
327
328
9.2.2 Shaped Forms and Graphics Paths
Graphics paths are very useful when you need to create shaped (nonrectangular) forms and controls. Using a
graphics path, you can also write a form with a text-based shape. For example, you can write a form
application that looks like Figure 9.18, which includes a text string, two ellipses, and two rectangles.
Writing applications with shaped forms is easy if we use graphics paths. First we create a GraphicsPath
object and add components (such as rectangles, ellipses, or text) to the path. Then we create a Region object
from the graphics path and set it as the form's Region property. For example, Listing 9.10 adds text, two
rectangles, and two ellipses to a graphics path, creates a Region object from this graphics path, and sets it as
the Region property of the form. The output of this code will generate a form that looks like Figure 9.18.
To test this code, create a Windows application and add this code to the form's load event handler.
Let's examine the properties and methods of the GraphicsPath class before we start using them. Table 9.7
describes the properties.
The GraphicsPath class provides more than a dozen add methods to add graphics objects to a path. Among
these methods are AddArc, AddBezier, AddBeziers, AddCloseCurve, AddCurve, AddEllipse, AddLine,
328
329
AddLines, AddPath, AddPie, AddPolygon, AddRectangle, AddRectangles, and AddString. These methods
are used to add an arc, a Bézier, a set of Béziers, a closed curve, a curve, an ellipse, a line, a set of lines, a
path, a pie, a polygon, a rectangle, a set of rectangles, and a string, respectively. Other methods, which don't
belong to the add category, are described in Table 9.8.
Property
Description
FillMode
Represents the fill mode of a graphics path, which determines how the interior of a graphics path is filled.
This property is a FillMode enumeration type and has two values: Alternate and Winding.
PathData
Returns a PathData object containing path data for a graphics path. The path data of a graphics path is
composed of arrays of points and types. The Points property of PathData returns an array of points, and the
Types property returns an array of types of points.
PathPoints
PathTypes
PointCount
The winding mode specifies that areas are filled according to the nonzero winding rule, which
says that you can determine whether a test point is inside or outside a closed curve as follows:
Draw a line from a test point to a point that is distant from the curve. Count the number of times
the curve crosses the test line from left to right, and the number of times the curve crosses the test
line from right to left. If those two numbers are the same, the test point is outside the curve;
otherwise the test point is inside the curve.
329
330
9.2.4 Subpaths
A graphics path can contain many subpaths. Having subpaths provides better control over individual paths.
An application can break a graphics path into subpaths by using the StartFigure method. It can close open
subpaths by using the CloseFigure or CloseAllFigures methods. StartFigure starts a new subpath of a
path, and CloseFigure closes the opened subpath. CloseAllFigures closes all subpaths of a graphics path.
Listing 9.11 uses the StartFigure method to create three subpaths, and the CloseFigure and
CloseAllFigures methods to close open figures. The first path contains an arc and a line, the second path
contains two lines and a curve, and the third path contains two lines.
Method
Description
ClearMarkers
Clears all markers from a path if any were set with [Link].
CloseAllFigures
CloseFigure
Flatten
GetLastPoint
Reset
Removes all points and types from a path and sets the fill mode to Alternative.
Reverse
SetMarkers
StartFigure
Transform
330
331
Warp
Widen
Replaces a path with curves that enclose the area that is filled when the path is drawn by the specified pen.
Figure 9.19 shows the output from Listing 9.11. There are three unconnected subpaths.
331
332
The Reverse method can be used to reverse the order of points in a path, and the Reset method to remove
(empty) all points from a path. The following code snippet shows how to use these two methods:
[Link]();
[Link]();
As mentioned earlier, a graphics path is a set of graphics subpaths. We can determine the number of subpaths
and the related data of a subpath by using the GraphicsPathIterator class. This class allows us to iterate
through all the subpaths of a graphics path.
The Count and SubpathCount properties of GraphicsPathIterator return the total number of points and the
number of subpaths in a graphics path, respectively. The CopyData method can be used to copy the points of a
path and their types. It returns the number of points, which is also the number of types copied.
The HasCurves method returns true if a path has curves in it; otherwise it returns false. The NextMarker
method moves the iterator to the next marker in the path. The NextPathType method returns the starting and
ending indices of the next group of data points that all have the same type.
The NextSubpath method returns the starting index, ending index, and a Boolean value of true if the subpath
is closed (false if the subpath is open), and moves to the next subpath. The Rewind method resets the iterator
to the beginning of the path.
Listing 9.12 creates and draws a graphics path and uses GraphicsPathIterator to find and show the data for
all subpaths.
332
333
// Get the Graphics object
Graphics g = [Link];
// Create a rectangle
Rectangle rect = new Rectangle(50, 50, 100, 50);
// Create a graphics path
GraphicsPath path = new GraphicsPath();
PointF[] ptsArray =
{
new PointF(20, 20),
new PointF(60, 12),
new PointF(100, 20)
};
// Add a curve, a rectangle, an ellipse, and a line
[Link](ptsArray);
[Link](rect);
rect.Y += 60;
[Link](rect);
[Link](120, 50, 220, 100);
// Draw path
[Link]([Link], path);
// Create a graphics path iterator
GraphicsPathIterator pathIterator =
new GraphicsPathIterator(path);
// Display total points and subpaths
string str = "Total points = "
+ [Link]();
str += ", Sub paths = "
+ [Link]();
[Link](str);
// Rewind
[Link]();
// Read all subpaths and their properties
for(int i=0; i<[Link]; i++)
{
int strtIdx, endIdx;
bool bClosedCurve;
[Link](out strtIdx,
out endIdx, out bClosedCurve);
str = "Start Index = " + [Link]()
+ ", End Index = " + [Link]()
+ ", IsClosed = " + [Link]();
[Link](str);
}
}
[ Team LiB ]
[ Team LiB ]
The Graphics class provides methods and properties to define the attributes of graphics objects.
For example, you can set the rendering quality of text using the TextRenderingHint property.
The smoothing mode represents the quality of the graphics objects, the compositing quality
represents the quality of composite images, the compositing mode represents whether pixels from
a source image overwrite or are combined with background pixels, and the interpolation mode
333
334
represents how intermediate values between two endpoints are calculated. These attributes are set
with the SmoothingMode, CompositingMode, CompositingQuality, and InterpolationMode
properties which are applicable for an entire Graphics object. For example, if you set the
SmoothingMode property of a Graphics object to AntiAlias, all graphics objects attached to that
Graphics object will be anti-aliased.
A graphics container is a temporary graphics object that acts as a canvas for graphics shapes,
allowing an application to set a container property separately from the main Graphics object. An
application can apply properties to a Graphics object within a container, and these properties
won't be available outside of that container. Thus we can selectively apply properties to
Graphics objects.
In Figure 9.20, for example, a Graphics object includes three graphics containers, each with
different properties. These properties are not available outside of their containers. All graphics
objects inside a container may be affected by the container property. It's also possible to have
nested containers.
Graphics containers do not inherit their parent's settings. In Figure 9.20, for example, the
Graphics object is a container whose compositing quality is set to high, and whose smoothing
mode is set to high-speed. The graphics containers won't have high-speed and high-quality
rendering unless we set them within the container itself. The smoothing mode of graphics
container A is set to anti-aliasing; that of graphics container B is set to high quality. Graphics
container C is a nested container within graphics container A, with interpolation mode set to
high.
Before we discuss graphics containers in more detail, let's take a look at graphics states.
During the life cycle of a Graphics object, the object maintains a list of graphics states. These
graphics states fall into various categories depending on the operations being applied to the
Graphics object. For example, setting the compositing quality of a Graphics object changes the
object's state.
334
335
Graphics states can be divided into three categories:
1. Quality settings
2. Transformations
3. Clipping region
The first state of the Graphics object involves the quality of shapes and images. This state
changes when you set the quality of a Graphics object using the SmoothingMode,
TextRenderingHint, CompositingMode, CompositingQuality, and InterpolationMode
properties of the Graphics class.
Transformation is another state that a Graphics object maintains. Transformation is the process
of changing graphics objects from one state to another by rotation, scaling, reflection, translation,
and shearing.
The Graphics object maintains two transformation states: world and page. The world
transformation defines the conversion of world coordinates to page coordinates. World
coordinates are coordinates that you define in your program, and page coordinates are
coordinates that GDI+ uses to expose the object coordinates. The page transformation defines the
conversion of page coordinates to device coordinates. Device coordinates determine how a
graphics object will be displayed on a particular display device.
Note
The world unit (by default) is always defined as a pixel. For example, in the following code
snippet a rectangle will be drawn starting at 0 pixels from the left edge and 0 pixels from the top
edge, with width and height of 100 and 50 pixels, respectively.
Graphics g = [Link]();
[Link]([Link], 0, 0, 100, 50);
Page coordinates may be different from world coordinates, depending on the page unit and page
scaling of the Graphics object. For example, if the page unit is an inch, the page coordinates will
start at point (0, 0), but the width and height of the rectangle will be 100 inches and 50 inches,
respectively.
335
336
The
document
unit (1/300
inch) as the
unit of
measure.
Inch An inch as
the unit of
measure.
Millimeter A
millimeter
as the unit
of measure.
Pixel A pixel as
the unit of
measure.
Point A printer's
point (1/72
inch) as the
unit of
measure.
World The world
unit as the
unit of
measure.
The PageScale and PageUnit properties define a page transformation. The PageUnit property defines the
unit of measure used for page coordinates, and the PageScale property defines the scaling between world and
page units for a Graphics object. The PageUnit property takes a value of type GraphicsUnit enumeration,
which is defined in Table 9.9.
Listing 9.13 draws three ellipses with the same size but different PageUnit values: Pixel, Millimeter, and
Point.
Figure 9.21 shows the output from Listing 9.13. Although the parameters to DrawEllipse are the same, we
336
337
The third state of the Graphics object is the clipping region. A Graphics object maintains a clipping region
that applies to all items drawn by that object. You can set the clipping region by calling the SetClip method.
It has six overloaded forms, which vary in using a Graphics object, graphics path, region, rectangle, or handle
to a GDI region as the first parameter. The second parameter in all six forms is CombineMode, which has six
values: Complement, Exclude, Intersect, Replace, Union, and Xor. The Clip property of the Graphics
object specifies a Region object that limits the portion of a Graphics object that is currently available for
drawing. The ClipBounds property returns a RectangleF structure that represents a bounding rectangle for
the clipping region of a Graphics object.
Note
The GraphicsState class represents the state of a Graphics object. This class does not have any useful
properties or methods, but it is used by the Save and Restore methods of the Graphics object. A call to the
Save method saves a GraphicsState object as an information block on the stack and returns it. When this
object is passed to the Restore method, the information block is removed from the stack and the graphics
state is restored to the saved state.
You can make multiple calls to Save (even nested), and each time a new state will be saved and a new
GraphicState object will be returned. When you call Restore, the block will be freed on the basis of the
GraphicsState object you pass as a parameter.
Now let's see how this works in our next example. We create a Windows application, add a MainMenu control
and its items, and write click event handlers for these items. Listing 9.14 creates and saves graphics states
using the Save method, then restores them one by one. The first saved state stores page units and a rotation
transformation; the second state stores a translation transformation. We save the first graphics state as gs1.
Then we call the TranslateTransform method, which translates and transforms the graphics object. We save
the new graphics state as gs2. Now we call ResetTransform, which removes all the transformation effects.
337
338
Then we draw an ellipse. We restore the graphics states by calling [Link] methods for both
gs1 and gs2, and we fill a rectangle and draw an ellipse, respectively.
Figure 9.22 shows the output from Listing 9.14. The first ellipse has no transformation effects, but the
rectangle and ellipse below do have transformation effects.
338
339
Graphics containers were introduced earlier in this chapter. Now let's see how to create and use them in our
applications.
The BeginContainer method of the Graphics class creates a container. Each BeginContainer method is
paired with an EndContainer method. You can also create nested containers. The following code snippet
creates two containers:
As mentioned earlier, graphics containers are temporary canvases. Let's see how to set the quality of different
text for different containers. Listing 9.15 creates two containers, and each has different properties. The first
container sets the TextRenderingHint property to AntiAlias and the TextContrast property to 4. The
second container sets TextRenderingHint to AntiAliasGridFit and TextContrast to 12. After creating
Font and SolidBrush objects, we set the TextRenderingHint property of the Graphics object, and then we
call DrawString. Finally, we call EndContainer to terminate the container scope.
339
340
// First container boundary finishes here
[Link](gContrainer1);
// Draw string outside of the container
[Link]("Text String", tnrFont, blueBrush,
new PointF(10, 80));
// Dispose of Graphics object
[Link]();
[Link]();
}
Note
Figure 9.23 shows the output from Listing 9.15. Notice the quality difference in the text.
In the previous section we saw how we can use containers to draw text with different rendering quality and
performance. We can draw other shapes using SmoothingMode, CompositingQuality, and other properties.
Listing 9.16 uses the AntiAlias, GammaCorrected, and HighSpeed options to draw rectangles and ellipses.
We create a container by calling BeginContainer, set the smoothing mode to anti-aliasing, and set the
compositing quality and gamma correction of the Graphics object. Then we draw an ellipse and a rectangle.
After that we create a second graphics container by making another call to BeginContainer and set the
smoothing mode and compositing quality to high speed, and then we draw a new ellipse and rectangle.
Finally, we make two calls to the EndContainer method to close the containers.
340
341
Pen bluePen = new Pen([Link], 10);
// Create first graphics container
GraphicsContainer gContainer1 = [Link]();
// Set its properties
[Link] = [Link];
[Link] =
[Link];
// Draw graphics objects
[Link](redPen, 10, 10, 100, 50);
[Link](bluePen, 210, 0, 100, 100);
// Create second graphics container
GraphicsContainer gContainer2 = [Link]();
// Set its properties
[Link] = [Link];
[Link] = [Link];
// Draw graphics objects
[Link](redPen, 10, 150, 100, 50);
[Link](bluePen, 210, 150, 100, 100);
// Destroy containers
[Link](gContainer2);
[Link](gContainer1);
// Dispose of objects
[Link]();
[Link]();
[Link]();
}
Figure 9.24 shows the output from Listing 9.16 The first ellipse and rectangle are smoother than the second
set.
Graphics containers are also useful when you need to render large images either with high quality or at high
speed. For example, if you have two large images and only one is quality-sensitive, you can create two
graphics containers and set high quality for the first container and high speed for the second.
[ Team LiB ]
[ Team LiB ]
341
342
In the .NET Framework library, the PropertyItem object is used as a placeholder for
metadata. The PropertyItem class provides four properties: Id, Len, Type, and Value. All of
these properties have both read and write access.
The Id property is a tag, which identifies the metadata item. Table 9.10 describes Id tag
values.
The Value property is an array of values whose format is determined by the Type property.
The Len property represents the length of the array of values in bytes. The Type property
represents the data type of values stored in the array. Table 9.11 describes the format of the
Type property values.
Numeric Value
Description
A Byte object
A 16-bit integer
342
343
A 32-bitinteger
Not used
Undefined
Not used
SLong
10
SRational
An Image object may contain more than one PropertyItem object. The PropertyItems property of the Image
class represents an array of PropertyItem objects corresponding to an image. The PropertyIdList property
of the Image class returns an array of property IDs stored in an image object. Listing 9.17 uses the
PropertyItems property of the Image class and reads all property items of an image.
343
344
[ Team LiB ]
[ Team LiB ]
In this chapter blending is divided into three categories: color blending, alpha blending, and mixed blending.
Color blending, which produces what are known as color gradients, involves drawing and filling graphics
shapes, lines, and curves starting with a color at one end and finishing with another color at the other end.
Figure 9.26 shows a good example of color blending.
Alpha blending is used to draw and fill transparent shapes, lines, and curves. Pens and brushes are used to
create alpha blending. First we create a pen or brush using the alpha component value as the color of a brush
or pen, and then we use that brush or pen to fill and draw shapes, lines, and curves. Semitransparent or
translucent graphics shapes, lines, and curves are examples of alpha blending. For example, Figure 9.27
contains three lines with opaque and semitransparent colors, and a string with semitransparent color on top of
an image a perfect example of alpha blending.
344
345
Figure 9.27. Transparent graphics shapes in an image using alpha blending
Note
Images in this book are not colored, so you may not see the exact effects described in the text. To see the
exact effects, run the sample code.
Mixed blending is probably a new concept to most readers. You won't find it mentioned in the MSDN
documentation. Mixed blending is a combination of color and alpha blending. Figure 9.28 shows an example.
If you run the sample code, you will see that the output consists of not only a transparent image, but also a
color blending sample.
345
346
9.5.1 Color Blending
Gradient brushes play a major role in color blending. LinearGradientBrush and PathGradientBrush both
represent brush objects with color blending.
As we discussed in Chapter 4, a linear gradient brush is a brush with two colors: a starting color and an ending
color. A path gradient brush is used to fill graphics paths. Instead of starting a color from one end, the path
gradient brush starts a color from the center of the path and ends with the second color at the outer boundary
of the path.
A blend pattern is a combination of two colors (a starting color and an ending color) defined by factors and
positions. The Blend class represents a blend pattern in the .NET Framework. It provides two properties:
Factors and Positions. The Factors property specifies the percentage of the starting color and the ending
color to be used at the corresponding position. The Positions property specifies the percentages of distance
for each gradation of color along the gradient line. The values of Factors and Positions must be between 0
and 1, where 0 represents the starting position and 1 represents the ending position. For example, 0.4f
specifies that a point is 40 percent of the total distance from the starting point.
After creating a Blend object, you can attach it to a linear gradient brush by setting the Blend property of the
LinearGradientBrush object. In Listing 9.18 we create a Blend object and its Factors and Positions
properties, and then we set the Blend property of the LinearGradientBrush object. We can use this brush to
fill graphics shapes.
Listing 9.18 Creating a Blend object and setting its Factors and Positions properties
The ColorBlend class defines arrays of colors and positions used for interpolating color blending in a
multicolor gradient. The Positions property, an array of floating points (values vary between 0.0 and 1.0),
represents the positions of the colors along a gradient line; and the Colors property, an array of Color objects,
represents the color to use at corresponding positions. Each position defined in Positions has a
corresponding color in the Colors array. Hence if six positions are defined in the Positions array, the
Colors array will have six Color objects.
To use a ColorBlend object, create the object and set its Positions and Colors properties, as shown in
Listing 9.19. The InterpolationColors property of the LinearGradientBrush and PathGradientBrush
classes uses the ColorBlend object.
Listing 9.19 Creating a ColorBlend object and setting its Colors and Positions properties
346
347
{
0.0f, 0.2f, 0.4f,
0.6f, 0.8f, 1.0f
};
// Create a ColorBlend object and set its Colors and
// Positions properties
ColorBlend colorBlend = new ColorBlend();
[Link] = clrArray;
[Link] = posArray;
[Link] = colorBlend;
The LinearGradientBrush object represents a linear gradient brush, which lets us specify the starting and
ending colors, and the starting and ending points, of the gradient pattern.
Note
The linear gradient brushes work differently from solid and hatch brushes. For solid and hatch brushes, an
application creates a brush and uses the brush to fill graphics shapes; the brush pattern applies to the entire
shape. For linear gradient brushes, an application creates a linear gradient brush with a rectangle. The
rectangle passed in the constructor of the LinearGradientBrush object defines the boundaries of a gradient
pattern. For example, Listing 9.20 creates a linear gradient brush with starting point (0, 0), ending point (50,
50), starting color red, and ending color green. Then the code fills a rectangle starting at point (0, 0) and
ending at point (200, 50):
LinearGradientBrush rgBrush =
new LinearGradientBrush
(
new RectangleF(0, 0, 50, 50),
[Link], [Link],
[Link]
);
[Link](rgBrush, 0, 0, 200, 50);
Figure 9.29 shows the output from Listing 9.20. After point (50, 50) the gradient pattern repeats itself.
Now let's create one more linear gradient brush using code from Listing 9.21. The brush's range is greater, and
the rectangle starts at point (50, 50), with height and width 200 and 50, respectively.
347
348
Listing 9.21 Setting a brush's rectangle
LinearGradientBrush rgBrush =
new LinearGradientBrush
(
new RectangleF(0, 0, 200, 200),
[Link], [Link],
[Link]
);
[Link](rgBrush, 50, 50, 200, 50);
As the output of Listing 9.21 shows (see Figure 9.30), the pattern repeats after it crosses point (200, 200).
Figure 9.31 shows the output from Listing 9.22. The first image starts with red and ends with green; the
second image has green as the center, and red as both the starting and the ending edge color.
348
349
Figure 9.31. Using the SetBlendTriangularShape method
The SetSigmaBellShape method creates a gradient falloff based on a bell-shaped curve. Much like
SetBlendTriangularShape, this method takes two parameters representing focus and scale (the focus
parameter is optional) whose values vary from 0 to 1. Listing 9.23 shows the SetSigmaBellShape method
being used.
Figure 9.32 shows the output from Listing 9.23. The first image starts with red and ends with green. After the
sigma bell shape is set, the image's center is green, and its starting and ending edges are red.
349
350
Now let's compare the effects of SetSigmaBellShape and SetBlendTriangularShape. Listing 9.24 draws
three rectangles: one using the LinearGradient brush with no effects, one using SetSigmaBellShape, and
one using SetBlendTriangularShape.
Figure 9.33 shows the output from Listing 9.24. The first image is the original image, the second image is a
sigma bell shape, and the third image is a blend triangular shape. SetBlendTriangularShape produces a
glassy effect in the center of the color, and SetSigmaBellShape produces a faded effect.
350
351
The first parameter of SetBlendTriangularShape and SetSigmaBellShape represents the center of the
gradient (color), which varies between 0.0f and 1.0f, where 0.0f is the starting point and 1.0f is the ending
point of the gradient.
Now let's change the center of the gradient by modifying the two relevant lines of Listing 9.24 as follows:
[Link](0.8f, 1.0f);
[Link](0.2f, 1.0f);
The new output looks like Figure 9.34. The center of the gradient in the second and third images is visibly
different.
351
352
So far in this section, we have been using only two colors (the default supported by LinearGradientBrush).
What if we want to use more than two colors? No problem!
The LinearGradientBrush class provides properties that are useful for blending. Two of these properties are
InterpolationColors and Blend. The Blend property is represented by the Blend object, and
InterpolationColors is represented by the ColorBlend object. To apply multicolor gradients, simply create
Blend and ColorBlend objects, attach these objects to a LinearGradientBrush object, and use the brush to
fill shapes.
Listing 9.25 creates a ColorBlend object, sets its Colors and Positions properties, and sets the
InterpolationColors property of the brush.
352
353
{
0.0f, 0.2f, 0.4f,
0.6f, 0.8f, 1.0f
};
// Create a ColorBlend object and
// set its Colors and Positions properties
ColorBlend colorBlend = new ColorBlend();
[Link] = clrArray;
[Link] = posArray;
// Set InterpolationColors property
[Link] = colorBlend;
// Draw shapes
[Link](brBrush, rect);
rect.Y = 150;
[Link] = 100;
[Link] = 100;
[Link](brBrush, rect);
// Dispose of object
[Link]();
}
Figure 9.35 shows the output from Listing 9.25. The gradient has multiple colors.
The Blend property of LinearGradientBrush allows you to attach a Blend object to the brush, which
represents the positions and factors of the blend. Listing 9.26 creates a Blend object and sets its Factors and
Positions properties, as well as the Blend property of the brush.
353
354
Blend blend = new Blend();
float[] factArray = {0.0f, 0.3f, 0.5f, 1.0f};
float[] posArray = {0.0f, 0.2f, 0.6f, 1.0f};
// Set Blend's Factors and Positions properties
[Link] = factArray;
[Link] = posArray;
// Set Blend property of the brush
[Link] = blend;
// Fill a rectangle and an ellipse
[Link](brBrush, 10, 20, 200, 100);
[Link](brBrush, 10, 150, 120, 120);
// Dispose of object
[Link]();
}
Figure 9.36 shows the output from Listing 9.26. The blend's position and colors are controlled by the Factors
property.
We use gamma correction when we want to display a drawing accurately on a computer screen. Gamma
correction controls the overall brightness of an image. Images that are not properly corrected may look either
too dark or bleached out. By setting the gamma correction, we tell GDI+ to change the brightness and set the
best ratios of red to green to blue.
The GammaCorrection property, a Boolean type, is used to apply gamma correction on a linear gradient
brush. This property can be true (enabled) or false (disabled). Brushes with gamma correction have more
uniform intensity than brushes with no gamma correction.
Listing 9.27 draws two rectangles. The first has no gamma correction; the second does have gamma
correction. If you run this code, you will notice that the second rectangle has a more uniform gradation.
354
355
[Link]([Link]);
// Create a rectangle
Rectangle rect =
new Rectangle(20, 20, 100, 50);
// Create a linear gradient brush
LinearGradientBrush rgBrush =
new LinearGradientBrush(
rect, [Link], [Link],
0.0f, true);
// Fill rectangle
[Link](rgBrush, rect);
rect.Y = 90;
// Set gamma correction of the brush
[Link] = true;
// Fill rectangle
[Link](rgBrush, rect);
// Dispose of object
[Link]();
}
As we discussed in Chapter 4 (Section 4.1.6), the PathGradientBrush object is used to fill a graphics path
with a gradient. We can specify the center and boundary colors of a path.
The CenterColor and SurroundColors properties are used to specify the center and boundary colors. Listing
9.28 uses the CenterColor and SurroundColors properties; it sets the center color of the path to red and the
surrounding color to green.
355
356
}
If you run the code from Listing 9.28, you will see that the focus is the center of the ellipse, and there is
scattering in a faded color toward the boundary of the ellipse. The center is red, and the border is green (see
Figure 9.37).
The FocusScales property changes the focus point for the gradient falloff. The following code snippet sets
the FocusScales property:
After FocusScales is set, the color of the ellipse changes from the center of the ellipse to a rectangle. Figure
9.38 shows the new output.
We can even specify multiple surrounding colors. For example, we can create an array of different colors and
use them for the SurroundColors property of the brush. To do so, we replace the following line of Listing
9.28:
356
357
Color[] colors =
{[Link], [Link],
[Link], [Link]};
[Link] = colors;
If you add this code to the application, you will see a totally different output. As Figure 9.39 shows, the new
ellipse has four different boundary colors.
357
358
[Link] = colorBlend;
[Link] = new PointF(0.6f, 0.2f);
Color[] colors = {[Link]};
// Set center and surrounding colors
[Link] = [Link];
[Link] = colors;
// Draw ellipse
[Link](rgBrush, rect);
// Dispose of object
[Link]();
}
You can even apply blending on a path gradient brush using the Blend property. Listing 9.30 creates a Blend
object and sets the Blend property of the brush.
358
359
{
[Link], [Link],
[Link], [Link]
};
// Set CenterColor and SurroundColors
[Link] = [Link];
[Link] = colors;
[Link](rgBrush, rect);
// Dispose of object
[Link]();
}
Figure 9.41 shows the output from Listing 9.30. Blending is done with four different colors.
Just as with LinearGradientBrush, you can use the SetBlendTriangularShape and SetSigmaBellShape
methods with PathGradientBrush.
[ Team LiB ]
[ Team LiB ]
The final color of an ARGB color structure is calculated by the following formula:
This formula is applied on each component of the source color and background color.
In alpha blending, an application creates a color with an alpha component and uses this color to
create a pen or a brush. This pen or brush is used to draw and fill graphics shapes, and it
calculates the final color. Alpha blending may sound unfamiliar, but programmatically it is
simply a method of setting the alpha component (transparency) of a color, and using it to fill
and draw graphics shapes.
359
360
9.6.1 Brushes, Pens, and Alpha Blending
The process of alpha blending involves three simple steps. First an application creates a color
with transparency (the alpha component). The following line creates a Color object with alpha
component value 40:
The second step is to create a brush or pen using that color. The following lines create a
transparent pen and a brush:
Finally, the application uses the transparent brush or pen to fill and draw graphics shapes, lines,
and curves. The following code uses the Pen and Brush objects we created in the previous steps
to draw a line and to draw and fill a rectangle:
Listing 9.31 uses this approach to draw lines, a rectangle, an ellipse, and text objects with
varying transparency. You can add this code to a menu item or a button click event handler.
Listing 9.31 Using alpha blending to draw non-opaque or semi-opaque graphics shapes
360
361
// Dispose of object
[Link]();
}
Figure 9.42 shows the output from Listing 9.31. The lines, rectangle, ellipse, and text on this
form are semitransparent.
We often see a semitransparent date and place name on a photo. You can draw transparent
graphics shapes on images using the same method: Create a graphics shape using semi- or
non-opaque colors, and then draw on the image.
Listing 9.32 draws graphics shapes on an image. First we create an Image object and call
DrawImage to draw an image. Then we create transparent pens and brushes and call fill and
draw methods to draw graphics shapes. You can add the code in Listing 9.32 to any menu item
or button click event handler.
361
362
[Link](new SolidBrush(
[Link](150, 255, 255, 255)), rect);
SolidBrush semiTransBrush =
new SolidBrush([Link](90, 255, 255, 50));
[Link]("Some Photo \nDate: 04/09/2001",
new Font("Verdana", 14), semiTransBrush,
new RectangleF(20, 100, 300, 100) );
// Dispose of object
[Link]();
}
Figure 9.43 shows the output from Listing 9.32. Lines, text, a rectangle, and an ellipse are
drawn on top of the image, but you can see through them because these shapes are
semitransparent.
As mentioned earlier, blending is a process of combining two colors: a source color and a
background color. The compositing mode specifies how source colors are combined with
background colors.
The CompositingMode property of the Graphics class represents the compositing mode of a
graphics surface, which applies to all graphics shapes for that surface. The CompositingMode
enumeration has two members: SourceCopy and SourceOver. SourceCopy specifies that when
a color is rendered, it overwrites the background color, and SourceOver specifies that when a
color is rendered, it is blended with the background color using the alpha component.
The following code snippet shows how to set the CompositingMode property of a Graphics
object.
Graphics g = [Link]();
[Link]([Link]);
[Link] = [Link];
[Link] = [Link];
362
363
// Dispose of object
[Link]();
CompositingMode may be helpful in scenarios where you need to draw overlapped images.
Suppose you draw one rectangle and one ellipse, and an area of the ellipse overlaps a small area
of the rectangle. You may or may not want to show the overlapped area of the rectangle. The
compositing mode provides you the option of doing either.
Instead of applying CompositingMode to all of the graphics, you can apply it to selected shapes.
One way to do this is to create a temporary Graphics object (a new surface), draw all the
shapes you need and apply the compositing mode on this object. You can also create graphics
containers and apply the necessary settings to each graphics container.
The quality of compositing is inversely proportional to the rendering speed: The higher the
quality, the slower the rendering. The CompositingQuality property of the Graphics object
represents the quality of a composition process, which takes a value of type
CompositingQuality enumeration. The CompositingQuality enumeration is defined in Table
9.12.
Listing 9.33 draws two sets of shapes. Each set has a rectangle and an ellipse. First we create a
Bitmap object, and then we create a temporary Graphics object using the FromImage method
by passing the Bitmap object. We set the CompositingMode property of this Graphics object to
SourceOver, which means that the color rendered overwrites the background color. Then we
draw a rectangle and an ellipse.
363
364
// Create two SolidBrush objects
SolidBrush redBrush =
new SolidBrush([Link](150, 255, 0, 0));
SolidBrush greenBrush =
new SolidBrush([Link](180, 0, 255, 0));
// Create a Bitmap object
Bitmap tempBmp = new Bitmap(200, 150);
// Create a Graphics object
Graphics tempGraphics =
[Link](tempBmp);
// Set compositing mode and compositing
// quality of Graphics object
[Link] =
[Link];
[Link] =
[Link];
// Fill rectangle
[Link](redBrush, rect1);
rect1.X += 30;
rect1.Y += 30;
// Fill ellipse
[Link](greenBrush, rect1);
[Link] =
[Link];
// Draw image
[Link](tempBmp, 0, 0);
// Fill rectangle
[Link]([Link], rect2);
rect2.X += 30;
rect2.Y += 30;
// Fill ellipse
[Link]([Link], rect2);
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
}
Figure 9.44 shows the output from Listing 9.33. You can clearly see that an ellipse copies over the color of a
rectangle.
Now we change the value of CompositingMode to SourceCopy by using the following code snippet:
[Link] =
364
365
[Link];
Figure 9.45 shows the new output. The color of the rectangle and the color of ellipse do not overlap now, but
the color of the rectangle is gone and that area is overridden by the ellipse.
Mixed blending is a combination of both alpha blending and color blending. It is useful when you need to
draw transparent and blended graphics shapes for example, drawing a transparent image with transparent
shapes using a blended linear gradient brush.
Listing 9.34 shows how to mix these two types of blending. Using the InterpolationColors property, we
create a LinearGradientBrush object and set its Colors and Positions properties to specify the blending
colors and positions. After that we create a Bitmap object and apply a color matrix using SetColorMatrix.
Then we draw a rectangle and an ellipse, and we call DrawImage.
365
366
// set its Colors and Positions properties
ColorBlend colorBlend = new ColorBlend();
[Link] = clrArray;
[Link] = posArray;
// Set InterpolationColors property
[Link] = colorBlend;
// Create a Bitmap object from a file
Bitmap bitmap = new Bitmap("[Link]");
// Create a points array
float[][] ptsArray =
{
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 0.5f, 0},
new float[] {0, 0, 0, 0, 1}
};
// Create a ColorMatrix object using pts array
ColorMatrix clrMatrix =
new ColorMatrix(ptsArray);
// Create an ImageAttributes object
ImageAttributes imgAttributes =
new ImageAttributes();
// Set color matrix of ImageAttributes
[Link](clrMatrix,
[Link],
[Link]);
// Fill rectangle
[Link](brBrush, rect);
rect.Y += 120;
// Fill ellipse
[Link](brBrush, rect);
// Draw image using ImageAttributes
[Link](bitmap,
new Rectangle(0, 0,
[Link], [Link]),
0, 0, [Link], [Link],
[Link], imgAttributes);
// Dispose of objects
[Link]();
[Link]();
[Link]();
}
Figure 9.46 shows the output from Listing 9.34. The rectangle and ellipse are blended (multicolor) and
translucent (alpha-blended).
366
367
[ Team LiB ]
[ Team LiB ]
• Region data
• The SmoothingMode enumeration
• The PixelOffsetMode enumeration
Sometimes we need to get and set a region's data or create a Region object from an array of
bytes. A region's data is an array of bytes that specify the region. The RegionData class can
be used to read or write the array. This class has only one property, Data, which returns an
array of bytes that describe the region.
// Create a rectangle
Rectangle rect = new Rectangle(20, 20, 200, 200);
Region rgn = new Region(rect);
// Create a RegionData object
RegionData rgnData = [Link]();
// Get data
byte[] btArry = [Link];
[Link]("Number of bytes :"
+ [Link]()
);
367
368
The smoothing mode specifies the rendering quality of graphics drawn on a surface. The
SmoothingMode property is used to get and set the smoothing mode of a graphics surface, and
it takes a value of SmoothingMode enumeration.
SmoothingMode defines anti-aliasing for lines, curves, and images. This property does not
affect text; the TextRenderingHint property is used for text. SmoothingMode has six
members, which are defined in Table 9.13.
To see SmoothingMode in action, let's draw a few graphics shapes. Listing 9.36 draws a
rectangle, an ellipse, and a line. The line that sets the smoothing mode of the Graphics object
is commented out.
368
369
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
}
Figure 9.47 shows the output from Listing 9.36. The outer edges of the shapes are not smooth.
Now let's uncomment the SmoothingMode line in Listing 9.36 and run the program again:
[Link] = [Link];
Figure 9.48 shows the new output. The shapes have smooth outer edges and look better overall.
PixelOffsetMode determines how pixels are offset during rendering. By offsetting pixels during rendering,
we can improve rendering quality, but at the expense of speed. The PixelOffsetMode property of the
369
370
Graphics class, with the help of SmoothingMode, is used to draw enhanced anti-aliasing images. The
PixelOffsetMode enumeration is defined in Table 9.14.
The PixelOffsetMode property helps when we want to enhance anti-aliased graphics. Here's how to set this
property:
[Link] = [Link];
[Link] = [Link];
Member
Description
Default
Half
Pixels are offset by 0.5 units, both horizontally and vertically, for high-speed anti-aliasing.
HighQuality
HighSpeed
Invalid
Invalid mode.
None
No pixel offset.
[ Team LiB ]
[ Team LiB ]
SUMMARY
The [Link].Drawing2D namespace defines advanced functionality to work with 2D graphics
objects. In this chapter we discussed the functionality defined in this namespace. We started the chapter by
discussing the line caps and line styles. We saw sample code that set the line cap, line dash style, and line dash
caps.
370
371
Next we covered graphics paths and graphics containers. We saw the usefulness of graphics paths and
containers, and their advantages over nongraphics paths and containers. We also discussed graphics container
states.
In the blending section of this chapter, we learned about color blending, alpha blending, and mixed blending.
We discussed how to use linear gradient and path gradient brushes to draw blended objects. We saw how to
use colors to draw alpha-blended graphics objects.
We also discussed other topics and classes defined in the [Link].Advanced2D namespace, including
metadata of images, how to set gamma correction, region data, and drawing quality.
Chapter 10 will focus on transformations, presenting the basics of transformations, matrices, and matrix
operations, and how to apply transformation in practice.
[ Team LiB ]
[ Team LiB ]
Any drawing process involves a source and a destination. The source of a drawing is the application that
created it, and the destination is a display or printer device. For example, the process of drawing a simple
rectangle starts with a command telling GDI+ to draw on the screen, followed by GDI+ iterating through
multiple steps before it finally renders a rectangle on the screen. In the same way, transformation involves
some steps before it actually renders the transformed object on a device. These steps are shown in Figure 10.1,
which shows that GDI+ is responsible for converting world coordinates to page coordinates and device
coordinates before it can render a transformed object.
371
372
[ Team LiB ]
[ Team LiB ]
Before GDI+ draws a graphics shape on a surface, the shape goes through a few transformation stages
(conversions). The first stage converts world coordinates to page coordinates. Page coordinates may or may
not be the same as world coordinates, depending on the transformation. The process of converting world
coordinates to page coordinates is called world transformation.
The second stage converts page coordinates to device coordinates. Device coordinates represent how a
graphics shape will be displayed on a device such as a monitor or printer. The process of converting page
coordinates to device coordinates is called page transformation. Figure 10.2 shows the stages of conversion
from world coordinates to device coordinates.
In GDI+, the default origin of all three coordinate systems is point (0, 0), which is at the upper left corner of
the client area. When we draw a line from point A (0, 0) to point B (120, 80), the line starts 0 pixels from the
upper left corner in the x-direction and 0 pixels from the upper left corner in the y-direction, and it will end
120 pixels over in the x-direction and 80 pixels down in the y-direction. The line from point A (0, 0) to point
B (120, 80) is shown in Figure 10.3.
Figure 10.3. Drawing a line from point (0, 0) to point (120, 80)
372
373
Drawing this line programmatically is very simple. We must have a Graphics object associated with a surface
(a form or a control). We can get a Graphics object in several ways. One way is to accept the implicit object
provided by a form's paint event handler; another is to use the CreateGraphics method. Once we have a
Graphics object, we call its draw and fill methods to draw and fill graphics objects. Listing 10.1 draws a line
from starting point A (0, 0) to ending point B (120, 80). You can add this code to a form's paint event handler.
Listing 10.1 Drawing a line from point (0, 0) to point (120, 80)
Graphics g = [Link];
Point A = new Point(0, 0);
Point B = new Point(120, 80);
[Link]([Link], A, B);
Figure 10.3 shows the output from Listing 10.1. All three coordinate systems (world, page, and device) draw a
line starting from point (0, 0) in the upper left corner of the client area to point (120, 80).
Now let's change to the page coordinate system. We draw a line from point A (0, 0) to point B (120, 80), but
this time our origin is point (50, 40) instead of the upper left corner. We shift the page coordinates from point
(0, 0) to point (50, 40). The TranslateTransform method of the Graphics class does this for us. We will
discuss this method in more detail in the discussion that follows. For now, let's try the code in Listing 10.2.
Listing 10.2 Drawing a line from point (0, 0) to point (120, 80) with origin (50, 40)
Graphics g = [Link];
[Link](50, 40);
Point A = new Point(0, 0);
Point B = new Point(120, 80);
[Link]([Link], A, B);
Figure 10.4 shows the output from Listing 10.2. The page coordinate system now starts at point (50, 40), so
the line starts at point (0, 0) and ends at point (120, 80). The world coordinates in this case are still (0, 0) and
(120, 80), but the page and device coordinates are (50, 40) and (170, 120). The device coordinates in this case
are the same as the page coordinates because the page unit is in the pixel (default) format.
Figure 10.4. Drawing a line from point (0, 0) to point (120, 80) with origin (50, 40)
373
374
What is the difference between page and device coordinates? Device coordinates determine what we actually
see on the screen. They can be represented in many formats, including pixels, millimeters, and inches. If the
device coordinates are in pixel format, the page coordinates and device coordinates will be the same (this is
typically true for monitors, but not for printers).
The PageUnit property of the Graphics class is of type GraphicsUnit enumeration. In Listing 10.3 we set
the PageUnit property to inches. Now graphics objects will be measured in inches, so we need to pass inches
instead of pixels. If we draw a line from point (0, 0) to point (2, 1), the line ends 2 inches from the left side
and 1 inch from the top of the client area in the page coordinate system. In this case the starting and ending
points are (0, 0) and (2, 1) in both world and page coordinates, but the device coordinate system converts them
to inches. Hence the starting and ending points in the device coordinate system are (0, 0) and (192, 96),
assuming a resolution of 96 dots per inch.
[Link] = [Link];
[Link]([Link], 0, 0, 2, 1);
Figure 10.5 shows the output from Listing 10.3. The default width of the pen is 1 page unit, which in this case
gives us a pen 1 inch wide.
374
375
Now let's create a new pen with a different width. Listing 10.4 creates a pen that's 1 pixel wide (it does so by
dividing the number of pixels we want in this case 1 by the page resolution, which is given by DpiX). We
draw the line again, this time specifying a red color.
Figure 10.6. Drawing with the [Link] option and a pixel width
We can also combine the use of page and device coordinates. In Listing 10.5 we transform page coordinates to
1 inch from the left and 0.5 inch from the top of the upper left corner of the client area. Our new page
coordinate system has starting and ending points of (1, 0.5) and (3, 1.5), but the device coordinate system
converts them to pixels. Hence the starting and ending points in device coordinates are (96, 48) and (288,
144), assuming a resolution of 96 dots per inch.
375
376
[ Team LiB ]
[ Team LiB ]
Translation is a transformation of the xy plane that moves a graphics object toward or away from the origin of
the surface in the x- or y-direction. For example, moving an object from point A (x1, y1) to point B (x2, y2) is
a translation operation in which an object is being moved (y2 y1) points in the y-direction.
Rotation moves an object around a fixed angle around the center of the plane.
In the reflection transformation, an object moves to a position in the opposite direction from an axis, along a
line perpendicular to the axis. The resulting object is the same distance from the axis as the original point, but
in the opposite direction.
Simple transformations, including rotation, scaling, and reflection are called linear transformations. A linear
transformation followed by translation is called an affine transformation.
The shearing transformation skews objects based on a shear factor. In the sample applications discussed
throughout this chapter, will see how to use these transformations in GDI+.
So far we've looked at only simple transformations. Now let's discuss some more complex
transformation-related functionality defined in the .NET Framework library.
[ Team LiB ]
[ Team LiB ]
376
377
In GDI+, the Matrix class represents a 3x2 matrix that contains x, y, and w values in the first,
second, and third columns, respectively.
Note
Before using the Matrix class in your applications, you need to add a reference to the
[Link].Drawing2D namespace.
We can create a Matrix object by using its overloaded constructors, which take an array of
points (hold the matrix items) as arguments. The following code snippet creates three Matrix
objects from different overloaded constructors. The first Matrix object has no values for its
items. The second and third objects have integer and floating point values, respectively, for the
first six items of the matrix.
377
378
Returns the y
translation
value of a
matrix.
The Matrix class provides properties for accessing and setting its member values. Table 10.1 describes these
properties.
The Matrix class provides methods to invert, rotate, scale, and transform matrices. The Invert method is
used to reverse a matrix if it is invertible. This method takes no parameters.
Note
The Transform property of the Graphics class is used to apply a transformation in the form of a Matrix
object. We will discuss this property in more detail in Section 10.4.
Listing 10.6 uses the Invert method to invert a matrix. We create a Matrix object and read its original
values. Then we call the Invert method and read the new values.
The Multiply method multiplies a new matrix against an existing matrix and stores the result in the first
matrix. Multiply takes two arguments. The first is the new matrix by which you want to multiply the existing
matrix, and the second is an optional MatrixOrder argument that indicates the order of multiplication.
The MatrixOrder enumeration has two values: Append and Prepend. Append specifies that the new operation
is applied after the preceding operation; Prepend specifies that the new operation is applied before the
preceding operation during cumulative operations. Listing 10.7 multiplies two matrices. We create two
Matrix objects and use the Multiply method to multiply the second matrix by the first. Then we read and
display the resultant matrix.
378
379
Listing 10.7 Multiplying two matrices
The Reset method resets a matrix to the identity matrix (see Figure 10.21 for an example of an identity
matrix). If we call the Reset method and then apply a matrix to transform an object, the result will be the
original object.
The Rotate and RotateAt methods are used to rotate a matrix. The Rotate method rotates a matrix at a
specified angle. This method takes two arguments: a floating point value specifying the angle, and
(optionally) the matrix order. The RotateAt method is useful when you need to change the center of the
rotation. Its first parameter is the angle; the second parameter (of type float) specifies the center of rotation.
The third (optional) parameter is the matrix order.
Listing 10.8 simply creates a Graphics object using the CreateGraphics method and calls DrawLine and
FillRectangle to draw a line and fill a rectangle, respectively.
379
380
Now let's rotate our graphics objects, using the Matrix object. In Listing 10.9 we create a Matrix object, call
its Rotate method to rotate the matrix 45 degrees, and apply the Matrix object to the Graphics object by
setting its Transform property.
Figure 10.9 shows the new output. Both objects (line and rectangle) have been rotated 45 degrees.
380
381
381
382
If we call the Reset method in Listing 10.10 after RotateAt and before [Link], like this:
the revised code generates Figure 10.11, which is the same as Figure 10.8. There is no rotation because the
Reset method resets the transformation.
382
383
The Scale method scales a matrix in the x- and y-directions. This method takes two floating values (scale
factors), for the x- and y-axes, respectively. In Listing 10.11 we draw a rectangle with a width of 20 and a
height of 30. Then we create a Matrix object and scale it by calling its Scale method with arguments 3 and 4
in the x- and y-directions, respectively.
Figure 10.12 shows the output from Listing 10.11. The first rectangle is the original rectangle; the second
rectangle is the scaled rectangle, in which the x position (and width) is scaled by 3, and the y position (and
height) is scaled by 4.
The Shear method provides a shearing transformation and takes two floating point arguments, which
represent the horizontal and vertical shear factors, respectively. In Listing 10.12 we draw a filled rectangle
with a hatch brush. Then we call the Shear method to shear the matrix by 2 in the vertical direction, and we
use Transform to apply the Matrix object.
383
384
Listing 10.12 Shearing graphics objects
Figure 10.13 shows the output from Listing 10.12. The first rectangle in this figure is the original; the second
is sheared.
The Translate method translates objects by the specified value. This method takes two floating point
arguments, which represent the x and y offsets. For example, Listing 10.13 translates the original rectangle by
100 pixels each in the x- and y-directions.
384
385
Listing 10.13 Translating graphics objects
Here we draw two rectangles with a width of 100 and a height of 60. Both rectangles start at (50, 50), but the
code generates Figure 10.14. Even though the rectangles were drawn with the same size and location, the
second rectangle after translation is now located 100 points away in the x- and y-directions from the first
rectangle.
[ Team LiB ]
[ Team LiB ]
385
386
We will see how to use these members in the examples throughout this
chapter.
The transformation methods provided by the Graphics class are MultiplyTransform, ResetTransform,
RotateTransform, ScaleTransform, TransformPoints, TranslateClip, and TranslateTransform. The
386
387
MultiplyTransform method multiplies a transformation matrix by the world transformation coordinates of a
Graphics object. It takes an argument of Matrix type. The second argument, which specifies the order of
multiplication operation, is optional. The following code snippet creates a Matrix object with the Translate
transformation. The MultiplyTransform method multiplies the Matrix object by the world coordinates of the
Graphics object, translating all graphics items drawn by the Graphics object.
RotateTransform rotates the world transform by a specified angle. This method takes a floating point
argument, which represents the rotation angle, and an optional second argument of MatrixOrder. The
following code snippet rotates the world transformation of the Graphics object by 45 degrees:
[Link](45.0F, [Link]);
The ScaleTransform method scales the world transformation in the specified x- and y-directions. The first
and second arguments of this method are x- and y-direction scaling factors, and the third optional argument is
MatrixOrder. The following code snippet scales the world transformation by 2 in the x-direction and by 3 in
the y-direction:
The TranslateClip method translates the clipping region in the horizontal and vertical directions. The first
argument of this method represents the translation in the x-direction, and the second argument represents the
translation in the y-direction:
[Link](20.0f, 10.0f);
The TranslateTransform method translates the world transformation by the specified x- and y-values and
takes an optional third argument of MatrixOrder:
[ Team LiB ]
[ Team LiB ]
387
388
Listing 10.14 draws two ellipses and a rectangle, then calls ScaleTransform, TranslateTransform, and
RotateTransform (a composite transformation). The items are drawn again after the composite
transformation.
388
389
}
A local transformation is applicable to only a specific item of a Graphics object. The best example of local
transformation is transforming a graphics path. The Translate method of the GraphicsPath class translates
only the items of a graphics path. Listing 10.15 translates a graphics path. We create a Matrix object and
apply rotate and translate transformations to it.
389
390
Figure 10.16 shows the output from Listing 10.15. The transformation affects only graphics path items (the
ellipse and the blue [dark] line).
[ Team LiB ]
[ Team LiB ]
Rotating images is similar to rotating other graphics. Listing 10.16 rotates an image. We create a Graphics
object using the CreateGraphics method. Then we create a Bitmap object from a file and call the DrawImage
method, which draws the image on the form. After that we create a Matrix object, call its Rotate method,
rotate the image by 30 degrees, and apply the resulting matrix to the surface using the Transform property.
Finally, we draw the image again using DrawImage.
390
391
// Dispose of objects
[Link]();
[Link]();
}
Figure 10.17 shows the output from Listing 10.16. The first image is the original; the second image is rotated.
Now let's apply other transformations. Replacing the Rotate method in Listing 10.16 with the following line
scales the image:
[Link](2, 1, [Link]);
391
392
Replacing the Rotate method in Listing 10.16 with the following line translates the image with 100 offset in
the x- and y-directions:
[Link](100, 100);
Replacing the Rotate method in Listing 10.16 with the following line shears the image:
[Link](2, 1);
392
393
You have probably noticed that image transformation is really no different from the transformation of other
graphics objects. We recommend that you download the source code samples from online to see the detailed
code listings.
[ Team LiB ]
[ Team LiB ]
As we discussed in earlier chapters, the color of each pixel of a GDI+ image or bitmap is represented by a
32-bit number, of which 8 bits each are used for the red, green, blue, and alpha components. Each of the four
components is a number from 0 to 255. For red, green, and blue, 0 represents no intensity and 255 represents
full intensity. For the alpha component, 0 represents transparent and 255 represents fully opaque. A color
vector includes four items: A, R, G, and B. The minimum values for this vector are (0, 0, 0, 0), and the
maximum values are (255, 255, 255, 255).
GDI+ allows the use of values between 0 and 1, where 0 represents the minimum intensity and 1 the
maximum intensity. These values are used in a color matrix to represent the intensity and opacity of color
components. For example, the color vector with minimum values is (0, 0, 0, 0), and the color vector with
maximum values is (1, 1, 1, 1).
In a color transformation we can apply a color matrix on a color vector by multiplying a 4x4 matrix. However,
a 4x4 matrix supports only linear transformations such as rotation and scaling. To perform nonlinear
transformations such as translation, we must use a 5x5 matrix. The element of the fifth row and the fifth
column of the matrix must be 1, and all of the other entries in the five columns must be 0.
393
394
The elements of the matrix are identified according to a zero-based index. The first element of the matrix is
M[0][0], and the last element is M[4][4]. A 5x5 identity matrix is shown in Figure 10.21. In this matrix the
elements M[0][0], M[1][1], M[2][2], and M[3][3] represent the red, blue, green, and alpha factors,
respectively. The element M[4][4] means nothing, and it must always be 1.
Now if we want to double the intensity of the red component of a color, we simply set M[0][0] equal to 2. For
example, the matrix shown in Figure 10.22 doubles the intensity of the red component, decreases the intensity
of the green component by half, triples the intensity of the blue component, and decreases the opacity of the
color by half (making it semitransparent).
In the matrix shown in Figure 10.22, we multiplied the intensity values. We can also add intensity values by
using other matrix elements. For example, the matrix shown in Figure 10.23 will double the intensity of the
red component and add 0.2 to each of the red, green, and blue component intensities.
In this section we will discuss the ColorMatrix class. As you might guess from its name, this class defines a
matrix of colors. In the preceding sections we discussed the Matrix class. The ColorMatrix class is not very
different from the Matrix class. Whereas the Matrix class is used in general transformation to transform
graphics shapes and images, the ColorMatrix class is specifically designed to transform colors. Before we see
practical use of the color transformation, we will discuss the ColorMatrix class, its properties, and its
methods.
The ColorMatrix class constructor takes an array that contains the values of matrix items. The Item property
of this class represents a cell of the matrix and can be used to get and set cell values. Besides the Item
property, the ColorMatrix class provides 25 MatrixXY properties, which represent items of the matrix at row
(x + 1) and column (y + 1). MatrixXY properties can be used to get and set an item's value.
394
395
Listing 10.17 creates a ColorMatrix object with item (4, 4) set to 0.5 (half opacity). Then it sets the values of
item (3, 4) to 0.8 and item (1, 1) to 0.3.
float[][] ptsArray ={
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 0.5f, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix clrMatrix = new ColorMatrix(ptsArray);
if( clrMatrix.Matrix34 <= 0.5)
{
clrMatrix.Matrix34 = 0.8f;
clrMatrix.Matrix11 = 0.3f;
}
Section 10.8 will describe how to apply color matrices to the transformation of colors.
[ Team LiB ]
[ Team LiB ]
The color matrix can be applied to an image via the SetColorMatrix method of the ImageAttributes class.
The ImageAttributes object is used as a parameter when we call DrawImage.
Translating colors increases or decreases color intensities by a set amount (not by multiplying them). Each
color component (red, green, and blue) has 255 different intensity levels ranging from 0 to 255. For example,
assume that the current intensity level for the red component of a color is 100. Changing its intensity level to
150 would imply translating by 50.
In a color matrix representation, the intensity varies from 0 to 1. The last row's first four elements represent
the translation of red, green, blue, and alpha components of a color, as shown in Figure 10.22. Hence, adding
a value to these elements will transform a color. For example, the t1, t2, t3, and t4 values in the following
color matrix represent the red, green, blue, and alpha component translations, respectively:
Color Matrix = {
{1, 0, 0, 0, 0},
{0, 1, 0, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 0, 1, 0},
{t1, t2, t3, t4, 1}};
Listing 10.18 uses a ColorMatrix object to translate colors. We change the current intensity of the red
component to 0.90. First we create a Graphics object using the CreateGraphics method, and we create a
395
396
Bitmap object from a file. Next we create an array of ColorMatrix elements and create a ColorMatrix object
from this array. Then we create an ImageAttributes object and set the color matrix using SetColorMatrix,
which takes the ColorMatrix object as its first parameter. After all that, we draw two images. The first image
has no effects; the second image shows the result of our color matrix transformation. Finally, we dispose of
the objects.
Figure 10.24 shows the output from Listing 10.18. The original image is on the left; on the right we have the
results of our color translation. If you change the values of other components (red, blue, and alpha) in the last
row of the color matrix, you'll see different results.
396
397
Scaling color involves multiplying a color component value by a scaling factor. For example, the t1, t2, t3,
and t4 values in the following color matrix represent the red, green, blue, and alpha components, respectively.
If we change the value of M[2][2] to 0.5, the transformation operation will multiply the green component by
0.5, cutting its intensity by half.
Color Matrix = {
{t1, 0, 0, 0, 0},
{0, t2, 0, 0, 0},
{0, 0, t3, 0, 0},
{0, 0, 0, t4, 0},
{0, 0, 0, 0, 1}};
397
398
[Link]);
// Draw image with no effects
[Link](curBitmap, 0, 0, 200, 200);
// Draw image with image attributes
[Link](curBitmap,
new Rectangle(205, 0, 200, 200),
0, 0, [Link], [Link],
[Link], imgAttribs) ;
// Dispose of objects
[Link]();
[Link]();
}
Figure 10.25 shows the output from Listing 10.19. The original image is on the left; on the right is the image
after color scaling. If you change the values of t1, t2, t3, and t4, you will see different results.
Earlier in this chapter we discussed image shearing. It can be thought of as anchoring one corner of a
rectangular region and stretching the opposite corner horizontally, vertically, or in both directions. Shearing
colors is the same process, but here the object is the color instead of the image.
Color shearing increases or decreases a color component by an amount proportional to another color
component. For example, consider the transformation in which the red component is increased by one half the
value of the blue component. Under such a transformation, the color (0.2, 0.5, 1) would become (0.7, 0.5, 1).
The new red component is 0.2 + (0.5)(1) = 0.7. The following color matrix is used to shear image colors.
float[][] ptsArray = {
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {.50f, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix clrMatrix = new ColorMatrix(ptsArray);
If we substitute this color matrix into Listing 10.19, the output will look like Figure 10.26.
398
399
Figure 10.26. Shearing colors
As explained earlier, color in GDI+ has four components: red, green, blue, and alpha. Rotating all four
components in a four-dimensional space is hard to visualize. However, such rotation can be visualized in a
three-dimensional space. To do this, we drop the alpha component from the color structure and assume that
there are only three colors red, green, and blue as shown in Figure 10.27. The three colors red, green, and
blue are perpendicular to each other, so the angle between any two primary colors is 90 degrees.
Suppose that the red, green, and blue colors are represented by points (1, 0, 0), (0, 1, 0), and (0, 0, 1),
respectively. If we rotate a color with a green component of 1, and red and blue components of 0 each, by 90
degrees, the new color will have a red component of 1, and green and blue components of 0 each. If we rotate
the color less than 90 degrees, the new color will be located somewhere between green and red.
Figure 10.28 shows how to initialize a color matrix to perform rotations about each of the three components:
red, green, and blue.
399
400
Listing 10.20 rotates the colors by 45 degrees from the red component.
Figure 10.29 slows the output from Listing 10.20. On the left is the original image; on the right is the image
after color rotation.
400
401
Figure 10.29. Rotating colors
[ Team LiB ]
[ Team LiB ]
We create a Matrix object with the transformation properties and apply it to the surface using the Transform
property of the Graphics object. Listing 10.21 creates a Matrix object and sets it as the Transform property.
We then call DrawString, which draws the text on the form. To test this code, add the code to a form's paint
event handler.
Graphics g = [Link];
string str =
"Colors, fonts, and text are common" +
" elements of graphics programming." +
"In this chapter, you learned " +
" about the colors, fonts, and text" +
" representations in the "+
".NET Framework class library. "+
"You learned how to create "+
"these elements and use them in GDI+.";
// Create a Matrix object
Matrix M = new Matrix(1, 0, 0.5f, 1, 0, 0);
[Link](45.0f,
[Link]);
[Link](-20, -70);
[Link] = M;
[Link](str,
new Font("Verdana", 10),
new SolidBrush([Link]),
new Rectangle(50,20,200,300) );
401
402
We can apply shearing and other effects by changing the values of Matrix. For example, if we change Matrix
as follows:
We can reverse the text just by changing the value of the Matrix object as follows:
402
403
Figure 10.32. Using the transformation matrix to reverse text
[ Team LiB ]
[ Team LiB ]
In a composite transformation, the order of the individual transformations is very important. Matrix operations
are not cumulative. For example, the result of a Graphics Rotate Translate Scale Graphics
operation will be different from the result of a Graphics Scale Rotate Translate Graphics
operation. The main reason that order is significant is that transformations like rotation and scaling are done
with respect to the origin of the coordinate system. The result of scaling an object that is centered at the origin
is different from the result of scaling an object that has been moved away from the origin. Similarly, the result
of rotating an object that is centered at the origin is different from the result of rotating an object that has been
moved away from the origin.
The MatrixOrder enumeration, which is an argument to the transformation methods, represents the
transformation order. It has two values: Append and Prepend.
Let's write an application to see how transformation order works. We create a Windows application and add a
MainMenu control and three menu items to the form. The MatrixOrder class is defined in the
[Link].Drawing2D namespace, so we also add a reference to this namespace.
Listing 10.22 draws a rectangle before and after applying a Scale Rotate Translate transformation
sequence.
403
404
new Rectangle(20, 20, 100, 100);
// Create a solid brush
SolidBrush brush =
new SolidBrush([Link]);
// Fill rectangle
[Link](brush, rect);
// Scale
[Link](1.75f, 0.5f);
// Rotate
[Link](45.0f, [Link]);
// Translate
[Link](150.0f, 50.0f,
[Link]);
// Fill rectangle again
[Link](brush, rect);
// Dispose of objects
[Link]();
[Link]();
}
Figure 10.33 shows the output from Listing 10.22. The original rectangle is in the upper left; on the lower
right is the rectangle after composite transformation.
Now let's change the order of transformation to Translate Rotate Scale with Append, as shown in
Listing 10.23.
404
405
// Fill rectangle
[Link](brush, rect);
// Translate
[Link](100.0f, 50.0f,
[Link]);
// Scale
[Link](1.75f, 0.5f);
// Rotate
[Link](45.0f,
[Link]);
// Fill rectangle again
[Link](brush, rect);
// Dispose of objects
[Link]();
[Link]();
}
Figure 10.34 shows the output from Listing 10.23. The original rectangle is in the same place, but the
transformed rectangle has moved.
Now let's keep the code from Listing 10.23 and change only the matrix transformation order from Append to
Prepend, as shown in Listing 10.24.
405
406
[Link](100.0f, 50.0f,
[Link]);
// Rotate
[Link](45.0f,
[Link]);
// Scale
[Link](1.75f, 0.5f);
// Fill rectangle again
[Link](brush, rect);
// Dispose of objects
[Link]();
[Link]();
}
The new output is shown in Figure 10.35. The matrix order affects the result.
[ Team LiB ]
[ Team LiB ]
SUMMARY
In this chapter we first discussed the basics of transformation, coordinate systems, the role of coordinate
systems in the transformation process, and transformation functionality. We learned
Matrices play a vital role in transformation. We can customize the transformation process and its variables by
creating and applying a transformation matrix. This chapter showed
• How to use the Matrix and ColorMatrix classes, and their role in transformation
• How to use the matrix operations for image processing, including translation, scaling, shearing, and
rotation
• How to use recoloring and color transformation to manipulate the colors of graphics objects
• How to perform color transformations
406
407
Transformations can be applied not only to graphics images and objects, but also to text strings. Drawing
vertical or skewed text is one example of text transformation. This chapter explained how to transform text.
Printing also plays an important part in GDI+. In Chapter 11 you will learn various components of the
[Link] namespace and how to use them.
[ Team LiB ]
[ Team LiB ]
We'll begin with a brief history of printing, followed by an introduction to the printing classes available in
.NET. Toward the end of the chapter we will delve deep into printing functionality. After reading this chapter,
you should have a good idea of printing functionality defined in the .NET Framework, and how to implement
this functionality in your applications. Here are some of the topics we will discuss in this chapter:
[ Team LiB ]
[ Team LiB ]
When Microsoft DOS was the standard PC desktop operating system, every application had to supply its own
printing software or printer drivers. If you bought a piece of software from Company X, you had to hope that
it supported your printer. Thus, often you had to check which printers your new software supported and buy
one of those. Either that, or wait until Company X supported your printer, which, more often than not, never
happened.
Companies tended to produce printer drivers for only a select few of the popular printers on the market, such
as the HP LaserJet. Even worse, you might have a printer driver for your laser printer when using a drawing
package, but if you wanted to use a word processor from a different company, it would not be surprising to
find that your printer was not supported!
407
408
During this time, companies like Hewlett-Packard were driving the printer business and introducing standards
that could only make things better. At this point HP had been in the printer business a long time and had
introduced many different types of printers and plotters. It had already introduced a standard language
(Hewlett-Packard Graphics Language, or HPGL) for drawing graphics on a plotter, which allowed the user to
issue draw commands like, "Draw a line from point A to point B."
Hewlett-Packard introduced the LaserJet series of laser printers, which became extremely successful because
of their high quality and low cost. These printers were driven by a language called PCL (Printer Control
Language). (Even today, printers manufactured by HP and several other companies support PCL.) Even if you
don't have the exact printer driver you need, if your printer supports PCL you can at least get some output
from it.
Moreover, Hewlett-Packard used PCL with all its printers, so if you wrote an application to communicate with
the HP LaserJet Series II, you could be pretty certain that the code would work with later printers in the range.
Although HP is not the only printer manufacturer, it can certainly be credited with jump-starting the market.
While companies like Hewlett-Packard were making printing easier, the software problems still existed. If you
did not have an appropriate printer driver for your application, you would not get anything out of your printer.
In most cases you can just choose a driver from an earlier model in the same line. For instance you
could use an HP LaserJet II driver to drive an HP LaserJet 4 printer. This works because
Hewlett-Packard uses PCL to control it sprinters, so even though the LaserJet II may use an older
version of PCL, the LaserJet 4 still supports it. The message here is that when you're buying your
next printer, make sure the operating system you intend to use supports it!
With the release of Microsoft Windows in its various forms, the printing crisis was more or less over.
Windows provided a standard graphical user interface, or GUI, and anything that you could draw on-screen
could be printed out. Microsoft provided Windows drivers for the most common printers. Over time, as new
versions of Windows came out, more and more printers were supported. Now all that the programmers had to
do was write code for Windows, and they could use that same code to talk to any printer that Windows
supported.
[ Team LiB ]
[ Team LiB ]
GDI+ is an application-level library that allows applications to interact with display devices such as monitors,
printers, and scanners through the device drivers. Figure 11.1 illustrates the role of GDI+ in the drawing
408
409
process. The application passes data to GDI+. GDI+ is responsible for converting the data into graphics
format (pixels) with the help of display drivers and sending it to the display driver, which displays the data on
a device such as a monitor.
The printing process, which is very similar to the drawing process, is shown in Figure 11.2. The application
sends data to GDI+, which communicates with a printer driver that sends data to the printer.
The drawing process involves a surface, which is the container for graphics shapes. In Windows applications,
a form works as a drawing surface. In previous chapters we used the Graphics object associated with a form
to access the surface associated with a form.
There are several ways to get the Graphics object associated with a form. The simplest way is to use the
form's paint event handler and [Link] property, which returns the Graphics object for
the form to which this paint event handler belongs. Another way is to use the CreateGraphics method.
Listing 11.1 uses [Link] to get the Graphics object associated with a form. Once you
have the drawing surface (Graphics object), you can use draw and fill methods.
The printing process is somewhat different from the drawing process. In a printing process, a printer works as
a drawing surface. In a drawing process, we already have a form as a drawing surface. To print something on
a printer, however, we need the printer object. The basic steps of a printing process are
409
410
Step 3. Call the draw and fill methods of the Graphics object.
In Sections 11.2.2 and 11.2.3 we will discuss the printing process in more detail.
Before we discuss the programmatic flow of a printing process, let's look at the conceptual flow. Every
printing process involves five basic steps, as illustrated in Figure 11.3.
Step 1. Specify a printer. In this step we select a printer to be used in the printing process. You may
want to select a printer from multiple printers available to your application.
Step 2. Set the printer properties. In this step we can set properties such as color, paper tray, paper
size, and print quality. This step is optional; if we do not set printer properties, the process uses
default settings.
Step 3. Get the printer surface. Unlike the drawing surface (a form), which is available on the form's
paint event handler, the printer surface is available only through the print-page event handler. As
such, this step requires creating a print-page event handler. One parameter of the event handler is of
type PrintPageEventArgs, whose Graphics member represents the printer surface associated with
this print-page event handler. In Section 11.2.3 we will see how to implement the print-page event
handler programmatically.
Step 4. Draw graphics shapes, lines, curves, text, and images. Once we have the printer surface,
everything works in much the same way as the drawing process. We can call draw and fill methods to
draw lines, curves, shapes, text, and images.
Step 5. Print. After we call the draw and fill methods of the Graphics object associated with a printer,
the final step is to print the objects.
410
411
The previous section dealt with the conceptual flow of the printing process. In this section we will examine
the programmatic flow.
Figure 11.4 is a flowchart displaying the four programmatic steps of the printing process.
Step 1. Create a PrintDocument object and specify the printer. This printer will be used as a surface.
Step 2. Set the printer and page properties. We set the PrinterSettings and PageSettings objects
for this optional step. If we don't set these properties, the default settings of the printer will be used.
We will cover PrinterSettings and PageSettings in more detail later.
Step 3. Set the print-page event handler. The print-page event handler is responsible for printing. We
create a print-page event handler by setting the [Link] member. Process A (see
Figure 11.5) is called from the print-page event handler, as illustrated in Figure 11.4.
411
412
Step 4. Print the document. Finally, we call the [Link] method, which sends printing
objects to the printer.
Process A, which is shown in Figure 11.5, describes how and what to send to the printer. This process is
defined as the print-page event handler:
The second parameter, PrintPageEventArgs, provides access to the printer surface through its Graphics
member. As Figure 11.5 shows, first we get the Graphics object from PrintPageEventArgs.
The next step is to set the page and paper setting using the MarginBounds, PageBounds, and PageSettings
members of the PrintPageEventArgs enumeration. We will discuss these properties in more detail later.
The final step of this process is to call draw and fill methods of the Graphics object as we used to do in the
drawing process. We will see a working example of this process in Section 11.3.
using [Link];
Alternatively, we can use the [Link] namespace by adding it to the classes directly.
Note
Before you use any printer-related classes in your application, a printer must be installed on your machine.
412
413
[ Team LiB ]
[ Team LiB ]
Using Visual Studio .NET, create a Windows application project named HelloPrinterSamp, as shown in
Figure 11.6.
using [Link];
Then we add controls for a label, a combo box, and a button to the form. We change the Text and Name
properties of the form and these controls. (See the online source code for more details.) The final form should
look like Figure 11.7.
413
414
When you run this application, the combo box will display the available printers on your machine. You can
select any printer from this list, and when you click the Hello Printer button, it will print "Hello Printer!" on
your printer.
We load the available printers on the form's load event handler. The [Link]
property returns the installed printers on a machine. [Link] returns
the total number of printers. In Listing 11.2 we check if printers are installed on the machine, read them, and
add them to the printer list combo box.
The next step is to add code to the Hello Printer button click event handler (see Listing 11.3). This code is
responsible for printing. We create a PrintDocument object and set the [Link].
PrinterName property to the printer selected from the printer list combo box. Then we add a print-page event
handler and call the [Link] method, which prints the document.
414
415
new PrintPageEventHandler(pd_PrintPage);
// Print the document
[Link]();
}
The last step is to add the print-page event handler code (see Listing 11.4). This code is responsible for
creating a Graphics object for the printer. It calls the DrawString method, which is responsible for drawing
text. First we create a Graphics object from [Link]. Then we create Font and
SolidBrush objects and call DrawString to draw some text on the printer. The DrawString method takes a
string that represents the text to be drawn; the font; a brush; and a layout rectangle that represents the starting
point, width, and height of a rectangle for the text.
Note
See Chapter 3 for more detail on the DrawString method. And for more about solid brushes and fonts, see
Chapters 4 and 5, respectively.
Now you can run the application, select a printer from the list, and click the Hello Printer button. You should
see "Hello Printer!" on your printed page.
[ Team LiB ]
[ Team LiB ]
The PrinterSettings object is the gateway to reading and setting printer settings.
PrinterSettings specifies how a document will be printed during a print process.
415
416
After creating a PrinterSettings object instance, we usually use the
[Link] or [Link] property to access
the PrinterSettings objects corresponding to the PrintDocument and PageSettings
objects, respectively. We will discuss these in more detail in a moment.
The InstalledPrinters static property returns the names of all available printers on a
machine, including printers available on the network. This property returns all the printer
names in a [Link] object.
foreach(String printer in
[Link])
{
string str = [Link]();
}
The PaperSizes property returns the paper sizes supported by a printer. It returns all the paper
sizes in a [Link] object.
The PrinterResolutions property returns all the resolutions supported by a printer. It returns
all the printer resolutions in a [Link] object that
contains PrinterResolution objects.
416
417
Listing 11.7 reads the printer resolutions and adds them to a ListBox control. Here
YourPrinterName is the name of the printer you want to use. If you do not set a printer name,
the default printer will be used.
The PrinterResolution class, which represents the resolution of a printer, is used by the
PrinterResolutions and PrinterResolution properties of PrinterSettings to get and set
printer resolutions. Using these two properties, we can get all the printer resolutions available
on a printer. We can also use it to set the printing resolution for a page.
The PrinterResolution class has three properties: Kind, X, and Y. The Kind property is used
to determine whether the printer resolution is the PrinterResolutionKind enumeration type
or Custom. If it's Custom, the X and Y properties are used to determine the printer resolution in
the horizontal and vertical directions, respectively, in dots per inch. If the Kind property is not
Custom, the value of X and Y each is 1.
The CanDuplex property is used to determine whether a printer can print on both sides of a
page. If so, we can set the Duplex property to true to print on both sides of a page.
Listing 11.8 determines whether your printer can print on both sides of a page. If your program
responds true, you have a very good printer.
The Duplex enumeration specifies the printer's duplex settings, which are used by
PrinterSettings. The members of the Duplex enumeration are described in Table 11.1.
The Collate property (both get and set) is used only if we choose to print more than one copy
of a document. If the value of Collate is true, an entire copy of the document will be printed
before the next copy is printed. If the value is false, all copies of page 1 will be printed, then
all copies of page 2, and so on.
The code snippet that follows sets the Collate property of PrinterSettings to true:
417
418
[Link] The Copies Property
The Copies property (both get and set) allows us to enter the number of copies of a document
that we want to print. Not all printers support this feature (in which case this setting will be
ignored). The MaximumCopies property, which is described in Section [Link], tells us how
many copies the printer can print.
Let's assume you have a low-end printer and need to print on both sides of the page. To do this,
you would need to create a custom software solution. Let's also assume that your application is
printing a 100-page text document. Because the document consists of text alone, this is not too
difficult to achieve. You would simply read from a text stream and keep track of whether you have
the space to print the next line. If not, you would tell the printer to go to another page. In this
scenario you would end up with 100 single-sided pages.
So how do you get double-sided printing? In the tradition of good programming, you cheat, of
course! The solution to this problem is to track the page number, and on the first pass print only
odd-numbered pages (1, 3, 5, and so on). Once you have done this, display a dialog box that tells
you to take all the sheets of paper just printed and reload them into the printer so they will be fed
into the printer upside down. Now you can print the even-numbered pages (2, 4, 6, and so on).
Voilà! The user gets duplex printing functionality from a cheap printer.
418
419
[Link] The IsPlotter Property
The IsPlotter property tells us if the printer we're using is actually a plotter that can accept plotter
commands.
If we print without setting the PrinterName property, our printout will be sent to the default printer. The
PrinterName property allows us to specify a printer to use. The IsValid property tells us whether the
PrinterName value we have selected represents a valid printer on our system.
The MaximumCopies property determines how many copies the printer can print. Some printers do not allow
us to print more than one copy at a time.
Listing 11.10 reads the maximum number of copies that a printer can print.
The SupportsColor property tells us whether the current printer supports printing in color. It will return true
if the printer supports color printing and false otherwise.
Listing 11.11 reads the value of the SupportsColor property to find out whether a printer supports colors.
419
420
[Link] Other PrinterSettings Properties
Besides the properties discussed already, the PrinterSettings class provides the additional properties listed
in Table 11.2. We will discuss these properties in detail in our examples.
Most printers can use papers of more than one size (height and width). The PaperSize class is used to read
and set the paper size used by a printer.
The PaperSize class represents the size of paper used in printing. This class is used by PrinterSettings
through its PaperSizes property to get and set the paper sizes for the printer.
Property
Description
DefaultPageSettings
FromPage
Returns the page number of the first page to print. Both get and set.
IsDefaultPrinter
LandscapeAngle
Returns the angle, in degrees, by which the portrait orientation is rotated to produce the landscape orientation.
Valid rotation values are 90 and 270 degrees. If landscape is not supported, the only valid rotation value is 0
degrees.
MaximumPage
Returns the maximum value of FromPage or ToPage that can be selected in a print dialog. Both get and set.
MinimumPage
Returns the minimum value of FromPage or ToPage that can be selected in a print dialog. Both get and set.
PrintRange
Returns the page numbers that the user has specified to be printed. Both get and set.
PrintToFile
Returns a value indicating whether the printing output is sent to a file instead of a port. Both get and set.
ToPage
420
421
Returns the page number of the last page to print. Both get and set.
The PaperSize class has four properties: Height, Kind, PaperName, and Width. Height, Width, and
PaperName have both get and set access. The Height and Width properties are used to get and set the paper's
height and width, respectively, in hundredths of an inch. The PaperName property is used to get and set the
name of the type of paper, but it can be used only when the Kind property is set to Custom. The Kind property
returns the type of paper.
We can construct custom paper sizes using the PaperSize class. Listing 11.12 reads the PaperSize
properties.
The PaperSource class specifies the paper tray from which the printer retrieves the paper for the current
printing task. This class is used by PrinterSettings through its PaperSources property to get and set the
paper source trays that are available on the printer. The PaperSize class has two properties: Kind and
SourceName. The Kind property returns an enumerated value for the paper source, and SourceName returns the
name of the paper source as a string.
Listing 11.13 reads all the paper sources and displays them in a message box.
The PrinterResolutionKind enumeration specifies a printer resolution, as described in Table 11.3. This
enumeration is used by the PrinterResolution, PrinterSettings, and PageSettings classes.
421
422
printer. PaperSizeCollection contains PaperSizes objects.
2. [Link]. A printer may support different paper sources (trays).
This class represents a collection of paper sources (trays) provided by a printer.
PaperSourceCollection is available via the PaperSources property and contains PaperSource
objects.
3. [Link]. A printer may support different resolutions.
This class represents a collection of resolutions supported by a printer.
PrinterResolutionCollection is accessible via the PrinterResolutions property and contains
PrinterResolution objects.
Member
Description
Custom
Custom resolution
Draft
Draft-quality resolution
High
High resolution
Low
Low resolution
Medium
Medium resolution
All of these collection classes provide Count and Item properties. The Count property returns the total
number of items in a collection, and the Item property returns the item at the specified index. We will use
these classes in our samples.
On the basis of the preceding discussion of printer settings, and of printerrelated classes and their members,
let's write an application using these classes. In this application we will display available printers, the
resolutions they support, available paper sizes, and other printer properties. This application will also allow us
to set printer properties.
First we create a Windows application and add a combo box, two list boxes, three buttons, six check boxes,
and two text boxes to the form. The final form looks like Figure 11.8. Then we add a reference to the
[Link] namespace.
422
423
Figure 11.8. The printer settings form
Next we write code. The Available Printers combo box displays all available installed printers on the machine
in the ListBox control. We load all installed printers on the form's load event. As Listing 11.14 shows, we use
the InstalledPrinters static property of PrinterSettings, which returns all installed printer names. We
check if the installed printers count is more than 0 and add the installed printers to the combo box.
The Get Printer Resolution button returns resolutions supported by a printer selected in ListBox1. The
PrinterResolutions property of PrinterSettings returns the printer resolutions supported by the printer.
Listing 11.15 reads all available resolutions for the selected printer in ListBox1 and adds them to ListBox2.
423
424
if([Link] == [Link])
{
[Link]("Select a printer from the list");
return;
}
// Get the current selected printer from the
// list of printers
string str = [Link]();
// Create a PrinterSettings object
PrinterSettings ps = new PrinterSettings();
// Set the current printer
[Link] = str;
// Read all printer resolutions and add
// them to the list box
foreach(PrinterResolution pr
in [Link])
{
[Link]([Link]());
}
}
The Get Paper Size button returns the available paper sizes. Again we use the PaperSizes property of
PrinterSettings, which returns all available paper sizes. Listing 11.16 reads all available paper sizes and
adds them to the list box.
The Get Printer Properties button gets the printer properties and sets the check boxes and text box controls
according to the values returned. The Get Printer Properties button click event handler code is given in Lising
11.17. We read many printer properties that were discussed earlier in this chapter.
424
425
PrinterSettings ps = new PrinterSettings();
string str = [Link]();
[Link] = str;
// Check if the printer is valid
if(![Link])
{
[Link]("Not a valid printer");
return;
}
// Set printer name and copies
[Link] = [Link]();
[Link] = [Link]();
Now let's run the application. By default, the Available Printers combo box displays all available printers.
Select a printer from the list, and click the Get Printer Resolution button, which displays the printer
resolutions supported by the selected printer. Also click on the Get Paper Size and Get Printer Properties
buttons. The final output of the application is shown in Figure 11.9.
425
426
[ Team LiB ]
[ Team LiB ]
The PrintDocument class is used to tell the printing system how printing will take
place. Table 11.4 describes the properties of the PrintDocument class.
426
427
while printing the
document.
PrintController Returns the print
controller that guides the
printing process.
PrinterSettings Returns the printer
settings represented by a
PrinterSettings object.
Method
Description
OnBeginPrint
Raises the BeginPrint event, which is called after the Print method and before the first page of the
document is printed.
OnEndPrint
Raises the EndPrint event, which is called when the last page of the document has been printed.
OnPrintPage
OnQueryPageSettings
Raises the QueryPageSettings event, which is called immediately before each PrintPage event.
All of these methods allow derived classes to handle the event without attaching a delegate. This is the
preferred technique for handling the event in a derived class. We will discuss these methods and their events,
and how to handle them, in our examples.
During the printing process, the printing system fires events according to the stage of a printing process. The
three common events are BeginPrint, PrintPage, and EndPrint. As their names indicate, the BeginPrint
event occurs when the Print method is called, and the EndPrint event occurs when the last page of the
document has been printed. The PrintPage event occurs for each page being printed (as in Figure 11.10)
when the Print method is called and after the BeginPrint event has occurred.
427
428
Figure 11.10 shows a flowchart for the print events during a printing process. The BeginPrint event is raised
after the Print method is called. Then the printing process checks if there are any pages. If there are, the
PrintPage event occurs, which is responsible for the actual printing, and the control goes back to check if
there are more pages to print. When all pages are done printing, the EndPage event is fired.
The PrintEventArgs class provides data for BeginPrint and EndPrint events. This class is inherited from
CancelEventArgs, which implements a single property called Cancel, that indicates if an event should be
canceled (in the current .NET Framework release, PrintEventArgs is reserved for future use).
The BeginPrint event occurs when the Print method is called and before the first page prints. BeginPrint
takes a PrintEventArgs object as an argument. This event is the best place to initialize resources. The
PrintEventHandler method, which is used to handle the event code, is called whenever the BeginPrint
event occurs.
The PrintPage event occurs when the Print method is called and before a page prints. When we create a
PrintPageEventHandler delegate, we identify a method that handles the PrintPage event. The event handler
is called whenever the PrintPage event occurs.
The code snippet that follows creates a PrintPageEventHandler delegate, where pd_PrintPage is an event
handler:
PrintPageEventHandler takes a PrintPageEventArgs object as its second argument, which has the six
properties described in Table 11.6.
The following code snippet shows how to get the Graphics object from PrintPageEventArgs:
428
429
// Use g now
}
The EndPrint event occurs when the last page of the document has been printed. It takes a PrintEventArgs
object as an argument. This is the best place to free your resources. The PrintEventHandler method is called
whenever the EndPrint event occurs and is used to handle the event code.
Now let's write an application that shows how to use these events. We create a Windows application and add a
a combo box and a button to the form. We set [Link] to printersList and the text of the button to
PrintEvents Start. The final form looks like Figure 11.11.
Property
Description
Cancel
Indicates whether the print job should be canceled. Both get and set.
Graphics
HasMorePages
Indicates whether an additional page should be printed. Used in multipage documents before the Print
method is called. Both get and set.
MarginBounds
PageBounds
429
430
Returns the total area of the page.
PageSettings
using [Link];
Then we add code on the form's load event handler that adds all installed printers to the combo box (see
Listing 11.18).
Now we write code for the button click event handler. Listing 11.19 creates all three print event handlers,
attaches them to a PrintDocument object, and calls PrintDocument's print methods.
430
431
As stated earlier, the BeginPrint event handler can be used to initialize resources before printing starts, and
the EndPrint event handler can be used to free allocated resources. Listing 11.20 shows all three print event
handlers. The PrintPage event handler uses the properties for PrintPageEventArgs and calls
DrawRectangle and FillRectangle to print the rectangles. This example simply shows how to call these
events. You can use the PrintPage event handler to draw anything you want to print, as we have seen in
previous examples.
As this discussion has shown, the print event can be handy when you need to initialize or free resources.
[ Team LiB ]
[ Team LiB ]
431
432
As usual, we create a Windows application and add a reference to the [Link] namespace.
We then add a text box and four buttons to the form. We also change the Name and Text properties of the
button controls. The final form looks like Figure 11.12. As you might guess, the Browse Text File button
allows us to browse for text files.
The code for the Browse Text File button is given in Listing 11.21. This button allows you to browse a file
and adds the selected file name to the text box. Clicking the Print Text File button prints the selected text file.
We use an OpenFileDialog object to open a text file and set [Link] as the selected file name. The
functionality of the Print Text and Print Events buttons is obvious.
Note
C# Corner's FAQ ([Link] includes a long list of .NET how-tos and frequently
asked questions and contains the code for these simple functionalities.
Listing 11.21 The Browse Text File button click event handler
432
433
}
Now let's add code for the Print Text File button click. First we add two private variables to the application as
follows:
Then we proceed as shown in Listing 11.22. The code is pretty simple. First we make sure that the user has
selected a file name. Then we create a StreamReader object and read the file by passing the file name as the
only argument. Next we create a font with font family Verdana and size 10 (see Chapter 5 for more on fonts).
After that we create a PrintDocument object, add a PrintPage event handler, and call the Print method. The
rest is done by the PrintPage event handler.
Note
Listing 11.22 The Print Text File button click event handler
The code for the PrintPage event handler PrintTextFileHandler is given in Listing 11.23. Here we read
one line at a time from the text file, using the [Link] method, and call DrawString, which
prints each line until we reach the end of the file. To give the text a defined size, we use the
[Link] method.
433
434
Note
See Chapter 3 and 5 for details about the DrawString method and fonts, respectively.
You should be able to add code for the Print Text and Print Events buttons yourself. Their functionality
should be obvious.
Now run the application, browse a text file, and hit the Print Text File button, and you should be all set.
Note
Using the same method, you can easily add printing functionality to the GDI+ editor. You can add a menu
item called Print to the editor that will print an opened text file.
[ Team LiB ]
[ Team LiB ]
434
435
Again, we create a Windows application and add a main menu to the form. We add four menu items to the
main menu. The final form looks like Figure 11.13. As you might guess, the Draw Items and View Image
menu items will draw graphics objects and show an image, respectively. The Print Image and Print Graphics
Items menu items will print the image and the graphics items, respectively.
Let's write code for the menu items. We'll do the Draw Items first, as in Listing 11.24. This menu item draws
two lines, a rectangle, and an ellipse. First we create a Graphics object using the [Link]
method and call the DrawLine, DrawRectangle, and FillEllipse methods. See Chapter 3 for more on these
methods.
435
436
// Draw graphics items
[Link]([Link], 10, 10, 10, 100);
[Link]([Link], 10, 10, 100, 10);
[Link]([Link], 20, 20, 200, 200);
[Link]([Link], 40, 40, 100, 100);
// Dispose of object
[Link]();
}
Now let's write code for Print Graphics Items. We want to print the output shown in Figure 11.14. We create a
PrintDocument object, add a PrintPage event handler, and call the Print method. The PrintPage event
handler draws the graphics items.
Listing 11.25 contains two methods. The PrintGraphicsItems_Click method is a menu click event handler
that creates a PrintDocument object, sets its PrintPage event, and calls the Print method. The second
method, PrintGraphicsItemsHandler, simply calls the draw and fill methods of
[Link].
436
437
Graphics g = [Link];
// Draw graphics items
[Link]([Link], 10, 10, 10, 100);
[Link]([Link], 10, 10, 100, 10);
[Link]([Link], 20, 20, 200, 200);
[Link]([Link], 40, 40, 100, 100);
}
If you run the application and click on Print Graphics Items, the printer will generate output that looks like
Figure 11.14.
If you did not skip Chapters 7 and 8, then you already know how the DrawImage method of the Graphics
object is used to draw images. Similarly, the DrawImage method of [Link] prints an
image to the printer, which then prints that image onto paper.
Before we add code for the View Image menu item, we need to add two application scope variables as
follows:
View Image lets us browse for an image and then draws it on the form. As Listing 11.26 shows, we create a
Graphics object using [Link]. Then we use OpenFileDialog to browse files on the system.
Once a file has been selected, we create the Image object by using [Link], which takes the file name
as its only parameter. Finally, we use DrawImage to draw the image.
437
438
}
if(curImage != null)
{
// Draw image using the DrawImage method
[Link](curImage, AutoScrollPosition.X,
AutoScrollPosition.Y,
[Link], [Link] );
}
// Dispose of object
[Link]();
}
Now we run the application and select an image. Figure 11.15 shows the output.
Note
Now let's write a Print Image menu item click handler. This option prints an image that we're currently
viewing on the form. As in the previous example, we create a PrintDocument object, add a PrintPage event
handler, and call the Print method. This time, however, instead of using the DrawRectangle and DrawLine
methods, we use the DrawImage method, which draws the image.
As Listing 11.27 shows, our code creates a PrintDocument object, sets the PrintPage event of
PrintDocument and the PrintPage event handler, and calls [Link]. The PrintPage event
handler calls DrawImage.
438
439
// Create a PrintDocument object
PrintDocument pd = new PrintDocument();
// Add the PrintPage event handler
[Link] += new PrintPageEventHandler
([Link]);
// Print
[Link]();
}
If we run the application, open and view a file, and click the Print Image menu item, we get a printout that
looks like Figure 11.15.
[ Team LiB ]
[ Team LiB ]
• PrintDialog
• PrintPreviewDialog
• PrintPreviewControl
• PageSetupDialog
These classes are also available as Windows Forms controls in Visual Studio .NET; we
can add them to a form by dragging the control from the toolbox. The toolbox with the
three print dialogs is shown in Figure 11.16.
439
440
However, adding and using these controls programmatically is even easier than using the
toolbox, as we will soon see. Before you learn how to use them, let's explore their
functionality.
The PrintDialog class represents the PrintDialog control in the .NET Framework
library. This class represents a standard Windows printer dialog, which allows the user to
select a printer and choose which portions of the document to print. Table 11.7 describes
the PrintDialog class properties. By default, all of these properties are false when a
PrintDialog object is created, and all the properties have both get and set options.
Besides the properties defined in Table 11.7, PrintDialog has one method called Reset.
This method resets all options, the last selected printer, and the page settings to their
default values.
Listing 11.28 creates a PrintDialog object, sets its properties, calls ShowDialog, and
prints the document.
// Call ShowDialog
if ([Link]() == [Link])
[Link]();
440
441
Indicates whether
the Pages option
button is enabled.
Document Identifies the
PrintDocument
object used to
obtain printer
settings.
PrinterSettings Identifies the printer
settings that the
dialog box modifies.
PrintToFile Indicates whether
the Print to file
check box is
checked.
ShowHelp Indicates whether
the Help button is
displayed.
ShowNetwork Indicates whether
the Network button
is displayed.
The PageSetupDialog class represents the PageSetupDialog control in the .NET Framework library. This
class represents a standard Windows page setup dialog that allows users to manipulate page settings, including
margins and paper orientation. Users can also set a PageSettings object through PageSetupDialog's
PageSettings property. Table 11.8 describes the properties of the PageSetupDialog class. All of these
properties have both get and set options.
As with PrintDialog, the PageSetupDialog class has a Reset method that resets all the default values for the
dialog.
Listing 11.29 creates a PageSetupDialog object, sets its properties, calls ShowDialog, and prints the
document.
Property
Description
AllowMargins
Indicates whether the margins section of the dialog box is enabled. By default, true when a PageSetupDialog
object is created.
AllowOrientation
Indicates whether the orientation section of the dialog box (landscape versus portrait) is enabled. By default,
true when a PageSetupDialog object is created.
AllowPaper
441
442
Indicates whether the paper section of the dialog box (paper size and paper source) is enabled. By default,
true when a PageSetupDialog object is created.
AllowPrinter
Indicates whether the Printer button is enabled. By default, true when a PageSetupDialog object is created.
Document
Identifies the PrintDocument object from which to get page settings. By default, null when a
PageSetupDialog object is created.
MinMargins
Indicates the minimum margins the user is allowed to select, in hundredths of an inch. By default, null when
a PageSetupDialog object is created.
PageSettings
Identifies the page settings to modify. By default, null when a PageSetupDialog object is created.
PrinterSettings
Identifies the printer settings that the dialog box will modify when the user clicks the Printer button. By
default, null when a PageSetupDialog object is created.
ShowHelp
Indicates whether the Help button is visible. By default, false when a PageSetupDialog object is created.
ShowNetwork
Indicates whether the Network button is visible. By default, true when a PageSetupDialog object is created.
// PageSetupDialog settings
[Link] = printDoc;
[Link] = false;
[Link] = false;
[Link] = false;
[Link] = false;
[Link]();
if ([Link]() == [Link])
{
[Link] =
[Link];
[Link] =
[Link];
}
442
443
11.8.3 The PrintPreviewDialog Control
The PrintPreviewDialog class represents the PrintPreviewDialog control in the .NET Framework library.
This class represents a standard Windows print preview dialog, which allows users to preview capabilities
before printing. The PrintPreviewDialog class is inherited from the Form class, which means that this dialog
contains all the functionality defined in Form, Control, and other base classes.
In addition to the properties provided by the base classes, this class has its own properties. Many of these
properties are very common and are provided by many controls. Table 11.9 describes a few important
PrintPreviewDialog class properties. All of these properties have both get and set options.
Listing 11.30 creates a PrintPreviewDialog object, sets its properties, calls ShowDialog, and prints the
document.
Property
Description
Document
HelpButton
Indicates whether a help button should be displayed in the caption box of the form. The default value is
false.
KeyPreview
Indicates whether the form will receive key events before the event is passed to the control that has focus. The
default value is false.
ShowInTaskbar
Indicates whether the form is displayed in the Windows taskbar. The default value is true.
TransparencyKey
Identifies the color that will represent transparent areas of the form.
443
444
UseAntiAlias
Indicates whether printing uses the anti-aliasing features of the operating system.
WindowState
Now let's create a Windows application. In this application you will see how to use the print dialogs in your
Windows applications.
We create a Windows application and add a MainMenu control to the form. We also add four menu items and a
separator to the MainMenu control. The final form looks like Figure 11.17.
As usual, our first step is to add some private variables to the project, as follows:
// Variables
private Image curImage = null;
private string curFileName = null;
private PrintPreviewDialog previewDlg = null;
private PageSetupDialog setupDlg = null;
private PrintDocument printDoc = null;
private PrintDialog printDlg = null;
We also add the following namespaces to the project:
using [Link];
using [Link];
using [Link].Drawing2D;
using [Link];
444
445
On our form's load event, we initialize these dialogs. We also create a PrintPage event handler and add it to
the PrintDocument object, as shown in Listing 11.31.
Now we add the PrintPage event handler, which calls DrawGraphicsItems as shown in Listing 11.32. We
pass [Link] as the only parameter to DrawGraphicsItems.
The DrawGraphicsItems method draws an image and text on the printer or the form, depending on the
Graphics object. If we pass [Link], the DrawGraphicsItems method will draw graphics objects on
the form, but if we pass [Link], this method will send drawings to the printer.
The code for the DrawGraphicsItems method is given in Listing 11.33. This method also sets the smoothing
mode and text qualities via the SmoothingMode and TextRenderingHint properties. After that it calls
DrawImage and DrawText.
445
446
[Link], [Link] );
}
// Draw a string
[Link]("Printing Dialogs Test",
new Font("Verdana", 14),
new SolidBrush([Link]), 0, 0);
}
There's just one more thing to do before we write the menu item event handlers. We call DrawGraphicsItems
from the form's paint event handler, as Listing 11.34 shows. Adding this code will display the drawing on the
form.
Now we can write code for the menu items. The Open File menu item just lets us browse images and creates
an Image object by calling the [Link] method, as Listing 11.35 shows.
The code for PrintPreviewDialog, PageSetupDialog, and PrintDialog is given in Listing 11.36. We show
PrintDialog and call its [Link] method if the user selects OK on the print dialog. We set
PageSetupDialog page and printer settings when the user selects OK on the page setup dialog. For the print
preview dialog, we set the UseAntiAlias property and call ShowDialog.
446
447
Listing 11.36 Print dialogs
Now when we run the application and browse an image using the Open File menu item, the form looks like
Figure 11.18.
If we click on Print Preview, our program will display the print preview dialog, as shown in Figure 11.19.
447
448
Figure 11.19. The print preview dialog
As stated earlier, the page setup dialog allows us to set the page properties, including size, sources,
orientation, and margins. Clicking on Print Setup on the dialog menu brings up the page setup dialog, which is
shown in Figure 11.20.
Clicking on Print Dialog calls up the standard print dialog, shown in Figure 11.21.
448
449
[ Team LiB ]
[ Team LiB ]
Page settings are the properties of a page that are being used when a page is printed,
including color, page margins, paper size, page bounds, and page resolution.
The PageSettings class represents page settings in the .NET Framework library. This class
provides members to specify page settings. It is used by the
[Link] property to specify the page settings of a
PrintDocument object. Table 11.10 describes the properties of the PageSettings class.
Besides the properties described in Table 11.10, the PageSettings class provides three
methods: Clone, CopyToHdevmode, and SetHdevmode. The Clone method simply creates a
copy of the PageSettings object. CopyToHdevmode copies relevant information from the
PageSettings object to the specified DEVMODE structure, and SetHdevmode copies relevant
information to the PageSettings object from the specified DEVMODE structure. The DEVMODE
structure is used by Win32 programmers.
449
450
11.9.2 Page Margins
The Margins class represents a page margin in the .NET Framework library. It allows you
to get the current page margin settings and set new margin settings. This class has four
properties Left, Right, Top, and Bottom which represent the left, right, top, and bottom
margins, respectively, in hundredths of an inch. This class is used by the Margins property
of the PageSettings class. We will use this class and its members in our examples.
As mentioned earlier, the PaperSize class specifies the size and type of paper. You can create your own
custom paper sizes. For example, Listing 11.37 creates a custom paper size with a height of 200 and a width
450
451
of 100.
The PaperKind enumeration, as we saw earlier, is used by the Kind property to specify standard paper sizes.
This enumeration has over 100 members. Among them are A2, A3, A3Extra, A3ExtraTransverse, A3Rotated,
A3Transverse, A4, A5, A6, Custom, DCEnvelope, Executive, InviteEnvelope, ItalyEnvelope,
JapanesePostcard, Ledger, Legal, LegalExtra, Letter, LetterExtra, LetterSmall, Standard10x11
(10x14, 10x17, 12x11, 15x11, 9x11), Statement, and Tabloid.
The PaperSourceKind enumeration represents standard paper sources. Table 11.11 describes the members of
the PaperSourceKind enumeration.
Member
Description
AutomaticFeed
Cassette
A paper cassette
Custom
Envelope
An envelope
FormSource
LargeCapacity
LargeFormat
451
452
Large-format paper
Lower
Manual
ManualFeed
Middle
SmallFormat
Small-format paper
TractorFeed
A tractor feed
Upper
Now let's create an application that will allow us to get and set page settings. In this application we will create
a custom dialog.
We start by creating a new Windows application in [Link]. We add some controls to the form, with the
result shown in Figure 11.22. The Available Printers combo box displays all available printers. The Size and
Source combo boxes display paper sizes and sources, respectively. The Paper Orientation section indicates
whether paper is oriented in landscape mode or portrait mode. The Paper Margins text boxes obviously
represent left, right, top, and bottom margins. The Bounds property is represented by the Bounds (Rectangle)
text box. The Color Printing check box indicates whether the printer supports color printing. The Set
Properties button allows us to enter new values in the controls.
452
453
The form's load event (see Listing 11.38), loads all the required PageSettings-related settings using the
LoadPrinters, LoadPaperSizes, LoadPaperSources, and ReadOtherSettings methods.
The LoadPrinters, LoadPaperSizes, LoadPaperSources, and ReadOtherSettings methods are used to load
printers, paper sizes, paper sources, and other properties, respectively. The LoadPrinters method is given in
Listing 11.39. We simply read the InstalledPrinters property of PrinterSettings and add printers to the
printersList combo box.
The LoadPaperSizes method (see Listing 11.40), loads all available paper sizes to the combo box. We read
the PaperSizes property of PrinterSettings and add the paper type to the combo box. Then we create a
custom paper size and add this to the combo box as well. This example will give you an idea of how to create
your own custom paper sizes.
453
454
Listing 11.40 Loading paper sizes
The LoadPaperSources method (see Listing 11.41), reads all available paper sources and adds them to the
PaperSourceCombo combo box. We use the PaperSources property of PrinterSettings to read the paper
sources.
The last method, ReadOtherSettings, reads other properties of a printer, such as whether it supports color,
margins, and bounds. Listing 11.42 shows the ReadOtherSettings method.
454
455
if([Link])
[Link] = true;
else
[Link] = false;
// Page margins
[Link] =
[Link]();
[Link] =
[Link]();
[Link] =
[Link]();
[Link] =
[Link]();
// Landscape or portrait
if([Link])
[Link] = true;
else
[Link] = true;
// Bounds
[Link] =
[Link]();
}
Note
Remember that you need to add a reference to the [Link] namespace to your application
whenever you use classes from this namespace.
Now if we run the application, its form looks like Figure 11.23. Each of the Windows controls displays its
intended property.
Finally, we want to save settings through the Set Properties button click and write code for a Cancel button.
On the Set Properties button click, we set the properties using PrinterSettings. Make sure a printer is
available in the Available Printers combo box. The Cancel button simply closes the dialog.
455
456
The code for the Set Properties and Cancel button click event handlers is given in Listing 11.43, in which we
set the page settings, color, and landscape properties of a page.
// Landscape or portrait?
if([Link] )
[Link] = true;
else
[Link] = false;
}
The preceding discussion should enable you to customize page settings in the way that you want, instead of
using the standard page settings dialog provided in the PageSettingsDialog class.
Note
Even though the printing functionality defined in the [Link] namespace allows
developers to customize the standard Windows dialogs, I recommend that you use the standard Windows
dialogs unless you can't live without customizing them.
The PrintRange enumeration is used to specify the part of a document to print. This enumeration is used by
the PrinterSettings and PrintDialog classes. Table 11.12 describes the members of the PrintRange
enumeration.
You can use the PrintRange property of the PrinterSettings object to set the print range. Here's an
example of code that does this:
[Link] = [Link];
456
457
Table 11.12. PrintRange members
Member
Description
AllPages
Selection
SomePages
[ Team LiB ]
[ Team LiB ]
To demonstrate how to do this, our next program produces a useful printout showing all the fonts installed on
your computer. This program is a useful tool for demonstrating the calculation of how many pages to print
when you're using graphical commands to print.
We will use the PrintPreview facility to display the output in case you don't have access to a printer. In this
example we need to track how many fonts have been printed and how far down the page we are. If we're
going to go over the end of the page, we drop out of the pd_PrintPage event handler and set
[Link] to true to indicate that we have another page to print.
To see this functionality in action, let's create a Windows application and add a menu with three menu items
and a RichTextBox control to the form. The final form is shown in Figure 11.24.
457
458
The Display Fonts menu displays available fonts on the machine. Before we add code to this menu, we add
the following variables:
The code for the Display Fonts menu click is given in Listing 11.44. Here we read installed fonts on the
system and display them in the rich text box. We use InstalledFontCollection to read all installed fonts on
a machine. Then we use the [Link] property and make a loop to read all the
font families. We also check if these families support different styles, including regular, bold, italic, and
underline, and we add some text to the rich text box with the current font.
Note
458
459
f = new Font([Link](1),
12, [Link]);
else if([Link]([Link]))
f = new Font([Link](1),
12, [Link]);
else if ([Link]([Link]))
f = new Font([Link](1),
12, [Link]);
else
f = new Font([Link](1),
12, [Link]);
[Link]=f;
[Link](
[Link](1)+"\r\n");
[Link]=f;
[Link](
"abcdefghijklmnopqrstuvwxyz\r\n");
[Link]=f;
[Link](
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n");
[Link](
"==============================\r\n");
}
}
The code for the Print Preview and Print menu items is given in Listing 11.45. This code should look familiar
to you. We simply create PrintDocument and PrintPreviewDialog objects, set their properties, add a
print-page event handler, and call the Print and Show methods.
The print-page event handler, pd_PrintPage, is given in Listing 11.46. We print fonts using DrawString, and
we set [Link] to true. To make sure the text fits, we increase the y-position by
60 units.
459
460
Listing 11.46 The print-page event handler
That's it. If we run the program, the Print menu prints multiple pages, and the Print Preview menu shows the
print preview on two pages (see Figure 11.25).
460
461
As you can see, it's pretty easy to create multipage report generators. Now you can use the print options to
print documents with multiple pages.
If you want to display the name of the document you're printing, you can use the DocumentName property of
the PrintDocument object:
We have seen that using the DocumentPrintPreview class is fairly straightforward. In reality, all that's
happening is that this control is passed a graphics class representing each page in a printout.
[ Team LiB ]
[ Team LiB ]
To see a marginal-printing sample, let's create a Windows application. We add two buttons to the form. The
final form is shown in Figure 11.27.
461
462
Now we add code for the Normal Printing and Marginal Printing button click event handlers, as in Listing
11.47. Each handler creates a PrintDocument object, adds a PrintPage event handler, and calls the Print
method. The PrintPage event handlers for Normal Printing and Marginal Printing are NormalPrinting and
MarginPrinting, respectively.
Listing 11.47 The Normal Printing and Marginal Printing button event handlers
Now let's look at the NormalPrinting handler (see Listing 11.48). We start with the top location of the text as
unit 1. Then we calculate the next line's position using the height of the font and draw four lines with the
values of the top, left, bottom, and right margins. In the end we draw a rectangle with the default bounds of
the page.
462
463
// Create a font
Font font = new Font("Arial",16);
// Get the font's height
float fontheight = [Link]([Link]);
// Draw four strings
[Link]("Top Margin = "
+ [Link](),
font, [Link],
leftMargin, ypos);
ypos = ypos + fontheight;
[Link]("Bottom Margin = "
+ [Link](),
font, [Link],
leftMargin, ypos);
ypos = ypos + fontheight;
[Link] ("Left Margin = "
+ [Link](),
font, [Link],
leftMargin, ypos);
ypos = ypos + fontheight;
[Link] ("Right Margin = "
+ [Link](),
font, [Link],
leftMargin, ypos);
ypos = ypos + fontheight;
// Draw a rectangle with default margins
[Link](
new Pen([Link]),
[Link].X,
[Link].Y,
[Link],
[Link]);
}
If we run the application, we will see text describing the four margin values printed outside the rectangle.
Next comes code for the MarginPrinting event handler (see Listing 11.49). We use the default margin of the
page as the top location for the first text. Everything else is the same as in Listing 11.48.
463
464
font, [Link],
leftMargin, ypos);
ypos = ypos + fontheight;
[Link] ("Right Margin = "
+ [Link](),
font,[Link],
leftMargin, ypos);
ypos = ypos + fontheight;
// Draw a rectangle with default margins
[Link](
new Pen([Link]),
[Link].X,
[Link].Y,
[Link],
[Link]);
}
When we run this code, we will see text appearing inside the rectangle printed using the page margin values.
[ Team LiB ]
[ Team LiB ]
11.12 Getting into the Details: Custom Controlling and the Print
Controller
At this point you must feel like a printer master and have the confidence you need to write a printing
application. We have covered almost every aspect of printing in .NET, but guess what! There are still a few
surprises hidden in [Link]. You will probably never use the classes that we're going to
discuss in this section, but it's not a bad idea to know about them.
So far in this chapter we've created a PrintDocument object, created a PrintPage event handler, and called
the Print method of [Link] took care of everything internally for us. Now we will
see how to control PrintDocument. For this, we need a print controller, which controls how a PrintDocument
object handles printing.
The PrintController class represents print controllers in the .NET Framework library. It's an abstract base
class, so its functionality comes from its three derived classes: PreviewPrintController,
StandardPrintController, and PrintControllerWithStatusDialog. PrintController and its derived
classes are shown schematically in Figure 11.28.
Normally PrintController is used by PrintDocument. When PrintDocument starts printing by calling the
Print method, it invokes the print controller's OnStartPrint, OnEndPrint, OnStartPage, and OnEndPage
methods, which determine how a printer will print the document. Usually the OnStartPrint method of
PrintController is responsible for obtaining the Graphics object, which is later used by the PrintPage
event handler.
464
465
The StandardPrintController class is used to send pages to the printer. We set the PrintController
property of PrintDocument to [Link].
PrintControllerWithStatusDialog adds a status dialog to the printing functionality. It shows the name of
the document currently being printed. To attach PrintControllerWithStatusDialog, we set
PrintDocument's PrintController property to [Link].
The PreviewPrintController class is used for generating previews of pages being printed. Besides the
methods defined in the PrintController class, PreviewPrintController provides one property
(UseAntiAlias) and one method (GetPreviewPageInfo). The UseAntiAlias property indicates whether
anti-aliasing will be used when the print preview is being displayed.
The GetPreviewPageInfo method captures the pages of a document as a series of images and returns them as
an array called PreviewPageInfo. The PreviewPageInfo class provides print preview information for a
single page. This class has two properties: Image and PhysicalSize. The Image property returns an Image
object, which represents an image of the printed page, and PhysicalSize represents the size of the printed
page in hundredths of an inch.
Let's write a sample application. We create a Windows application, and we add a MainMenu control, an item,
and a StatusBar control to the form. Our final form looks like Figure 11.29.
Before adding any code to this form, we create a MyPrintController class, which is inherited from
StandardPrintController. You can use the PreviewPrintController or
PrintControllerWithStatusDialog classes in the same way. The code for the MyPrintController class is
given in Listing 11.50. We override all four methods: OnStartPrint, OnStartPage, OnEndPrint, and
OnEndPage. On these methods we notify the status bar about the status of the printing process. This
information could be useful for displaying page numbers or other print status information when we're printing
multipage documents.
465
466
}
public override void OnStartPrint
(PrintDocument printDoc,
PrintEventArgs peArgs)
{
[Link] = "OnStartPrint Called";
[Link](printDoc, peArgs);
}
public override Graphics OnStartPage
(PrintDocument printDoc,
PrintPageEventArgs ppea)
{
[Link] = "OnStartPage Called";
return [Link](printDoc, ppea);
}
public override void OnEndPage
(PrintDocument printDoc,
PrintPageEventArgs ppeArgs)
{
[Link] = "OnEndPage Called";
[Link](printDoc, ppeArgs);
}
public override void OnEndPrint
(PrintDocument printDoc,
PrintEventArgs peArgs)
{
[Link] = "OnEndPrint Called";
[Link] = str;
[Link](printDoc, peArgs);
}
}
To call the MyPrintController class, we need to set the PrintController property of PrintDocument to
invoke MyPrintController's overridden methods. Let's write a menu click event handler and set the
[Link] property there. In Listing 11.51 we create a PrintDocument object, set its
DocumentName and PrintController properties, enable the PrintPage event handler, and call Print to print
the document.
Listing 11.52 gives the code for the PrintPage event handler, which just draws some text on the printer.
466
467
new Font("Verdana", 20);
[Link]("Print Controller Test",
verdana20Font,
brush, 20, 20);
}
If we run the application and print, we will see that the status bar displays the status of the printing process.
The first event message is shown in Figure 11.30.
You can extend this functionality to write your own custom print controllers.
[ Team LiB ]
[ Team LiB ]
SUMMARY
Printing functionality in the .NET Framework library is defined in the [Link]
namespace. In this chapter we discussed almost every possible aspect of printing. We began by discussing the
history of printing in Microsoft Windows. Then we discussed printing-related functionality in the Microsoft
.NET Framework.
After a basic introduction to printing in .NET, you learned the basic steps required to write a printing
application and how printing differs from on-screen drawing. You also learned how to print simple text;
graphics objects such as lines, rectangles, and circles; images; text files; and other documents.
The PrinterSettings class provides members to get and set printer settings. We discussed how to use this
class and its members.
The .NET Framework library provides printing-related standard dialogs. You learned to use the PrintDialog,
PrintPreviewDialog, and PageSetupDialog classes to provide a familiar Windows look and feel in your
applications.
Multipage printing can be a bit tricky. You learned how to write an application with multipage printing
functionality.
467
468
At the end of this chapter we discussed how to write custom printing and page setup dialogs using
PageSettings and related classes. We also discussed the advanced custom print controller related classes
and how to use them in applications.
Using GDI+ in Web applications is a requirement for Web developers. Chapter 12 will cover the use of GDI+
to draw on the Web.
[ Team LiB ]
[ Team LiB ]
If we want to draw a rectangle on the Web, we create a Graphics object and call its DrawRectangle method.
However, getting a Graphics object for a Web page is different from getting one for a Windows Form, as we
will discuss in greater detail later.
Another restriction in Web applications is the fact that a browser can display only images. If we wanted to
draw a rectangle on a Web page, the rectangle would first have to be drawn and converted into an image and
then sent to the browser for display.
To draw graphics shapes in a Windows Forms application, we simply call the draw or fill method, and GDI+
draws the shape on the form, as Figure 12.1 shows.
468
469
Figure 12.1. Drawing in Windows Forms
Drawing in Web Forms involves one extra step. When you call a draw or fill method, GDI+ doesn't
communicate directly with the Web Forms. Instead, it allows us to save a graphics shape as an image. Later
we send the image to the browser for display. This process is shown in Figure 12.2.
After completing this chapter, you will be amazed by the power and flexibility of GDI+ and [Link].
[ Team LiB ]
[ Team LiB ]
To understand [Link] and Visual Studio .NET integration, we will write a simple non-GDI+ Web
application. In this application we will add some controls to a Web page: a generic button, a text box, and an
Image button. After adding these controls, we will write code in the button click event handler that will read
the contents of the text box as a file name and display the file in the Image button. Let's get started!
Creating a new [Link] Web application using Visual Studio .NET is simple: First we create a new project
by choosing File | New | Project | Visual C"docEmphStrong">[Link] Web Application template. As Figure
12.3 shows, we give our application the name FirstWebApp. It resides in the GDIPlusGuide folder of
localhost, which is the default Web server on our local machine.
469
470
The Location box displays the default option of [Link] and the application name. Here
localhost represents the default IIS server running on our local machine. The default virtual directory for
localhost is C:\Inetpub\wwwroot.
Note
If you are using a remote server for your development, you'll need to provide your server name instead of
localhost. You can either create the project in the root of the server or create a new folder.
Clicking the OK button creates a new directory, FirstWebApp in the server's virtual directory. It also creates a
new Web application and sends us to the default [Link] page (see Figure 12.4).
470
471
From here we can edit our page's HTML. Two modes are available: Design and HTML (see the bottom left
corner of Figure 12.4). We click the HTML button to edit the code, as shown in Figure 12.5.
The HTML view shows us the HTML code of a page, its controls, and its control properties. The HTML
editor also lets us edit the HTML manually. (Although we can edit the code of a page manually in HTML
view, we will not need to do that for the examples in this book.)
If we switch back to the design mode and right-click on the page, we see several options: View HTML
Source, Build Style, View in Browser, View Code, Synchronize Document Outline, and so on.
We can set the properties of a page by selecting Properties from the context menu (which we bring up with a
right mouse-click). The Properties menu opens the DOCUMENT Property Pages window (see Figure 12.6).
Three tabs are available in this window: General, Color and Margins, and Keywords. Most of the properties
are self-explanatory. The General tab contains page title, background image, target schema, character set,
page layout, and client and server language properties.
The Page Layout property has two options: GridLayout and FlowLayout. We use GridLayout when we want
to drop controls to the page and reposition them. If we want to add text to the page, we must set the page
layout to FlowLayout. After we set the Page Layout property to FlowLayout, the editor works as a text editor.
471
472
Visual Studio .NET provides a Web Forms control toolbox that's similar to the Windows control toolbox. We
can open the toolbox by selecting the View | Toolbox main menu item. The Web Forms category of the
toolbox contains the server-side controls (controls available on the server, for which all processing is done on
the server). When a browser requests a control, [Link] converts the request into HTML and sends it to the
browser. The HTML category contains HTML controls. HTML controls are simple HTML tags with all
processing done on the client side. As a result, HTML controls are often faster than server-side controls.
Let's switch the page back to the Design and GridLayout mode and add a button, a text box, and an Image
control to the page by dragging these controls from the Web Forms toolbox to [Link]. We will use
the View Image button to view an image. The ImageUrl property of the View Image button represents the
image that this control will view.
The page should now look like Figure 12.7 (after you position your controls). As the figure shows, we change
the button's text to "View Image" by right-clicking on the Properties menu item, which launches the
Properties window.
Figure 12.7. The [Link] design mode after the addition of Web Forms controls
The last step of this tutorial is to add an event handler for the button click event, which will set the ImageUrl
property of the Image button. This is similar to adding a control event in a Windows Forms application. You
can double-click on the button to add a button click event handler.
Double-clicking on the button adds a Button1_Click method to the [Link] class, which hosts
code for the page controls and events. Now we write a line of code that sets the ImageUrl property of the
Image control as the text of the TextBox control. The button click event handler code is given in Listing 12.1.
Now compile and run the project. In the text box we type "[Link] (or
any valid image URL) as the URL name and click the View Image button. The output of the program looks
472
473
Now that we have seen how to create a simple Web application using Visual Studio .NET and [Link], in
the next section we will move on to GDI+ and show how to use GDI+ to write graphics Web applications.
[ Team LiB ]
[ Team LiB ]
In this application we will draw a few simple graphics objects, including lines and rectangles. First we create a
Web Application using Visual Studio .NET. After creating a Web application, we need to add a GDI+-related
namespace to the project. We import namespaces as follows:
using [Link];
using [Link].Drawing2D;
using [Link];
Note
See Chapter 1 to learn more about GDI+ namespaces and classes. If you use Visual Studio .NET to create
your Web application, the wizard will add System and [Link] namespace references automatically.
473
474
Now we add code to draw graphics objects. Listing 12.2 draws two lines and a rectangle. You can write the
code on the page-load event handler or on a button click event handler.
We will discuss this code in more detail in the following section. If you are using a text editor to write your
applications, you can write the code given in Listing 12.3.
Now when we run our application, the output generated by Listing 12.2 or 12.3 should look like Figure 12.9.
474
475
Figure 12.9. Drawing simple graphics objects on the Web
Let's break down the code shown in Listings 12.2 and 12.3. We begin by importing GDI+-related namespaces
in the application: System, [Link], [Link].Drawing2D, and
[Link]. If we were using Visual Studio .NET, we would simply use the using
directive followed by the namespace name.
Next we have a Page_Load event, which is executed when a Web page is loaded. We create a pen and brush
using the Pen and HatchBrush classes.
One important limitation of Web applications is Web browser capability. A Web browser can display only
certain objects. For example, all graphics objects in a Web browser will be displayed as images. So before a
Web browser can display graphics objects, we need to convert them into images that can be displayed by the
browser. Our next step, then, is to create a Bitmap object. The following line creates a 200x200 Bitmap
object.
You already know that the Graphics object functions as a canvas and provides members to draw lines,
shapes, and images. Now we need to create a Graphics object from the bitmap:
Graphics g = [Link](curBitmap);
475
476
Once we have a Graphics object, we can draw shapes, lines, and images. In the following code we use the
DrawLine and FillRectangle methods to draw lines and a filled rectangle:
If you don't know how draw and fill methods work, you may want to look again at Chapter 3.
We're almost done. So far we have created Bitmap and Graphics objects, and we have drawn lines and a
rectangle. Because a Web browser can display only images (not pixels), we need to convert the bitmap into an
image. The Save method of the Bitmap object does the trick for us. The following line is responsible for
rendering a bitmap and sending it to the browser:
[Link]([Link],
[Link]);
[Link]();
[Link]();
The Bitmap class is inherited from the Image class, which defines the Save method. This method saves an
image to the specified Stream object in the specified format. For example, in our code the Save method takes
the following two arguments: [Link] and ImageFormat:
[Link]([Link],
[Link]);
The Response property of the Page class returns the HttpResponse object associated with the page, which
allows us to send HTTP response data to the client and contains information about the response. The
OutputStream property of HttpResponse enables binary output to the outgoing HTTP content body. In other
words, [Link] sends the images to the browser in a compatible format. The second
parameter is of ImageFormat enumeration type and specifies the format of the image. ImageFormat is
discussed in more detail in Chapter 7 (see Table 7.4).
The Save method also allows us to save an image on a local physical hard drive. The following code saves the
bitmap on the C:\\ drive.
[Link]("C:\\[Link]",
[Link]);
476
477
[ Team LiB ]
[ Team LiB ]
After creating the Graphics object, we set its smoothing mode to AntiAlias, create font and size objects, and
call the DrawString, DrawLine, and DrawEllipse methods to draw text, lines, and an ellipse, respectively. At
this point the bitmap we created contains these objects. The next step is to call the Save method and send the
image to the browser, which we do with the [Link] method. Finally, we call the Dispose method to
dispose of various objects.
477
478
[Link], new PointF(40, 20));
// Draw ellipse
[Link]( new Pen([Link], 3),
40, 20, [Link], [Link]);
// Send output to the browser and
// dispose of objects
[Link]([Link],
[Link]);
[Link]();
For all practical purposes, Listing 12.4 could be a Windows Forms application. The only new code required
creates a Bitmap object and calls its Save method to send output to the browser. We use the DrawString
method to draw text, the DrawLine method to draw lines, and the DrawRectangle method to draw
rectangles just as in any other GDI+ application.
Figure 12.10 shows the output from Listing 12.4. The program draws lines, ellipses, and text.
Note
For more on the Graphics class and its fill and draw methods, see Chapter 3.
[ Team LiB ]
[ Team LiB ]
To test this, let's create a Web application using Visual Studio .NET and add the code given in Listing 12.5 on
the page-load event. This code views an image on the browser. First we create a Bitmap object from an image,
then we create a Graphics object from the image, and then we call the Save method of Bitmap.
478
479
Listing 12.5 Drawing images on the Web
Notice that we didn't even need to call the DrawImage method. Figure 12.11 shows the output from Listing
12.5.
As we discussed in Chapter 3, the SmoothingMode and TextRenderingHint properties of the Graphics object
can be used to set the quality of images and text, respectively. GDI+ cannot draw text directly into a Web
application. Like lines, curves, and other graphics shapes, text must also be rendered as an image for display
in the browser. All graphics lines, curves, shapes, text, and images are first converted to an image and directed
to a browser, so only the SmoothingMode property will be applicable. SmoothingMode has five members:
AntiAlias, Default, HighQuality, HighSpeed, and None. The following code snippet sets the smoothing
mode of the Graphics object:
// Set modes
[Link] = [Link];
479
480
12.4.2 Using LinearGradientBrush and PathGradientBrush
You can use linear and path gradient brushes in Web applications just as we did in Chapter 4. Listing 12.6
uses LinearGradientBrush and PathGradientBrush to fill a rectangle and a path. First we create a linear
gradient brush and a graphics path, and we add two ellipses to the graphics path.
Next we create a path gradient brush, which takes the path as its only parameter, and we set the CenterColor
property of the path. Then we create Bitmap and Graphics objects and call [Link] and
[Link], which fill a path and rectangle, respectively.
As in the previous examples, finally we call the [Link] method and dispose of the objects.
480
481
The alpha component of a color represents its transparency. Alpha component values vary from 0 to 255,
where 0 indicates fully transparent and 255 indicates opaque. Listing 12.7 draws a rectangle, an ellipse, and
text on top of an image.
First we create Bitmap and Graphics objects and set the Graphics smoothing mode. Then we create a color
with transparency using the [Link] method, where transparency is the first parameter. Next, using
the following code, we create a pen from this semitransparent color, which gives us a semitransparent pen:
481
482
We also create a semitransparent brush by passing a semitransparent color as a parameter to SolidBrush, as
follows:
SolidBrush alphaBrush =
new SolidBrush([Link](90, 0, 255, 0));
Now to draw transparent shapes, we simply use the transparent brushes and pens. As Figure 12.13 shows, the
graphics shapes are semitransparent.
[ Team LiB ]
[ Team LiB ]
A line chart is a set of continuous lines. In the example presented in this section, we will read the size of the
chart and data points and draw a chart based on the points. Our discussion will focus first on the ChartComp
component, and then on the client application.
ChartComp is a class that defines the functionality to add points to the chart and draw the chart. The client
application (discussed in Section 12.5.2) is a Web application that calls the chart's members to add points to
the chart and draw it.
The code for the ChartComp class is given in Listing 12.8. The constructor of the class takes the type, color,
size, and a page to which this chart belongs. The overloaded InsertPoint method adds a point to the array of
points, and the DrawChart method draws the points stored in the array. DrawChart first draws a rectangle, and
then it draws points toward the x- and y-axes.
482
483
Listing 12.8 The ChartComp class
// Chart component
class ChartComp
{
public Bitmap curBitmap;
public ArrayList ptsArrayList =
new ArrayList();
public float X0 = 0, Y0 = 0;
public float chartX, chartY;
public Color chartColor = [Link];
// chartType: 1=Line, 2=Pie, 3=Bar.
// For future use only.
public int chartType = 1;
private int Width, Height;
private Graphics g;
private Page curPage;
struct ptStructure
{
public float x;
public float y;
public Color clr;
}
// ChartComp constructor
public ChartComp(int cType, Color cColor,
int cWidth, int cHeight, Page cPage)
{
Width = cWidth;
Height = cHeight;
chartX = cWidth;
chartY = cHeight;
curPage = cPage;
chartType = cType;
chartColor = cColor;
curBitmap = new Bitmap(Width, Height);
g = [Link](curBitmap);
}
// Destructor. Disposes of objects.
~ChartComp()
{
[Link]();
[Link]();
}
// InsertPoint method. Adds a point
// to the array.
public void InsertPoint(int xPos,
int yPos, Color clr)
{
ptStructure pt;
pt.x = xPos;
pt.y = yPos;
[Link] = clr;
// Add the point to the array
[Link](pt);
}
public void InsertPoint(int position,
int xPos, int yPos, Color clr)
{
ptStructure pt;
pt.x = xPos;
pt.y = yPos;
[Link] = clr;
// Add the point to the array
[Link](position, pt);
}
// Draw methods
public void DrawChart()
483
484
{
int i;
float x, y, x0, y0;
[Link]="image/jpeg";
[Link] = [Link];
[Link](new SolidBrush(chartColor),
0, 0, Width, Height);
int chWidth = Width-80;
int chHeight = Height-80;
[Link]([Link],
40, 40, chWidth, chHeight);
[Link]("GDI+ Chart", new Font("arial",14),
[Link], Width/3, 10);
// Draw x- and y-axis line, points, positions
for(i=0; i<=5; i++)
{
x = 40+(i*chWidth)/5;
y = chHeight+40;
string str = (X0 + (chartX*i/5)).ToString();
[Link](str, new Font("Verdana",10),
[Link], x-4, y+10);
[Link]([Link], x, y+2, x, y-2);
}
for(i=0; i<=5; i++)
{
x = 40;
y = chHeight+40-(i*chHeight/5);
string str = (Y0 + (chartY*i/5)).ToString();
[Link](str, new Font("Verdana",10),
[Link], 5, y-6);
[Link]([Link], x+2, y, x-2, y);
}
// Transform coordinates so that point (0,0)
// is in the lower left corner
[Link](180);
[Link](-40, 40);
[Link](-1, 1);
[Link](0, -(Height));
// Draw all points from the array
ptStructure prevPoint = new ptStructure();
foreach(ptStructure pt in ptsArrayList)
{
x0 = chWidth*(prevPoint.x-X0)/chartX;
y0 = chHeight*(prevPoint.y-Y0)/chartY;
x = chWidth*(pt.x-X0)/chartX;
y = chHeight*(pt.y-Y0)/chartY;
[Link]([Link], x0, y0, x, y);
[Link](new SolidBrush([Link]),
x0-5, y0-5, 10, 10);
[Link](new SolidBrush([Link]),
x-5, y-5, 10, 10);
prevPoint = pt;
}
[Link]([Link],
[Link]);
}
}
The client application is a Web page that is used to get input from the user. The main form of the application
is shown in Figure 12.14. The user can enter his/her chart size, and values for five points, including the color
of each one.
484
485
Figure 12.14. Entering points on a chart
The Draw Chart button draws a line chart. Code for the Draw Chart button click is given in Listing 12.9,
where we create an object of type ChartComp and call its InsertPoint and DrawChart methods. InsertPoint
adds a point to the chart. DrawChart draws a line chart from the first point to the last point entered by the user.
485
486
[Link]();
}
Now if you use the data entered in Figure 12.14 and click the Draw Chart button, the output will look like
Figure 12.15.
[ Team LiB ]
[ Team LiB ]
We create a Web Forms application using Visual Studio .NET. We add some text and two buttons to the page.
The final Web page looks like Figure 12.16. The Draw Chart button will draw a pie chart, and the Fill Chart
button will fill the chart with different colors.
486
487
Now we add some variables (see Listing 12.10). Instead of reading values from the user, we use hard-coded
values for the valArray and clrArray arrays. The valArray array stores the different portion values of a pie
chart, and clrArray stores colors for these portions. If you wish, you can modify the page and add some text
boxes to allow users to provide these values at runtime.
// User-defined variables
public Bitmap curBitmap;
private Rectangle rect =
new Rectangle(250, 150, 200, 200);
public ArrayList sliceList = new ArrayList();
private Color curClr = [Link];
int[] valArray = {50, 25, 75, 100, 50};
Color[] clrArray = {[Link], [Link],
[Link], [Link], [Link]};
int total = 0;
Now we add a method called DrawPieChart. It will both draw and fill the chart. The code for the
DrawPieChart method is given in Listing 12.11. We simply read values from the portion and color arrays, and
we create SolidBrush and Pen objects, depending on which button is clicked. We create a Bitmap object and
set the smoothing mode of the page to AntiAlias. We also initialize the values of the angle and sweep
variables.
We also have a Boolean variable called flMode. If flMode is true, the DrawPieChart method calls FillPie
to fill the pie chart; otherwise it calls DrawPie, which draws only the boundaries of the chart. In the end, we
save the bitmap, send it to the browser, and dispose of the objects.
487
488
{
total += valArray[i];
}
// Read color and value from array
// and calculate sweep
for (int i=0; i<[Link]; i++)
{
int val = valArray[i];
Color clr = clrArray[i];
sweep = 360f * val / total;
// If fill mode, fill pie
if(flMode)
{
SolidBrush brush = new SolidBrush(clr);
[Link](brush, 20.0F, 20.0F, 200,
200, angle, sweep);
}
else // If draw mode, draw pie
{
Pen pn = new Pen(clr, 2);
[Link](pn, 20.0F, 20.0F, 200,
200, angle, sweep);
}
angle += sweep;
}
// Send output to the browser
[Link]([Link],
[Link]);
// Dispose of objects
[Link]();
[Link]();
}
The Draw Chart button click generates the output shown in Figure 12.17 and the Fill Chart button click fills in
the chart, with output as shown in Figure 12.18.
488
489
Figure 12.18. The Fill Chart button click in action
We call the DrawPieChart method from our Draw Chart and Fill Chart buttons with a single argument false
or true, respectively as shown in Listing 12.12.
Listing 12.12 The Draw Chart and Fill Chart button click handlers
SUMMARY
In this chapter we discussed how to use GDI+ drawing functionality in Web applications. We started by
discussing the basic process of drawing graphics shapes and images on the Web using [Link] and GDI+.
After that we discussed the drawing process for Web applications, and how it differs from the Windows
drawing process.
Next we introduced [Link] and how to write a simple [Link] application using a text editor or Visual
Studio .NET. Then we discussed how to draw simple graphics objects such as lines, curves, rectangles, and
images on the Web. After drawing simple graphics objects, you learned how to set the quality and
transparency of images.
489
490
At the end of the chapter we saw line chart and pie chart applications, as real-world examples of GDI+ on the
Web.
Performance is a major factor that developers worry about when dealing with graphics. Chapter 13 is
dedicated to GDI+ performance. In it, we will discuss how to optimize GDI+ applications for the best
performance.
[ Team LiB ]
[ Team LiB ]
Let's start with an introduction of the basic architecture of drawing (rendering or painting) within Windows
Forms using GDI+. By the end of this chapter, you will be armed with GDI+ tips and tricks that make a
significant difference in the efficiency of many performance-oriented graphics applications. Note, however,
that these tips and tricks may not be applicable for Web applications.
Paint event functionality is defined in the [Link] class, which is the base class for
Windows Forms controls such as Label, ListBox, DataGrid, and TreeView. A paint event is fired when a
control is redrawn. The Form class itself is inherited from the Control class. Figure 13.1 shows the Form class
hierarchy.
490
491
Figure 13.1. The Form class hierarchy
The PaintEventArgs class provides data for the paint event. It provides two read-only properties:
ClipRectangle and [Link] indicates the rectangle in which to paint, and the Graphics
property indicates the Graphics object associated with the paint event of a particular control (including the
form itself). Always be careful when you're dealing with the paint event because it is unpredictable and called
automatically.
The Control class also provides OnPaint methods, which can be overridden in the derived classes to fire the
paint event. The signature of the OnPaint method is defined as follows:
As this definition shows, OnPaint takes a PaintEventArgs object as its only argument. The Graphics
property of PaintEventArgs is used to get the Graphics object associated with a control including the form.
Adding a paint event handler for any Control-derived class is pretty simple. We write an event handler that
has two parameters, of types object and PaintEventArgs:
We can give the event handler whatever name we want. After implementing this event handler, we use the
parameter args (which is a PaintEventArgs object) to get the Graphics object for the control. The following
code delegates the event handler for the Paint event:
[Link] +=
new [Link]
([Link]);
491
492
The following code gives the paint event handler for a form:
Now we can use the PaintEventArgs object to get the Graphics object associated with the form and use the
Graphics object's methods and properties to draw and fill lines, curves, shapes, text, and images. Let's draw a
rectangle, an ellipse, and some text on the form, as shown in Listing 13.1.
Figure 13.2 shows the output from Listing 13.1. Now if the form is covered by another window and the focus
returns to the form, the code on the paint event handler will repaint the form.
492
493
As mentioned earlier, the paint event handler can be added to any Windows control that is inherited from the
Control class, such as Button, ListBox, or DataGrid. In other words, each Windows control can have a paint
event handler and a Graphics object, which represents the control as a drawing canvas. That means we can
use a button or a list box as a drawing canvas.
Let's add DataGrid and Button controls to a form. We will use the button and the data grid as our drawing
canvases. Listing 13.2 adds the paint event methods of our Button1 and DataGrid1 controls.
Listing 13.3 gives the code for the Button and DataGrid paint event handlers. This code is useful when we
need to draw graphics shapes on a control itself. For example, a column of a data grid can be used to display
images or graphics shapes. In our example we draw an ellipse on these controls, instead of drawing on a form.
The [Link] object represents the Graphics object associated with a particular control.
Once you have the Graphics object of a control, you are free to call its draw and fill methods.
Figure 13.3 shows the output of Listing 13.3. As you can see, a button or a data grid can function as a drawing
canvas. The top left-hand corner of a control is the (0, 0) coordinate of the canvas associated with that control.
493
494
At this stage it is worth pointing out another big advantage that GDI+ has over GDI: the flexibility to have a
Graphics object associated with a control.
We have already seen this in previous chapters. We can override the OnPaint method by defining it as
follows:
Then we can use the Graphics property of PaintEventArgs to draw lines, shapes, text, and images. Listing
13.4 draws a few graphics shapes and text on our form's OnPaint method. To test this code, create a Windows
application and add the code to it.
494
495
13.1.5 Using Visual Studio .NET to Add the Paint Event Handler
If you are using Visual Studio .NET, the easiest way to add a paint event handler is to use the Properties
windows of a form or control and add a paint event handler. We have seen examples of this in previous
chapters.
It is usually good programming practice to dispose of objects when you're finished using them. But it may not
always be the best practice. A Graphics object must always be disposed of if it was created via the
CreateGraphics method or other "CreateFrom" methods. If we use a Graphics object on a paint event or the
OnPaint method from the [Link] property, we do not have to dispose of it.
Note
Do not dispose of Graphics objects associated with Windows controls such as Button, ListBox, or
DataGrid.
If you create objects such as pens and brushes, always dispose of them. Although it is acceptable practice to
rely on the garbage collector, doing so may often be at the expense of application performance. Garbage
collection can be a costly affair because the garbage collector checks the memory for objects that haven't been
disposed of, and this process absorbs processor time. However, the Dispose method of an object tells the
garbage collector that the object is finished and ready to be disposed of. Calling the Dispose method
eliminates the need to have the garbage collector check memory, and thus saves processor time.
In Web pages, it is always good practice to dispose of objects as soon as they are done being used.
The OnPaintBackground method paints the background of a control. This method is usually overridden in the
derived classes to handle the event without attaching a delegate. Calling the OnPaintBackground method calls
OnPaintBackground of the base class automatically, so we do not need to call it explicitly.
One of the best programming practices is the efficient use of variables and their scope. Before adding a new
variable to a program, think for a second and ask yourself, "Do I really need this variable?" If you need a
variable, do you really need it right now? The scope of variables and use of complex calculations can easily
degrade the performance of your applications. Using global scope for pens, brushes, paths, and other objects
may be useful instead of defining variables in the OnPaint or OnPaintBackground methods.
Let's look at a practical example: Listing 13.5 is written on a form's paint event handler, which creates pens
and brushes, and draws rectangles and polygons.
495
496
HatchBrush hatchBrush =
new HatchBrush([Link],
[Link], [Link]);
Pen redPen = new Pen([Link], 2);
Pen hatchPen = new Pen(hatchBrush, 4);
SolidBrush brush = new SolidBrush([Link]);
// Create points for curve
PointF p1 = new PointF(40.0F, 50.0F);
PointF p2 = new PointF(60.0F, 70.0F);
PointF p3 = new PointF(80.0F, 34.0F);
PointF p4 = new PointF(120.0F, 180.0F);
PointF p5 = new PointF(200.0F, 150.0F);
PointF[] ptsArray ={ p1, p2, p3, p4, p5 };
float x = 5.0F, y = 5.0F;
float width =
[Link] - 100;
float height =
[Link] - 100;
// Draw objects
[Link](redPen, ptsArray);
[Link](hatchPen,
x, y, width, height);
[Link](lgBrush,
200, 200, 200, 200);
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
}
In this example we define many variables, all of local scope. Throughout the application, the redPen,
hatchBrush, hatchPen, brush, and other variables remain the same. Programmatically, it doesn't matter
whether we define these variables locally or globally; the choice depends entirely on the application. It may be
better to have variables defined with a global scope. If you repaint the form frequently, defining these
variables globally may improve performance because time will not be wasted on re-creating the objects for
each pass. On the other hand, defining objects globally may consume more resources (memory).
It is also good to avoid lengthy calculations in frequently called routines. Here's an example: Listing 13.6
draws a line in a loop. As you can see, int x and int y are defined inside the loop.
We can easily replace the code in Listing 13.6 with Listing 13.7, which is more efficient. If a code statement
496
497
does the same thing every time a control reaches it inside a loop, it is a good idea to move that statement
outside the loop to save processing cycles.
Sometimes using a floating point data type instead of an integer may affect the quality of a drawing, even
though floating point data is costly in terms of resources.
A well-designed and well-coded application also plays a vital role in performance. For example, replacing
multiple if statements with a single case statement may improve performance.
[ Team LiB ]
[ Team LiB ]
Double buffering is a similar concept. You may have seen one of the frequently asked questions on GDI+
discussion forums: "How do we create flicker-free drawings"? The double buffering technique is used to
provide faster, smoother drawings by reducing flicker. In this technique, all objects are drawn on an off-screen
canvas with the help of a temporary image and a Graphics object. The image is then copied to the control. If
the drawing operation is small and includes drawing only simple objects such as rectangles or lines, there is
no need for double buffering (it may even degrade performance). If there are many calculations or drawn
elements, performance and appearance may be greatly improved through the use of double buffering.
To prove the point, let's write an example. Listing 13.8 gives the code for a drawing method that draws
several lines.
497
498
[Link]([Link], 0,
partY * i,
(width) - partX * i,
0);
}
}
To test our application, we will call it from a button click. The code for a button click event handler is given in
Listing 13.9.
Now let's draw the same lines using a Bitmap object. We create a temporary Graphics object from a
temporary image and call its draw and fill methods. Instead of calling DrawLine with respect to a form, we
call DrawImage, which draws the image generated by the DrawLine method.
As Listing 13.10 shows, we create a Bitmap object in a buffer and send the entire buffer all at once using
DrawImage. We add the code given in Listing 13.10 on the Bitmap Draw button click event handler.
Graphics g = [Link]();
[Link]([Link]);
// Create a Bitmap object with the size of the form
Bitmap curBitmap = new Bitmap([Link],
[Link]);
// Create a temporary Graphics object from the bitmap
498
499
Graphics g1 = [Link](curBitmap);
// Draw lines on the temporary Graphics object
DrawLines(g1);
// Call DrawImage of Graphics and draw bitmap
[Link](curBitmap, 0, 0);
// Dispose of objects
[Link]();
[Link]();
[Link]();
Comparing the two methods given in Listings 13.9 and 13.10 reveals a significant difference in drawing
performance. In Listing 13.9, drawing begins as soon as we hit the Simple Draw button and continues until it
is done. By contrast, when we hit the Bitmap Draw button, drawing doesn't start immediately. This method
actually draws on an in-memory Bitmap object, and when all drawing is done, it displays the bitmap.
[ Team LiB ]
[ Team LiB ]
499
500
AllPaintingInWmPaint bits to true.
EnableNotifyMessage If true, the OnNotifyMessage method is called for every
message sent to the control's WndProc method. This style
defaults to false.
FixedHeight The control has a fixed height.
FixedWidth The control has a fixed width.
Opaque The control is drawn opaque, and the background is not
painted.
ResizeRedraw The control is redrawn when it is resized.
Selectable The control can receive focus.
StandardClick The control implements standard click behavior.
StandardDoubleClick The control implements standard double-click behavior.
When using this option, you must also set StandardClick
to true.
SupportsTransparentBackColor The control accepts a Color object with alpha transparency
for the background color. The UserPaint bit must be set to
true, and the control must be derived from the Control
class, like this:
[Link]([Link], true);
UserMouse The control does its own mouse processing, and mouse
events are not handled by the operating system.
UserPaint The control paints itself rather than having the operating
system do it. This option applies to classes derived from
Control.
Let's apply the SetStyle method to achieve double buffering. Double buffering can be enabled
programmatically with the following code:
We can also control the redrawing of controls when a control is resized. Setting
[Link] to true, as in the code snippet that follows, forces controls to be redrawn every
time a control (or a form) is resized.
SetStyle([Link], true);
Sometimes we will not want a control to be redrawn when it is resized. In this case we can set ResizeRedraw
to false.
Note
Many controls, such as PictureBox, are double-buffered automatically, which means we don't need to write
any additional code when viewing images in a PictureBox control.
[ Team LiB ]
500
501
[ Team LiB ]
Sometimes anti-aliasing won't affect the quality of a drawing, and it is bad programming practice to use this
processor-intensive feature when it is not required. In other cases we might need to set anti-aliasing for just
one object out of 50. In these cases it is better to set the anti-alias option for that object only, instead of the
entire canvas.
Sections 13.4.1 through 13.4.6 describe some more tips and tricks that may help improve an application's
performance.
Avoiding unwanted repainting is a good technique to increase painting performance. GDI+ provides many
techniques for painting only required objects. Using regions and clipping rectangles may help in some cases.
If you need to draw a single object with anti-aliasing on, just set anti-aliasing for that object instead of for the
entire surface (form). Using regions is one of the best techniques for repainting only a required area. For better
performance, you should know what area you need to redraw and invalidate only that area, thereby using
regions instead of repainting the entire form. See Chapter 6 for details of how to invalidate and clip specific
regions.
Graphics paths may be useful when we need to redraw certain graphics items. For example, suppose we have
hundreds of graphics items, including lines, rectangles, images, and text associated with a surface but we need
to redraw only the rectangles. We can create a graphics path with all rectangles and just redraw that path,
instead of the entire surface.
We may also want to use graphics paths when drawing different shapes, depending on the complexity of the
application. For example, Listing 13.11 uses draw methods to draw two lines, two rectangles, and an ellipse.
We can write this code on a button or a menu click event handler.
Graphics g = [Link]();
[Link]([Link]);
// Create a black pen
Pen blackPen = new Pen([Link], 2);
// Draw objects
[Link](blackPen, 50, 50, 200, 50);
[Link](blackPen, 50, 50, 50, 200);
[Link](blackPen, 60, 60, 150, 150);
[Link](blackPen, 70, 70, 100, 100);
[Link](blackPen, 90, 90, 50, 50);
// Dispose of objects
[Link]();
[Link]();
501
502
Listing 13.12 draws the same graphics objects. The only difference is that this code uses a graphics path.
Graphics g = [Link]();
[Link]([Link]);
// Create a black pen
Pen blackPen = new Pen([Link], 2);
// Create a graphics path
GraphicsPath path = new GraphicsPath();
[Link](50, 50, 200, 50);
[Link](50, 50, 50, 200);
[Link](new Rectangle(60, 60, 150, 150));
[Link](new Rectangle(70, 70, 100, 100));
[Link](90, 90, 50, 50);
[Link](blackPen, path);
// Dispose of objects
[Link]();
[Link]();
Both Listings 13.11 and 13.12 generate the output shown in Figure 13.5. There is no straightforward rule for
when to use graphics paths. The choice depends on the complexity of your application.
Figure 13.5. The same result from two different drawing methods
In the preceding example we saw how to replace multiple drawing statements with a single graphics path
drawing statement. But graphics paths have some limitations. For example, we can't draw each element (line,
rectangle, or an ellipse) of a graphics path with a separate pen or brush. We have to draw or fill them
individually.
Drawing lines and drawing rectangles are probably the most common operations. If you are drawing more
than one line or rectangle using the same colors, you should use the DrawLine/DrawLines and
DrawRectangle/DrawRectangles methods, respectively. For example, Listing 13.13 draws three rectangles
using the same brush.
502
503
Listing 13.13 Using DrawRectangle to draw rectangles
Figure 13.6 shows the output from Listing 13.13. Three rectangles have been drawn.
You can replace the code in Listing 13.13 with Listing 13.14, which uses DrawRectangles to draw the same
number of rectangles. Now we use an array of rectangles.
503
504
// Dispose of object
[Link]();
}
If we run this code, the output looks exactly like Figure 13.6.
It is always good practice to write minimal code on events that are called frequently because that code will be
executed whenever the event is called. The Paint event is specifically designed for painting purposes and is
called when redrawing is necessary. It is always advisable to write your painting (or redrawing)-related code
for this event only. Writing code for other events, such as mouse-move or keyboard events, may cause serious
problems or may not invalidate areas as necessary.
You can always create system pens and system brushes with system colors by using the SystemColors class,
but for performance reasons it is advisable to use SystemPens and SystemBrushes instead of SystemColors.
For example, the following code creates SolidBrush and Pen objects using SystemColors. The brush and pen
have the ActiveCaption and ControlDarkDark system colors, respectively.
SolidBrush brush =
(SolidBrush)[Link]
([Link]);
Pen pn = [Link]
([Link]);
We can create the same brush and pen by using the static methods of SystemBrushes and SystemPens, as the
following code snippet illustrates:
SolidBrush brush =
(SolidBrush)[Link];
Pen pn = [Link];
Never dispose of system pens and brushes. Any attempt to do so will result in an unhandled exception. For
example, adding the following two lines to the code will throw an exception:
[Link]();
[Link]();
Listing 13.15 shows the complete code of a form's paint event handler.
504
505
Graphics g = [Link];
// AVOID
/*SolidBrush brush =
(SolidBrush)[Link]
([Link]);
Pen pn = [Link]
([Link]);
*/
SolidBrush brush =
(SolidBrush)[Link];
Pen pn = [Link];
[Link](pn, 20, 20, 20, 100);
[Link](pn, 20, 20, 100, 20);
[Link](brush, 30, 30, 50, 50);
// DON'T
// [Link]();
// [Link]();
}
Figure 13.7 shows the output from Listing 13.15. The lines and rectangle are drawn with system colors.
Automatic scaling could result in performance degradation. If possible, avoid automatic scaling. The
DrawImage method takes a Bitmap object and a rectangle with upper left corner position and specified width
and height. If we pass only the upper left corner position, GDI+ may scale the image, which decreases
performance. For example, the code
[Link](image,
10, 10, [Link],
[Link]);
505
506
[ Team LiB ]
[ Team LiB ]
SUMMARY
Quality and performance are two basic requirements of all graphics applications. Although an increase in one
demands a sacrifice in the other, a good developer will employ good design and coding techniques to provide
an optimal solution. In this chapter we discussed some techniques that may be helpful in writing optimal
solutions for graphics applications.
We learned about the paint event mechanism and different ways to fire the paint event automatically, as well
as manually. We also discussed double buffering, and how it can be achieved with or without the SetStyle
method. In addition, we learned a few good programming techniques and covered some topics that may help
you implement some good, performance-oriented coding and design practices.
As a GDI developer, you may want to use some of the "cool" techniques of GDI that are not supported by
GDI+. Chapter 14 is dedicated to GDI interoperability. In it, we will discuss how you can mix GDI and GDI+
to take advantage of interoperability.
[ Team LiB ]
[ Team LiB ]
This chapter is written particularly for developers who want to use GDI in their managed applications. If you
have no interest in GDI, feel free to skip this chapter. It will always be here, should the GDI need arise!
[ Team LiB ]
[ Team LiB ]
506
507
We can use COM libraries in managed applications by simply adding a reference
to the COM library using the Add Reference option of [Link] or the Type
Library Importer ([Link]) .NET tool. Both of these options allow developers
to convert a COM library to a .NET assembly, which can then be treated as other
.NET assemblies. The graphical user interface (GUI) functionality of Windows is
defined in a Win32 library called [Link]. Using Win32 libraries in managed
code is a little more difficult than using COM libraries. However, there is nothing
to worry about because the
[Link] class allows
developers to use functionality defined in unmanaged libraries such as [Link].
[[Link]
("[Link]")]
After adding this code, we're ready to use the functions defined in the [Link]
library in our .NET application.
Now let's take a look at a simple program that uses the MoveFile function of
Win32 defined in the [Link] library. The code in Listing 14.1 first imports
the library and then calls the MoveFile function to move a file from one location to
another.
[[Link]
("[Link]")]
public static extern bool MoveFile
(String src, String dst);
As with [Link], we can import other Win32 libraries to use them in .NET
applications. The DllImportAttribute class provides six field members, which
are described in Table 14.1.
507
508
The DllImportAttribute class has two properties: TypeId and Value. TypeId
gets a unique identifier for an attribute when the attribute is implemented in the
derived class, and Value returns the name of the DLL with the entry point.
Member
Description
Cdecl
The caller cleans the stack. This property enables calling functions with varargs.
FastCall
508
509
StdCall
The callee cleans the stack. This is the default convention for calling unmanaged functions from managed
code.
ThisCall
The first parameter is the this pointer and is stored in the ECX register. Other parameters are pushed onto the
stack. This calling convention is used to call methods in classes exported from an unmanaged DLL.
Winapi
Uses the default platform-calling convention. For example, on Windows it's StdCall, and on Windows CE it's
Cdecl.
One of the most frequently asked questions on discussion forums and newsgroups related to GDI in managed
code has to do with the use of BitBlt. Is this because developers want to implement sprites and scrolling-type
actions in their applications? If you want to use the BitBlt function, you are probably aware of what it does.
For the uninitiated, however, we should explain that this function performs a bit-block transfer of the color
data corresponding to a rectangle of pixels from one device context to another. It is defined as follows:
BOOL BitBlt(
HDC hdcDest, // handle to destination device context
int nXDest, // x-coordinate of destination upper left corner
int nYDest, // y-coordinate of destination upper left corner
int nWidth, // width of destination rectangle
int nHeight, // height of destination rectangle
HDC hdcSrc, // handle to source device context
int nXSrc, // x-coordinate of source upper left corner
int nYSrc, // y-coordinate of source upper left corner
DWORD dwRop // raster operation code
);
More details of BitBlt are available in the GDI SDK documentation. Just type "BitBlt" in MSDN's index to
find it.
First we need to import the BitBlt method and the [Link] library using the DllImportAttribute class.
[[Link]
("[Link]")]
public static extern bool BitBlt(
IntPtr hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
IntPtr hdcSrc,
int nXSrc,
int nYSrc,
System.Int32 dwRop
);
509
510
Now we just call BitBlt. The code in Listing 14.2 uses the BitBlt function. As the function definition
shows, we need source and destination device contexts. There is no concept of device context in managed
code, but to maintain GDI interoperability, the Graphics class's GetHdc method is used to create a device
context for a Graphics object (a surface). GetHdc returns an IntPtr object.
In Listing 14.2, first we create a Graphics object by using CreateGraphics and we draw a few graphics
items. From this Graphics object we create a Bitmap object, and we create one more Graphics object as the
destination surface by using the FromImage method of the Graphics object. Next we call BitBlt with
destination and source device contexts as parameters. Finally, we make sure to call ReleaseHdc, which
releases device context resources. The Save method saves a physical copy of the image. We also call the
Dispose method of Graphics objects.
510
511
14.1.3 Using GDI Print Functionality
We discussed .NET printing functionality in Chapter 11, but what about using GDI printing in managed code?
One reason for using GDI may be speed and familiarity with GDI or having more control over the printer.
Until now we have been selecting objects such as fonts and lines and then drawing on a page, which is then
printed out. Keep in mind that all the fonts you can use within the .NET environment have to be TrueType
fonts. Before TrueType came along, there was something called PCL (Printer Control Language), also known
as bitmap fonts. So what's the difference?, you may ask. It's simple: A PCL or bitmap font is made up of
patterns of dots that represent each letter.
The problem is that a different PCL font was required for every size of letter needed, such as 12, 14, and so
on. Different PCL fonts were needed even for italic and bold versions! As you can imagine, it was necessary
to have lots of PCL fonts to maintain the flexibility we take for granted today.
TrueType fonts, on the other hand, are a lot more flexible. The reason is that the fonts are mathematical
representations of each letter rather than a pattern of dots. If I decide I need a Times New Roman font at size
20, the font is simply recalculated rather than just a different pattern of dots being loaded.
What happens if your printer does not support the TrueType font you have selected? The only way to print it
is to send what you want to print to the printer as graphics, which can be time-consuming if you're creating
large printouts.
The code in Listing 14.3 does a few new things. For one, it uses Win32 APIs to talk directly to the printer,
which gives us the best possible speed. Finally, it demonstrates the use of PCL5 commands to draw a box on
the page.
Using the code in Listing 14.3, you would be able to create detailed pages consisting of multiple fonts and
graphics. The nice thing is that they can all be created by just sending text to the printer rather than using
graphics commands.
You may want to change the printer before you test this code. The following line of code specifies the printer:
[Link]("\\\\[Link]\\hpl",
ref lhPrinter,0);
// [Link]
// Shows how to write data directly to the
// printer using Win32 APIs.
// This code sends Hewlett-Packard PCL5 codes
// to the printer to print
// out a rectangle in the middle of the page.
using System;
using [Link];
using [Link];
[StructLayout( [Link])]
public struct DOCINFO
{
[MarshalAs([Link])]
public string pDocName;
[MarshalAs([Link])]
511
512
public string pOutputFile;
[MarshalAs([Link])]
public string pDataType;
}
[ DllImport( "[Link]",
CharSet=[Link],ExactSpelling=false,
CallingConvention=[Link] )]
public static extern long StartDocPrinter(IntPtr hPrinter,
int Level, ref DOCINFO pDocInfo);
[ DllImport( "[Link]",
CharSet=[Link],ExactSpelling=true,
CallingConvention=[Link])]
public static extern long StartPagePrinter(
IntPtr hPrinter);
[ DllImport( "[Link]",
CharSet=[Link], ExactSpelling=true,
CallingConvention=[Link])]
public static extern long WritePrinter(IntPtr hPrinter,
string data, int buf, ref int pcWritten);
[ DllImport( "[Link]" ,
CharSet=[Link],ExactSpelling=true,
CallingConvention=[Link])]
public static extern long EndPagePrinter(IntPtr
hPrinter);
[ DllImport( "[Link]" ,
CharSet=[Link], ExactSpelling=true,
CallingConvention=[Link])]
public static extern long EndDocPrinter(IntPtr hPrinter);
[ DllImport( "[Link]",
CharSet=[Link],ExactSpelling=true,
CallingConvention=[Link] )]
public static extern long ClosePrinter(IntPtr
hPrinter);
}
512
513
st1="\x1b*c600a6b0P\f";
// lhPrinter contains the handle for the printer opened.
// If lhPrinter is 0, then an error has occurred.
[Link]("\\\\[Link]\\hpl",
ref lhPrinter,0);
[Link](lhPrinter,1,ref di);
[Link](lhPrinter);
try
{
// Moves the cursor 900 dots (3 inches at
// 300 dpi) in from the left margin, and
// 600 dots (2 inches at 300 dpi) down
// from the top margin
st1="\x1b*p900x600Y";
[Link](lhPrinter,
st1, [Link], ref pcWritten);
513
514
}
catch (Exception e)
{
[Link]([Link]);
}
[Link](lhPrinter);
[Link](lhPrinter);
[Link](lhPrinter);
}
}
Using this code will enable us to drive a printer at its maximum output rate.
[ Team LiB ]
[ Team LiB ]
GDI+ currently has no support for raster operations. When we use R2_XOR pen operations, we use the
[Link]() method to get the handle to the device context. During the operation when your
application uses the HDC, the GDI+ should not draw anything on the Graphics object until the
[Link] method is called. Every GetHdc call must be followed by a call to ReleaseHdc on a
Graphics object, as in the following code snippet:
g2 = [Link](curBitmap);
IntPtr hdc1 = [Link]();
IntPtr hdc2 = [Link]();
BitBlt(hdc2, 0, 0,
[Link],
[Link],
hdc1, 0, 0, 13369376);
[Link]([Link], 40, 40, 200, 200);
[Link](hdc1);
[Link](hdc2);
If we make a GDI+ call after GetHdc, the system will throw an "object busy" exception. For example, in the
preceding code snippet we make a DrawRectangle call after GetHdc and before ReleaseHdc. As a result we
will get an exception saying, "The object is currently in use elsewhere."
After a call to GetHdc, we can simply call a Graphics object from a bitmap that returns a new HBITMAP
structure. This bitmap does not contain the original image, but rather a sentinel pattern, which allows GDI+ to
514
515
track changes to the bitmap. When ReleaseHdc is called, changes are copied back to the original image. This
type of device context is not suitable for raster operations because the handle to device context is considered
write-only, and raster operations require it to be read-only. This approach may also degrade the performance
because creating a new bitmap and saving changes to the original bitmap operations may tie up all your
resources.
[ Team LiB ]
[ Team LiB ]
SUMMARY
With the help of .NET runtime interoperability services, we can use the functionality of the Win32 libraries in
managed code. The DllImportAttribute class is used to import a Win32 DLL into managed code. In this
chapter we saw how to use this class to import [Link] functions in managed code. We also saw how to
use printing and BitBlt functions in managed code.
GDI+ can also be used to write simple and fun drawing applications. This is what we will discuss in Chapter
15. There you will see how GDI+ can be useful for writing fun applications.
[ Team LiB ]
[ Team LiB ]
[ Team LiB ]
[ Team LiB ]
The Control class provides the basic functionality and serves as the base class for Windows forms and
controls. Although this class has many properties and methods, we will concentrate on only a few of them.
515
516
The ForeColor and BackColor properties determine the foreground and background colors of controls,
respectively. Both properties are of type Color, and they implement get and set property options.
The Font property represents the font of the text displayed by a control. The DefaultBackColor,
DefaultFont, and DefaultForeColor static properties of the Control class implement the get option only,
and they return the default background color, font, and foreground color of a control, respectively.
The BackgroundImage property allows us to both get and set the background image of a control. This
property is of type Image. Images with translucent or transparent colors are not supported by Windows Forms
as background images.
Now let's write an application that will use all of the properties we just named.
First we create a Windows application and name it ButtonViewer. Then we add controls (for three buttons,
one text box, and one panel) to the form by dragging them from the Visual Studio .NET toolbox. After adding
controls to the form, we reposition and resize them, and we change their Text and Name properties. The final
form looks like Figure 15.1.
As Figure 15.1 shows, two of the buttons are named Browse and Close, respectively, and one button has no
text. The Browse button allows us to browse an image file, and the Close button closes the application. The
TextBox control displays the file name selected by a click of the Browse button. The third button (shown
larger and without text in Figure 15.1) displays the image selected by the Browse button.
Now let's change the background color, foreground color, styles, and fonts of these controls. To do so, we add
code in the form's load event handler, as shown in Listing 15.1. As the code indicates, we set the control's
BackColor, ForeColor, FlatStyle, BorderStyle, and Font properties. (See Chapter 5 for details on fonts
and colors.)
516
517
Listing 15.1 Setting a control's BackColor, ForeColor, and Font properties
The Close button click handler simply calls the [Link] method, as shown in Listing 15.2.
The Browse button click event handler (see Listing 15.3) uses an OpenFileDialog control to browse for an
image and sets the selected image as the background image of the button. It also sets the file name as text of
the text box control. Finally, it calls the Invalidate method to repaint the form.
517
518
}
Invalidate();
}
How can I draw transparent controls? This is one of the commonly asked questions on discussion forums.
Drawing transparent controls involves two steps. First we set a form's style to enable support for transparent
controls. We do this by calling the SetStyle method of the form, passing
[Link] as the first argument, and setting the second argument
(which in turn sets the SupportTransparentBackColor bit) to true. Next we set the control's BackColor
property to a transparent color. Either we can use [Link], or we can create a Color object using
an alpha component value less than 255 to provide custom semitransparency. Listing 15.4 sets the background
color of controls to transparent.
Note
Not all controls support transparent color. For example, if you set the BackColor property of a text box to
[Link], you will get an exception.
518
519
[ Team LiB ]
[ Team LiB ]
Let's draw a circular form. We can use the GraphicsPath class to draw graphics paths. In this application
we'll create a circular form and a circular picture box, which will display an image. To test this application,
we follow these simple steps:
We create a Windows application and add a button and a picture box to the form. Then we set the Text
property of the button control to "Exit" and write the following line on the button click event handler:
[Link]();
Next we add a reference to the [Link].Drawing2D namespace so that we can use the GraphicsPath
class:
using [Link].Drawing2D;
On the form-load event handler, we create a Bitmap object from a file and load the bitmap in the picture box
as shown in the following code snippet.
The last step is to set the form and picture box as circular. We can modify the InitializeComponent method
and add code as in Listing 15.5 at the end of the method, or we can add the code on the form-load event
handler. We just set the Region property of the form and picture box to the region of our GraphicsPath
object.
519
520
[Link](rect);
// Set the Region property of the picture box
// by creating a region from the path
[Link] = new Region(path);
[Link] += 200;
[Link] += 200;
[Link]();
[Link](rect);
[Link] = new Region(path);
// Create an image from a file and
// set the picture box's Image property
Image bmp = [Link]("[Link]");
[Link] = bmp;
}
When we build and run the application; the output will look like Figure 15.3. Because we have eliminated the
normal title bar controls, we must implement an Exit button.
[ Team LiB ]
[ Team LiB ]
In this section we will create an application with support for the display of copyright information on displayed
images. First we create a Windows application and add a File | Open menu item, a button with text "Add
Copyright," and a picture box. The final form looks like Figure 15.4.
520
521
After adding the controls, we add a reference to the [Link] namespace to the application.
Then we add a class Image variable to the application as follows:
Image origImage;
The File | Open menu allows us to browse images and view a thumbnail of a specific image. The code for the
menu click event handler is given in Listing 15.6. After reading the name of the image, we create an Image
object from the file name using the [Link] static method. After creating one Image object, we create
another Image object using the GetThumbnailImage method of the Image class. GetThumbnailImage returns a
thumbnail image. After that we simply set the Image property of PictureBox to display the image.
If we run the application and open a file using the Open menu item, the image will be displayed. The output
looks like Figure 15.5.
521
522
Once the image has been loaded, we click the Add Copyright button and let the program do its work.
Basically we need to create an image on the fly, add text to the image using the DrawString method, and then
save the image. To give the text a different shade, we need to change the color of the pixels that draw the text.
In other words, we must change the brightness of the pixels that represent the text to distinguish the text pixels
from the image pixels. We increase the values for the red, green, and blue component of the color by 25 to
brighten the text pixels. We use the MeasureString method of the Graphics class to set the size and font of
the text. (We discussed MeasureString in detail in Chapter 3.)
The maximum value for each of the red, green, and blue components of a color is 255. What happens if these
values are already set to 255? Do we still increase their value by 25? No. In that case we cheat and don't touch
these pixels. In most cases this approach works because there is always a pixel that is totally different in
brightness. One additional thing we could do would be to analyze the image, determine whether it's a dark or
bright image, and adjust it accordingly.
To find out which pixels to change, we create a second bitmap that is the same size as the original image. We
write "Add Copyright Info" on this image and use it as the pattern for the main image.
We also want to use the largest font we can to create a big word across the image. Of course, the image can be
any size, so we can predict the font size. To do this we create a graphics class based on our pattern image and
use the MeasureString method until we get a font that fits the graphic, as in Listing 15.7.
while(foundfont==false)
{
Font fc = new Font("Georgia",
fntSize, [Link]);
sizeofstring = new SizeF(imgWidth,imgHeight);
sizeofstring =
[Link]("Copyright GDI+ Inc.,",fc);
if ([Link]<[Link])
{
if ([Link]<[Link])
{
foundfont=true;
[Link]("Copyright GDI+ Inc.,",
fc, new SolidBrush([Link]),
1, 15);
}
}
else
fntSize = fntSize - 1;
}
The complete code for the Add Copyright button click event handler is given in Listing 15.8. We read the
image size and create a Bitmap object from the original size of the image. Then we create a Graphics object
on the fly using this Bitmap object. Once the pattern bitmap has been created, all we have to do is loop
522
523
through all the pixels and if a pixel is black (which means that it's part of the word), we go to the main image
and increase its brightness, producing a glasslike effect.
523
524
x1=re;
y1=gr;
z1=bl;
if (bl+25<255)
bl=bl+25;
if (gr+25<255)
gr=gr+25;
if (re+25<255)
re=re+25;
if (x1-25>0)
x1=x1-25;
if (y1-25>0)
y1=y1-25;
if (z1-25>0)
z1=z1-25;
[Link](new Pen(
new SolidBrush([Link])),
x, y+1, 3, 3);
[Link](new Pen(
new SolidBrush(
[Link](a,x1,y1,z1))),
x, y, 1, 1);
}
}
}
[Link]("Output file is [Link]",
"GraphicsCopyright");
[Link]();
[Link]("[Link]",
[Link]);
}
Now we can run the application and browse images. When we click the Add Copyright button, we will get a
message when the program is done adding text. The result creates what is commonly known as a watermark in
the image (see Figure 15.6).
[ Team LiB ]
[ Team LiB ]
524
525
First we need to create a database. We start by creating a new Access database called [Link]
and adding a table to the database called "Users." The database table schema should look like Figure 15.7.
Microsoft Access stores binary large objects (BLOBs) using the OLE object data type.
To make our application a little more interactive and user-friendly, let's create a Windows application and add
a text box, three button controls, and a PictureBox control. The final form looks like Figure 15.8. As you can
probably guess, the Browse Image button allows users to browse for bitmap files; the Save Image button saves
the image to the database; and the Read Image button reads the first row of the database table, saves binary
data as a bitmap, and displays the image in the picture box.
525
526
Before we write code on button clicks, we need to define the following variables:
// User-defined variables
private Image curImage = null;
private string curFileName = null;
private string connectionString =
"Provider=[Link].4.0; " +
"Data Source=F:\\[Link]" ;
private string savedImageName =
"F:\\[Link]";
using [Link];
using [Link];
The stream-related classes are defined in the [Link] namespace. We will use the OLE DB data provider,
which is defined in the [Link] namespace, to work with our Access database.
The Browse Image button click code is given in Listing 15.9, which simply browses bitmap files and saves
the file name in curFileName. We can set a filter to access the file formats we want.
526
527
[Link] = "Open a Bitmap File";
if([Link]() == [Link])
{
curFileName = [Link];
[Link] = curFileName;
}
}
The Save Image button code given in Listing 15.10 creates a FileStream object from the bitmap file, opens a
connection with the database, adds a new data row, set its values, and saves the row back to the database.
string userDes =
"Mahesh Chand is a founder of C# Corner ";
userDes +=
"Author: 1. A Programmer's Guide to [Link];";
userDes += ", 2. Applied [Link]. ";
527
528
}
[Link]("Image Saved");
}
Once the data has been saved, the next step is to read data from the database table, save it as a bitmap again,
and view the bitmap on the form. We can view an image using the [Link] method or using a
picture box. Our example uses a picture box.
The code for reading binary data is shown in Listing 15.11. We open a connection, create a data adapter, fill a
data set, and get the first row of the Users table. If you want to read all the images, you may want to modify
your application or loop through all the rows. Once a row has been read, we retrieve the data stored in the
UserPhoto column in a stream and save it as a bitmap file. Later we view that bitmap file in a picture box by
setting its Image property to the file name.
528
529
To see the program in action, we select the [Link] file by using the Browse Image button, and we click
the Save Image button. When we open the database, we see that a new record has been added to the Users
table. When we click on the Read Image button, a new [Link] file is added to the current folder.
The output is shown in Figure 15.9.
[ Team LiB ]
[ Team LiB ]
The DrawItem event is raised by owner-drawn controls. This event passes an argument of
type DrawItemEventArgs, which contains data related to the event. The user uses this
529
530
data to paint a specific item of the control. The properties of the DrawItemEventArgs
class are given in Table 15.1.
Besides the properties listed in Table 15.1, the DrawItemEventArgs class provides two
useful methods: DrawBackground and DrawFocusRectangle. The DrawBackground
method draws the background of the item when we select an item in a control. The
DrawFocusRectangle method draws a focus rectangle around the text of an item selected
in the control. These methods take no arguments.
The MeasureItem event is raised by owner-drawn controls when the size (width and
height) of the items in a control is being determined. This event passes an argument of
type MeasureItemEventArgs,which contains data related to the event. This data is used
by the user to paint a specific item of the control. The MeasureItemEventArgs class
properties are listed in Table 15.2.
Property
Description
Graphics
Index
Index of the item participating in the measure-item event. Both get and set.
530
531
ItemHeight
ItemWidth
The ListBox class represents a list box control in Windows Forms. This class provides two events DrawItem
and MeasureItem that participate in owner drawing processes.
Briefly, in owner-drawn controls the developer (not the framework) programmatically handles the process of
creating controls. One example of an owner-drawn control is a list box in which you can change the color,
font, and size of the individual items.
Note
Let's create a Windows application using Visual Studio .NET and add a ListBox control by dragging it from
the toolbox to the form. We start by drawing a list box with different colors, background color, and size. Then
we set the DrawMode to OwnerDrawVariable using the Properties window. Finally, we add the code from
Listing 15.12 on InitializeComponent after the ListBox code. This code sets the DrawMode property of
ListBox and adds DrawItem and MeasureItem event handlers.
[Link] =
[Link];
[Link] +=
new [Link](
[Link]);
[Link] +=
new [Link](
[Link]);
Next we define four arrays to store the text, size, foreground color, and background color, respectively, of a
ListBox item. We define the following variables in the form class:
The next step is to initialize these arrays as in Listing 15.13. Our code also binds the text array to the ListBox
control. You can add this code on the form's constructor after InitializeComponent or on the form's load
event handler.
531
532
Listing 15.13 Initializing arrays
The final step is to write DrawItem and MeasureItem event handlers. The code for these handlers is given in
Listing 15.14. We draw a focus rectangle and background of items, and then we draw text using DrawString
by passing the color, text, and size after reading from arrays. The MeasureItem event handler sets the height
of the ListBox control items.
Note
532
533
If we run the application, the output will look like Figure 15.10.
Sometimes we want to display images in a ListBox control. By applying the method described in the
preceding section, we can easily create an owner-drawn ListBox control with images in it. In the previous
example we created an array of strings and used DrawString to draw them. This time we create an array of
Image objects and call the DrawImage method. First we define an array of Image objects as follows:
Then we initialize the image array. We can create an Image object from a file by using the [Link]
method. The following code snippet initializes the image array:
533
534
Next we calculate the sizes of the images and draw them using DrawImage on the DrawItem event handler. We
can also set the sizes of items on the MeasureItem event handler. Listing 15.15 shows how to draw images
using the DrawImage method.
Listing 15.15 DrawItem and MeasureItem event handlers for an image ListBox control
534
535
[ Team LiB ]
[ Team LiB ]
SUMMARY
GDI+ can be used to write fun applications. In this chapter we covered more practical uses of GDI+ for
real-world Windows applications. Topics discussed in this chapter included how to write interactive GUI
rectangular and nonrectangular Windows applications, how to add custom text to images, how to read and
write images to and from a stream or database, and finally, how to create owner-drawn controls.
[ Team LiB ]
[ Team LiB ]
If you come from a C++ background, you are probably familiar with techniques such as C++ exception
handling, structured exception handling, and MFC exceptions. If you come from a Visual Basic background,
you are probably familiar with the On Error statement. Before .NET, every language implemented its own
error handling. With .NET, all languages that create managed code share the same error handling mechanism.
All .NET-supported languages (C"docText">C++ developers will probably be familiar with the try...catch
block, which provides structured exception handling. Suspect code is placed within a try block, and when an
exception occurs, the control is directed to the catch block. We will discuss the try...catch block in more
detail in the following sections.
Note
C++ and C# are case-sensitive languages; [Link] is not, in the sense that no matter what is typed in, the
editor automatically corrects the capitalization. In C# and C++, the statement is try...catch; in [Link], it
is Try...Catch.
[ Team LiB ]
[ Team LiB ]
535
536
using System;
using [Link];
namespace ListingA1andA2
{
class Class1
{
static void Main(string[] args)
{
[Link]("c:\\[Link]", [Link]);
}
}
}
What if the file does not exist? We get the error message shown in Figure A.1. We are fortunate that CLR
handles so much for us because otherwise this error message could have been a lot worse.
Now let's make a small modification to our program. The new code is shown in Listing A.2. This time we use
a simple try...catch block to handle the exception.
using System;
using [Link];
namespace ListingA1andA2
{
class Class1
{
static void Main(string[] args)
{
try
{
[Link]("c:\\[Link]", [Link]);
}
catch (Exception exp)
{
[Link]([Link]);
536
537
}
}
}
}
Figure A.2 shows the output from the modified program. Not only is the exception handled, but also the cause
of the exception is reported.
[ Team LiB ]
[ Team LiB ]
Using the try...catch statement is very straightforward. First we decide which code we want the error
handler to monitor by placing that code inside the try block. When an exception occurs in the encapsulated
code, a control goes to the catch block that handles the exception. A simple template for a try...catch
block is shown in Listing A.3.
try
{
// Place the code that may generate
// an exception in this block
}
catch ( exception type)
{
// This code executes when the try block fails and
// the filter on the catch statement is true.
// Here you can write your own custom error message
// or get the message description or other details
// from the exception class.
}
537
538
the filter condition is true. The finally block always executes last, just before the error handling block loses
scope, regardless of whether an exception has occurred. The finally block is the perfect place to close files
and dispose of objects. A simple try...catch...finally statement is shown in Listing A.4.
try
{
// Place the code that may generate
// an exception in this block
}
catch ( exception type)
{
// This code executes when the try block fails and
// the filter on the catch statement is true.
// Here you can write your own custom error message
// or get the message description or other details
// from the exception class.
}
finally
{
// Release and dispose of objects and
// other resources here
}
Listing A.5 allocates resources at the beginning of the method and releases them inside the finally block.
Regardless of whether an exception occurs, execution control will pass to the finally block and release the
resources.
538
539
{
p1, p2, p3, p4, p5, p6, p7
};
// Draw Bézier curve
[Link](redPen, ptsArray);
}
catch(Exception exp)
{
string errMsg = "Message: " + [Link];
errMsg += "Source: "+ [Link]();
errMsg += "TargetSite: "+ [Link];
errMsg += "HelpLink: "
+ [Link]();
errMsg += "StackTrace: "
+ [Link]();
[Link](errMsg);
}
finally
{
// Release resources
// Dispose of objects
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
}
}
We can provide more specific error handling by nesting try...catch blocks. The only case in which we
might not want to use nested try...catch blocks is when we want to catch different types of exceptions. For
example, one block might catch memory-related exceptions; another, I/O-related exceptions; and a third,
general exceptions.
Listing A.6 uses nested try...catch statements. In this code we create two images. The first image we draw
only once, but the second image we draw 15 times at different locations. The first try...catch statement
covers the entire code with a general exception, and the second try...catch statement is specific to the
OutOfMemory exception. We can use as many try...catch blocks as exceptions we want to catch. For
example, if our code performs I/O operations, we may want to use the IOException class. We can also
customize the default message to match the error type.
539
540
// Draw the second image many times
int x1, y1, x2, y2, w, h;
x1 = x2 = AutoScrollPosition.X;
y1 = AutoScrollPosition.Y;
y2 = 300;
w = 20;
h = 20;
// Make a loop to draw second image
// on top of the first image
for(int i=0; i<=15; i++)
{
try
{
// Draw from top left to bottom right
[Link](smallImage,
new Rectangle(x1, y1, w, h),
0, 0, [Link],
[Link],
[Link] );
// Draw from top right to bottom left
[Link](smallImage,
new Rectangle(x2, y2, w, h),
0, 0, [Link],
[Link],
[Link] );
x1 += 20;
y1 += 20;
x2 += 20;
y2 -= 20;
}
catch (OutOfMemoryException memExp)
{
[Link]([Link]);
}
}
}
catch(Exception exp)
{
[Link]([Link]);
}
finally
{
// Dispose of objects
[Link]();
}
}
The try...catch statement also allows us to use multiple catch statements with a single try statement,
which helps when we're catching multiple types of exceptions and customizing error messages to match the
type of error.
Listing A.7 is a modified version of Listing A.6 that uses a try statement with two catch statements.
Listing A.7 Using multiple catch statements with a single try statement
540
541
try
{
// Create an image from a file
Image curImage = [Link]("[Link]");
// Draw image
[Link](curImage, AutoScrollPosition.X,
AutoScrollPosition.Y,
[Link], [Link] );
// Create a second image from a file
Image smallImage =
[Link]("[Link]");
// Draw the second image many times
int x1, y1, x2, y2, w, h;
x1 = x2 = AutoScrollPosition.X;
y1 = AutoScrollPosition.Y;
y2 = 300;
w = 20;
h = 20;
// Make a loop to draw second image
// on top of the first image
for(int i=0; i<=15; i++)
{
// Draw from top left to bottom right
[Link](smallImage,
new Rectangle(x1, y1, w, h),
0, 0, [Link],
[Link],
[Link] );
// Draw from top right to bottom left
[Link](smallImage,
new Rectangle(x2, y2, w, h),
0, 0, [Link],
[Link],
[Link] );
x1 += 20;
y1 += 20;
x2 += 20;
y2 -= 20;
}
}
catch (OutOfMemoryException memExp)
{
[Link]([Link]);
}
catch(Exception exp)
{
[Link]([Link]);
}
finally
{
// Dispose of objects
[Link]();
}
}
[ Team LiB ]
[ Team LiB ]
541
542
The Exception class is the first class we will discuss. It caters to errors that occur during
normal application execution. This is the base class for all exception classes. In our
previous samples, we have already seen how to use the Exception class. Table A.1
describes its properties.
Listing A.8 uses the Exception class properties to display information about an exception.
The .NET Framework class library defines a multitude of exception classes each designed
to handle a specific kind of exception. For example, the IOException error is thrown when
an I/O error occurs. All of the classes work in a similar way. If you want to handle
I/O-related errors, use IOException instead of Exception. This allows your code to
respond to a more specific exception. Unlike [Link] and other libraries, GDI+ doesn't
have any specific exception handling classes.
542
543
Returns the error
message that
describes the
current exception.
Read-only.
Source Indicates the
name of the
application or
object that causes
the error. Both
get and set.
StackTrace A string
representation of
the frames on the
call stack at the
time the
exception
occurred.
Read-only.
TargetSite Returns the
method that
throws the
exception.
Read-only.
Some of the common exception handling classes are listed below. The SystemException class, which is
derived from the Exception class, is the base class for system (runtime)-generated errors. The following class
hierarchy shows the SystemException-derived classes:
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
.CodeDomSerializerException
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
543
544
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
.InvalidComObjectException
[Link]
.InvalidOleVariantTypeException
[Link]
.MarshalDirectiveException
[Link]
.SafeArrayRankMismatchException
[Link]
.SafeArrayTypeMismatchException
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
As we saw in the class hierarchy, the .NET Framework defines hundreds of exception classes some of them
specific to a particular operation. For example, OutOfMemoryException is thrown when there is not enough
memory to continue the execution of a program.
The [Link] class represents arithmetic exceptions that occur in arithmetic, casting, or
conversion operations. All of its members are inherited from the Exception class. ArithmeticException has
three derived classes: DivideByZeroException, NotFiniteNumberException, and OverflowException.
DivideByZeroException occurs when code tries to divide an integral or decimal value by zero.
NotFiniteNumberException occurs when a floating point value is positive infinity, negative infinity, or not a
number. OverflowException occurs when an arithmetic, casting, or conversion operation in a checked
context results in an overflow.
[Link] and its derived classes represent exceptions that occur when we're working
with data ([Link]) components.
544
545
[Link] represents an exception that is thrown when the stack overflows because
too many method calls have been executed.
[ Team LiB ]
545
546
546
GDI+ is an improvement over the traditional GDI, offering advanced graphics functionality, better performance, and enhanced features such as gradient brushes, alpha blending, and the ability to draw text, lines, curves, and various shapes with more complex rendering capabilities . GDI+ provides object-oriented programming support and is integrated within the .NET Framework, primarily in the System.Drawing namespace, making it easier for developers to create graphics applications in Windows and Web environments . Furthermore, GDI+ enhances color handling and provides advanced transformations, which are less efficient in GDI .
Transformations in GDI+ can be applied globally or locally to graphics objects through the Transform property of the Graphics class. Composite transformations like scaling, translating, and rotating can dramatically alter the rendering of graphics by changing their size, position, and orientation in a single operation . This not only affects how graphics are displayed but also their interaction with other graphical elements, allowing for more dynamic and sophisticated visual effects . Transformations are utilized for both aesthetic enhancements and functional layout arrangements in graphical applications .
Optimizing GDI+ applications involves using practices such as minimizing resource allocation and disposal within frequently called code, using appropriate data structures for graphics management, and leveraging double buffering to reduce flickering . Employing efficient painting techniques, selecting the correct graphics objects, and reducing overdraw by strategically deciding what and when to render can enhance performance . Additionally, understanding the intricacies of graphics state management and using SetClip and IntersectClip to limit the drawing area are essential optimization strategies .
Brushes and pens in GDI+ are used to draw and fill graphics objects. Pens define the outline of shapes, while brushes fill areas with color. GDI+ supports various brush types, such as SolidBrush, TextureBrush, GradientBrush, and HatchBrush, each offering different visual outcomes . For instance, HatchBrush allows for textured fills with patterns, while GradientBrush can be used for smooth color transitions which are not possible with solid color fills . This versatility enables developers to create visually appealing graphics with complex texture and shading effects .
The Graphics class is essential for rendering graphics objects in GDI+. It provides methods to draw figures, fill regions with colors, and manipulate graphics objects. Key methods include DrawString for text, DrawLine for lines, FillRectangle for filling areas of the screen, and transformations like RotateTransform and ScaleTransform for graphical transformations . The Graphics class allows for effective handling of pens and brushes, managing colors and text . It is the cornerstone for any drawing operation, making complex drawing tasks easier and more manageable .
GDI+ allows handling of static images through methods such as Image.FromFile and Graphics.DrawImage to load and display images . For animated images, GDI+ provides the ImageAnimator class to handle GIFs and TIFFs with the Animate, CanAnimate, StopAnimate, and UpdateFrames methods to manage frame transitions and display sequences . Additionally, developers can implement transformations and execute image serialization with classes within the System.Drawing.Imaging namespace to manipulate image qualities and formats effectively .
Exception handling in GDI+ applications is structured using the principles similar to C++ exception handling, provided by the .NET Framework's structured exception handling model . Exception handling is crucial for diagnosing and resolving runtime errors, ensuring that applications remain robust and user-friendly . Although the examples in the book do not include exception handling code for simplicity, it is recommended that developers understand and apply these techniques to manage errors gracefully and improve application stability .
GDI+ supports both Windows and Web application development by providing a comprehensive set of graphics tools and functionalities that integrate seamlessly with the .NET Framework. It enables developers to create rich graphical interfaces and visual elements in Windows applications using Windows Forms, and similarly, supports Web applications via ASP.NET for server-side graphics rendering . This dual support allows for consistent graphics execution across different platforms, promoting code reuse and better resource management .
In GDI+, textual content can be added to images using the DrawString method, which requires specifying the font style, color, and positioning on the image . Challenges include ensuring the text is legible against varied image backgrounds and maintaining text quality when resizing images. Developers can use color manipulation like adjusting pixel brightness to enhance text visibility, but must be careful of color limits (e.g., max RGB values) to avoid distortion . These techniques allow for complex personalization and branding on images, driving user engagement in media applications .
Marginal printing in GDI+ requires careful consideration because printers may have limitations regarding the area of the page they can effectively print on. It is advised not to attempt printing at the edges as placement can vary between printers due to paper movement within the device . Various printer models and types may have different capabilities, such as how close to the edge they can print without causing issues. Understanding these hardware constraints is essential for developing reliable printing applications .