BufferedRefreshImpl.mesa
Copyright Ó 1986, 1987, 1988, 1989, 1990, 1992 by Xerox Corporation. All rights reserved.
Contents: Builds up an abstraction of transparent layers onto which shapes can be drawn. Each layer may have a backing pixel map, in which case it need not be redrawn when other layers change. This scheme is used by Gargoyle and Solidviews.
Pier, June 25, 1993 11:37 am PDT
Bier, July 13, 1993 4:35 pm PDT
Doug Wyatt, September 15, 1989 3:49:32 pm PDT
Michael Plass, March 25, 1992 11:51 am PST
DIRECTORY
BufferedRefresh, BufferedRefreshTypes, BufferedRefreshPort, CodeTimer, Imager, ImagerBackdoor, ImagerBitmapContext, ImagerDitherContext, ImagerPixel, ImagerSample, ImagerTransformation, Basics, Real;
BufferedRefreshImpl: CEDAR PROGRAM
IMPORTS BufferedRefreshPort, CodeTimer, Imager, ImagerBackdoor, ImagerBitmapContext, ImagerDitherContext, ImagerPixel, ImagerSample, ImagerTransformation, Basics, Real
EXPORTS BufferedRefresh =
BEGIN OPEN BufferedRefresh, BufferedRefreshTypes;
[Artwork node; type 'ArtworkInterpress on' to command tool]
CreateSandwich: PUBLIC PROC [layers: LIST OF Layer] RETURNS [Sandwich] = {
Layers should be specified in back to front order.
sandwich: Sandwich ~ NEW[SandwichObj];
tail: LIST OF LayerData ¬ NIL;
FOR list: LIST OF Layer ¬ layers, list.rest UNTIL list = NIL DO
layerData: LayerData ~ NEW[LayerDataObj ¬ [name: list.first.name, refreshProc: list.first.refreshProc, wantsMap: list.first.backingMap, hasMap: FALSE, drawBkgnd: list.first.drawBkgnd]];
prev: LIST OF LayerData ~ tail;
tail ¬ CONS[layerData, NIL];
IF prev=NIL THEN sandwich.layers ¬ tail ELSE prev.rest ¬ tail;
ENDLOOP;
RETURN[sandwich];
};
FitSandwichToScreen: PUBLIC PROC [sandwich: Sandwich, cw, ch: INTEGER, screen: Imager.Context, cx, cy: INTEGER ¬ 0] = {
Tells the sandwich how big its buffers need to be. cx and cy specify the position of the lower left hand corner of the buffers.
class: ATOM ~ Imager.GetClass[screen];
IF sandwich.class=class AND sandwich.cw=cw AND sandwich.ch=ch THEN RETURN;
sandwich.cw ¬ cw;
sandwich.ch ¬ ch;
sandwich.cx ¬ cx;
sandwich.cy ¬ cy;
sandwich.class ¬ class;
Construct the layer backing maps.
sandwich.viewerToClient ¬ ImagerTransformation.Scale[1.0];
sandwich.clientToViewer ¬ ImagerTransformation.Scale[1.0];
FOR list: LIST OF LayerData ¬ sandwich.layers, list.rest UNTIL list = NIL DO
layer: LayerData ~ list.first;
bottomLayer: BOOL ~ (list=sandwich.layers);
IF layer.hasMap THEN {
layer.backingMap ¬ NIL;
layer.backingContext ¬ NIL;
layer.hasMap ¬ FALSE;
};
IF layer.wantsMap THEN {
box: ImagerSample.Box ~ [min: [0, 0], max: [ch, cw]];
type: {none, bitmap, dither, cg6} ¬ none;
IF bottomLayer THEN SELECT TRUE FROM
class=$Bitmap => type ¬ bitmap;
Basics.IsBound[LOOPHOLE[ImagerDitherContext.Create]] AND class=ImagerDitherContext.classCode => type ¬ dither;
class=$CG6 => type ¬ cg6; -- instead of class=ImagerCG6Context.classCode so we can still source-share
ENDCASE => type ¬ none
ELSE type ¬ bitmap; -- all layers but the bottom are 1-bit masks
SELECT type FROM
bitmap => {
layer.backingMap ¬ ImagerSample.NewSampleMap[box];
layer.backingContext ¬ ImagerBitmapContext.Create[
deviceSpaceSize: box.max,
scanMode: [down, right],
surfaceUnitsPerInch: [72.0, 72.0],
pixelUnits: TRUE,
fontCacheName: ImagerBitmapContext.classCode];
keyword syntax is used here for compatibility with the Cedar7.0 version of ImagerBitmapContext.Create
ImagerBitmapContext.SetBitmap[layer.backingContext, layer.backingMap];
};
dither => {
The color map is synchronized in case we are running on X or some other platform that can change the color map.
screenMap: SampleMap ~ ImagerDitherContext.GetSampleMap[screen];
bps: ImagerSample.BitsPerSample ~ ImagerSample.GetBitsPerSample[screenMap];
layer.backingMap ¬ ImagerSample.NewSampleMap[box, bps];
layer.backingContext ¬ ImagerDitherContext.Create[box.max, [down, right],
[72.0, 72.0], TRUE, ImagerDitherContext.classCode];
BufferedRefreshPort.SetDitherMapFromScreen[layer.backingContext, screen];
ImagerDitherContext.SetSampleMap[layer.backingContext, layer.backingMap];
};
cg6 => {
bps: ImagerSample.BitsPerSample ~ 8;
layer.backingMap ¬ ImagerSample.NewSampleMap[box, bps];
layer.backingContext ¬ BufferedRefreshPort.CreateCG6Context[box.max, TRUE, $CG6];
BufferedRefreshPort.SetCG6DitherMapFromScreen[layer.backingContext, screen];
BufferedRefreshPort.SetCG6SampleMap[layer.backingContext, layer.backingMap];
};
ENDCASE;
layer.hasMap ¬ (type#none);
layer.mapOK ¬ FALSE;
};
ENDLOOP;
};
bigRect: Imager.Rectangle ~ [-Real.LargestNumber, -Real.LargestNumber, Real.LargestNumber, Real.LargestNumber];
BigButNotTooBig: REAL = 1.0e+18;
bigButNotTooBigRect: Imager.Rectangle = [-BigButNotTooBig, -BigButNotTooBig, 2*BigButNotTooBig, 2*BigButNotTooBig];
DrawSandwichInBoxes: PUBLIC PROC [sandwich: Sandwich, screen: Imager.Context, clientToViewer, viewerToClient: Imager.Transformation, clientData: REF, bkgndColor: Imager.Color, ignoreBackingMap: BOOL ¬ FALSE, noBuffer: BOOL ¬ FALSE, changeRects: LIST OF Imager.Rectangle] = {
drawLayers: PROC ~ {
Draw each of the layers
FOR list: LIST OF LayerData ¬ sandwich.layers, list.rest UNTIL list = NIL DO
layer: LayerData ~ list.first;
bottomLayer: BOOL ~ (list=sandwich.layers);
IF ignoreBackingMap THEN {
IF screen#NIL THEN {
CodeTimer.StartInt[$DrawIgnoreMap2, $Gargoyle];
[] ¬ DrawLayer[sandwich, screen, layer.refreshProc, layer.drawBkgnd, FALSE, bkgndColor, clientData, bottomLayer, rect];
CodeTimer.StopInt[$DrawIgnoreMap2, $Gargoyle];
};
}
ELSE IF NOT layer.hasMap THEN {
IF screen#NIL THEN {
CodeTimer.StartInt[$DrawNoMap2, $Gargoyle];
[] ¬ DrawLayer[sandwich, screen, layer.refreshProc, layer.drawBkgnd, FALSE, bkgndColor, clientData, bottomLayer, rect];
CodeTimer.StopInt[$DrawNoMap2, $Gargoyle];
};
}
ELSE { -- use backing map
IF NOT layer.mapOK THEN {
UpdateMap2: PROC = {
CodeTimer.StartInt[$DrawBackingMap2, $Gargoyle];
Imager.TranslateT[layer.backingContext, [-sandwich.cx, -sandwich.cy]];
layer.drawnOn ¬ DrawLayer[sandwich, layer.backingContext, layer.refreshProc, layer.drawBkgnd, TRUE, bkgndColor, clientData, TRUE, bigRect]; -- use bigRect (so the whole layer is recomputed), because if a layer is not OK, it may have been marked that way long ago before the current rect was computed. To be safe, we will have to transfer the full screen in DoWithBuffer below as well.
CodeTimer.StopInt[$DrawBackingMap2, $Gargoyle];
layer.mapOK ¬ TRUE;
};
Imager.DoSave[layer.backingContext, UpdateMap2];
};
IF screen#NIL THEN
IF bottomLayer THEN {
CodeTimer.StartInt[$TransferLayer2, $Gargoyle];
following DrawPixels call courtesy MP. June 25, 1993.
Imager.DrawPixels[
context: screen,
pixelMap: ImagerPixel.MakePixelMap[layer.backingMap],
colorOperator: ImagerBackdoor.GetBufferColorOperator[screen],
position: [0, sandwich.ch]
];
CodeTimer.StopInt[$TransferLayer2, $Gargoyle];
}
ELSE {
MaskLayer: PROC = {
Imager.SetColor[screen, Imager.black];
Imager.MaskBitmap[screen, layer.backingMap, [sandwich.ch, 0]];
};
CodeTimer.StartInt[$MaskLayer2, $Gargoyle];
IF layer.drawnOn THEN Imager.DoSave[screen, MaskLayer];
CodeTimer.StopInt[$MaskLayer2, $Gargoyle];
};
};
ENDLOOP;
};
x, y, w, h: INTEGER;
rect: Imager.Rectangle;
FOR list: LIST OF Imager.Rectangle ¬ changeRects, list.rest UNTIL list = NIL DO
Compute x, y, w, h, and rect, which are free variables in drawLayers.
rect ¬ list.first;
IF rect.x = -Real.LargestNumber THEN {
x ¬ sandwich.cx; y ¬ sandwich.cy; w ¬ sandwich.cw; h ¬ sandwich.ch;
}
ELSE {
thisRect: Imager.Rectangle ¬ ImagerTransformation.TransformRectangle[clientToViewer, rect];
x ¬ Real.Floor[thisRect.x];
y ¬ Real.Floor[thisRect.y];
w ¬ Real.Ceiling[thisRect.x + thisRect.w]-x;
h ¬ Real.Ceiling[thisRect.y + thisRect.h]-y;
};
sandwich.clientToViewer ¬ clientToViewer;
sandwich.viewerToClient ¬ viewerToClient;
IF screen=NIL THEN {
CodeTimer.StartInt[$NILScreen2, $Gargoyle];
drawLayers[]; -- just update backing contexts
CodeTimer.StopInt[$NILScreen2, $Gargoyle];
}
ELSE {
IF ignoreBackingMap AND noBuffer THEN Imager.ConcatT[screen, viewerToClient]
IF noBuffer THEN Imager.ConcatT[screen, viewerToClient]
So MMM nested editors will work. For these cases, viewerToClient means editorToClient
ELSE ImagerBackdoor.SetT[screen, idX ! Imager.Error => { -- get rid of Biscrollers transformation
Imager.ConcatT[screen, viewerToClient]; -- old way if SetT not implemented
CONTINUE;
};];
IF noBuffer
THEN {
CodeTimer.StartInt[$NoBuffer2, $Gargoyle];
Imager.DoSaveAll[context: screen, action: drawLayers];
CodeTimer.StopInt[$NoBuffer2, $Gargoyle];
}
ELSE {
CodeTimer.StartInt[$DoWithBuffer2, $Gargoyle];
Imager.DoWithBuffer[context: screen, action: drawLayers,
x: x, y: y, w: w, h: h, backgroundColor: bkgndColor];
Including a backgroundColor argument speeds up the call by nearly a factor of 2 since the command doesn't need to read the existing pixels.
CodeTimer.StopInt[$DoWithBuffer2, $Gargoyle];
};
};
ENDLOOP;
};
DrawSandwich: PUBLIC PROC [sandwich: Sandwich, screen: Imager.Context, clientToViewer, viewerToClient: Imager.Transformation, clientData: REF, bkgndColor: Imager.Color, ignoreBackingMap: BOOL ¬ FALSE, noBuffer: BOOL ¬ FALSE, changeRect: Imager.Rectangle ¬ [-Real.LargestNumber, -Real.LargestNumber, Real.LargestNumber, Real.LargestNumber]] = {
drawLayers: PROC ~ {
Draw each of the layers
FOR list: LIST OF LayerData ¬ sandwich.layers, list.rest UNTIL list = NIL DO
layer: LayerData ~ list.first;
bottomLayer: BOOL ~ (list=sandwich.layers);
IF ignoreBackingMap THEN {
IF screen#NIL THEN {
CodeTimer.StartInt[$DrawIgnoreMap, $Gargoyle];
[] ¬ DrawLayer[sandwich, screen, layer.refreshProc, layer.drawBkgnd, FALSE, bkgndColor, clientData, bottomLayer, changeRect];
CodeTimer.StopInt[$DrawIgnoreMap, $Gargoyle];
};
}
ELSE IF NOT layer.hasMap THEN {
IF screen#NIL THEN {
CodeTimer.StartInt[$DrawNoMap, $Gargoyle];
[] ¬ DrawLayer[sandwich, screen, layer.refreshProc, layer.drawBkgnd, FALSE, bkgndColor, clientData, bottomLayer, changeRect];
CodeTimer.StopInt[$DrawNoMap, $Gargoyle];
};
}
ELSE { -- use backing map
IF NOT layer.mapOK THEN {
UpdateMap: PROC = {
CodeTimer.StartInt[$DrawBackingMap, $Gargoyle];
Imager.TranslateT[layer.backingContext, [-sandwich.cx, -sandwich.cy]];
layer.drawnOn ¬ DrawLayer[sandwich, layer.backingContext, layer.refreshProc, layer.drawBkgnd, TRUE, bkgndColor, clientData, TRUE, bigRect]; -- use bigRect (so the whole layer is recomputed), because if a layer is not OK, it may have been marked that way long ago before the current changeRect was computed. To be safe, we will have to transfer the full screen in DoWithBuffer below as well.
CodeTimer.StopInt[$DrawBackingMap, $Gargoyle];
layer.mapOK ¬ TRUE;
};
Imager.DoSave[layer.backingContext, UpdateMap];
};
IF screen#NIL THEN
IF bottomLayer THEN {
CodeTimer.StartInt[$TransferLayer2, $Gargoyle];
following DrawPixels call courtesy MP. June 25, 1993.
Imager.DrawPixels[
context: screen,
pixelMap: ImagerPixel.MakePixelMap[layer.backingMap],
colorOperator: ImagerBackdoor.GetBufferColorOperator[screen],
position: [sandwich.cx, sandwich.ch+sandwich.cy]
];
CodeTimer.StopInt[$TransferLayer2, $Gargoyle];
}
ELSE {
MaskLayer: PROC = {
Imager.SetColor[screen, Imager.black];
Imager.MaskBitmap[screen, layer.backingMap, [sandwich.ch, 0]];
};
CodeTimer.StartInt[$MaskLayer, $Gargoyle];
IF layer.drawnOn THEN Imager.DoSave[screen, MaskLayer];
CodeTimer.StopInt[$MaskLayer, $Gargoyle];
};
};
ENDLOOP;
};
x, y, w, h: INTEGER;
IF changeRect.x = -Real.LargestNumber THEN {
x ¬ sandwich.cx; y ¬ sandwich.cy; w ¬ sandwich.cw; h ¬ sandwich.ch;
}
ELSE {
thisRect: Imager.Rectangle ¬ ImagerTransformation.TransformRectangle[clientToViewer, changeRect];
x ¬ Real.Floor[thisRect.x];
y ¬ Real.Floor[thisRect.y];
w ¬ Real.Ceiling[thisRect.x + thisRect.w]-x;
h ¬ Real.Ceiling[thisRect.y + thisRect.h]-y;
};
sandwich.clientToViewer ¬ clientToViewer;
sandwich.viewerToClient ¬ viewerToClient;
IF screen=NIL THEN {
CodeTimer.StartInt[$NILScreen, $Gargoyle];
drawLayers[]; -- just update backing contexts
CodeTimer.StopInt[$NILScreen, $Gargoyle];
}
ELSE {
IF ignoreBackingMap AND noBuffer THEN Imager.ConcatT[screen, viewerToClient]
IF noBuffer THEN Imager.ConcatT[screen, viewerToClient]
So MMM nested editors will work. For these cases, viewerToClient means editorToClient
ELSE ImagerBackdoor.SetT[screen, idX ! Imager.Error => { -- get rid of Biscrollers transformation
Imager.ConcatT[screen, viewerToClient]; -- old way if SetT not implemented
CONTINUE;
};];
IF noBuffer
THEN {
CodeTimer.StartInt[$NoBuffer, $Gargoyle];
Imager.DoSaveAll[context: screen, action: drawLayers];
CodeTimer.StopInt[$NoBuffer, $Gargoyle];
}
ELSE {
CodeTimer.StartInt[$DoWithBuffer, $Gargoyle];
Imager.DoWithBuffer[context: screen, action: drawLayers, x: x, y: y, w: w, h: h, backgroundColor: bkgndColor];
Including a backgroundColor argument speeds up the call by nearly a factor of 2 since the command doesn't need to read the existing pixels.
CodeTimer.StopInt[$DoWithBuffer, $Gargoyle];
}
};
};
idX: ImagerTransformation.Transformation = ImagerTransformation.Scale[1.0];
debug: BOOL ¬ FALSE;
SetDebug: PROC [t: INT] = {debug ¬ t=1;};
pageBox: Imager.Rectangle ~ [ 0, 0, 8.5*72.0, 11.0*72.0];
DrawLayer: PROC [sandwich: Sandwich, context: Imager.Context, proc: RefreshProc, drawBkgnd: BOOL, mapUpdate: BOOL, bkgndColor: Imager.Color, clientData: REF ANY ¬ NIL, clear: BOOL ¬ FALSE, changeRect: Imager.Rectangle ¬ [-Real.LargestNumber, -Real.LargestNumber, Real.LargestNumber, Real.LargestNumber]] RETURNS [drewSomething: BOOL ¬ TRUE] = {
inner: PROC ~ {
bounds: Imager.Rectangle ¬ [0.0, 0.0, 0.0, 0.0];
Imager.ConcatT[context, sandwich.clientToViewer];
bounds ¬ ImagerBackdoor.GetBounds[context ! Imager.Error => CONTINUE];
can raise Imager.Error or return 0 bounds, meaning bounds are unknown
IF bounds.w=0.0 OR bounds.h=0.0 THEN { -- unknown bounds. Unlikely case.
IF drawBkgnd THEN {
IF mapUpdate OR bkgndColor#NIL THEN {
IF bkgndColor=NIL THEN Imager.SetColor[context, Imager.white]
ELSE Imager.SetColor[context, bkgndColor];
Imager.MaskRectangle[context, IF changeRect.w # Real.LargestNumber THEN changeRect ELSE pageBox];
};
}
ELSE IF clear THEN {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, IF changeRect.w # Real.LargestNumber THEN changeRect ELSE pageBox];
};
if bounds are unknown, use changeRect to draw.
Imager.SetColor[context, Imager.black]; -- in case the client forgets
drewSomething ¬ proc[context, IF changeRect.w # Real.LargestNumber THEN changeRect ELSE bigButNotTooBigRect, clientData];
}
ELSE { -- known reasonable bounds. Likely case.
If this is a background layer: If we're updating a map here than clear to the background color (or white if none). Otherwise, clear only if the background color is non-NIL.
For other layers: clear to white if asked.
IF drawBkgnd THEN {
CodeTimer.StartInt[$LayerBkgnd, $Gargoyle];
IF mapUpdate OR bkgndColor#NIL THEN {
IF bkgndColor=NIL THEN Imager.SetColor[context, Imager.white]
ELSE Imager.SetColor[context, bkgndColor];
Imager.MaskRectangle[context, IF changeRect.w # Real.LargestNumber THEN changeRect ELSE bounds];
};
CodeTimer.StopInt[$LayerBkgnd, $Gargoyle];
}
ELSE IF clear THEN {
CodeTimer.StartInt[$LayerBkgnd, $Gargoyle];
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, IF changeRect.w # Real.LargestNumber THEN changeRect ELSE bounds];
CodeTimer.StopInt[$LayerBkgnd, $Gargoyle];
};
Imager.ClipRectangle[context, bounds]; -- UNNECESSARY and BUGGY
Imager.SetColor[context, Imager.black]; -- in case the client forgets
drewSomething ¬ proc[context, IF changeRect.w # Real.LargestNumber THEN changeRect ELSE bounds, clientData];
};
};
Imager.DoSaveAll[context, inner];
};
FindLayer: PROC [sandwich: Sandwich, layerName: ATOM] RETURNS [layer: LayerData] = {
FOR list: LIST OF LayerData ¬ sandwich.layers, list.rest UNTIL list = NIL DO
IF list.first.name = layerName THEN RETURN[list.first];
ENDLOOP;
ERROR;
};
SetLayerOK: PUBLIC PROC [sandwich: Sandwich, layerName: ATOM, ok: BOOL] = {
layer: LayerData ~ FindLayer[sandwich, layerName];
layer.mapOK ¬ ok;
};
GetLayerOK: PUBLIC PROC [sandwich: Sandwich, layerName: ATOM] RETURNS [ok: BOOL] = {
layer: LayerData ~ FindLayer[sandwich, layerName];
ok ¬ layer.mapOK;
};
RepairLayer: PUBLIC PROC [sandwich: Sandwich, layerName: ATOM, proc: RefreshProc,
bkgndColor: Imager.Color, clientData: REF ANY ¬ NIL, clear: BOOL ¬ FALSE] = {
layer: LayerData ~ FindLayer[sandwich, layerName];
IF layer.hasMap AND (clear OR layer.mapOK) THEN {
DoRepairLayer: PROC = {
Below, drawBkgnd is FALSE to avoid unintentional clearing of the full background layer
Imager.TranslateT[layer.backingContext, [-sandwich.cx, -sandwich.cy]];
layer.drawnOn ¬ DrawLayer[sandwich, layer.backingContext, proc, FALSE, TRUE, bkgndColor, clientData, clear];
layer.mapOK ¬ TRUE;
};
Imager.DoSave[layer.backingContext, DoRepairLayer];
};
};
SameSizeMaps: PROC [a, b: SampleMap] RETURNS [BOOL] = {
RETURN[ImagerSample.GetBox[a]=ImagerSample.GetBox[b]];
};
SaveLayer: PUBLIC PROC [sandwich: Sandwich, layerName: ATOM] = {
If the named layer is backed with a bitmap, that bitmap will be copied for later use (see RestoreLayer).
layer: LayerData ~ FindLayer[sandwich, layerName];
IF NOT layer.hasMap THEN RETURN;
IF layer.savedMap#NIL AND SameSizeMaps[layer.savedMap, layer.backingMap] THEN {
ImagerSample.Transfer[dst: layer.savedMap, src: layer.backingMap];
}
ELSE {
layer.savedMap ¬ ImagerSample.Copy[layer.backingMap];
};
};
RestoreLayer: PUBLIC PROC [sandwich: Sandwich, layerName: ATOM] = {
If the named layer is backed with a bitmap, then that bitmap will be replaced by the bitmap that most recently was saved (see SaveLayer). This will only happen if the saved bitmap and the current bitmap are of the same size.
layer: LayerData ~ FindLayer[sandwich, layerName];
IF NOT layer.hasMap THEN RETURN;
IF layer.savedMap#NIL AND SameSizeMaps[layer.savedMap, layer.backingMap] THEN {
IF Imager.GetClass[layer.backingContext]=ImagerBitmapContext.classCode THEN {
temp: Imager.SampleMap ~ layer.backingMap;
layer.backingMap ¬ layer.savedMap;
layer.savedMap ¬ temp;
ImagerBitmapContext.SetBitmap[layer.backingContext, layer.backingMap];
}
ELSE ImagerSample.Transfer[dst: layer.backingMap, src: layer.savedMap];
};
};
END.