<> <> <> <> <> <> <<>> 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, <> 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] = { <> 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; <> 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]; <> ImagerBitmapContext.SetBitmap[layer.backingContext, layer.backingMap]; }; dither => { <> 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 ~ { <> 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]; <> }; }; 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 <> 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] <> 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]; <> 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 ~ { <> 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]; <> }; }; thisRect: Imager.Rectangle; CodeTimer.StartInt[$TransferLayer, $Gargoyle]; <> <> 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] <> 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]; <> 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]; <> IF bounds.w=0.0 OR bounds.h=0.0 THEN { -- unknown bounds. Unlikely case. <> 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.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] = { <> 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] = { <> 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.