Image generation on a Cedar 5 Workstation CEDAR 5.3 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 by Doug Wyatt, August 30, 1984 3:01:09 pm PDT 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 038404, April 1984) 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, 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. Font.mesa The Cedar interface Font.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. Font masters in Interpress form A FontDescription includes a masks Vector. For each character index i, define operators i GET to have the following effect: { fd masks GETP i GETP DOSAVEALL -- image the mask -- fd characterMetrics GETP i GETP 0 FSET -- Frame[0] _ characterMetrics[i] -- 0 FGET widthX GETPROP NOT { 0 } IF 1 FSET -- Frame[1] _ widthX, default 0 -- 0 FGET widthY GETPROP NOT { 0 } IF 2 FSET -- Frame[2] _ widthY, default 0 -- 0 FGET amplified GETPROP NOT { 0 } IF 3 FSET -- Frame[3] _ amplified, default 0 -- 1 FGET 3 FGET { 18 IGET MUL } IF 4 FSET -- Frame[4] _ widthX*amplifySpace -- 2 FGET 3 FGET { 18 IGET MUL } IF 5 FSET -- Frame[5] _ widthY*amplifySpace -- 4 FGET 5 FGET SETXYREL -- move current position by (possibly amplified) width -- 0 FGET correction GETPROP NOT { 0 } IF 6 FSET -- Frame[6] _ correction, default 0 -- 6 FGET 1 EQ { 4 FGET 5 FGET CORRECTSPACE } IF -- CORRECTSPACE if correction=1 -- 6 FGET 2 EQ { CORRECTMASK } IF -- CORRECTMASK if correction=2 -- } DOSAVESIMPLEBODY ÊŠ– "cedar" style˜Iblock•Mark centerHeaderšœ)˜)K– centerFooteršÏk ˜ Ititlešœ)˜)Isubtitlešœ!˜!Iabstractš œÏmœ œœ+˜šK˜K˜K˜I boilerplateš ÏqœÏoœ œ œ  Ñbox˜’head˜˜šœî˜îKšœZ˜ZKšœM˜MKšœD˜D—K˜—˜Kšœé˜é—˜Kšœ3žœ8ÏiœC˜²—˜J˜—˜KšœÓ˜Ó——˜5˜ ˜êIunit˜—˜˜Inote˜Å—˜(Q˜*—K˜¬šœO¢ œ˜ZQšœC˜C—˜Qšœ˜—šœ®˜®Qšœ'˜'—K˜€šœ¢˜¢QšœK˜K—šœÁ˜ÁQšœ(˜(—K˜|˜rQšœG˜G—šœâ˜âQšœ$˜$Qšœ$˜$—K˜X˜HQšœœG˜Z—˜œQšœ ˜ —šœ¢œ&˜GQšœ˜—šœ ˜ QšœU˜U—Kšœ6œœœÓ˜›Kšœó˜óšœ8˜8Qšœ1˜1Qšœ0˜0Qšœ'˜'Qšœ*˜*Qšœ%˜%Qšœ.˜.Qšœ%˜%Qšœ.˜.Qšœ'˜'Qšœ.˜.—˜‚QšœœD˜O—KšœP¢œW˜»˜ðQ˜Q˜Q˜wQ˜uQšœM˜MQ˜———˜˜J˜‰—˜J˜•—˜J˜Û—˜J˜í——˜˜ šœ7œ˜JQšÏb œœ˜.——˜KšœT˜T˜JšœÐci¢˜ —˜ Jšœ¢Ïc˜ —˜ Jšœ¥˜ ———˜=š œœ œœœœ ˜Btable2šœt˜tS–1.4 in restIndentšœò˜òS–1.4 in restIndentšœ ˜ ——˜BSšœ‹˜‹ šœ˜QšœC˜C— ˜QšœD˜D———˜šœ ˜ IbodyšœÏ˜Ï—˜Kšœ¢œ˜*š œ¢œ¢ œ¢œœ˜Pšœ˜Kš ¢œ¢œœ¢œœ œ¥˜3Kš ¢œ¢œœ¢œœœ¥$˜KKšœœ¢œœœœœ¥"˜LKšœœ¢œœœœœ¥"˜LKšœœ¢ œœœœœ¥¢ ¥˜RKš œœœœœœ¥!˜LKš œœœœœœ¥!˜LKšœœœœ¥9˜PKšœœ¢ œœœœœ¥&˜TKšœœœœœ œœ¥ ¥˜PKš œœœ œœ¥ ¥˜@Kšœ˜K˜K˜—K˜————…—27ª