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@inf.ethz.ch.
  1. Gfx
  2. Contexts
  3. Vector Graphics
  4. Graphical Attributes
  5. Text
  6. Coordinate Systems
  7. Bitmaps
  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. What are paths?
    5. What are subpaths and why are they needed?
    6. How do I specify a path?
    7. What's the difference between Enter/Exit and Enter0/Exit0?
    8. What to do if I don't know initial and terminal directions of a closed subpath?
    9. Why do arcs have so many parameters?
    10. How are self-intersecting paths treated?
    11. What path modes do exist?
    12. 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?
  5. Text
    1. How do I display a string?
    2. How can I set the current point manually?
    3. How do I change the current font?
    4. How do I change the current text color?
    5. Can I convert characters to paths?
    6. How can I center a caption?
    7. Can I use my huge collection of TrueType fonts?
    8. 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. Bitmaps
    1. Why can't I just draw Oberon pictures?
    2. What are bitmap formats?
    3. Can I define my own bitmap formats?
    4. What is a blend operator?
    5. Can I define custom blend operators?
    6. What is a filter?
    7. And I can define custom filters, too?
    8. I'd still like to use existing Oberon pictures. How?
    9. And while we're at it, how about Oberon patterns?
    10. How can I integrate my images in XYZ format into Gfx?
  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?
    3. How can I contribute to Gfx?

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.

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 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.

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; map: GfxMaps.Map;
  NEW(map); GfxMaps.Create(map, width, height, format);
  NEW(ctxt); GfxBuffer.Init(ctxt, map);

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.

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 morph a path into something else.

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.Enter0(ctxt, x0, y0);
  Gfx.LineTo(ctxt, x1, y1);
  Gfx.Exit0(ctxt);
  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, appending curve elements to it and exiting it again. 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.

What's the difference between Enter/Exit and Enter0/Exit0?

The former are needed for properly rendering closed paths. They each expect an additional direction vector (dx, dy). When rendering closed paths (especially when rendering thick lines), supply the direction of the last subpath curve just before it gets to the entry/exit point to Enter and the direction of the first subpath curve just after the entry/exit point to Exit. As an example, let's render a unit square:
  Gfx.Begin(ctxt, {Gfx.Stroke});
  Gfx.Enter(ctxt, 0, 0, 0, -1);
  Gfx.LineTo(ctxt, 1, 0); Gfx.LineTo(ctxt, 1, 1); Gfx.LineTo(ctxt, 0, 1); Gfx.LineTo(ctxt, 0, 0);
  Gfx.Exit(ctxt, 0, 0, 1, 0);
  Gfx.End(ctxt);
Because the last line goes from (0, 1) to (0, 0), its direction is (0, -1), which we pass to Enter. Similarly, the first line goes from (0, 0) to (1, 0), therefore we pass its direction (1, 0) to Exit. Specifying closed subpaths in this manner makes sure that there are no artifacts caused by missing join styles at the shared entry/exit point.

As you might already have guessed, Enter0 and Exit0 are just abbreviated calls which set (dx, dy) to (0, 0), which indicates to Gfx that the current subpath is not closed.

What to do if I don't know initial and terminal directions of a closed subpath?

You'll find that most of the time it's rather easy to figure out the direction vectors needed for Enter and Exit. If for once it isn't, you might try the following pattern:
  Gfx.Begin(ctxt, {Gfx.Record});
  Gfx.Enter0(ctxt, x, y); .... ; Gfx.Exit0(ctxt, x, y);
  Gfx.End(ctxt);
  GfxPaths.Close(ctxt.path);
  Gfx.Render(ctxt, {...});
What this does is record the path without direction vectors (as if it weren't closed) and then calling GfxPaths.Close, which automatically sets the correct directions at subpath ends if a subpath has a common entry/exit point. Then you can render the (now hopefully closed) path in the mode you originally wanted to render it.

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.Enter(ctxt, -2, 0, 0, 1);
  Gfx.ArcTo(ctxt, 2, 0, 0, 0, -2, 0, 0, 1);
  Gfx.Exit(ctxt, 0, -1);

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

  Gfx.Enter(ctxt, 0.7071, 0.7071, -1, 1);
  Gfx.ArcTo(ctxt, -0.7071, 0.7071, 0, 0, 1, 0, 0, 1);
  Gfx.Exit(ctxt, -1, -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 as a Gfx 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 bitmap 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 (not the color associated with the bitmap). 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 map: GfxMaps.Map; pat: Gfx.Pattern;
  NEW(map); GfxPictures.PatternToMap(2, map);
  pat := Gfx.NewPattern(ctxt, map, 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 numbers. The first number of each pair holds the length of a visible part (a dash), whereas the second holds the distance from the end of the current dash to the start of the next dash. 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 p: ARRAY 4 OF REAL;
  p[0] := 4; p[1] := 2; p[2] := 1; p[3] := 2;
  Gfx.SetDashPattern(ctxt, p, 4, 0);
Here follows a simple dash pattern that starts in the middle of a dash:
  VAR p: ARRAY 2 OF REAL;
  p[0] := 10; p[1] := 10;
  Gfx.SetDashPattern(ctxt, p, 2, 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 device pixel, 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). The question is now: what happens at the entry and exit points of a subpath? Answer: a line cap style is rendered. And what happens when there is a corner in a subpath because two consecutive lines have different directions? Answer: a line join style is drawn.

How do I change the current line cap style?

Use Gfx.SetCapStyle to set the current cap style. The current cap style is a procedure variable in the context that has a somewhat complicated interface. Fortunately, the most common kinds of cap style are already predefined in Gfx: Contexts use butt caps by default and whenever they have been reset. If you need to define a custom cap style, refer to the interface of module Gfx for details.

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.

As with cap styles you may define your own line join styles. However, a detailed description of the join style interface is beyond the scope of this document. You will gain further insight by studying the definition of Gfx.JoinStyle and the default implementations of the predefined styles.

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

Except for a few special cases, rendering 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 closed, or any moment inbetween. It is obviously impossible to synchronize changing attributes and output operations. Context attributes therefore mustn't be modified during rendering.

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 can I set the current point manually?

If you should ever need to set the current point manually, call
  Gfx.SetPoint(ctxt, x, y);

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-BoldItalic" 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));

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 outline fonts for Oberon metafonts here 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 ater a path has been begun. 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 map: GfxMaps.Map; done: BOOLEAN;
  NEW(map); GfxMaps.Load(map, "Bart.Pict", done); ASSERT(done, 110);
  Gfx.SetFillPattern(ctxt, Gfx.NewPattern(ctxt, map, 0, 0));
  Gfx.SetFontName(ctxt, "Oberon-Bold", 64);
  Gfx.Begin(ctxt, {Gfx.Fill});
  Gfx.ShowAt(ctxt, 100, 100, "Bart");
  Gfx.End(ctxt);

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.

Can I use my huge collection of TrueType fonts?

Yes, if you have installed the OpenType type for Oberon available for download here and compile the module GfxOType in the Gfx distribution. Also make sure that the following line is part of your Oberon.Text:
  { FontFormats = { TTF = 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?

In the lower left corner of the drawable area. This is often equal to (0, 0).

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 dots per 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 itself to make special cases more efficient.

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.

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 can be found by resetting the current transformation matrix, inverting the default CTM and applying it to a unit vector.

Bitmaps

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 bitmap formats with a depth of eight, which proved to be too restrictive, especially in the context of filtered bitmap transformations. So now there is a module GfxMaps, which is part of Gfx and offers a lot of advanced features.

What are bitmap formats?

Gfx bitmaps are not restricted to indexed formats. They can include components holding RGB information, palette index numbers, and alpha values. The number of bits used per pixel may vary as well. All information about what is stored in a pixel and how it is stored is gathered in a bitmap format. Several bitmap formats are predefined and are exported as global format variables from GfxMaps: It is strongly suggested that you use one of these predefined formats whenever you create a bitmap with GfxMaps.Create.

Can I define my own bitmap formats?

Answer: yes, you can, if you are willing to accept a small performance hit. Apart from information about which components are available and how many bits per pixel are used, a format also contains two procedures pack and unpack. They are supposed to store (pack) a pixel at a given address and to load (unpack) a pixel when called. Call GfxMaps.InitFormat to initialize all relevant fields of your custom format. By the way, the performance hit comes from the fact that the pack and unpack procedures are often bypassed for pixel transfers between predefined formats, which cannot be done for formats defined outside GfxMaps.

What is a blend operator?

A blend operator is a generalization of the mode parameter used by many Oberon Display procedures. It defines how source pixels affect destination pixels. Two very often used blend operators are exported as global variables from GfxMaps and should be used wherever appropriate:

Can I define custom blend operators?

Yes! Just declare a variable of type GfxMaps.BlendOp and set its blend procedure to a procedure implementing your blending scheme. The blend procedure gets a source and a destination pixel and must store its result in a result parameter. However, because GfxMaps uses optimized code for its predefined blend operators which bypasses calling the blend procedure, you may find that using your custom blend operator is slower than using one of the builtin operators.

What is a filter?

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

And I can define custom filters, too?

Yes. Call GfxMaps.InitFilter with a blend procedure 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 a Gfx map using the following code:
  VAR map: GfxMaps.Map; done: BOOLEAN;
  NEW(map); GfxMaps.Load(map, "MyImage.Pict", done);
  IF done THEN ... END;
Note: this only works if your Oberon.Text has been augmented by a line
  { ImageFormats = { Pict = GfxPictures.Install }}
Alternatively, if the picture is already available as a structure in memory, it can be converted to a Gfx bitmap like this:
  VAR map: GfxMaps.Map;
  NEW(map); GfxPictures.PictToMap(pict, map);
Bear in mind that the picture contents are copied. If you later need to convert the map back to a picture, you can do so with GfxPictures.MapToPict. Converting a Gfx map to a picture requires an additional palette parameter to select the set of colors that will be used in the picture. If the map is in GfxMaps.I8 format (e.g. because it is a converted picture), you should probably use the palette from the map format. If the picture needs to be displayed, consider using GfxMaps.DefaultPal which contains exactly the same colors as the Oberon display. Finally, if the source bitmap is in a hi-color or true-color format, you might want to calculate a palette that best represents the colors used in the bitmap with GfxMaps.ComputePalette and use this palette for the conversion.

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

Studying the definition of GfxPictures reveals a procedure PatternToMap which converts an Oberon pattern to a Gfx map in GfxMaps.A1 format. The conversion from Gfx bitmaps to Oberon patterns is not supported.

How can I integrate my images in XYZ format into Gfx?

Since Gfx bitmaps support so many different formats, it would be nice to be able to load and store image files in all the dozens of file formats that exist. Currently only Oberon picture files are supported, but it's easy to add other file formats if you follow these steps:
  1. Implement procedures for loading and storing a bitmap in the new file format.
  2. Add a command procedure (e.g. "Install") to your module which sets the global procedure variables GfxMaps.LoadProc and GfxMaps.StoreProc to your load and store procedures.
  3. Add a line for installing your command to your Oberon text:
      ImageFormats = {
        Pict = GfxPictures.Install
        Xyz = GfxXyz.Install
      }
    

Clipping

What is the initial clipping region of a context?

This depends on the context type but usually the clipping region is a rectangle around 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:
  Gfx.SaveClip(ctxt);
  Gfx.Begin(ctxt, {Gfx.Clip}); ... ; Gfx.End(ctxt);
  ....
  Gfx.RestoreClip(ctxt);

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 Gfx.Record mode must have been set 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 althouth 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. Other than 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 the Institute for Computer Systems at ETH Zürich.

Who do I report bugs and suggestions to?

Directly to the author of Gfx, Erich Oswald.

How can I contribute to Gfx?


Erich Oswald Apr 1999