DIRECTORY Atom, Basics, CedarProcess, ColorDisplayManager, ConvertRasterObject, FS, G3dColorDisplaySupport, G3dRender, G3dRenderWithPixels, G3dSortandDisplay, Imager, ImagerBackdoor, ImagerPixel, ImagerSample, IO, Process, Real, RealFns, Rope, SF, Terminal, ViewerClasses; G3dColorDisplaySupportImpl: CEDAR MONITOR IMPORTS Atom, Basics, CedarProcess, ColorDisplayManager, ConvertRasterObject, FS, G3dRender, G3dRenderWithPixels, G3dSortandDisplay, ImagerBackdoor, ImagerPixel, ImagerSample, IO, Process, Real, RealFns, Rope, SF, Terminal EXPORTS G3dColorDisplaySupport ~ BEGIN PropList: TYPE ~ Atom.PropList; ROPE: TYPE ~ Rope.ROPE; RopeDesc: TYPE ~ G3dRenderWithPixels.RopeDesc; RopeProc: TYPE ~ G3dRenderWithPixels.RopeProc; Context: TYPE ~ G3dRender.Context; ContextProc: TYPE ~ G3dRender.ContextProc; ImagerProc: TYPE ~ G3dRender.ImagerProc; ContextClass: TYPE ~ G3dRender.ContextClass; DisplayMode: TYPE ~ G3dRender.DisplayMode; Box: TYPE ~ ImagerSample.Box; Rectangle: TYPE ~ G3dRender.Rectangle; Triple: TYPE ~ G3dRender.Triple; TripleSequence: TYPE ~ G3dRender.TripleSequence; RGB: TYPE ~ G3dRender.RGB; Pixel: TYPE ~ G3dRender.Pixel; IntegerPair: TYPE ~ G3dRender.IntegerPair; Pair: TYPE ~ G3dRender.Pair; PairSequence: TYPE ~ G3dRender.PairSequence; IntRGB: TYPE ~ RECORD [r, g, b: CARDINAL]; IntRGBSequence: TYPE ~ RECORD [SEQUENCE length: NAT OF IntRGB]; NatSequence: TYPE ~ G3dRender.NatSequence; Patch: TYPE ~ G3dRender.Patch; PatchProc: TYPE ~ G3dRender.PatchProc; ClipState: TYPE ~ G3dRender.ClipState; PixelBuffer: TYPE ~ ImagerPixel.PixelBuffer; PixelMap: TYPE ~ ImagerPixel.PixelMap; SampleMap: TYPE ~ ImagerSample.SampleMap; ImagerProcRec: TYPE ~ G3dRender.ImagerProcRec; LORA: TYPE ~ LIST OF REF ANY; timeResolution: CARD16 _ 3; PasteInLabel: PROC[fileRoot: Rope.ROPE, label: Rope.ROPE] RETURNS[Rope.ROPE] ~ G3dRender.PasteInLabel; GetProp: PROC [propList: PropList, prop: REF ANY] RETURNS [REF ANY] ~ Atom.GetPropFromList; PutProp: PROC [propList: PropList, prop: REF ANY, val: REF ANY] RETURNS [PropList] ~ Atom.PutPropOnList; Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ { result _ Real.Round[number]; IF result < number THEN result _ result + 1; }; Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Real.Round[in]; IF Real.Float[out] > in THEN out _ out - 1; }; UpdateFullColorDisplay: PROC [context: Context] ~ { screenPixels: PixelMap _ NARROW[GetProp[context.displayProps, $ScreenPixels]]; xMin: NAT _ context.pixels.box.min.f; width: NAT _ context.pixels.box.max.f-xMin; buf1: ImagerSample.SampleBuffer _ ImagerSample.ObtainScratchSamples[width]; buf2: ImagerSample.SampleBuffer _ ImagerSample.ObtainScratchSamples[width]; DoUpdateFullColorDisplay[context, screenPixels, buf1, buf2, xMin, width]; ImagerSample.ReleaseScratchSamples[buf1]; ImagerSample.ReleaseScratchSamples[buf2]; }; PeriodicUpdateFullColorDisplay: CedarProcess.ForkableProc ~ { context: Context _ NARROW[data]; screenPixels: PixelMap _ NARROW[GetProp[context.displayProps, $ScreenPixels]]; xMin: NAT _ context.pixels.box.min.f; width: NAT _ context.pixels.box.max.f-xMin; waitTime: CARD16 _ timeResolution*3; buf1: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[width]; buf2: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[width]; DO -- stuff buffered pixels onto screen every waitTime seconds Process.Pause[Process.SecondsToTicks[waitTime]]; DoUpdateFullColorDisplay[context, screenPixels, buf1, buf2, xMin, width]; CedarProcess.CheckAbort[ ]; ENDLOOP; }; DoUpdateFullColorDisplay: PROC [ context: Context, screenPixels: PixelMap, buf1, buf2: ImagerSample.SampleBuffer, xMin, width: NAT] ~ { IF screenPixels # NIL THEN FOR i: NAT IN [context.pixels.box.min.s..context.pixels.box.max.s) DO ImagerSample.GetSamples[ map: context.pixels[0], initIndex: [i, xMin], buffer: buf1, count: width]; ImagerSample.GetSamples[ map: context.pixels[1], initIndex: [i, xMin], buffer: buf2, count: width]; ImagerSample.PutSamples[ map: screenPixels[0], initIndex: [i, 2*xMin], buffer: buf1, delta: [0, 2], count: width]; ImagerSample.PutSamples[ map: screenPixels[0], initIndex: [i, 1+2*xMin], buffer: buf2, delta: [0, 2], count: width]; ENDLOOP; }; UpdateViewer: CedarProcess.ForkableProc ~ { -- updates viewer from buffered pixel map context: Context _ NARROW[data]; waitTime: CARD16 _ IF context.class.displayType = $FullColor THEN timeResolution * 3 ELSE timeResolution; WHILE TRUE DO -- stuff buffered pixels onto screen every waitTime seconds Process.Pause[ Process.SecondsToTicks[waitTime] ]; context.class.drawInViewer[ context, NEW[ImagerProcRec _ [StuffBuf, NIL]] ]; CedarProcess.CheckAbort[ ]; ENDLOOP; }; GetDisplay: ContextProc ~ { IF context.viewer # NIL THEN { context.class.updateViewer[ context ]; context.viewPort _ NEW[ Rectangle _ [context.viewer.cx, context.viewer.cy, context.viewer.cw, context.viewer.ch] ]; G3dRenderWithPixels.AllocatePixelMemory[context]; } ELSE GrabColorDisplay[context]; -- clear off color display and take it over SELECT context.class.displayType FROM $FullColor, $Gray => { LoadColorRamp[ context.terminal, [0.,0.,0.], [1.,1.,1.], [.43, .43, .43] ]; }; $PseudoColor => { LoadStd8BitClrMap[context.terminal]; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected displayType"]; context.pixelAspectRatio _ 1.0; -- standard square-pixel display assumed context.displayInValid _ TRUE; }; GrabColorDisplay: PROC[ context: Context ] ~ { vt: Terminal.Virtual; box: Box; s0, s2: ImagerSample.RasterSampleMap _ NIL; clrType: ATOM; IF context.terminal = NIL THEN context.terminal _ vt _ Terminal.Current[] ELSE vt _ context.terminal; SELECT context.class.displayType FROM $PseudoColor => clrType _ $Dither8; $Gray => clrType _ $Gray8; $FullColor => clrType _ $FullColor; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected Display type"]; ColorDisplayManager.Start[ type: clrType, -- ATOM, Colordisplay types: $Gray8, $Dither8, $Dither1, $FullColor side: left, -- Interminal.Side {left, right} level: mouse, -- ColorDisplayManager.Level ~ {off, allocated, visible, mouse, viewers} resolution: none -- ColorDisplayDefs.ColorDisplayType {none, standard, highResolution} ]; SELECT context.class.displayType FROM $FullColor => { s0 _ ImagerSample.MapFromFrameBuffer[vt.GetColorFrameBufferA[]]; -- 16 bits, R&G s2 _ ImagerSample.MapFromFrameBuffer[vt.GetColorFrameBufferB[]]; -- 8 bits, B }; $PseudoColor, $Gray => s0 _ ImagerSample.MapFromFrameBuffer[vt.GetColorFrameBufferA[]]; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected Display type"]; box _ ImagerSample.GetBox[s0]; -- get frame buffer size context.viewPort _ NEW[ Rectangle _ [box.min.f, box.min.s, box.max.f - box.min.f, box.max.s - box.min.s] ]; G3dRenderWithPixels.AllocatePixelMemory[context]; IF context.class.displayType = $FullColor THEN { screenPxls: PixelMap; TRUSTED { screenPxls _ ImagerPixel.MakePixelMap[ ImagerSample.UnsafeNewSampleMap[ box: [box.min, [box.max.s, 2 * box.max.f]], bitsPerSample: 8, bitsPerLine: ImagerSample.GetBitsPerLine[s0], base: ImagerSample.GetBase[s0], ref: ImagerSample.GetRef[s0], words: ImagerSample.WordsForLines[ box.max.s, ImagerSample.GetBitsPerLine[s0] ] ] ]; }; context.displayProps _ PutProp[context.displayProps, $ScreenPixels, screenPxls]; }; IF s2 = NIL THEN context.pixels[0] _ s0; -- on-screen mem (pseudoclr, grey) IF s2 # NIL THEN context.pixels[2] _ s2; -- on-screen mem (Blue record for full-color) context.viewPort _ NIL; context.window _ NIL; }; ValidateDisplay: ContextProc ~{ -- update viewPort, etc. IF context.viewer # NIL THEN { IF GetProp[ context.displayProps, $ViewerAdjusted ] # NIL THEN context.class.drawInViewer[context, NIL]; -- get specs for new viewer IF context.pixels = NIL OR context.pixels.samplesPerPixel = 1 THEN context.class.updateViewer[ context ]; -- drawing directly on screen context.stopMe^ _ FALSE; -- make sure stop button is released }; IF context.pixels # NIL -- check for mismatched viewPort and pixel storage THEN { IF context.pixels.box.max.f < context.viewPort.w OR context.pixels.box.max.s < context.viewPort.h THEN G3dRenderWithPixels.AllocatePixelMemory[context]; } ELSE IF context.viewer = NIL THEN G3dRenderWithPixels.AllocatePixelMemory[context]; }; Init: PROC[] ~ { -- register Imager-based drawing types standardClass: ContextClass _ [ displayType: $PseudoColor, setUpDisplayType: GetDisplay, validateDisplay: ValidateDisplay, render: MakeFrame, loadBackground: FillInBackGround, draw2DLine: Draw2DLine, draw2DPolygon: Draw2DPoly, draw2DRope: Draw2DRope, displayPolygon: PolygonTiler ]; G3dRender.RegisterDisplayClass[ standardClass, $PseudoColor ]; standardClass.displayType _ $FullColor; G3dRender.RegisterDisplayClass[ standardClass, $FullColor ]; standardClass.displayType _ $Gray; G3dRender.RegisterDisplayClass[ standardClass, $Gray ]; }; MappedRGB: PUBLIC PROC[context: Context, clr: Pixel] RETURNS[Pixel] ~ { SELECT context.class.displayType FROM $ImagerDithered => { mapVal: NAT _ 24 * (clr[r]*5 / 256) + 4 * (clr[g]*6 / 256) + (clr[b]*4 / 256); IF mapVal >= 60 THEN mapVal _ mapVal + 135; -- move to top of map clr[r] _ mapVal; }; $PseudoColor => clr[r] _ 42 * (clr[r]*6 / 256) + 6 * (clr[g]*7 / 256) + (clr[b]*6 / 256) +2; $Gray => clr[r] _ ( clr[r] + clr[g] + clr[b] ) / 3; ENDCASE; -- ignore other modes RETURN[ clr ]; }; LoadStd8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual] ~ { Linearize: PROC [value: REAL] RETURNS[NAT] ~ { RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ]; }; IF vt = NIL THEN vt _ Terminal.Current[]; vt.SetColor[0, 0, 0, 0, 0]; vt.SetColor[1, 0, 0, 0, 0]; FOR i: NAT IN [2..254) DO -- 6 x 7 x 6 color cube j: NAT _ i - 2; red: NAT _ Linearize[51.0 * (j/42)]; grn: NAT _ Linearize[42.5 * ((j/6) MOD 7)]; blu: NAT _ Linearize[51.0 * (j MOD 6)]; vt.SetColor[i, 0, red, grn, blu]; ENDLOOP; vt.SetColor[254, 0, 255, 255, 255]; vt.SetColor[255, 0, 255, 255, 255]; }; LoadColorRamp: PUBLIC PROC [vt: Terminal.Virtual, clr1: RGB _ [0,0,0], clr2: RGB _ [255,255,255], exponent: RGB _ [.43,.43,.43] ] ~ { state: Terminal.ColorMode; maxVal: REAL; IF vt = NIL THEN vt _ Terminal.Current[]; state _ vt.GetColorMode[]; maxVal _ IF state.full THEN 255.0 ELSE Real.Float[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1]; clr1.R _ MAX[0.0, MIN[1.0, clr1.R]]; clr2.R _ MAX[0.0, MIN[1.0, clr2.R]]; clr1.G _ MAX[0.0, MIN[1.0, clr1.G]]; clr2.G _ MAX[0.0, MIN[1.0, clr2.G]]; clr1.B _ MAX[0.0, MIN[1.0, clr1.B]]; clr2.B _ MAX[0.0, MIN[1.0, clr2.B]]; FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO -- linear ramp exponentiated jr: [0..256) _ Real.Fix[ RealFns.Power[clr1.R + i/maxVal * (clr2.R - clr1.R), exponent.R] * maxVal]; jg: [0..256) _ Real.Fix[ RealFns.Power[clr1.G + i/maxVal * (clr2.G - clr1.G), exponent.G] * maxVal]; jb: [0..256) _ Real.Fix[ RealFns.Power[clr1.B + i/maxVal * (clr2.B - clr1.B), exponent.B] * maxVal]; IF Terminal.GetColorMode[vt].full THEN { vt.SetRedMap[i, jr]; vt.SetGreenMap[i, jg]; vt.SetBlueMap[i, jb]; } ELSE vt.SetColor[i, 0, jr, jg, jb]; ENDLOOP; }; FillInBackGround: ContextProc ~ { IF context.viewer # NIL -- do through viewer THEN context.class.drawInViewer[context, NEW[ImagerProcRec _ [ViewerBackFill, NIL]]] ELSE G3dRenderWithPixels.FillInBackGround[context]; -- clear directly }; ViewerBackFill: ImagerProc ~ { -- get pixelmap into context and then do it DoFillInBackGround: PROC[pixelMap: PixelMap] ~ { tempPixels: PixelMap _ context.pixels; tempViewPort: Rectangle _ context.viewPort^; context.pixels _ pixelMap; context.viewPort.x _ context.viewPort.x + pixelMap.box.min.f; -- adjust to device coords context.viewPort.y _ context.viewPort.y + pixelMap.box.min.s; G3dRenderWithPixels.FillInBackGround[context]; context.pixels _ tempPixels; context.viewPort^ _ tempViewPort; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoFillInBackGround, context.viewPort^]; }; Draw2DLine: PROC[context: Context, p1, p2: Pair, color: Pixel] ~ { mapClr: Pixel _ MappedRGB[context, color]; IF context.viewer # NIL -- do through viewer THEN { data: REF LineDesc _ NEW[LineDesc _ [p1, p2, mapClr] ]; context.class.drawInViewer[context, NEW[ImagerProcRec _ [ViewerLine, data]]]; } ELSE G3dRenderWithPixels.Draw2DLine[context, p1, p2, mapClr]; -- do directly }; LineDesc: TYPE ~ RECORD[p1, p2: Pair, color: Pixel]; ViewerLine: ImagerProc ~ { -- get pixelmap into context and then do it DoDrawLine: PROC[pixelMap: PixelMap] ~ { desc: REF LineDesc _ NARROW[data]; tempPixels: PixelMap _ context.pixels; context.pixels _ pixelMap; G3dRenderWithPixels.Draw2DLine[context, desc.p1, desc.p2, desc.color]; context.pixels _ tempPixels; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoDrawLine, context.viewPort^]; }; Draw2DPoly: PROC[context: Context, poly: PairSequence, color: Pixel] ~ { mapClr: Pixel _ MappedRGB[context, color]; IF context.viewer # NIL -- do through viewer THEN { data: REF PolyDesc _ NEW[PolyDesc _ [poly, mapClr] ]; context.class.drawInViewer[context, NEW[ImagerProcRec _ [Viewer2DPoly, data]]]; } ELSE G3dRenderWithPixels.Draw2DPoly[context, poly, mapClr]; -- do directly }; PolyDesc: TYPE ~ RECORD[poly: PairSequence, color: Pixel]; Viewer2DPoly: ImagerProc ~ { -- get pixelmap into context and then do it DoDraw2DPoly: PROC[pixelMap: PixelMap] ~ { desc: REF PolyDesc _ NARROW[data]; tempPixels: PixelMap _ context.pixels; context.pixels _ pixelMap; G3dRenderWithPixels.Draw2DPoly[context, desc.poly, desc.color]; context.pixels _ tempPixels; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoDraw2DPoly, context.viewPort^]; }; PolygonTiler: PatchProc ~ { IF context.viewer # NIL -- do through viewer THEN { plyData: REF PolygonDesc _ NEW[PolygonDesc _ [patch, data] ]; context.class.drawInViewer[context, NEW[ImagerProcRec _ [ViewerTiler, plyData]]]; } ELSE { [] _ G3dRenderWithPixels.PolygonTiler[context, patch, data]; -- do directly }; RETURN[NIL]; }; PolygonDesc: TYPE ~ RECORD[patch: REF Patch, data: REF ANY]; ViewerTiler: ImagerProc ~ { -- get pixelmap into context and then do it DoPolygonTiler: PROC[pixelMap: PixelMap] ~ { desc: REF PolygonDesc _ NARROW[data]; tempPixels: PixelMap _ context.pixels; context.pixels _ pixelMap; [] _ G3dRenderWithPixels.PolygonTiler[context, desc.patch, desc.data]; context.pixels _ tempPixels; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoPolygonTiler, context.viewPort^]; }; Draw2DRope: PUBLIC RopeProc ~ { IF context.viewer = NIL THEN G3dRenderWithPixels.DrawRope[context, rope, position, color, size, font] ELSE { ropeData: REF RopeDesc _ NEW[ RopeDesc _ [rope, position, color, size, font] ]; context.class.drawInViewer[ context, NEW[ImagerProcRec _ [G3dRenderWithPixels.DoRope, ropeData]] ]; }; }; standardNames: ARRAY[0..6) OF RECORD[ pref, alt, other: Rope.ROPE ] _ [ ["-gray", "-grey", NIL], ["-red", "-r", "-rd"], ["-grn", "-green", "-g"], ["-blu", "-blue", "-b"], ["-alpha", "-a", "-alp"], ["-depth", "-d", "-z"] ]; FindFile: PROC [fileRoot: Rope.ROPE, names: RECORD[ pref, alt, other: Rope.ROPE ] ] RETURNS [name: Rope.ROPE] ~ { ok: BOOLEAN _ TRUE; [] _ FS.FileInfo[name _ PasteInLabel[fileRoot, names.pref] ! FS.Error => {ok _ FALSE; CONTINUE}]; IF ok THEN RETURN[ name ] ELSE ok _ TRUE; [] _ FS.FileInfo[name _ PasteInLabel[fileRoot, names.alt] ! FS.Error => {ok _ FALSE; CONTINUE}]; IF ok THEN RETURN[ name ] ELSE ok _ TRUE; [] _ FS.FileInfo[name _ PasteInLabel[fileRoot, names.other] ! FS.Error => {ok _ FALSE; CONTINUE}]; IF ok THEN RETURN[ name ] ELSE ok _ TRUE; [] _ FS.FileInfo[name _ fileRoot ! FS.Error => {ok _ FALSE; CONTINUE}]; IF ok THEN RETURN[ name ] ELSE { SIGNAL G3dRender.Error[ $NotFound, Rope.Cat[" Can't find ", fileRoot, " or with extensions: ", Rope.Cat[names.pref, " ", names.alt, " ", names.other] ] ]; RETURN[ NIL ]; }; }; GetAIS: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE, xOffset, yOffset: INTEGER _ 0, center: BOOLEAN _ TRUE, labeled: BOOLEAN _ FALSE ] RETURNS[ xSize, ySize: INTEGER] ~ { numFiles: NAT; names: ARRAY[0..5) OF RECORD[ pref, alt, other: Rope.ROPE ]; inSamples: ARRAY[0..5) OF SampleMap _ ALL[NIL]; pixelsFromAIS: PixelMap; -- place to put sample maps if needed elsewhere IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button fileRoot _ G3dRender.PrependWorkingDirectory[context, fileRoot]; IF context.pixels = NIL THEN G3dRenderWithPixels.AllocatePixelMemory[ context ]; -- image buffer SELECT context.class.displayType FROM $Dithered, $PseudoColor => { names[0] _ [NIL, NIL, NIL]; numFiles _ 1; }; $Gray => { names[0] _ standardNames[0]; numFiles _ 1; }; $FullColor, $ImagerFullClr => { names[0] _ standardNames[1]; names[1] _ standardNames[2]; names[2] _ standardNames[3]; numFiles _ 3; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Bad RenderMode"]; IF context.antiAliasing THEN { names[numFiles] _ standardNames[4]; numFiles _ numFiles + 1; }; IF context.depthBuffering THEN { names[numFiles] _ standardNames[5]; numFiles _ numFiles + 1; }; FOR i: NAT IN [0..numFiles) DO -- get a file for each pixel entry fileName: Rope.ROPE _ FindFile[ fileRoot, names[i] ]; IF fileName # NIL THEN { box: Box; inSamples[i] _ ConvertRasterObject.SampleMapFromAIS[ aisfile: fileName, useVM: TRUE ]; box _ ImagerSample.GetBox[ inSamples[i] ]; xSize _ SF.SizeF[box]; ySize _ SF.SizeS[box]; IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button IF context.viewer # NIL -- on viewer, put pixels there THEN { context.class.updateViewer[context]; -- get viewPort, etc. context.class.drawInViewer[ -- get pixel map from viewer context, NEW[ImagerProcRec _ [ WriteToViewer, LIST[ NEW[NAT _ i], inSamples[i], NEW[INTEGER _ xOffset], NEW[INTEGER _ yOffset], NEW[BOOLEAN _ center] ] ]] ]; IF labeled THEN { -- put file name at bottom of display intPos: IntegerPair; pos: Pair; viewerBox: Box _ [ min: [ f: context.viewer.cx, s: context.viewer.cy ], max: [ f: context.viewer.cw+context.viewer.cx, s: context.viewer.ch+context.viewer.cy ] ]; [intPos, , ] _ ComputeBox[viewerBox, box, xOffset, xOffset, center]; pos _ [ 0.02 + 1.0 * intPos.x / viewerBox.max.f, 0.02 + 1.0 * intPos.y / viewerBox.max.s ]; context.class.draw2DRope[ context: context, rope: fileName, position: pos, size: 20 * viewerBox.max.f / 640.0 ]; }; } ELSE { IF context.viewPort # NIL THEN { box _ [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]], [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ]; box.max.s _ box.max.s + box.min.s; box.max.f _ box.max.f + box.min.f; } ELSE box _ context.pixels.box; TransferSamples[ ImagerSample.Clip[context.pixels[i], box], inSamples[i], xOffset, yOffset, center ]; }; }; ENDLOOP; pixelsFromAIS _ ImagerPixel.MakePixelMap[ inSamples[0], inSamples[1], inSamples[2], inSamples[3], inSamples[4] ]; context.props _ PutProp[context.props, $TempPixels, pixelsFromAIS]; }; GetRGB: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE, xOffset, yOffset: INTEGER _ 0, center: BOOLEAN _ TRUE, labeled: BOOLEAN _ FALSE ] RETURNS[ xSize, ySize: INTEGER] ~ { SampleMapsFromRGB: PROC[ fileName: Rope.ROPE ] ~ { in: IO.STREAM _ FS.StreamOpen[fileName: fileName]; box: Box _ [[f: 0, s: 0], [f: 720, s: 486]]; rBuf: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[720]; gBuf: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[720]; bBuf: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[720]; FOR i: NAT IN [0..3) DO inSamples[i] _ ImagerSample.NewSampleMap[ box: box, bitsPerSample: 8, bitsPerLine: 720*8 ]; ENDLOOP; FOR y: NAT IN [0..486) DO FOR x: NAT IN [0..720) DO rBuf[x] _ ORD[IO.GetChar[in]]; gBuf[x] _ ORD[IO.GetChar[in]]; bBuf[x] _ ORD[IO.GetChar[in]]; ENDLOOP; ImagerSample.PutSamples[map: inSamples[0], initIndex: [y, 0], buffer: rBuf, count: 720]; ImagerSample.PutSamples[map: inSamples[1], initIndex: [y, 0], buffer: gBuf, count: 720]; ImagerSample.PutSamples[map: inSamples[2], initIndex: [y, 0], buffer: bBuf, count: 720]; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button ENDLOOP; IO.Close[in]; }; box: Box; inSamples: ARRAY[0..3) OF SampleMap _ ALL[NIL]; pixelsFromAIS: PixelMap; -- place to put sample maps if needed elsewhere IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button fileRoot _ G3dRender.PrependWorkingDirectory[context, fileRoot]; IF context.pixels = NIL THEN G3dRenderWithPixels.AllocatePixelMemory[ context ]; IF context.class.displayType # $FullColor THEN SIGNAL G3dRender.Error[$MisMatch, "Must be a full-color display"]; SampleMapsFromRGB[ fileRoot ]; box _ ImagerSample.GetBox[ inSamples[0] ]; xSize _ SF.SizeF[box]; ySize _ SF.SizeS[box]; IF context.viewer # NIL -- on viewer, put pixels there THEN { context.class.updateViewer[context]; -- get viewPort, etc. FOR i: NAT IN [0..3) DO context.class.drawInViewer[ -- get pixel map from viewer context, NEW[ImagerProcRec _ [ WriteToViewer, LIST[ NEW[NAT _ i], inSamples[i], NEW[INTEGER _ xOffset], NEW[INTEGER _ yOffset], NEW[BOOLEAN _ center] ] ]] ]; ENDLOOP; IF labeled THEN { -- put file name at bottom of display intPos: IntegerPair; pos: Pair; viewerBox: Box _ [ min: [ f: context.viewer.cx, s: context.viewer.cy ], max: [ f: context.viewer.cw+context.viewer.cx, s: context.viewer.ch+context.viewer.cy ] ]; [intPos, , ] _ ComputeBox[viewerBox, box, xOffset, xOffset, center]; pos _ [ 0.02 + 1.0 * intPos.x / context.pixels.box.max.f, 0.02 + 1.0 * intPos.y / context.pixels.box.max.s ]; context.class.draw2DRope[ context: context, rope: fileRoot, position: pos, size: 20 * xSize / 640.0 ]; }; } ELSE { IF context.viewPort # NIL THEN { box _ [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]], [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ]; box.max.s _ box.max.s + box.min.s; box.max.f _ box.max.f + box.min.f; } ELSE box _ context.pixels.box; FOR i: NAT IN [0..3) DO TransferSamples[ ImagerSample.Clip[context.pixels[i], box], inSamples[i], xOffset, yOffset, center ]; ENDLOOP; }; pixelsFromAIS _ ImagerPixel.MakePixelMap[ inSamples[0], inSamples[1], inSamples[2] ]; context.props _ PutProp[context.props, $TempPixels, pixelsFromAIS]; }; WriteToViewer: ImagerProc ~ { -- get pixelmap from viewer and get sample map into it DoWrite: PROC[pixelMap: PixelMap] ~ { list: LORA _ NARROW[data]; i: NAT _ NARROW[list.first, REF NAT]^; TransferSamples[ dstMap: pixelMap[i], srcMap: NARROW[list.rest.first], xOffset: NARROW[list.rest.rest.first, REF INTEGER]^, yOffset: NARROW[list.rest.rest.rest.first, REF INTEGER]^, center: NARROW[list.rest.rest.rest.rest.first, REF BOOLEAN]^ ]; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^]; }; GetPixelsFromViewer: PUBLIC PROC[ context: Context, dstMap: PixelMap ] ~ { context.class.drawInViewer[ context, NEW[ImagerProcRec _ [SnatchPixels, LIST[dstMap]]] ]; }; SnatchPixels: ImagerProc ~ { DoGetPixels: PROC[pixelMap: PixelMap] ~ { dstMap: PixelMap _ NARROW[NARROW[data, LORA].first]; FOR i: NAT IN [0..pixelMap.samplesPerPixel) DO TransferSamples[ dstMap: dstMap[i], srcMap: pixelMap[i], xOffset: 0, yOffset: 0 ]; ENDLOOP; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoGetPixels, context.viewPort^]; }; TransferSamples: PROC[ dstMap, srcMap: SampleMap, xOffset, yOffset: INTEGER, center: BOOLEAN _ TRUE ] ~ { srcBox: Box _ ImagerSample.GetBox[srcMap]; dstBox: Box _ ImagerSample.GetBox[dstMap]; dstMin, srcMin, size: IntegerPair; [dstMin, srcMin, size] _ ComputeBox[dstBox, srcBox, xOffset, yOffset, center]; ImagerSample.BasicTransfer[ dst: dstMap, src: srcMap, dstMin: [f: dstMin.x, s: dstMin.y], -- position srcMin: [f: srcMin.x, s: srcMin.y], size: [f: size.x, s: size.y] -- clip ]; }; ComputeBox: PROC[ box1, box2: Box, xOffset, yOffset: INTEGER, center: BOOLEAN _ TRUE ] RETURNS[box1Pos, box2Pos, size: IntegerPair] ~ { xDelta, yDelta: INTEGER _ 0; IF center THEN { -- Shift larger sample map to fit in center of smaller, then offset xDelta _ xOffset + (INTEGER[SF.SizeF[box2]] - SF.SizeF[box1]) /2; yDelta _ -yOffset + (INTEGER[SF.SizeS[box2]] - SF.SizeS[box1]) /2; } ELSE { -- shift tio align with bottom of viewport, then offset xDelta _ xOffset; yDelta _ -yOffset + (INTEGER[SF.SizeS[box2]] - SF.SizeS[box1]); }; box1Pos _ [x: box1.min.f + MAX[0, -xDelta], y: box1.min.s + MAX[0, -yDelta]]; -- position box2Pos _ [x: box2.min.f + MAX[0, xDelta], y: box2.min.s + MAX[0, yDelta]]; size _ [ x: MIN[ SF.SizeF[box2] - MAX[0, xDelta], SF.SizeF[box1] - MAX[0, -xDelta] ], -- clip y: MIN[ SF.SizeS[box2] - MAX[0, yDelta], SF.SizeS[box1] - MAX[0, -yDelta] ] ]; }; WriteFromViewer: ImagerProc ~ { DoWrite: PROC[pixelMap: PixelMap] ~ { list: LORA _ NARROW[data]; i: NAT _ NARROW[list.first, REF NAT]^; fileName: ROPE _ NARROW[list.rest.first]; ConvertRasterObject.AISFromSampleMap[fileName, pixelMap[i]]; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^]; }; PutAIS: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE _ "Temp.ais", doEverything: BOOLEAN _ FALSE ] ~ { addOn: NAT _ IF context.antiAliasing THEN 1 ELSE 0; numFiles: NAT; names: ARRAY[0..4) OF Rope.ROPE _ ALL[NIL]; IF context.depthBuffering THEN addOn _ addOn + 1; fileRoot _ G3dRender.PrependWorkingDirectory[context, fileRoot]; SELECT context.class.displayType FROM $Dithered, $PseudoColor => { names[0] _ NIL; numFiles _ 1; }; $Gray => { names[0] _ "-gray"; numFiles _ 1; }; $FullColor, $ImagerFullClr => { names[0] _ "-red"; names[1] _ "-grn"; names[2] _ "-blu"; numFiles _ 3; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Bad RenderMode"]; IF context.antiAliasing AND doEverything THEN { names[numFiles] _ "-alpha"; numFiles _ numFiles + 1; }; IF context.depthBuffering AND doEverything THEN { names[numFiles] _ "-depth"; numFiles _ numFiles + 1; }; G3dSortandDisplay.ValidateContext[context]; FOR i: NAT IN [0..numFiles) DO -- write a file for each pixel entry IF context.viewer # NIL THEN { context.class.updateViewer[context]; -- get viewPort, etc. context.class.drawInViewer[ context, NEW[ImagerProcRec _ [ WriteFromViewer, LIST[ NEW[NAT _ i], PasteInLabel[fileRoot, names[i]] ] ]] ] } ELSE { box: Box; IF context.viewPort # NIL THEN { box _ [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]], [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ]; box.max.s _ box.max.s + box.min.s; box.max.f _ box.max.f + box.min.f; } ELSE box _ context.pixels.box; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button ConvertRasterObject.AISFromSampleMap[ PasteInLabel[fileRoot, names[i]], ImagerSample.Clip[context.pixels[i], box] ]; }; ENDLOOP; }; WriteRGBFromViewer: ImagerProc ~ { DoWrite: PROC[pixelMap: PixelMap] ~ { list: LORA _ NARROW[data]; fileName: ROPE _ NARROW[list.first]; RGBFromSampleMaps[context, fileName, pixelMap[0], pixelMap[1], pixelMap[2] ]; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^]; }; PutRGB: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE _ "Temp.rgb" ] ~ { fileRoot _ G3dRender.PrependWorkingDirectory[context, fileRoot]; IF context.class.displayType # $FullColor THEN SIGNAL G3dRender.Error[$MisMatch, "Must be a full-color image"]; G3dSortandDisplay.ValidateContext[context]; IF context.pixels # NIL THEN RGBFromSampleMaps[ context, fileRoot, context.pixels[0], context.pixels[1], context.pixels[2] ] ELSE IF context.viewer # NIL THEN { context.class.updateViewer[context]; -- get viewPort, etc. context.class.drawInViewer[ context, NEW[ImagerProcRec _ [ WriteRGBFromViewer, LIST[ fileRoot ] ]] ] } }; RGBFromSampleMaps: PROC[ context: Context, fileName: Rope.ROPE, rMap, gMap, bMap: SampleMap ] ~ { out: IO.STREAM _ FS.StreamOpen[fileName: fileName, accessOptions: create]; box: Box _ ImagerSample.GetBox[rMap]; xSize: NAT _ box.max.f - box.min.f; ySize: NAT _ box.max.s - box.min.s; yMin: INTEGER _ (486 - ySize) / 2; xMin: INTEGER _ (720 - xSize) / 2; rBuf: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[720]; gBuf: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[720]; bBuf: ImagerSample.SampleBuffer _ ImagerSample.NewSamples[720]; FOR y: NAT IN [0..485] DO IF y >= yMin AND y < yMin + ySize THEN { -- get a line from sample map mpx: NAT _ box.min.f; mpy: NAT _ y - yMin + box.min.s; ImagerSample.GetSamples[map: rMap, initIndex: [mpy, mpx], buffer: rBuf, count: xSize]; ImagerSample.GetSamples[map: gMap, initIndex: [mpy, mpx], buffer: gBuf, count: xSize]; ImagerSample.GetSamples[map: bMap, initIndex: [mpy, mpx], buffer: bBuf, count: xSize]; }; FOR x: NAT IN [0..719] DO r,g,b: BYTE; IF y < yMin OR y > yMin + ySize OR x < xMin OR x > xMin + xSize THEN r _ g _ b _ 0 ELSE { r _ rBuf[x - xMin]; g _ gBuf[x - xMin]; b _ bBuf[x - xMin]; }; IO.PutChar[out, VAL[r]]; IO.PutChar[out, VAL[g]]; IO.PutChar[out, VAL[b]]; ENDLOOP; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button ENDLOOP; IO.Close[out]; }; MakeFrame: PUBLIC ContextProc ~ { refreshProc: CedarProcess.Process _ NIL; tmpViewer: ViewerClasses.Viewer; tmpContext: Context; G3dSortandDisplay.ValidateContext[context]; -- ensures viewer, viewPort and pixel store updated IF context.viewer # NIL THEN tmpContext _ G3dRender.GetTmpContext[context] -- get modifiable context ELSE tmpContext _ context; -- drawing into memory or directly on display { ENABLE UNWIND => CedarProcess.Abort[refreshProc]; -- in case of aborts, etc. IF NOT tmpContext.doVisibly -- buffering, copy over when done THEN { tmpViewer _ tmpContext.viewer; tmpContext.viewer _ NIL; tmpContext.pixels _ context.pixels; IF tmpContext.pixels = NIL THEN { G3dRenderWithPixels.AllocatePixelMemory[context]; --gets pixelstore tmpContext.pixels _ context.pixels; }; } ELSE IF tmpContext.viewer # NIL AND tmpContext.pixels # NIL AND tmpContext.pixels.samplesPerPixel > 1 THEN { refreshProc _ CedarProcess.Fork[UpdateViewer, context]; -- start refresh tmpViewer _ tmpContext.viewer; tmpContext.viewer _ NIL; }; IF GetProp[ context.displayProps, $ScreenPixels ] # NIL -- full color, no vwr THEN refreshProc _ CedarProcess.Fork[PeriodicUpdateFullColorDisplay, context]; G3dRenderWithPixels.MakeFrame[tmpContext]; }; IF refreshProc # NIL THEN CedarProcess.Abort[refreshProc]; -- kill periodic update IF NOT tmpContext.doVisibly OR tmpViewer # NIL THEN { tmpContext.viewer _ tmpViewer; context.class.drawInViewer[ tmpContext, NEW[ImagerProcRec _ [StuffBuf, NIL]] ]; }; IF refreshProc # NIL THEN { [] _ CedarProcess.Join[refreshProc]; -- wait for refreshProc done UpdateFullColorDisplay[context]; }; }; StuffBuf: PUBLIC ImagerProc ~ { DoTransfer: PROC[pixelMap: PixelMap] ~ { FOR i: NAT IN [0..samplesPerColor) DO ImagerSample.Transfer[dst: pixelMap[i], src: context.pixels[i], delta: pixelMap.box.min]; ENDLOOP; }; samplesPerColor: NAT _ IF context.class.displayType = $FullColor THEN 3 ELSE 1; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoTransfer, context.viewPort^]; }; MakeHiResFrame: PUBLIC PROC[ context: Context, width, height: NAT, name: ROPE] ~ { mode: DisplayMode; hiResCtxt: Context; SELECT context.class.displayType FROM $Gray => mode _ gray; $PseudoColor => mode _ dither; ENDCASE => mode _ fullColor; hiResCtxt _ G3dRender.CreateUndisplayedContext[ oldContext: context, width: width, height: height, displayMode: mode, keepLog: TRUE ]; hiResCtxt.viewer _ NIL; G3dRenderWithPixels.AntiAliasing[hiResCtxt]; IF context.depthBuffering THEN G3dRenderWithPixels.DepthBuffering[hiResCtxt]; hiResCtxt.changed _ TRUE; -- ensure screen coordinates get updated hiResCtxt.class.render[hiResCtxt]; G3dRender.StoreImage[hiResCtxt, name ]; -- store resulting image }; DitherImage: PUBLIC PROC[dstContext, rgbContext: Context] ~ { Action: PROC ~ { width: NAT _ Real.Fix[MIN[dstContext.viewPort.w, rgbContext.viewPort.w] ]; height: NAT _ Real.Fix[MIN[dstContext.viewPort.h, rgbContext.viewPort.h] ]; scanSegIn: PixelBuffer _ ImagerPixel.NewPixels[rgbContext.pixels.samplesPerPixel, width]; scanSegOut: PixelBuffer _ ImagerPixel.NewPixels[dstContext.pixels.samplesPerPixel, width]; IF rgbContext.pixels.samplesPerPixel < 3 THEN SIGNAL G3dRender.Error[$MisMatch, "24-bit input needed for dithering"]; FOR y: NAT IN [0..height) DO ImagerPixel.GetPixels[ -- get rgb pixels self: rgbContext.pixels, pixels: scanSegIn, initIndex: [f: 0, s: y], count: width ]; ImagerPixel.GetPixels[ -- get rgb pixels self: dstContext.pixels, pixels: scanSegOut, initIndex: [f: 0, s: y], count: width ]; FOR x: NAT IN [0..width) DO scanSegOut[0][x] _ DitheredRGB[$PseudoColor, x, y, scanSegIn[0][x], scanSegIn[1][x], scanSegIn[2][x] ]; ENDLOOP; ImagerPixel.PutPixels[ -- store result in foreground self: dstContext.pixels, pixels: scanSegOut, initIndex: [f: 0, s: y], count: width ]; ENDLOOP; }; CedarProcess.DoWithPriority[background, Action]; -- be nice to other processess }; ditherTable: ARRAY [0..4) OF ARRAY [0..4) OF NAT _ [[0,12,3,15], [8,4,11,7], [2,14,1,13], [10,6,9,5]]; DitheredRGB: PROC[renderMode: ATOM, x, y, red, grn, blu: INTEGER] RETURNS[INTEGER] ~ { val2R, val2G, val2B, pixelValue: NAT; SELECT renderMode FROM $PseudoColor => { threshold: NAT _ ditherTable[ Basics.BITAND[x,3] ][ Basics.BITAND[y,3] ]; valR: NAT _ Basics.BITSHIFT[ Basics.BITSHIFT[red,2] + red, -4 ]; -- (red * 5) / 16 valG: NAT _ Basics.BITSHIFT[ Basics.BITSHIFT[grn,2] + Basics.BITSHIFT[grn,1], -4 ]; valB: NAT _ Basics.BITSHIFT[ Basics.BITSHIFT[blu,2] + blu, -4 ]; -- (blu * 5) / 16 val2R _ Basics.BITSHIFT[valR,-4]; -- valR / 16 IF Basics.BITAND[valR,15] > threshold THEN val2R _ val2R + 1; -- valr MOD 16 val2G _ Basics.BITSHIFT[valG,-4]; IF Basics.BITAND[valG,15] > threshold THEN val2G _ val2G + 1; val2B _ Basics.BITSHIFT[valB,-4]; IF Basics.BITAND[valB,15] > threshold THEN val2B _ val2B + 1; RETURN[ MIN[ 255, Basics.BITSHIFT[val2R,5] + Basics.BITSHIFT[val2R,3] + Basics.BITSHIFT[val2R,1] + Basics.BITSHIFT[val2G,2] + Basics.BITSHIFT[val2G,1] + val2B + 2 ] ]; --val2R*42 + val2G*6 + val2B + 2 }; $ImagerDithered => { threshold: NAT _ ditherTable[x MOD 4][y MOD 4]; valR: NAT _ 4* red / 16; valG: NAT _ 5* grn / 16; valB: NAT _ 3* blu / 16; val2R _ valR/16; IF valR MOD 16 > threshold THEN val2R _ val2R + 1; val2G _ valG/16; IF valG MOD 16 > threshold THEN val2G _ val2G + 1; val2B _ valB/16; IF valB MOD 16 > threshold THEN val2B _ val2B + 1; pixelValue _ val2R*24 + val2G*4 + val2B; IF pixelValue >= 60 THEN pixelValue _ pixelValue + 135; -- move to top of map RETURN[ MIN[255, pixelValue] ]; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected display type"]; RETURN[ 255 ]; }; Init[]; END. ΦG3dColorDisplaySupportImpl.mesa Last Edited by: Crow, September 19, 1989 10:09:37 am PDT Glassner, March 14, 1989 2:18:27 pm PST Bloomenthal, December 29, 1988 9:06:41 pm PST Types Globals Renamed Procedures Utility Procedures Updates from buffered pixel map height: NAT _ screenPixels.box.max.s; width: NAT _ screenPixels.box.max.f/2; Read 8 bit samples into red buffer: Read 8 bit samples into green buffer: Now, interleave the red and green 8 bit samples into the 16 bit red/green color display: Write the red, starting at 0, with delta.f = 2, thus writing the even bytes: Write the green, starting at 1, with delta.f = 2, thus writing the odd bytes: PROC [data: REF] RETURNS [results: REF _ NIL] Initialize Standard Displays Establishes PixelMap corresponding to bits on color display. If viewers are being buffered, sets up pixel maps in VM to be blitted across as appropriate. This is called by G3dRender.LoadDisplayType For using color display without viewers Reform screen memory to address by 8-bit chunks reconfigure R&G color display sample maps as a big, double-width 8-bit sample map: For full-color, first sample map is off-screen red, second is off-screen green, third is on-screen blue. Green and R&G and blitted together as necessary Computed by G3dSortandDisplay.ValidateView or ThreeDViewer.GetViewportFromViewer Colors Low-level drawing Put a string of characters on the screen Image File Storage and Retrieval G3dRenderWithPixels.CopyUnder should work without the following commented: Ensure image buffer in memory PROC[ context: Context, imagerCtx: Imager.Context, data: REF ANY _ NIL ]; Transfers pixels from viewer into supplied Pixel Map [ IF center THEN { -- Shift larger sample map to fit in center of smaller, then offset xDelta _ xOffset + (INTEGER[SF.SizeF[srcBox]] - SF.SizeF[dstBox]) /2; yDelta _ -yOffset + (INTEGER[SF.SizeS[srcBox]] - SF.SizeS[dstBox]) /2; } ELSE { -- shift tio align with bottom of viewport, then offset xDelta _ xOffset; yDelta _ -yOffset + (INTEGER[SF.SizeS[srcBox]] - SF.SizeS[dstBox]); }; ImagerSample.BasicTransfer[ dst: dstMap, src: srcMap, dstMin: [f: dstBox.min.f + MAX[0, -xDelta], s: dstBox.min.s + MAX[0, -yDelta]], -- position srcMin: [f: srcBox.min.f + MAX[0, xDelta], s: srcBox.min.s + MAX[0, yDelta]], size: [ f: MIN[ SF.SizeF[srcBox] - MAX[0, xDelta], SF.SizeF[dstBox] - MAX[0, -xDelta] ], s: MIN[ SF.SizeS[srcBox] - MAX[0, yDelta], SF.SizeS[dstBox] - MAX[0, -yDelta] ] ] -- clip ]; PROC[ context: Context, imagerCtx: Imager.Context, data: REF ANY _ NIL ]; PROC[ context: Context, imagerCtx: Imager.Context, data: REF ANY _ NIL ]; Frame Generation and Animation The following blows up if the viewPort is NIL (As it often can be) !!! Use forked process to copy bits over periodically, too complex to do onscreen If PeriodicUpdateFullColorDisplay, in its final pass, missed some pixels being rendered: Ordered dither for crude looks at full-color Ordered dither for crude looks at full-color using Imager's color map Κ Ύ˜™ J™8Icode™'K™-K™IdefaultšΟk œEœ€œ œ˜—headšΟlΠklžŸ˜)JšœFœ`œ œ ˜ίJšœ˜J˜Jšœ˜—šž™Jšœ œ˜"Jšœœœ˜Lšœ œ ˜0Lšœ œ ˜0Lšœœ˜$Lšœ œ˜+Lšœ œ˜)Lšœ œ˜-Jšœ œ˜*Lšœœ˜ Lšœ œ˜'Lšœœ˜"Lšœœ˜0Jšœœ œ˜Jšœœ˜!Jšœ œ˜,Jšœœ˜ Jšœœ˜-Jšœœœ œ˜-Jš œœœœ œœ ˜?Jšœ œ˜+Jšœœ˜!Jšœ œ˜(Jšœ œ˜(Jšœ œ˜.Jšœ œ˜(Jšœ œ˜+Lšœœ˜/Lš œœœœœœ˜ —šž™Lšœœ˜—šΠbl™aš Οn œœœœœœ˜LJšœ˜N˜—š‘œΟsœ ’œ ’œ’’œ’’œ’œ˜CJšœ˜J˜—š‘œœœœœœœ ˜RJšœ˜——šž™š ‘œœ œœ œ˜8J˜Jšœœ˜,J˜—š ‘œœœœœ˜3J˜Jšœœ˜,J˜—š‘œœ˜3Jšœ’œ’œ/˜NJšœœ˜%Jšœœ"˜,J˜KJ˜KJ˜IJ˜)J˜)Jšœ‘˜—š‘œ˜=J™ Lšœœ˜ Jšœ’œ’œ/˜NJšœœ%œ™PJšœœ˜%Jšœœ"˜,Jšœ œ˜$J˜AJ˜AšœΟc;˜AJšœœ(˜0J˜IJ˜Jšœ˜—Jšœ‘˜—š‘œœ˜ Jšœ˜J˜J˜&Jšœ œ˜J˜Jšœœ˜š œœœœ6˜JJ™#˜J˜J—J™%˜J˜J—J™YJ™L˜J˜Y—J™M˜J˜[—Jšœ˜—J˜J˜—š‘ œ £*˜VLš œœœ œœ™-Lšœœ˜ Jš œ œœ(œ*œ˜šœœœ£;˜KJšœœ*˜2Jšœ%œœ˜LJ˜Jšœ˜—J˜——šž™š‘ œ˜Lš£^™^L™=L™,šœœ˜šœ˜J˜&šœœ˜$N˜LN˜—N˜1N˜—Lšœ£+œ˜O—šœ˜%˜N˜KL˜—˜L˜$L˜—Lšœœ6˜G—Nšœ"£(˜JLšœœ˜L˜—L˜š‘œœ˜/Lš£'™'J˜J˜ Jšœ'œ˜+Jšœ œ˜šœœ˜Jšœ+˜/Jšœ˜—šœ˜%J˜#J˜J˜#Jšœœ7˜H—˜Jšœ££<˜SJšœ£ ˜.Jšœ£H˜WJšœ£E˜VJ˜—šœ˜%˜LšœA£˜PLšœA£ ˜ML˜—˜L˜@—Lšœœ7˜H—Jšœ#£˜;šœœ˜JšœS˜S—J˜1šœ(œ˜0J™/J˜šœ˜ J™R˜&˜ J˜,J˜J˜-J˜J˜J˜OJ˜—J˜—J˜—LšœP˜PJ˜——˜L™™Lšœœœ£"˜LLšœœœ£-˜WL˜šœœ˜J™P—Lšœœ˜L˜—š‘œ£˜:šœœœ˜šœ4˜9Jšœ%œ£˜K—šœœœ$˜>Jšœ*£˜K—Jšœœ£$˜@J˜—šœœ£2˜Mšœ˜Lšœ/˜1šœœ/˜4Lšœ1˜6—L˜—Lšœœœœ2˜S—L˜—š‘œœ £&˜=˜J˜J˜L˜!L˜L˜!L˜L˜L˜J˜J˜—J˜?J˜'J˜=J˜"J˜7J˜——šž™š Πbn‘œœœœ ˜Gšœ˜&˜JšœœF˜QJšœœ£˜CN˜J˜—J˜\J˜5Jšœ£˜!—Nšœ˜N˜—š‘œ œ˜9š ‘ œœ œœœ˜.Jšœ:˜@J˜—Nšœœœ˜)N˜:š œœœ œ £˜;Jšœœ ˜Jšœœ˜%Jšœœœ˜,Jšœœœ˜(J˜!Jšœ˜—N˜KN˜N™—š ‘ œ œœœœ˜N˜Nšœœ˜ Nšœœœ˜)N˜šœ œ ˜Nšœ˜ Nšœœ%˜D—Nš œ œœœœ˜INš œ œœœœ˜INš œ œœœœ˜IN˜š œœœœœ£˜SJ˜kJ˜jJ˜hšœ ˜"LšœN˜RLšœ ˜$—Jšœ˜—N˜——šž™š‘œ˜!šœœ £˜7Nšœ%œ"œ˜TLšœ2£˜G—J˜—š‘œ£+˜Kš‘œœ˜0J˜VJ˜Jšœ?£˜YJ˜=J˜.J˜AJ˜—J˜WJ˜—š‘ œœ2˜BN˜*šœœ£˜:šœ˜Nšœœ œ˜7Nšœ$œ&˜MN˜—Lšœ;£˜M—J˜Jšœ œœ˜4—š‘ œ£+˜Gš‘ œœ˜(Jšœœ œ˜"J˜DJ˜FJ˜J˜—J˜OJ˜—š‘ œœ8˜HN˜*šœœ £˜7šœ˜Nšœœ œ˜5Nšœ$œ(˜ON˜—Lšœ:£˜L—J˜Jšœ œœ#˜:—š‘ œ£+˜Jš‘ œœ˜*Jšœœ œ˜"J˜DJ˜?J˜J˜—J˜QJ˜—š‘ œ˜šœœ £˜7šœ˜Nšœ œœ˜=Nšœ$œ*˜QN˜—šœ˜Lšœ?£˜ML˜——Jšœœ˜ J˜Jš œ œœœœœ˜<—š‘ œ£+˜Jš‘œœ˜,Jšœœœ˜%J˜*J˜J˜FJ˜J˜—J˜SJ˜—š‘ œœ ˜J™(šœ˜JšœI˜Mšœ˜Jšœ œ œ3˜O˜J˜ Jšœ9˜œœœ˜iJš œœœ œœ˜,Jš œœœœœ˜Hšœœœ ˜šœ˜šœ˜Jšœ ˜ J˜yJ˜—Jšœœ˜J˜——K˜—š‘œ œ"œœœœ œœœœ˜ΎJšœ £˜Nš œœœœœ˜