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: BOOLEAN _ FALSE; ProjectEdges: PUBLIC PROC [edgeProjection: EdgeProjection, pixelMap: PixelMap, runSizeMap: RunSizeMap] ~ { bb: ImagerPixelMap.DeviceRectangle _ pixelMap.Trim.Window; line: ImagerPixelSeq.PixelSeq _ edgeProjection.buffer; min: INTEGER _ IF edgeProjection.sProjection THEN bb.fMin ELSE bb.sMin; size: INTEGER _ IF 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: CARDINAL _ IF j = edgeProjection.size THEN 0 ELSE line[j]; SELECT cur FROM > prev => runStart _ j; < prev => { runSize: NAT _ j-runStart; targetSize: NAT _ IF runSize < runSizeMap.maxSize THEN runSizeMap[runSize] ELSE Real.RoundI[REAL[runSize]/edgeProjection.reductionFactor]; IF targetSize # 0 AND useDamped THEN { scale: REAL _ REAL[targetSize]/REAL[runSize]; quarterPeriod: INTEGER _ Real.RoundLI[1/(4*scale)]; waveExtent: INTEGER _ Real.RoundLI[(waveformPeriods-0.25)/scale]; start: NAT _ MAX[runStart-quarterPeriod, 0]; end: NAT _ MIN[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: REAL _ REAL[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: INTEGER _ IF sProjection THEN bb.sSize ELSE bb.fSize; seq: PixelSeq _ ImagerPixelSeq.ObtainScratch[seqSize]; min: INTEGER _ IF sProjection THEN bb.fMin ELSE bb.sMin; size: INTEGER _ IF 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: REAL _ IF 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: NAT _ NAT.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; RETURN [rsm]; }; 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; 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= 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]; }; }; t.Clear; EnumerateSampleCoordinates[sProjection, sAction]; 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: INTEGER _ INTEGER.LAST; fMin: INTEGER _ INTEGER.LAST; sMax: INTEGER _ INTEGER.FIRST; fMax: INTEGER _ INTEGER.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: CHAR _ CHAR.FIRST; ec: CHAR _ CHAR.LAST; fatType: NAT _ 1; minFatness: REAL _ .707; debugHist: StrokeHistogram _ NIL; debugFat: NAT; hairlinePercentile: REAL _ 5.0; evenStrokes: BOOLEAN _ TRUE; 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]; 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; rsm _ RunSizeMapFromHistogram[hist, reductionFactor]; 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; show: BOOLEAN _ TRUE; pause: BOOLEAN _ TRUE; nextHits: NAT _ 0; DisplayType: TYPE ~ {null, penalty, badness}; displayType: DisplayType _ penalty; ButtonEvent: CONDITION _ [timeout: 100]; END. $GridModulationImpl.mesa Copyright (C) 1984, Xerox Corporation. All rights reserved. Michael Plass, November 27, 1985 6:36:16 pm PST rsmViewer _ ShowHist[rsmViewer, NEW[StrokeHistogramRep _ [rsm.maxSize, rsm]], 0, 0]; 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] }; Set to FALSE at compile time to eliminate some extra runtime checks. 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: 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: IF show THEN ShowModulation[sProjection, fProjection, pixelMap]; Show[sProjection, fProjection, pixelMap, t]; IF show THEN ShowModulation[sProjection, fProjection, pixelMap]; IF show THEN ShowSmall[t]; histViewer _ ShowHist[histViewer, hist, 0, hairline]; altHistViewer _ ShowHist[altHistViewer, hist, 0, 0]; rsmViewer _ ShowHist[rsmViewer, NEW[StrokeHistogramRep _ [rsm.maxSize, rsm]], 0, 0]; 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: 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; Ê![˜šœ™J™Jšœ6˜6Jš œœœ œ œ ˜8Jš œœœ œ œ ˜;šœœœ˜$Jšœ œ˜Jšœ œ˜Jšœ œ)˜Jšœœ˜ Jšœœœœ˜šœœœ˜%Jšœœ˜Jšœœ˜$Jšœœ.˜5Jšœ œ˜'Jšœ˜—Jšœœ˜(Jšœ˜—šœœœ ˜Jšœ˜Jšœ˜—Jšœ˜ Jšœ˜J˜—š žœœœœœœ˜Xš žœœ œœœ˜/Jšœœ˜ Jšœœ˜Jšœœœ˜)Jš œœœœœœ ˜2Jš œœœœœ˜'Jšœ˜—Jšœ.˜.Jšœœ˜šœœœ ˜Jšœœ˜ Jšœœ˜š œœœœ˜#Jšœ"œ ˜2Jšœ˜—Jšœ˜Jšœ˜—Jšœ œ1™TJšœ˜ Jšœ˜J˜—šœœ™$J™—šžœœ1œœ ™XJšœœ™ Jšœœ™J™ šœœœ™Jšœœ™Jšœ™—Jšœ)œœ™WJšœ(™(Jšœ@™@Jšœ ™ Jšœ™Jšœ$™$Jšœ™Jšœ™šœœœ™Jšœœ/™5Jšœ™Jšœ™—Jš œ œœœ@œ™nJšœ#™#Jšœ ™Jšœ™J™—š žœœœœœœ˜/Jšœ˜Jšœ˜J˜—šžœœœœœœœ˜QJ˜—šœœ˜J˜—šœ œœ˜Jšœœ8™DJ˜—š žœœœœœ˜(Jšœ˜Jš œœœœœ˜&J˜—J˜š žœœœœœ˜*Jšœ˜Jš œœœœœ˜&J˜J˜—Jšœ œ ˜Jšœ œœœ˜šž œœœD˜^Jšœœ"˜-Jšœ œ5˜HJšœ œ7˜HJšœ œ˜%Jšœ œ˜#Jšœ œ˜#JšœÎœ ™Üš œœœ œœœ˜NJš œ œœœœœ˜?Jšœ0˜6Jšœ˜—Jšœ–œ™ š œ œœ œœœ˜UJš œ œœœœœ˜?Jšœ ˜&Jšœ˜—Jšœ œ˜%Jšœ œ˜.Jšœ œœ˜-Jšœœ˜!Jšœ œ=˜Lš žœœœœœ˜:Jšœ#˜)Jšœ˜—Jšœœ<˜NJšœ*˜*šœ˜Jšœ1œ˜SJšœ œ˜@—šœ˜Jšœ*œ˜DJšœ:˜>—šœœœ˜+Jšœ œ˜#Jšœœ˜Jšœ˜—šœœœ ˜ Jšœ œ œ ˜=Jšœ˜—šœœœ˜ šœœœ˜+Jšœœœ˜Jšœ œ˜ Jšœœ ˜Jšœ œ˜šœœœ˜#Jšœœ˜šœœœ˜)Jšœœœ ˜Jšœœ.˜5Jšœ œ˜*Jšœ˜—Jšœ˜—Jšœ œœ4˜UJšœœœ ˜JJšœ˜Jšœ˜Jšœ˜—Jšœ˜—šÏc$˜)Jšœœ ˜šœœœ ˜ Jšœœ"œ˜NJšœ ˜Jšœ˜—Jšœœ˜2Jšœ˜—Jšœ'˜'Jšœ%˜%˜J˜——š žœœœ*œœœ˜Jšœœ"˜-Jšœ œ˜-Jšœœ!˜6Jšœ œ˜0Jšœ œ˜*šœœ2œ˜UJšœœœ˜+Jšœœ/˜=Jšœœ!˜9JšœC˜CJšœ˜—Jšœ˜J˜—šœœœ œ˜*J˜—š žœœœœœ˜4Jšœ˜ Jšœ˜J˜—šžœœœ œ˜:Jšœ˜%Jšœ˜J˜—šž œœ6œœ˜kšœœœœ˜Fšœœœ˜1Jšœ œ˜/Jšœ œ˜/Jšœ˜—Jšœ˜—Jšœ œœ˜Jšœ œœ˜Jšœ3˜3Jšœœ˜>Jšœ˜Jšœ˜Jšœ˜J˜—šžœœœ@œ˜rJšœ5˜5JšœE˜EJšœE˜EJšœa˜ašœ œœœ˜Gšœœœ˜8Jšœœ˜#Jšœœ˜"šœ œœœ˜Gšœœœ˜8Jšœœ,˜9Jšœœ-˜:Jšœ˜—Jšœ˜—Jšœ1˜1Jšœ˜—Jšœ˜—Jšœœ4™@Jšœ˜Jšœ1˜1Jšœ,™,Jšœœ4™@Jšœœ™Jšœ˜ Jšœ˜J˜—š žœœœœœ˜SJšœw˜wJšœ˜Jšœœœ˜!Jšœ>˜>Jšœ>˜>Jšœ˜ Jšœ˜J˜—š ž œœœ]œœ˜ªJšœ5˜5Jšœ3˜3Jšœœ˜"JšœN˜NJšœN˜NJšœm˜mJšœ œ˜#Jšœœ˜Jšœœ˜ Jšœ4˜4Jšœ<˜<šœ œœœ˜GJšœœ˜"Jšœ œ˜$Jšœ˜šœœœ+˜>šœ œœœ˜GJšœœ˜"Jšœ œ˜$šœ œœ˜/Jšœœ˜)š œœœœœ˜LJšœœ2˜LJšœ˜—Jšœ˜Jšœ˜—Jšœ˜—Jšœ;˜;Jšœ1˜1Jšœ˜—šœœœ˜#Jšœ˜Jšœ˜—JšœE˜EJšœ˜—Jšœ ˜ Jšœ1˜1Jšœ#˜#Jšœ#˜#Jšœ˜ Jšœ˜J˜—š žœœœ œœœ˜?Jšœœ˜Jšœœ˜(Jšœ œœ˜Jšœœœœ˜Jšœœ˜Jšœ˜J˜—š ž œœœ œœ œ˜KJšœ!˜'Jšœ˜J˜—šž œœ$œ%˜cJšœœœœ˜Jšœœœœ˜Jšœœœœ˜Jšœœœœ˜šžœœ˜ Jšœ<˜JšœBœ™HJšœr™rJšœ]™]J™—Jšœ,™,Jšœ™J™—šž œœ™(Jš œœœœKœ™ƒJšœ.™.Jšœ™J™—Jšœœœ˜Jšœœœ˜Jšœ œ˜Jšœ œ˜-Jšœ#˜#šœ œ˜(J˜—šžœœœF™VJšœœœ™Jšœ#™#Jšœœœœ™šœ™%Jšœ3™3Jšœ ™ Jšœ™šœœœ™@Jšœ™Jšœ ™Jšœ™—Jšœ™—Jšœœ™&Jšœ™J™—šžœœ™"Jšœœœ™Jšœœ ™.šœ ™Jšœ!œ™5Jšœ,œ œœ™aJšœœœ™>Jšœœ™—J™J™—šžœœ™,Jšœœœ™Jšœœ ™.šœ ™Jšœœ™3Jšœ œ™4Jšœœ™—J™J™—J™Jšœ)œ™-Jšœ$œ™(Jšœ#œ™'Jšœ&œ™*šœ"œ™&J™—Jšœ˜J˜——…—[z‘ù