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.