G3dColorDisplaySupportImpl.mesa
Last Edited by: Crow, September 19, 1989 10:09:37 am PDT
Glassner, March 14, 1989 2:18:27 pm PST
Bloomenthal, December 29, 1988 9:06:41 pm PST
DIRECTORY Atom, Basics, CedarProcess, ColorDisplayManager, ConvertRasterObject, FS, G3dColorDisplaySupport, G3dRender, G3dRenderWithPixels, G3dSortandDisplay, Imager, ImagerBackdoor, ImagerPixel, ImagerSample, IO, Process, Real, RealFns, Rope, SF, Terminal, ViewerClasses;
G3dColorDisplaySupportImpl: CEDAR MONITOR
IMPORTS Atom, Basics, CedarProcess, ColorDisplayManager, ConvertRasterObject, FS, G3dRender, G3dRenderWithPixels, G3dSortandDisplay, ImagerBackdoor, ImagerPixel, ImagerSample, IO, Process, Real, RealFns, Rope, SF, Terminal
EXPORTS G3dColorDisplaySupport
~ BEGIN
Types
PropList:    TYPE ~ Atom.PropList;
ROPE:    TYPE ~ Rope.ROPE;
RopeDesc:   TYPE ~ G3dRenderWithPixels.RopeDesc;
RopeProc:   TYPE ~ G3dRenderWithPixels.RopeProc;
Context:   TYPE ~ G3dRender.Context;
ContextProc:  TYPE ~ G3dRender.ContextProc;
ImagerProc:  TYPE ~ G3dRender.ImagerProc;
ContextClass:  TYPE ~ G3dRender.ContextClass;
DisplayMode: TYPE ~ G3dRender.DisplayMode;
Box:    TYPE ~ ImagerSample.Box;
Rectangle:  TYPE ~ G3dRender.Rectangle;
Triple:   TYPE ~ G3dRender.Triple;
TripleSequence: TYPE ~ G3dRender.TripleSequence;
RGB:     TYPE ~ G3dRender.RGB;
Pixel:    TYPE ~ G3dRender.Pixel;
IntegerPair:   TYPE ~ G3dRender.IntegerPair;
Pair:     TYPE ~ G3dRender.Pair;
PairSequence:  TYPE ~ G3dRender.PairSequence;
IntRGB:    TYPE ~ RECORD [r, g, b: CARDINAL];
IntRGBSequence: TYPE ~ RECORD [SEQUENCE length: NAT OF IntRGB];
NatSequence:  TYPE ~ G3dRender.NatSequence;
Patch:    TYPE ~ G3dRender.Patch;
PatchProc:   TYPE ~ G3dRender.PatchProc;
ClipState:   TYPE ~ G3dRender.ClipState;
PixelBuffer:   TYPE ~ ImagerPixel.PixelBuffer;
PixelMap:   TYPE ~ ImagerPixel.PixelMap;
SampleMap:   TYPE ~ ImagerSample.SampleMap;
ImagerProcRec:  TYPE ~ G3dRender.ImagerProcRec;
LORA:    TYPE ~ LIST OF REF ANY;
Globals
timeResolution: CARD16 ← 3;
Renamed Procedures
PasteInLabel: PROC[fileRoot: Rope.ROPE, label: Rope.ROPE] RETURNS[Rope.ROPE]
~ G3dRender.PasteInLabel;
GetProp: PROC [propList: PropList, prop: REF ANY] RETURNS [REF ANY]
~ Atom.GetPropFromList;
PutProp: PROC [propList: PropList, prop: REF ANY, val: REF ANY] RETURNS [PropList]
~ Atom.PutPropOnList;
Utility Procedures
Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ {
result ← Real.Round[number];
IF result < number THEN result ← result + 1;
};
Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ {
out ← Real.Round[in];
IF Real.Float[out] > in THEN out ← out - 1;
};
UpdateFullColorDisplay: PROC [context: Context] ~ {
screenPixels: PixelMap NARROW[GetProp[context.displayProps, $ScreenPixels]];
xMin: NAT ← context.pixels.box.min.f;
width: NAT ← context.pixels.box.max.f-xMin;
buf1: ImagerSample.SampleBuffer ← ImagerSample.ObtainScratchSamples[width];
buf2: ImagerSample.SampleBuffer ← ImagerSample.ObtainScratchSamples[width];
DoUpdateFullColorDisplay[context, screenPixels, buf1, buf2, xMin, width];
ImagerSample.ReleaseScratchSamples[buf1];
ImagerSample.ReleaseScratchSamples[buf2];
};
PeriodicUpdateFullColorDisplay: CedarProcess.ForkableProc ~ {
Updates from buffered pixel map
context: Context ← NARROW[data];
screenPixels: PixelMap NARROW[GetProp[context.displayProps, $ScreenPixels]];
height: NAT ← screenPixels.box.max.s; width: NAT ← screenPixels.box.max.f/2;
xMin: NAT ← context.pixels.box.min.f;
width: NAT ← context.pixels.box.max.f-xMin;
waitTime: CARD16 ← timeResolution*3;
buf1: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[width];
buf2: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[width];
DO    -- stuff buffered pixels onto screen every waitTime seconds
Process.Pause[Process.SecondsToTicks[waitTime]];
DoUpdateFullColorDisplay[context, screenPixels, buf1, buf2, xMin, width];
CedarProcess.CheckAbort[ ];
ENDLOOP;
};
DoUpdateFullColorDisplay: PROC [
context: Context,
screenPixels: PixelMap,
buf1, buf2: ImagerSample.SampleBuffer,
xMin, width: NAT]
~ {
IF screenPixels # NIL
THEN FOR i: NAT IN [context.pixels.box.min.s..context.pixels.box.max.s) DO
Read 8 bit samples into red buffer:
ImagerSample.GetSamples[
map: context.pixels[0], initIndex: [i, xMin], buffer: buf1, count: width];
Read 8 bit samples into green buffer:
ImagerSample.GetSamples[
map: context.pixels[1], initIndex: [i, xMin], buffer: buf2, count: width];
Now, interleave the red and green 8 bit samples into the 16 bit red/green color display:
Write the red, starting at 0, with delta.f = 2, thus writing the even bytes:
ImagerSample.PutSamples[
map: screenPixels[0], initIndex: [i, 2*xMin], buffer: buf1, delta: [0, 2], count: width];
Write the green, starting at 1, with delta.f = 2, thus writing the odd bytes:
ImagerSample.PutSamples[
map: screenPixels[0], initIndex: [i, 1+2*xMin], buffer: buf2, delta: [0, 2], count: width];
ENDLOOP;
};
UpdateViewer: CedarProcess.ForkableProc ~ { -- updates viewer from buffered pixel map
PROC [data: REF] RETURNS [results: REFNIL]
context: Context ← NARROW[data];
waitTime: CARD16IF context.class.displayType = $FullColor THEN timeResolution * 3
                  ELSE timeResolution;
WHILE TRUE DO   -- stuff buffered pixels onto screen every waitTime seconds
Process.Pause[ Process.SecondsToTicks[waitTime] ];
context.class.drawInViewer[ context, NEW[ImagerProcRec ← [StuffBuf, NIL]] ];
CedarProcess.CheckAbort[ ];
ENDLOOP;
};
Initialize Standard Displays
GetDisplay: ContextProc ~ { 
Establishes PixelMap corresponding to bits on color display. If viewers are being buffered,
sets up pixel maps in VM to be blitted across as appropriate.
This is called by G3dRender.LoadDisplayType
IF context.viewer # NIL
THEN {
context.class.updateViewer[ context ];
context.viewPort ← NEW[ Rectangle ←
[context.viewer.cx, context.viewer.cy, context.viewer.cw, context.viewer.ch]
];
G3dRenderWithPixels.AllocatePixelMemory[context];
}
ELSE GrabColorDisplay[context];    -- clear off color display and take it over
SELECT context.class.displayType FROM
$FullColor, $Gray => {
LoadColorRamp[ context.terminal, [0.,0.,0.], [1.,1.,1.], [.43, .43, .43] ];
};
$PseudoColor => {
LoadStd8BitClrMap[context.terminal];
};
ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected displayType"];
context.pixelAspectRatio ← 1.0;   -- standard square-pixel display assumed
context.displayInValid ← TRUE;
};
GrabColorDisplay: PROC[ context: Context ] ~ { 
For using color display without viewers
vt: Terminal.Virtual;
box: Box;
s0, s2: ImagerSample.RasterSampleMap ← NIL;
clrType: ATOM;
IF context.terminal = NIL
THEN context.terminal ← vt ← Terminal.Current[]
ELSE vt ← context.terminal;
SELECT context.class.displayType FROM
$PseudoColor => clrType ← $Dither8;
$Gray   => clrType ← $Gray8;
$FullColor => clrType ← $FullColor;
ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected Display type"];
ColorDisplayManager.Start[
type: clrType,  -- ATOM, Colordisplay types: $Gray8, $Dither8, $Dither1, $FullColor
side: left,   -- Interminal.Side {left, right}
level: mouse,  -- ColorDisplayManager.Level ~ {off, allocated, visible, mouse, viewers}
resolution: none -- ColorDisplayDefs.ColorDisplayType {none, standard, highResolution}
];
SELECT context.class.displayType FROM
$FullColor => {
s0 ← ImagerSample.MapFromFrameBuffer[vt.GetColorFrameBufferA[]]; -- 16 bits, R&G
s2 ← ImagerSample.MapFromFrameBuffer[vt.GetColorFrameBufferB[]]; -- 8 bits, B
};
$PseudoColor, $Gray =>
s0 ← ImagerSample.MapFromFrameBuffer[vt.GetColorFrameBufferA[]];
ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected Display type"];
box ← ImagerSample.GetBox[s0];     -- get frame buffer size
context.viewPort ← NEW[
Rectangle ← [box.min.f, box.min.s, box.max.f - box.min.f, box.max.s - box.min.s] ];
G3dRenderWithPixels.AllocatePixelMemory[context];
IF context.class.displayType = $FullColor THEN {
Reform screen memory to address by 8-bit chunks
screenPxls: PixelMap;
TRUSTED {
reconfigure R&G color display sample maps as a big, double-width 8-bit sample map:
screenPxls ← ImagerPixel.MakePixelMap[
ImagerSample.UnsafeNewSampleMap[
box: [box.min, [box.max.s, 2 * box.max.f]],
bitsPerSample: 8,
bitsPerLine: ImagerSample.GetBitsPerLine[s0],
base: ImagerSample.GetBase[s0],
ref: ImagerSample.GetRef[s0],
words: ImagerSample.WordsForLines[ box.max.s, ImagerSample.GetBitsPerLine[s0] ]
]
];
};
context.displayProps ← PutProp[context.displayProps, $ScreenPixels, screenPxls];
};
For full-color, first sample map is off-screen red, second is off-screen green, third is on-screen blue. Green and R&G and blitted together as necessary
IF s2 = NIL THEN context.pixels[0] ← s0;  -- on-screen mem (pseudoclr, grey)
IF s2 # NIL THEN context.pixels[2] ← s2;  -- on-screen mem (Blue record for full-color)
context.viewPort ← NIL;
Computed by G3dSortandDisplay.ValidateView or ThreeDViewer.GetViewportFromViewer
context.window ← NIL;
};
ValidateDisplay: ContextProc ~{   -- update viewPort, etc.
IF context.viewer # NIL THEN {
IF GetProp[ context.displayProps, $ViewerAdjusted ] # NIL
THEN context.class.drawInViewer[context, NIL];  -- get specs for new viewer
IF context.pixels = NIL OR context.pixels.samplesPerPixel = 1
THEN context.class.updateViewer[ context ];   -- drawing directly on screen
context.stopMe^ ← FALSE;    -- make sure stop button is released
};
IF context.pixels # NIL    -- check for mismatched viewPort and pixel storage
THEN {
IF context.pixels.box.max.f < context.viewPort.w
OR context.pixels.box.max.s < context.viewPort.h
THEN G3dRenderWithPixels.AllocatePixelMemory[context];
}
ELSE IF context.viewer = NIL THEN G3dRenderWithPixels.AllocatePixelMemory[context];
};
Init: PROC[] ~ {       -- register Imager-based drawing types
standardClass: ContextClass ← [
displayType: $PseudoColor,
setUpDisplayType: GetDisplay,
validateDisplay: ValidateDisplay,
render: MakeFrame,
loadBackground: FillInBackGround,
draw2DLine: Draw2DLine,
draw2DPolygon: Draw2DPoly,
draw2DRope: Draw2DRope,
displayPolygon: PolygonTiler 
];
G3dRender.RegisterDisplayClass[ standardClass, $PseudoColor ];
standardClass.displayType ← $FullColor;
G3dRender.RegisterDisplayClass[ standardClass, $FullColor ];
standardClass.displayType ← $Gray;
G3dRender.RegisterDisplayClass[ standardClass, $Gray ];
};
Colors
MappedRGB: PUBLIC PROC[context: Context, clr: Pixel] RETURNS[Pixel] ~ {
SELECT context.class.displayType FROM 
$ImagerDithered  => {
mapVal: NAT ← 24 * (clr[r]*5 / 256) + 4 * (clr[g]*6 / 256) + (clr[b]*4 / 256);
IF mapVal >= 60 THEN mapVal ← mapVal + 135;   -- move to top of map
clr[r] ← mapVal;
};
$PseudoColor => clr[r] ← 42 * (clr[r]*6 / 256) + 6 * (clr[g]*7 / 256) + (clr[b]*6 / 256) +2;
$Gray => clr[r] ← ( clr[r] + clr[g] + clr[b] ) / 3;
ENDCASE;    -- ignore other modes
RETURN[ clr ];
};
LoadStd8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual] ~ {
Linearize: PROC [value: REAL] RETURNS[NAT] ~ {
RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ];
};
IF vt = NIL THEN vt ← Terminal.Current[];
vt.SetColor[0, 0, 0, 0, 0]; vt.SetColor[1, 0, 0, 0, 0];
FOR i: NAT IN [2..254) DO          -- 6 x 7 x 6 color cube
j: NAT ← i - 2;
red: NAT ← Linearize[51.0 * (j/42)];
grn: NAT ← Linearize[42.5 * ((j/6) MOD 7)];
blu: NAT ← Linearize[51.0 * (j MOD 6)];
vt.SetColor[i, 0, red, grn, blu];
ENDLOOP;
vt.SetColor[254, 0, 255, 255, 255]; vt.SetColor[255, 0, 255, 255, 255];
};
LoadColorRamp: PUBLIC PROC [vt: Terminal.Virtual, clr1: RGB ← [0,0,0],
          clr2: RGB ← [255,255,255], exponent: RGB ← [.43,.43,.43] ] ~ {
state: Terminal.ColorMode;
maxVal: REAL;
IF vt = NIL THEN vt ← Terminal.Current[];
state ← vt.GetColorMode[];
maxVal ← IF state.full
THEN 255.0
ELSE Real.Float[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1];
clr1.R ← MAX[0.0, MIN[1.0, clr1.R]]; clr2.R ← MAX[0.0, MIN[1.0, clr2.R]];
clr1.G ← MAX[0.0, MIN[1.0, clr1.G]]; clr2.G ← MAX[0.0, MIN[1.0, clr2.G]];
clr1.B ← MAX[0.0, MIN[1.0, clr1.B]]; clr2.B ← MAX[0.0, MIN[1.0, clr2.B]];
FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO   -- linear ramp exponentiated
jr: [0..256) ← Real.Fix[
    RealFns.Power[clr1.R + i/maxVal * (clr2.R - clr1.R), exponent.R] * maxVal];
jg: [0..256) ← Real.Fix[
    RealFns.Power[clr1.G + i/maxVal * (clr2.G - clr1.G), exponent.G] * maxVal];
jb: [0..256) ← Real.Fix[
    RealFns.Power[clr1.B + i/maxVal * (clr2.B - clr1.B), exponent.B] * maxVal];
IF Terminal.GetColorMode[vt].full
THEN { vt.SetRedMap[i, jr]; vt.SetGreenMap[i, jg]; vt.SetBlueMap[i, jb]; }
ELSE vt.SetColor[i, 0, jr, jg, jb];
ENDLOOP;
};
Low-level drawing
FillInBackGround: ContextProc ~ {
IF context.viewer # NIL            -- do through viewer
THEN context.class.drawInViewer[context, NEW[ImagerProcRec ← [ViewerBackFill, NIL]]]
ELSE G3dRenderWithPixels.FillInBackGround[context];   -- clear directly
};
ViewerBackFill: ImagerProc ~ {  -- get pixelmap into context and then do it
DoFillInBackGround: PROC[pixelMap: PixelMap] ~ {
tempPixels: PixelMap ← context.pixels; tempViewPort: Rectangle ← context.viewPort^;
context.pixels ← pixelMap;
context.viewPort.x ← context.viewPort.x + pixelMap.box.min.f;  -- adjust to device coords
context.viewPort.y ← context.viewPort.y + pixelMap.box.min.s;
G3dRenderWithPixels.FillInBackGround[context];
context.pixels ← tempPixels; context.viewPort^ ← tempViewPort;
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoFillInBackGround, context.viewPort^];
};
Draw2DLine: PROC[context: Context, p1, p2: Pair, color: Pixel] ~ {
mapClr: Pixel ← MappedRGB[context, color];
IF context.viewer # NIL               -- do through viewer
THEN {
data: REF LineDesc ← NEW[LineDesc ← [p1, p2, mapClr] ];
context.class.drawInViewer[context, NEW[ImagerProcRec ← [ViewerLine, data]]];
}
ELSE G3dRenderWithPixels.Draw2DLine[context, p1, p2, mapClr];  -- do directly
};
LineDesc: TYPE ~ RECORD[p1, p2: Pair, color: Pixel];
ViewerLine: ImagerProc ~ {  -- get pixelmap into context and then do it
DoDrawLine: PROC[pixelMap: PixelMap] ~ {
desc: REF LineDesc ← NARROW[data];
tempPixels: PixelMap ← context.pixels; context.pixels ← pixelMap;
G3dRenderWithPixels.Draw2DLine[context, desc.p1, desc.p2, desc.color];
context.pixels ← tempPixels;
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoDrawLine, context.viewPort^];
};
Draw2DPoly: PROC[context: Context, poly: PairSequence, color: Pixel] ~ {
mapClr: Pixel ← MappedRGB[context, color];
IF context.viewer # NIL            -- do through viewer
THEN {
data: REF PolyDesc ← NEW[PolyDesc ← [poly, mapClr] ];
context.class.drawInViewer[context, NEW[ImagerProcRec ← [Viewer2DPoly, data]]];
}
ELSE G3dRenderWithPixels.Draw2DPoly[context, poly, mapClr];   -- do directly
};
PolyDesc: TYPE ~ RECORD[poly: PairSequence, color: Pixel];
Viewer2DPoly: ImagerProc ~ {   -- get pixelmap into context and then do it
DoDraw2DPoly: PROC[pixelMap: PixelMap] ~ {
desc: REF PolyDesc ← NARROW[data];
tempPixels: PixelMap ← context.pixels; context.pixels ← pixelMap;
G3dRenderWithPixels.Draw2DPoly[context, desc.poly, desc.color];
context.pixels ← tempPixels;
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoDraw2DPoly, context.viewPort^];
};
PolygonTiler: PatchProc ~ {
IF context.viewer # NIL            -- do through viewer
THEN {
plyData: REF PolygonDesc ← NEW[PolygonDesc ← [patch, data] ];
context.class.drawInViewer[context, NEW[ImagerProcRec ← [ViewerTiler, plyData]]];
}
ELSE {
[] ← G3dRenderWithPixels.PolygonTiler[context, patch, data];   -- do directly
};
RETURN[NIL];
};
PolygonDesc: TYPE ~ RECORD[patch: REF Patch, data: REF ANY];
ViewerTiler: ImagerProc ~ {    -- get pixelmap into context and then do it
DoPolygonTiler: PROC[pixelMap: PixelMap] ~ {
desc: REF PolygonDesc ← NARROW[data];
tempPixels: PixelMap ← context.pixels;
context.pixels ← pixelMap;
[] ← G3dRenderWithPixels.PolygonTiler[context, desc.patch, desc.data];
context.pixels ← tempPixels;
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoPolygonTiler, context.viewPort^];
};
Draw2DRope: PUBLIC RopeProc ~ {
Put a string of characters on the screen
IF context.viewer = NIL
THEN G3dRenderWithPixels.DrawRope[context, rope, position, color, size, font]
ELSE {
ropeData: REF RopeDesc ← NEW[ RopeDesc ← [rope, position, color, size, font] ];
context.class.drawInViewer[
context,
NEW[ImagerProcRec ← [G3dRenderWithPixels.DoRope, ropeData]]
];
};
};
Image File Storage and Retrieval
standardNames: ARRAY[0..6) OF RECORD[ pref, alt, other: Rope.ROPE ] ← [
["-gray", "-grey", NIL],
["-red", "-r", "-rd"],
["-grn", "-green", "-g"],
["-blu", "-blue", "-b"],
["-alpha", "-a", "-alp"],
["-depth", "-d", "-z"]
];
FindFile: PROC [fileRoot: Rope.ROPE, names: RECORD[ pref, alt, other: Rope.ROPE ] ]
RETURNS
[name: Rope.ROPE] ~ {
ok: BOOLEANTRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.pref]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.alt]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.other]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← fileRoot ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ]
ELSE {
SIGNAL G3dRender.Error[
$NotFound,
Rope.Cat[" Can't find ", fileRoot, " or with extensions: ",
   Rope.Cat[names.pref, " ", names.alt, " ", names.other] ]
];
RETURN[ NIL ];
};
};
GetAIS: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE, xOffset, yOffset: INTEGER ← 0,
       center: BOOLEANTRUE, labeled: BOOLEANFALSE ]
     RETURNS
[ xSize, ySize: INTEGER] ~ {
numFiles: NAT;
names: ARRAY[0..5) OF RECORD[ pref, alt, other: Rope.ROPE ];
inSamples: ARRAY[0..5) OF SampleMap ← ALL[NIL];
pixelsFromAIS: PixelMap;      -- place to put sample maps if needed elsewhere
IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
fileRoot ← G3dRender.PrependWorkingDirectory[context, fileRoot];
IF context.pixels = NIL
THEN G3dRenderWithPixels.AllocatePixelMemory[ context ];     -- image buffer
SELECT context.class.displayType FROM
$Dithered, $PseudoColor => {
names[0] ← [NIL, NIL, NIL];
numFiles ← 1;
};
$Gray => {
names[0] ← standardNames[0];
numFiles ← 1;
};
$FullColor, $ImagerFullClr  => {
names[0] ← standardNames[1];
names[1] ← standardNames[2];
names[2] ← standardNames[3];
numFiles ← 3;
};
ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Bad RenderMode"];
IF context.antiAliasing
THEN { names[numFiles] ← standardNames[4]; numFiles ← numFiles + 1; };
IF context.depthBuffering
THEN { names[numFiles] ← standardNames[5]; numFiles ← numFiles + 1; };
FOR i: NAT IN [0..numFiles) DO   -- get a file for each pixel entry
fileName: Rope.ROPE ← FindFile[ fileRoot, names[i] ];
IF fileName # NIL
THEN {
box: Box;
inSamples[i] ← ConvertRasterObject.SampleMapFromAIS[
aisfile: fileName, useVM: TRUE
];
box ← ImagerSample.GetBox[ inSamples[i] ];
xSize ← SF.SizeF[box]; ySize ← SF.SizeS[box];
IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
IF context.viewer # NIL      -- on viewer, put pixels there
THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
context.class.drawInViewer[    -- get pixel map from viewer
context,
NEW[ImagerProcRec ← [
WriteToViewer,
LIST[ NEW[NAT ← i], inSamples[i],
NEW[INTEGER ← xOffset], NEW[INTEGER ← yOffset],
  NEW
[BOOLEAN ← center]
 ]
]]
];
IF labeled THEN {    -- put file name at bottom of display
intPos: IntegerPair; pos: Pair;
viewerBox: Box ← [
min: [ f: context.viewer.cx, s: context.viewer.cy ],
max: [ f: context.viewer.cw+context.viewer.cx,
  s: context.viewer.ch+context.viewer.cy ]
];
[intPos, , ] ← ComputeBox[viewerBox, box, xOffset, xOffset, center];
pos ← [ 0.02 + 1.0 * intPos.x / viewerBox.max.f,
  0.02 + 1.0 * intPos.y / viewerBox.max.s ];
context.class.draw2DRope[
context: context, rope: fileName, position: pos,
size: 20 * viewerBox.max.f / 640.0
];
};
}
ELSE {
G3dRenderWithPixels.CopyUnder should work without the following commented:
IF context.viewPort # NIL
THEN {
box ← [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]],
  [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ];
box.max.s ← box.max.s + box.min.s; box.max.f ← box.max.f + box.min.f;
}
ELSE box ← context.pixels.box;
TransferSamples[
ImagerSample.Clip[context.pixels[i], box], inSamples[i],
xOffset, yOffset, center
];
};
};
ENDLOOP;
pixelsFromAIS ← ImagerPixel.MakePixelMap[
inSamples[0], inSamples[1], inSamples[2], inSamples[3], inSamples[4]
];
context.props ← PutProp[context.props, $TempPixels, pixelsFromAIS];
};
GetRGB: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE, xOffset, yOffset: INTEGER ← 0,
       center: BOOLEANTRUE, labeled: BOOLEANFALSE ]
     RETURNS
[ xSize, ySize: INTEGER] ~ {
SampleMapsFromRGB: PROC[ fileName: Rope.ROPE ] ~ {
in: IO.STREAMFS.StreamOpen[fileName: fileName];
box: Box ← [[f: 0, s: 0], [f: 720, s: 486]];
rBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
gBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
bBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
FOR i: NAT IN [0..3) DO
inSamples[i] ← ImagerSample.NewSampleMap[
box: box, bitsPerSample: 8, bitsPerLine: 720*8
];
ENDLOOP;
FOR y: NAT IN [0..486) DO
FOR x: NAT IN [0..720) DO
rBuf[x] ← ORD[IO.GetChar[in]];
gBuf[x] ← ORD[IO.GetChar[in]];
bBuf[x] ← ORD[IO.GetChar[in]];
ENDLOOP;
ImagerSample.PutSamples[map: inSamples[0], initIndex: [y, 0], buffer: rBuf, count: 720];
ImagerSample.PutSamples[map: inSamples[1], initIndex: [y, 0], buffer: gBuf, count: 720];
ImagerSample.PutSamples[map: inSamples[2], initIndex: [y, 0], buffer: bBuf, count: 720];
IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ENDLOOP;
IO.Close[in];
};
box: Box;
inSamples: ARRAY[0..3) OF SampleMap ← ALL[NIL];
pixelsFromAIS: PixelMap;      -- place to put sample maps if needed elsewhere
IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
fileRoot ← G3dRender.PrependWorkingDirectory[context, fileRoot];
Ensure image buffer in memory
IF context.pixels = NIL THEN G3dRenderWithPixels.AllocatePixelMemory[ context ];
IF context.class.displayType # $FullColor
THEN SIGNAL G3dRender.Error[$MisMatch, "Must be a full-color display"];
SampleMapsFromRGB[ fileRoot ];
box ← ImagerSample.GetBox[ inSamples[0] ];
xSize ← SF.SizeF[box]; ySize ← SF.SizeS[box];
IF context.viewer # NIL      -- on viewer, put pixels there
THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
FOR i: NAT IN [0..3) DO
context.class.drawInViewer[    -- get pixel map from viewer
context,
NEW[ImagerProcRec ← [
WriteToViewer,
LIST[ NEW[NAT ← i], inSamples[i],
NEW[INTEGER ← xOffset], NEW[INTEGER ← yOffset],
  NEW
[BOOLEAN ← center]
 ]
]]
];
ENDLOOP;
IF labeled THEN {    -- put file name at bottom of display
intPos: IntegerPair; pos: Pair;
viewerBox: Box ← [
min: [ f: context.viewer.cx, s: context.viewer.cy ],
max: [ f: context.viewer.cw+context.viewer.cx,
  s: context.viewer.ch+context.viewer.cy ]
];
[intPos, , ] ← ComputeBox[viewerBox, box, xOffset, xOffset, center];
pos ← [ 0.02 + 1.0 * intPos.x / context.pixels.box.max.f,
  0.02 + 1.0 * intPos.y / context.pixels.box.max.s ];
context.class.draw2DRope[
context: context, rope: fileRoot, position: pos, size: 20 * xSize / 640.0
];
};
}
ELSE {
IF context.viewPort # NIL
THEN {
box ← [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]],
  [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ];
box.max.s ← box.max.s + box.min.s; box.max.f ← box.max.f + box.min.f;
}
ELSE box ← context.pixels.box;
FOR i: NAT IN [0..3) DO
TransferSamples[
ImagerSample.Clip[context.pixels[i], box], inSamples[i],
xOffset, yOffset, center
];
ENDLOOP;
};
pixelsFromAIS ← ImagerPixel.MakePixelMap[ inSamples[0], inSamples[1], inSamples[2] ];
context.props ← PutProp[context.props, $TempPixels, pixelsFromAIS];
};
WriteToViewer: ImagerProc ~ {  -- get pixelmap from viewer and get sample map into it
PROC[ context: Context, imagerCtx: Imager.Context, data: REF ANYNIL ];
DoWrite: PROC[pixelMap: PixelMap] ~ {
list: LORANARROW[data];
i: NATNARROW[list.first, REF NAT]^;
TransferSamples[
dstMap: pixelMap[i],
srcMap: NARROW[list.rest.first],
xOffset: NARROW[list.rest.rest.first, REF INTEGER]^,
yOffset: NARROW[list.rest.rest.rest.first, REF INTEGER]^,
center: NARROW[list.rest.rest.rest.rest.first, REF BOOLEAN]^
];
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^];
};
GetPixelsFromViewer: PUBLIC PROC[ context: Context, dstMap: PixelMap ] ~ {
Transfers pixels from viewer into supplied Pixel Map
context.class.drawInViewer[
context,
NEW[ImagerProcRec ← [SnatchPixels, LIST[dstMap]]]
];
};
SnatchPixels: ImagerProc ~ {
DoGetPixels: PROC[pixelMap: PixelMap] ~ {
dstMap: PixelMap ← NARROW[NARROW[data, LORA].first];
FOR i: NAT IN [0..pixelMap.samplesPerPixel) DO
TransferSamples[ dstMap: dstMap[i], srcMap: pixelMap[i], xOffset: 0, yOffset: 0 ];
ENDLOOP;
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoGetPixels, context.viewPort^];
};
TransferSamples: PROC[ dstMap, srcMap: SampleMap, xOffset, yOffset: INTEGER,
        center: BOOLEANTRUE ] ~ {
srcBox: Box ← ImagerSample.GetBox[srcMap]; dstBox: Box ← ImagerSample.GetBox[dstMap];
dstMin, srcMin, size: IntegerPair;
[dstMin, srcMin, size] ← ComputeBox[dstBox, srcBox, xOffset, yOffset, center];
ImagerSample.BasicTransfer[
dst: dstMap, src: srcMap,
dstMin: [f: dstMin.x, s: dstMin.y], -- position
srcMin: [f: srcMin.x, s: srcMin.y],
size: [f: size.x, s: size.y]    -- clip
];
[
IF center
THEN {     -- Shift larger sample map to fit in center of smaller, then offset
xDelta ← xOffset + (INTEGER[SF.SizeF[srcBox]] - SF.SizeF[dstBox]) /2;
yDelta ← -yOffset + (INTEGER[SF.SizeS[srcBox]] - SF.SizeS[dstBox]) /2;
}
ELSE {        -- shift tio align with bottom of viewport, then offset
xDelta ← xOffset;
yDelta ← -yOffset + (INTEGER[SF.SizeS[srcBox]] - SF.SizeS[dstBox]);
};
ImagerSample.BasicTransfer[
dst: dstMap, src: srcMap,
dstMin: [f: dstBox.min.f + MAX[0, -xDelta], s: dstBox.min.s + MAX[0, -yDelta]], -- position
srcMin: [f: srcBox.min.f + MAX[0, xDelta], s: srcBox.min.s + MAX[0, yDelta]],
size: [ f: MIN[ SF.SizeF[srcBox] - MAX[0, xDelta], SF.SizeF[dstBox] - MAX[0, -xDelta] ],
  s: MIN[ SF.SizeS[srcBox] - MAX[0, yDelta], SF.SizeS[dstBox] - MAX[0, -yDelta] ]
  ]  -- clip
];
};
ComputeBox: PROC[ box1, box2: Box, xOffset, yOffset: INTEGER, center: BOOLEANTRUE ]
    RETURNS[box1Pos, box2Pos, size: IntegerPair] ~ {
xDelta, yDelta: INTEGER ← 0;
IF center
THEN {     -- Shift larger sample map to fit in center of smaller, then offset
xDelta ← xOffset + (INTEGER[SF.SizeF[box2]] - SF.SizeF[box1]) /2;
yDelta ← -yOffset + (INTEGER[SF.SizeS[box2]] - SF.SizeS[box1]) /2;
}
ELSE {        -- shift tio align with bottom of viewport, then offset
xDelta ← xOffset;
yDelta ← -yOffset + (INTEGER[SF.SizeS[box2]] - SF.SizeS[box1]);
};
box1Pos ← [x: box1.min.f + MAX[0, -xDelta], y: box1.min.s + MAX[0, -yDelta]]; -- position
box2Pos ← [x: box2.min.f + MAX[0, xDelta], y: box2.min.s + MAX[0, yDelta]];
size ← [ x: MIN[ SF.SizeF[box2] - MAX[0, xDelta], SF.SizeF[box1] - MAX[0, -xDelta] ], -- clip
  y: MIN[ SF.SizeS[box2] - MAX[0, yDelta], SF.SizeS[box1] - MAX[0, -yDelta] ] ];
};
WriteFromViewer: ImagerProc ~ {
PROC[ context: Context, imagerCtx: Imager.Context, data: REF ANYNIL ];
DoWrite: PROC[pixelMap: PixelMap] ~ {
list: LORANARROW[data];
i: NATNARROW[list.first, REF NAT]^;
fileName: ROPENARROW[list.rest.first];
ConvertRasterObject.AISFromSampleMap[fileName, pixelMap[i]];
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^];
};
PutAIS: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE ← "Temp.ais",
       doEverything: BOOLEANFALSE ] ~ {
addOn: NATIF context.antiAliasing THEN 1 ELSE 0;
numFiles: NAT;
names: ARRAY[0..4) OF Rope.ROPEALL[NIL];
IF context.depthBuffering THEN addOn ← addOn + 1;
fileRoot ← G3dRender.PrependWorkingDirectory[context, fileRoot];
SELECT context.class.displayType FROM
$Dithered, $PseudoColor => { names[0] ← NIL; numFiles ← 1; };
$Gray => { names[0] ← "-gray"; numFiles ← 1; };
$FullColor, $ImagerFullClr  => {
names[0] ← "-red"; names[1] ← "-grn"; names[2] ← "-blu"; numFiles ← 3;
};
ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Bad RenderMode"];
IF context.antiAliasing AND doEverything
THEN { names[numFiles] ← "-alpha"; numFiles ← numFiles + 1; };
IF context.depthBuffering AND doEverything
THEN { names[numFiles] ← "-depth"; numFiles ← numFiles + 1; };
G3dSortandDisplay.ValidateContext[context];
FOR i: NAT IN [0..numFiles) DO   -- write a file for each pixel entry
IF context.viewer # NIL
THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
context.class.drawInViewer[
context,
NEW[ImagerProcRec ← [
WriteFromViewer,
LIST[ NEW[NAT ← i], PasteInLabel[fileRoot, names[i]] ]
]]
]
}
ELSE {
box: Box;
IF context.viewPort # NIL
THEN {
box ← [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]],
  [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ];
box.max.s ← box.max.s + box.min.s; box.max.f ← box.max.f + box.min.f;
}
ELSE box ← context.pixels.box;
IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ConvertRasterObject.AISFromSampleMap[
PasteInLabel[fileRoot, names[i]],
ImagerSample.Clip[context.pixels[i], box]
];
};
ENDLOOP;
};
WriteRGBFromViewer: ImagerProc ~ {
PROC[ context: Context, imagerCtx: Imager.Context, data: REF ANYNIL ];
DoWrite: PROC[pixelMap: PixelMap] ~ {
list: LORANARROW[data];
fileName: ROPENARROW[list.first];
RGBFromSampleMaps[context, fileName, pixelMap[0], pixelMap[1], pixelMap[2] ];
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^];
};
PutRGB: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE ← "Temp.rgb" ] ~ {
fileRoot ← G3dRender.PrependWorkingDirectory[context, fileRoot];
IF context.class.displayType # $FullColor
THEN SIGNAL G3dRender.Error[$MisMatch, "Must be a full-color image"];
G3dSortandDisplay.ValidateContext[context];
IF context.pixels # NIL
THEN RGBFromSampleMaps[ context, fileRoot,
         context.pixels[0], context.pixels[1], context.pixels[2] ]
ELSE IF context.viewer # NIL THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
context.class.drawInViewer[
context,
NEW[ImagerProcRec ← [ WriteRGBFromViewer, LIST[ fileRoot ] ]]
]
}
};
RGBFromSampleMaps: PROC[ context: Context, fileName: Rope.ROPE,
         rMap, gMap, bMap: SampleMap ] ~ {
out: IO.STREAMFS.StreamOpen[fileName: fileName, accessOptions: create];
box: Box ← ImagerSample.GetBox[rMap];
xSize: NAT ← box.max.f - box.min.f;
ySize: NAT ← box.max.s - box.min.s;
yMin: INTEGER ← (486 - ySize) / 2;
xMin: INTEGER ← (720 - xSize) / 2;
rBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
gBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
bBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
FOR y: NAT IN [0..485] DO
IF y >= yMin AND y < yMin + ySize THEN { -- get a line from sample map
mpx: NAT ← box.min.f;
mpy: NAT ← y - yMin + box.min.s;
ImagerSample.GetSamples[map: rMap, initIndex: [mpy, mpx], buffer: rBuf, count: xSize];
ImagerSample.GetSamples[map: gMap, initIndex: [mpy, mpx], buffer: gBuf, count: xSize];
ImagerSample.GetSamples[map: bMap, initIndex: [mpy, mpx], buffer: bBuf, count: xSize];
};
FOR x: NAT IN [0..719] DO
r,g,b: BYTE;
IF y < yMin OR y > yMin + ySize OR x < xMin OR x > xMin + xSize
THEN r ← g ← b ← 0
ELSE { r ← rBuf[x - xMin]; g ← gBuf[x - xMin]; b ← bBuf[x - xMin]; };
IO.PutChar[out, VAL[r]]; IO.PutChar[out, VAL[g]]; IO.PutChar[out, VAL[b]];
ENDLOOP;
IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ENDLOOP;
IO.Close[out];
};
Frame Generation and Animation
MakeFrame: PUBLIC ContextProc ~ {
refreshProc: CedarProcess.Process ← NIL;
tmpViewer: ViewerClasses.Viewer;
tmpContext: Context;
The following blows up if the viewPort is NIL (As it often can be) !!!
G3dSortandDisplay.ValidateContext[context]; -- ensures viewer, viewPort and pixel store updated
IF context.viewer # NIL
THEN tmpContext ← G3dRender.GetTmpContext[context] -- get modifiable context
ELSE tmpContext ← context;    -- drawing into memory or directly on display
{ ENABLE UNWIND => CedarProcess.Abort[refreshProc]; -- in case of aborts, etc.
IF NOT tmpContext.doVisibly        -- buffering, copy over when done
THEN {
tmpViewer ← tmpContext.viewer; tmpContext.viewer ← NIL;
tmpContext.pixels ← context.pixels;
IF tmpContext.pixels = NIL THEN {
G3dRenderWithPixels.AllocatePixelMemory[context];   --gets pixelstore
tmpContext.pixels ← context.pixels;
};
}
ELSE IF tmpContext.viewer # NIL AND tmpContext.pixels # NIL
AND
tmpContext.pixels.samplesPerPixel > 1 THEN {
Use forked process to copy bits over periodically, too complex to do onscreen
refreshProc ← CedarProcess.Fork[UpdateViewer, context]; -- start refresh
tmpViewer ← tmpContext.viewer; tmpContext.viewer ← NIL;
};
IF GetProp[ context.displayProps, $ScreenPixels ] # NIL -- full color, no vwr
THEN refreshProc ← CedarProcess.Fork[PeriodicUpdateFullColorDisplay, context];
G3dRenderWithPixels.MakeFrame[tmpContext];
};
IF refreshProc # NIL THEN CedarProcess.Abort[refreshProc];  -- kill periodic update
IF NOT tmpContext.doVisibly OR tmpViewer # NIL THEN {
tmpContext.viewer ← tmpViewer;
context.class.drawInViewer[ tmpContext, NEW[ImagerProcRec ← [StuffBuf, NIL]] ];
};
IF refreshProc # NIL THEN {
[] ← CedarProcess.Join[refreshProc]; -- wait for refreshProc done
If PeriodicUpdateFullColorDisplay, in its final pass, missed some pixels being rendered:
UpdateFullColorDisplay[context];
};
};
StuffBuf: PUBLIC ImagerProc ~ {
DoTransfer: PROC[pixelMap: PixelMap] ~ {
FOR i: NAT IN [0..samplesPerColor) DO
ImagerSample.Transfer[dst: pixelMap[i], src: context.pixels[i], delta: pixelMap.box.min];
ENDLOOP;
};
samplesPerColor: NATIF context.class.displayType = $FullColor THEN 3 ELSE 1;
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoTransfer, context.viewPort^];
};
MakeHiResFrame: PUBLIC PROC[ context: Context, width, height: NAT, name: ROPE] ~ {
mode: DisplayMode;
hiResCtxt: Context;
SELECT context.class.displayType FROM
$Gray   => mode ← gray;
$PseudoColor => mode ← dither;
ENDCASE  => mode ← fullColor;
hiResCtxt ← G3dRender.CreateUndisplayedContext[
oldContext: context, width: width, height: height, displayMode: mode, keepLog: TRUE
];
hiResCtxt.viewer ← NIL;
G3dRenderWithPixels.AntiAliasing[hiResCtxt];
IF context.depthBuffering THEN G3dRenderWithPixels.DepthBuffering[hiResCtxt];
hiResCtxt.changed ← TRUE;       -- ensure screen coordinates get updated
hiResCtxt.class.render[hiResCtxt];
G3dRender.StoreImage[hiResCtxt, name ];       -- store resulting image
};
DitherImage: PUBLIC PROC[dstContext, rgbContext: Context] ~ {
Action: PROC ~ {
width: NAT ← Real.Fix[MIN[dstContext.viewPort.w, rgbContext.viewPort.w] ];
height: NAT ← Real.Fix[MIN[dstContext.viewPort.h, rgbContext.viewPort.h] ];
scanSegIn: PixelBuffer ← ImagerPixel.NewPixels[rgbContext.pixels.samplesPerPixel, width];
scanSegOut: PixelBuffer ← ImagerPixel.NewPixels[dstContext.pixels.samplesPerPixel, width];
IF rgbContext.pixels.samplesPerPixel < 3
THEN SIGNAL G3dRender.Error[$MisMatch, "24-bit input needed for dithering"];
FOR y: NAT IN [0..height) DO
ImagerPixel.GetPixels[          -- get rgb pixels
self: rgbContext.pixels, pixels: scanSegIn, initIndex: [f: 0, s: y], count: width ];
ImagerPixel.GetPixels[          -- get rgb pixels
self: dstContext.pixels, pixels: scanSegOut, initIndex: [f: 0, s: y], count: width ];
FOR x: NAT IN [0..width) DO
scanSegOut[0][x] ← DitheredRGB[$PseudoColor, x, y,
          scanSegIn[0][x], scanSegIn[1][x], scanSegIn[2][x] ];
ENDLOOP;
ImagerPixel.PutPixels[          -- store result in foreground
self: dstContext.pixels, pixels: scanSegOut, initIndex: [f: 0, s: y], count: width ];
ENDLOOP;
};
CedarProcess.DoWithPriority[background, Action];    -- be nice to other processess
};
ditherTable: ARRAY [0..4) OF ARRAY [0..4) OF NAT
             [[0,12,3,15], [8,4,11,7], [2,14,1,13], [10,6,9,5]];
DitheredRGB: PROC[renderMode: ATOM, x, y, red, grn, blu: INTEGER] RETURNS[INTEGER] ~ {
val2R, val2G, val2B, pixelValue: NAT;
SELECT renderMode FROM
$PseudoColor => {
Ordered dither for crude looks at full-color
threshold: NAT ← ditherTable[ Basics.BITAND[x,3] ][ Basics.BITAND[y,3] ];
valR: NAT ← Basics.BITSHIFT[ Basics.BITSHIFT[red,2] + red, -4 ];  -- (red * 5) / 16
valG: NAT ← Basics.BITSHIFT[ Basics.BITSHIFT[grn,2] + Basics.BITSHIFT[grn,1], -4 ];
valB: NAT ← Basics.BITSHIFT[ Basics.BITSHIFT[blu,2] + blu, -4 ];  -- (blu * 5) / 16
val2R ← Basics.BITSHIFT[valR,-4];           -- valR / 16
IF Basics.BITAND[valR,15] > threshold THEN val2R ← val2R + 1;  -- valr MOD 16
val2G ← Basics.BITSHIFT[valG,-4];
IF Basics.BITAND[valG,15] > threshold THEN val2G ← val2G + 1;
val2B ← Basics.BITSHIFT[valB,-4];
IF Basics.BITAND[valB,15] > threshold THEN val2B ← val2B + 1;
RETURN[ MIN[ 255,
Basics.BITSHIFT[val2R,5] + Basics.BITSHIFT[val2R,3] + Basics.BITSHIFT[val2R,1]
+ Basics.BITSHIFT[val2G,2] + Basics.BITSHIFT[val2G,1]
+ val2B + 2 ] ];     --val2R*42 + val2G*6 + val2B + 2
};
$ImagerDithered => {
Ordered dither for crude looks at full-color using Imager's color map
threshold: NAT ← ditherTable[x MOD 4][y MOD 4];
valR: NAT ← 4* red / 16;
valG: NAT ← 5* grn / 16;
valB: NAT ← 3* blu / 16;
val2R ← valR/16; IF valR MOD 16 > threshold THEN val2R ← val2R + 1;
val2G ← valG/16; IF valG MOD 16 > threshold THEN val2G ← val2G + 1;
val2B ← valB/16; IF valB MOD 16 > threshold THEN val2B ← val2B + 1;
pixelValue ← val2R*24 + val2G*4 + val2B;
IF pixelValue >= 60 THEN pixelValue ← pixelValue + 135;  -- move to top of map
RETURN[ MIN[255, pixelValue] ];
};
ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected display type"];
RETURN[ 255 ];
};
Init[];
END.