InterpressToTapeImpl.mesa
Copyright Ó 1987, 1988 by Xerox Corporation. All rights reserved.
Maureen Stone, July 24, 1989 12:35:12 pm PDT
Doug Wyatt, April 12, 1988 6:38:07 pm PDT
Writes an Interpress master onto in raster form on a tape.
Formats supported are Crosfield (for making color separations at Kedie Orent)
DIRECTORY
ConvertToRasters, CrosfieldTape, OptronicsTape, Interpress, Imager, Rope, InterpressToTape, ImagerPixel, ImagerSample, ImagerTransformation, ImagerMaskCapture, PrintColor, Real, AIS, FS, IO, MessageWindow, PrintColorTransformations, TapesCommon, UnixTapeOps, SF, ImagerBox;
InterpressToTapeImpl: CEDAR PROGRAM
IMPORTS ConvertToRasters, CrosfieldTape, Interpress, Real, AIS, Rope, Imager, FS, IO, MessageWindow, PrintColorTransformations, OptronicsTape, UnixTapeOps, ImagerTransformation, ImagerMaskCapture, ImagerBox
EXPORTS InterpressToTape
~ BEGIN OPEN InterpressToTape;
ROPE: TYPE ~ Rope.ROPE;
ColorCorrection: TYPE ~ PrintColor.ColorCorrection;
PixelBuffer: TYPE ~ ImagerPixel.PixelBuffer;
ColorType: TYPE ~ OptronicsTape.ColorType;
Common Types and Utilities
DefaultTapeSpec: PUBLIC PROC [name: ROPE, tapeNumber: NAT] RETURNS[TapeSpec] = {
Convenience procedure
spec: TapeSpec ← NEW[TapeSpecRep ← [
note: name,
tapeNumber: tapeNumber,
serverName: "Chroma",
drive: 0,
density: GCR6250,
tapeSize: 2400
]];
RETURN[spec];
};
DefaultIPSize: PUBLIC PROC [name: ROPE, page: NAT ← 1, tol: REAL ← 0.02] RETURNS [fOrg, sOrg, fDim, sDim: REAL] = {
Renders the IP file through ImagerMaskCapture and returns the size to within a tolerance specified in inches. The bigger the tolerance the faster this will run. The returned box is guaranteed to be big enough. It's good to leave a little extra space around these images.
m: Imager.Transformation ← ImagerTransformation.Scale[1/tol/0.0254]; -- ~1/tol pixels/inch
n: Imager.Transformation ← ImagerTransformation.PostScale[m, 0.0254]; -- back to inches
master: Interpress.Master ← Interpress.Open[name, NIL];
Operator: PROC [context: Imager.Context] ~ {
Imager.SetColor[context, Imager.black];
Imager.SetStrokeEnd[context, square];
Imager.SetStrokeWidth[context, 0.0];
Imager.SetAmplifySpace[context, 1.0];
Interpress.DoPage[master, page, context, NIL];
};
b: SF.Box ← ImagerMaskCapture.CaptureBounds[Operator, m
! ImagerMaskCapture.Cant => RESUME];
r: ImagerBox.Rectangle ← ImagerTransformation.InverseTransformRectangle[
n, ImagerBox.RectangleFromBox[[b.min.s, b.min.f, b.max.s, b.max.f]]];
RETURN[fOrg: r.x, sOrg: r.y, fDim: r.w, sDim: r.h];
};
SizeFromIPSpecs: PUBLIC PROC[files: LIST OF IPSpec, format: Format, tapeSpec: TapeSpec] RETURNS[sizes: LIST OF Sizes, total: REAL] = {
crosfield: FileAction = {
fileSpec: CrosfieldTape.FileSpec ← NEW[CrosfieldTape.FileSpecRep ← [
pixelsPerInch: CrosfieldValToPPI[pixelsPerInch], nLines: nLines, nPixels: nPixels,
note: name, mapPixel: CrosfieldTape.ByteToCrosfield
]];
size: REAL ← CrosfieldTape.SizeOfFile[fileSpec, tapeSpec.density];
sizes ← CONS[[name, size], sizes];
total ← total+size;
};
optronics: FileAction = {
fileSpec: OptronicsTape.FileSpec ← NEW[OptronicsTape.FileSpecRep ← [
pixelsPerInch: OptronicsValToPPI[pixelsPerInch], nLines: nLines, nPixels: nPixels,
rgbInterleaved: NOT achromatic
]];
size: REAL ← OptronicsTape.SizeOfFile[fileSpec, tapeSpec.density];
sizes ← CONS[[name, size], sizes];
total ← total+size;
};
SELECT format FROM
optronics => {
total ← OptronicsTape.SizeOfHeader[tapeSpec.density];
DoFileList[files: files, fileAction: optronics, reduceResolution: 1, format: format];
};
crosfield => {
total ← CrosfieldTape.SizeOfHeader[tapeSpec.density];
DoFileList[files: files, fileAction: crosfield, reduceResolution: 1, format: format];
};
ENDCASE => ERROR;
};
WillNotFit: PUBLIC SIGNAL = CODE;
Converters
Writes the files specified on the tape in specified format format. List cannot be longer than 80, a generous limit given the typical file size.
WriteTape: PUBLIC PROC [files: LIST OF IPSpec, format: Format, tapeSpec: TapeSpec] = {
SELECT format FROM
crosfield => WriteCrosfieldTape[files, tapeSpec];
optronics => WriteOptronicsTape[files, tapeSpec];
ENDCASE;
};
As generating tapes and getting them processed takes a long time, it is important to be sure the correct bits are being written. Preview is used for checking position and windowing only. WriteAIS allows the more experience user to examine the CMYK or RGB density bytes produced by the different formats. For both routines, the the AIS files names will be generated by concatinating the wd with the root of the file name in the IPSpec. Typical use is to put all the AIS files in the same working directory.
It is inconvenient to examine AIS files at the high resolutions used by the tape formats.
reduceResolution reduces the resolution in the IPSpec by the indicated fraction.
Preview: PUBLIC PROC [files: LIST OF IPSpec, wd: ROPENIL, reduceResolution: REAL ← 0.25] = {
This routine previews the IPSpec to be sure it is accurately windowed by making three RGB intensity files. The colorCorrection field of the IPSpec is ignored.
WriteRGBAIS[files: files, wd: wd, reduceResolution: reduceResolution, preview: TRUE];
};
WriteAIS: PUBLIC PROC [files: LIST OF IPSpec, format: Format, wd: ROPENIL, reduceResolution: REAL ← 0.25] = {
Uses exactly the same code for rasterization as WriteTape but puts the bytes in 3 or 4 AIS files. This is CMYK negative files for Crosfield format, and RGB density files (will look negative) for Optronics. Use Preview to produce files easily viewed for proofing.
SELECT format FROM
crosfield => WriteCrosfieldAIS[files: files, wd: wd, reduceResolution: reduceResolution];
optronics => WriteRGBAIS[files: files, wd: wd, reduceResolution: reduceResolution, preview: FALSE];
ENDCASE;
};
Internal Procedures
InitCrosfieldTape: PUBLIC PROC [files: LIST OF IPSpec, tapeSpec: TapeSpec, rewindOnClose: BOOLEAN] = {
Must include correct FileSpecs for every file that may appear on the tape.
tape: UnixTapeOps.TapeHandle ← UnixTapeOps.OpenDrive[tapeSpec.serverName, tapeSpec.drive, tapeSpec.density];
fileSpecList: CrosfieldTape.FileSpecList ← CrosfieldFromIPSpec[files];
size: REAL ← 0;
Check the size and write the master directory.
size ← CrosfieldTape.SizeOfList[fileSpecList, tapeSpec.density];
IF size > tapeSpec.tapeSize THEN SIGNAL WillNotFit;
CrosfieldTape.InitializeTape[tapeHandle: tape, files: fileSpecList, tapeNumber: tapeSpec.tapeNumber, name: tapeSpec.note];
IF rewindOnClose THEN UnixTapeOps.WriteEOT[tape];
UnixTapeOps.CloseDrive[tape, rewindOnClose];
};
AddToCrosfieldTape: PUBLIC PROC[tapeSpec: TapeSpec, fileNumber: NAT, file: IPSpec, inPosition, rewindOnClose: BOOLEAN] = {
File numbers start at 1. Files must be added in the order specified in the master directory.
tapeSpec should match the one used in the call to InitCrosfieldTape.
fileSpecList: CrosfieldTape.FileSpecList ← CrosfieldFromIPSpec[LIST[file]];
fileSpec: CrosfieldTape.FileSpec ← fileSpecList.first; --special case of a general procedure
tape: UnixTapeOps.TapeHandle;
tapeNotOpen: BOOLEANTRUE;
rasterize: FileAction ~ {
crosfield: TapesCommon.FileHandle;
rasterProc: ConvertToRasters.RasterProc = { --Callback from ConvertToRasters
IF tapeNotOpen THEN { --wait to open the tape until rasters start coming
tape ← UnixTapeOps.OpenDrive[serverName: tapeSpec.serverName, driveNumber: tapeSpec.drive,density: tapeSpec.density, rewind: FALSE];
crosfield ← CrosfieldTape.AddFileToTape[tape, fileSpec, fileNumber, tapeSpec.tapeNumber, inPosition];
tapeNotOpen ← FALSE;
};
[] ← crosfield.newLine[crosfield, raster]; --send one raster to tape
};
convert[rasterProc]; --images the InterpressMaster. May take a long time
};
DoFileList[files: LIST[file], fileAction: rasterize, reduceResolution: 1, format: crosfield];
IF rewindOnClose THEN UnixTapeOps.WriteEOT[tape];
UnixTapeOps.CloseDrive[tape, rewindOnClose];
};
CrosfieldFromIPSpec: PROC[files: LIST OF IPSpec] RETURNS [fileSpecList: CrosfieldTape.FileSpecList] = {
buildFileSpecs: FileAction ~ {
fileSpec: CrosfieldTape.FileSpec ← NEW[CrosfieldTape.FileSpecRep ← [
pixelsPerInch: CrosfieldValToPPI[pixelsPerInch], nLines: nLines, nPixels: nPixels,
note: name, mapPixel: CrosfieldTape.ByteToCrosfield
]];
fileSpecList ← CONS[fileSpec, fileSpecList];
};
reverseList: PROC[list: CrosfieldTape.FileSpecList] RETURNS [new: CrosfieldTape.FileSpecList] = {
FOR l: CrosfieldTape.FileSpecList ← list, l.rest UNTIL l=NIL DO
new ← CONS[l.first, new];
ENDLOOP;
};
DoFileList[files: files, fileAction: buildFileSpecs, reduceResolution: 1, format: crosfield];
fileSpecList ← reverseList[fileSpecList]; --aesthetics
};
WriteCrosfieldTape: PROC [files: LIST OF IPSpec, tapeSpec: TapeSpec] = {
tape: UnixTapeOps.TapeHandle ← UnixTapeOps.OpenDrive[tapeSpec.serverName, tapeSpec.drive, tapeSpec.density];
fileSpecList: CrosfieldTape.FileSpecList ← CrosfieldFromIPSpec[files];
handleList, currentFile: TapesCommon.FileHandleList;
size: REAL ← 0;
rasterize: FileAction ~ {
crosfield: TapesCommon.FileHandle ← currentFile.first;
rasterProc: ConvertToRasters.RasterProc = { --Callback from ConvertToRasters
[] ← crosfield.newLine[crosfield, raster]; --send one raster to tape
};
currentFile ← currentFile.rest;
convert[rasterProc]; --images the InterpressMaster
};
Check the size and write the master directory.
size ← CrosfieldTape.SizeOfList[fileSpecList, tapeSpec.density];
IF size > tapeSpec.tapeSize THEN SIGNAL WillNotFit;
handleList ← CrosfieldTape.WriteTape[tapeHandle: tape, files: fileSpecList, tapeNumber: tapeSpec.tapeNumber, name: tapeSpec.note];
currentFile ← handleList; --will be enumerated by rasterize
DoFileList[files: files, fileAction: rasterize, reduceResolution: 1, format: crosfield];
UnixTapeOps.WriteEOT[tape];
UnixTapeOps.CloseDrive[tape];
};
WriteOptronicsTape: PROC [files: LIST OF IPSpec, tapeSpec: TapeSpec] = {
tape: UnixTapeOps.TapeHandle ← UnixTapeOps.OpenDrive[tapeSpec.serverName, tapeSpec.drive, tapeSpec.density];
fileSpecList: OptronicsTape.FileSpecList;
handleList, currentFile: TapesCommon.FileHandleList;
size: REAL ← 0;
buildFileSpecs: FileAction ~ {
fileSpec: OptronicsTape.FileSpec ← NEW[OptronicsTape.FileSpecRep ← [
pixelsPerInch: OptronicsValToPPI[pixelsPerInch], nLines: nLines, nPixels: nPixels,
rgbInterleaved: NOT achromatic, maxD: DefaultMaxD[achromatic]
]];
fileSpecList ← CONS[fileSpec, fileSpecList];
};
reverseList: PROC[list: OptronicsTape.FileSpecList] RETURNS [new: OptronicsTape.FileSpecList] = {
FOR l: OptronicsTape.FileSpecList ← list, l.rest UNTIL l=NIL DO
new ← CONS[l.first, new];
ENDLOOP;
};
rasterize: FileAction ~ {
fileHandle: TapesCommon.FileHandle ← currentFile.first;
rasterProc: ConvertToRasters.RasterProc = { --Callback from ConvertToRasters
[] ← fileHandle.newLine[fileHandle, raster]; --send one raster to tape
};
currentFile ← currentFile.rest;
convert[rasterProc]; --images the InterpressMaster
};
Build the fileSpecs
DoFileList[files: files, fileAction: buildFileSpecs, reduceResolution: 1, format: optronics];
fileSpecList ← reverseList[fileSpecList]; --aesthetics
Check the size.
size ← OptronicsTape.SizeOfList[fileSpecList, tapeSpec.density];
IF size > tapeSpec.tapeSize THEN SIGNAL WillNotFit;
handleList ← OptronicsTape.WriteTape[tapeHandle: tape, header: tapeSpec.note, files: fileSpecList];
currentFile ← handleList; --will be enumerated by rasterize
DoFileList[files: files, fileAction: rasterize, reduceResolution: 1, format: optronics];
UnixTapeOps.WriteEOT[tape];
UnixTapeOps.CloseDrive[tape];
};
AISInfo: TYPE ~ RECORD [fileName: ROPE, raster: AIS.Raster, fRef: AIS.FRef, wRef: AIS.WRef];
WriteRGBAIS: PROC [files: LIST OF IPSpec, wd: ROPENIL, reduceResolution: REAL ← 0.25, preview: BOOLEAN] ~ {
Color: TYPE ~ MACHINE DEPENDENT {red(0), green(1), blue(2), gray(3)};
colorSuffix: ARRAY Color OF ROPE = ["red", "grn", "blu", NIL];
fileAction: FileAction ~ {
currentLine: NAT ← 0;
ais: ARRAY Color OF AISInfo;
rgbToDensity: OptronicsTape.TRCTable ← NIL;
SetUp: PROC [c: Color] = {
ais[c].raster ← NEW[AIS.RasterPart ← [scanCount: nLines,
scanLength: nPixels, scanMode: rd, bitsPerPixel: 8, linesPerBlock: -1, paddingPerBlock: 0]];
IF colorSuffix[c] = NIL THEN ais[c].fileName ← IO.PutFR["%g%g.ais",
[rope[wd]], [rope[name]]]
ELSE ais[c].fileName ← IO.PutFR["%g%g-%g.ais",
[rope[wd]], [rope[name]], [rope[colorSuffix[c]]]];
ais[c].fRef ← AIS.CreateFile[name: ais[c].fileName, raster: ais[c].raster];
ais[c].wRef ← AIS.OpenWindow[f: ais[c].fRef];
};
Done: PROC [c: Color] = {
AIS.CloseWindow[w: ais[c].wRef];
AIS.CloseFile[f: ais[c].fRef];
};
rasterProc: ConvertToRasters.RasterProc = { -- Callback from convert
length: NAT ~ raster.length;
writeLine: PROC[c: Color] = {
wRef: AIS.WRef ~ ais[c].wRef;
buf: ImagerSample.SampleBuffer ~ raster[ORD[c]]; -- the order better be right!
*** for more speed, consider using AIS.UnsafeWriteLine here ***
FOR pixel: NAT IN [0..length) DO
AIS.WriteSample[w: wRef, value: buf[pixel], line: currentLine, pixel: pixel];
ENDLOOP;
};
IF NOT preview THEN OptronicsTape.RGBToDensity[raster, rgbToDensity];
IF achromatic THEN writeLine[gray]
ELSE FOR c: Color IN [red..blue] DO writeLine[c]; ENDLOOP;
currentLine ← currentLine+1;
};
IF achromatic THEN SetUp[gray] ELSE {SetUp[red]; SetUp[green]; SetUp[blue]};
IF NOT preview THEN rgbToDensity ← OptronicsTape.MakeDensityToRGB[255,255, DefaultMaxD[achromatic]];
convert[rasterProc]; -- call to image the Interpress Master
IF achromatic THEN Done[gray] ELSE {Done[red]; Done[green]; Done[blue]};
};
DoFileList[files: files, fileAction: fileAction, reduceResolution: reduceResolution, format: optronics]
};
WriteCrosfieldAIS: PROC [files: LIST OF IPSpec, wd: ROPENIL, reduceResolution: REAL ← 0.25] ~ {
Color: TYPE ~ MACHINE DEPENDENT {cyan(0), magenta(1), yellow(2), black(3)};
colorSuffix: ARRAY Color OF ROPE = ["cyan", "magenta", "yellow", "black"];
fileAction: FileAction ~ {
currentLine: NAT ← 0;
ais: ARRAY Color OF AISInfo;
SetUp: PROC [c: Color] = {
ais[c].raster ← NEW[AIS.RasterPart ← [scanCount: nLines,
scanLength: nPixels, scanMode: rd, bitsPerPixel: 8, linesPerBlock: -1, paddingPerBlock: 0]];
ais[c].fileName ← IO.PutFR["%g%g-%g.ais",
[rope[wd]], [rope[name]], [rope[colorSuffix[c]]]];
ais[c].fRef ← AIS.CreateFile[name: ais[c].fileName, raster: ais[c].raster];
ais[c].wRef ← AIS.OpenWindow[f: ais[c].fRef];
};
Done: PROC [c: Color] = {
AIS.CloseWindow[w: ais[c].wRef];
AIS.CloseFile[f: ais[c].fRef];
};
rasterProc: ConvertToRasters.RasterProc = { -- Callback from convert
length: NAT ~ raster.length;
FOR c: Color IN Color DO
wRef: AIS.WRef ~ ais[c].wRef;
buf: ImagerSample.SampleBuffer ~ raster[ORD[c]]; -- the order better be right!
*** for more speed, consider using AIS.UnsafeWriteLine here ***
FOR pixel: NAT IN [0..length) DO
AIS.WriteSample[w: wRef, value: buf[pixel], line: currentLine, pixel: pixel];
ENDLOOP;
ENDLOOP;
currentLine ← currentLine+1;
};
SetUp[cyan]; SetUp[magenta]; SetUp[yellow]; SetUp[black];
convert[rasterProc]; -- call to image the Interpress Master
Done[cyan]; Done[magenta]; Done[yellow]; Done[black];
};
DoFileList[files: files, fileAction: fileAction, reduceResolution: reduceResolution, format: crosfield];
};
FileAction: TYPE ~ PROC [
name: ROPE,
nLines, nPixels: NAT,
pixelsPerInch: NAT,
convert: PROC [ConvertToRasters.RasterProc],
achromatic: BOOLEAN
] RETURNS [stop: BOOLFALSE];
metersPerInch: REAL = 0.0254;
defaultCC: ColorCorrection ← PrintColorTransformations.SWOPWithGCLinearLStar[];
DoFileList: PROC [files: LIST OF IPSpec, fileAction: FileAction, reduceResolution: REAL ← 1, format: Format] ~ {
log: Interpress.LogProc ~ {};
FOR list: LIST OF IPSpec ← files, list.rest UNTIL list=NIL DO
ip: IPSpec ← list.first;
IF ip=NIL THEN EXIT ELSE {
name: ROPE ~ FileNameBase[ip.name];
comp: REAL ← Compensate[ip.pixelsPerInch, format]; --crosfield pixels aren't square
nLines: NAT ~ Real.Round[0.5+ip.sDim*ip.pixelsPerInch*reduceResolution*comp];
nPixels: NAT ~ Real.Round[0.5+ip.fDim*ip.pixelsPerInch*reduceResolution];
rasterType: PROC RETURNS[ConvertToRasters.RasterType] = {
IF format=crosfield THEN RETURN[cmyk]
ELSE IF ip.achromatic THEN RETURN[gray]
ELSE RETURN[rgb];
};
cc: PROC RETURNS[ColorCorrection] = {
IF format=optronics THEN RETURN[NIL];
IF ip.colorCorrection=NIL THEN RETURN[defaultCC]
ELSE RETURN[ip.colorCorrection];
};
rasterSpec: ConvertToRasters.RasterSpec ← [
type: rasterType[], fSize: nPixels, sSize: nLines,
surfaceUnitsPerPixel: ip.surfaceUnitsPerPixel,
colorCorrection: cc[]
];
convert: PROC [rasterProc: ConvertToRasters.RasterProc] ~ {
call to image the Interpress Master
[] ← ConvertToRasters.FromIP[ip: ip.name, ppiF: ip.pixelsPerInch*reduceResolution, ppiS: ip.pixelsPerInch*reduceResolution*comp, page: ip.page, rasterSpec: rasterSpec, proc: rasterProc, x: ip.fOrg, y: ip.sOrg, w: ip.fDim, h: ip.sDim];
};
MessageWindow.Append[IO.PutFR["Starting on %g, %g lines by %g pixels at %g", IO.rope[name], IO.int[nLines], IO.int[nPixels],IO.time[]], TRUE];
IF fileAction[name: name, nLines: nLines, nPixels: nPixels, pixelsPerInch: ip.pixelsPerInch, convert: convert, achromatic: ip.achromatic].stop THEN EXIT;
}
ENDLOOP;
};
FileNameBase: PROC [name: ROPE] RETURNS [ROPE] ~ {
fullFName: ROPE; cp: FS.ComponentPositions;
[fullFName: fullFName, cp: cp] ← FS.ExpandName[name];
RETURN[Rope.Substr[fullFName, cp.base.start, cp.base.length]];
};
Compensate: PROC [ppi: REAL, format: Format] RETURNS [yScale: REAL] ~ {
Compensates for the non-square Crosfield pixels
IF format=crosfield THEN yScale ← SELECT ppi FROM
300 => 304.8/300, 450 => 406.4/450, 600 => 609.6/600, ENDCASE => ERROR
ELSE yScale ← 1;
};
OptronicsValToPPI: PROC [val: NAT] RETURNS [ppi: OptronicsTape.PixelsPerInch] ~ {
ppi ← SELECT val FROM
63 => ppi63, 127 => ppi127, 254 => ppi254, 508 => ppi508, 1015 => ppi1015, 2030 => ppi2030, ENDCASE => ERROR;
};
CrosfieldValToPPI: PROC [val: NAT] RETURNS [ppi: CrosfieldTape.PixelsPerInch] ~ {
ppi ← SELECT val FROM
300 => ppi300, 450 => ppi450, 600 => ppi600, ENDCASE => ERROR;
};
DefaultMaxD: PROC [achromatic: BOOLEAN] RETURNS [REAL] ~ {
RETURN[IF achromatic THEN 2.0 ELSE 1.4];
};
Debugging Procs
MakeIPSpec: PROC [name: ROPE, page: NAT, pixelsPerInch: NAT, fDim: REAL, sDim: REAL, surfaceUnitsPerPixel: NAT ← 5, colorCorrection: ColorCorrection ← NIL, gray: BOOLEANFALSE] RETURNS [IPSpec] ~ {
RETURN[NEW[InterpressToTape.IPSpecRep ← [name: name, page: page, pixelsPerInch: pixelsPerInch, fDim: fDim, sDim: sDim, surfaceUnitsPerPixel: surfaceUnitsPerPixel, colorCorrection: colorCorrection, gray: gray]]];
};
Sizes: TYPE = RECORD[name: ROPE, size: REAL];
ToTape: PROC [name: ROPE, page: NAT, pixelsPerInch: NAT, fDim, sDim: REAL, surfaceUnitsPerPixel: NAT ← 1, drive: NAT ← 1, nBands: NAT ← 1] ~ {
ipSpec: IPSpec ←NEW[InterpressToTape.IPSpecRep ← [name: Rope.Cat[name, ".ip"], page: page, pixelsPerInch: pixelsPerInch, fDim: fDim, sDim: sDim, surfaceUnitsPerPixel: surfaceUnitsPerPixel, nBands: nBands]];
InterpressToCrosfield[LIST[ipSpec], drive, name, 102];
};
ToAIS: PROC [name: ROPE, page: NAT, pixelsPerInch: NAT, fDim, sDim: REAL, surfaceUnitsPerPixel: NAT ← 1, nBands: NAT ← 1, pixelProc: CrosfieldTape.PixelProc, suffix: ROPENIL] ~ {
ipSpec: IPSpec ←NEW[InterpressToTape.IPSpecRep ← [name: Rope.Cat[name, ".ip"], page: page, pixelsPerInch: pixelsPerInch, fDim: fDim, sDim: sDim, surfaceUnitsPerPixel: surfaceUnitsPerPixel, nBands: nBands]];
InterpressToAIS[LIST[ipSpec], suffix, pixelProc];
};
END.