The Gfx Howto

This document describes typical usage patterns of the Gfx library, organized as a hierarchical sets of questions and answers. Its goal is to serve as an easily accessable resource on how to use Gfx in practice. I've tried to come up with questions (and answers) that might arise in everyday life. If you cannot find your question or a satisfying answer, suggest an update to oswald@acm.org.
  1. Gfx
  2. Contexts
  3. Vector Graphics
  4. Graphical Attributes
  5. Text
  6. Coordinate Systems
  7. Images
  8. Clipping
  9. Paths
  10. What next?

  1. Gfx
    1. What is Gfx?
    2. Who uses Gfx?
    3. What can I use it for?
    4. Where do I get Gfx?
    5. Does Gfx require any other packages or files?
    6. Is there any example code available?
  2. Contexts
    1. What is a context?
    2. What kinds of contexts do exist?
    3. How do I create a context for drawing to the screen?
    4. How do I create a context for drawing to a bitmap?
    5. How do I create a context for printing my graphics?
    6. How do I create Postscript and EPS files?
  3. Vector Graphics
    1. How do I draw a line?
    2. How do I draw a rectangle?
    3. How do I draw circles and ellipses?
    4. How do I draw an arc?
    5. What are paths?
    6. What are subpaths and why are they needed?
    7. How do I specify a path?
    8. How do I render closed paths correcly?
    9. What are Enter and Exit for?
    10. Why do arcs have so many parameters?
    11. How are self-intersecting paths treated?
    12. What path modes do exist?
    13. What can I use recorded paths for?
  4. Graphical Attributes
    1. How do I set the current stroke and fill colors?
    2. How do I change the current stroke and fill patterns?
    3. How do I draw dashed curves?
    4. How do I change the current line width?
    5. How do I change the current line cap style?
    6. How do I change the current line join style?
    7. How do I control the quality of arc and bezier approximation?
    8. Why doesn't Gfx let me change attributes within a path?
    9. How do I preserve all attributes and restore them later?
  5. Text
    1. How do I display a string?
    2. How do I change the current font?
    3. How do I change the current text color?
    4. Can I convert characters to paths?
    5. How can I center a caption?
    6. Can I use my huge collection of TrueType fonts?
    7. How about my Type1 fonts?
  6. Coordinate Systems
    1. Where is the coordinate origin of a context?
    2. How can I move the current coordinate origin?
    3. What is the default unit of a context?
    4. How do I scale the current coordinate system?
    5. How do I rotate the current coordinate system?
    6. Can I specify other transformations to the current coordinate system?
    7. How can I undo the changes I've made to the current coordinate system?
    8. How can I return to the original coordinate system?
    9. What are the dimensions of a device pixel?
  7. Images
    1. Why can't I just draw Oberon pictures?
    2. What is the Images package? Where do I get it?
    3. How do I draw an image?
    4. What is a filter?
    5. Can I define custom filters?
    6. I'd still like to use existing Oberon pictures. How?
    7. And while we're at it, how about Oberon patterns?
  8. Clipping
    1. What is the initial clipping region of a context?
    2. How do I change a context's default clipping region?
    3. How do I convert a gadget mask to a clipping region?
    4. How can I change the current clipping region?
    5. How can I undo the changes I've made to the current clipping region?
    6. How can I return the the clipping region to its initial state?
  9. Paths
    1. How can I access the elements in the current path?
    2. How to calculate bounding box and length of path?
    3. How can I modify a recorded path?
    4. How to render an edited path?
  10. What next?
    1. Are there other sources of information?
    2. Who do I report bugs and suggestions to?

Gfx

What is Gfx?

Gfx is a set of modules for rendering two-dimensional graphics using high-level operations. Applications use procedures operating on a context structure to describe the shapes that they wish to draw. The context fulfills these requests e.g. by modifying pixels on a raster display or by appending text to a page description that is being gathered in a file. Gfx currently runs on Oberon System 3.

Who uses Gfx?

The first application to use Gfx was Leonardo, the graphics editor provided as part of the System 3 distribution. In fact, Gfx was developed as an integral part of Leonardo but is now also available to other applications.

Another project using Gfx is a PDF viewer that has been developed by Marcel Bösiger during a semester project. Kaspar Fischer should be finishing his dvi file viewer in spring 2000.

What can I use it for?

Producing graphical output in a device independent manner. Drawing complex vector graphics. Drawing scaled and rotated bitmaps and text. Gfx should be suitable for producing any non-trivial graphical output.

Where do I get Gfx?

Since Leonardo is part of all System 3 distributions, you might already have Gfx installed. The most current release is available at the ETH FTP server.

Does Gfx require any other packages or files?

Gfx requires that the Images package is already installed (which used to be a part of Gfx in earlier releases). Apart from that, Gfx runs on top of the basic Oberon system. However, you might consider installing the Metafont packages containing outlines for Oberon fonts and the OpenType package for Oberon System 3 as well to take advantage of all features provided by Gfx.

Is there any example code available?

The Gfx distribution contains two modules which will usually never be imported by clients.

Contexts

What is a context?

When speaking about Gfx, a context is an abstract heap object of type Gfx.Context. Contexts maintain a set of state variables and a set of methods for changing these state variables and for rendering graphical shapes. To get some physical output you need to instance a concrete extension of Gfx.Context.

What kinds of contexts do exist?

The current distribution includes the following concrete context types:

How do I create a context for drawing to the screen?

  VAR ctxt: GfxDisplay.Context;
  NEW(ctxt); GfxDisplay.Init(ctxt, llx, lly, urx, ury);

How do I create a context for drawing to a bitmap?

  VAR ctxt: GfxBuffer.Context; img: Images.Image;
  NEW(img); Images.Create(img, width, height, Images.DisplayFormat);
  NEW(ctxt); GfxBuffer.Init(ctxt, img);

How do I create a context for printing my graphics?

  VAR ctxt: GfxPrinter.Context;
  Printer.Open(...);
  NEW(ctxt); GfxPrinter.Init(ctxt);

How do I create Postscript and EPS files?

  VAR ctxt: GfxPS.Context;
  NEW(ctxt);
  
  (* Postscript file: *)
  GfxPS.Init(ctxt, FALSE, FALSE, GfxPS.A4W, GfxPS.A4H, border, border, border, border, 600);
  GfxPS.Open(ctxt, Files.New("out.ps"));
  ....
  GfxPS.ShowPage(ctxt);
  GfxPS.Close(ctxt)
  
  (* EPS file: *)
  GfxPS.InitEPS(ctxt, FALSE, 600);
  GfxPS.Open(ctxt, Files.New("out.eps"));
  ...
  GfxPS.Close(ctxt)

Vector Graphics

How do I draw a line?

The easiest way to stroke a line from (x0, y0) to (x1, y1) is

  Gfx.DrawLine(ctxt, x0, y0, x1, y1, {Gfx.Stroke})

How do I draw a rectangle?

To stroke a rectangle, call

  Gfx.DrawRect(ctxt, x0, y0, x1, y1, {Gfx.Stroke})

with (x0, y0) and (x1, y1) marking two opposite corners of the rectangle. For drawing a filled rectangle, set the mode parameter to Gfx.Fill. You can include both Gfx.Stroke and Gfx.Fill in the mode parameter, in which case the rectangle will be first filled and then outlined.

How do I draw circles and ellipses?

To stroke a circle or an axis-aligned ellipse, call one of

  Gfx.DrawCircle(ctxt, x, y, r, {Gfx.Stroke})
  Gfx.DrawEllipse(ctxt, x, y, rx, ry, {Gfx.Stroke})

where the center of the circle or ellipse is at (x, y), the circle radius is r and the ellipse half-radii are rx and ry. If you want the circle or ellipse to be filled then include Gfx.Fill in the mode parameter.

How do I draw an arc?

Use

  Gfx.DrawArc(ctxt, x, y, rx, ry, start, end, {Gfx.Stroke})

(x, y) determines the center of an ellipse, rx is its horizontal radius, and ry its vertical radius (if both are equal, you get a circular arc). start and end are the angles of the start and end point of the arc, measured counterclockwise from the positive x axis in radians.

What are paths?

Paths are generalizations of primitive shapes such as lines, rectangles, circles, and ellipses. With paths you can describe triangles, rings, etc. A path consists of any number of subpaths, each of which in turn consisting of a connected sequence of lines, elliptical arcs and cubic Bézier curves. You can stroke a path, fill its interior, or do both at the same time. You can even restrict future rendering operations to the interior of a path or visit a recorded path to construct a new path.

What are subpaths and why are they needed?

A subpath is a connected sequence of lines, elliptical arcs and cubic Bézier curves. It starts at one point (its entry point) and ends at another (its exit point). Entry and exit point may of course be coincident. Although most paths consists of exactly one subpath, shapes that include holes can only be specified by several subpaths. E.g. a ring is most easily specified by a subpath describing an outer ring (consisting of a 360° counter-clockwise arc) and another subpath describing an inner ring (consisting of a 360° clockwise arc with smaller radius).

How do I specify a path?

A line could be rendered using

  Gfx.Begin(ctxt, {Gfx.Stroke});
  Gfx.MoveTo(ctxt, x0, y0);
  Gfx.LineTo(ctxt, x1, y1);
  Gfx.End(ctxt);

It's important to know that first you have to begin a current path in the rendering mode of your choice (in our case to stroke the path). Then you can repeatedly render subpaths by entering a subpath (using Gfx.MoveTo) and appending curve elements to it. The end point of each curve element becomes the new current point of the path and serves as the starting point of the next element. Don't forget to end the current path or some pending draw operations might never be executed.

How do I render closed paths correctly?

Rendering e.g. a triangle is best done like this:

  Gfx.Begin(ctxt, {Gfx.Fill, Gfx.Stroke});
  Gfx.MoveTo(ctxt, 0, 0);
  Gfx.LineTo(ctxt, 100, 0);
  Gfx.LineTo(ctxt, 50, 70);
  Gfx.Close(ctxt);
  Gfx.End(ctxt);

The directive to close the path has two purposes: first, it appends a line connecting the current point with the subpath's first point if necessary. Second, it renders a line join at the common first/last point instead of two line caps. This guarantees a smooth transition at that point.

What are Enter and Exit for?

Enter and Exit provide an alternative method for rendering closed paths. Enter is similar to MoveTo but expects an additional vector argument which indicates the direction of the last line segment in the subpath (which must therefore be known in advance). Instead of closing the subpath, it is terminated with Exit. The vector argument in this case is the direction of the first line. The triangle from the above example could therefore also be specified like this:

  Gfx.Begin(ctxt, {Gfx.Fill, Gfx.Stroke});
  Gfx.Enter(ctxt, 0, 0, -50, -70);
  Gfx.LineTo(ctxt, 100, 0);
  Gfx.LineTo(ctxt, 50, 70);
  Gfx.LineTo(ctxt, 0, 0);
  Gfx.Exit(ctxt, 100, 0);
  Gfx.End(ctxt);

The motivation for having Enter and Exit is that they allow to exit a closed subpath at some arbitrary point, render something else, and re-enter the subpath at the same point later. This additional flexibility is very convenient for some special applications.

Why do arcs have so many parameters?

The huge number of parameters for elliptic arcs might be scary at first but you'll find that they offer a maximum of flexibility. The three points (x0, y0), (x1, y1) and (x2, y2) define an ellipse having its center at (x0, y0). The other two points are endpoints of so called conjugate diameter pairs. Assuming (x0, y0) = (0, 0), if (x1, y1) = (A sin(t), B sin(t + d)) then (x2, y2) = (A cos(t), B cos(t + d)). Another (maybe a bit more comprehensive) way of explaining conjugate diameter pairs is to imagine a parallelogram centered at (x0, y0) and spanned by the vectors from (x0, y0) to (x1, y1) and (x2, y2). The ellipse fits into the parallelogram and touches it in the middle of its sides, two of which are located at (x1, y1) and (x2, y2)..

If the current point of the path is not on the ellipse, a line from the current point to its projection onto the ellipse boundary is rendered. From the projection of the current point on the ellipse boundary, the ellipse is traversed until the projection of the end coordinates onto the ellipse is reached. If the end point does not lie on the ellipse, a line is drawn from the current point on the ellipse to the end point.

If the cross product of the conjugate diameter pair vectors is positive, the arc is traversed in clockwise order, if it is negative, the traversal is in counter-clockwise order.

Example 1: a unit circle

  Gfx.Enter(ctxt, 1, 0, 0, 1);
  Gfx.ArcTo(ctxt, 1, 0, 0, 0, 1, 0, 0, 1);
  Gfx.Exit(ctxt, 0, 1);

Example 2: a semi-ellipse from (-2, 0) to (2, 0) through (0, 1) (note that it has to run counter-clockwise):

  Gfx.MoveTo(ctxt, -2, 0);
  Gfx.ArcTo(ctxt, 2, 0, 0, 0, -2, 0, 0, 1);

Example 3: a circular arc on the unit circle from 45° to 135°:

  Gfx.MoveTo(ctxt, 0.7071, 0.7071);
  Gfx.ArcTo(ctxt, -0.7071, 0.7071, 0, 0, 1, 0, 0, 1);

How are self-intersecting paths treated?

When using self-intersecting paths for filling and clipping, it's not inherently clear which points are part of the path interior and which aren't. Imagine drawing a ray originating at a point in question and examine its intersections with the path. Value each intersection as +1 if the path crosses the ray from right to left and as -1 if the path crosses the ray from left to right.

What path modes do exist?

When beginning a new path with Gfx.Begin or when rendering the current path with Gfx.Render, a mode parameter describes how the path should be rendered. The drawing mode is a set which contains at least one of the following elements:

What can I use recorded paths for?

Since you usually use Gfx because you want to render graphics, it may not be obvious why you should be content with just recording paths. However, it is sometimes necessary to modify the current path before calling Gfx.Render to render it.

Graphical Attributes

How do I set the current stroke and fill colors?

Gfx manages two current colors at once, one for stroking and one for filling paths, which are both set to black whenever Gfx.Reset is called. To set the current stroke color to red and the current fill color to white, call

  Gfx.SetStrokeColor(ctxt, Gfx.Red);
  Gfx.SetFillColor(ctxt, Gfx.White);

For convenience, the following colors are already defined in Gfx: black, white, red, green, blue, cyan, magenta, yellow and three shades of grey. Of course other colors may be defined as well, since a color is represented as a record type with three fields holding values for red, green and blue.

How do I change the current stroke and fill patterns?

A Gfx pattern is defined by an image bitmap and a pin point for anchoring the pattern origin. A pattern must be instantiated with Gfx.NewPattern, please don't initialize patterns yourself. After a pattern has been instantiated, you can pass it to Gfx.SetStrokePattern or Gfx.SetFillPattern. To turn pattern stroking and filling off again, pass a NIL value (which is also the default value for both patterns). If the image used for defining the pattern doesn't contain any color components (i.e. is a pure alpha bitmap), the current stroke and fill colors are used. Patterns do not undergo any transformations, they are always drawn in the default coordinate system.

The following example shows how to fill a rectangle in green using the standard Oberon pattern 2 (consisting of many small dots):

  VAR img: Images.Image; pat: Gfx.Pattern;
  NEW(img); PictImages.PatternToImage(2, img);
  pat := Gfx.NewPattern(ctxt, img, 0, 0);
  Gfx.SetFillColor(ctxt, Gfx.Green);
  Gfx.SetFillPattern(ctxt, pat);
  Gfx.DrawRect(ctxt, 10, 10, 80, 60, {Gfx.Fill});

How do I draw dashed curves?

All curves are rendered in one continuous stroke by default. You can change this by passing a dash pattern to Gfx.SetDashPattern. A dash pattern consists of several pairs of lengths. For each pair i, a dash of length on[i] is rendered, then a distance of off[i] is skipped. When i reaches the size of the pattern, it is reset to zero. Another parameter (called the dash phase) is used as an initial offset into the pattern at the entry point of a subpath, which can be useful for slightly adjusting an already defined pattern.

Let's define a simple dash-dot pattern:

  VAR on, off: ARRAY 2 OF REAL;
  on[0] := 4; off[0] := 2; on[1] := 1; off[1] := 2;
  Gfx.SetDashPattern(ctxt, on, off, 2, 0);

Here follows a simple dash pattern that starts in the middle of a dash:

  VAR onoff: ARRAY 2 OF REAL;
  onoff[0] := 10;
  Gfx.SetDashPattern(ctxt, onoff, onoff, 1, 5);

Dash lengths and phase are in current user coordinates, i.e. they are subject to the current transformation.

How do I change the current line width?

Use Gfx.SetLineWidth to set the line width to any positive real number. The current transformation at the time a path is begun is applied to the line width before any rendering. If the resulting width is smaller than a device pixel, a hairline is drawn, which is the thinnest line that can be rendered on any device. To render lines as thinly as possible you should therefore set the line width to zero. However, to achieve consistent line widths on all output devices you should choose a non-zero value. The default line width is one, which corresponds to one display pixel in the default coordinate system.

When the current line width exceeds one and a half device pixels, curves are rendered by offsetting the curve by half the line width from each side of the path and filling the resulting area (or any equivalent method that a specific implementation chooses). To achieve a smooth appearance, a line join is drawn wherever two consecutive line segments meet at an angle. In addition, a line cap is drawn at the first and last point of a subpath, except for closed subpaths where another line join is drawn.

How do I change the current line cap style?

Use Gfx.SetCapStyle to set the current cap style. There are three predefined cap styles in Gfx: Contexts use butt caps by default and whenever they have been reset.

How do I change the current line join style?

Use Gfx.SetJoinStyle. Like with cap styles, Gfx offers a selection of predefined join styles: Miter joins look natural and can be rendered reasonably quick and are therefore chosen as default join style. However, if the angle between two lines becomes very small, the intersection of their outer edges can be very far away from the original path. It would be better to use a threshold angle and render bevel joins in these cases. For this reason, contexts maintain a style limit attribute. The maximal distance between any point rendered by a style and the original path must not exceed half the line width times the style limit. The style limit can be set with Gfx.SetStyleLimit and is by default set to 5.

How do I control the quality of arc and bezier approximation?

Except for a few special cases, rendering thick and/or dashed arcs and Bézier curves is very complicated. Most concrete contexts therefore choose to approximate these curves with straight lines, which are far easier to handle. The curves are usually subdivided until each part is close enough to a straight line, where "close enough" means that the maximal distance from any point on the original curve to the linear approximation is smaller than a given limit. This limit is stored in the flatness attribute of a context and can be modified with Gfx.SetFlatness. Unlike most other attributes, the current flatness is measured in device pixels and is independent of the current transformation matrix. The default flatness is set to one device pixel.

Why doesn't Gfx let me change attributes within a path?

Between Gfx.Begin and Gfx.End, an attempt to set any context attribute causes a run-time error. Gfx doesn't allow you to change graphical attributes within a path because most of them depend on the current transformation matrix (see Coordinate Systems), which can be changed while inside a path. Now combine this with the fact that a context may render individual path elements whenever it sees fit, which may be as soon as a curve is specified, when the path is terminated, or any moment inbetween. It is obviously impossible to synchronize changing attributes and output operations. Context attributes therefore mustn't be modified during rendering.

How do I preserve all attributes and restore them later?

Like this:

  VAR state: Gfx.State;
  ...
  Gfx.Save(ctxt, {Gfx.all}, state);
  ....
  Gfx.Restore(ctxt, state);
Instead of saving all attributes, an arbitrary subset can be used. The corresponding elements are exported as constants from Gfx.

Text

How do I display a string?

The most convenient way is to call

  Gfx.DrawStringAt(ctxt, x, y, "Hello");

You can then immediately append another string to the one you've just displayed with

  Gfx.DrawString(ctxt, " World!");

Both procedures will display the string using the current fill color and the current font.

How do I change the current font?

To set the current font family to "Oberon", style to "Bold" at size 12 call

  Gfx.SetFontName(ctxt, "Oberon-Bold", 12);

You can also create a font instance which is derived from an existing font by applying an arbitrary transformation matrix. As an example, let's simulate a "Oberon-BoldSlanted" font (which doesn't exist) by shearing an "Oberon-Bold" font, using size 20.

  GfxMatrix.Init(mat, 1, 0, 0.25, 1, 0, 0);	(* shear matrix *)
  Gfx.SetFont(ctxt, GfxFonts.Open("Oberon-Bold", 20, mat));

While Gfx does allow you to specify any transformed font, these fonts are likely to look too ugly to be useful say for general text display. Only if a raster font of the correct size is available can high quality be expected, otherwise the character patterns of an existing raster fonts are scaled to the correct size or an outline font (if available) is used to create character patterns dynamically.

How do I change the current text color?

Since Gfx.DrawString uses the current fill color, set the current text color by calling Gfx.SetFillColor with the appropriate color value.

Can I convert characters to paths?

Yes, if you have the corresponding outline fonts installed. You can download outlines for Oberon metafonts. Once these outline fonts are available on your system, Gfx will use them automatically if you request a font instance for which no matching Oberon raster font is found or if you explicitly request to use character outlines inside a path by calling either of Gfx.ShowAt or Gfx.Show. These work like Gfx.DrawStringAt and Gfx.DrawString but can only be called within a path. Instead of always using Gfx.Fill mode, however, they render characters of a string in the current drawing mode of the path. That means that you can stroke character outlines, draw patterned characters or use character shapes as clipping regions.

The following example fills the characters of the string "Bart" with a pattern of "Bart.Pict" bitmaps:

  VAR img: Images.Image; done: BOOLEAN;
  NEW(img); Images.Load(img, "Bart.Pict", done); ASSERT(done, 110);
  Gfx.SetFillPattern(ctxt, Gfx.NewPattern(ctxt, img, 0, 0));
  Gfx.SetFontName(ctxt, "Oberon-Bold", 64);
  Gfx.DrawStringAt(ctxt, 100, 100, "Bart");

How can I center a caption?

Use Gfx.GetStringWidth to find out how the current point would be advanced if a string were drawn and displace the string by half this vector. If you think it's odd that Gfx.GetStringWidth returns two values, remember that fonts can be transformed and their direction of advancement may no longer be horizontal.

Can I use my huge collection of TrueType fonts?

Yes, if you have the OpenType package for Oberon installed and compile the module GfxOType in the Gfx distribution. Also make sure that the following line is part of your Oberon.Text:

  { FontFormats = { GfxOType.Install }}

You should then be able to use TrueType fonts just like regular Oberon fonts from within Gfx.

How about my Type1 fonts?

Unfortunately there is no support for Type1 fonts in Gfx at the moment. If you have a working Type1 rasterizer for Oberon, please don't hesitate and make it available to Gfx and OType.

Coordinate Systems

Where is the coordinate origin of a context?

Usually in the lower left corner of the drawable area.

How can I move the current coordinate origin?

It is often convenient to move the origin of the coordinate system somewhere else, e.g. if you plan to describe a graphic that is symmetric around an origin and therefore uses both positive and negative coordinate values.

  Gfx.Translate(ctxt, dx, dy);

displaces the coordinate origin by the vector (dx, dy) for all following rendering operations. This is effected by prepending a translation matrix to the current transformation matrix of the context.

What is the default unit of a context?

The default unit of a context corresponds to an Oberon Display unit, which corresponds to 1/91.44 of an inch.

How do I scale the current coordinate system?

By calling one of

  Gfx.Scale(ctxt, sx, sy);
  Gfx.ScaleAt(ctxt, sx, sy, x, y);

all following operations will use a scaled coordinate system. The second call uses the given point instead of (0, 0) as an invariant origin for the transformation.

How do I rotate the current coordinate system?

With one of

  Gfx.Rotate(ctxt, sin, cos);
  Gfx.RotateAt(ctxt, sin, cos, x, y);

E.g. a counter-clockwise rotation by 90° has sin=1 and cos=0. Gfx expects sine and cosine of an angle and not the value of the angle to avoid calculating sine and cosine of trivial angles.

Can I specify other transformations to the current coordinate system?

Yes, if you can describe the transformation with a three-row, two-column matrix. This includes any combination of translation, scaling, rotation and shearing. Prepend this matrix to the current transformation matrix with

  Gfx.Concat(ctxt, mat);

and it affects all following operations. The GfxMatrix module contains several operations for dealing with matrices.

How can I undo the changes I've made to the current coordinate system?

By saving the current transformation matrix before modifying the coordinate system and later restoring it:

  VAR save: GfxMatrix.Matrix;
  save := ctxt.ctm;
  ...
  Gfx.SetCTM(ctxt, save);

How can I return to the original coordinate system?

By calling Gfx.ResetCTM or Gfx.Reset, where the latter also resets the clip region and all context attributes. If you didn't set up the context yourself, you should rather save and restore the CTM instead because your caller might have modified the coordinate system before calling you.

What are the dimensions of a device pixel?

The dimensions of a device pixel in the current coordinate system can be found by inverting the current CTM and applying it to a unit vector.

Images

Why can't I just draw Oberon pictures?

For a long time, Gfx has indeed been working with regular Oberon pictures. However, they only support indexed image formats with a depth of eight bits, which proved to be too restrictive, especially in the context of filtered image transformations. Now Gfx is based on the Images package, extending it by a module GfxImages for transforming images.

What is the Images package? Where do I get it?

Images lets you load and store image files of up to 32 bit depth in various file formats and provides basic image processing capabilities. It can be downloaded from ftp.inf.ethz.ch. Be sure to read the accompanying Images.Tool and register image file extensions in Oberon.Text.

How do I draw an image?

  Gfx.DrawImageAt(ctxt, x, y, image, GfxImages.NoFilter);
The lower left corner of the image is positioned at the (x, y) and the current transformation matrix is applied to the image. If the current transformation has been modified and no longer corresponds to the default coordinate system, the filter parameter controls the quality of the image transformation.

What is a filter?

A filter is an extension of an Images transfer mode. In addition to compositing source and destination pixels, it offers procedure variables for shifting and scaling pixel rows and columns, which makes them suitable for implementing image transformations. The problem with image transformations is that fast algorithms suffer from poor quality and that smarter algorithms producing better quality are much slower. Filters allow GfxImages to leave that decision up to its callers. It offers two sets of filter procedures and two predefined filters exported as global variables:

Can I define custom filters?

Yes. Call GfxImages.InitFilter with a compositing operator and appropriate shift and scale procedures and you have your custom filter. You might want to take a look at the implementation of the predefined filter procedures first, though.

I'd still like to use existing Oberon pictures. How?

If you have an Oberon picture stored in a file, you can directly load it into an image using the following code:

  VAR img: Images.Image; done: BOOLEAN;
  NEW(img); Images.Load(img, "MyImage.Pict", done);
  IF done THEN ... END;

Alternatively, if the picture is already available as a structure in memory, it can be converted to an image like this:

  VAR img: Images.Image;
  NEW(img); PictImages.PictToImage(pict, img);

Bear in mind that the picture contents are copied. If you later need to convert the image back to a picture, you can do so with PictImages.ImageToPict. Unless the image is in indexed format and has its own custom palette, the default fixed palette defined in module Images is used for initializing the picture palette.

And while we're at it, how about Oberon patterns?

Studying the definition of PictImages reveals a procedure PatternToImage which converts an Oberon pattern to an image in Images.A1 format. The reverse conversion from images to patterns is done with PictImages.ImageToPattern.

Clipping

What is the initial clipping region of a context?

This depends on the context type but usually the clipping region is a rectangle containing the drawable area of the context.

How do I change a context's default clipping region?

Many context modules export procedures for setting the default clipping region of a context to a rectangle or a given region. The default clipping region is the one that is reestablished when Gfx.ResetClip or Gfx.Reset is called.

How do I convert a gadget mask to a clipping region?

By enumerating the mask and adding the enumerated rectangles to the region:

  VAR R: GfxRegions.Region;
  
  PROCEDURE AddRect (x, y, w, h: INTEGER);
  BEGIN
    GfxRegions.AddRect(R, x, y, x + w, y + h)
  END AddRect;
  
  PROCEDURE MaskToRegion (mask: Display3.Mask; reg: GfxRegions.Region);
  BEGIN
    R := reg; GfxRegions.Clear(R);
    Display3.EnumRect(mask, mask.X, mask.Y, mask.W, mask.H, AddRect)
  END MaskToRegion;

How can I change the current clipping region?

The current clipping region of a context can be intersected with an arbitrary path by rendering the path in Gfx.Clip mode. The following code snippet restricts all following rendering operations to the interior of the letter "A" within a circle:

  Gfx.SetFontName(ctxt, "Oberon-Bold", 128);
  Gfx.Begin(ctxt, {Gfx.Clip});
  Gfx.ShowAt(ctxt, 100, 100, "A");
  Gfx.End(ctxt);
  Gfx.DrawCircle(ctxt, 150, 150, 50, {Gfx.Clip});

How can I undo the changes I've made to the current clipping region?

Save the current clipping region before you modify it and restore it later:

  VAR clip: Gfx.ClipArea;
  ...
  clip := Gfx.GetClip(ctxt);
  Gfx.Begin(ctxt, {Gfx.Clip}); ... ; Gfx.End(ctxt);
  ....
  Gfx.SetClip(ctxt, clip);

How can I return the the clipping region to its initial state?

By calling Gfx.RestoreClip.

Paths

How can I access the elements in the current path?

The module GfxPaths offers two methods for accessing the elements stored in a path. The first is to open a path scanner (GfxPaths.Scanner) on the path and advance from element to element. The second is to enumerate all path elements by passing an enumerator procedure to GfxPaths.Enumerate. The procedure is then called for each element in the path. While the enumeration method offers less control over the traversal, it can be used to visit flattened paths and elements or natural splines, which cannot be stored in paths at all.

If you access the elements in the current path of a context, bear in mind that the drawing mode must include Gfx.Record and that the current context matrix has been applied to all coordinates.

How to calculate bounding box and length of path?

The module GfxPaths offers useful procedures for finding out details about a path. GfxPaths.GetBox returns the bounding box for all elements in the path and GfxPaths.Length returns the length of a (flattened) path.

How can I modify a recorded path?

Due to the internal storage structure of paths, there are no procedures for altering individual path elements once a path is built although you can apply a transformation matrix to a path or reverse it. Other modifications can be achieved by constructing a temporary path and copying back its contents to the original path afterwards.

How to render an edited path?

Gfx.DrawPath is one possibility. The current path can be drawn with Gfx.Render.

What next?

Are there other sources of information?

Most of the topics touched in this document are presented with a bit more structure in the Gfx Overview. Apart from that, most features are also discussed where they are defined in a module interface. For the deepest insight, you should look directly at the source code, which is part of the distribution. If all this doesn't help, send inquiries and suggestions to Erich Oswald.

Who do I report bugs and suggestions to?

Directly to the author of Gfx, Erich Oswald.
Erich Oswald Mar 2000