ImageTwiddleImpl.mesa
Last Edited by: Crow, April 21, 1989 6:34:48 pm PDT
Bloomenthal, September 26, 1988 12:04:42 pm PDT
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
Types
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;
Colors
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] ~ {
Rotates color map entries in given range
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] ~ {
Print color map on log for context
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] ~ {
set color map to constant steps simulating restricted number of grey levels
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] ~ {
adjust pixel values over whole image to conform restricted number of grey levels
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;
threshTable[i] ← Linearize[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] ~ {
Adjust the values in the display memory for TRC or ? (only for full-color or gray)
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] ~ {
Adjust the values in the display memory for Saturation (only for full-color)
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] ~ {
setting up colors for old terrain pictures
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] ];
AIS Files and Texture Images
DitherImage:
PUBLIC PROC[dstContext, rgbContext: Context] ~ {
G3dColorDisplaySupport.DitherImage[dstContext, rgbContext]; -- convenience call
};
ScaleDownImage:
PUBLIC
PROC[dstContext, srcContext: Context, strtchFctr:
REAL ← 1.0] ~{
Bresenham-like walk across image, areas averaged down
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
};