MakeTunedRasterFontImpl.mesa
Copyright (C) 1984, Xerox Corporation. All rights reserved.
Michael Plass, November 6, 1985 10:27:46 am PST
DIRECTORY AIS, Basics, CedarProcess, Process, DynamicBits, BitmapViewer, Commander, Imager, ImagerBox, ImagerPixelMap, ImagerPressFontSubst, ImagerTransformation, IO, ImagerFont, FontEdit, Real, Rope, RasterFontIO, ViewerClasses, GridModulation, FontTuningParameters, ImagerPixelSeq, PixelMapOps, ImagerMaskCapture, ProcessProps;
MakeTunedRasterFontImpl: CEDAR PROGRAM
IMPORTS AIS, Basics, CedarProcess, Process, DynamicBits, BitmapViewer, Commander, Imager, ImagerBox, ImagerPixelMap, ImagerPressFontSubst, ImagerTransformation, IO, ImagerFont, FontEdit, Real, Rope, RasterFontIO, GridModulation, FontTuningParameters, ImagerPixelSeq, PixelMapOps, ImagerMaskCapture, ProcessProps
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
Font: TYPE ~ ImagerFont.Font;
PixelMap: TYPE ~ ImagerPixelMap.PixelMap;
DeviceRectangle: TYPE ~ ImagerPixelMap.DeviceRectangle;
InternalFont: TYPE ~ RasterFontIO.InternalFont;
visible: BOOLEANTRUE;
bc: CHARCHAR.FIRST;
ec: CHARCHAR.LAST;
debugpm: PixelMap;
VideoInvert: PROC [pm: PixelMap] RETURNS [PixelMap] ~ {
pm.Fill[pm.Window, CARDINAL.LAST, [xor, null]];
debugpm ← pm;
RETURN [pm];
};
Threshold: PROC [pm: PixelMap] RETURNS [PixelMap] ~ {
bb: DeviceRectangle ← pm.Window;
new: PixelMap ← ImagerPixelMap.Create[0, bb];
seq: ImagerPixelSeq.PixelSeq ← ImagerPixelSeq.ObtainScratch[bb.fSize];
FOR s: INTEGER IN [bb.sMin..bb.sMin+bb.sSize) DO
seq.LoadF[s, bb.fMin, bb.fSize, pm];
FOR j: NAT IN [0..bb.fSize) DO
seq[j] ← seq[j] / 128;
ENDLOOP;
seq.StoreF[s, bb.fMin, bb.fSize, new];
ENDLOOP;
ImagerPixelSeq.ReleaseScratch[seq];
RETURN [new];
};
alternating: BOOLTRUE;
FromFont: PROC [font: Font, param: FontTuningParameters.Ref] RETURNS [internalFont: InternalFont] ~ {
bb: Imager.Box ← ImagerBox.BoxFromExtents[ImagerFont.FontBoundingBox[font]];
dbb: ImagerPixelMap.DeviceRectangle ← [Real.RoundLI[-bb.ymax], Real.RoundLI[+bb.xmin], Real.RoundLI[bb.ymax]-Real.RoundLI[bb.ymin], Real.RoundLI[bb.xmax]-Real.RoundLI[bb.xmin]];
viewer: ViewerClasses.Viewer ← IF visible THEN BitmapViewer.Create[[name: font.name], TRUE, center, center] ELSE NIL;
pfd: ImagerPressFontSubst.PressFontDescription ~ ImagerPressFontSubst.FindSubstitute[font.name, font.charToClient, press];
internalFont ← RasterFontIO.Create[dbb, dbb.fSize/param.gridReductionFactor];
IF pfd#NIL THEN {
internalFont.family ← pfd.family;
internalFont.face ← pfd.face;
};
internalFont.bitsPerEmQuad ← font.charToClient.SingularValues.x/param.gridReductionFactor;
CedarProcess.SetPriority[background];
BEGIN
inputBB: ImagerPixelMap.DeviceRectangle ← [dbb.sMin-2, dbb.fMin-2, dbb.sSize+4, dbb.fSize+4];
antialiasingFilter: PixelMap ~ GridModulation.ComputeConvolutionKernel[param.gridReductionFactor];
pmScratch: REF ImagerPixelMap.PixelMapRep ← NIL;
sScratch: GridModulation.EdgeProjection ← GridModulation.CreateEdgeProjection[TRUE, 0, inputBB.sSize, param.gridReductionFactor];
fScratch: GridModulation.EdgeProjection ← GridModulation.CreateEdgeProjection[FALSE, 0, inputBB.fSize, param.gridReductionFactor];
model: DynamicBits.Model;
ConvertPixelMap: PROC [pixelMap: PixelMap] RETURNS [PixelMap] ~ {
rawGray: PixelMap ← GridModulation.ConvertGrayPixelMap[
pixelMap: pixelMap,
reductionFactor: param.gridReductionFactor,
param: param.gridParam,
runSizeMap: runSizeMap,
kernel: antialiasingFilter,
sScratch: sScratch,
fScratch: fScratch,
pmScratch: pmScratch
];
gray: PixelMap ← VideoInvert[DynamicBits.AddBorder[rawGray.Trim[0], 2, 0]];
new: PixelMap ← Threshold[gray];
fixedBits: PixelMap;
scratch: REFNIL;
DoTestPass: PROC [passNumber: NAT] ~ {
w: DeviceRectangle ~ gray.Window;
swath: DeviceRectangle ← [w.sMin, w.fMin, w.sSize, param.swathWidth];
IF alternating AND passNumber MOD 2 = 1 THEN {
FOR f: INT DECREASING IN [w.fMin..w.fMin+w.fSize-param.swathWidth) DO
swath: DeviceRectangle ← [w.sMin, f, w.sSize, param.swathWidth];
scratch ← DynamicBits.TuneSwath[blurred, new, fixedBits, swath, model, scratch];
Process.CheckForAbort[];
ENDLOOP;
}
ELSE {
FOR f: INT IN [w.fMin..w.fMin+w.fSize-param.swathWidth) DO
swath: DeviceRectangle ← [w.sMin, f, w.sSize, param.swathWidth];
scratch ← DynamicBits.TuneSwath[blurred, new, fixedBits, swath, model, scratch];
Process.CheckForAbort[];
ENDLOOP;
};
IF viewer # NIL THEN {
BitmapViewer.SetBitmap[viewer, VideoInvert[new.Copy]];
};
};
blurred: PixelMap ← gray.Copy;
DynamicBits.Convolve[blurred, model.kernel, 255];
fixedBits ← DynamicBits.FindFixedBits[blurred, -model.neighborhood.sMin-model.kernel.sOrigin];
IF viewer # NIL THEN {
BitmapViewer.SetBitmap[viewer, VideoInvert[new.Copy]];
};
FOR i: INT IN [0..param.tuningPasses) DO
DoTestPass[i];
ENDLOOP;
pmScratch ← rawGray.refRep;
new ← VideoInvert[new];
RETURN [new.Trim[0]];
};
runSizeMap: GridModulation.RunSizeMap ← GridModulation.SimpleRunSizeMap[MAX[inputBB.sSize, inputBB.fSize], param.gridReductionFactor];
sProjection: GridModulation.EdgeProjection ← GridModulation.CreateEdgeProjection[TRUE, inputBB.sMin-param.gridReductionFactor, inputBB.sSize+2*param.gridReductionFactor, param.gridReductionFactor, NIL];
fProjection: GridModulation.EdgeProjection ← GridModulation.CreateEdgeProjection[FALSE, inputBB.fMin-param.gridReductionFactor, inputBB.fSize+2*param.gridReductionFactor, param.gridReductionFactor, NIL];
printerModel: DynamicBits.PrinterModel ~ {
intensity: REAL ← param.intensity[encoding];
scaledIntensity: REAL
(intensity-minMeanIntensity) / (maxMeanIntensity-minMeanIntensity);
RETURN [Real.RoundLI[scaledIntensity*DynamicBits.Intensity.LAST], Real.RoundLI[param.noiseWeight*param.noisePenalty[encoding]]]
};
minMeanIntensity: REAL ← 9999999999.9;
maxMeanIntensity: REAL ← 0;
m: Imager.Transformation ~ ImagerTransformation.Rotate[90];
FOR c: NAT IN [0..param.intensity.length) DO
minMeanIntensity ← MIN[minMeanIntensity, param.intensity[c]];
maxMeanIntensity ← MAX[maxMeanIntensity, param.intensity[c]];
ENDLOOP;
model ← DynamicBits.CreatePrinterModel[param.printerModelNeighborhood, printerModel, param.comparisonKernel];
FOR c: CHAR IN [bc..ec] DO
IF font.Contains[[0,ORD[c]]] THEN {
op: PROC[context: Imager.Context] ~ {
Char: ImagerFont.XStringProc ~ {charAction[[0, ORD[c]]]};
Imager.SetFont[context, font];
Imager.Show[context, Char];
};
widthVector: Imager.VEC ← ImagerFont.Width[font, [0, ORD[c]]];
pixels: ImagerPixelMap.PixelMap ← ImagerMaskCapture.CaptureBitmap[op, m];
pixels ← pixels.Trim[0];
IF viewer # NIL THEN BitmapViewer.SetBitmap[viewer, pixels];
internalFont.charRep[c] ← [
fWidth: widthVector.x/param.gridReductionFactor,
sWidth: -widthVector.y/param.gridReductionFactor,
pixels: ConvertPixelMap[pixels]
];
};
ENDLOOP;
END;
};
ConvertFile: PUBLIC PROC [outputFileName: ROPE, fontName: ROPE, size: REAL, rotation: REAL, bitsPerInch: REAL, param: FontTuningParameters.Ref] ~ {
t: Imager.Transformation ~ ImagerTransformation.Scale[size*param.gridReductionFactor].Concat[ImagerTransformation.Rotate[rotation]];
font: Font ← ImagerFont.Find[fontName].Modify[t];
internalFont: InternalFont ← FromFont[font, param];
internalFont.bitsPerInch ← bitsPerInch;
FontEdit.WriteFormatDerivedFromName[internalFont, outputFileName];
};
DoTestPass: PROC [passNumber: NAT, blurred: PixelMap, new: PixelMap, fixedBits: PixelMap, model: DynamicBits.Model, scratch: REF, param: FontTuningParameters.Ref, viewer: ViewerClasses.Viewer] RETURNS [newScratch: REF] ~ {
w: DeviceRectangle ~ blurred.Window;
n: NATSELECT passNumber MOD 4 FROM
0, 2 => w.fSize,
1, 3 => w.sSize,
ENDCASE => ERROR;
FOR i: INT IN [0..n) DO
swath: DeviceRectangle ←
SELECT passNumber MOD 4 FROM
0 => [w.sMin, w.fMin+i, w.sSize, param.swathWidth],
1 => [w.sMin+i, w.fMin, param.swathWidth, w.fSize],
2 => [w.sMin, w.fMin+w.fSize-1-i, w.sSize, param.swathWidth],
4 => [w.sMin+w.sSize-1-i, w.fMin, param.swathWidth, w.fSize],
ENDCASE => ERROR;
scratch ← DynamicBits.TuneSwath[blurred, new, fixedBits, swath, model, scratch];
IF viewer # NIL THEN {
BitmapViewer.SetBitmap[viewer, new];
};
Process.CheckForAbort[];
ENDLOOP;
RETURN [newScratch]
};
rotate: BOOLTRUE;
randomInit: BOOLTRUE;
paranoidPass: INTLAST[INT];
ConvertAIS: PUBLIC PROC [outputFileName: ROPE, aisName: ROPE, param: FontTuningParameters.Ref, comment: ROPE] ~ {
viewer: ViewerClasses.Viewer ← IF visible THEN BitmapViewer.Create[[name: outputFileName]] ELSE NIL;
gray: PixelMap ← PixelMapOps.LoadAIS[aisName].pixelMap;
new: PixelMap;
fixedBits: PixelMap;
scratch: REFNIL;
printerModel: DynamicBits.PrinterModel ~ {
intensity: REAL ← param.intensity[encoding];
scaledIntensity: REAL
(intensity-minMeanIntensity) / (maxMeanIntensity-minMeanIntensity);
RETURN [Real.RoundLI[scaledIntensity*DynamicBits.Intensity.LAST], Real.RoundLI[param.noiseWeight*param.noisePenalty[encoding]]]
};
model: DynamicBits.Model;
minMeanIntensity: REAL ← 9999999999.9;
maxMeanIntensity: REAL ← 0;
DoTestPass: PROC [passNumber: NAT] ~ {
w: DeviceRectangle ~ blurred.Window;
FOR f: INT IN [w.fMin..w.fMin+w.fSize-param.swathWidth) DO
swath: DeviceRectangle ← [w.sMin, f, w.sSize, param.swathWidth];
scratch ← DynamicBits.TuneSwath[blurred, new, fixedBits, swath, model, scratch];
IF viewer # NIL THEN {
BitmapViewer.SetBitmap[viewer, new];
};
IF passNumber>paranoidPass THEN PrintBadness[passNumber*1000+f];
Process.CheckForAbort[];
ENDLOOP;
};
PrintBadness: PROC [pass: INT] ~ {
WITH ProcessProps.GetProp[$CommanderHandle] SELECT FROM
cmd: Commander.Handle => {
err: INT ← DynamicBits.ErrorOf[blurred, new, model];
perPixelErr: REALREAL[err]/Basics.LongMult[new.sSize, new.fSize];
cmd.out.PutF["After %g pass(es), badness = %g (%g per pixel)\n", IO.int[pass], IO.int[err], IO.real[perPixelErr]];
};
ENDCASE => NULL;
};
rotation: [0..4) ← 0;
Rotate: PROC ~ {
new ← ImagerPixelMap.Rotate[new];
fixedBits ← ImagerPixelMap.Rotate[fixedBits];
blurred ← ImagerPixelMap.Rotate[blurred];
rotation ← (CARDINAL[rotation] + 1) MOD 4;
model ← DynamicBits.RotateModel[model];
scratch ← NIL;
new.sOrigin ← new.fOrigin ← fixedBits.sOrigin ← fixedBits.fOrigin ← blurred.sOrigin ← blurred.fOrigin ← 0;
};
blurred: PixelMap ← gray.Copy;
p: PixelMap;
IF randomInit THEN {
new ← VideoInvert[DynamicBits.RandomDither[gray]];
}
ELSE {
new ← ImagerPixelMap.Create[0, gray.Window];
new.TransferTile[ImagerPixelMap.TileFromStipple[5A5AH]];
};
FOR c: NAT IN [0..param.intensity.length) DO
minMeanIntensity ← MIN[minMeanIntensity, param.intensity[c]];
maxMeanIntensity ← MAX[maxMeanIntensity, param.intensity[c]];
ENDLOOP;
model ← DynamicBits.CreatePrinterModel[param.printerModelNeighborhood, printerModel, param.comparisonKernel];
DynamicBits.Convolve[blurred, model.kernel, 255];
fixedBits ← ImagerPixelMap.Create[0, gray.Window];
fixedBits.Clear[];
PrintBadness[0];
FOR i: INT IN [0..param.tuningPasses) DO
DoTestPass[i];
PrintBadness[i+1];
IF rotate THEN Rotate[];
IF viewer # NIL THEN {
BitmapViewer.SetBitmap[viewer, new];
};
ENDLOOP;
UNTIL rotation = 0 DO Rotate[] ENDLOOP;
IF viewer # NIL THEN {
BitmapViewer.SetBitmap[viewer, new];
};
StorePixelMap[outputFileName, new, TRUE, comment];
p ← DynamicBits.ApplyModel[new, model];
StorePixelMap[outputFileName.Concat["p"], p, FALSE, comment.Concat[" printer Model"]];
DynamicBits.Convolve[p, model.kernel, 255];
StorePixelMap[outputFileName.Concat["pb"], p, FALSE, comment.Concat[" printer and brain Model"]];
StorePixelMap[outputFileName.Concat["b"], blurred, FALSE, comment.Concat[" original with brain Model"]];
};
StorePixelMap: PROC [aisFileName: ROPE, source: ImagerPixelMap.PixelMap, bitmap: BOOLEANTRUE, comment: ROPE ← NIL] ~ TRUSTED {
output: AIS.FRef ← AIS.CreateFile[name: aisFileName, raster: NEW[AIS.RasterPart ← [
scanCount: source.sSize,
scanLength: source.fSize,
scanMode: rd,
bitsPerPixel: IF source.refRep.lgBitsPerPixel = 0 AND bitmap THEN 0 ELSE Basics.BITSHIFT[1, source.refRep.lgBitsPerPixel],
linesPerBlock: -1,
paddingPerBlock: 65535
]]];
outputWindow: AIS.WRef ← AIS.OpenWindow[output];
lineMap: ImagerPixelMap.PixelMap ← ImagerPixelMap.Create[source.refRep.lgBitsPerPixel, [source.sOrigin+source.sMin, source.fOrigin+source.fMin, 1, source.fSize]];
lineBufferDesc: AIS.Buffer ← [length: lineMap.refRep.words, addr: lineMap.refRep.pointer];
AIS.WriteComment[output, comment];
FOR i: NAT IN [0..source.sSize) DO
lineMap.Clear;
lineMap.Transfer[source];
lineMap.sOrigin ← lineMap.sOrigin + 1;
AIS.UnsafeWriteLine[outputWindow, lineBufferDesc, i];
ENDLOOP;
AIS.CloseFile[output];
};
Break: PROC [char: CHAR] RETURNS [IO.CharClass] = {
IF char = '← OR char = '; THEN RETURN [break];
IF char = ' OR char = '  OR char = ', OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
GetToken: PROC [stream: IO.STREAM] RETURNS [rope: ROPENIL] = {
rope ← stream.GetTokenRope[Break ! IO.EndOfStream => CONTINUE].token;
};
GetReal: PROC [stream: IO.STREAM] RETURNS [real: REAL ← 0.0] = {
real ← stream.GetReal[! IO.Error, IO.EndOfStream => CONTINUE];
};
MakeTunedRasterFontCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
outputName: ROPE ← GetToken[stream];
gets: ROPE ← GetToken[stream];
modelName: ROPE ← GetToken[stream];
inputName: ROPE ← GetToken[stream];
size: REAL ← GetReal[stream];
sizeUnits: ROPE ← GetToken[stream];
bitsPerInch: REAL ← GetReal[stream];
rotation: REAL ← GetReal[stream];
param: FontTuningParameters.Ref ← NIL;
IF bitsPerInch = 0.0 THEN bitsPerInch ← 384.0;
SELECT TRUE FROM
sizeUnits = NIL OR sizeUnits.Equal["pxl", FALSE] => NULL;
sizeUnits.Equal["pt", FALSE] => {size ← size/72.0*bitsPerInch};
sizeUnits.Equal["mica", FALSE] => {size ← size/2540.0*bitsPerInch};
ENDCASE => gets ← NIL;
IF NOT gets.Equal["←"] OR size <= 0.0 THEN {
cmd.out.PutRope[helpRope];
cmd.out.PutChar['\n];
RETURN;
};
param ← FontTuningParameters.Load[modelName.Concat[".fontTune"]];
ConvertFile[outputName, inputName, size, rotation, bitsPerInch, param];
};
helpRope: ROPE ~ "Convert a UnifiedFont to raster format\n <outputName> ← <model> <fontName> <size> <sizeUnits> <resolution> <rotation>\n <model> is the name of a printer model\n <fontName> is an Interpress hierarchical name\n <size> is the desired output size\n <sizeUnits> is pt, mica, or pxl\n <resolution> is in bits per inch\n <rotation> is in degrees";
BitTuneAISCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
outputName: ROPE ← GetToken[stream];
gets: ROPE ← GetToken[stream];
inputName: ROPE ← GetToken[stream];
modelName: ROPE ← GetToken[stream];
param: FontTuningParameters.Ref ← NIL;
proc: PROC ~ {ConvertAIS[outputName, inputName, param, Rope.Concat["BitTuneAIS ", cmd.commandLine]]};
IF NOT gets.Equal["←"] OR modelName = NIL THEN {
cmd.out.PutRope[helpRopeAIS];
cmd.out.PutChar['\n];
RETURN;
};
param ← FontTuningParameters.Load[modelName.Concat[".fontTune"]];
CedarProcess.DoWithPriority[priority: background, action: proc]
};
helpRopeAIS: ROPE ~ "Convert a Gray-scale AIS file to one bit per pixel\n <outputName> ← <inputName> <model>\n <model> is the name of a printer model\n";
Commander.Register["MakeTunedRasterFont", MakeTunedRasterFontCommand, helpRope];
Commander.Register["BitTuneAIS", BitTuneAISCommand, helpRopeAIS];
END.