BufferedRefreshImpl.mesa
Copyright Ó 1986, 1987, 1988, 1989, 1990 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, August 28, 1991 6:42 pm PDT
Bier, September 20, 1991 11:43 am PDT
Doug Wyatt, September 15, 1989 3:49:32 pm PDT
DIRECTORY
BufferedRefresh, BufferedRefreshExtras, BufferedRefreshPort, CodeTimer, Imager, ImagerBackdoor, ImagerBitmapContext, ImagerBox, ImagerColor, ImagerDitherContext, ImagerPixel, ImagerSample, ImagerTransformation, PBasics, Real, SF;
BufferedRefreshImpl:
CEDAR
PROGRAM
IMPORTS BufferedRefreshPort, CodeTimer, Imager, ImagerBackdoor, ImagerBitmapContext, <<ImagerBox,>> ImagerColor, ImagerDitherContext, ImagerSample, ImagerTransformation, PBasics, Real, SF
EXPORTS BufferedRefresh, BufferedRefreshExtras =
BEGIN OPEN BufferedRefresh;
[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]];
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] = {
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.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;
PBasics.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
ANY, 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, 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, clientData, bottomLayer, rect];
CodeTimer.StopInt[$DrawNoMap2, $Gargoyle];
};
}
ELSE {
-- use backing map
IF
NOT layer.mapOK
THEN {
CodeTimer.StartInt[$DrawBackingMap2, $Gargoyle];
layer.drawnOn ← DrawLayer[sandwich, layer.backingContext, layer.refreshProc, 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;
};
IF screen#
NIL
THEN
IF bottomLayer
THEN {
TransferLayer:
PROC [pixelMap: ImagerPixel.PixelMap] ~ {
IF pixelMap.samplesPerPixel=1
THEN {
dst: SampleMap ~ pixelMap[0];
src: SampleMap ~ layer.backingMap;
dstBox: SF.Box ~ ImagerSample.GetBox[dst];
srcBox: SF.Box ← ImagerSample.GetBox[src];
delta: SF.Vec;
size: SF.Vec ← SF.Size[srcBox];
srcBox ← SF.Intersect[srcBox, [[size.s-(y+h), x],[size.s-y, x+w]]];
delta ← SF.Sub[dstBox.min, srcBox.min];
ImagerSample.Transfer[dst: dst, src: src, delta: delta];
ImagerSample.BasicTransfer[dst: dst, src: src, dstMin: dstBox.min, srcMin: box.min, size: SF.Size[box]];
};
};
thisRect: Imager.Rectangle;
CodeTimer.StartInt[$TransferLayer2, $Gargoyle];
thisRect ← [0,0,sandwich.cw,sandwich.ch];
ImagerBackdoor.AccessBufferRectangle[screen, TransferLayer, thisRect];
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 ← 0; y ← 0; 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]
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: bufferBkgnd];
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
ANY, 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, 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, clientData, bottomLayer, changeRect];
CodeTimer.StopInt[$DrawNoMap, $Gargoyle];
};
}
ELSE {
-- use backing map
IF
NOT layer.mapOK
THEN {
CodeTimer.StartInt[$DrawBackingMap, $Gargoyle];
layer.drawnOn ← DrawLayer[sandwich, layer.backingContext, layer.refreshProc, 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;
};
IF screen#
NIL
THEN
IF bottomLayer
THEN {
TransferLayer:
PROC [pixelMap: ImagerPixel.PixelMap] ~ {
IF pixelMap.samplesPerPixel=1
THEN {
dst: SampleMap ~ pixelMap[0];
src: SampleMap ~ layer.backingMap;
dstBox: SF.Box ~ ImagerSample.GetBox[dst];
srcBox: SF.Box ← ImagerSample.GetBox[src];
delta: SF.Vec;
size: SF.Vec ← SF.Size[srcBox];
srcBox ← SF.Intersect[srcBox, [[size.s-(y+h), x],[size.s-y, x+w]]];
delta ← SF.Sub[dstBox.min, srcBox.min];
ImagerSample.Transfer[dst: dst, src: src, delta: delta];
ImagerSample.BasicTransfer[dst: dst, src: src, dstMin: dstBox.min, srcMin: box.min, size: SF.Size[box]];
};
};
thisRect: Imager.Rectangle;
CodeTimer.StartInt[$TransferLayer, $Gargoyle];
IF changeRect.x = -Real.LargestNumber THEN thisRect ← [0,0,sandwich.cw,sandwich.ch]
ELSE thisRect ← changeRect;
thisRect ← [0,0,sandwich.cw,sandwich.ch];
ImagerBackdoor.AccessBufferRectangle[screen, TransferLayer, thisRect];
CodeTimer.StopInt[$TransferLayer, $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 ← 0; y ← 0; 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]
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: bufferBkgnd];
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;};
DrawLayer:
PROC [sandwich: Sandwich, context: Imager.Context, proc: RefreshProc, 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.
Don't clear, intersect or clip if bounds are unknown. Just 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 clear
THEN {
CodeTimer.StartInt[$LayerWhite, $Gargoyle];
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, IF changeRect.w # Real.LargestNumber THEN changeRect ELSE bounds];
CodeTimer.StopInt[$LayerWhite, $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,
clientData:
REF
ANY ←
NIL, clear:
BOOL ←
FALSE] = {
layer: LayerData ~ FindLayer[sandwich, layerName];
IF layer.hasMap
AND (clear
OR layer.mapOK)
THEN {
layer.drawnOn ← DrawLayer[sandwich, layer.backingContext, proc, clientData, clear];
layer.mapOK ← TRUE;
};
};
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];
};
};
bufferBkgnd: Imager.Color ← Imager.white;
SetBkgndRed:
PROC = {
bufferBkgnd ← ImagerColor.ColorFromRGB[[1.0, 0.0, 0.0]];
};
SetBkgndWhite:
PROC = {
bufferBkgnd ← ImagerColor.ColorFromRGB[[1.0, 1.0, 1.0]];
};
END.