<> <> <> DIRECTORY Atom, Basics, CedarProcess, Convert, G3dColorDisplaySupport, G3dRender, G3dRenderWithPixels, G3dSortandDisplay, Imager, ImagerBackdoor, ImagerColor, ImagerColorFns, ImagerPixel, ImagerSample, ImageTwiddle, IO, NamedColors, Real, RealFns, Rope, Terminal, ThreeDViewer; ImageTwiddleImpl: CEDAR MONITOR IMPORTS Atom, Basics, CedarProcess, Convert, G3dColorDisplaySupport, G3dRender, G3dRenderWithPixels, Imager, ImagerBackdoor, ImagerColor, ImagerColorFns, ImagerPixel, ImagerSample, IO, NamedColors, Real, RealFns, Rope, Terminal, ThreeDViewer EXPORTS ImageTwiddle ~ BEGIN <> Context: TYPE ~ G3dRender.Context; ContextClass: TYPE ~ G3dRender.ContextClass; Triple: TYPE ~ G3dRender.Triple; RGB: TYPE ~ G3dRender.RGB; IntegerPair: TYPE ~ G3dRender.IntegerPair; IntRGB: TYPE ~ RECORD[ r, g, b: CARDINAL]; IntRGBSequence: TYPE ~ RECORD [ SEQUENCE length: NAT OF IntRGB ]; NatSequence: TYPE ~ G3dRender.NatSequence; NatSequenceRep: TYPE ~ G3dRender.NatSequenceRep; Patch: TYPE ~ G3dRender.Patch; ClipState: TYPE ~ G3dRender.ClipState; ImagerProc: TYPE ~ G3dRender.ImagerProc; ImagerProcRec: TYPE ~ G3dRender.ImagerProcRec; PixelMap: TYPE ~ ImagerPixel.PixelMap; <> SetNamedColor: PUBLIC PROC [imagerCtx: Imager.Context, color: Rope.ROPE] ~ { clr: RGB _ ImagerColorFns.RGBFromHSL[ NamedColors.RopeToHSL[color] ]; Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ]; }; SetRGBColor: PUBLIC PROC [imagerCtx: Imager.Context, clr: RGB] ~ { Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ]; }; Rotate8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual, firstValue, lastValue, duration: NAT _ 0] ~ { <> r, g, b: ARRAY [0..256) OF [0..256); times: NAT; IF lastValue = 0 THEN lastValue _ 255; FOR i: NAT IN [firstValue .. lastValue] DO [r[i], g[i], b[i]] _ Terminal.GetColor[ vt, i]; ENDLOOP; IF duration = 0 THEN times _ 0 -- will be one complete cycle ELSE times _ duration * 75; -- seconds times fields per second times _ times + -- Fill out full cycle (lastValue - firstValue + 1) - times MOD (lastValue - firstValue + 1); FOR i: NAT IN [0..times) DO tr, tg, tb: [0..256); CedarProcess.CheckAbort[]; -- check for external abort request before proceeding vt.WaitForBWVerticalRetrace[]; -- await top of scan (to control update rate) tr _ r[firstValue]; tg _ g[firstValue]; tb _ b[firstValue]; FOR j: NAT IN (firstValue .. lastValue] DO r[j-1] _ r[j]; g[j-1] _ g[j]; b[j-1] _ b[j]; ENDLOOP; r[lastValue] _ tr; g[lastValue] _ tg; b[lastValue] _ tb; FOR i: NAT IN [firstValue .. lastValue] DO Terminal.SetColor[ vt, i, 0, r[i], g[i], b[i]]; ENDLOOP; ENDLOOP; }; Show8BitClrMap: PUBLIC PROC [context: Context] ~ { ThreeDViewer.DrawInViewer[ context, NEW[ImagerProcRec _ [ViewerShowClrMap, NIL] ] ]; }; ViewerShowClrMap: ImagerProc ~ { DoShowClrMap: PROC[pixelMap: PixelMap] ~ { firstBottom: INTEGER _ Real.Fix[.75 * context.viewPort.h]; size: INTEGER _ Real.Fix[ .025 * MIN[context.viewPort.h, context.viewPort.w] ]; firstTop: INTEGER _ firstBottom + size; firstLeft: INTEGER _ Real.Fix[context.viewPort.w/2.0] - 16*size; firstRight: INTEGER _ firstLeft + size; FOR i: NAT IN [0..256) DO top: NAT _ firstTop + size * (i / 32); bottom: NAT _ firstBottom + size * (i / 32); left: NAT _ firstLeft + size * (i MOD 32); right: NAT _ firstRight + size * (i MOD 32); ImagerSample.Fill[pixelMap[0], [min: [f: left, s: bottom], max: [f: right, s: top] ], i]; ENDLOOP; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoShowClrMap, context.viewPort^]; }; ShowMapOnLog: PUBLIC PROC [context: Context, vt: Terminal.Virtual] ~ { <> RopeFromQuad: PROC[ i, r, g, b: CARD] RETURNS[ Rope.ROPE ] ~ { output: Rope.ROPE _ Rope.Cat[ " \t ", Convert.RopeFromCard[i], " ", Convert.RopeFromCard[r], " " ]; output _ Rope.Cat[ output, Convert.RopeFromCard[g], " ", Convert.RopeFromCard[b] ]; RETURN[ output ]; }; log: IO.STREAM _ NARROW[ Atom.GetPropFromList[context.props, $Log] ]; state: Terminal.ColorMode _ vt.GetColorMode[]; maxVal: REAL _ IF state.full THEN 255.0 ELSE REAL[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1]; FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO -- linear ramp exponentiated jr, jg, jb: CARD; IF Terminal.GetColorMode[vt].full THEN { jr _ vt.GetRedMap[i]; jg _ vt.GetGreenMap[i]; jb _ vt.GetBlueMap[i]; } ELSE [jr, jg, jb] _ vt.GetColor[i]; IF log # NIL THEN { log.PutRope[ RopeFromQuad[ i, jr, jg, jb ] ]; IF (i/4) * 4 = i THEN log.PutRope["\n"]; G3dRender.FlushLog[context]; }; ENDLOOP; }; LoadMultiRamps: PUBLIC PROC [vt: Terminal.Virtual, colors: LIST OF RGB] ~ { clr: REF IntRGBSequence _ NEW[ IntRGBSequence[32] ]; numClrs, thisClr, nextClr, rampLength: NAT _ 0; state: Terminal.ColorMode _ vt.GetColorMode[]; maxVal: REAL _ IF state.full THEN 255.0 ELSE REAL[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1]; WHILE colors # NIL DO clr[numClrs].r _ Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.R]] ]; clr[numClrs].g _ Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.G]] ]; clr[numClrs].b _ Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.B]] ]; numClrs _ numClrs + 1; colors _ colors.rest; ENDLOOP; rampLength _ Real.Fix[maxVal] / (numClrs-1); FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO jr, jg, jb: [0..256); position: NAT _ i MOD rampLength; IF position = 0 THEN IF nextClr < numClrs-1 THEN { thisClr _ nextClr; nextClr _ nextClr + 1; } ELSE thisClr _ nextClr; jr _ clr[thisClr].r * (rampLength - position) / rampLength + clr[nextClr].r * position / rampLength; jg _ clr[thisClr].g * (rampLength - position) / rampLength + clr[nextClr].g * position / rampLength; jb _ clr[thisClr].b * (rampLength - position) / rampLength + clr[nextClr].b * position / rampLength; 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; }; ThresholdClrMap: PUBLIC PROC [vt: Terminal.Virtual, levels: NAT] ~ { <> Linearize: PROC [value: REAL] RETURNS[NAT] ~ { RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ]; }; FOR i: NAT IN [0..256) DO -- grayscale j: NAT _ (i * levels / 256) * (256 / levels); gray: NAT _ Linearize[j]; vt.SetColor[i, 0, gray, gray, gray]; ENDLOOP; }; threshTable: NatSequence _ NEW[ NatSequenceRep[256] ]; threshLevels: NAT _ 0; ThresholdImage: PUBLIC PROC [context: Context, levels: NAT] ~ { threshLevels _ levels; IF context.viewer # NIL -- do through viewer THEN context.class.drawInViewer[context, NEW[ImagerProcRec _ [ThresholdImage2, NIL]]] ELSE DoThresholdImage[context.pixels]; -- do directly }; ThresholdImage2: ImagerProc ~ { ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoThresholdImage, context.viewPort^]; }; DoThresholdImage: PROC [pixelMap: PixelMap] ~ { <> Action: PROC ~ { Linearize: PROC [value: REAL] RETURNS[NAT] ~ { RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ]; }; maxValue: REAL _ 255.0; xStart: NAT _ Real.Round[pixelMap.box.min.f]; yStart: NAT _ Real.Round[pixelMap.box.min.s]; width: NAT _ Real.Round[pixelMap.box.max.f - pixelMap.box.min.f]; height: NAT _ Real.Round[pixelMap.box.max.s - pixelMap.box.min.s]; scanSeg: ImagerPixel.PixelBuffer _ ImagerPixel.NewPixels[ pixelMap.samplesPerPixel, width ]; FOR i: NAT IN [0..256) DO j: NAT _ (i * threshLevels / 256) * (256 / threshLevels); threshTable[i] _ j; <> ENDLOOP; FOR y: NAT IN [ yStart .. height + yStart ) DO ImagerPixel.GetPixels[ self: pixelMap, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; FOR x: NAT IN [ 0 .. width ) DO IF scanSeg.samplesPerPixel > 2 THEN { scanSeg[0][x] _ threshTable[scanSeg[0][x]]; scanSeg[1][x] _ threshTable[scanSeg[1][x]]; scanSeg[2][x] _ threshTable[scanSeg[2][x]]; } ELSE scanSeg[0][x] _ threshTable[scanSeg[0][x]]; ENDLOOP; ImagerPixel.PutPixels[ self: pixelMap, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; ENDLOOP; }; CedarProcess.DoWithPriority[background, Action]; }; << >> AdjustValueRamp: PUBLIC PROC[context: Context, exponent: RGB] ~ { <> Action: PROC[] ~ { maxValue: REAL _ 255.0; xStart: NAT _ Real.Round[context.viewPort.x]; yStart: NAT _ Real.Round[context.viewPort.x]; width: NAT _ Real.Round[context.viewPort.w]; height: NAT _ Real.Round[context.viewPort.h]; expTableR: NatSequence _ NEW[ NatSequenceRep[256] ]; expTableG: NatSequence _ NEW[ NatSequenceRep[256] ]; expTableB: NatSequence _ NEW[ NatSequenceRep[256] ]; scanSeg: ImagerPixel.PixelBuffer _ ImagerPixel.NewPixels[ context.pixels.samplesPerPixel, width ]; SELECT context.class.displayType FROM $FullColor => FOR i: NAT IN [0..256) DO expTableR[i] _ Real.Round[ RealFns.Power[ i/255.0, exponent.R] * 255.0 ]; expTableG[i] _ Real.Round[ RealFns.Power[ i/255.0, exponent.G] * 255.0 ]; expTableB[i] _ Real.Round[ RealFns.Power[ i/255.0, exponent.B] * 255.0 ]; ENDLOOP; $Gray => { exp: REAL _ (exponent.R + exponent.G + exponent.B) / 3.0; FOR i: NAT IN [0..256) DO expTableG[i] _ Real.Round[ RealFns.Power[ i/255.0, exp] * 255.0 ]; ENDLOOP; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Improper renderMode"]; FOR y: NAT IN [ yStart .. height + yStart ) DO ImagerPixel.GetPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; FOR x: NAT IN [ 0 .. width ) DO SELECT context.class.displayType FROM $FullColor => { scanSeg[0][x] _ expTableR[scanSeg[0][x]]; scanSeg[1][x] _ expTableG[scanSeg[1][x]]; scanSeg[2][x] _ expTableB[scanSeg[2][x]]; }; $Gray => scanSeg[0][x] _ expTableG[scanSeg[0][x]]; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Improper renderMode"]; ENDLOOP; ImagerPixel.PutPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; ENDLOOP; }; CedarProcess.DoWithPriority[background, Action]; }; AdjustSaturation: PUBLIC PROC[context: Context, percent: REAL] ~ { <> Action: PROC[] ~ { maxValue: REAL _ 255.0; xStart: NAT _ Real.Round[context.viewPort.x]; yStart: NAT _ Real.Round[context.viewPort.x]; width: NAT _ Real.Round[context.viewPort.w]; height: NAT _ Real.Round[context.viewPort.h]; scanSeg: ImagerPixel.PixelBuffer _ ImagerPixel.NewPixels[ context.pixels.samplesPerPixel, width ]; FOR y: NAT IN [ yStart .. height + yStart ) DO ImagerPixel.GetPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; FOR x: NAT IN [ 0 .. width ) DO SELECT context.class.displayType FROM $FullColor => { scale: REAL; redVal: CARDINAL _ scanSeg[0][x]; grnVal: CARDINAL _ scanSeg[1][x]; bluVal: CARDINAL _ scanSeg[2][x]; min: CARDINAL _ MIN[redVal, grnVal, bluVal]; max: CARDINAL _ MAX[redVal, grnVal, bluVal]; newMin: CARDINAL _ MIN[max, INTEGER[Real.Fix[percent * min]]]; IF (min = 0) OR (max - min = 0) THEN LOOP; scale _ 1.0 * (max - newMin) / (max - min); redVal _ Real.Fix[ (redVal - min) * scale + newMin ]; grnVal _ Real.Fix[ (grnVal - min) * scale + newMin ]; bluVal _ Real.Fix[ (bluVal - min) * scale + newMin ]; scanSeg[0][x] _ redVal; scanSeg[1][x] _ grnVal; scanSeg[2][x] _ bluVal; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Only for full color"]; ENDLOOP; ImagerPixel.PutPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; ENDLOOP; }; CedarProcess.DoWithPriority[background, Action]; }; LoadOldStd8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual] ~ { Linearize: PROC [value: REAL] RETURNS[NAT] ~ { RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ]; }; FOR i: NAT IN [0..40) DO -- grayscale gray: NAT _ Linearize[i*6.5]; vt.SetColor[i + 216, 0, gray, gray, gray]; ENDLOOP; FOR i: NAT IN [0..216) DO -- 6 x 6 x 6 color cube vt.SetColor[i, 0, Linearize[42.5 * (i/36 + 1)], Linearize[42.5 * ((i/6) MOD 6 + 1)], Linearize[42.5 * (i MOD 6 + 1)] ]; ENDLOOP; vt.SetColor[0, 0, 0, 0, 0]; }; <<>> SetUpTerrainColors: PUBLIC PROC[vt: Terminal.Virtual] ~ { <> terrainHues: ARRAY [0..4) OF RGB _ [[.05, .4, .0], [.5, .6, .1], [.4, .4, .4], [1., 1., 1.]]; <<{ forest, savannah, rock, snow }>> skyHue: RGB _ [.5, .5, 1.0]; waterHue: RGB _ [.2, .2, .8]; start: NAT _ 8; -- start^ _ 8; length: NAT _ 240; valueStepSize, minValue, rampValue, index, hueRampSize: NAT _ 0; hueRampSize _ 238 / (LENGTH[terrainHues] + 1); valueStepSize _ 256 / hueRampSize; minValue _ 256 - valueStepSize*hueRampSize; FOR j: NAT IN [0..4) DO -- do hue steps from each hue to next currentHue: RGB _ [terrainHues[j].R, terrainHues[j].G, terrainHues[j].B]; rampValue _ minValue; FOR i: NAT IN [0..hueRampSize) DO -- build hue ramps r, g, b: NAT; r _ Real.Round[ rampValue * currentHue.R ]; g _ Real.Round[ rampValue * currentHue.G ]; b _ Real.Round[ rampValue * currentHue.B ]; vt.SetColor[index+start, 0, r, g, b ]; rampValue _ rampValue + valueStepSize; index _ index + 1; ENDLOOP; ENDLOOP; vt.SetColor[239+start, 0, Real.Fix[skyHue.R*255], Real.Fix[skyHue.G*255], Real.Fix[skyHue.B*255] ]; vt.SetColor[start, 0, Real.Fix[waterHue.R*255], Real.Fix[waterHue.G*255], Real.Fix[waterHue.B*255] ]; }; <<>> <> DitherImage: PUBLIC PROC[dstContext, rgbContext: Context] ~ { G3dColorDisplaySupport.DitherImage[dstContext, rgbContext]; -- convenience call }; ScaleDownImage: PUBLIC PROC[dstContext, srcContext: Context, strtchFctr: REAL _ 1.0] ~{ <> Action: PROC ~ { srcHght: NAT _ Real.Round[srcContext.viewPort.h]; dstHght: NAT _ Real.Round[dstContext.viewPort.h]; srcWdth: NAT _ Real.Round[srcContext.viewPort.w]; dstWdth: NAT _ Real.Round[dstContext.viewPort.w]; numHits: NatSequence _ NEW[ NatSequenceRep[dstWdth] ]; -- holds divisor for average xPos, yPos: INTEGER; xCtr, yCtr: NAT _ 0; scanSegIn, scanSegOut: ImagerPixel.PixelBuffer; scanSegIn _ ImagerPixel.ObtainScratchPixels[ srcContext.pixels.samplesPerPixel, srcWdth ]; scanSegOut _ ImagerPixel.ObtainScratchPixels[ dstContext.pixels.samplesPerPixel, dstWdth ]; FOR x: NAT IN [0..dstWdth) DO -- clear the pixels FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] _ 0; ENDLOOP; ENDLOOP; yPos _ 2 * dstHght - srcHght; -- offset for vertical incrementer FOR x: NAT IN [0..dstWdth) DO numHits[x] _ 0; ENDLOOP; -- clear divisor array FOR y: NAT IN [ 0 .. srcHght ) DO -- work up through source scan lines ImagerPixel.GetPixels[ self: srcContext.pixels, pixels: scanSegIn, initIndex: [f: 0, s: y], count: srcWdth ]; xPos _ 2 * dstWdth - srcWdth; xCtr _ 0; FOR x: NAT IN [ 0 .. srcWdth ) DO -- work across source scan line FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][xCtr] _ scanSegOut[i][xCtr] + scanSegIn[i][x]; -- sum intensities ENDLOOP; numHits[xCtr] _ numHits[xCtr] + 1; -- sum hits per pixel IF xPos > 0 THEN { xCtr _ xCtr + 1; xPos _ xPos - 2 * srcWdth; IF xCtr >= dstWdth THEN EXIT; }; xPos _ xPos + 2 * dstWdth; ENDLOOP; IF yPos > 0 THEN { -- big increment exceeded FOR x: NAT IN [0..dstWdth) DO FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] _ Real.Fix[ strtchFctr * scanSegOut[i][x] / numHits[x] ]; ENDLOOP; numHits[x] _ 0; ENDLOOP; ImagerPixel.PutPixels[ self: dstContext.pixels, pixels: scanSegOut, initIndex: [f: 0, s: yCtr], count: dstWdth ]; FOR x: NAT IN [0..dstWdth) DO -- clear the pixels FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] _ 0; ENDLOOP; ENDLOOP; yPos _ yPos - 2 * srcHght; -- subtract big increment from position yCtr _ yCtr + 1; IF yCtr >= dstHght THEN EXIT; }; yPos _ yPos + 2 * dstHght; -- add in little incrment until big increment exceeded ENDLOOP; ImagerPixel.ReleaseScratchPixels[scanSegIn]; ImagerPixel.ReleaseScratchPixels[scanSegOut]; IF dstContext.viewer # NIL THEN -- make sure image gets copied to screen ThreeDViewer.DrawInViewer[ dstContext, NEW[ImagerProcRec _ [G3dColorDisplaySupport.StuffBuf, NIL]] ]; }; IF srcContext.viewPort = NIL THEN srcContext.class.validateDisplay[srcContext]; IF dstContext.viewPort = NIL THEN dstContext.class.validateDisplay[dstContext]; IF dstContext.pixels = NIL THEN G3dRenderWithPixels.BufferRendering[dstContext, TRUE]; CedarProcess.DoWithPriority[background, Action]; -- be nice to other processess }; END.