RISCOS.com

www.riscos.com Technical Support:
Programmer's Reference Manual

 

Draw module


Introduction

The Draw module is an implementation of PostScript type drawing. A collection of moves, lines, and curves in a user-defined coordinate system are grouped together and can be manipulated as one object, called a path.

A path can be manipulated in memory or upon writing to the VDU. There is full control over the following characteristics of the path:

  • rotation, scaling and translation of the path
  • thickness of a line
  • description of dots and dashes for a line
  • joins between lines can be mitred, round or bevelled
  • the leading or trailing end of a line, or dot (which are in fact just very short dashes), can be butt, round, a projecting square or triangular (used for arrows)
  • filling of arbitrary shapes
  • what the fill considers to be interior

A path can be displayed in many different ways. For example, if you write a path that draws a petal, and draw it several times rotating about a point, you will have a flower. This uses only one of the characteristics that you can control.

The Draw application was written using this module, and this is the kind of application that it is suited to. It is advisable to read the section on Draw in the User Guide to familiarise yourself with some of the properties of the Draw module.

Overview

There are many specialised terms used within the Draw module. Here are the most important ones. If you are familiar with PostScript, then many of these should be the same.

  • A path element is a sequence of words. The first word in the sequence has a command number, called the element type, in the bottom byte. Following this are parameters for that element type.
  • A subpath is a sequence of path elements that defines a single connected polygon or curve. The ends of the subpath may be connected, so it forms a loop (in which case it is said to be closed) or may be loose ends (in which case it is said to be open). A subpath can cross itself or other subpaths in the same path.
  • A path is a sequence of subpaths and path elements.
  • A Bezier curve is a type of smooth curve connecting two endpoints, with its direction and curvature controlled by two control points.
  • Flattening is the process of converting a Bezier curve into a series of small lines when outputting.
  • Flatness is how closely the lines will approximate the original Bezier curve.
  • A transformation matrix is the standard mathematical tool for two-dimensional transformations using a three by three array. It can rotate, scale and translate (move).
  • To stroke means to draw a thickened line centred on a path.
  • A gap is effectively a transparent line segment in a subpath. If the subpath is stroked, the piece around the gap will not be plotted. Gaps are used by Draw to implement dashed lines.
  • Line caps are placed at the ends of an open subpath and at the ends of dashes in a dashed line when they are stroked. They can be butt, round, a projecting square or triangular.
  • Joins occur between adjacent lines, and between the start and end of a closed subpath. They can be mitred, round or bevelled.
  • To Fill means to draw everything inside a path.
  • Interior pixels are ones that are filled. Exterior pixels are not filled.
  • A winding number rule is the rule for deciding what is interior or exterior to a path when filling. The interior parts are those that are filled.
  • Boundary pixels are those that would be drawn if the line were stroked with minimum thickness for the VDU.
  • Thickening a path is converting it to the required thickness - that is generating a path which, if filled, would produce the same results as stroking the original path.

Scaling systems

This is an area where you must take great care when using the Draw module, because four different systems are used in different places.

OS units

OS units are notionally 1/180th of an inch, and are the standard units used by the VDU drivers for specifying output to the screen.

This coordinate system is (not surprisingly) what the Draw module uses when it strokes a path onto the screen.

Internal Draw units

Internally, Draw uses a coordinate system the units of which are 1/256th of an OS unit. We shall call these internal Draw units.

In a 32 bit internal Draw number, the top 24 bits are the number of OS units, and the bottom 8 bits are the fraction of an OS unit.

User units

The coordinates used in a path can be in any units that you wish to use. These are converted by the transformation matrix into internal Draw units when generating output.

Note that because it is a fixed point system, scaling problems can occur if the user units differ too much from the internal Draw units. Because of this problem, you are limited in the range of user units that you can use.

Transform units

Transform units are only used to specify some numbers in the transformation matrix. They divide a word into two parts: the top two bytes are the integer part, and the bottom two bytes are the fraction part.

Transformation matrix

This is a three by three matrix that can be used to rotate, scale or translate a path in a single operation. It is laid out like this:

a b 0
c d 0
e f 1

This matrix transforms a coordinate (x, y) into another coordinate (x', y') as follows:

x' = ax + cy + e
y' = bx + dy + f

The common transformations can all be easily done with this matrix. Translation by a given displacement is done by e for the x axis and f for the y axis. Scaling the x axis uses a, while the y axis uses d. Rotation can be performed by setting a = cos([THETA])_ B = SIN([THETA])_ C = -SIN([THETA]) AND D = COS([THETA])_ WHERE [THETA] is the angle of rotation.

a, b, c and d are given in transform units to allow accurate specification of the fractional part. e and f are specified in internal Draw units, so that the integer part can be large enough to adequately specify displacements on the screen. (Were transform units to be used for these coefficients, then the maximum displacement would only be 256 OS units, which is not very far on the screen.)

Winding rules

The winding rule determines what the Draw module considers to be interior, and hence filled.

Even-odd means that an area is filled if a ray from that area to outside the path's bounding box crosses an odd number of paths.

Non-zero winding fills areas on the basis of the direction in which the subpaths which surround the area were constructed. If an equal number of subpaths in each direction surround the area, it is not filled, otherwise it is.

The positive winding rule will fill an area if it is surrounded by more anti-clockwise subpaths than clockwise. The negative winding rule works in reverse to this.

Even-odd and non-zero winding are printer driver compatible, whereas the other two are not. If you wish to use the path with a printer driver, then bear this in mind.

Stroking and filling

Flattening means bisecting any Bezier curves recursively until each of the resulting small lines lies within a specified distance of the curve. This distance is called flatness. The longer this distance, the more obvious will be the straight lines that approximate the curve.

All moving and drawing is relative to the VDU graphics origin (as set by VDU 29,x;y; ).

None of the Draw SWIs will plot outside the boundaries of the VDU graphics window (as set by VDU 24,l;b;r;t; ).

All calls use the colour (both pixel pattern and operation) set up for the VDU driver. Note that not all such colours are compatible with printer drivers.

Printing

If your program needs to generate printer output, then it is very important that you read the chapter entitled Printer Drivers. The Draw SWIs that are affected by printing have comments in them about the limitations and effects.

Floating point

SWI numbers and names have been allocated to support floating point Draw operations. In fact for every SWI described in this chapter, there is an equivalent one for floating point - just add FP to the end of each name.

The floating point numbers used in the specification are IEEE single precision floating point numbers.

They may be supported in some future version of RISC OS, but if you try to use them in current versions you'll get an error back.

Technical Details

Data structures

Many common structures are used by Draw module SWIs. Rather than duplicate the descriptions of these in each SWI, they are given here. Some SWIs have small variations which are described with the SWI.

Path

The path structure is a sequence of subpaths, each of which is a sequence of elements. Each element is from one to seven words in length. The lower byte of the first word is the element type. The remaining three bytes of it are free for client use. On output to the input path the Draw module will leave these bytes unchanged. However, on output to a standard output path the Draw module will store zeroes in these three bytes.

The element type is a number from 0 to 8 that is followed by the parameters for the element, each a word long. The path elements are as follows:

Element Type Parameters Description
0 n End of path. n is ignored when reading the path, but is used to check space when reading and writing a path.
1 ptr Pointer to continuation of path. ptr is the address of the first path element of the continuation.
2 x y Move to (x, y) starting new subpath. The new subpath does affect winding numbers and so is filled normally. This is the normal way to start a new subpath.
3 x y Move to (x, y) starting new subpath. The new subpath does not affect winding numbers when filling. This is mainly for internal use and rarely used by applications.
4 Close current subpath with a gap.
5 Close current subpath with a line. It is better to use one of these two to close a subpath than 2 or 3, because this guarantees a closed subpath.
6 x1 y1 x2 y2 x3 y3 Bezier curve to (x3, y3) with control points at (x1, y1) and (x2, y2).
7 x y Gap to (x, y). Do not start a new subpath. Mainly for internal use in dot-dash sequences.
8 x y Line to (x, y).

You will notice that there are some order constraints on these element types:

  • path elements 2 and 3 start new subpaths
  • path elements 6, 7 and 8 may only appear while there is a current subpath
  • path elements 4 and 5 may only appear while there is a current subpath, and end it, leaving no current subpath
  • path elements 2 and 3 can also be used to close the current subpath (which is a part of starting a new subpath).
Open and closed subpaths

When you are stroking (using Draw_Stroke), if a subpath ends with a 4 or 5 then it is closed, and the ends are joined - whereas a 2 or 3 leaves a subpath open, and the loose ends are capped. These four path elements explicitly leave a stroked subpath either open or closed.

Some other operations implicitly close open subpaths, and this will be stated in their descriptions.

Just because the ends of a subpath have the same coordinates, that doesn't mean the subpath is closed. There is no reason why the loose ends of an open subpath cannot be coincident.

Output path

After a SWI has written to an output path, it is identical to an input path. When it is first passed to the SWI as a parameter, the start of the block pointed to should contain an element type zero (end of path) followed by the number of available bytes. This is so that the Draw module will not accidentally overrun the buffer.

Fill style

The fill style is a word that is passed in a call to Draw_Fill, Draw_Stroke, Draw_StrokePath or Draw_ProcessPath. It is a bitfield, and all of the calls use at least the following common states. See the description of each call for differences from this:

Bit(s) Value Meaning
0, 1 0 non-zero winding number rule.
1 negative winding number rule.
2 even-odd winding number rule.
3 positive winding number rule.
2 0 don't plot non-boundary exterior pixels.
1 plot non-boundary exterior pixels.
3 0 don't plot boundary exterior pixels.
1 plot boundary exterior pixels.
4 0 don't plot boundary interior pixels.
1 plot boundary interior pixels.
5 0 don't plot non-boundary interior pixels.
1 plot non-boundary interior pixels.
6 - 31 reserved - must be written as zero
Matrix

The matrix is passed as pointer to a six word block, in the order a, b, c, d, e, and f as described earlier. That is:

Offset Value Common use(s)
0 a x scale factor, or cos([THETA]) TO rotate
4 b sin([THETA]) to rotate
8 c -sin([THETA]) to rotate
12 d y scale factor, or cos([THETA]) to rotate
16 e x translation
20 f y translation

If the pointer is zero, then the identity matrix is assumed - no transformation takes place.

Remember that a - d are in Transform units, while e and f are in internal Draw units; for example plotting with a scale factor of 1 - which is &1000 Transform units - and with a translation of (64, 32) - which are respectively &4000 and &2000 internal Draw units - would use the values [&1000, 0, 0, &1000, &4000, &2000].

Flatness

Flatness is the maximum distance that a line is allowed to be from a Bezier curve when flattening it. It is expressed in user units. So a smaller flatness will result in a more accurate rendering of the curve, but take more time and space. For very small values of flatness, it is possible to cause the 'No room in RMA' error.

A recommended range for flatness is between half and one pixel. Any less than this and you're wasting time; any more than this and the curve becomes noticeably jagged. A good starting point is:

flatness = number of user units in x axis / number of pixels in x axis

A value of zero will use the default flatness. This is set to a useful value that balances speed and accuracy when stroking to the VDU using the default scaling.

Note that if you are going to send a path to a high resolution printer, then you may have to set a smaller flatness to avoid jagged curves.

Line thickness

The line thickness is in user coordinates.

  • If the thickness is zero then the line is drawn with the minimum width that can be used, given the limitations of the pixel size (so lines are a single pixel wide).
  • If the thickness is n, then the line will be drawn with a thickness of n/2 user coordinates translated to pixels on either side of the theoretical line position.
  • If the line thickness is non-zero, then the cap and join parameter must also be passed.
Cap and join

The cap and join styles are each passed as a pointer to a four word block. A pointer of zero can be passed if cap and join are ignored (as they are for zero thickness lines). The block is structured as follows:

Word Byte Description
0 0 join style
0 = mitred joins
1 = round joins
2 = bevelled joins
1 leading cap style
0 = butt caps
1 = round caps
2 = projecting square caps
3 = triangular caps
2 trailing cap style (as leading cap style)
3 reserved - must be written as zero.
4 This value must be set if using mitred joins.
0,1 fractional part of mitre limit for mitre joins
2,3 integer part of mitre limit for mitre joins
8 0,1 setting for leading triangular cap width on each side(in 256ths of line widths, so &0100 is 1 linewidth)
2,3 setting for leading triangular cap length away from the line, in the same measurements as above
12 all This sets the trailing triangular cap size, using the same structure as the previous word.

The mitre limit is a little more complex than the others, so it is explained here rather than above. At any given corner, the mitre length is the distance from the point at which the inner edges of the stroke meet, to the point where the outer edges of the stroke meet. This distance increases as the angle between the lines decreases. If the ratio of the mitre length to the line width exceeds the mitre limit, stroke treats the corner with a bevel join instead of a mitre join. Also see the notes on scaling, later in this section.

Under RISC OS 2, the mitre limit is treated as unsigned. It is now treated as signed, but must be positive (ie <= &7FFFFFFF).

Note that words at offsets 4, 8, and 12 are only used if the appropriate style is selected by the earlier parts. The structure can therefore be made shorter if triangular caps and mitres are not used.

Dash pattern

The dash pattern is passed as a pointer to a block, the size of which is defined at the start, as follows:

Word Description
0 distance into dash pattern to start in user coordinates
4 number of elements (n) in the dash pattern
8 - 4n+4 elements in the dash pattern, each of which is a distance in user coordinates.

Again the pointer can be zero, which implies that continuous lines are drawn.

Each element specifies a distance to draw in the present state. The pattern starts with the draw on, and alternates off and on for each successive element. If it reaches the end of the pattern while drawing the line, then it will restart at the beginning.

If n is odd, then the elements will alternate on or off with each pass through the pattern: so the first element will be on the first pass, off the second pass, on the third pass, and so on.

Scaling

The Draw module uses fixed point arithmetic for speed. The number representations used are chosen to keep rounding errors small enough not to be noticeable.

However, if you use the transformation matrix to scale a path up a great deal, you will also scale up the rounding errors and make them visible.

To avoid such problems, we recommend that you don't use scale factors of more than 8 when converting from User units to internal Draw units. (This maximum recommended scale factor of 8 is &80000 in the Transform units used in the transformation matrix.)

Draw SWIs

Though there are a number of SWIs, they all call Draw_ProcessPath. Because this takes so many parameters, the other SWIs are provided as an easy way of using its functionality.

There are two that output to the VDU. Draw_Stroke emulates the PostScript stroke function and will draw a path onto the VDU. Draw_Fill acts like the fill function and fills the inside of a path. It is likely that most applications will only use these two SWIs.

The others are shortcuts for processing a path in one way or other. Draw_StrokePath acts exactly like Draw_Stroke, except it puts its output into a path rather than onto the VDU. Filling its output path produces the same results as stroking its input path. Draw_FlattenPath will handle only the flattening of a path, writing its output to a path. Likewise, Draw_TransformPath will only use the matrix on a path. All these processing SWIs are useful when a path will be sent to the VDU many times. If the path is flattened or transformed before the stroking, then it will be done faster.

Printer drivers

If you are using a printer driver, you should note that it cannot deal with all calls to the Draw module. For full details of this, see the chapter entitled Printer Drivers. As a general rule, you should avoid the following features:

  • AND, OR, etc operations on colours when writing to the screen.
  • Choice of fill style: eg fill excluding/including boundary, fill exterior, etc.
  • Positive and negative winding number rules.
  • Line cap enhancements, particularly differing leading and trailing caps and triangular caps.

The printer driver will also intercept DrawV and modify how parts of the Draw module work. Here is a list of the effects that are common to all the SWIs that output to the VDU normally:

  • cannot deal with positive or negative winding numbers
  • cannot fill:
  • non-boundary exterior pixels
  • exterior boundary pixels only
  • interior boundary pixels only
  • exterior boundary and interior non-boundary pixels
  • an application should not rely on any difference between the following fill states:
  • interior non-boundary pixels only
  • all interior pixels
  • all interior pixels and exterior boundary pixels

SWI Calls


Draw_ProcessPath
(SWI &40700)

Main Draw SWI

On entry

R0 = pointer to input path buffer (see below)
R1 = fill style
R2 = pointer to transformation matrix, or 0 for identity matrix
R3 = flatness, or 0 for default
R4 = line thickness, or 0 for default
R5 = pointer to line cap and join specification (if required)
R6 = pointer to dash pattern, or 0 for no dashes
R7 = pointer to output path buffer, or value (see below)

On exit

R0 depends on entry value of R7

if R7 = 0, 1 or 2, then R0 is corrupted
if R7 = 3, then R0 = size of output buffer
if R7 is a pointer, then R0 = pointer to new end of path indicator
R1 - R7 preserved

Interrupts

Interrupts are enabled
Fast interrupts are enabled

Processor Mode

Processor is in SVC mode

Re-entrancy

SWI is not re-entrant

Use

All the other SWIs in the Draw module are converted into calls to this SWI. They are provided to ensure that suitable names exist for common operations and to reduce the number of registers to set up when calling.

The input path, matrix, flatness, line thickness, cap and join, and dash pattern are as specified in the Data structures.

The fill style is as on Fill style, with the following additions:

Bit(s) Meaning
6 - 26 reserved - must be written as zero
27 set if open subpaths are to be closed
28 set if the path is to be flattened
29 set if the path is to be thickened
30 set if the path is to be re-flattened after thickening
31 set for floating point output (not implemented)

Normally, the output path will act as described on Output path, but with the following changes if the following values are passed in R7:

Value Meaning
0 Output to the input path buffer. Only valid if the input path's length (ie storage requirement) does not change during the call, such as when doing a transformation only.
1 Fill the path normally.
2 Fill the path, subpath by subpath. (Draw_Stroke will often use this to economise on RMA usage).
3 Count how large an output buffer is required for the given path and actions.
&80000000+pointer Output the path's bounding box, in transformed coordinates. The buffer will contain the four words: low x, low y, high x, high y.
pointer Output to a specified output buffer.
The length of the buffer must be indicated by putting a suitable path element 0 at the start of the buffer, and a pointer to the new path element 0 is returned in R0 to allow you to append to the output path.

You may do the following things with this call, in this order:

  1. Open subpaths may be closed (if selected by bit 27 of R1).
  2. The path may be flattened (if selected by bit 28 of R1). This uses R3.
  3. The path may be dashed (if R6 [NOT EQUAL] 0).
  4. The path may be thickened (if selected by bit 29 of R1). This uses R4 and R5.
  5. The path may be re-flattened (if selected by bit 30 of R1). This uses R3.
  6. The path may be transformed (if R2 [NOT EQUAL] 0).
  7. Finally, the path is output in one of a number of ways, depending on R7.

Note that R3, R4 and R5 may be left unspecified if the options that use them are not specified.

If you try dashing, thickening or filling on an unflattened Bezier curve, it will produce an error, as this is not allowed.

If you are using the printer driver, then it will intercept this SWI and affect its operation. In addition to the general comments in the chapter entitled Printer drivers, it is unable to handle R7 = 1 or 2.

Related SWIs

None

Related vectors

DrawV


Draw_Fill
(SWI &40702)

Process a path and send to VDU, filling the interior portion

On entry

R0 = pointer to input path
R1 = fill style, or 0 for default
R2 = pointer to transformation matrix, or 0 for identity matrix
R3 = flatness, or 0 for default

On exit

R0 corrupted
R1 - R3 preserved

Interrupts

Interrupts are enabled
Fast interrupts are enabled

Processor Mode

Processor is in SVC mode

Re-entrancy

SWI is not re-entrant

Use

This command emulates the PostScript 'fill' operator. It performs the following actions:

  • closes open subpaths
  • flattens the path
  • transforms it to standard coordinates
  • fills the resulting path and draws to the VDU.

The input path, matrix, and flatness are as specified in the Data structures.

The fill style is as specified on Fill style with the following addition. A fill style of zero is a special case. It specifies a useful default fill style, namely &30. This means fill to halfway through boundary, non-zero rule.

If you are using the printer driver, then it will intercept this SWI and affect its operation. See the general comments in the Printer drivers.

Related SWIs

None

Related vectors

DrawV


Draw_Stroke
(SWI &40704)

Process a path and send to VDU

On entry

R0 = pointer to input path
R1 = fill style, or 0 for default (see below)
R2 = pointer to transformation matrix, or 0 for identity matrix
R3 = flatness, or 0 for default
R4 = line thickness, or 0 for default
R5 = pointer to line cap and join specification (if required)
R6 = pointer to dash pattern, or 0 for no dashes

On exit

R0 corrupted
R1 - R6 preserved

Interrupts

Interrupts are enabled
Fast interrupts are enabled

Processor Mode

Processor is in SVC mode

Re-entrancy

SWI is not re-entrant

Use

This command emulates the PostScript 'stroke' operator. It performs the following actions:

  • flattens the path
  • applies a dash pattern to the path, if R6 [NOT EQUAL] 0
  • thickens the path, using the specified joins and caps
  • re-flattens the path, to flatten round caps and joins, so that they can be filled.
  • transforms the path to standard coordinates
  • fills the resulting path and draws to the VDU.

The input path, matrix, flatness, cap and join, and dash pattern are as specified in the Data structures.

The fill style is as specified on Fill style with the following additions. A fill style of zero is a special case. If the line thickness in R4 is non-zero, then it means &30, as in Draw_Fill. If R4 is zero, then &18 is the default, as the flattened and thickened path will have no interior in this case.

If the top bit of the fill style is set, this makes the Draw module plot the stroke all at once rather than one subpath at a time. This means the code will never double plot a pixel, but uses up much more temporary work-space.

The line thickness is as on Line thickness, with the following added restrictions. If the specified thickness is zero, Draw cannot deal with filling non-boundary exterior pixels and not filling boundary exterior pixels at the same time, ie fill bits 3 - 2 being 01. If the specified thickness is non-zero, Draw cannot deal with filling just the boundary pixels, ie fill bits 5 - 2 being 0110.

If you are using the printer driver, then it will intercept this SWI and affect its operation. In addition to the general comments in the Printer drivers, you should also be aware that most printer drivers will not pay any attention to bit 31 of the fill style - ie plot subpath by subpath or all at once (see above). Use Draw_ProcessPath to get around this problem by processing it before stroking.

Related SWIs

Draw_StrokePath

Related vectors

DrawV


Draw_StrokePath
(SWI &40706)

Like Draw_Stroke, except writes its output to a path

On entry

R0 = pointer to input path
R1 = pointer to output path, or 0 to calculate output buffer size
R2 = pointer to transformation matrix, or 0 for identity matrix
R3 = flatness, or 0 for default
R4 = line thickness, or 0 for default
R5 = pointer to line cap and join specification
R6 = pointer to dash pattern, or 0 for no dashes

On exit

R0 depends on entry value of R1

if R1 = 0, then R0 = calculated output buffer size
if R1 = pointer, then R0 = pointer to end of path marker in output path
R1 - R6 preserved

Interrupts

Interrupts are enabled
Fast interrupts are enabled

Processor Mode

Processor is in SVC mode

Re-entrancy

SWI is not re-entrant

Use

The input and output paths, matrix, flatness, line thickness, cap and join, and dash pattern are as specified in the Data structures.

This call acts exactly like a call to Draw_Stroke, except that it doesn't write its output to the VDU, but to an output path.

Related SWIs

Draw_Stroke

Related vectors

DrawV


Draw_FlattenPath
(SWI &40708)

Converts an input path into a flattened output path

On entry

R0 = pointer to input path
R1 = pointer to output path, or 0 to calculate output buffer size
R2 = flatness, or 0 for default

On exit

R0 depends on entry value of R1

if R1 = 0, then R0 = calculated output buffer size
if R1 = pointer, then R0 = pointer to end of path marker in output path
R1, R2 preserved

Interrupts

Interrupts are enabled
Fast interrupts are enabled

Processor Mode

Processor is in SVC mode

Re-entrancy

SWI is not re-entrant

Use

The input and output paths, and flatness are as specified in the Data structures.

This call acts like a subset of Draw_StrokePath. It will only flatten a path. This would be useful if you wanted to stroke a path multiple times and didn't want the speed penalty of flattening the path every time.

Related SWIs

Draw_StrokePath

Related vectors

DrawV


Draw_TransformPath
(SWI &4070A)

Converts an input path into a transformed output path

On entry

R0 = pointer to input path
R1 = pointer to output path, or 0 to overwrite the input path
R2 = pointer to transformation matrix, or 0 for identity matrix
R3 = 0

On exit

R0 depends on entry value of R1

if R1 = 0, then R0 is corrupted
if R1 = pointer, then R0 = pointer to end of path marker in output path
R1 - R3 preserved

Interrupts

Interrupts are enabled
Fast interrupts are enabled

Processor Mode

Processor is in SVC mode

Re-entrancy

SWI is not re-entrant

Use

The input and output paths, and matrix are as specified in the Data structures.

This call acts like a subset of Draw_StrokePath. It will only transform a path. This would be useful if you wanted to stroke a path multiple times and didn't want the speed penalty of transforming the path every time. It is also useful if you want to transform a path before dashing, thickening and so on, to avoid having the rounding errors from the latter operations magnified by the transformation.

Related SWIs

Draw_StrokePath

Related vectors

DrawV

Application Notes

Example of simple drawing

The test program that is shown here was devised to represent millimetres internally and scale them to be the correct size when drawn on a particular monitor. Because monitors are different sizes, and even the same model can be adjusted differently in terms of vertical and horizontal picture size, this example would have to be adjusted to suit your particular setup.

This example also has a restriction on screen modes. It will only work on one where the screen is 1280 OS units by 1024 OS units - which most of the current modes are (but not, for example, 132 column modes). This corresponds to 327680 internal Draw units by 262144 internal Draw units.

The first thing to do is to fill the screen with a colour and measure the horizontal and vertical size in millimetres. For this test, the display area measured 210mm across by 160mm down.

Because of scaling limitations, we will work with a user scale of thousandths of millimetres. Thus, there are 210000 user units across and 160000 user units down.

The BASIC program described here is presented in a jumbled order so that the features are described and written one at a time. Once it is all typed in, then it will seem a lot more obvious.

Transformation matrix

The next step is to work out the scaling factors for the transformation matrix. Taking the horizontal size first, we start with 327680 internal Draw units = 210000 user units, giving 1.5604 internal Draw units per user unit. Vertically, 262144 internal Draw units = 160000 user units, giving 1.6384 internal Draw units per user unit.

These figures must now be converted to the Transform units used for scaling in the transformation matrix. The 32 bit Transform number is 216 times the actual value, since its fractional part is 16 bits long. So horizontally we want 216 × 1.5604, which is 102261 (&18F75), and vertically we want 216 × 1.6384, which is 107374 (&1A36E).

The transformation matrix is initialised as follows:

&00018F75 0 0
0 &0001A36E 0
0 0 1

This could be calculated automatically, using the following BASIC code, which, whilst not the most efficient, is hopefully the clearest way of representing it:

 30 xsize = 210000 : ysize = 160000
 40 xscale% = (1280 * 256 / xsize) * &010000
 50 yscale% = (1024 * 256 / ysize) * &010000

After this, xscale% would be &00018F75 and yscale% would be &0001A36E, the values to place in the matrix. The matrix would be programmed as follows:

 20 DIM transform% 23
 60 transform%!0 = xscale%  :REM element a in the matrix
 70 transform%!4 = 0        :REM element b
 80 transform%!8 = 0        :REM element c
 90 transform%!12 = yscale% :REM element d
100 transform%!16 = 0       :REM element e
110 transform%!20 = 0       :REM element f

Important

It is important to remember that, whilst this example is using thousandths of millimetres as its internal coordinate system, they could be anything within the valid limits. Draw is not affected by what they are. Using the technique described above, any valid units can be used. We used 210000 by 160000 user units for our scale; it could be 500000 by 350000 or 654363 by 314159 or whatever. This program will work with all valid scales, simply by changing the definitions of xsize and ysize.

Creating the path

In order to create the path, this simple program uses a procedure to put a single word into the path and advance the pointer. In a large application, it would be a good idea to write individual routines to generate each element type, because this technique would become tedious in a large program.

This preamble defines what needs to be at the start of the program. Notice that line 20 overwrites the earlier definition.

 10 pathlength% = 256
 20 DIM path% pathlength% - 1, transform% 23
160 pathptr% = 0 :REM Initialise the pointer

Later on in the program would be the procedure to add a word to the path:

320 END
330 DEF PROCadd(value%)
340 IF pathptr%+4 > pathlength% THEN ERROR 0,"Insufficient path buffer"
350 path%!pathptr% = value%
360 pathptr% += 4
370 ENDPROC

The simple path shown here generates a rectangle with no bottom line. It is 90mm by 40mm and offset by 80mm in the x and y axes from the origin.

170 PROCadd(2) : PROCadd(80000) : PROCadd(80000)   :REM Move to start
180 PROCadd(8) : PROCadd(80000) : PROCadd(120000)  :REM Draw
190 PROCadd(8) : PROCadd(170000) : PROCadd(120000)
200 PROCadd(8) : PROCadd(170000) : PROCadd(80000)
250 PROCadd(4)                                     : REM Close the subpath. PROCadd(5) would close the rectangle
260 PROCadd(0) : PROCadd(pathlength%-pathptr%-4)   :REM End path

Simple stroke

Once the path and the transformation matrix have been completed, all that remains is to set the graphics origin and stroke the path onto the screen.

270 VDU 29,0;0;
280 SYS "Draw_Stroke",path%,0,transform%,0,0,0,0

Translation

Another matrix operation that can be performed is translation, or moving. Remember that the parameters in the matrix are in internal Draw coordinates, not the millimetres used in this example as user coordinates. If you want to translate in OS coordinates, then the translation must be multiplied by 256.

In this example, we are going to re-stroke the path, translated 60 OS units in x and -100 OS units in y.

290 transform%!16 = 60<<8
300 transform%!20 = -100<<8
310 SYS "Draw_Stroke",path%,0,transform%,0,0,0,0

You will now see two versions of the path, the new one 100 OS units lower and 60 OS units shifted to the right.

Similarly, the matrix may be modified to rotate the path. If you aren't sure how to do this, then see any mathematical text on matrix arithmetic.

Curves

In order to add a curve to the path, we will add a new subpath to the section that creates the path. This curve draws an alpha shape. Note that element type 2 implicitly closes the initial subpath:

210 PROCadd(2) : PROCadd(50000) : PROCadd(50000)   :REM x1, y1
220 PROCadd(6) : PROCadd(80000) : PROCadd(80000)   :REM x2, y2
230 PROCadd(85000) : PROCadd(30000)                :REM x3, y3
240 PROCadd(50000) : PROCadd(60000)                :REM x4, y4

Whilst the flatness can be left at its default value, this shows how the stroke commands can be changed to set the flatness to a sensible value. 640 is used because this program was run in a 640 pixel mode.

280 SYS "Draw_Stroke",path%,0,transform%,xsize/640,0,0,0
310 SYS "Draw_Stroke",path%,0,transform%,xsize/640,0,0,0

Line thickness

To make the lines shown thicker than the default, it is necessary to specify a thickness and also the joins and caps block. Notice that line 20 has been changed to allocate space for the joins and caps block. We will use round caps and bevelled joints.

 20 DIM path% pathlength%-1, transform% 23, joinsandcaps% 15
120 joinsandcaps%!0 = &010102
130 joinsandcaps%!4 = 0
140 joinsandcaps%!8 = 0
150 joinsandcaps%!12 = 0

Now all that remains is to change the stroke commands to specify a thickness and point to the block just specified. For this example we will make the first stroke 5000 units (5mm) thick and the second one half that:

280 SYS "Draw_Stroke",path%,0,transform%,xsize/640, 5000,joinsandcaps%,0
310 SYS "Draw_Stroke",path%,0,transform%,xsize/640, 2500,joinsandcaps%,0

Plainly, there are many more features that could be added to this program. But you should have the idea now of how it fits together and be able to experiment for yourself.

This edition Copyright © 3QD Developments Ltd 2015
Last Edit: Tue,03 Nov 2015