ImageTwiddleImpl.mesa
Last Edited by: Crow, April 4, 1989 11:21:55 am PDT
Bloomenthal, September 26, 1988 12:04:42 pm PDT
DIRECTORY
Basics     USING [ BITSHIFT ],
Atom     USING [ DottedPair, PropList, GetPropFromList ],
CedarProcess   USING [ CheckAbort, DoWithPriority ],
Terminal    USING [ ColorMode, GetColor, GetColorMode,
         GetBlueMap, GetGreenMap, GetRedMap, SetColor, SetBlueMap,
         SetGreenMap, SetRedMap, Virtual, WaitForBWVerticalRetrace ],
IO      USING [ PutRope, STREAM ] ,
Rope     USING [ Cat, ROPE ] ,
Convert    USING [ RopeFromCard ] ,
Real     USING [ Fix, Round ],
RealFns    USING [ Power ],
Imager    USING [ Context, SetColor ],
ImagerBackdoor  USING [ AccessBufferRectangle ],
ImagerPixel   USING [ GetPixels, NewPixels, ObtainScratchPixels, PixelBuffer, PixelMap,
         PutPixels, ReleaseScratchPixels ],
ImagerSample  USING [ Fill ],
ImagerColor   USING [ ColorFromRGB ],
ImagerColorFns  USING [ RGBFromHSL ],
NamedColors   USING [ RopeToHSL ],
ThreeDBasics  USING [ ClipState, Context, ContextClass, Error, ImagerProc,
         ImagerProcRec, IntegerPair, NatSequence, Patch,
         Quad, RGB, ShadingValue, ShapeSequence, Triple, Vertex ],
ThreeDViewer  USING [ DrawInViewer ],
SceneUtilities  USING [ FlushLog ],
RenderWithPixels USING [ BufferRendering ],
ColorDisplayRender USING [ DitherImage, StuffBuf ],
ImageTwiddle  USING [ ];
ImageTwiddleImpl: CEDAR MONITOR
IMPORTS Atom, Basics, CedarProcess, ColorDisplayRender, Convert, Imager, ImagerBackdoor, ImagerColor, ImagerColorFns, ImagerPixel, ImagerSample, IO, NamedColors, Real, RealFns, RenderWithPixels, Rope, SceneUtilities, Terminal, ThreeDBasics, ThreeDViewer
EXPORTS ImageTwiddle
~ BEGIN
Types
Context: TYPE ~ ThreeDBasics.Context;
ContextClass: TYPE ~ ThreeDBasics.ContextClass;
Triple: TYPE ~ ThreeDBasics.Triple;
RGB: TYPE ~ ThreeDBasics.RGB;
IntegerPair: TYPE ~ ThreeDBasics.IntegerPair;
IntRGB: TYPE ~ RECORD[ r, g, b: CARDINAL];
IntRGBSequence: TYPE ~RECORD [ SEQUENCE length: NAT OF IntRGB ];
NatSequence: TYPE ~ ThreeDBasics.NatSequence;
Patch: TYPE ~ ThreeDBasics.Patch;
ClipState: TYPE ~ ThreeDBasics.ClipState;
ImagerProc: TYPE ~ ThreeDBasics.ImagerProc;
ImagerProcRec: TYPE ~ ThreeDBasics.ImagerProcRec;
PixelMap: TYPE ~ ImagerPixel.PixelMap;
Colors
SetNamedColor: PUBLIC PROC [imagerCtx: Imager.Context, color: Rope.ROPE] ~ {
clr: RGB ← ImagerColorFns.RGBFromHSL[ NamedColors.RopeToHSL[color] ];
Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ];
};
SetRGBColor: PUBLIC PROC [imagerCtx: Imager.Context, clr: RGB] ~ {
Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ];
};
Rotate8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual, firstValue, lastValue, duration: NAT ← 0] ~ { 
Rotates color map entries in given range
r, g, b: ARRAY [0..256) OF [0..256);
times: NAT;
IF lastValue = 0 THEN lastValue ← 255;
FOR i: NAT IN [firstValue .. lastValue] DO
[r[i], g[i], b[i]] ← Terminal.GetColor[ vt, i];
ENDLOOP;
IF duration = 0
THEN times ← 0          -- will be one complete cycle
ELSE times ← duration * 75;      -- seconds times fields per second
times ← times +            -- Fill out full cycle
  (lastValue - firstValue + 1) - times MOD (lastValue - firstValue + 1);
FOR i: NAT IN [0..times) DO
tr, tg, tb: [0..256);
CedarProcess.CheckAbort[];   -- check for external abort request before proceeding
vt.WaitForBWVerticalRetrace[];    -- await top of scan (to control update rate)
tr ← r[firstValue]; tg ← g[firstValue]; tb ← b[firstValue];
FOR j: NAT IN (firstValue .. lastValue] DO
r[j-1] ← r[j]; g[j-1] ← g[j]; b[j-1] ← b[j];
ENDLOOP;
r[lastValue] ← tr; g[lastValue] ← tg; b[lastValue] ← tb;
FOR i: NAT IN [firstValue .. lastValue] DO
Terminal.SetColor[ vt, i, 0, r[i], g[i], b[i]];
ENDLOOP;
ENDLOOP;
};
Show8BitClrMap: PUBLIC PROC [context: REF Context] ~ {
ThreeDViewer.DrawInViewer[ context, NEW[ImagerProcRec ← [ViewerShowClrMap, NIL] ] ];
};
ViewerShowClrMap: ImagerProc ~ {
DoShowClrMap: PROC[pixelMap: PixelMap] ~ {
firstBottom: INTEGER ← Real.Fix[.75 * context.viewPort.h];
size: INTEGER ← Real.Fix[ .025 * MIN[context.viewPort.h, context.viewPort.w] ];
firstTop: INTEGER ← firstBottom + size;
firstLeft: INTEGER ← Real.Fix[context.viewPort.w/2.0] - 16*size;
firstRight: INTEGER ← firstLeft + size;
FOR i: NAT IN [0..256) DO
top: NAT ← firstTop + size * (i / 32);
bottom: NAT ← firstBottom + size * (i / 32);
left: NAT ← firstLeft + size * (i MOD 32);
right: NAT ← firstRight + size * (i MOD 32);
ImagerSample.Fill[pixelMap[0], [min: [f: left, s: bottom], max: [f: right, s: top] ], i];
ENDLOOP;
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoShowClrMap, context.viewPort^];
};
ShowMapOnLog: PUBLIC PROC [context: REF Context, vt: Terminal.Virtual] ~ {
Print color map on log for context
RopeFromQuad: PROC[ i, r, g, b: CARD] RETURNS[ Rope.ROPE ] ~ {
output: Rope.ROPE ← Rope.Cat[ " \t ", Convert.RopeFromCard[i], " ",
           Convert.RopeFromCard[r], " " ];
output ← Rope.Cat[ output, Convert.RopeFromCard[g], " ", Convert.RopeFromCard[b] ];
RETURN[ output ];
};
log: IO.STREAMNARROW[ Atom.GetPropFromList[context.props, $Log] ];
state: Terminal.ColorMode ← vt.GetColorMode[];
maxVal: REALIF state.full
THEN 255.0
ELSE REAL[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1];
FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO      -- linear ramp exponentiated
jr, jg, jb: CARD;
IF Terminal.GetColorMode[vt].full
THEN {
jr ← vt.GetRedMap[i];
jg ← vt.GetGreenMap[i];
jb ← vt.GetBlueMap[i];
}
ELSE [jr, jg, jb] ← vt.GetColor[i];
IF log # NIL THEN {
log.PutRope[ RopeFromQuad[ i, jr, jg, jb ] ];
IF (i/4) * 4 = i THEN log.PutRope["\n"];
SceneUtilities.FlushLog[context];
};
ENDLOOP;
};
LoadMultiRamps: PUBLIC PROC [vt: Terminal.Virtual, colors: LIST OF RGB] ~ {
clr: REF IntRGBSequence ← NEW[ IntRGBSequence[32] ];
numClrs, thisClr, nextClr, rampLength: NAT ← 0;
state: Terminal.ColorMode ← vt.GetColorMode[];
maxVal: REALIF state.full
THEN 255.0
ELSE REAL[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1];
WHILE colors # NIL DO
clr[numClrs].r ← Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.R]] ];
clr[numClrs].g ← Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.G]] ];
clr[numClrs].b ← Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.B]] ];
numClrs ← numClrs + 1;
colors ← colors.rest;
ENDLOOP;
rampLength ← Real.Fix[maxVal] / (numClrs-1);
FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO
jr, jg, jb: [0..256);
position: NAT ← i MOD rampLength;
IF position = 0 THEN IF nextClr < numClrs-1
THEN { thisClr ← nextClr; nextClr ← nextClr + 1; }
ELSE thisClr ← nextClr;
jr ← clr[thisClr].r * (rampLength - position) / rampLength
+ clr[nextClr].r * position / rampLength;
jg ← clr[thisClr].g * (rampLength - position) / rampLength
 + clr[nextClr].g * position / rampLength;
jb ← clr[thisClr].b * (rampLength - position) / rampLength
 + clr[nextClr].b * position / rampLength;
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;
};
ThresholdClrMap: PUBLIC PROC [vt: Terminal.Virtual, levels: NAT] ~ {
set color map to constant steps simulating restricted number of grey levels
Linearize: PROC [value: REAL] RETURNS[NAT] ~ {
RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ];
};
FOR i: NAT IN [0..256) DO              -- grayscale
j: NAT ← (i * levels / 256) * (256 / levels);
gray: NAT ← Linearize[j];
vt.SetColor[i, 0, gray, gray, gray];
ENDLOOP;
};
threshTable: REF NatSequence ← NEW[ NatSequence[256] ];
threshLevels: NAT ← 0;
ThresholdImage: PUBLIC PROC [context: REF Context, levels: NAT] ~ {
threshLevelslevels;
IF context.viewer # NIL            -- do through viewer
THEN context.class.drawInViewer[context, NEW[ImagerProcRec ← [ThresholdImage2, NIL]]]
ELSE DoThresholdImage[context.pixels];   -- do directly
};
ThresholdImage2: ImagerProc ~ {
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoThresholdImage, context.viewPort^];
};
DoThresholdImage: PROC [pixelMap: PixelMap] ~ {
adjust pixel values over whole image to conform restricted number of grey levels
Action: PROC ~ {
Linearize: PROC [value: REAL] RETURNS[NAT] ~ {
RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ];
};
maxValue: REAL ← 255.0;
xStart: NAT ← Real.Round[pixelMap.box.min.f];
yStart: NAT ← Real.Round[pixelMap.box.min.s];
width: NAT ← Real.Round[pixelMap.box.max.f - pixelMap.box.min.f];
height: NAT ← Real.Round[pixelMap.box.max.s - pixelMap.box.min.s];
scanSeg: ImagerPixel.PixelBuffer ← ImagerPixel.NewPixels[
pixelMap.samplesPerPixel, width
];
FOR i: NAT IN [0..256) DO
j: NAT ← (i * threshLevels / 256) * (256 / threshLevels);
threshTable[i] ← Linearize[j];
ENDLOOP;
FOR y: NAT IN [ yStart .. height + yStart ) DO
ImagerPixel.GetPixels[ self: pixelMap, pixels: scanSeg,
       initIndex: [f: xStart, s: y], count: width ];
FOR x: NAT IN [ 0 .. width ) DO
IF scanSeg.samplesPerPixel > 2
THEN {
scanSeg[0][x] ← threshTable[scanSeg[0][x]];
scanSeg[1][x] ← threshTable[scanSeg[1][x]];
scanSeg[2][x] ← threshTable[scanSeg[2][x]];
}
ELSE scanSeg[0][x] ← threshTable[scanSeg[0][x]];
ENDLOOP;
ImagerPixel.PutPixels[ self: pixelMap, pixels: scanSeg,
       initIndex: [f: xStart, s: y], count: width ];
ENDLOOP;
};
CedarProcess.DoWithPriority[background, Action];
};

AdjustValueRamp: PUBLIC PROC[context: REF Context, exponent: RGB] ~ {
Adjust the values in the display memory for TRC or ? (only for full-color or gray)
Action: PROC[] ~ {
maxValue: REAL ← 255.0;
xStart: NAT ← Real.Round[context.viewPort.x];
yStart: NAT ← Real.Round[context.viewPort.x];
width: NAT ← Real.Round[context.viewPort.w];
height: NAT ← Real.Round[context.viewPort.h];
expTableR: REF NatSequence ← NEW[ NatSequence[256] ];
expTableG: REF NatSequence ← NEW[ NatSequence[256] ];
expTableB: REF NatSequence ← NEW[ NatSequence[256] ];
scanSeg: ImagerPixel.PixelBuffer ← ImagerPixel.NewPixels[
context.pixels.samplesPerPixel, width
];
SELECT context.class.displayType FROM
$FullColor  => FOR i: NAT IN [0..256) DO
expTableR[i] ← Real.Round[ RealFns.Power[ i/255.0, exponent.R] * 255.0 ];
expTableG[i] ← Real.Round[ RealFns.Power[ i/255.0, exponent.G] * 255.0 ];
expTableB[i] ← Real.Round[ RealFns.Power[ i/255.0, exponent.B] * 255.0 ];
ENDLOOP;
$Gray   => {
exp: REAL ← (exponent.R + exponent.G + exponent.B) / 3.0;
FOR i: NAT IN [0..256) DO
expTableG[i] ← Real.Round[ RealFns.Power[ i/255.0, exp] * 255.0 ];
ENDLOOP;
};
ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Improper renderMode"]];
FOR y: NAT IN [ yStart .. height + yStart ) DO
ImagerPixel.GetPixels[ self: context.pixels, pixels: scanSeg,
       initIndex: [f: xStart, s: y], count: width ];
FOR x: NAT IN [ 0 .. width ) DO
SELECT context.class.displayType FROM
$FullColor  => {
scanSeg[0][x] ← expTableR[scanSeg[0][x]];
scanSeg[1][x] ← expTableG[scanSeg[1][x]];
scanSeg[2][x] ← expTableB[scanSeg[2][x]];
};
$Gray   => scanSeg[0][x] ← expTableG[scanSeg[0][x]];
ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Improper renderMode"]];
ENDLOOP;
ImagerPixel.PutPixels[ self: context.pixels, pixels: scanSeg,
       initIndex: [f: xStart, s: y], count: width ];
ENDLOOP;
};
CedarProcess.DoWithPriority[background, Action];
};
AdjustSaturation: PUBLIC PROC[context: REF Context, percent: REAL] ~ {
Adjust the values in the display memory for Saturation (only for full-color)
Action: PROC[] ~ {
maxValue: REAL ← 255.0;
xStart: NAT ← Real.Round[context.viewPort.x];
yStart: NAT ← Real.Round[context.viewPort.x];
width: NAT ← Real.Round[context.viewPort.w];
height: NAT ← Real.Round[context.viewPort.h];
scanSeg: ImagerPixel.PixelBuffer ← ImagerPixel.NewPixels[
context.pixels.samplesPerPixel, width
];
FOR y: NAT IN [ yStart .. height + yStart ) DO
ImagerPixel.GetPixels[ self: context.pixels, pixels: scanSeg,
       initIndex: [f: xStart, s: y], count: width ];
FOR x: NAT IN [ 0 .. width ) DO
SELECT context.class.displayType FROM
$FullColor  => {
scale: REAL;
redVal: CARDINAL ← scanSeg[0][x];
grnVal: CARDINAL ← scanSeg[1][x];
bluVal: CARDINAL ← scanSeg[2][x];
min: CARDINALMIN[redVal, grnVal, bluVal];
max: CARDINALMAX[redVal, grnVal, bluVal];
newMin: CARDINALMIN[max, INTEGER[Real.Fix[percent * min]]];
IF (min = 0) OR (max - min = 0) THEN LOOP;
scale ← 1.0 * (max - newMin) / (max - min);
redVal ← Real.Fix[ (redVal - min) * scale + newMin ];
grnVal ← Real.Fix[ (grnVal - min) * scale + newMin ];
bluVal ← Real.Fix[ (bluVal - min) * scale + newMin ];
scanSeg[0][x] ← redVal;
scanSeg[1][x] ← grnVal;
scanSeg[2][x] ← bluVal;
};
ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Only for full color"]];
ENDLOOP;
ImagerPixel.PutPixels[ self: context.pixels, pixels: scanSeg,
       initIndex: [f: xStart, s: y], count: width ];
ENDLOOP;
};
CedarProcess.DoWithPriority[background, Action];
};
LoadOldStd8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual] ~ {
Linearize: PROC [value: REAL] RETURNS[NAT] ~ {
RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ];
};
FOR i: NAT IN [0..40) DO              -- grayscale
gray: NAT ← Linearize[i*6.5];
vt.SetColor[i + 216, 0, gray, gray, gray];
ENDLOOP;
FOR i: NAT IN [0..216) DO          -- 6 x 6 x 6 color cube
vt.SetColor[i, 0,
Linearize[42.5 * (i/36 + 1)],
Linearize[42.5 * ((i/6) MOD 6 + 1)],
Linearize[42.5 * (i MOD 6 + 1)]
];
ENDLOOP;
vt.SetColor[0, 0, 0, 0, 0];
};
SetUpTerrainColors: PUBLIC PROC[vt: Terminal.Virtual] ~ {
setting up colors for old terrain pictures
terrainHues: ARRAY [0..4) OF RGB ← [[.05, .4, .0], [.5, .6, .1], [.4, .4, .4], [1., 1., 1.]];
{ forest, savannah, rock, snow }
skyHue: RGB ← [.5, .5, 1.0];
waterHue: RGB ← [.2, .2, .8];
start: NAT ← 8;      -- start^ ← 8;
length: NAT ← 240;
valueStepSize, minValue, rampValue, index, hueRampSize: NAT ← 0;
hueRampSize ← 238 / (LENGTH[terrainHues] + 1);
valueStepSize ← 256 / hueRampSize;
minValue ← 256 - valueStepSize*hueRampSize;
FOR j: NAT IN [0..4) DO     -- do hue steps from each hue to next
currentHue: RGB ← [terrainHues[j].R, terrainHues[j].G, terrainHues[j].B];
rampValue ← minValue;
FOR i: NAT IN [0..hueRampSize) DO   -- build hue ramps
r, g, b: NAT;
r ← Real.Round[ rampValue * currentHue.R ];
g ← Real.Round[ rampValue * currentHue.G ];
b ← Real.Round[ rampValue * currentHue.B ];
vt.SetColor[index+start, 0, r, g, b ];
rampValue ← rampValue + valueStepSize;
index ← index + 1;
ENDLOOP;
ENDLOOP;
vt.SetColor[239+start, 0,
   Real.Fix[skyHue.R*255], Real.Fix[skyHue.G*255], Real.Fix[skyHue.B*255] ];
vt.SetColor[start, 0,
  Real.Fix[waterHue.R*255], Real.Fix[waterHue.G*255], Real.Fix[waterHue.B*255] ];
};
AIS Files and Texture Images
DitherImage: PUBLIC PROC[dstContext, rgbContext: REF Context] ~ {
ColorDisplayRender.DitherImage[dstContext, rgbContext];   -- convenience call
};
ScaleDownImage: PUBLIC PROC[dstContext, srcContext: REF Context, strtchFctr: REAL ← 1.0] ~{
Bresenham-like walk across image, areas averaged down
Action: PROC ~ {
srcHght: NAT ← Real.Round[srcContext.viewPort.h];
dstHght: NAT ← Real.Round[dstContext.viewPort.h];
srcWdth: NAT ← Real.Round[srcContext.viewPort.w];
dstWdth: NAT ← Real.Round[dstContext.viewPort.w];
numHits: REF NatSequence ← NEW[ NatSequence[dstWdth] ]; -- holds divisor for average
xPos, yPos: INTEGER;
xCtr, yCtr: NAT ← 0;
scanSegIn, scanSegOut: ImagerPixel.PixelBuffer;
scanSegIn ← ImagerPixel.ObtainScratchPixels[ srcContext.pixels.samplesPerPixel, srcWdth ];
scanSegOut ← ImagerPixel.ObtainScratchPixels[ dstContext.pixels.samplesPerPixel, dstWdth ];
FOR x: NAT IN [0..dstWdth) DO          -- clear the pixels
FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] ← 0; ENDLOOP;
ENDLOOP;
yPos ← 2 * dstHght - srcHght;       -- offset for vertical incrementer
FOR x: NAT IN [0..dstWdth) DO numHits[x] ← 0; ENDLOOP;  -- clear divisor array
FOR y: NAT IN [ 0 .. srcHght ) DO     -- work up through source scan lines
ImagerPixel.GetPixels[ self: srcContext.pixels, pixels: scanSegIn,
       initIndex: [f: 0, s: y], count: srcWdth ];
xPos ← 2 * dstWdth - srcWdth;
xCtr ← 0;
FOR x: NAT IN [ 0 .. srcWdth ) DO     -- work across source scan line
FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO
scanSegOut[i][xCtr] ← scanSegOut[i][xCtr] + scanSegIn[i][x]; -- sum intensities
ENDLOOP;
numHits[xCtr] ← numHits[xCtr] + 1;        -- sum hits per pixel
IF xPos > 0 THEN { 
xCtr ← xCtr + 1; xPos ← xPos - 2 * srcWdth; IF xCtr >= dstWdth THEN EXIT;
};
xPos ← xPos + 2 * dstWdth;
ENDLOOP;
IF yPos > 0 THEN {            -- big increment exceeded
FOR x: NAT IN [0..dstWdth) DO
FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO
scanSegOut[i][x] ← Real.Fix[ strtchFctr * scanSegOut[i][x] / numHits[x] ];
ENDLOOP;
numHits[x] ← 0;
ENDLOOP;
ImagerPixel.PutPixels[ self: dstContext.pixels, pixels: scanSegOut,
       initIndex: [f: 0, s: yCtr], count: dstWdth ];
FOR x: NAT IN [0..dstWdth) DO          -- clear the pixels
FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] ← 0; ENDLOOP;
ENDLOOP;
yPos ← yPos - 2 * srcHght;     -- subtract big increment from position
yCtr ← yCtr + 1; IF yCtr >= dstHght THEN EXIT;
};
yPos ← yPos + 2 * dstHght;  -- add in little incrment until big increment exceeded
ENDLOOP;
ImagerPixel.ReleaseScratchPixels[scanSegIn];
ImagerPixel.ReleaseScratchPixels[scanSegOut];
IF dstContext.viewer # NIL THEN    -- make sure image gets copied to screen
ThreeDViewer.DrawInViewer[
dstContext,
NEW[ImagerProcRec ← [ColorDisplayRender.StuffBuf, NIL]]
];
};
IF srcContext.viewPort = NIL THEN srcContext.class.validateDisplay[srcContext];
IF dstContext.viewPort = NIL THEN dstContext.class.validateDisplay[dstContext];
IF dstContext.pixels = NIL THEN RenderWithPixels.BufferRendering[dstContext, TRUE];
CedarProcess.DoWithPriority[background, Action];    -- be nice to other processess
};
END.