<> <> <> <<>> 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]; }; <> <<>> <> <> <> <> <> <> <> < 400 DO squashFactor _ 2*squashFactor ENDLOOP;>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<};>> <<>> 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> SUB: PROC [outputPixelIndex: NAT, delta: INTEGER] RETURNS [NAT] ~ --INLINE-- { IF paranoid AND delta NOT IN [swathStart..swathEnd) THEN ERROR; RETURN [outputPixelIndex*swathSize+(delta-swathStart)] }; <> 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]; }; }; <> 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; <> <> <> <> <> <> <> <<];>> <> <> <> <> <> <<];>> <> <> <> <<{action: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER]>> <<~ {t.Clip[[bb.sMin, inputPixelCoord, bb.sSize, 1]].TransferTile[gray]};>> <> <<};>> <<{action: PROC [index, val: NAT] ~>> <<{t.Fill[[bb.sMin+bb.sSize+1, index+fProjection.origin, val+1, 1], 1]};>> <> < fProjection.penalty,>> < fProjection.totalBadness,>> < ERROR;>> <> <<};>> <<{action: PROC [inputPixelCoord: INTEGER, outputPixelCoord: INTEGER]>> <<~ {t.Clip[[inputPixelCoord, bb.fMin, 1, bb.fSize]].TransferTile[gray];};>> <> <<};>> <<{action: PROC [index, val: NAT] ~>> <<{t.Fill[[index+sProjection.origin, bb.fMin+bb.fSize+1, 1, val+1], 1]};>> <> < sProjection.penalty,>> < ERROR,>> < ERROR;>> <> <<};>> <> <> <> <> <<};>> <> <<};>> <<>> <> <> <> <<};>> <<>> show: BOOLEAN _ TRUE; pause: BOOLEAN _ TRUE; nextHits: NAT _ 0; DisplayType: TYPE ~ {null, penalty, badness}; displayType: DisplayType _ penalty; ButtonEvent: CONDITION _ [timeout: 100]; <> < NULL;>> <> <> <> <> <> <> <> <> <> <> <> <> <<};>> <<>> <> < NULL;>> <> <> < {displayType _ penalty; NOTIFY ButtonEvent};>> < {displayType _ badness; NOTIFY ButtonEvent};>> < ERROR;>> <<};>> <<>> <<>> <> <> <> <> <> <<>> END.