Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Drawing

Lluis Sanchez edited this page Aug 19, 2013 · 1 revision

The drawing model used by Xwt is very similar to Cairo's, where invisible 'paths' are created and then Filled or Stroked with the current Color, Pattern or LineStyle to make them visible on the drawing surface or Canvas. Two types of drawing surface are available, the first of which derives from Xwt.Canvas and the second from Xwt.Drawing.Image.

A Canvas is linked to an on-screen display, and drawing takes place when a region of the display is exposed or needs to be redrawn. The Backend creates a suitable drawing surface, and calls the OnDraw method for the user to perform the necessary drawing operations. Any 'double-buffering' takes place behind-the-scenes in the backend. To update a region of the display, the QueueDraw (Rectangle dirtyRect) method should be called, and this queues a redraw (subsequently resulting in OnDraw (ctx, dirtyRect) being called).

An Image is created as an off-screen drawing surface which can be drawn into directly, and which can be copied to the on-screen display within the OnDraw method.

Coordinate System

The origin of the Xwt coordinate system is located in the top-left corner of the Canvas with positive X-values increasing from left to right and positive Y-values increasing downwards from top to bottom. Positive angles of rotation are measured from the positive X-axis clockwise towards the positive Y-axis.

When applied to a pixel-based display, the X and Y coordinates may be thought of as defining a rectangular grid, with the pixels lying between the lines of this grid. The default linewidth in Xwt is 2, so that a line in the X direction with integer Y coordinates will have a linewidth of exactly 2 pixels. However, to draw a single (or any odd) pixel-width line, the endpoints must be defined on half-pixel coordinates. If this is not done, these lines will appear slightly blurred, as anti-aliasing is applied to spread the apparent width into adjacent pixels.

Drawing Context

Before any drawing can be done, a Drawing.Context must be obtained from the drawing surface. In the case of Xwt.Canvas a Context is created by the Backend and supplied to the overridden method Canvas.OnDraw (Context ctx, Rectangle dirtyRect). A Context may also be obtained from an ImageBuilder, and used to draw directly onto the Image which is being built.

The Context keeps track of all the state variables required for drawing, such as LineWidth, LineDash, Color, and Path. When a Context is created, these state variables have default values, which can be changed by SetLineWidth, SetColor, etc. Snapshots of the Context can be preserved by ctx.Save () and recalled by ctx.Restore (), which operate in a stack-like manner. It is conventional for all drawing routines to save the Context passed to them and restore it when returning, so that a drawing routine can always assume that it has been passed a default context.

The Path itself is empty when the Context is first created, and is then added to by 'drawing actions' such as ctx.MoveTo (x0, y0), ctx.LineTo (x1, y1) and so on. The Path maintains a 'Current Point' which tracks the position (x0, y0), (x1, y1) after each action above. The Path may be closed using ctx.ClosePath () and this joins the Current Point back to the Path start point to form a closed figure. The actions can be imagined as moving a cutting stylus to form a drawing 'mask' or 'stencil' through which 'paint' is transferred to the surface when the Path is 'stroked' or 'filled'.

Possible Path drawing actions include the following:

  • MoveTo (x, y) : Moves the Current Point to (x, y) without making the Path connect to the previous point.
  • LineTo (x, y) : Moves the Current Point to (x, y), joining it with a line to the previous point.
  • RelMoveTo (dx, dy) : As MoveTo, but with the distances relative to the Current Point.
  • RelLineTo (dx, dy) : As LineTo, but with distances relative to the Current Point.
  • Rectangle (rect) : Adds a Rectangle 'rect' to the Path.
  • Arc (x,y, r, a1,a2) : Adds a circular arc of radius r, centred at (x,y), from angle a1 to angle a2.

Stroke

Calling ctx.Stroke () uses the current LineWidth, LineDash and Color to realise the figure described by the Path, which, until that point, has no defined width. The current Color is applied for half the LineWidth on each side of the Path. After ctx.Stroke (), the Path is emptied, ready to start the next Path. StrokePreserve () allows the Path to be retained (and extended) if necessary.

Fill

Calling ctx.Fill () instead treats the closed Path as a 'hole' through which the current Color is applied to fill the inside of the figure. Filling a Path fills right up to the edge of the Path and no further. After ctx.Fill () the Path is emptied, with ctx.FillPreserve () allowing the Path to be retained. Perhaps the most common use of this is when a figure is filled with one color, and then outlined along the same path using a different color.

Text

Text is drawn by creating a TextLayout which retains state variables such as Font, FontStyle, BackgroundColor, etc. Various constructors are available, with TextLayout () using the SystemFont and TextLayout (Canvas canvas) inheriting the Font from the canvas. The TextLayout.Text property allows the text string itself to be accessed, and this text can contain mark-up The reference point for the layout is at its top-left corner, and, to display the layout, ctx.DrawTextLayout (layout, x, y) places this at the specified coordinates. Rotation and Translation of the layout is done by setting up suitable transforms (below).

Transforms

Transforms are a way of setting up a relation between two coordinate systems. The device-space coordinate system is tied to the display surface, and is fixed. The user-space coordinate system matches that space by default, (ie, when the Context is first created) but can be changed to allow Rotation, Translation, and Scaling of those coordinates. The current transform is tracked by the Current Transformation Matrix (CTM), part of the Context state, which is used, at the point of drawing, to convert from user-space coordinates to device coordinates:

ctx.Translate (10, 20);			// Translate x by 10 and y by 20
ctx.Rotate (90);				// Rotate by 90 degrees
ctx.Rectangle (5, 0, 20, 10);	// Draw rectangle of width 20 and height 10 at x=5, y=0
ctx.Stroke ();

Deciding which transforms to apply, and in what order, may be viewed from a 'top-down' or 'bottom-up' perspective, although the end result is, of course, the same.

Top Down

This views successive transforms in the code as being applied to the user-space coordinate system in use when each drawing action is done, with each transform taking place after any existing transforms. In the above example, the user-space origin (0, 0) is first translated to (10, 20), and the axes are then rotated about this new origin by 90 degrees clockwise. A Rectangle is then drawn at a point (5, 0) in this rotated coordinate system. Referenced to the original coordinate system, this rectangle will be drawn at (10, 25), and with its width (20) along the original y-axis.

Bottom Up

This considers the figure to be drawn with no transforms in place, and then applies the transforms in reverse order to produce successive transformed figures. In the above example, the rectangle would be drawn at (5, 0) with its width (20) along the x-axis. The rectangle is then rotated about the origin (0, 0) by 90 degrees, so that its new top-left position is (0, 5), and its width (20) lies in the y-direction. It is then translated by (10, 20), so that its final top-left is at (10, 25). The original coordinate system is used throughout, and an updated figure is produced at each transform.

Matrix Transforms

The Current Transformation Matrix (CTM) which keeps track of all drawing transforms is identical in form to an Xwt.Drawing.Matrix and is known as an Affine Transform. It is the mechanism by which the user-space coordinates are transformed to device space when figures are drawn.

Each point (x, y) in a figure is represented as a 1x3 row vector [x y 1], and an Affine Transform is represented as a 3x3 matrix of the form

|m11  m12  0| 
|m21  m22  0|
|x0   y0   1|

When a transform is applied, each point (x, y) is transformed by matrix multiplication to become (x', y') so that

[x'  y'  1] = [x  y  1] * |m11  m12  0| = [ (x*m11+y*m21+x0)  (x*m12+y*m22+y0)  1 ]
                          |m21  m22  0|
                          |x0   y0   1|

When a Context is created, the initial CTM is the Matrix.Identity matrix

|1  0  0| 
|0  1  0|
|0  0  1|

which is effectively a null transform, since (x', y') == (x, y).

If multiple transforms are to be applied, the order in which these are coded is important. If two transforms A and B are represented by matrices

|a11  a12  0|  and  |b11  b12  0|
|a21  a22  0|       |b21  b22  0|
|ax0  ay0  1|       |bx0  by0  1|

then the result AB is not the same as BA.

In order for successive transforms to be applied after any existing ones (the Top-down approach), they must be Prepended to the CTM, so that if transform A is applied first, then transform B, the combined transform will be B*A. If the vector [x y 1] is transformed by this combined transform, the result is

[x"  y"  1] = [x  y  1] * |b11  b12  0| * |a11  a12  0|
                          |b21  b22  0|   |a21  a22  0|
                          |bx0  by0  1|   |ax0  ay0  1|

This can be viewed as successively transforming [x y 1] by B to form [x' y' 1], then transforming [x' y' 1] by A to form [x" y" 1]. From this viewpoint (the Bottom-up approach), the transforms are effectively applied in reverse order to that in the code.

The Drawing.Matrix routines can be used to build up transforms (in exactly the same way as Context transforms) which can be applied to the CTM by ctx.ModifyCTM (Matrix transform). This is useful for building and saving complex transforms, and applying these to the CTM in a single operation, helping to optimise drawing operations.

Clone this wiki locally