DIRECTORY
ColorizeViewPointSweep, ColorizeViewPoint, ColorizeViewPointBackdoor,
Convert, ImagerPixel, ImagerSample, IO, IPMaster, Profiles, Real, RealFns, Rope, SF;
OPEN ColorizeViewPointSweep;
ROPE: TYPE ~ Rope.ROPE;
ASSERTION: TYPE ~ BOOL [TRUE..TRUE];
defaultMaxHalftoneLevels: ROPE ~ "72";
maxSampleSize: NAT ~ 255;
ConstructSweepPixelArray:
PUBLIC
PROC [def:
LIST
OF
ROPE, palette: Profiles.Profile]
RETURNS [iSize:
NAT ← 0, paFrag:
ROPE] ~ {
Constructs an Interpress fragment defining a pixel array that will sweep between the colors specified in def. palette should contain the number of sweep levels desired. iSize is returned for use in determining the ColorModelOperator needed in MakeSampledColor.
ENABLE UNWIND => NULL;
sweepRope: ROPE ~ RopeFromList[def];
maxHalftoneLevels: NAT;
size, maxColorDistance, interpolationSteps: SF.Vec;
maxHalftoneLevels ← Convert.CardFromRope[Profiles.Token[profile: palette, key: "SweepLevels", default: defaultMaxHalftoneLevels] ! Convert.Error => {SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, Rope.Concat["Sweep levels must be a positive number. Setting sweep levels to default of ", defaultMaxHalftoneLevels]]; maxHalftoneLevels ← Convert.CardFromRope[defaultMaxHalftoneLevels]; CONTINUE}];
maxHalftoneLevels ← MIN[maxHalftoneLevels, maxSampleSize];
[size: size, iSize: iSize] ← UninterpolatedPASize[sweepRope];
[maxColorDistance: maxColorDistance] ← MaxColorDistanceBetweenAdjacentPixels[sweepRope: sweepRope, size: size, iSize: iSize];
interpolationSteps ← [
--these are the minimum number of steps needed to step gracefully over the widest color distance in the f and s directions, assuming the printer has maxHalftoneLevels steps available
s: MAX[1, INTEGER[INT[maxColorDistance.s]*INT[maxHalftoneLevels]/maxSampleSize]],
f: MAX[1, INTEGER[INT[maxColorDistance.f]*INT[maxHalftoneLevels]/maxSampleSize]]
];
RETURN [iSize: iSize, paFrag: BuildInterpolatedPARope[sweepRope: sweepRope, size: size, iSize: iSize, interpolationSteps: interpolationSteps]];
};
UninterpolatedPASize:
PROC [sweepRope:
ROPE]
RETURNS [size:
SF.Vec, iSize:
NAT] ~ {
Given a sweep rope, computes the size of the uninterpolated pixel array.
maxV: SF.Vec ← [INTEGER.FIRST, INTEGER.FIRST];
maxI: NAT ← 0;
CollectSweepStatistics:
PROC [v:
SF.Vec, i, val:
NAT] ~ {
maxV ← maxV.Max[v];
maxI ← MAX[maxI, i];
};
ParseSweepRope[sweepRope, CollectSweepStatistics];
RETURN [size: maxV.Add[[1,1]], iSize: maxI+1];
};
MaxColorDistanceBetweenAdjacentPixels:
PROC [sweepRope:
ROPE, size:
SF.Vec, iSize:
NAT]
RETURNS [maxColorDistance:
SF.Vec ← [1,1]] ~ {
For the uninterpolated pixel array, compute the maximum differences between adjacent samples.
FindMaxColorDistances:
PROC [v:
SF.Vec, i, val:
NAT] ~ {
I. Do First if Buffers Are Full:
IF v.s#prevS
THEN {
--(a new v.s has been passed from ParseSweepRope - the scratchbuffers must be processed before proceeding).
Ia. If p0 (& therefore p1) are non-NIL, analyze the s & f color distances:
IF p0#
NIL
THEN {
--both p0 and p1 buffers are full; check pairs to find maxColorDistances
FOR i:
NAT
IN [0..iSize)
DO
FOR f:
NAT
IN [0..size.f-1)
DO
--find maxColorDistance.f
maxColorDistance.f ← MAX[maxColorDistance.f, AbsDiff[p0[i][f], p0[i][f+1]]]
ENDLOOP;
FOR f:
NAT
IN [0..size.f)
DO
--find maxColorDistance.s
maxColorDistance.s ← MAX[maxColorDistance.s, AbsDiff[p0[i][f], p1[i][f]]];
ENDLOOP;
ENDLOOP;
};
Ib. Roll p1 into p0, and obtain a new p1
[p0, p1] ← RollScratchBuffers[p0: p0, p1: p1, iSize: iSize, fSize: size.f];
prevS ← v.s; --this will cause all the above to be skipped again until p1 is refilled (marked by v.s being passed in with a new value)
IF v.s=size.s
THEN
--this checks maxColorDistance.f on the final scanline which is held in p1. v.s is set to size.s on the FindMaxColorDistances call below which forces the final flush
FOR i:
NAT
IN [0..iSize)
DO
FOR
f:
NAT
IN [0..size.f-1)
DO
maxColorDistance.f ← MAX[maxColorDistance.f, AbsDiff[p1[i][f], p1[i][f+1]]];
ENDLOOP;
ENDLOOP;
};
II. Do always- drop current value into p1:
p1[i][v.f] ← val; --fill p1 with the sample values read by ParseSweepRope. p1 will fill until a new v.s is passed in. When v.s changes, i and v.f restart at 0.
};
p0, p1: ImagerPixel.PixelBuffer ← NIL; --scratch buffers used in FindMaxColorDistances and BuildPARope
prevS: NAT ← NAT.LAST; --Initializing to .LAST forces initting of p1 to occur
ParseSweepRope[sweepRope, FindMaxColorDistances];
FindMaxColorDistances[v: [size.s, 0], i: 0, val: 0]; --forces final eval of p1
ImagerPixel.ReleaseScratchPixels[pixels: p0];
ImagerPixel.ReleaseScratchPixels[pixels: p1];
};
BuildInterpolatedPARope:
PROC [sweepRope:
ROPE, size, interpolationSteps:
SF.Vec, iSize:
NAT]
RETURNS [paFrag:
ROPE] ~ {
Constructs an Interpress fragment defining a pixel array that will sweep between the colors specified in sweepRope.
BuildPARope:
PROC [v:
SF.Vec, i, val:
NAT] ~ {
--paRope is a rope which expresses a color sweep. BuildPARope takes info passed in from ParseSweepRope which gives [multiple] start and end colors in two directions for the sweep. BuildPARope fills scratch buffer p1 with the first row start colors (v.f/f/fast direction). Then when v.s changes it dumps that to p0 and fills p1 with the next fast direction row of colors. Once both p0 and p1 are filled (signalled by v.s changing again), BuildPARope calls InterpolateScan to interpolate each scan of colors from the row defined by p0 upto (not including) the row defined by p1 - using interpolationSteps.s steps. Each row is interpolated by InterpolateScan using interpolationSteps.f steps between each fast direction color (the v.f loop), for each sample (the i loop). Then p1 is filled again and the process repeated for each pair of rows defined.
I. Do First if Buffers Are Full:
IF v.s#prevS
THEN {
--(a new v.s has been passed from ParseSweepRope - the scratchbuffers must be processed before proceeding).
Ia. If p0 & p1 are full, build the pixel array:
IF p0#
NIL
THEN {
--both p0 and p1 buffers are full; interpolate them scan by scan and flush each scan into paRope
FOR s:
NAT
IN [0..interpolationSteps.s)
DO
--this loop interpolates between the row of colors held in p0 upto (but not including) the row of colors in p1
paRope ← paRope.Concat[Rope.FromRefText[InterpolateScan[scan: s, p0: p0, p1: p1, size: size, interpolationSteps: interpolationSteps, iSize: iSize, interpolatedScan: NewText[]]]]; --flush interpolatedScan into paRope...
ENDLOOP; --...then loop for new s
};
Ib. Roll the buffers ... p0 ← p1, and prepare p1 to be filled again:
[p0, p1] ← RollScratchBuffers[p0: p0, p1: p1, iSize: iSize, fSize: size.f];
prevS ← v.s; --this will cause all the above to be skipped again until p1 is refilled (marked by v.s being passed in with a new value)
IF v.s=size.s THEN paRope ← paRope.Concat[Rope.FromRefText[InterpolateScan[scan: interpolationSteps.s, p0: p0, p1: p1, size: size, interpolationSteps: interpolationSteps, iSize: iSize, interpolatedScan: NewText[]]]]; --this picks up the final scanline for the last row of the vertical interpolation process. v.s is set to size.s on the BuildPARope call below which forces the final flush
};
II. Do always- fill p1:
p1[i][v.f] ← val; --fill p1 with the sample values read by ParseSweepRope. p1 will fill until a new v.s is passed in. When v.s changes, i and v.f restart at 0.
}; --end of BuildPARope
p0, p1: ImagerPixel.PixelBuffer ← NIL; --scratch buffers used in BuildPARope
paRope: Rope.ROPE ← "\001"; --1 byte/sample in pixel array
prevS: NAT ← NAT.LAST; -- .LAST forces init to occur in BuildPixelArray
interpPASize:
SF.Vec ~ [
s: (size.s-1)*interpolationSteps.s+1,
f: (size.f-1)*interpolationSteps.f+1
];
tLen: NAT ~ interpPASize.f*iSize;
NewText: PROC RETURNS [t: REF TEXT] ~ INLINE {(t ← NEW[TEXT[tLen]]).length ← tLen};
ParseSweepRope[sweepRope, BuildPARope];
BuildPARope[v: [size.s, 0], i: 0, val: 0]; --forces final flush of p0, p1
{
--Build arguments to
MAKESAMPLEDCOLOR
out: IO.STREAM ~ IO.ROS[];
IPMaster.PutInt[stream: out, n: interpPASize.s]; --xPixels
IPMaster.PutInt[stream: out, n: interpPASize.f]; --yPixels
IPMaster.PutInt[stream: out, n: iSize]; --samplesPerPixel
IPMaster.PutInt[stream: out, n: maxSampleSize]; --maxSampleSize
IPMaster.PutInt[stream: out, n: 1]; --samplesInterleaved~TRUE
IPMaster.PutRational[stream: out, n: 1, d: interpPASize.s]; --T ← 1/xPixels 1/yPixels SCALE2
IPMaster.PutRational[stream: out, n: 1, d: interpPASize.f];
IPMaster.PutOp[stream: out, op: scale2];
IPMaster.PutSequenceRope[stream: out, seq: sequenceLargeVector, rope: paRope]; --The actual pixel array data
IPMaster.PutOp[stream: out, op: makepixelarray]; --MAKEPIXELARRAY
paFrag ← IO.RopeFromROS[self: out];
};
ImagerPixel.ReleaseScratchPixels[pixels: p0];
ImagerPixel.ReleaseScratchPixels[pixels: p1];
};
InterpolateScan:
PROC [scan:
INT, p0, p1: ImagerPixel.PixelBuffer, size, interpolationSteps:
SF.Vec, iSize:
NAT, interpolatedScan:
REF
TEXT]
RETURNS [
REF
TEXT] ~ {
--"scan" is a number which gives the particular horizontal row within a vertical interpolation being done scan by scan. I.e. scan IN [0..interpolationSteps.s].
assert: ASSERTION ~ interpolatedScan.length = iSize*((size.f-1)*interpolationSteps.f+1);
scanComplement: INT ~ interpolationSteps.s-scan;
FOR i:
NAT
IN [0..iSize)
DO
FOR uf
--uninterpolated f--:
NAT
IN [0..size.f-1)
DO
--Interpolation is done from one defined color point up to (but not including) the next within a scanline for each sample. Then scanIndex jumps ahead, we loop back to here and interpolation is done for the next pair of color points (if any). Because it is "up to but not including", the final color must be added in at the end. See below.
from: NAT ~ (scan*p1[i][uf]+scanComplement*p0[i][uf])/interpolationSteps.s; --this is the beginning color for a particular scan
to: NAT ~ (scan*p1[i][uf+1]+scanComplement*p0[i][uf+1])/interpolationSteps.s; --this is the next defined color in the scan: it is the interpolation target
scanIndex: NAT ← uf*iSize*interpolationSteps.f + i;
FOR f:
NAT
IN [0 .. interpolationSteps.f)
DO
--this does the interpolation along a scanline, from one defined color upto the next
interpolatedScan[scanIndex] ← VAL[BYTE[(INT[f]*to+INT[interpolationSteps.f-f]*from)/INT[interpolationSteps.f]]];
scanIndex ← scanIndex+iSize;
ENDLOOP;
ENDLOOP;
{
--The above loop takes care of all pairs of colors along a scanline, but the last defined color is left out. Put it in the scanLine here: (This incidentally also takes care of the case where size.f=1)
scanIndex: NAT ← interpolatedScan.length - iSize + i;
to: NAT ~ (scan*p1[i][size.f-1]+scanComplement*p0[i][size.f-1])/interpolationSteps.s; --same as "to" above, with size.f-1 indexing the last color specified in p0 and p1
interpolatedScan[scanIndex] ← VAL[BYTE[to]]; --same as above with f=interpolationSteps.f
};
ENDLOOP;
RETURN [interpolatedScan]
};
--end of InterpolateScan
AbsDiff:
PROC [a,b: ImagerSample.Sample]
RETURNS [
INTEGER] ~
INLINE {
RETURN [ABS[INTEGER[a]-INTEGER[b]]]
};
ParseSweepRope:
PROC [rope:
ROPE, action:
PROC [v:
SF.Vec, i, val:
NAT]] ~ {
--expects values btwn 0 and 1; returns values btwn 0 and maxSampleSize
IPFragBreak:
IO.BreakProc = {
[char: CHAR] RETURNS [IO.CharClass]
RETURN [
SELECT char
FROM
IN [IO.NUL .. IO.SP] , '\t => sepr,
IN ['0..'9], '. => other,
ENDCASE => break];
};
n, m, i, val: NAT ← 0;
stream: IO.STREAM ~ IO.RIS[rope: rope];
numFound: BOOL ← FALSE; --number must be first in Sweep rope
DO
token: ROPE;
tokenChar: CHAR;
token ← IO.GetTokenRope[stream: stream, breakProc: IPFragBreak ! IO.EndOfStream => EXIT].token;
SELECT tokenChar ← token.Fetch
FROM
IN ['0..'9] , '. => {
test, realVal: REAL;
numFound ← TRUE;
test ← Convert.RealFromRope[r: token
! Convert.Error => {
SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Difficulty converting token [%g] in sweep profile entry %g. Value set to 1.0.", v1: [rope[token]], v2: [rope[rope]]]];
test ← 1.0;
CONTINUE } ];
IF test>1.0 THEN SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Sweep color values should be between 0 and 1; %g is being set to 1 in sweep %g", v1: [real[test]], v2: [rope[rope]] ]];
realVal ← MIN[1, MAX[0, test]];
action[v: [s: n, f: m], i: i, val: ColorizeViewPointBackdoor.AltRound[realVal * maxSampleSize]];
i ← i+1;
};
'- => {IF ~numFound THEN GOTO BadRope; m ← m+1; i ← 0; numFound ← FALSE};
', => {IF ~numFound THEN GOTO BadRope; n ← n+1; m ← i ← 0; numFound ← FALSE};
ENDCASE => SIGNAL ColorizeViewPoint.Warning[$MalformedPaletteEntry, IO.PutFR[format: "Unexpected character [%g] in sweep profile entry %g.", v1: [character[tokenChar]], v2: [rope[rope]]]];
REPEAT
BadRope => ERROR ColorizeViewPoint.Error[$MalformedPaletteEntry, IO.PutFR[format: "Numbers must come first in sweep profile entry %g.", v1: [rope[rope]]]];
ENDLOOP;
};
RollScratchBuffers:
PROC [p0, p1: ImagerPixel.PixelBuffer, iSize, fSize:
NAT]
RETURNS [np0, np1: ImagerPixel.PixelBuffer] ~ {
IF p1=
NIL
THEN {
assert: ASSERTION ~ p0=NIL;
p1 ← ImagerPixel.ObtainScratchPixels[samplesPerPixel: iSize, length: fSize];
FOR i:
NAT
IN [0..iSize)
DO
ImagerSample.ClearSamples[buffer: p1[i]];
ENDLOOP;
}
ELSE {
assertP1: ASSERTION ~ p1.samplesPerPixel=iSize AND p1.length=fSize;
assertP0: ASSERTION ~ p0=NIL OR (p0.samplesPerPixel=iSize AND p0.length=fSize);
IF p0=NIL THEN p0 ← ImagerPixel.ObtainScratchPixels[samplesPerPixel: iSize, length: fSize];
FOR i:
NAT
IN [0..iSize)
DO
ImagerSample.CopySamples[dst: p0[i], src: p1[i]]; --roll by copying
ENDLOOP;
};
RETURN [p0, p1];
};
RopeFromList: PROC [list: LIST OF ROPE] RETURNS [rope: ROPE ← NIL] ~ INLINE {IF list=NIL THEN ERROR ColorizeViewPoint.Error[$MalformedPaletteEntry, "Sweep definition missing - no colors follow the word \"Sweep\""] ELSE FOR each: LIST OF ROPE ← list, each.rest UNTIL each=NIL DO rope ← rope.Cat[" ", each.first]; ENDLOOP};
ConstructSweepTransform:
PUBLIC
PROC [ts: SampledColorTransformSet, sc:
REF SampledColorIPFragments]
RETURNS [transform:
ROPE] ~ {
--Constructs an Interpress fragment defining the um transformation needed by makesampledcolor. A sampled color is produced by combining sc.beforeTransform, transform, sc.afterTransform. If the ts is a rectangle (width.y=height.x=0), it will be rotated to be the correct transform for the angle sc.sweepAngleMod360
RotateTransformSet:
PROC [] ~ {
--rotates rectangles to match sweep angle
IF sc.sweepAngleMod360#0
THEN {
--the algorithm below specifies the minimum rectangle needed to completely enclose the original rectangle, when tilted at sweepAngle.
This part works for angles > 0 and < 90 degrees
sinA: REAL ← RealFns.SinDeg[sc.sweepAngleMod360 MOD 90]; -- move angle to first quadrant
cosA: REAL ← RealFns.CosDeg[sc.sweepAngleMod360 MOD 90];
w: REAL ← ts.width.x;
h: REAL ← ts.height.y;
ts.offset.x ← ts.offset.x - h*sinA*cosA;
ts.offset.y ← ts.offset.y + h*sinA*sinA;
ts.width.x ← cosA*(h*sinA + w*cosA);
ts.width.y ← -sinA*(h*sinA + w*cosA);
ts.height.x ← sinA*(h*cosA + w*sinA);
ts.height.y ← cosA*(h*cosA + w*sinA);
This part adjusts for angles >= 90 and <360
SELECT sc.sweepAngleMod360/90 + 1
--quadrant--
FROM
--rotate the offset and edges according to the quadrant
1 => NULL;
2 => {
temp: VEC ~ ts.width;
ts.offset.x ← ts.offset.x + ts.height.x; ts.offset.y ← ts.offset.y + ts.height.y;
ts.width.x ← -1*ts.height.x; ts.width.y ← -1*ts.height.y;
ts.height ← temp;
};
3 => {
ts.offset.x ← ts.offset.x + ts.height.x + ts.width.x; ts.offset.y ← ts.offset.y + ts.height.y + ts.width.y;
ts.width.x ← -1*ts.width.x; ts.width.y ← -1*ts.width.y;
ts.height.x ← -1*ts.height.x; ts.height.y ← -1*ts.height.y;
};
4 => {
temp: VEC ~ ts.height;
ts.offset.x ← ts.offset.x + ts.width.x; ts.offset.y ← ts.offset.y + ts.width.y;
ts.height.x ← -1*ts.width.x; ts.height.y ← -1*ts.width.y;
ts.width ← temp;
};
ENDCASE => ERROR;
};
};
out: IO.STREAM ~ IO.ROS[];
IF ts#nullTransformSet AND ts.height.x=0 AND ts.width.y=0 THEN RotateTransformSet[];
build the transform for the pixel array
IPMaster.PutReal[stream: out, val: ts.height.x];
IPMaster.PutReal[stream: out, val: ts.width.x];
IPMaster.PutReal[stream: out, val: ts.offset.x];
IPMaster.PutReal[stream: out, val: ts.height.y];
IPMaster.PutReal[stream: out, val: ts.width.y];
IPMaster.PutReal[stream: out, val: ts.offset.y];
RETURN [out.RopeFromROS[]];
};