GridModulationImpl.mesa
Copyright (C) 1984, Xerox Corporation. All rights reserved.
Michael Plass, November 27, 1985 6:36:16 pm PST
DIRECTORY Basics, ImagerPixelSeq, ImagerPixelMap, RasterFontIO, GridModulation, Rope, Real, RealFns, FattenPixels, Process;
GridModulationImpl: CEDAR MONITOR
IMPORTS Basics, ImagerPixelSeq, ImagerPixelMap, Real, RealFns, Rope, FattenPixels, Process
EXPORTS GridModulation
~ BEGIN OPEN GridModulation;
CreateEdgeProjection: PUBLIC PROC [sProjection: BOOLEAN, origin: INTEGER, size: NAT, reductionFactor: NAT, scratch: EdgeProjection ← NIL] RETURNS [EdgeProjection] ~ {
IF scratch = NIL THEN scratch ← NEW[EdgeProjectionRep];
IF scratch.buffer = NIL OR scratch.buffer.maxSize < size THEN {
scratch^ ← [
reductionFactor: reductionFactor,
weight: 0,
sProjection: sProjection,
origin: origin,
size: size,
buffer: ImagerPixelSeq.Create[size],
penalty: NEW[RealSeqRep[size]],
totalBadness: NEW[RealSeqRep[size*2]],
link: ImagerPixelSeq.Create[size*2],
bestEnd: 0,
swathStart: 0,
swathSize: 0
];
}
ELSE {
scratch.reductionFactor ← reductionFactor;
scratch.weight ← 0;
scratch.sProjection ← sProjection;
scratch.origin ← origin;
scratch.size ← size;
};
FOR i: NAT IN [0..size) DO
scratch.penalty[i] ← 0.0;
ENDLOOP;
RETURN [scratch]
};
waveformPeriods: NAT ~ 6;
samplesPerPeriod: NAT ~ 32;
timeConstant: REAL ← 1.0;
dampedWaveform: ARRAY [0..waveformPeriods*samplesPerPeriod) OF REAL ← InitDampedWaveform[];
InitDampedWaveform: PROC RETURNS [d: ARRAY [0..waveformPeriods*samplesPerPeriod) OF REAL] ~ {
sum: REAL ← 0.0;
max: REAL;
FOR i: NAT IN [0..samplesPerPeriod/4) DO
x: REAL ← (i-samplesPerPeriod/4)/REAL[samplesPerPeriod];
d[i] ← RealFns.CosDeg[x*360];
ENDLOOP;
FOR i: NAT IN [samplesPerPeriod/4..waveformPeriods*samplesPerPeriod) DO
x: REAL ← (i-samplesPerPeriod/4)/REAL[samplesPerPeriod];
d[i] ← -RealFns.SinDeg[x*360]*RealFns.Exp[-x/timeConstant];
ENDLOOP;
FOR i: NAT IN [samplesPerPeriod/4..waveformPeriods*samplesPerPeriod) DO
d[i] ← sum ← sum + d[i];
ENDLOOP;
FOR i: NAT IN [samplesPerPeriod/4..waveformPeriods*samplesPerPeriod) DO
d[i] ← d[i] - sum;
ENDLOOP;
max ← d[samplesPerPeriod/4];
FOR i: NAT IN [samplesPerPeriod/4..waveformPeriods*samplesPerPeriod) DO
d[i] ← d[i]/max;
ENDLOOP;
};
DampedWaveform: PROC [x: REAL] RETURNS [y: REAL] ~ {
i: INTEGER ← Real.RoundLI[(x+0.25)*samplesPerPeriod];
y ← IF i IN [0..waveformPeriods*samplesPerPeriod) THEN dampedWaveform[i] ELSE 0.0;
};
useDamped: BOOLEANFALSE;
ProjectEdges: PUBLIC PROC [edgeProjection: EdgeProjection, pixelMap: PixelMap, runSizeMap: RunSizeMap] ~ {
bb: ImagerPixelMap.DeviceRectangle ← pixelMap.Trim.Window;
line: ImagerPixelSeq.PixelSeq ← edgeProjection.buffer;
min: INTEGERIF edgeProjection.sProjection THEN bb.fMin ELSE bb.sMin;
size: INTEGERIF edgeProjection.sProjection THEN bb.fSize ELSE bb.sSize;
penalty: RealSeq ← edgeProjection.penalty;
FOR i: INTEGER IN [min..min+size) DO
prev: CARDINAL ← 0;
runStart: NAT;
IF edgeProjection.sProjection THEN line.LoadS[edgeProjection.origin, i, edgeProjection.size, pixelMap]
ELSE line.LoadF[i, edgeProjection.origin, edgeProjection.size, pixelMap];
FOR j: NAT IN [0..edgeProjection.size] DO
cur: CARDINALIF j = edgeProjection.size THEN 0 ELSE line[j];
SELECT cur FROM
> prev => runStart ← j;
< prev => {
runSize: NAT ← j-runStart;
targetSize: NATIF runSize < runSizeMap.maxSize THEN runSizeMap[runSize] ELSE Real.RoundI[REAL[runSize]/edgeProjection.reductionFactor];
IF targetSize # 0 AND useDamped THEN {
scale: REALREAL[targetSize]/REAL[runSize];
quarterPeriod: INTEGER ← Real.RoundLI[1/(4*scale)];
waveExtent: INTEGER ← Real.RoundLI[(waveformPeriods-0.25)/scale];
start: NATMAX[runStart-quarterPeriod, 0];
end: NATMIN[runStart+waveExtent, runStart+runSize+quarterPeriod, edgeProjection.size];
FOR k: NAT IN [start..end) DO
p: REAL ← DampedWaveform[(INTEGER[k]-runStart+0.5)*scale];
penalty[k] ← penalty[k] + p;
ENDLOOP;
start ← MAX[runStart+runSize-waveExtent, runStart-quarterPeriod, 0];
end ← MIN[runStart+runSize+quarterPeriod, edgeProjection.size];
FOR k: NAT IN [start..end) DO
p: REAL ← DampedWaveform[(runStart+runSize-INTEGER[k]-0.5)*scale];
penalty[k] ← penalty[k] + p;
ENDLOOP;
};
IF targetSize # 0 AND NOT useDamped THEN {
pip: REAL ← 2.0/targetSize;
delta: REALREAL[runSize]/REAL[targetSize];
t: REAL ← runStart + delta*0.5;
FOR k: NAT IN [0..targetSize) DO
index: NAT ← Real.Fix[t];
penalty[index] ← penalty[index] - pip;
t ← t + delta;
ENDLOOP;
IF runStart > 0 THEN
penalty[runStart-1] ← penalty[runStart-1] + 0.5;
penalty[runStart] ← penalty[runStart] + 0.5;
IF runStart+runSize < edgeProjection.size THEN
penalty[runStart+runSize] ← penalty[runStart+runSize] + 0.5;
penalty[runStart+runSize-1] ← penalty[runStart+runSize-1] + 0.5;
};
};
ENDCASE => NULL;
prev ← cur;
ENDLOOP;
ENDLOOP;
edgeProjection.weight ← edgeProjection.weight + size;
};
CreateStrokeHistogram: PUBLIC PROC [scratch: StrokeHistogram ← NIL] RETURNS [StrokeHistogram] ~ {
IF scratch = NIL THEN scratch ← NEW[StrokeHistogramRep];
IF scratch.count = NIL THEN scratch.count ← ImagerPixelSeq.Create[20];
scratch.size ← 0;
RETURN [scratch]
};
BumpHist: PROC [hist: StrokeHistogram, n: NAT] ~ {
IF hist.count.maxSize <= n THEN {
new: PixelSeq ← ImagerPixelSeq.Create[MAX[n+1, NAT[MIN[INT[hist.count.maxSize]*3/2, NAT.LAST]]]];
FOR i: NAT IN [0..hist.size) DO
new[i] ← hist.count[i];
ENDLOOP;
hist.count ← new;
};
WHILE hist.size <= n DO
hist.count[hist.size] ← 0;
hist.size ← hist.size + 1;
ENDLOOP;
hist.count[n] ← hist.count[n] + 1;
};
AccumulateHistogram: PUBLIC PROC [strokeHistogram: StrokeHistogram, pixelMap: PixelMap, sProjection: BOOLEAN] ~ {
bb: ImagerPixelMap.DeviceRectangle ← pixelMap.Trim.Window;
seqSize: INTEGERIF sProjection THEN bb.sSize ELSE bb.fSize;
seq: PixelSeq ← ImagerPixelSeq.ObtainScratch[seqSize];
min: INTEGERIF sProjection THEN bb.fMin ELSE bb.sMin;
size: INTEGERIF sProjection THEN bb.fSize ELSE bb.sSize;
FOR i: INTEGER IN [min..min+size) DO
prevPix: NAT ← 0;
runSize: NAT ← 0;
IF sProjection THEN seq.LoadS[bb.sMin, i, seqSize, pixelMap]
ELSE seq.LoadF[i, bb.fMin, seqSize, pixelMap];
FOR j: NAT IN [0..seqSize] DO
pix: [0..1] ← IF j = seqSize THEN 0 ELSE seq[j];
SELECT 2*prevPix+pix FROM
0 => NULL;
1 => runSize ← 0;
2 => BumpHist[strokeHistogram, runSize];
3 => runSize ← runSize + 1;
ENDCASE => ERROR;
prevPix ← pix;
ENDLOOP;
ENDLOOP;
ImagerPixelSeq.ReleaseScratch[seq];
};
FindTotal: PUBLIC PROC [hist: StrokeHistogram] RETURNS [sum: INT] ~ {
sum ← 0;
FOR i: NAT IN [0..hist.size) DO
sum ← sum + hist.count[i];
ENDLOOP;
};
FindPercentile: PUBLIC PROC [hist: StrokeHistogram, percentile: REAL] RETURNS [INT] ~ {
size: NAT ~ hist.size;
total: REAL ← FindTotal[hist];
target: REAL ← total*percentile/100.0;
partial: REALIF size = 0 THEN 0 ELSE hist.count[0];
i: NAT ← 0;
WHILE (i + 1) < size AND partial < target DO
i ← i + 1;
partial ← partial + hist.count[i];
ENDLOOP;
RETURN [i];
};
distortionWeightRelativeToMaxCount: REAL ← 1.0;
RunSizeMapFromHistogram: PUBLIC PROC [hist: StrokeHistogram, reductionFactor: NAT] RETURNS [RunSizeMap] ~ {
size: NAT ~ hist.size+1;
rsm: RunSizeMap ← ImagerPixelSeq.Create[size];
halfFact: REAL ← reductionFactor*0.5;
weight: REAL ← Weight[];
Weight: PROC RETURNS [REAL] ~ {
maxCount: NAT ← 0;
FOR i: NAT IN [0..hist.size) DO
maxCount ← MAX[maxCount, hist.count[i]];
ENDLOOP;
RETURN [maxCount*distortionWeightRelativeToMaxCount]
};
Count: PROC [i: NAT] RETURNS [NAT] ~ {RETURN [IF i>=size THEN 0 ELSE hist.count[i]]};
rsm.Clear[size];
FOR i: NAT IN [0..(size+reductionFactor-1)/reductionFactor) DO
best: REAL ← Real.LargestNumber;
bestk: NATNAT.LAST;
FOR j: NAT IN [0..reductionFactor) DO
k: NAT ← i*reductionFactor+j;
ratio: REAL ← (j-halfFact)/halfFact;
p: REAL ← ratio*ratio*weight + Count[k] + Count[k+1];
IF p < best THEN {best ← p; bestk ← k};
ENDLOOP;
IF bestk+1 < size THEN rsm[bestk+1] ← 1;
ENDLOOP;
FOR i: NAT IN (0..size) DO
rsm[i] ← rsm[i]+rsm[i-1];
ENDLOOP;
RETURN [rsm];
};
SimpleRunSizeMap: PUBLIC PROC [size: NAT, reductionFactor: NAT] RETURNS [RunSizeMap] ~ {
Badness: PROC [in, out: INT] RETURNS [REAL] ~ {
a: INT ← in;
b: INT ← out*reductionFactor;
IF a < b THEN {t: INT ← a; a ← b; b ← t};
IF b = 0 THEN RETURN [IF a=0 THEN 0 ELSE 999999.0]
ELSE RETURN [ABS[1.0-REAL[a]/REAL[b]]];
};
rsm: RunSizeMap ← ImagerPixelSeq.Create[size];
factor: REAL ← reductionFactor;
FOR i: NAT IN [0..size) DO
r: INT ← Real.RoundLI[i/factor];
best: INT ← r;
FOR s: INT IN [MAX[r-1, 0]..r+1] DO
IF Badness[i, s] < Badness[i, best] THEN best ← s;
ENDLOOP;
rsm[i] ← best;
ENDLOOP;
rsmViewer ← ShowHist[rsmViewer, NEW[StrokeHistogramRep ← [rsm.maxSize, rsm]], 0, 0];
RETURN [rsm];
};
Viewer: TYPE ~ ViewerClasses.Viewer;
ShowHist: PROC [viewer: Viewer, hist: StrokeHistogram, m1, m2: NAT] RETURNS [Viewer] ~ {
max: NAT ← 0;
squashFactor: NAT ← 1;
pm: PixelMap;
FOR i: NAT IN [0..hist.size) DO
max ← MAX[max, hist.count[i]];
ENDLOOP;
WHILE (max+squashFactor/2)/squashFactor > 400 DO squashFactor ← 2*squashFactor ENDLOOP;
max ← (max+squashFactor-1)/squashFactor;
pm ← ImagerPixelMap.Create[0, [-max, -2, max+2+4, hist.size+2]];
pm.Clear;
pm.Fill[[-max, -1, max, 1], 1];
pm.Fill[[0, -1, 1, hist.size+1], 1];
pm.Fill[[2, m1, 1, 1], 1];
pm.Fill[[2, m2, 2, 1], 1];
FOR i: NAT IN [0..hist.size) DO
h: NAT ← (hist.count[i]+squashFactor/2)/squashFactor;
pm.Fill[[-h, i, h, 1], 1];
ENDLOOP;
IF viewer = NIL OR viewer.destroyed THEN viewer ← BitmapViewer.Create[[name: "Stroke Width Histogram"], TRUE];
BitmapViewer.SetBitmap[viewer, pm];
RETURN [viewer]
};
Scale: PROC [i: INT, r: REAL] RETURNS [INT] ~ {
RETURN[Real.RoundLI[i*r]];
};
Sqr: PROC [nat: NAT] RETURNS [INT] ~ INLINE {RETURN [Basics.LongMult[nat, nat]]};
ties: INT ← 0;
paranoid: BOOLEAN ~ TRUE;
Set to FALSE at compile time to eliminate some extra runtime checks.
Floor: PROC[a: REAL] RETURNS[c: INT] = {
c ← Real.Fix[a];
IF c>a THEN RETURN[c-1] ELSE RETURN[c]
};
Ceiling: PROC[a: REAL] RETURNS[c: INT] = {
c ← Real.Fix[a];
IF c<a THEN RETURN[c+1] ELSE RETURN[c]
};
hugeReal: REAL ~ 1.0E+20;
nullLink: NAT ~ NAT.LAST/2;
DetermineGrid: PUBLIC PROC [edgeProjection: EdgeProjection, param: OptimizationParameters] ~ {
factor: NAT ~ edgeProjection.reductionFactor;
swathStart: INTEGER ~ Floor[factor*0.5-param.maxOutputDeviation*factor];
swathEnd: INTEGER ~ Ceiling[factor*0.5+param.maxOutputDeviation*factor];
swathSize: NAT ~ swathEnd-swathStart;
minSpan: NAT ~ Ceiling[factor*0.5];
maxSpan: NAT ~ Ceiling[factor*1.5];
This version of the dynamic programming is driven by a loop on the output pixels, rather than by the input. It requires a different organization of the totalBadness and link tables. Since mesa does not allow run-time sizing of two-dimensional arrays, the sequences are allocated sequentially and the indices are calculated with the SUB procedure:
SUB: PROC [outputPixelIndex: NAT, delta: INTEGER] RETURNS [NAT] ~ --INLINE-- {
IF paranoid AND delta NOT IN [swathStart..swathEnd) THEN ERROR;
RETURN [outputPixelIndex*swathSize+(delta-swathStart)]
};
Thus the tables are organized with one row for each output pixel. The following procedure finds the input pixel number from the same parameters that SUB takes:
InputIndex: PROC [outputPixelIndex: NAT, delta: INTEGER] RETURNS [NAT] ~ --INLINE-- {
IF paranoid AND delta NOT IN [swathStart..swathEnd) THEN ERROR;
RETURN [outputPixelIndex*factor+delta]
};
inputSize: NAT ~ edgeProjection.size;
outputSize: NAT ~ (inputSize+factor-1)/factor;
tableSize: NAT ~ SUB[outputSize, swathStart];
reductionSqr: REAL ← Sqr[factor];
costScale: REAL ← param.evenness*4.0*(edgeProjection.weight+1)/reductionSqr;
DistortionCost: PROC [distortion: REAL] RETURNS [REAL] ~ {
RETURN [distortion*distortion*costScale];
};
pinningScale: REAL ← param.edgePinning*(edgeProjection.weight+1)/(factor*0.5);
penalty: RealSeq ← edgeProjection.penalty;
totalBadness: RealSeq ←
IF edgeProjection.totalBadness.length >= tableSize THEN edgeProjection.totalBadness
ELSE (edgeProjection.totalBadness ← NEW[RealSeqRep[tableSize]]);
link: PixelSeq ←
IF edgeProjection.link.maxSize >= tableSize THEN edgeProjection.link
ELSE (edgeProjection.link ← ImagerPixelSeq.Create[tableSize]);
FOR j: INTEGER IN [swathStart..swathEnd) DO
totalBadness[SUB[0, j]] ← hugeReal;
link[SUB[0, j]] ← nullLink;
ENDLOOP;
FOR j: INTEGER IN [0..factor) DO
totalBadness[SUB[0, j]] ← ABS[j+0.5-factor*0.5]*pinningScale;
ENDLOOP;
FOR i: NAT IN (0..outputSize) DO
FOR j: INTEGER IN [swathStart..swathEnd) DO
subij: NAT ~ SUB[i, j];
inputij: NAT ~ InputIndex[i, j];
best: REAL ← hugeReal;
bestLink: NAT ← 0;
FOR k: NAT IN [minSpan..maxSpan] DO
delta: INTEGER ~ j-(factor-k);
IF delta IN [swathStart..swathEnd) THEN {
s: NAT ~ SUB[i-1, delta];
p: REAL ← DistortionCost[k-factor] + totalBadness[s];
IF p < best THEN {best ← p; bestLink ← s};
};
ENDLOOP;
IF inputij IN [0..inputSize) THEN best ← best + penalty[inputij]*param.penaltyWeight;
IF i = outputSize-1 THEN best ← best + ABS[j+0.5-factor*0.5]*pinningScale;
totalBadness[subij] ← best;
link[subij] ← bestLink;
ENDLOOP;
ENDLOOP;
BEGIN -- Find the best ending position --
bestj: NAT ← factor/2;
FOR j: INTEGER IN [0..factor) DO
IF totalBadness[SUB[outputSize-1, j]] < totalBadness[SUB[outputSize-1, bestj]]
THEN bestj ← j;
ENDLOOP;
edgeProjection.bestEnd ← SUB[outputSize-1, bestj];
END;
edgeProjection.swathStart ← swathStart;
edgeProjection.swathSize ← swathSize;
};
EnumerateSampleCoordinates: PUBLIC PROC [edgeProjection: EdgeProjection, action: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER]] ~ {
factor: NAT ~ edgeProjection.reductionFactor;
inputOrigin: INTEGER ~ edgeProjection.origin;
outputOrigin: INTEGER ← FloorDiv[inputOrigin, factor];
swathStart: INTEGER ~ edgeProjection.swathStart;
swathSize: NAT ~ edgeProjection.swathSize;
FOR i: INTEGER ← edgeProjection.bestEnd, edgeProjection.link[i] UNTIL i = nullLink DO
outputPixelIndex: NAT ~ NAT[i] / swathSize;
delta: INTEGER ~ i - outputPixelIndex*swathSize + swathStart;
inputPixelIndex: INTEGER ~ outputPixelIndex*factor+delta;
action[inputPixelIndex+inputOrigin, outputPixelIndex+outputOrigin];
ENDLOOP;
};
Bounds: TYPE ~ RECORD [min, max: INTEGER];
Size: PROC [bounds: Bounds] RETURNS [NAT] ~ INLINE {
RETURN [bounds.max - bounds.min]
};
Expand1: PROC [bounds: Bounds] RETURNS [Bounds] ~ INLINE {
RETURN [[bounds.min-1, bounds.max+1]]
};
FindBounds: PROC [edgeProjection: EdgeProjection, minLarge, maxLarge: INTEGER] RETURNS [bounds: Bounds] ~ {
action: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER] ~ {
IF inputPixelCoord IN [minLarge..maxLarge) THEN {
bounds.min ← MIN[bounds.min, outputPixelCoord];
bounds.max ← MAX[bounds.max, outputPixelCoord];
};
};
bounds.min ← INTEGER.LAST;
bounds.max ← INTEGER.FIRST;
EnumerateSampleCoordinates[edgeProjection, action];
IF bounds.max < bounds.min THEN {bounds.max ← bounds.min ← 0};
bounds.max ← bounds.max + 2;
bounds.min ← bounds.min - 1;
};
ModulatedSample: PUBLIC PROC [sProjection, fProjection: EdgeProjection, pixelMap: PixelMap] RETURNS [PixelMap] ~ {
bb: ImagerPixelMap.DeviceRectangle ← pixelMap.Window;
sBounds: Bounds ← FindBounds[sProjection, bb.sMin, bb.sMin+bb.sSize];
fBounds: Bounds ← FindBounds[fProjection, bb.fMin, bb.fMin+bb.fSize];
t: PixelMap ← ImagerPixelMap.Create[0, [sBounds.min, fBounds.min, Size[sBounds], Size[fBounds]]];
sAction: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER] ~ {
IF inputPixelCoord IN [bb.sMin..bb.sMin+bb.sSize) THEN {
sSmall: INTEGER ← outputPixelCoord;
sLarge: INTEGER ← inputPixelCoord;
fAction: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER] ~ {
IF inputPixelCoord IN [bb.fMin..bb.fMin+bb.fSize) THEN {
pix: CARDINAL ← pixelMap.GetBit[sLarge, inputPixelCoord];
IF pix#0 THEN t.Fill[[sSmall, outputPixelCoord, 1, 1], 1];
};
};
EnumerateSampleCoordinates[fProjection, fAction];
};
};
IF show THEN ShowModulation[sProjection, fProjection, pixelMap];
t.Clear;
EnumerateSampleCoordinates[sProjection, sAction];
Show[sProjection, fProjection, pixelMap, t];
IF show THEN ShowModulation[sProjection, fProjection, pixelMap];
IF show THEN ShowSmall[t];
RETURN [t]
};
ComputeConvolutionKernel: PUBLIC PROC [reductionFactor: INT] RETURNS [PixelMap] ~ {
t: PixelMap ← ImagerPixelMap.Create[4, [-reductionFactor, -reductionFactor, reductionFactor*2+1, reductionFactor*2+1]];
t.Clear;
t.Fill[[0,0,1,1], CARDINAL.LAST];
ImagerPixelSeq.BoxFilter[t, reductionFactor, reductionFactor];
ImagerPixelSeq.BoxFilter[t, reductionFactor, reductionFactor];
RETURN [t]
};
GraySample: PUBLIC PROC [sProjection, fProjection: EdgeProjection, pixelMap: PixelMap, kernel: PixelMap, pmScratch: REF ImagerPixelMap.PixelMapRep] RETURNS [PixelMap] ~ {
bb: ImagerPixelMap.DeviceRectangle ← pixelMap.Window;
kb: ImagerPixelMap.DeviceRectangle ← kernel.Window;
kfMax: INTEGER ~ kb.fMin+kb.fSize;
sBounds: Bounds ← Expand1[FindBounds[sProjection, bb.sMin, bb.sMin+bb.sSize]];
fBounds: Bounds ← Expand1[FindBounds[fProjection, bb.fMin, bb.fMin+bb.fSize]];
t: PixelMap ← ImagerPixelMap.Reshape[pmScratch, 3, [sBounds.min, fBounds.min, Size[sBounds], Size[fBounds]]];
fOrigin: INTEGER ~ bb.fMin+kb.fMin;
fSize: NAT ~ bb.fSize+kb.fSize;
fMax: INTEGER ~ fOrigin + fSize;
seq: PixelSeq ~ ImagerPixelSeq.ObtainScratch[fSize];
out: PixelSeq ~ ImagerPixelSeq.ObtainScratch[Size[fBounds]];
sAction: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER] ~ {
inputS: INTEGER ~ inputPixelCoord;
outputS: INTEGER ~ outputPixelCoord;
out.Clear[Size[fBounds]];
FOR s: INTEGER IN [inputS+kb.sMin..inputS+kb.sMin+kb.sSize) DO
fAction: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER] ~ {
inputF: INTEGER ~ inputPixelCoord;
outputF: INTEGER ~ outputPixelCoord;
IF outputF IN [fBounds.min..fBounds.max) THEN {
sum: CARDINAL ← out[outputF-fBounds.min];
FOR f: INTEGER IN [MAX[inputF+kb.fMin, fOrigin]..MIN[inputF+kfMax, fMax]) DO
IF seq[f-fOrigin] # 0 THEN sum ← sum + kernel.Get16Bits[s-inputS, f-inputF];
ENDLOOP;
out[outputF-fBounds.min] ← sum;
};
};
seq.LoadF[s: s, f: fOrigin, size: fSize, source: pixelMap];
EnumerateSampleCoordinates[fProjection, fAction];
ENDLOOP;
FOR j: NAT IN [0..Size[fBounds]) DO
out[j] ← out[j]/256;
ENDLOOP;
out.StoreF[s: outputS, f: fBounds.min, size: Size[fBounds], dest: t];
};
t.Clear[];
EnumerateSampleCoordinates[sProjection, sAction];
ImagerPixelSeq.ReleaseScratch[seq];
ImagerPixelSeq.ReleaseScratch[out];
RETURN [t]
};
FloorDiv: PROC [num: INTEGER, denom: NAT] RETURNS [INTEGER] ~ {
q, r: CARDINAL;
[q, r] ← Basics.DivMod[ABS[num], denom];
IF num >= 0 THEN RETURN[q]
ELSE IF r = 0 THEN RETURN[-q]
ELSE RETURN[-1-q];
};
CeilingDiv: PROC [num: INTEGER, denom: NAT] RETURNS [quotient: INTEGER] ~ {
RETURN [FloorDiv[num+(denom-1), denom]]
};
ComputeFontBB: PROC [input: RasterFontIO.InternalFont] RETURNS [ImagerPixelMap.DeviceRectangle] ~ {
sMin: INTEGERINTEGER.LAST;
fMin: INTEGERINTEGER.LAST;
sMax: INTEGERINTEGER.FIRST;
fMax: INTEGERINTEGER.FIRST;
Do: PROC[pixelMap: PixelMap] ~ {
bb: ImagerPixelMap.DeviceRectangle ← pixelMap.BoundedWindow;
sMin ← MIN[sMin, bb.sMin];
sMax ← MAX[sMax, bb.sMin+bb.sSize];
fMin ← MIN[fMin, bb.fMin];
fMax ← MAX[fMax, bb.fMin+bb.fSize];
};
FOR c: CHAR IN CHAR DO
Do[input.charRep[c].pixels];
ENDLOOP;
Do[input.defaultChar.pixels];
RETURN [[sMin, fMin, sMax-sMin, fMax-fMin]];
};
ConvertGrayPixelMap: PUBLIC PROC [pixelMap: PixelMap, reductionFactor: INT, param: OptimizationParameters, runSizeMap: RunSizeMap, kernel: PixelMap, sScratch, fScratch: EdgeProjection, pmScratch: REF ImagerPixelMap.PixelMapRep] RETURNS [result: PixelMap] ~ {
inputBB: ImagerPixelMap.DeviceRectangle ← pixelMap.BoundedWindow;
sMin: INTEGER ~ FloorDiv[inputBB.sMin, reductionFactor] - 1;
sMax: INTEGER ~ CeilingDiv[inputBB.sMin+inputBB.sSize, reductionFactor] + 1;
fMin: INTEGER ~ FloorDiv[inputBB.fMin, reductionFactor] - 1;
fMax: INTEGER ~ CeilingDiv[inputBB.fMin+inputBB.fSize, reductionFactor] + 1;
outputBB: ImagerPixelMap.DeviceRectangle ~ [sMin, fMin, sMax-sMin, fMax-fMin];
sProjection: EdgeProjection ← CreateEdgeProjection[TRUE, sMin*reductionFactor, (sMax-sMin)*reductionFactor, reductionFactor, sScratch];
fProjection: EdgeProjection ← CreateEdgeProjection[FALSE, fMin*reductionFactor, (fMax-fMin)*reductionFactor, reductionFactor, fScratch];
ProjectEdges[fProjection, pixelMap, runSizeMap];
ProjectEdges[sProjection, pixelMap, runSizeMap];
DetermineGrid[fProjection, param];
DetermineGrid[sProjection, param];
result ← GraySample[sProjection, fProjection, pixelMap, kernel, pmScratch];
Process.CheckForAbort[];
};
ConvertPixelMap: PUBLIC PROC [pixelMap: PixelMap, reductionFactor: INT, param: OptimizationParameters, runSizeMap: RunSizeMap, sScratch, fScratch: EdgeProjection] RETURNS [result: PixelMap] ~ {
inputBB: ImagerPixelMap.DeviceRectangle ← pixelMap.BoundedWindow;
sMin: INTEGER ~ FloorDiv[inputBB.sMin, reductionFactor] - 1;
sMax: INTEGER ~ CeilingDiv[inputBB.sMin+inputBB.sSize, reductionFactor] + 1;
fMin: INTEGER ~ FloorDiv[inputBB.fMin, reductionFactor] - 1;
fMax: INTEGER ~ CeilingDiv[inputBB.fMin+inputBB.fSize, reductionFactor] + 1;
outputBB: ImagerPixelMap.DeviceRectangle ~ [sMin, fMin, sMax-sMin, fMax-fMin];
sProjection: EdgeProjection ← CreateEdgeProjection[TRUE, sMin*reductionFactor, (sMax-sMin)*reductionFactor, reductionFactor, sScratch];
fProjection: EdgeProjection ← CreateEdgeProjection[FALSE, fMin*reductionFactor, (fMax-fMin)*reductionFactor, reductionFactor, fScratch];
ProjectEdges[fProjection, pixelMap, runSizeMap];
ProjectEdges[sProjection, pixelMap, runSizeMap];
DetermineGrid[fProjection, param];
DetermineGrid[sProjection, param];
result ← ModulatedSample[sProjection, fProjection, pixelMap];
Process.CheckForAbort[];
};
sampleRope: Rope.ROPE ← "ABEFHOWtteeafg[]{}--==;";
bc: CHARCHAR.FIRST;
ec: CHARCHAR.LAST;
fatType: NAT ← 1;
minFatness: REAL ← .707;
debugHist: StrokeHistogram ← NIL;
debugFat: NAT;
hairlinePercentile: REAL ← 5.0;
evenStrokes: BOOLEANTRUE;
ConvertFont: PUBLIC PROC [input: RasterFontIO.InternalFont, reductionFactor: INT, param: OptimizationParameters] RETURNS [RasterFontIO.InternalFont] ~ {
ComputeFatDiameter: PROC RETURNS [NAT] ~ {
hist: StrokeHistogram ← CreateStrokeHistogram[];
hairline: INTEGER ← 0;
FOR i: INT IN [0..sampleRope.Length) DO
c: CHAR ← sampleRope.Fetch[i];
charRep: RasterFontIO.InternalCharRep ← input.charRep[c];
AccumulateHistogram[hist, charRep.pixels, TRUE];
AccumulateHistogram[hist, charRep.pixels, FALSE];
ENDLOOP;
hairline ← FindPercentile[hist, hairlinePercentile];
histViewer ← ShowHist[histViewer, hist, 0, hairline];
debugHist ← hist;
RETURN [debugFat ← MAX[Scale[reductionFactor+1-hairline, minFatness], 1]];
};
fatDiameter: NAT ← ComputeFatDiameter[];
halfFat: NAT ← (fatDiameter+1)/2;
Fatten: PROC [pixelMap: PixelMap] RETURNS [PixelMap] ~ {
IF fatDiameter > 1 THEN {
pixelMap ← FattenPixels.AddBorder[pixelMap, halfFat, 0];
SELECT fatType FROM
0 => NULL;
1 => FattenPixels.FattenRound[pixelMap, fatDiameter];
2 => FattenPixels.FattenDiamond[pixelMap, fatDiameter];
3 => FattenPixels.FattenSquare[pixelMap, fatDiameter];
ENDCASE => NULL;
};
RETURN [pixelMap.Trim]
};
ComputeRunSizeMap: PROC RETURNS [rsm: RunSizeMap] ~ {
hist: StrokeHistogram ← CreateStrokeHistogram[];
FOR i: INT IN [0..sampleRope.Length) DO
c: CHAR ← sampleRope.Fetch[i];
pixelMap: PixelMap ← Fatten[input.charRep[c].pixels];
AccumulateHistogram[hist, pixelMap, TRUE];
AccumulateHistogram[hist, pixelMap, FALSE];
ENDLOOP;
altHistViewer ← ShowHist[altHistViewer, hist, 0, 0];
rsm ← RunSizeMapFromHistogram[hist, reductionFactor];
rsmViewer ← ShowHist[rsmViewer, NEW[StrokeHistogramRep ← [rsm.maxSize, rsm]], 0, 0];
RETURN [rsm];
};
runSizeMap: RunSizeMap ← IF evenStrokes THEN ComputeRunSizeMap[] ELSE NIL;
new: RasterFontIO.InternalFont ← NEW[RasterFontIO.InternalFontRep];
inputBB: ImagerPixelMap.DeviceRectangle ← ComputeFontBB[input];
sProjection: EdgeProjection ← CreateEdgeProjection[TRUE, inputBB.sMin-reductionFactor, inputBB.sSize+2*reductionFactor, reductionFactor];
fProjection: EdgeProjection ← CreateEdgeProjection[FALSE, inputBB.fMin-reductionFactor, inputBB.fSize+2*reductionFactor, reductionFactor];
new.family ← input.family;
new.face ← input.face;
new.bitsPerEmQuad ← input.bitsPerEmQuad/reductionFactor;
new.defaultChar.pixels ← ConvertPixelMap[Fatten[input.defaultChar.pixels], reductionFactor, param, runSizeMap, sProjection, fProjection];
new.defaultChar.fWidth ← input.defaultChar.fWidth/reductionFactor;
new.defaultChar.sWidth ← input.defaultChar.sWidth/reductionFactor;
FOR c: CHAR IN CHAR DO
IF c IN [bc..ec] AND input.charRep[c] # input.defaultChar THEN {
charRep: RasterFontIO.InternalCharRep ← input.charRep[c];
charRep.pixels ← ConvertPixelMap[Fatten[charRep.pixels], reductionFactor, param, runSizeMap, sProjection, fProjection];
charRep.fWidth ← charRep.fWidth/reductionFactor;
charRep.sWidth ← charRep.sWidth/reductionFactor;
new.charRep[c] ← charRep;
}
ELSE {
new.charRep[c] ← new.defaultChar;
};
ENDLOOP;
RETURN [new]
};
EnumerateValues: PROC [seq: RealSeq, size: NAT, maxVal: NAT, action: PROC [index, val: NAT]] ~ {
min: REAL ← 999999;
max: REAL ← -999999;
scale: REAL;
FOR i: NAT IN [0..size) DO
max ← MAX[max, seq[i]];
min ← MIN[min, seq[i]];
ENDLOOP;
scale ← maxVal/(max-min+1);
FOR i: NAT IN [0..size) DO
action[i, Real.RoundLI[(seq[i]-min)*scale]];
ENDLOOP;
};
gray: ImagerPixelMap.Tile ← ImagerPixelMap.TileFromStipple[5A5AH];
gsize: NAT ← 200;
size of the displayed graphs.
ShowModulation: PROC [sProjection, fProjection: EdgeProjection, pixelMap: PixelMap] ~ {
bb: ImagerPixelMap.DeviceRectangle ← [
sMin: sProjection.origin,
fMin: fProjection.origin,
sSize: sProjection.size,
fSize: fProjection.size
];
xbb: ImagerPixelMap.DeviceRectangle ← [
sMin: bb.sMin,
fMin: bb.fMin,
sSize: bb.sSize + gsize + 1,
fSize: bb.fSize + gsize + 1
];
t: PixelMap ← ImagerPixelMap.Create[0, xbb];
t.Clear;
t.Transfer[pixelMap];
{action: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER]
~ {t.Clip[[bb.sMin, inputPixelCoord, bb.sSize, 1]].TransferTile[gray]};
EnumerateSampleCoordinates[fProjection, action]
};
{action: PROC [index, val: NAT] ~
{t.Fill[[bb.sMin+bb.sSize+1, index+fProjection.origin, val+1, 1], 1]};
seq: RealSeq ← SELECT displayType FROM
penalty => fProjection.penalty,
badness => fProjection.totalBadness,
ENDCASE => ERROR;
EnumerateValues[seq, fProjection.size, gsize, action];
};
{action: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER]
~ {t.Clip[[inputPixelCoord, bb.fMin, 1, bb.fSize]].TransferTile[gray];};
EnumerateSampleCoordinates[sProjection, action]
};
{action: PROC [index, val: NAT] ~
{t.Fill[[index+sProjection.origin, bb.fMin+bb.fSize+1, 1, val+1], 1]};
seq: RealSeq ← SELECT displayType FROM
penalty => sProjection.penalty,
badness => ERROR,
ENDCASE => ERROR;
EnumerateValues[seq, sProjection.size, gsize, action];
};
IF modulationViewer = NIL OR modulationViewer.destroyed THEN {
modulationViewer ← BitmapViewer.Create[[name: "Grid Modulation"], TRUE];
Menus.InsertMenuEntry[modulationViewer.menu, Menus.CreateEntry[name: "Penalty/Badness", proc: PenaltyBadnessHit]];
Menus.InsertMenuEntry[modulationViewer.menu, Menus.CreateEntry[name: "Next", proc: NextHit]];
};
BitmapViewer.SetBitmap[modulationViewer, t];
};
ShowSmall: PROC [pixelMap: PixelMap] ~ {
IF smallViewer = NIL OR smallViewer.destroyed THEN smallViewer ← BitmapViewer.Create[[name: "Sampled With Grid Modulation"], TRUE];
BitmapViewer.SetBitmap[smallViewer, pixelMap];
};
show: BOOLEANTRUE;
pause: BOOLEANTRUE;
nextHits: NAT ← 0;
DisplayType: TYPE ~ {null, penalty, badness};
displayType: DisplayType ← penalty;
ButtonEvent: CONDITION ← [timeout: 100];
Show: ENTRY PROC [sProjection, fProjection: EdgeProjection, pixelMap, t: PixelMap] ~ {
ENABLE UNWIND => NULL;
currentDisplay: DisplayType ← null;
IF NOT show THEN RETURN;
UNTIL currentDisplay = displayType DO
ShowModulation[sProjection, fProjection, pixelMap];
ShowSmall[t];
currentDisplay ← displayType;
WHILE currentDisplay = displayType AND pause AND nextHits = 0 DO
Process.CheckForAbort[];
WAIT ButtonEvent;
ENDLOOP;
ENDLOOP;
IF pause THEN nextHits ← nextHits - 1;
};
NextHit: ENTRY Menus.ClickProc ~ {
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer ← NARROW[parent];
SELECT mouseButton FROM
red => {nextHits ← nextHits + 1; NOTIFY ButtonEvent};
yellow => {modulationViewer ← smallViewer ← NIL; pause ← TRUE; nextHits ← 1; NOTIFY ButtonEvent};
blue => {pause ← NOT pause; nextHits ← 0; NOTIFY ButtonEvent};
ENDCASE => ERROR;
};
PenaltyBadnessHit: ENTRY Menus.ClickProc ~ {
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer ← NARROW[parent];
SELECT mouseButton FROM
red => {displayType ← penalty; NOTIFY ButtonEvent};
blue => {displayType ← badness; NOTIFY ButtonEvent};
ENDCASE => ERROR;
};
modulationViewer: ViewerClasses.Viewer ← NIL;
smallViewer: ViewerClasses.Viewer ← NIL;
histViewer: ViewerClasses.Viewer ← NIL;
altHistViewer: ViewerClasses.Viewer ← NIL;
rsmViewer: ViewerClasses.Viewer ← NIL;
END.