<<>> <> <> <> <> <> <<>> DIRECTORY FunctionCache USING [Cache, CompareProc, GlobalCache, Insert, Lookup], Imager USING [black, ClipRectangle, ConcatT, Context, DoSave, DoSaveAll, Error, MaskBitmap, MaskPixel, Object, SetColor, SetXY, TranslateT, white], ImagerBackdoor USING [GetBounds, GetColor, GetTransformation, SetT], ImagerBrick USING [Brick, HalftoneProperties], ImagerBitmapContext USING [classCode], ImagerBox USING [Box, BoxFromRectangle, IntersectBox, Rectangle], ImagerColor, ImagerColorPrivate, ImagerDeviceColor, ImagerDevice USING [Device, DeviceClass, DeviceClassRep, DeviceParm, MakeDeviceParm], ImagerMaskCache USING [GetNamedCache, SetNamedCacheParameters], ImagerMaskCapture USING [CaptureBitmap], ImagerPixel USING [MakePixelMap, PixelBuffer, PixelMap, PixelMapRep, Resample], ImagerPixelArray USING [maxVec, PixelArray, Transfer], ImagerPrivate USING [Class, DefaultDrawObject], ImagerRaster USING [Create, CreateClass, GetDevice, SetDeviceClipBox], ImagerSample USING [Clear, Clip, EdgeAction, FillBoxes, Function, GetBitsPerSample, GetBox, GetSize, GetTileSamples, Halftone, maxSample, maxVec, Move, NewSampleMap, ObtainScratchMap, ObtainScratchSamples, Put, PutSamples, RasterSampleMap, RawDescriptor, RawListTransfer, RegionFill, ReleaseDescriptor, ReleaseScratchMap, ReleaseScratchSamples, SampleBuffer, SampleMap, Shift, TileBoxes, TileFromStipple, Transfer, TransferBoxes, WordsForMap], ImagerTransformation USING [ApplyCat, CloseToTranslation, Copy, Destroy, Invert, Scale, ScanMode, SingularValues, Transformation, TransformRectangle, TransformVec], Real USING [Floor, Round], RefTab USING [Create, Delete, EachPairAction, Fetch, GetSize, Key, Pairs, Ref, Store, Val], SF USING [Box, BoxGenerator, Displace, maxVec, SizeF, Vec], Vector2 USING [Add, Sub, VEC]; ImagerBitmapContextImpl: CEDAR MONITOR IMPORTS RefTab, FunctionCache, ImagerDevice, Imager, ImagerBackdoor, ImagerBitmapContext, ImagerBox, ImagerColor, ImagerColorPrivate, ImagerDeviceColor, ImagerMaskCache, ImagerMaskCapture, ImagerPixel, ImagerPixelArray, ImagerPrivate, ImagerRaster, ImagerSample, ImagerTransformation, Real, SF, Vector2 EXPORTS ImagerBitmapContext ~ BEGIN <> Color: TYPE ~ ImagerColor.Color; ColorOperator: TYPE ~ ImagerColor.ColorOperator; Context: TYPE ~ Imager.Context; Device: TYPE ~ ImagerDevice.Device; Object: TYPE ~ Imager.Object; PixelMap: TYPE ~ ImagerPixel.PixelMap; PixelArray: TYPE ~ ImagerPixelArray.PixelArray; RasterSampleMap: TYPE ~ ImagerSample.RasterSampleMap; Rectangle: TYPE ~ ImagerBox.Rectangle; SampleMap: TYPE ~ ImagerSample.SampleMap; ScanMode: TYPE ~ ImagerTransformation.ScanMode; Transformation: TYPE ~ ImagerTransformation.Transformation; VEC: TYPE ~ Vector2.VEC; classCode: PUBLIC ATOM ¬ $Bitmap; fontCacheMaxSize: NAT ¬ 4000; Case: TYPE ~ {constant, tile, sampled, sampledBlack}; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD [ color: ImagerColor.Color ¬ NIL, -- used to re-validate color when the halftone changes. bitmap: SampleMap ¬ NIL, savedBuffer: SampleMap ¬ NIL, case: Case ¬ constant, constant: [0..1] ¬ 1, function: ImagerSample.Function ¬ [null, null], maskBitmapFunction: ImagerSample.Function ¬ [null, null], tile: ImagerBrick.Brick ¬ [0, NIL, 0], -- bitmap for case=tile separation: ATOM, brick: ImagerBrick.Brick ¬ [0, NIL, 0], -- thresholds control: ImagerDeviceColor.DeviceColorControl ¬ NIL, controlStamp: CARD ¬ 0, colorTransform: ImagerColorPrivate.ColorTransform ¬ NIL, paToDevice: Transformation, source: ImagerPixel.PixelMap ¬ NIL, scratchSampleMapThatIsInUse: SampleMap ¬ NIL, -- SampleMap that should go back to the scratch pool at the next SetColor scratchStippleMap: SampleMap ¬ NIL, -- scratch storage for stipple case scratchPixelMap: PixelMap ¬ NIL, -- scratch descriptor for AccessBuffer scratchBitmapContext: Imager.Context ¬ NIL -- for making fancy tiles ]; <> Create: PUBLIC PROC [deviceSpaceSize: SF.Vec, scanMode: ScanMode, surfaceUnitsPerInch: VEC, surfaceUnitsPerPixel: NAT ¬ 1, pixelUnits: BOOL, fontCacheName: ATOM] RETURNS [Context] ~ { data: Data ~ NEW[DataRep ¬ [paToDevice: ImagerTransformation.Scale[1]]]; deviceParm: ImagerDevice.DeviceParm ~ ImagerDevice.MakeDeviceParm[class: deviceClass, sSize: deviceSpaceSize.s, fSize: deviceSpaceSize.f, scanMode: scanMode, surfaceUnitsPerInch: surfaceUnitsPerInch, surfaceUnitsPerPixel: surfaceUnitsPerPixel, fontCache: IF fontCacheName = NIL THEN NIL ELSE ImagerMaskCache.GetNamedCache[fontCacheName]]; context: Context ~ ImagerRaster.Create[class: contextClass, deviceClass: deviceClass, deviceParm: deviceParm, data: data, pixelUnits: pixelUnits]; data.brick ¬ defaultBrick; data.control ¬ ImagerDeviceColor.GetDeviceColorControl[ImagerRaster.GetDevice[context]]; ImagerRaster.SetDeviceClipBox[context, [[0,0], [0,0]]]; RETURN [context]; }; SetBitmap: PUBLIC PROC [context: Context, bitmap: SampleMap] ~ { device: Device ~ ImagerRaster.GetDevice[context]; data: Data ~ NARROW[device.data]; data.bitmap ¬ bitmap; ImagerRaster.SetDeviceClipBox[context, IF bitmap = NIL THEN [] ELSE ImagerSample.GetBox[bitmap]]; }; GetBitmap: PUBLIC PROC [context: Context] RETURNS [SampleMap] ~ { device: Device ~ ImagerRaster.GetDevice[context]; data: Data ~ NARROW[device.data]; RETURN [data.bitmap]; }; <> MakeSimpleBrick: PROC [t: ARRAY [0..4) OF PACKED ARRAY [0..4) OF [0..16)] RETURNS [ImagerBrick.Brick] ~ { b: SampleMap ~ ImagerSample.NewSampleMap[box: [max: [4, 4]], bitsPerSample: 8]; FOR s: NAT IN [0..4) DO FOR f: NAT IN [0..4) DO ImagerSample.Put[b, [s, f], t[s][f]]; ENDLOOP; ENDLOOP; RETURN [[maxSample: 15, sampleMap: b, phase: 0]] }; coarseBrick: ImagerBrick.Brick ¬ MakeSimpleBrick[[ [00, 01, 13, 14], [08, 02, 03, 15], [09, 10, 04, 05], [07, 11, 12, 06] ]]; fineBrick: ImagerBrick.Brick ¬ MakeSimpleBrick[[ [00, 08, 02, 10], [14, 04, 12, 06], [03, 11, 01, 09], [13, 07, 15, 05] ]]; defaultBrick: ImagerBrick.Brick ¬ fineBrick; SetBrick: PUBLIC PROC [context: Context, brick: ImagerBrick.Brick] ~ { device: Device ~ ImagerRaster.GetDevice[context]; WITH device.data SELECT FROM data: Data => { IF data.brick.maxSample # brick.maxSample THEN { data.colorTransform ¬ NIL; }; data.brick ¬ brick; IF data.color # NIL THEN BitmapSetColor[device, data.color, NIL]; }; ENDCASE => NULL; }; BitmapSetHalftoneProperties: PROC [device: Device, halftoneProperties: ImagerBrick.HalftoneProperties] ~ { black: ImagerBrick.HalftoneProperties ¬ halftoneProperties; UNTIL black = NIL OR black.first.toner=$black DO black ¬ black.rest ENDLOOP; IF black # NIL THEN WITH device.data SELECT FROM data: Data => { brick: ImagerBrick.Brick ~ black.first.brick; IF data.brick.maxSample # brick.maxSample THEN { data.colorTransform ¬ NIL; }; data.brick ¬ brick; IF data.color # NIL THEN BitmapSetColor[device, data.color, NIL]; }; ENDCASE => NULL; }; GetBrick: PUBLIC PROC [context: Context] RETURNS [ImagerBrick.Brick] ~ { device: Device ~ ImagerRaster.GetDevice[context]; WITH device.data SELECT FROM data: Data => { RETURN [data.brick] }; ENDCASE => NULL; RETURN [[0, NIL, 0]] }; SetSeparation: PUBLIC PROC [context: Context, separation: ATOM] ~ { device: Device ~ ImagerRaster.GetDevice[context]; WITH device.data SELECT FROM data: Data => { data.separation ¬ separation; Imager.SetColor[context, ImagerBackdoor.GetColor[context]]; -- to re-validate the color }; ENDCASE => NULL; }; GetSeparation: PUBLIC PROC [context: Context] RETURNS [ATOM] ~ { device: Device ~ ImagerRaster.GetDevice[context]; WITH device.data SELECT FROM data: Data => { RETURN [data.separation] }; ENDCASE => NULL; RETURN [NIL] }; GetExtendedBox: PROC [brick: ImagerBrick.Brick] RETURNS [SF.Box] ~ { <> bpw: NAT = BITS[WORD]; box: SF.Box ¬ ImagerSample.GetBox[brick.sampleMap]; size: SF.Vec ~ ImagerSample.GetSize[brick.sampleMap]; fSizeBox: NAT ~ size.f; IF brick.phase = 0 AND size.f IN [1..bpw) AND size.s IN [1..16) AND bpw MOD NAT[size.f] = 0 THEN { box.max.f ¬ box.min.f+bpw; }; RETURN [box] }; allowedColorSpaces: LIST OF ImagerColorPrivate.ColorSpace ~ LIST[$Y]; GetColorTransform: PROC [data: Data, colorOperator: ColorOperator] RETURNS [ImagerColorPrivate.ColorTransform] ~ { colorSpace: ImagerColorPrivate.ColorSpace ~ ImagerColorPrivate.ChooseColorSpace[colorOperator, allowedColorSpaces]; IF data.colorTransform = NIL OR colorSpace # data.colorTransform.domain OR data.controlStamp # data.control.stamp THEN { rangeMax: ImagerColorPrivate.ColorPoint ~ ImagerColorPrivate.MakeColorPoint[1, data.brick.maxSample+1]; ct: ImagerColorPrivate.ColorTransform ~ NEW[ImagerColorPrivate.ColorTransformRep ¬ [domain: $Y, rangeMax: rangeMax, proc: Transform, data: data.control]]; data.colorTransform ¬ ct; data.controlStamp ¬ data.control.stamp; }; RETURN [data.colorTransform] }; Transform: PROC [self: ImagerColorPrivate.ColorTransform, in: ImagerColorPrivate.ColorPoint, out: ImagerColorPrivate.ColorPoint] ~ { control: ImagerDeviceColor.DeviceColorControl ~ NARROW[self.data]; ImagerDeviceColor.YFromY[control, in[0], self.rangeMax[0], out]; }; MakeTile: PROC [sample: CARDINAL, brick: ImagerBrick.Brick] RETURNS [ImagerBrick.Brick] ~ { box: SF.Box ~ GetExtendedBox[brick]; b: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[box]]; sampleMap: SampleMap ~ ImagerSample.ObtainScratchMap[box]; FOR s: INTEGER IN [box.min.s..box.max.s) DO ImagerSample.GetTileSamples[tile: brick.sampleMap, phase: brick.phase, initIndex: [s, 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: sampleMap, initIndex: [s, box.min.f], buffer: b]; ENDLOOP; ImagerSample.ReleaseScratchSamples[b]; RETURN [[maxSample: 1, sampleMap: sampleMap, phase: brick.phase]]; }; SampledColorData: TYPE ~ REF SampledColorDataRep; SampledColorDataRep: TYPE ~ RECORD [ pa: PixelArray, colorOperator: ColorOperator, separation: ATOM, maxSample: CARDINAL, filterDiameter: NAT, source: ImagerPixel.PixelMap ]; me: REF TEXT ~ "Bitmap"; -- a globally unique REF for use as a clientID in the global cache. BitmapSetColor: PROC [device: Device, color: Color, viewToDevice: Transformation] ~ { data: Data ~ NARROW[device.data]; realRaster: BOOL ~ WITH data.bitmap SELECT FROM r: RasterSampleMap => TRUE ENDCASE => FALSE; allowBitmaps, hardSampledCase: BOOL; { <> data.color ¬ NIL; data.source ¬ NIL; data.tile.sampleMap ¬ NIL; IF data.scratchSampleMapThatIsInUse # NIL THEN { ImagerSample.ReleaseScratchMap[data.scratchSampleMapThatIsInUse]; data.scratchSampleMapThatIsInUse ¬ NIL; }; }; WITH color SELECT FROM c: ImagerColor.OpConstantColor => { transform: ImagerColorPrivate.ColorTransform ~ GetColorTransform[data, c.colorOperator]; point: ImagerColorPrivate.ColorPoint ¬ ImagerColorPrivate.ColorPointFromColor[c, transform]; max: REAL = transform.rangeMax[0]; f: REAL ¬ point[0]; point ¬ ImagerColorPrivate.DestroyColorPoint[point]; data.case ¬ constant; data.function ¬ [null, null]; data.color ¬ c; -- Need to revalidate this case if the halftone screen changes. SELECT f FROM < 0.5 => { data.case ¬ constant; data.constant ¬ 1 }; > max-0.5 => { data.case ¬ constant; data.constant ¬ 0 }; ENDCASE => { data.tile ¬ MakeTile[Real.Round[max-f], data.brick]; data.scratchSampleMapThatIsInUse ¬ data.tile.sampleMap; data.case ¬ tile; }; }; sampledColor: ImagerColor.SampledColor => { data.color ¬ sampledColor; -- Need to revalidate this case if the halftone screen changes. BitmapSetSampledColor[data: data, sampledColor: sampledColor, viewToDevice: viewToDevice]; }; s: ImagerColor.SampledBlack => { pa: PixelArray ~ s.pa; ImagerTransformation.ApplyCat[data.paToDevice, pa.m, s.um, viewToDevice]; data.function ¬ IF s.clear THEN [or, null] ELSE [null, null]; SELECT TRUE FROM data.paToDevice.form = 3 AND data.paToDevice.integerTrans => { <> min: SF.Vec ~ [s: data.paToDevice.tx, f: data.paToDevice.ty]; max: SF.Vec ~ [s: min.s + pa.sSize, f: min.f + pa.fSize]; sampleMap: SampleMap ~ ImagerSample.ObtainScratchMap[box: [min: min, max: max]]; ImagerPixelArray.Transfer[pa: pa, dst: sampleMap, dstMin: min]; data.case ¬ tile; data.tile.maxSample ¬ 1; data.tile.sampleMap ¬ sampleMap; data.scratchSampleMapThatIsInUse ¬ sampleMap; data.tile.phase ¬ 0; }; newStuff AND TryTransformingBitmapIntoTile[data, pa] => { <> }; ENDCASE => { <> sampleMap: SampleMap ~ ImagerSample.ObtainScratchMap[box: [max: [pa.sSize, pa.fSize]]]; ImagerPixelArray.Transfer[pa: pa, dst: sampleMap]; data.case ¬ sampledBlack; data.source ¬ ImagerPixel.MakePixelMap[sampleMap]; data.scratchSampleMapThatIsInUse ¬ sampleMap; }; }; s: ImagerColor.SpecialColor => { SELECT s.type FROM $Stipple => { stippleData: ImagerColorPrivate.StippleData ~ NARROW[s.data]; data.function ¬ stippleData.function; SELECT stippleData.word FROM 00000H => { data.case ¬ constant; data.constant ¬ 0 }; 0FFFFH => { data.case ¬ constant; data.constant ¬ 1 }; ENDCASE => { data.case ¬ tile; data.tile.sampleMap ¬ data.scratchStippleMap ¬ ImagerSample.TileFromStipple[stipple: stippleData.word, scratch: data.scratchStippleMap]; data.tile.phase ¬ 0; }; }; $BitmapTile => { <> list: LIST OF REF ~ NARROW[s.data]; brick: REF ImagerBrick.Brick ~ NARROW[list.first]; rf: REF ImagerSample.Function ~ NARROW[list.rest.first]; data.function ¬ rf­; data.case ¬ tile; data.tile.sampleMap ¬ brick.sampleMap; data.tile.phase ¬ brick.phase; }; ENDCASE => { IF s.substitute # NIL THEN { [] ¬ BitmapSetColor[device, s.substitute, viewToDevice] } ELSE { ERROR Imager.Error[[code: $unknownSpecialColor, explanation: "Unknown special color has no substitute value"]] }; }; }; ENDCASE => ERROR; data.maskBitmapFunction ¬ [null, null]; IF data.case = constant THEN SELECT data.function FROM [null, null] => { SELECT data.constant FROM 0 => data.maskBitmapFunction ¬ [and, complement]; 1 => data.maskBitmapFunction ¬ [or, null]; ENDCASE => NULL; }; [xor, null] => { IF data.constant = 1 THEN data.maskBitmapFunction ¬ [xor, null]; }; ENDCASE => NULL; allowBitmaps ¬ (data.maskBitmapFunction # [null, null]); hardSampledCase ¬ data.case = sampledBlack OR data.case = sampled; device.state.allow ¬ [ unorderedBoxes: NOT hardSampledCase, multipleCoverage: (data.function.dstFunc # xor AND NOT hardSampledCase), regionFill: data.case = constant AND realRaster, bitmap: allowBitmaps, rawBitmaps: allowBitmaps AND realRaster ]; }; newStuff: BOOL ¬ TRUE; -- for testing TryTransformingBitmapIntoTile: PROC [data: Data, pa: PixelArray] RETURNS [BOOL] ~ { m: Transformation ~ data.paToDevice; IF m.form#0 THEN { sRect: Rectangle ~ [0, 0, pa.sSize, pa.fSize]; dRect: Rectangle ~ ImagerTransformation.TransformRectangle[m, sRect]; IF MAX[ABS[dRect.x], ABS[dRect.y], ABS[dRect.w], ABS[dRect.h]] <= 16383 AND ABS[dRect.w*dRect.h] <= 1048576.0 THEN { sSize: NAT ~ Real.Round[dRect.w]; fSize: NAT ~ Real.Round[dRect.h]; IF dRect.w-sSize+4.0 = 4.0 AND dRect.h-fSize+4.0 = 4.0 THEN { <> action: PROC ~ { pamInverse: Transformation ~ ImagerTransformation.Invert[pa.m]; Imager.TranslateT[data.scratchBitmapContext, [-dRect.x, -dRect.y]]; Imager.ConcatT[data.scratchBitmapContext, m]; Imager.ConcatT[data.scratchBitmapContext, pamInverse]; Imager.MaskPixel[data.scratchBitmapContext, pa]; ImagerTransformation.Destroy[pamInverse]; }; sampleMap: SampleMap ¬ ImagerSample.ObtainScratchMap[box: [max: [sSize, fSize]]]; tileMap: SampleMap ~ ImagerSample.Shift[sampleMap, [s: Real.Floor[dRect.x+0.5], f: Real.Floor[dRect.y+0.5]]]; ImagerSample.Clear[sampleMap]; IF data.scratchBitmapContext = NIL THEN data.scratchBitmapContext ¬ Create[deviceSpaceSize: SF.maxVec, scanMode: [slow: right, fast: up], surfaceUnitsPerInch: [1,1], pixelUnits: TRUE, fontCacheName: $Bitmap]; SetBitmap[data.scratchBitmapContext, sampleMap]; Imager.DoSave[data.scratchBitmapContext, action]; SetBitmap[data.scratchBitmapContext, NIL]; TRUSTED {ImagerSample.ReleaseDescriptor[sampleMap]; sampleMap ¬ NIL}; data.case ¬ tile; data.tile.maxSample ¬ 1; data.tile.sampleMap ¬ tileMap; data.scratchSampleMapThatIsInUse ¬ tileMap; data.tile.phase ¬ 0; RETURN [TRUE]; }; }; }; RETURN [FALSE]; }; ComputeSource: PROC [sampledColor: ImagerColor.SampledColor, filterDiameter: NAT, data: Data] RETURNS [ImagerPixel.PixelMap] ~ { colorOperator: ColorOperator ~ sampledColor.colorOperator; pixelMap: ImagerPixel.PixelMap ~ ImagerColorPrivate.Translate[colorOperator: sampledColor.colorOperator, transform: GetColorTransform[data, sampledColor.colorOperator], pa: sampledColor.pa]; <> RETURN [pixelMap] }; BitmapSetSampledColor: PROC [data: Data, sampledColor: ImagerColor.SampledColor, viewToDevice: Transformation] ~ { pa: PixelArray ~ sampledColor.pa; largerPaSize: NAT ~ MIN[pa.sSize, pa.fSize] + 1; cache: FunctionCache.Cache ~ FunctionCache.GlobalCache[]; filterDiameter: NAT ¬ 1; -- for low-pass filtering scd: SampledColorData ¬ NIL; compare: FunctionCache.CompareProc ~ { WITH argument SELECT FROM scd: SampledColorData => RETURN [scd.pa = pa AND scd.colorOperator = sampledColor.colorOperator AND scd.separation = data.separation AND scd.maxSample = data.brick.maxSample+1 AND scd.filterDiameter = filterDiameter]; ENDCASE => RETURN [FALSE] }; ImagerTransformation.ApplyCat[data.paToDevice, pa.m, sampledColor.um, viewToDevice]; IF interpolate THEN filterDiameter ¬ MIN[MAX[Real.Round[1.0/MAX[ImagerTransformation.SingularValues[data.paToDevice].y, 1.0/largerPaSize]], 1], 255]; data.case ¬ sampled; data.function ¬ [null, null]; IF pa.immutable AND data.control.table[$grayTransfer]=NIL THEN { scd ¬ NARROW[FunctionCache.Lookup[cache, compare, me].value]; IF scd = NIL THEN { pixelMap: ImagerPixel.PixelMap ~ ComputeSource[sampledColor, filterDiameter, data]; scd ¬ NEW [SampledColorDataRep ¬ [pa: pa, colorOperator: sampledColor.colorOperator, separation: data.separation, maxSample: data.brick.maxSample+1, filterDiameter: filterDiameter, source: pixelMap]]; FunctionCache.Insert[x: cache, argument: scd, value: scd, size: ImagerSample.WordsForMap[size: ImagerSample.GetSize[pixelMap[0]], bitsPerSample: ImagerSample.GetBitsPerSample[pixelMap[0]]], clientID: me]; }; data.source ¬ scd.source; } ELSE { data.source ¬ ComputeSource[sampledColor, filterDiameter, data]; <> }; }; <> interpolate: BOOL ¬ FALSE; BitmapMaskBoxes: PROC [device: Device, bounds: SF.Box, boxes: SF.BoxGenerator] ~ { data: Data ~ NARROW[device.data]; SELECT data.case FROM constant => { ImagerSample.FillBoxes[map: data.bitmap, boxes: boxes, value: data.constant, function: data.function]; }; tile => { ImagerSample.TileBoxes[map: data.bitmap, boxes: boxes, tile: data.tile.sampleMap, phase: data.tile.phase, function: data.function]; }; sampled => TRUSTED { buffer: ImagerSample.SampleBuffer ~ ImagerSample.ObtainScratchSamples[SF.SizeF[bounds]]; Action: SAFE PROC [pixels: ImagerPixel.PixelBuffer, min: SF.Vec] ~ TRUSTED { buffer.length ¬ pixels.length; ImagerSample.GetTileSamples[tile: data.brick.sampleMap, initIndex: min, buffer: buffer, phase: data.brick.phase]; ImagerSample.Halftone[map: data.bitmap, min: min, sampleBuffer: pixels[0], thresholdBuffer: buffer, function: data.function]; }; ImagerPixel.Resample[self: data.source, m: data.paToDevice, interpolate: interpolate, boxes: boxes, bounds: bounds, action: Action]; ImagerSample.ReleaseScratchSamples[buffer]; }; sampledBlack => { Action: PROC [pixels: ImagerPixel.PixelBuffer, min: SF.Vec] ~ { ImagerSample.PutSamples[map: data.bitmap, initIndex: min, buffer: pixels[0], start: 0, count: pixels.length, function: data.function]; }; ImagerPixel.Resample[self: data.source, m: data.paToDevice, interpolate: FALSE, boxes: boxes, bounds: bounds, action: Action]; }; ENDCASE => ERROR; }; BitmapMaskRegion: PROC [device: Device, bounds: SF.Box, edgeGenerator: PROC [ImagerSample.EdgeAction]] ~ { data: Data ~ NARROW[device.data]; IF data.case # constant THEN ERROR; ImagerSample.RegionFill[dst: NARROW[data.bitmap], edgeGenerator: edgeGenerator, value: data.constant, function: data.function]; }; BitmapMaskBitmap: PROC [device: Device, bitmap: SampleMap, delta: SF.Vec, bounds: SF.Box, boxes: SF.BoxGenerator] ~ { data: Data ~ NARROW[device.data]; ImagerSample.TransferBoxes[dst: data.bitmap, src: bitmap, delta: delta, boxes: boxes, function: data.maskBitmapFunction]; }; BitmapMaskRawBitmaps: PROC [device: Device, list: LIST OF ImagerSample.RawDescriptor] ~ { data: Data ~ NARROW[device.data]; ImagerSample.RawListTransfer[dst: NARROW[data.bitmap], src: list, function: data.maskBitmapFunction]; }; BitmapDrawBitmap: PROC [device: Device, bitmap: SampleMap, delta: SF.Vec, bounds: SF.Box, boxes: SF.BoxGenerator] ~ { data: Data ~ NARROW[device.data]; ImagerSample.TransferBoxes[dst: data.bitmap, src: bitmap, delta: delta, boxes: boxes]; }; BitmapMoveBox: PROC [device: Device, dstMin, srcMin, size: SF.Vec] ~ { data: Data ~ NARROW[device.data]; ImagerSample.Move[map: data.bitmap, dstMin: dstMin, srcMin: srcMin, size: size]; }; BitmapSwitchBuffer: PROC [device: Device, bounds: SF.Box, copy: BOOL, alt: BOOL] ~ { data: Data ~ NARROW[device.data]; oldBuffer: SampleMap ~ data.bitmap; newBuffer: SampleMap ~ IF alt THEN ImagerSample.ObtainScratchMap[box: bounds, bitsPerSample: 1] ELSE data.savedBuffer; IF copy THEN ImagerSample.Transfer[dst: newBuffer, src: oldBuffer]; data.bitmap ¬ newBuffer; IF alt THEN { data.savedBuffer ¬ oldBuffer } ELSE { data.savedBuffer ¬ NIL; ImagerSample.ReleaseScratchMap[oldBuffer] }; }; <> ObjectTable: TYPE = REF ObjectTableObj; ObjectTableObj: TYPE = RECORD [ table: RefTab.Ref, entries: NAT, totalWords: CARD32, garbageIndex: NAT ¬ 0 ]; objectTable: ObjectTable ¬ NEW[ObjectTableObj ¬ [ table: RefTab.Create[mod: 101], entries: 0, totalWords: 0 ]]; AllBits: TYPE = REF AllBitsObj; AllBitsObj: TYPE = RECORD [ list: LIST OF ObjectCacheData ]; <> ObjectCacheData: TYPE ~ REF ObjectCacheDataRep; ObjectCacheDataRep: TYPE ~ RECORD [ object: Object, clientToDevice: Transformation, size: CARD32 ¬ 10000, v: SELECT tag: * FROM notCached => [], bitmap => [mask: SampleMap, shadingBits: SampleMap], < [boxes: ManhattanPolygon],>> ENDCASE ]; maxBitsForCachedObject: REAL ~ 262144; -- reduced from 2000000 because allocations took too long, Bier maxObjects: NAT ¬ 500; maxWordsOfStorage: CARD32 ¬ 250000; FindBitsAtTransformation: ENTRY PROC [objectTable: ObjectTable, object: Object, clientToDevice: Transformation] RETURNS [objectCacheData: ObjectCacheData] = { OPEN ImagerTransformation; -- see CloseToTranslation ENABLE UNWIND => NULL; val: RefTab.Val; allBits: AllBits; found: BOOL ¬ FALSE; [found, val] ¬ RefTab.Fetch[objectTable.table, object]; IF NOT found THEN RETURN[NIL]; allBits ¬ NARROW[val]; FOR list: LIST OF ObjectCacheData ¬ allBits.list, list.rest UNTIL list = NIL DO IF CloseToTranslation[list.first.clientToDevice, clientToDevice] THEN RETURN[list.first]; ENDLOOP; objectCacheData ¬ NIL; }; InsertBitsAtTransformation: ENTRY PROC [objectTable: ObjectTable, object: Object, objectCacheData: ObjectCacheData] = { OPEN ImagerTransformation; -- see CloseToTranslation ENABLE UNWIND => NULL; val: RefTab.Val; allBits: AllBits; found: BOOL ¬ FALSE; [found, val] ¬ RefTab.Fetch[objectTable.table, object]; IF NOT found THEN { allBits ¬ NEW[AllBitsObj ¬ [list: LIST[objectCacheData]]]; } ELSE { allBits ¬ NARROW[val]; allBits.list ¬ CONS[objectCacheData, allBits.list]; }; [] ¬ RefTab.Store[objectTable.table, object, allBits]; objectTable.entries ¬ objectTable.entries + 1; objectTable.totalWords ¬ objectTable.totalWords + objectCacheData.size; IF objectTable.entries > maxObjects OR objectTable.totalWords > maxWordsOfStorage THEN EnforceLimits[objectTable]; }; EnforceLimits: INTERNAL PROC [objectTable: ObjectTable] = { TrimBitsAfterIndex: RefTab.EachPairAction = { <> allBits: AllBits ¬ NARROW[val]; limitsDone: BOOL ¬ FALSE; IF index < objectTable.garbageIndex THEN { index ¬ index + 1; RETURN[FALSE]; }; objectTable.garbageIndex ¬ objectTable.garbageIndex + 1; UNTIL allBits.list = NIL DO objectTable.entries ¬ objectTable.entries - 1; objectTable.totalWords ¬ objectTable.totalWords - allBits.list.first.size; allBits.list ¬ allBits.list.rest; limitsDone ¬ objectTable.entries <= maxObjects AND objectTable.totalWords <= maxWordsOfStorage; IF allBits.list = NIL THEN cleanupNILs ¬ CONS[key, cleanupNILs]; IF limitsDone THEN RETURN[TRUE]; ENDLOOP; <> }; TrimBits: RefTab.EachPairAction = { <> allBits: AllBits ¬ NARROW[val]; limitsDone: BOOL ¬ FALSE; UNTIL allBits.list = NIL DO objectTable.entries ¬ objectTable.entries - 1; objectTable.totalWords ¬ objectTable.totalWords - allBits.list.first.size; allBits.list ¬ allBits.list.rest; limitsDone ¬ objectTable.entries <= maxObjects AND objectTable.totalWords <= maxWordsOfStorage; IF allBits.list = NIL THEN cleanupNILs ¬ CONS[key, cleanupNILs]; IF limitsDone THEN RETURN[TRUE]; ENDLOOP; <> }; index: NAT ¬ 0; done: BOOL ¬ FALSE; cleanupNILs: LIST OF RefTab.Key ¬ NIL; done ¬ RefTab.Pairs[objectTable.table, TrimBitsAfterIndex]; IF NOT done THEN [] ¬ RefTab.Pairs[objectTable.table, TrimBits]; FOR list: LIST OF RefTab.Key ¬ cleanupNILs, list.rest UNTIL list = NIL DO deleted: BOOL ¬ RefTab.Delete[objectTable.table, list.first]; IF NOT deleted THEN ERROR; ENDLOOP; IF objectTable.garbageIndex > RefTab.GetSize[objectTable.table] THEN objectTable.garbageIndex ¬ 0; }; RemoveBitsAtTransformation: INTERNAL PROC [objectTable: RefTab.Ref, objectCacheData: ObjectCacheData] = { OPEN ImagerTransformation; -- see CloseToTranslation val: RefTab.Val; allBits: LIST OF ObjectCacheData; found: BOOL ¬ FALSE; [found, val] ¬ RefTab.Fetch[objectTable, objectCacheData.object]; IF found THEN { allBits ¬ NARROW[val]; allBits ¬ DeleteBitsFromList[objectCacheData, allBits]; [] ¬ RefTab.Store[objectTable, objectCacheData.object, allBits]; }; }; DeleteBitsFromList: INTERNAL PROC [entity: ObjectCacheData, entityList: LIST OF ObjectCacheData] RETURNS [smallerList: LIST OF ObjectCacheData] = { beforeEnt, ent, afterEnt: LIST OF ObjectCacheData; notFound: BOOL ¬ FALSE; [beforeEnt, ent, afterEnt, notFound] ¬ FindBitsAndNeighbors[entity, entityList]; IF notFound THEN RETURN[entityList]; IF beforeEnt = NIL THEN smallerList ¬ afterEnt ELSE { beforeEnt.rest ¬ afterEnt; smallerList ¬ entityList; }; }; -- end of DeleteBitsFromList FindBitsAndNeighbors: INTERNAL PROC [entity: ObjectCacheData, entityList: LIST OF ObjectCacheData] RETURNS [beforeEnt, ent, afterEnt: LIST OF ObjectCacheData, notFound: BOOL ¬ FALSE] = { lastE: LIST OF ObjectCacheData ¬ NIL; eList: LIST OF ObjectCacheData ¬ entityList; IF eList = NIL THEN RETURN[NIL, NIL, NIL, TRUE]; UNTIL eList = NIL DO IF eList.first = entity THEN { beforeEnt ¬ lastE; ent ¬ eList; afterEnt ¬ eList.rest; RETURN}; lastE ¬ eList; eList ¬ eList.rest; ENDLOOP; RETURN[NIL, NIL, NIL, TRUE]; }; <<>> BitmapDrawObjectMultiColor: PROC [context: Context, object: Object, position: VEC, interactive: BOOL] ~ { <> device: Device = ImagerRaster.GetDevice[context]; data: Data = NARROW[device.data]; contextBox, intersection: ImagerBox.Box; objectRect: Rectangle; contextBox ¬ ImagerBox.BoxFromRectangle[ImagerBackdoor.GetBounds[context]]; objectRect ¬ [x: position.x+object.clip.x, y: position.y+object.clip.y, w: object.clip.w, h: object.clip.h]; -- in client coordinates intersection ¬ ImagerBox.IntersectBox[contextBox, ImagerBox.BoxFromRectangle[objectRect]]; IF intersection.xmax=intersection.xmin THEN RETURN; -- not visible IF interactive AND data.control.table[$grayTransfer] = NIL THEN { <> <> <<(1) If the object is too large (too many bits would be needed to cache it), it is not present>> <<(2) If the Imager.Object is already in the cache, at approximately the current rotation and size, then we can use its cached bitmap without any further work.>> clientToDevice: Transformation ~ ImagerBackdoor.GetTransformation[context: context, from: client, to: device]; bounds: Rectangle ~ ImagerTransformation.TransformRectangle[clientToDevice, objectRect]; <> IF ABS[bounds.w*bounds.h] <= maxBitsForCachedObject THEN { objectCacheData: ObjectCacheData ¬ FindBitsAtTransformation[objectTable, object, clientToDevice]; IF objectCacheData = NIL THEN { -- no such Imager.Object in cache. Try to add one ClipAndDraw: PROC [c: Context] ~ { Imager.ClipRectangle[c, object.clip]; object.draw[object, c]; }; bitmap: SampleMap ¬ NIL; size: INT ¬ 0; bitmap ¬ ImagerMaskCapture.CaptureBitmap[ClipAndDraw, clientToDevice, FALSE]; -- build a mask BEGIN -- store the mask in the representation o: REF ObjectCacheDataRep.bitmap ¬ NEW[ObjectCacheDataRep.bitmap]; movedBox: SF.Box ¬ SF.Displace[ImagerSample.GetBox[bitmap], [viewOrigin, viewOrigin]]; <> objectCacheData ¬ o; o.mask ¬ bitmap; o.shadingBits ¬ RenderOntoWhite[ClipAndDraw, clientToDevice, movedBox]; size ¬ ImagerSample.WordsForMap[ImagerSample.GetSize[bitmap]] + 10; END; objectCacheData.object ¬ object; objectCacheData.clientToDevice ¬ ImagerTransformation.Copy[clientToDevice]; objectCacheData.size ¬ size; InsertBitsAtTransformation[objectTable, object, objectCacheData]; }; <> WITH objectCacheData SELECT FROM ocd: REF ObjectCacheDataRep.bitmap => { -- the fast case t1: VEC ~ ImagerTransformation.TransformVec[clientToDevice, position]; t2: VEC ~ [clientToDevice.c, clientToDevice.f]; t3: VEC ~ [ocd.clientToDevice.c, ocd.clientToDevice.f]; t: VEC ¬ Vector2.Add[t1, Vector2.Sub[t2, t3]]; save: PROC ~ { m: Transformation ¬ ImagerBackdoor.GetTransformation[context: context, from: device, to: view]; ImagerBackdoor.SetT[context, m]; -- work in device coordinates here ImagerTransformation.Destroy[m]; m ¬ NIL; Imager.SetColor[context, Imager.white]; Imager.MaskBitmap[context: context, bitmap: ocd.mask, scanMode: [slow: right, fast: up], position: t]; Imager.SetColor[context, Imager.black]; Imager.MaskBitmap[context: context, bitmap: ocd.shadingBits, scanMode: [slow: right, fast: up], position: Vector2.Sub[t, [viewOrigin, viewOrigin]]]; }; Imager.DoSave[context, save]; ImagerTransformation.Destroy[clientToDevice]; RETURN; }; ENDCASE => NULL; }; -- use the slow case ImagerTransformation.Destroy[clientToDevice]; }; -- end of interactive case ImagerPrivate.DefaultDrawObject[context, object, position, interactive]; }; viewOrigin: NAT ~ 10000; RenderOntoWhite: PROC [operator: PROC [Context], clientToDevice: Transformation, box: SF.Box] RETURNS [shadingBits: SampleMap] = { context: Imager.Context; proc: PROC ~ { Imager.TranslateT[context, [viewOrigin, viewOrigin]]; Imager.ConcatT[context, clientToDevice]; operator[context]; }; shadingBits ¬ ImagerSample.NewSampleMap[box]; ImagerSample.Clear[shadingBits]; context ¬ Create[deviceSpaceSize: [INTEGER.LAST, INTEGER.LAST], scanMode: [right, up], surfaceUnitsPerInch: [1,1], pixelUnits: TRUE, fontCacheName: ImagerBitmapContext.classCode]; <> Imager.SetXY[context, [0, 0]]; SetBitmap[context, shadingBits]; Imager.DoSaveAll[context, proc]; }; < {ok ¬ FALSE; CONTINUE}]; IF ok THEN { o: REF ObjectCacheDataRep.bitmap ¬ NEW[ObjectCacheDataRep.bitmap]; objectCacheData ¬ o; o.bitmap ¬ bitmap; size ¬ ImagerSample.WordsForMap[ImagerSample.GetSize[bitmap]] + 10; } ELSE { objectCacheData ¬ NEW[ObjectCacheDataRep.notCached]; size ¬ SIZE[ObjectCacheDataRep.notCached]; }; objectCacheData.object ¬ object; objectCacheData.clientToDevice ¬ ImagerTransformation.Copy[clientToDevice]; FunctionCache.Insert[x: objectCache, argument: objectCacheData, value: objectCacheData, size: size, clientID: NIL]; }; WITH objectCacheData SELECT FROM ocd: REF ObjectCacheDataRep.bitmap => { t1: VEC ~ ImagerTransformation.TransformVec[clientToDevice, position]; t2: VEC ~ [clientToDevice.c, clientToDevice.f]; t3: VEC ~ [ocd.clientToDevice.c, ocd.clientToDevice.f]; t: VEC ¬ Vector2.Add[t1, Vector2.Sub[t2, t3]]; save: PROC ~ { m: Transformation ~ ImagerBackdoor.GetTransformation[context: context, from: device, to: view]; ImagerBackdoor.SetT[context, m]; Imager.MaskBitmap[context: context, bitmap: ocd.bitmap, scanMode: [slow: right, fast: up], position: t]; ImagerTransformation.Destroy[m]; }; Imager.DoSave[context, save]; ImagerTransformation.Destroy[clientToDevice]; RETURN; }; ENDCASE => NULL; }; ImagerTransformation.Destroy[clientToDevice]; }; ImagerPrivate.DefaultDrawObject[context, object, position, interactive]; }; >> BitmapGetBufferColorOperator: PROC [device: Device] RETURNS [ColorOperator] ~ { RETURN [ImagerColor.NewColorOperatorGrayLinear[sWhite: 0, sBlack: 1]] }; BitmapAccessBuffer: PROC [device: Device, box: SF.Box, action: PROC [pixelMap: PixelMap]] ~ { data: Data ~ NARROW[device.data]; bitmap: SampleMap ~ ImagerSample.Clip[data.bitmap, box]; pixelMap: PixelMap ~ IF data.scratchPixelMap = NIL THEN NEW[ImagerPixel.PixelMapRep[1]] ELSE data.scratchPixelMap; data.scratchPixelMap ¬ NIL; pixelMap.box ¬ ImagerSample.GetBox[bitmap]; pixelMap[0] ¬ bitmap; action[pixelMap]; pixelMap[0] ¬ NIL; TRUSTED {ImagerSample.ReleaseDescriptor[bitmap]}; data.scratchPixelMap ¬ pixelMap; }; <> deviceClass: ImagerDevice.DeviceClass ~ NEW[ImagerDevice.DeviceClassRep ¬ [ SetColor: BitmapSetColor, SetPriority: NIL, SetHalftoneProperties: BitmapSetHalftoneProperties, MaskBoxes: BitmapMaskBoxes, MaskRegion: BitmapMaskRegion, MaskBitmap: BitmapMaskBitmap, MaskRawBitmaps: BitmapMaskRawBitmaps, DrawBitmap: BitmapDrawBitmap, MaskChar: NIL, MoveBox: BitmapMoveBox, SwitchBuffer: BitmapSwitchBuffer, GetBufferColorOperator: BitmapGetBufferColorOperator, AccessBuffer: BitmapAccessBuffer ]]; contextClass: ImagerPrivate.Class ~ CreateClass[]; CreateClass: PROC RETURNS [class: ImagerPrivate.Class] ~ INLINE { class ¬ ImagerRaster.CreateClass[type: classCode]; class.DrawObject ¬ BitmapDrawObjectMultiColor; }; ImagerMaskCache.SetNamedCacheParameters[$Bitmap, [runsCost: [slope: 2, offset: 3]]]; END. <> <> <> <<>>