5.1.1 Canvas, Pen and Brush Objects
AspJpeg.NET is equipped with a versatile set of drawing routines which enable your application
to draw high-quality antialiased graphics and text on top of an image. All the drawing methods are
incapsulated by the JpegCanvas object which is accessible
via the JpegImage.Canvas property. For example, the following code draws a line:
objImage.Canvas.DrawLine( 10, 20, 140, 340 );
The drawing operations are all subject to the current drawing parameters such as the stroking color, line width, fill color, opacity, etc.
These parameters are specified via the properties of the JpegPen and JpegBrush objects
accessible via JpegCanvas's Pen and Brush properties, respectively. The JpegPen object is responsible for stroking operations, and
JpegBrush for filling operations. For example,
the following code sets the line width to 5:
objImage.Canvas.Pen.Width = 5;
This section of the chapter will focus on drawing graphics. Drawing text is described in detail in the Section 5.3 of this chapter.
5.1.2 Path-based Drawing Model
The central notion in AspJpeg.NET's drawing model is a path, which is a composition of straight and curved line segments which may
connect to one another or may be disconnected.
A straight line is defined by two points - the current point and endpoint.
A curved path segment is specified as a Cubic Bezier Curve. Such curves are defined by four points: the two endpoints
(the current point P0 and the final point P3) and two control points P1 and P2.
The curve does not, in general, pass through the control points:
A path is made up of one or more disconnected subpaths, each comprising a sequence of connected segments.
Typically, a subpath is defined by specifying its starting point, appending one or more segments to it, and then closing it.
Once a path is defined, it is painted by stroking, filling, or both.
With AspJpeg.NET, a new path, or a new subpath within an existing path, is created by calling the JpegCanvas.MoveTo
method. This establishes the first current point of the path (or subpath). A straight line
is added to the path via JpegCanvas.LineTo, and a curve via JpegCanvas.CurveTo.
The current path or subpath is closed by calling JpegCanvas.ClosePath. Once the entire path
is built, it is stroked with JpegCanvas.Stroke, filled with JpegCanvas.Fill,
or both filled and stroked with JpegCanvas.FillStroke.
The following code snippet draws a 5-end star on a new blank image with a while background (0xFFFFFFFF).
The pen color is blue (0xFF0000FF) and brush color is gray (0xFF808080). The pen width is 6.
JpegManager objJpeg = new JpegManager();
JpegImage objImage = objJpeg.CreateImage(300, 300, 0xFFFFFFFF);
objImage.Canvas.Pen.Width = 6;
objImage.Canvas.Brush.Color = 0xFF808080;
objImage.Canvas.Pen.Color = 0xFF0000FF;
objImage.Canvas.MoveTo(150, 50);
objImage.Canvas.LineTo(209, 231);
objImage.Canvas.LineTo(55, 119);
objImage.Canvas.LineTo(245, 119);
objImage.Canvas.LineTo(91, 231);
objImage.Canvas.ClosePath();
objImage.Canvas.FillStroke();
objImage.Save( @"c:\path\output.jpg" );
The following code snippet draws a propeller-like figure by defining and filling a path made up of two subpaths:
objImage.Canvas.Brush.Color = 0xFFFF0000;
objImage.Canvas.MoveTo(306, 96);
objImage.Canvas.CurveTo(446, 196, 166, 396, 306, 496);
objImage.Canvas.ClosePath();
objImage.Canvas.MoveTo(106, 296);
objImage.Canvas.CurveTo(206, 156, 406, 436, 506, 296);
objImage.Canvas.ClosePath();
objImage.Canvas.Fill();
5.1.3 Filling Rules
The Fill and FillStroke methods used in the previous section paint the insides of all the subpaths of a current path,
considered together. Any subpaths that are open are implicitly closed before being filled.
For a simple path, it is intuitively clear what region lies inside. However, for a more complex path -- for example,
a path that intersects itself or has one subpath that encloses another -- the interpretation of "inside" is not always obvious.
The path machinery uses one of two rules for determining which points lie inside a path: the nonzero winding number rule and the even-odd rule.
The nonzero winding number rule determines whether a given point is inside a path by conceptually drawing a
ray from that point to infinity in any direction and then examining the places where a segment of the path crosses the ray.
Starting with a count of 0, the rule adds 1 each time a path segment crosses the ray from left to right and subtracts 1
each time a segment crosses from right to left. After counting all the crossings, if the result is 0 then the point is outside the path; otherwise it is inside.
An alternative to the nonzero winding number rule is the even-odd rule. This rule determines the "insideness" of a point by
drawing a ray from that point in any direction and simply counting the number of path segments that cross the ray,
regardless of direction. If this number is odd, the point is inside; if even, the point is outside.
This yields the same results as the nonzero winding number rule for paths with simple shapes,
but produces different results for more complex shapes.
By default, AspJpeg.NET uses the nonzero winding number rule. To use the even-odd rule, the Fill and
FillStroke methods should be called with true passed to them, as follows:
objImage.Canvas.FillStroke(true);
The 5-end start drawing code above would produce the following result if true were passed to the FillStroke method:
5.1.4 Stroke Parameters: Width, Cap and Join Styles
The current line width is set via the Canvas.Pen.Width property which is set to 1 by default. A line width can be a fractional number.
The Canvas.Pen.CapStyle property specifies the shape to be used at the ends of open subpaths (and dashes, if any)
when they are stoked. The CapStyle property can be set to one of three values:
0: Butt cap. The stroke is squared off at the endpoint of the path. There is no projection beyond the end of the path. This is the default value.
1: Round cap. A semicircular arc with a diameter equal to the line width is drawn around the endpoint and filled in.
2: Projecting square cap. The stroke continues beyond the endpoint of the path for a distance equal to half the line width and is then squared off.
The Canvas.Pen.JoinStyle property specifies the shape to be used at the corners of paths that are stroked.
The JoinStyle property can be set to one of three values:
0: Miter join. The outer edges of the strokes for the two segments are extended until they meet at an angle. If the segments meet at too sharp an angle (as defined by the miter limit parameter described below), a bevel join is used instead. This is the default style.
1: Round join. A circle with a diameter equal to the line width is drawn around the point where the two segments meet and is filled in, producing a rounded corner.
2: Bevel join. The two segments are finished with butt caps and the resulting notch beyond the ends of the segments is filled with a triangle.
When two line segments meet at a sharp angle and mitered joins have been specified as the line join style, it is possible for the miter to extend far
beyond the thickness of the line stroking the path. The miter limit imposes a maximum on the ratio of the miter length to the line width.
When the limit is exceeded, the join is converted from a miter to a bevel. The miter limit can be specified via the Canvas.Pen.MiterLimit
property, which is 10 by default.
5.1.5 Line Dash Pattern
The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
It is specified by a dash array and a dash phase. The dash array’s elements are numbers that specify the lengths of
alternating dashes and gaps; the dash phase specifies the distance into the dash pattern at which to start the dash.
By default, the dash value array is empty (null) and dash phase is 0, which corresponds to a solid, unbroken line.
The dash patten array is specified via the Canvas.Pen.DashPattern property which expects an array of float's.
By default, this property is null which corresponds to a solid line.
The dash phase is specified via the Canvas.Pen.DashPhase property, which is 0 by default.
The following five code snippets produce patterns displayed below:
' 1
objImage.Canvas.Pen.DashPatten = new float[1] { 3 };
objImage.Canvas.Pen.DashPhase = 0;
' 2
objImage.Canvas.Pen.DashPatten = new float[1] { 2 };
objImage.Canvas.Pen.DashPhase = 1;
' 3
objImage.Canvas.Pen.DashPatten = new float[2] { 2, 1 };
objImage.Canvas.Pen.DashPhase = 0;
' 4
objImage.Canvas.Pen.DashPatten = new float[2] { 3, 5 };
objImage.Canvas.Pen.DashPhase = 6;
' 5
objImage.Canvas.Pen.DashPatten = new float[2] { 2, 3 };
objImage.Canvas.Pen.DashPhase = 11;