-- DynamicBitsXXXImpl.meta -- Copyright (C) 1984, 1985, Xerox Corporation. All rights reserved. -- Michael Plass, November 6, 1985 9:52:38 am PST -- PARAMETERS sKernelRadius, fKernelRadius, sModelRadius, fModelRadius, swathSize DIRECTORY DynamicBits, Basics, ImagerPixelMap, ImagerSample, PixelMapOps; DynamicBits101Impl: CEDAR PROGRAM IMPORTS Basics, ImagerPixelMap, ImagerSample, PixelMapOps, DynamicBits ~ BEGIN OPEN DynamicBits; KernelArray: TYPE ~ ARRAY [(-1)..2) OF ARRAY [(-1)..2) OF INTEGER; SampleBuffer: TYPE ~ ImagerSample.SampleBuffer; PixelRow: TYPE ~ RECORD[sOrigin, fOrigin: INTEGER, sample: SampleBuffer]; CreatePixelRow: PROC [s, f: INTEGER, size: NAT] RETURNS [PixelRow] ~ { buffer: SampleBuffer ~ ImagerSample.NewBuffer[iSize: 1, jSize: size]; RETURN [[sOrigin: s, fOrigin: f, sample: buffer]] }; CopyPixelRow: PROC [old: PixelRow] RETURNS [PixelRow] ~ { new: PixelRow ~ CreatePixelRow[old.sOrigin, old.fOrigin, old.sample.jSize]; PixelMapOps.CopySamples[buffer: new.sample, bi: 0, bj: 0, count: old.sample.jSize, source: old.sample, si: 0, sj: 0]; RETURN [new] }; ModelData: TYPE ~ REF ModelDataRep; ModelDataRep: TYPE ~ ARRAY [0..2) OF REF ModelDataItem; ModelDataItem: TYPE ~ RECORD [ intensity: Intensity, noisePenalty: NoisePenalty, a: KernelArray ]; ModelParameterFault: PUBLIC ERROR [data: REF] ~ CODE; neighborhoodBounds: DeviceRectangle ~ [0, 0, 1, 1]; CheckNeighborhood: PROC [neighborhood: DeviceRectangle] ~ { IF neighborhood # neighborhoodBounds THEN ModelParameterFault[$neighborhood]; }; kernelBounds: DeviceRectangle ~ [(-1), (-1), 3, 3]; CheckKernel: PROC [bounds: DeviceRectangle] ~ { IF bounds # kernelBounds THEN ModelParameterFault[$kernel]; }; ArrayFromKernel: PROC [kernel: Kernel] RETURNS [kernelArray: KernelArray] ~ { CheckKernel[kernel.Window]; FOR s: INTEGER IN [(-1)..2) DO FOR f: INTEGER IN [(-1)..2) DO kernelArray[s][f] ← LOOPHOLE[kernel.GetPixel[s, f], INTEGER]; ENDLOOP; ENDLOOP; }; CreatePrinterModel: PROC [neighborhood: DeviceRectangle, printerModel: PrinterModel, kernel: PixelMap] RETURNS [model: Model] ~ { bitmap: PixelMap ← ImagerPixelMap.Create[0, neighborhood]; data: ModelData ← NEW[ModelDataRep]; kernelArray: KernelArray ← ArrayFromKernel[kernel]; CheckNeighborhood[neighborhood]; model ← NEW[ModelRep]; FOR encoding: CARDINAL IN [0..2) DO intensity: Intensity; noisePenalty: NoisePenalty; scaledArray: KernelArray; TRUSTED { t: CARDINAL ← Basics.BITSHIFT[encoding, Basics.bitsPerWord-1]; bitmap ← MakePixelMapFromBits[@t, 1, neighborhoodBounds, bitmap.refRep]; }; [intensity, noisePenalty] ← printerModel[bitmap, encoding]; FOR s: INTEGER IN [(-1)..2) DO FOR f: INTEGER IN [(-1)..2) DO k: INTEGER ← kernelArray[s][f]; scaledArray[s][f] ← IF k>=0 THEN (Basics.LongMult[k, intensity]+127)/255 ELSE -((Basics.LongMult[-k, intensity]+127)/255); ENDLOOP; ENDLOOP; data[encoding] ← NEW[ModelDataItem ← [intensity, noisePenalty, scaledArray]]; ENDLOOP; model.neighborhood ← neighborhood; model.kernel ← kernel.Copy; model.data ← data; }; DoWithPrinterModel: PROC [model: Model, action: PROC[PrinterModel]] ~ { data: ModelData ~ NARROW[model.data]; printerModel: PROC [bitmap: PixelMap, encoding: CARDINAL] RETURNS [Intensity, NoisePenalty] ~ { item: REF ModelDataItem ~ data↑[encoding]; RETURN [item.intensity, item.noisePenalty]; }; action[printerModel]; }; StorePixelRow: PROC [p: PixelRow, pixelMap: PixelMap] ~ { fMin: INTEGER ← MAX[p.fOrigin, pixelMap.fOrigin+pixelMap.fMin]; fMax: INTEGER ← MIN[p.fOrigin+p.sample.jSize, pixelMap.fOrigin+pixelMap.fMin+pixelMap.fSize]; IF fMax<=fMin THEN RETURN; PixelMapOps.PutF[pixelMap: pixelMap, s: p.sOrigin, f: fMin, buffer: p.sample, bi: 0, bj: fMin-p.fOrigin, count: fMax-fMin]; }; Load: PROC [p: PixelRow, pixelMap: PixelMap] ~ { -- Takes care of boundary conditions fMin: INTEGER ← MAX[p.fOrigin, pixelMap.fOrigin+pixelMap.fMin]; fMax: INTEGER ← MIN[p.fOrigin+p.sample.jSize, pixelMap.fOrigin+pixelMap.fMin+pixelMap.fSize]; sMin: INTEGER ← MIN[MAX[p.sOrigin, pixelMap.sOrigin+pixelMap.sMin], pixelMap.sOrigin+pixelMap.sMin+pixelMap.sSize-1]; IF fMax<=fMin THEN {PixelMapOps.ClearSamples[buffer: p.sample, count: p.sample.jSize]; RETURN}; PixelMapOps.GetF[pixelMap: pixelMap, s: sMin, f: fMin, buffer: p.sample, bi: 0, bj: fMin-p.fOrigin, count: fMax-fMin]; FOR i: INTEGER IN [0..fMin-p.fOrigin) DO p.sample[i] ← p.sample[fMin-p.fOrigin]; ENDLOOP; FOR i: INTEGER IN [fMax-p.fOrigin..p.sample.jSize) DO p.sample[i] ← p.sample[fMax-1-p.fOrigin]; ENDLOOP; }; Convolve: PROC [pixelMap: PixelMap, kernel: Kernel, unitIntensity: CARDINAL] ~ { bounds: DeviceRectangle ← pixelMap.BoundedWindow; kernelArray: KernelArray ← ArrayFromKernel[kernel]; p: ARRAY [(-1)..2) OF PixelRow ← CreatePixelRows[]; CreatePixelRows: PROC RETURNS[p: ARRAY[(-1)..2) OF PixelRow]~{ FOR i: INTEGER IN [(-1)..2) DO p[i] ← CreatePixelRow[bounds.sMin+i, bounds.fMin+(-1), bounds.fSize+3-1]; ENDLOOP; }; q: PixelRow ← CopyPixelRow[p[0]]; kernelMag: INTEGER ← KernelWeight[kernel]; halfUnit: CARDINAL ← unitIntensity/2; maxMag: INT ← Basics.LongMult[ABS[kernelMag], INT[unitIntensity]]+halfUnit; Cd: PROC [i: INTEGER] RETURNS [CARDINAL] ~ INLINE {RETURN [LOOPHOLE[i]]}; short: BOOLEAN ← maxMag < CARDINAL.LAST; FOR ss: INT IN [(-1)..2) DO FOR ff: INT IN [(-1)..2) DO short ← short AND kernelArray[ss][ff] >= 0; ENDLOOP; ENDLOOP; FOR i: INTEGER IN [(-1)..2) DO Load[p[i], pixelMap] ENDLOOP; THROUGH [0..bounds.sSize) DO IF short THEN { -- Use 16-bit arithemetic for greater speed. FOR f: NAT IN [-(-1)..bounds.fSize-(-1)) DO pix: CARDINAL ← p[-(-1)].sample[f-(-1)]*Cd[kernelArray[(-1)][(-1)]] + p[-(-1)].sample[f-0]*Cd[kernelArray[(-1)][0]] + p[-(-1)].sample[f-1]*Cd[kernelArray[(-1)][1]] + p[-0].sample[f-(-1)]*Cd[kernelArray[0][(-1)]] + p[-0].sample[f-0]*Cd[kernelArray[0][0]] + p[-0].sample[f-1]*Cd[kernelArray[0][1]] + p[-1].sample[f-(-1)]*Cd[kernelArray[1][(-1)]] + p[-1].sample[f-0]*Cd[kernelArray[1][0]] + p[-1].sample[f-1]*Cd[kernelArray[1][1]] + 0; q.sample[f] ← (pix+halfUnit)/unitIntensity; ENDLOOP; } ELSE { FOR f: NAT IN [-(-1)..bounds.fSize-(-1)) DO pix: INT ← 0; FOR ss: INT IN [(-1)..2) DO FOR ff: INT IN [(-1)..2) DO pix ← pix + p[-ss].sample[f-ff]*INT[kernelArray[ss][ff]]; ENDLOOP; ENDLOOP; q.sample[f] ← (pix+halfUnit)/unitIntensity; ENDLOOP; }; StorePixelRow[q, pixelMap]; {t: PixelRow ← p[(-1)]; p[(-1)] ← p[0]; p[0] ← p[1]; p[2-1] ← t}; p[2-1].sOrigin ← p[2-1].sOrigin + (2-(-1)); Load[p[2-1], pixelMap]; q.sOrigin ← p[0].sOrigin; ENDLOOP; }; ApplyModel: PROC [bitmap: PixelMap, model: Model] RETURNS [PixelMap] ~ { w: DeviceRectangle ← bitmap.Window; gray: PixelMap ← ImagerPixelMap.Create[3, w]; bitRow: PixelRow ← CreatePixelRow[w.sMin+0, w.fMin+0, w.fSize+1]; indexRow: PixelRow ← CreatePixelRow[w.sMin, w.fMin, w.fSize]; destRow: PixelRow ← CopyPixelRow[indexRow]; data: ModelData ← NARROW[model.data]; MergeBits: PROC ~ { FOR f: NAT IN [0..indexRow.sample.jSize) DO k: CARDINAL ← 0; FOR ff: NAT IN [f..f+1) DO k ← k*2+bitRow.sample[ff]; ENDLOOP; indexRow.sample[f] ← (indexRow.sample[f] * 2 + k) MOD 2; ENDLOOP; }; PixelMapOps.ClearSamples[buffer: bitRow.sample, count: bitRow.sample.jSize]; PixelMapOps.ClearSamples[buffer: indexRow.sample, count: indexRow.sample.jSize]; PixelMapOps.ClearSamples[buffer: destRow.sample, count: destRow.sample.jSize]; FOR i: NAT IN [0..1) DO Load[bitRow, bitmap]; MergeBits[]; bitRow.sOrigin ← bitRow.sOrigin + 1; ENDLOOP; FOR s: NAT IN [0..w.sSize) DO FOR f: NAT IN [0..destRow.sample.jSize) DO destRow.sample[f] ← data[indexRow.sample[f]].intensity; ENDLOOP; StorePixelRow[destRow, gray]; destRow.sOrigin ← destRow.sOrigin + 1; Load[bitRow, bitmap]; MergeBits[]; bitRow.sOrigin ← bitRow.sOrigin + 1; ENDLOOP; RETURN [gray] }; TuneSwath: PROC [gray: PixelMap, bitmap: PixelMap, fixedBits: PixelMap, swath: DeviceRectangle, model: Model, scratch: REF] RETURNS [newScratch: REF] ~ { -- Make sure the swath has the right fSize xxx: [0..0] ← swath.fSize-1; -- sOrigin and fOrigin give the position of the origin of the local swatch in the bitmap coordinate system. sOrigin: INTEGER ← swath.sMin-1-1; fOrigin: INTEGER ← swath.fMin; pixelRow: PixelRow ← CreatePixelRow[sOrigin, fOrigin+(-2), 3-(-2)]; swathRow: PixelRow ← CreatePixelRow[sOrigin, fOrigin, 1]; bit: ARRAY [(-1)..2) OF ARRAY [(-2)..3) OF [0..1]; -- The current bits from the bitmap within the swatch index: ARRAY [(-1)..2) OF ARRAY [(-2)..3) OF [0..2); -- The current bits, packed up into indices into the model array fixed: ARRAY [(-1)..2) OF ARRAY [0..1) OF [0..1]; -- A local copy of a piece of fixedBits target: ARRAY [(-1)..2) OF INTEGER; -- A local copy of a piece of gray trial: ARRAY [(-1)..2) OF INTEGER; -- The partially computed approximation to the gray modelData: ModelData ← NARROW[model.data]; nonFixedBitCount: NAT ← 0; SF: TYPE ~ RECORD [s: [(-1)..2), f: [0..1)]; nonFixedBitIndex: ARRAY [0..(2-(-1))*1) OF SF; questionBits: [0..2); -- The bits that need to be determined for a given context. contextBits: [0..4); -- The context bits in the swath. prevContextBits: [0..4); -- The context bits for the previous subproblem. SubproblemSolution: TYPE ~ RECORD [ setting: [0..2), -- The setting of questionBits that achieves the lowest total penalty for the swath so far. minErrHighHalf: [0..CARDINAL.LAST/2], minErrLowHalf: CARDINAL -- The lowest total penalty for the swath so far. -- Packed so the whole thing fits in two words, since there are a lot of them. ]; MakeSubproblemSolution: PROC [setting: [0..2), minErr: INT] RETURNS [SubproblemSolution] ~ INLINE BEGIN long: Basics.LongNumber ← [li[minErr]]; RETURN [[setting: setting, minErrHighHalf: long.highbits, minErrLowHalf: long.lowbits]] END; MinErr: PROC [s: SubproblemSolution] RETURNS [INT] ~ INLINE BEGIN long: Basics.LongNumber ← [num[lowbits: s.minErrLowHalf, highbits: s.minErrHighHalf]]; RETURN [long.li] END; PerSwatchTable: TYPE ~ ARRAY [0..4) OF SubproblemSolution; avail: LIST OF REF PerSwatchTable ← NARROW[scratch]; subproblemTable: LIST OF REF PerSwatchTable ← NIL; Alloc: PROC RETURNS [new: LIST OF REF PerSwatchTable] ~ BEGIN IF avail = NIL THEN new ← LIST[NEW[PerSwatchTable]] ELSE { new ← avail; avail ← avail.rest; new.rest ← NIL; }; new.first↑ ← ALL[[0, CARDINAL.LAST/2, CARDINAL.LAST]]; END; ComputeFixedIndex: PROC ~ BEGIN nonFixedBitCount ← 0; FOR s: [(-1)..2) IN [(-1)..2) DO FOR f: [0..1) IN [0..1) DO IF fixed[s][f] = 0 THEN { nonFixedBitIndex[nonFixedBitCount] ← [s,f]; nonFixedBitCount ← nonFixedBitCount + 1; }; ENDLOOP; ENDLOOP; END; Initialize: PROC ~ BEGIN FOR s: [(-1)..2) IN [(-1)..2) DO pixelRow.sOrigin ← sOrigin+s; Load[pixelRow, bitmap]; FOR f: [(-2)..3) IN [(-2)..3) DO bit[s][f] ← pixelRow.sample[f+fOrigin-pixelRow.fOrigin]; ENDLOOP; IF s+sOrigin NOT IN [swath.sMin..swath.sMin+swath.sSize) THEN fixed[s] ← ALL[1] ELSE { swathRow.sOrigin ← sOrigin+s; Load[swathRow, fixedBits]; FOR f: [0..1) IN [0..1) DO fixed[s][f] ← swathRow.sample[f+fOrigin-swathRow.fOrigin]; ENDLOOP; }; ENDLOOP; pixelRow.sOrigin ← sOrigin; Load[pixelRow, gray]; FOR f: INTEGER IN [(-1)..2) DO target[f] ← pixelRow.sample[f+fOrigin-pixelRow.fOrigin]; ENDLOOP; ComputeOuterModelIndices[]; ComputePartialTrial[]; ComputeFixedIndex[]; END; Advance: PROC ~ BEGIN sOrigin ← sOrigin + 1; FOR s: [(-1)..2) IN [(-1)..2-1) DO bit[s] ← bit[s+1]; fixed[s] ← fixed[s+1]; ENDLOOP; pixelRow.sOrigin ← sOrigin+2-1; Load[pixelRow, bitmap]; FOR f: [(-2)..3) IN [(-2)..3) DO bit[2-1][f] ← pixelRow.sample[f+fOrigin-pixelRow.fOrigin]; ENDLOOP; IF 2-1+sOrigin NOT IN [swath.sMin..swath.sMin+swath.sSize) THEN fixed[2-1] ← ALL[1] ELSE { swathRow.sOrigin ← sOrigin+2-1; Load[swathRow, fixedBits]; FOR f: [0..1) IN [0..1) DO fixed[2-1][f] ← swathRow.sample[f+fOrigin-swathRow.fOrigin]; ENDLOOP; }; pixelRow.sOrigin ← sOrigin; Load[pixelRow, gray]; FOR f: INTEGER IN [(-1)..2) DO target[f] ← pixelRow.sample[f+fOrigin-pixelRow.fOrigin]; ENDLOOP; ComputeOuterModelIndices[]; ComputePartialTrial[]; ComputeFixedIndex[]; END; ComputeOuterModelIndices: PROC ~ BEGIN index[(-1)][(-2)] ← bit[(-1)][(-2)]; index[(-1)][(-1)] ← bit[(-1)][(-1)]; index[(-1)][1] ← bit[(-1)][1]; index[(-1)][2] ← bit[(-1)][2]; index[0][(-2)] ← bit[0][(-2)]; index[0][(-1)] ← bit[0][(-1)]; index[0][1] ← bit[0][1]; index[0][2] ← bit[0][2]; index[1][(-2)] ← bit[1][(-2)]; index[1][(-1)] ← bit[1][(-1)]; index[1][1] ← bit[1][1]; index[1][2] ← bit[1][2]; END; ComputeInnerModelIndices: PROC ~ BEGIN index[(-1)][0] ← bit[(-1)][0]; index[0][0] ← bit[0][0]; index[1][0] ← bit[1][0]; END; ComputePartialTrial: PROC ~ BEGIN -- From the index values t: REF ModelDataItem ← NIL; trial ← ALL[0]; t ← modelData[index[(-1)][(-2)]]; trial[(-1)] ← trial[(-1)] + t.a[1][1]; t ← modelData[index[(-1)][(-1)]]; trial[(-1)] ← trial[(-1)] + t.a[1][0]; trial[0] ← trial[0] + t.a[1][1]; t ← modelData[index[(-1)][1]]; trial[0] ← trial[0] + t.a[1][(-1)]; trial[1] ← trial[1] + t.a[1][0]; t ← modelData[index[(-1)][2]]; trial[1] ← trial[1] + t.a[1][(-1)]; t ← modelData[index[0][(-2)]]; trial[(-1)] ← trial[(-1)] + t.a[0][1]; t ← modelData[index[0][(-1)]]; trial[(-1)] ← trial[(-1)] + t.a[0][0]; trial[0] ← trial[0] + t.a[0][1]; t ← modelData[index[0][1]]; trial[0] ← trial[0] + t.a[0][(-1)]; trial[1] ← trial[1] + t.a[0][0]; t ← modelData[index[0][2]]; trial[1] ← trial[1] + t.a[0][(-1)]; t ← modelData[index[1][(-2)]]; trial[(-1)] ← trial[(-1)] + t.a[(-1)][1]; t ← modelData[index[1][(-1)]]; trial[(-1)] ← trial[(-1)] + t.a[(-1)][0]; trial[0] ← trial[0] + t.a[(-1)][1]; t ← modelData[index[1][1]]; trial[0] ← trial[0] + t.a[(-1)][(-1)]; trial[1] ← trial[1] + t.a[(-1)][0]; t ← modelData[index[1][2]]; trial[1] ← trial[1] + t.a[(-1)][(-1)]; END; ComputeRowPenalty: PROC RETURNS [penalty: CARDINAL] ~ BEGIN -- From the index values t: REF ModelDataItem ← NIL; try: ARRAY [(-1)..2) OF INTEGER ← trial; t ← modelData[index[(-1)][0]]; try[(-1)] ← try[(-1)] + t.a[1][(-1)]; try[0] ← try[0] + t.a[1][0]; try[1] ← try[1] + t.a[1][1]; t ← modelData[index[0][0]]; try[(-1)] ← try[(-1)] + t.a[0][(-1)]; try[0] ← try[0] + t.a[0][0]; try[1] ← try[1] + t.a[0][1]; t ← modelData[index[1][0]]; try[(-1)] ← try[(-1)] + t.a[(-1)][(-1)]; try[0] ← try[0] + t.a[(-1)][0]; try[1] ← try[1] + t.a[(-1)][1]; penalty ← 0 + ABS[try[(-1)]-target[(-1)]] + modelData[index[0][(-1)]].noisePenalty + ABS[try[0]-target[0]] + modelData[index[0][0]].noisePenalty + ABS[try[1]-target[1]] + modelData[index[0][1]].noisePenalty; END; SetCombination: PROC [k: CARDINAL] ~ BEGIN -- k is a packed version of the non-fixed bits -- Sets questionBits and contextBits FOR i: NAT IN [0..nonFixedBitCount) DO sf: SF ← nonFixedBitIndex[i]; bit[sf.s][sf.f] ← k MOD 2; k ← k/2; ENDLOOP; questionBits ← bit[(-1)][0]; contextBits ← (bit[0][0]*2 + bit[1][0]); IF k # 0 THEN ERROR; prevContextBits ← contextBits/2 + questionBits*(4/2); END; UnpackBits: PROC [questionBits, contextBits: CARDINAL] ~ BEGIN -- Unpacks the args into the swath portion of the bit array k: CARDINAL ← contextBits+questionBits*4; bit[1][0] ← k MOD 2; k ← k / 2; bit[0][0] ← k MOD 2; k ← k / 2; bit[(-1)][0] ← k MOD 2; k ← k / 2; END; DoDynamicProgramming: PROC ~ { -- If subproblemTable#NIL, subproblemTable.first gives the best ways to color the pixels up to sOrigin UNTIL sOrigin = swath.sMin+swath.sSize DO Initialize[]; BEGIN nComb: NAT ← Basics.BITSHIFT[1, nonFixedBitCount]; new: LIST OF REF PerSwatchTable ← Alloc[]; pst: REF PerSwatchTable ← new.first; prevPST: REF PerSwatchTable ← NIL; IF subproblemTable # NIL THEN prevPST ← subproblemTable.first; FOR k: NAT IN [0..nComb) DO totBad: INT ← 0; SetCombination[k]; ComputeInnerModelIndices[]; IF prevPST # NIL THEN totBad ← MinErr[prevPST[prevContextBits]]; totBad ← totBad+ComputeRowPenalty[]; IF totBad < MinErr[pst[contextBits]] THEN { pst[contextBits] ← MakeSubproblemSolution[questionBits, totBad]; }; ENDLOOP; new.rest ← subproblemTable; subproblemTable ← new; END; sOrigin ← sOrigin + 1; ENDLOOP; }; Traceback: PROC ~ { lowestPenalty: INT ← MinErr[subproblemTable.first[0]]; contextBits ← 0; -- First look through the most recent subproblem to find the best entry. FOR i: NAT IN (0..4) DO err: INT ← MinErr[subproblemTable.first[i]]; IF err < lowestPenalty THEN { lowestPenalty ← err; contextBits ← i; }; ENDLOOP; sOrigin ← swath.sMin+swath.sSize; FOR t: LIST OF REF PerSwatchTable ← subproblemTable, t.rest WHILE sOrigin-1 > swath.sMin DO questionBits ← t.first[contextBits].setting; sOrigin ← sOrigin - 1; UnpackBits[questionBits, contextBits]; swathRow.sOrigin ← sOrigin; FOR j: NAT IN [0..swathRow.sample.jSize) DO swathRow.sample[j] ← bit[0][j]; ENDLOOP; StorePixelRow[swathRow, bitmap]; contextBits ← contextBits/2 + questionBits*(4/2); ENDLOOP; }; DoDynamicProgramming[]; Traceback[]; newScratch ← subproblemTable; }; DynamicBits.RegisterImpl[[ sKernelRadius: 1, fKernelRadius: 1, sModelRadius: 0, fModelRadius: 0, swathSize: 1, CreatePrinterModel: CreatePrinterModel, DoWithPrinterModel: DoWithPrinterModel, Convolve: Convolve, ApplyModel: ApplyModel, TuneSwath: TuneSwath ]]; END.