<> <> <> DIRECTORY Basics, Checksum, ColorRegistry, ColorRegistryProcs, --FontTune,-- FunctionCache, Imager, ImagerColor, ImagerColorFns, ImagerColorPrivate, ImagerDevice, ImagerPD, ImagerPDPrivate, ImagerPixelArray, ImagerSample, ImagerPixel, ImagerTransformation, ImagerPDPublic, PDFileFormat, PDFileWriter, PrincOps, PrincOpsUtils, Real, Rope, Vector2, SF, ImagerPrivate; ImagerPDColorImpl: CEDAR PROGRAM IMPORTS Checksum, ColorRegistryProcs, --FontTune,-- FunctionCache, Imager, ImagerColorPrivate, ImagerPixelArray, ImagerSample, ImagerPixel, ImagerTransformation, PDFileWriter, Real, SF EXPORTS ImagerPDPrivate, ImagerPD ~ BEGIN ROPE: TYPE ~ Rope.ROPE; Context: TYPE ~ Imager.Context; Toner: TYPE ~ ImagerPDPublic.Toner; Toners: TYPE ~ LIST OF ImagerPDPublic.Toner; PrinterType: TYPE ~ ImagerPD.PrinterType; Color: TYPE ~ ImagerColor.Color; Transformation: TYPE ~ ImagerTransformation.Transformation; ConstantColor: TYPE ~ ImagerColor.ConstantColor; SampledColor: TYPE ~ ImagerColor.SampledColor; SampledBlack: TYPE ~ ImagerColor.SampledBlack; ColorOperator: TYPE ~ ImagerColor.ColorOperator; SampleMap: TYPE ~ ImagerSample.SampleMap; PixelArray: TYPE ~ ImagerPixelArray.PixelArray; Tile: TYPE ~ ImagerPD.Tile; TileEntry: TYPE ~ ImagerPDPrivate.TileEntry; ColorKind: TYPE ~ ImagerPDPrivate.ColorKind; PD: TYPE ~ REF PDRep; PDRep: PUBLIC TYPE ~ ImagerPDPrivate.PDRep; DeviceData: TYPE ~ ImagerPDPrivate.DeviceData; DeviceDataRep: TYPE ~ ImagerPDPrivate.DeviceDataRep; CMYK: TYPE ~ ImagerColorFns.CMYK; RGB: TYPE ~ ImagerColor.RGB; ColorPDBrick: TYPE ~ ColorRegistry.ColorPDBrick; Sample: TYPE ~ ImagerSample.Sample; SampleBuffer: TYPE ~ ImagerSample.SampleBuffer; SetColor: PUBLIC PROC [context: Context, color: Color, viewToDevice: Transformation] RETURNS [ImagerDevice.AllowedMasks] ~ { deviceData: DeviceData ~ NARROW[context.data]; writer: PDFileWriter.PDState ~ deviceData.writer; setColorFromCMYK: PROC[val: CMYK] = { SELECT deviceData.toner FROM cyan => SetColorFromFraction[deviceData, val.C]; magenta => SetColorFromFraction[deviceData,val.M]; yellow => SetColorFromFraction[deviceData,val.Y]; black => SetColorFromFraction[deviceData,val.K]; ENDCASE => SetColorUniform[deviceData, noInk]; }; deviceData.colorKind _ nil; deviceData.sampledSource _ NIL; WITH color SELECT FROM color: ImagerColor.SpecialColor => { defaultColor: PROC ={ IF color.substitute # NIL THEN { [] _ SetColor[context, color.substitute, viewToDevice] } ELSE { ERROR Imager.Error[[code: $unknownSpecialColor, explanation: "Unknown special color has no substitute value"]] }; }; SELECT color.type FROM $Distinct => { -- the distinct named colors nameData: ColorRegistry.Data _ NARROW[color.data]; deviceIndex: NAT _ IndexFromAtom[deviceData.deviceType, $Distinct]; ColorRegistryProcs.InitPrinterColors[deviceType: deviceData.deviceType, colorType: $Distinct]; --won't do any work if already initialized SELECT deviceData.deviceType FROM $color400, $colorversatec => { --find CMYK value: CMYK _ NARROW[ nameData.deviceValues[deviceIndex], REF CMYK]^; setColorFromCMYK[value]; }; $bw400, $versatec, $raven384 => { --find a Tile value: ImagerPD.Tile _ NARROW[nameData.deviceValues[deviceIndex], REF ImagerPD.Tile]^; SetColorFromTile[deviceData, value, FALSE, TRUE] }; ENDCASE => defaultColor[]; }; $ChipNDale => { -- the ChipNDale named colors nameData: ColorRegistry.Data _ NARROW[color.data]; deviceIndex: NAT _ IndexFromAtom[deviceData.deviceType, $Distinct]; ColorRegistryProcs.InitPrinterColors[deviceType: deviceData.deviceType, colorType: $ChipNDale]; --won't do any work if already initialized SELECT deviceData.deviceType FROM $color400, $colorversatec => { --find LIST of ColorRegistry.ColorPDBrick tiles: LIST OF REF ColorPDBrick _ NARROW[ nameData.deviceValues[deviceIndex]]; SetColorUniform[deviceData, off]; --have to have some color set IF tiles#NIL THEN { FOR l: LIST OF REF ColorPDBrick _ tiles, l.rest UNTIL l=NIL DO IF l.first.toner=LOOPHOLE[deviceData.toner, ImagerPDPublic.Toner] THEN SetColorFromTile[deviceData, l.first.tile, TRUE, TRUE]; ENDLOOP; }; }; ENDCASE => defaultColor[]; }; ENDCASE => defaultColor[]; }; color: ImagerColor.OpConstantColor => { <> IF ImagerColorPrivate.SupportsOutput[color.colorOperator, $CMYK] THEN { <> cmyk: CMYK; pixelIn: ImagerColorPrivate.PixelProc ~ { RETURN[color.pixel[i]] }; tupleAction: PROC [tupleOut: ImagerColorPrivate.TupleProc] ~ { cmyk _ [C: tupleOut[0], M: tupleOut[1], Y: tupleOut[2], K: tupleOut[3]]; }; ImagerColorPrivate.TupleFromPixel[color.colorOperator, [type: $CMYK, samplesPerPixelOut: 4], pixelIn, tupleAction]; setColorFromCMYK[cmyk]; } ELSE { rgb: RGB ~ ImagerColorPrivate.RGBFromColor[color]; <> SetColorFromRGB[deviceData, rgb, ImagerColorPrivate.IntensityFromColor[color]]; }; }; color: ImagerColor.SampledColor => { pa: PixelArray ~ color.pa; um: Transformation ~ color.um; colorOperator: ColorOperator ~ color.colorOperator; ImagerTransformation.ApplyCat[deviceData.paToDevice, pa.m, color.um, viewToDevice]; deviceData.sampledSource _ GetCachedSource[deviceData, color]; IF deviceData.sampledSource = NIL THEN { SetColorUniform[deviceData, noInk] } ELSE { deviceData.colorKind _ sampled; PDFileWriter.NewPriorityLevel[pdState: deviceData.writer, toner: deviceData.toner]; }; }; color: ImagerColor.SampledBlack => { pa: PixelArray ~ color.pa; um: Transformation ~ color.um; SetColorFromSampledBlack[context, pa, color.clear]; PDFileWriter.NewPriorityLevel[pdState: deviceData.writer, toner: deviceData.toner]; }; ENDCASE => ERROR; SELECT deviceData.colorKind FROM constant => RETURN [[unorderedBoxes: TRUE, multipleCoverage: TRUE, regionFill: FALSE, bitmap: TRUE, rawBitmaps: FALSE, runGroupChar: TRUE, rasterChar: TRUE]]; ENDCASE => RETURN [[unorderedBoxes: FALSE, multipleCoverage: FALSE, regionFill: FALSE, bitmap: FALSE, rawBitmaps: FALSE, runGroupChar: FALSE, rasterChar: FALSE]]; }; <> <> <> nDevices: NAT = 5; deviceList: ARRAY [0..nDevices) OF DeviceRec _ [ --devices we have implemented names for [8,$color400], [9,$colorversatec], [10,$raven384], [11,$bw400], [12,$versatec]]; DeviceRec: TYPE = RECORD[cnIndex: NAT, type: ATOM]; lastPair: DeviceRec _ [0, NIL]; --cheap cache IndexFromAtom: PROC [deviceType: ATOM, colorType: ATOM] RETURNS [cnIndex: NAT] ~ { FOR i: NAT IN [0..nDevices) DO IF deviceList[i].type = deviceType THEN { cnIndex _ deviceList[i].cnIndex; lastPair _ deviceList[i]; RETURN[cnIndex]}; ENDLOOP; ERROR; }; SetHalftoneProperties: PUBLIC PROC [pd: PD, toner: ImagerPD.Toner, brick: Tile] ~ { IF brick.maxSample#BYTE.LAST THEN ERROR; -- restrict this for now. pd.deviceData.halftoneBrick[VAL[ORD[toner]]] _ NEW[Tile _ brick]; }; SetColorCorrection: PUBLIC PROC [pd: PD, correctionProc: ImagerPD.CorrectionProc, data: REF] = { <> pd.deviceData.correctionProc _ correctionProc; pd.deviceData.colorCorrectionData _ data; [] _ FunctionCache.Obtain[x: FunctionCache.GlobalCache[], compare: FunctionCache.Any, limit: INT.LAST, clientID: pd.deviceData]; -- Flush our stuff from the cache. }; <<>> TileShape: TYPE ~ RECORD [box: SF.Box, phase: INTEGER]; GetGoodShape: PROC [box: SF.Box, phase: INTEGER] RETURNS [TileShape] ~ { size: SF.Vec ~ SF.Size[box]; IF size.s <= 0 OR size.f <= 0 THEN ERROR; WHILE phase < 0 DO phase _ phase + size.f ENDLOOP; IF (16 MOD NAT[size.s] = 0) AND (16 MOD NAT[size.f] = 0) AND ((NAT[phase]*(16/NAT[size.s])) MOD 16 = 0) THEN { <> RETURN [[box: [max: [16, 16]], phase: 0]] }; RETURN [[box: [max: size], phase: phase]]; }; CreateTile: PUBLIC PROC [maxSample: CARDINAL, sampleMap: SampleMap, phase: INTEGER _ 0, copy: BOOL _ TRUE] RETURNS [Tile] ~ { boxIn: SF.Box ~ ImagerSample.GetBox[sampleMap]; shape: TileShape ~ GetGoodShape[boxIn, phase]; raster: ImagerSample.RasterSampleMap _ WITH sampleMap SELECT FROM r: ImagerSample.RasterSampleMap => r ENDCASE => NIL; IF copy OR raster=NIL OR shape#[boxIn, phase] THEN { raster _ ImagerSample.NewSampleMap[box: shape.box, bitsPerSample: ImagerSample.GetBitsPerSample[sampleMap]]; ImagerSample.Clear[raster]; ImagerSample.Tile[map: raster, tile: sampleMap, phase: phase]; }; RETURN [[maxSample: maxSample, sampleMap: raster, phase: shape.phase]] }; CopyTile: PROC [tile: Tile] RETURNS [Tile] ~ { RETURN [CreateTile[tile.maxSample, tile.sampleMap, tile.phase, TRUE]]; }; TileHash: PROC [tile: Tile] RETURNS [hash: CARDINAL] ~ TRUSTED { box: SF.Box ~ ImagerSample.GetBox[tile.sampleMap]; bps: NAT ~ ImagerSample.GetBitsPerSample[tile.sampleMap]; r: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[box: box, bitsPerSample: bps, bitsPerLine: SF.SizeF[box]*bps]; unsafeBlock: Basics.UnsafeBlock ~ ImagerSample.GetUnsafeBlock[r]; zero: [0..0] ~ unsafeBlock.startIndex; p: LONG POINTER _ unsafeBlock.base; count: CARD _ CARD[unsafeBlock.count+Basics.bytesPerWord]/Basics.bytesPerWord; ImagerSample.Clear[r]; ImagerSample.Transfer[dst: r, src: tile.sampleMap]; hash _ 0; UNTIL count = 0 DO nWords: CARDINAL ~ MIN[count, NAT.LAST]; hash _ Checksum.ComputeChecksum[cs: hash, nWords: nWords, p: p]; p _ p + nWords; count _ count - nWords; ENDLOOP; ImagerSample.ReleaseScratchMap[r] }; TileEqual: PROC [a, b: Tile] RETURNS [BOOL] ~ { IF a.maxSample # b.maxSample THEN RETURN [FALSE]; IF a.phase # b.phase THEN RETURN [FALSE]; RETURN [ImagerSample.Equal[a.sampleMap, b.sampleMap]]; }; PutTileIntoLoad: PROC [writer: PDFileWriter.PDState, tile: Tile] RETURNS [CARD] ~ TRUSTED { box: SF.Box ~ ImagerSample.GetBox[tile.sampleMap]; base: LONG POINTER ~ ImagerSample.GetBase[tile.sampleMap].word; bitsPerLine: NAT ~ ImagerSample.GetBitsPerLine[tile.sampleMap]; IF bitsPerLine MOD 16 # 0 OR (bitsPerLine-SF.SizeF[box]) NOT IN [0..15) THEN ERROR; RETURN [PDFileWriter.LoadContiguousColorTile[pdState: writer, phase: tile.phase, sMin: box.min.s, fMin: box.min.f, sSize: SF.SizeS[box], fSize: SF.SizeF[box], bitsPtr: base]]; }; TileLoadWords: PROC [tile: Tile] RETURNS [INT] ~ { RETURN [SIZE[PDFileFormat.Tile] + ImagerSample.WordsForMap[ImagerSample.GetSize[tile.sampleMap]]]; }; SetColorFromTile: PROC [deviceData: DeviceData, tile: Tile, clear: BOOL, copy: BOOL] ~ { hash: CARDINAL ~ TileHash[tile]; bucket: NAT ~ hash MOD deviceData.tileTable.mod; loadIndex: LONG CARDINAL _ 0; found: BOOL _ FALSE; FOR t: LIST OF TileEntry _ deviceData.tileTable[bucket], t.rest UNTIL (found OR t = NIL) DO IF t.first.hash = hash AND TileEqual[tile, t.first.tile] THEN { found _ TRUE; loadIndex _ t.first.loadIndex; }; ENDLOOP; IF NOT found AND TileLoadWords[tile] <= PDFileWriter.RemainingLoadSize[deviceData.writer] THEN { loadIndex _ PutTileIntoLoad[deviceData.writer, tile]; deviceData.tileTable[bucket] _ CONS[[loadIndex, hash, IF copy THEN CopyTile[tile] ELSE tile], deviceData.tileTable[bucket]]; found _ TRUE; }; IF found THEN { PDFileWriter.SetColorTile[pdState: deviceData.writer, toner: deviceData.toner, tileRef: loadIndex, tFlag: (IF clear THEN transparent ELSE opaque)]; deviceData.colorKind _ constant; } ELSE { deviceData.colorTile _ (IF copy THEN CopyTile[tile] ELSE tile); deviceData.colorClear _ clear; deviceData.colorKind _ tile; IF clear THEN PDFileWriter.SetColorInk[deviceData.writer, deviceData.toner] ELSE PDFileWriter.NewPriorityLevel[pdState: deviceData.writer, toner: deviceData.toner]; }; }; UniformColor: TYPE ~ {ink, noInk, off}; SetColorUniform: PROC [deviceData: DeviceData, u: UniformColor] ~ { SELECT u FROM ink => PDFileWriter.SetColorInk[deviceData.writer, deviceData.toner]; noInk => { IF deviceData.priorityImportant THEN PDFileWriter.SetColorClear[deviceData.writer, deviceData.toner] ELSE PDFileWriter.SetColorOff[deviceData.writer, deviceData.toner]; }; off => PDFileWriter.SetColorOff[deviceData.writer, deviceData.toner]; ENDCASE => NULL; deviceData.colorKind _ constant; }; SetColorFromFraction: PROC [deviceData: DeviceData, f: REAL] ~ { SELECT f FROM >= 0.9999 => { -- all toner SetColorUniform[deviceData, ink] }; <= 0.0001 => { -- no toner SetColorUniform[deviceData, noInk] }; ENDCASE => { brick: Tile ~ deviceData.halftoneBrick[deviceData.toner]^; shape: TileShape ~ GetGoodShape[ImagerSample.GetBox[brick.sampleMap], brick.phase]; bitmap: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[shape.box]; b: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[shape.box]]; sample: CARDINAL ~ Real.InlineRoundC[f*brick.maxSample]; ImagerSample.Clear[bitmap]; FOR s: INTEGER IN [shape.box.min.s..shape.box.max.s) DO ImagerSample.GetTileSamples[tile: brick.sampleMap, phase: brick.phase, initIndex: [s, shape.box.min.f], buffer: b]; FOR j: INTEGER IN [0..b.length) DO b[j] _ IF sample > b[j] THEN 1 ELSE 0; ENDLOOP; ImagerSample.PutSamples[map: bitmap, initIndex: [s, shape.box.min.f], buffer: b]; ENDLOOP; SetColorFromTile[deviceData: deviceData, tile: [maxSample: 1, sampleMap: bitmap, phase: shape.phase], clear: FALSE, copy: TRUE]; ImagerSample.ReleaseScratchSamples[b]; ImagerSample.ReleaseScratchMap[bitmap]; }; }; brickFraction: REAL _ 0.5; --for experimenting SetColorFromBrick: PROC [deviceData: DeviceData, brick: Tile] ~ { shape: TileShape ~ GetGoodShape[ImagerSample.GetBox[brick.sampleMap], brick.phase]; bitmap: ImagerSample.RasterSampleMap ~ ImagerSample.ObtainScratchMap[shape.box]; b: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[shape.box]]; sample: CARDINAL ~ Real.InlineRoundC[brickFraction*brick.maxSample]; ImagerSample.Clear[bitmap]; FOR s: INTEGER IN [shape.box.min.s..shape.box.max.s) DO ImagerSample.GetTileSamples[tile: brick.sampleMap, phase: brick.phase, initIndex: [s, shape.box.min.f], buffer: b]; FOR j: INTEGER IN [0..b.length) DO b[j] _ IF sample > b[j] THEN 1 ELSE 0; ENDLOOP; ImagerSample.PutSamples[map: bitmap, initIndex: [s, shape.box.min.f], buffer: b]; ENDLOOP; SetColorFromTile[deviceData: deviceData, tile: [maxSample: 1, sampleMap: bitmap, phase: shape.phase], clear: FALSE, copy: TRUE]; ImagerSample.ReleaseScratchSamples[b]; ImagerSample.ReleaseScratchMap[bitmap]; }; SetColorFromRGB: PROC [deviceData: DeviceData, rgb: RGB, Y: REAL] ~ { IF deviceData.tonerUniverseSet[cyan] AND deviceData.tonerUniverseSet[magenta] AND deviceData.tonerUniverseSet[yellow] THEN { IF deviceData.correctionProc # NIL THEN { deviceData.pixelsRGB[0][0] _ Real.InlineRoundC[rgb.R*255]; deviceData.pixelsRGB[1][0] _ Real.InlineRoundC[rgb.G*255]; deviceData.pixelsRGB[2][0] _ Real.InlineRoundC[rgb.B*255]; deviceData.correctionProc[ deviceData.pixelsRGB, deviceData.pixelsCMYK, deviceData.colorCorrectionData, LOOPHOLE[deviceData.toner] --toner is actually ignored. Will return all 4 separations, so we could be more efficient here if we wanted to implement a cache. ]; SELECT deviceData.toner FROM cyan => SetColorFromFraction[deviceData, deviceData.pixelsCMYK[0][0]/255.0]; magenta => SetColorFromFraction[deviceData,deviceData.pixelsCMYK[1][0]/255.0]; yellow => SetColorFromFraction[deviceData,deviceData.pixelsCMYK[2][0]/255.0]; black => SetColorFromFraction[deviceData,deviceData.pixelsCMYK[3][0]/255.0]; ENDCASE => SetColorUniform[deviceData, noInk]; } ELSE SELECT deviceData.toner FROM cyan => SetColorFromFraction[deviceData, 1.0 - rgb.R]; magenta => SetColorFromFraction[deviceData, 1.0 - rgb.G]; yellow => SetColorFromFraction[deviceData, 1.0 - rgb.B]; ENDCASE => SetColorUniform[deviceData, noInk]; } ELSE IF deviceData.tonerUniverseSet[black] THEN { SetColorFromFraction[deviceData, 1.0 - Y]; } ELSE ERROR; }; UnimplementedColor: PUBLIC SIGNAL [color: Color] ~ CODE; SetColorFromSampledBlack: PROC [context: Context, pa: PixelArray, clear: BOOL] ~ { deviceData: DeviceData ~ NARROW[context.data]; IF deviceData.tonerUniverseSet[black] AND deviceData.toner # black THEN { SetColorUniform[deviceData, IF clear THEN off ELSE noInk] } ELSE { cache: FunctionCache.Cache ~ FunctionCache.GlobalCache[]; compare: FunctionCache.CompareProc ~ {RETURN [argument=pa]}; scd: ImagerPixel.PixelMap _ NARROW[FunctionCache.Lookup[cache, compare, deviceData].value]; IF scd = NIL THEN TRUSTED { sampleMap: ImagerSample.RasterSampleMap ~ ImagerSample.NewSampleMap[box: [max: [pa.sSize, pa.fSize]]]; ImagerSample.Clear[sampleMap]; ImagerPixelArray.Transfer[pa: pa, dst: sampleMap]; scd _ ImagerPixel.MakePixelMap[sampleMap]; IF pa.immutable THEN FunctionCache.Insert[cache, pa, scd, PixelMapWords[scd], deviceData]; }; deviceData.sampledSource _ scd; IF deviceData.paToDevice.form = 3 AND deviceData.paToDevice.integerTrans THEN { SetColorFromTile[deviceData, CreateTile[1, ImagerSample.Shift[scd[0], [deviceData.paToDevice.tx, deviceData.paToDevice.ty]], 0], clear, FALSE]; } ELSE { deviceData.colorKind _ sampledBlack; deviceData.colorClear _ clear; PDFileWriter.SetColorInk[deviceData.writer, deviceData.toner]; }; }; }; SampledColorData: TYPE ~ REF SampledColorDataRep; SampledColorDataRep: TYPE ~ RECORD [ pa: PixelArray, colorOperator: ColorOperator, source: ImagerPixel.PixelMap ]; PixelMapWords: PROC [pixelMap: ImagerPixel.PixelMap] RETURNS [INT] ~ { words: INT _ 0; FOR i: NAT IN [0..pixelMap.samplesPerPixel) DO words _ words + ImagerSample.WordsForMap[size: ImagerSample.GetSize[pixelMap[i]], bitsPerSample: ImagerSample.GetBitsPerSample[pixelMap[i]]] ENDLOOP; RETURN [words]; }; GetCachedSource: PROC [deviceData: DeviceData, sampledColor: ImagerColor.SampledColor] RETURNS [ImagerPixel.PixelMap] ~ { cache: FunctionCache.Cache ~ FunctionCache.GlobalCache[]; pa: ImagerPixelArray.PixelArray ~ sampledColor.pa; colorOperator: ImagerColor.ColorOperator ~ sampledColor.colorOperator; Compare: FunctionCache.CompareProc ~ { WITH argument SELECT FROM scd: SampledColorData => RETURN [scd.pa = pa AND scd.colorOperator = colorOperator]; ENDCASE => RETURN [FALSE] }; scd: SampledColorData _ NARROW[FunctionCache.Lookup[cache, Compare, deviceData].value]; fullColor: BOOL ~ colorOperator.chromatic AND deviceData.tonerUniverseSet[cyan] AND deviceData.tonerUniverseSet[magenta] AND deviceData.tonerUniverseSet[yellow]; index: NAT ~ IF fullColor THEN (SELECT deviceData.toner FROM black => 3, cyan => 0, magenta => 1, yellow => 2, ENDCASE => 0) ELSE 0; IF scd = NIL THEN { pixelMap: ImagerPixel.PixelMap ~ ComputeSource[deviceData, sampledColor]; scd _ NEW [SampledColorDataRep _ [pa: pa, colorOperator: colorOperator, source: pixelMap]]; IF pa.immutable THEN FunctionCache.Insert[x: cache, argument: scd, value: scd, size: PixelMapWords[pixelMap], clientID: deviceData]; }; IF index >= scd.source.samplesPerPixel THEN RETURN [NIL] ELSE RETURN [ImagerPixel.MakePixelMap[scd.source[index]]]; }; ComputeSource: PROC [deviceData: DeviceData, sampledColor: ImagerColor.SampledColor] RETURNS [ImagerPixel.PixelMap] ~ { pa: ImagerPixelArray.PixelArray ~ sampledColor.pa; colorOperator: ImagerColor.ColorOperator ~ sampledColor.colorOperator; fullColor: BOOL ~ colorOperator.chromatic AND deviceData.tonerUniverseSet[cyan] AND deviceData.tonerUniverseSet[magenta] AND deviceData.tonerUniverseSet[yellow]; cmyk: BOOL ~ ImagerColorPrivate.SupportsOutput[colorOperator, $CMYK]; size: SF.Vec ~ [s: NAT[pa.sSize], f: NAT[pa.fSize]]; samplesPerPixelOut: NAT ~ SELECT TRUE FROM fullColor AND deviceData.tonerUniverseSet[black] AND (deviceData.correctionProc#NIL OR cmyk) => 4, -- CMYK fullColor => 3, -- CMY ENDCASE => 1; -- Intensity type: ATOM ~ IF samplesPerPixelOut = 4 AND cmyk THEN $CMYK ELSE IF samplesPerPixelOut = 1 THEN $Y ELSE IF ImagerColorPrivate.SupportsOutput[colorOperator, $CMY] THEN $CMY ELSE $RGB; maxIn: ImagerPixel.PixelProc ~ { RETURN[pa.MaxSampleValue[i]] }; maxOut: ImagerPixel.PixelProc ~ { RETURN[BYTE.LAST] }; pixelMap: ImagerPixel.PixelMap ~ ImagerPixel.NewPixelMap[samplesPerPixel: samplesPerPixelOut, box: [max: size], maxSample: maxOut]; translateAction: PROC [translate: ImagerColorPrivate.TranslateProc] ~ { pixelsIn: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[pa.samplesPerPixel, size.f]; pixelsRGB: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[3, size.f]; pixelsOut: ImagerPixel.PixelBuffer ~ ImagerPixel.ObtainScratchPixels[samplesPerPixelOut, size.f]; FOR i: NAT IN [0..samplesPerPixelOut) DO ImagerSample.ClearSamples[pixelsOut[i]] ENDLOOP; FOR s: NAT IN [0..size.s) DO pa.GetPixels[s: s, f: 0, pixels: pixelsIn]; SELECT type FROM $CMYK, $CMY => translate[pixelsIn: pixelsIn, pixelsOut: pixelsOut]; $RGB => { Correction: ImagerPD.CorrectionProc ~ deviceData.correctionProc; correctionData: REF ~ deviceData.colorCorrectionData; translate[pixelsIn: pixelsIn, pixelsOut: pixelsRGB]; IF Correction # NIL THEN <> Correction[pixelsRGB, pixelsOut, correctionData, black] --toner field is ignored ELSE TRUSTED { FOR i: NAT IN [0..3) DO a: LONG POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixelsRGB[i], start: 0, count: size.f]; b: LONG POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixelsOut[i], start: 0, count: size.f]; FOR j: NAT IN [0..size.f) DO b[j] _ BYTE.LAST-a[j] ENDLOOP; -- convert to fraction of ink ENDLOOP; }; }; $Y => { buf: LONG POINTER TO Basics.RawWords ~ ImagerSample.PointerToSamples[buffer: pixelsOut[0], start: 0, count: size.f]; translate[pixelsIn: pixelsIn, pixelsOut: pixelsOut]; TRUSTED { FOR i: NAT IN [0..size.f) DO buf[i] _ BYTE.LAST-buf[i] ENDLOOP; -- convert to fraction of ink }; }; ENDCASE => ERROR; pixelMap.PutPixels[initIndex: [s, 0], pixels: pixelsOut, count: size.f]; ENDLOOP; ImagerPixel.ReleaseScratchPixels[pixelsOut]; ImagerPixel.ReleaseScratchPixels[pixelsIn]; }; ImagerColorPrivate.TranslatePixels[colorOperator: colorOperator, output: [type: type, samplesPerPixelOut: samplesPerPixelOut], maxIn: maxIn, maxOut: maxOut, translateAction: translateAction]; RETURN [pixelMap]; }; END.