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: BOOLFALSE, noBuffer: BOOLFALSE, 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: BOOLFALSE, noBuffer: BOOLFALSE, 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: BOOLFALSE;
SetDebug: PROC [t: INT] = {debug ← t=1;};
DrawLayer: PROC [sandwich: Sandwich, context: Imager.Context, proc: RefreshProc, clientData: REF ANYNIL, clear: BOOLFALSE, changeRect: Imager.Rectangle ← [-Real.LargestNumber, -Real.LargestNumber, Real.LargestNumber, Real.LargestNumber]] RETURNS [drewSomething: BOOLTRUE] = {
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 ANYNIL, clear: BOOLFALSE] = {
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.