Image generation on a Cedar 5 Workstation CEDAR 5.2 Image generation on a Cedar 5 Workstation The Imager and related interfaces Release as [Indigo]Documentation>ImagerDoc.Tioga Written by Doug Wyatt and Michael Plass Last edited June 27, 1984 XEROX Xerox Corporation Palo Alto Research Center 3333 Coyote Hill Road Palo Alto, California 94304 For Internal Xerox Use Only 1. Introduction Caveat This documentation is rather fragmentary. The Imager is patterned strongly after the imaging facilities available in Interpress, so refer to these internal publications for a description of the semantics of the various Imager operations: Interpress Electronic Printing Standard (Version 2.1, XSIS 048404, April 1984) (Chapter 4) Full Interpress (Interpress Extension Strategy) (DRAFT June 1983) (Chapter 4) Introduction to Interpress (Sproull & Reid, XSIG 038306, June 1983) This document will highlight the places where the Imager differs from Interpress, and try to give advice on uses that may not be apparent from the interface. Purpose of document This document describes the standard facilities available for generating images on a Cedar 5 workstation. These facilities are available through the interfaces Imager and Font. Imager contains facilities of interest to all users. ImagerExtras contains items that will be added to Imager the next time it can be recompiled. In cases where the descriptions of functions in the interface files differ from the descriptions in this document, this document should be viewed as the truth. Location of interfaces All interfaces are available through [Indigo]Top>Imager.df. Note that the Imager implementation is not contained in the basic Cedar system; you must start it explicitly. Overview of Functions To be written History Imager is a successor to the Cedar Graphics package that was developed by John Warnock and Doug Wyatt. The design of Imager benefited from advice offered by Frank Crow, Butler Lampson, Scott McGregor, Ken Pier, Lyle Ramshaw, Bob Sproull, and Maureen Stone. The main designers and implementors of Imager were Michael Plass and Doug Wyatt. 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]].ArcTo[[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 3. Transformations Device coordinates The Device coordinate system is in whatever units are most convenient for the implementation, and is normally inaccessible to the client. Surface coordinates The Surface coordinate system covers the entire output medium, one unit per pixel, with the origin at the lower-left corner and x to the right, y up. View coordinates The View coordinate system is normally just a translation from the Surface coordinates. This is normally maintained by the window package, not by random clients. When reading Interpress documentation, read "View coordinates" wherever you see "Device Coordinates". The View has a clipping area associated with it, which is often (but not always) just a rectangle. The View clipping area, as well as the View transformation, are not affected by Reset, DoSave, or DoSaveAll. Client coordinates The Client coordinate system is the one the client thinks in terms of. Initially in meters, it may be changed by the client at any time to whatever is most convenient. The client-to-view transformation is accessible as context.state.T. 4. Error handling Imager.Error Clients of Imager should only have to catch the single ERROR Imager.Error. Imager.Error: ERROR [error: Imager.ErrorCode]; Error code ATOMs Following is an incomplete list of the codes generated by the Imager implementation. Bugs $xxx -- xxx Client Errors $xxx -- xxx User Errors $xxx -- xxx 5. Device types, or how to say where you want the bits to go. Create: PROC[deviceType: ATOM, data: REF _ NIL] RETURNS [Context]; This is the way to create a new context aimed at a particular device. The allowable deviceType atoms currently are: $LFDisplay The default one-bit-per-pixel black-and-white display. The data parameter may be a REF ImagerPixelMaps.PixelMap (one bit per pixel) to direct the result to a different bitmap; otherwise the current black-and-white terminal is used. $PD For a PD file. The data parameter must be an ImagerPD.PDFileDescription to supply the name of the output file and the characteristics of the output device. ImagerBridge, or what to do while Viewers still uses CedarGraphics Write your client program so that it keeps its own Imager.Context somewhere in its client data. Inside of the viewer's PaintProc, use ImagerBridge.SetViewFromGraphicsContext to set the view of your Imager context to correspond with the area of the screen Viewers has provided for you. Do all of the painting inside of the PaintProc. Be sure and lock the imager context in some way, since calls to the PaintProc may come from different processes. Try to arrange your program so that the PaintProc does nothing but paint. When Viewers provides the mouse position (via the notify proc), it will be in View coordinates. Get them into client coordinates by means of Imager.Transform[Imager.Invert[context.state.T], [mouseX, mouseY]]; or, equivalently, by ImagerTransform.InverseTransform[[mouseX, mouseY], context.state.T]; 6. More about fonts. Fonts.mesa The Cedar interface Fonts.mesa provides a device independent way of specifying fonts to be used with the Cedar Imager. The client interface should be fairly self-explanatory. The purpose of this section is to describe the format of the font directory file, which describes all the fonts that are available in a particular environment. Cedar.fontDir In the current implementation, there is a file called Cedar.fontDir that lives on each local disk. A more automatic way of getting the most recent information, while still providing for local modification, may someday be available. The job of the font directory file is to provide a mapping from the font nametransformationdevice type triple to the names of the font description files appropriate to the particular devices. This sounds like a good database application, but this mechanism is devised to work without any fancy database system, as long as the list of available fonts does not become too gargantuan. In order to allow common parts of the font directory to be easily factored out, the font directory file format consists of a sequence of commands that specify parameters, interspersed with commands that define new entries in the font table. The easiest way to see how this works is by example: DEVICE: Ideal SIZE: ANY ROTATION: ANY FONTNAME: Xerox/NS/Times/italic/body CODESCHEME: XeroxText.codeScheme METRICS: TFM [Indigo]tfm>TimesRoman.tfm GRAPHICS: SD [Indigo]TimesRoman-MIR.sd DEFINEFONT; ROTATION: 0 DEVICE: Spruce GRAPHICS: PRESS [] SIZE: 5bp +- 1/2 bp DEFINEFONT; SIZE: 6bp +- .5 bp DEFINEFONT; SIZE: 6bp + 1bp - .5bp DEFINEFONT; DEVICE: Press SIZE: 5bp +- .1bp DEFINEFONT; SIZE: 6bp +- .1bp DEFINEFONT; SIZE: 7bp +- .2bp DEFINEFONT; SIZE: ANY ROTATION: ANY DEFINEFONT; DEVICE: Screen ROTATION: 0 METRICS: STRIKE [Indigo]Tioga>StrikeFonts>TimesRoman8I.strike GRAPHICS: STRIKE [Indigo]Tioga>StrikeFonts>TimesRoman8I.strike SIZE: 8pt +- 1pt DEFINEFONT; METRICS: STRIKE [Indigo]Tioga>StrikeFonts>TimesRoman12I.strike GRAPHICS: STRIKE [Indigo]Tioga>StrikeFonts>TimesRoman12I.strike SIZE: 12pt +- 2pt DEFINEFONT; What this says is essentially the following: If the user asks for the font Xerox/NS/Times/italic/body, and the device is Ideal (e.g., CedarGraphics or Interpress), get the metric information from [Indigo]tfm>TimesRoman.tfm and the graphics from [Indigo]TimesRoman-MIR.sd. If the device is a Spruce printer, and the rotation is zero, and the size is close to 5, 6, or 7 "big points" (i.e., 72-per-inch points), use the family name and face specified in the TFM file. Alternatively, if the device is a full Press printer, it is ok to substitute the 5, 6, or 7 point raster fonts if the size is very close and the rotation is zero; otherwise use the spline outline representation. Finally, if what is wanted is a font optimized to look good on the screen, use either [Indigo]StrikeFonts>TimesRoman8I.strike or [Indigo]StrikeFonts>TimesRoman12I.strike, depending on whether the size is close to 8 or 12 points. Ground rules for parsing: Case does not matter. Tokens are delimited by spaces, commas or semicolons. Rotations are counterclockwise, and in degrees. Units for size are millimeters(mm), centimeters(cm), inches(in), big points(bp, 72/in), or printer's points(pt, 72.27/in). ANY is a wildcard for SIZE and ROTATION. If an error is encountered while parsing the font directory, a message is appended to UnifiedFonts.errorLog, the screen is flashed, and a default font is provided. If no matching font is found, the same thing happens, except the screen is not blinked. Shortfalls The rules about the transformation are not really right, in that they talk about the font's transformation rather than the composite transformation in effect when the characters are actually imaged. This will all be made more wonderful in the future; for now be advised to stick to DEVICE: Ideal and SIZE: ANY ROTATION: ANY fonts, and future conversions will cause you little pain. The Ideal (read Spline) fonts will be a bit on the ugly side on the display, but this is expected to get better with some new algorithms still under development. Do not fall too much in love with the Font interface, as it is expected to change soon (to become more like Interpress). Wherever possible stick to the procedures provided in the Imager interface. The CODESCHEME machinery is not to be relied upon.