2. Everything you really wanted to know about imaging
Simple images
Suppose all you want to do is display some simple images in black-and-white. The first thing to do is get a context (see "Device types" below). The next thing to do is to establish a convenient coordinate system. On a newly-created context, the coordinate system has the origin in the lower left-hand corner, x increases to the right and y increases upward. The initial units are meters. Meters have the advantage of having an internationally accepted definitions, but they are too big to be convenient for most applications. Let's say you prefer to talk in terms of inches. Then the first thing to do is to say
Imager.ScaleT[context, 0.0254];
since there are 0.0254 meters per inch. Everything you do from now on should be expressed in inches, until you change the transformation again. (Other things like rotations and non-uniform scaling can be done with transformations, but we are talking about the simple stuff now.)
If you did not create the context yourself, but obtained it from a window package, the initial coordinate system may be different. Refer to the window package documentation to find out the units.
One simple thing to make is a rectangle:
Imager.MaskRectangle[context, x, y, w, h];
makes a rectangle with its lower-left corner at (x, y), with width w and height h. Note that the procedures that actually display something usually begin with "Mask". This is because they follow the Interpress metaphor of pushing ink (the "current color") through a mask (in this case a rectangle).
Another simple thing to make is a circle. To do this you first have to make a
Trajectory:
t: Imager.Trajectory ← Imager.MoveTo[[x1, y1]].ArcTo[[x2, y2], [x1, y1]];
and then apply it:
Imager.MaskFill[context, t];
This will make a solid disk with the points (x1, y1) and (x2, y2) at opposite sides of its circumference. If you want to draw just the circumference of the circle, then say:
Imager.MaskStrokeClosed[context, t, w];
where w is the width you would like the stroke to be. (The Imager always tries to make unbroken strokes, so that using a stroke width of 0 will always give the thinnest possible stroke. This is probably not what you want on the highest-resolution devices, so it is better to ask for the width you want even if it is smaller than the pixel size on the device you are currently using.)
More complicated trajectories can be constructed using line segments or pieces of conics or third-degree parametric cubics. A simple example would be a triangle:
t: Imager.Trajectory ← Imager.MoveTo[[x1, y1]].LineTo[[x2, y2]].LineTo[[x3, y3]];
For MaskFill or MaskStrokeClosed you don't have to supply the final edge; the Trajectory is automatically closed off with a straight line. If you do not want the stroke to be closed, then use:
Imager.MaskStroke[context, t, w, round];
the last parameter tells what the ends of the stroke should look like. The other options are square (the default) and butt.
There is a special procedure for the common special case of a stroke consisting of a single straight line segment:
Imager.MaskVector[context, [x1, y1], [x2, y2], strokeWidth, strokeEnd];
Sometimes it is a pain to have to specify the stroke width, stroke ends, and other parameters every time a masking operation is called. These things may be recorded as part of the context's state, using procedure calls, e.g.,
Imager.SetStrokeWidth[context, 0.1];
Imager.SetStrokeEnd[context, round];
and now subsequent calls may let the extra arguments default to get these preset values.
Text is another popular thing to display. First you need to make a font
myFont: Imager.FONT ← Imager.MakeFont[name: "Xerox/Pressfonts/TimesRoman/MRR", size: 0.2];
The size of the font should be expressed in terms of the units associated with the context you plan to use the font with, in our case inches. So these letters will be of a size appropriate for a line spacing of 1/5 of an inch. The next thing to do is to set the font on the context:
Imager.SetFont[context, myFont];
and then set the
current position to say where you want the text to go:
Imager.SetXY[context, [x, y]];
and finally:
Imager.ShowCharacters[context, "This text is brought to you courtesy of the Imager"];
The second argument to ShowCharacters may be either a ROPE or a REF TEXT. When ShowCharacters is done, the current position is set to be at the end of the text just displayed, so more text can be added to the line just be doing another ShowCharacters, perhaps with a different font.
Do not be distressed if the first time you show some characters it takes a long time. The Imager is just retrieving the font definitions and scan-converting them for your current device; subsequent calls to ShowCharacters will be much faster.
If you get tired of black, you can use different colors:
myLightGray: Imager.Color ← Imager.MakeGray[0.3];
myDarkGray: Imager.Color ← Imager.MakeGray[0.7];
Imager.SetColor[context, Imager.black];
Imager.MaskRectangle[context, 1, 1, 1, 1];
Imager.SetColor[context, myDarkGray];
Imager.MaskRectangle[context, 1.2, 1.2, 1, 1];
Imager.SetColor[context, myDarkGray];
Imager.MaskRectangle[context, 1.4, 1.4, 1, 1];
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, 1.6, 1.6, 1, 1];
If you want to call some code that does some drawing and don't want it to mess up your carefully constructed state, use DoSaveAll:
{body: PROC = {DrawTerrificPicture[context]}; Imager.DoSaveAll[context, body]};
Use DoSave instead of DoSaveAll if you want to see the changes to the so-called persistent variables (for most purposes, the current position is the only interesting persistent variable).
Perhaps the most frustrating sort of bug when using a graphics package is to see nothing come out at all. When this happens, set a breakpoint on the offending mask call, and examine context.state. Some of the things that can go wrong are:
color same as background.
noImage set by mistake.
current transformation (context.state.T) not right, causing the object to fall outside the view or be too small to see.
current position (cpx, cpy) not in the view. (These are stored in View coordinates, i.e., one unit per device pixel).
priorityImportant not set (this will make a difference only for some devices)
clipper incorrect