001: /*
002: * $RCSfile: ErrorDiffusionOpImage.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.2 $
009: * $Date: 2005/11/16 17:36:08 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.image.ColorModel;
017: import java.awt.image.DataBuffer;
018: import java.awt.image.IndexColorModel;
019: import java.awt.image.MultiPixelPackedSampleModel;
020: import java.awt.image.Raster;
021: import java.awt.image.RenderedImage;
022: import java.awt.image.SampleModel;
023: import java.awt.image.WritableRaster;
024: import java.util.Map;
025: import javax.media.jai.AreaOpImage;
026: import javax.media.jai.ColorCube;
027: import javax.media.jai.ImageLayout;
028: import javax.media.jai.KernelJAI;
029: import javax.media.jai.LookupTableJAI;
030: import javax.media.jai.OpImage;
031: import javax.media.jai.RasterAccessor;
032: import javax.media.jai.RasterFormatTag;
033: import javax.media.jai.RasterFactory;
034: import javax.media.jai.RasterFormatTag;
035: import javax.media.jai.UntiledOpImage;
036: import com.sun.media.jai.util.ImageUtil;
037: import com.sun.media.jai.util.JDKWorkarounds;
038:
039: /**
040: * An <code>OpImage</code> implementing the error diffusion operation as
041: * described in <code>javax.media.jai.operator.ErrorDiffusionDescriptor</code>.
042: *
043: * <p>This <code>OpImage</code> performs dithering of its source image into
044: * a single band image using a specified color map and error filter. For each
045: * pixel in the source image the nearest entry in the color map is found and
046: * the index of this entry is assigned to the <code>OpImage</code> at that
047: * location. The color quantization error is calculated by mapping the index
048: * back through the color map. The error in each band is then "diffused" to
049: * other neighboring pixels in the source image according to the specified
050: * error filter.
051: *
052: * @see javax.media.jai.ColorCube
053: * @see javax.media.jai.KernelJAI
054: * @see javax.media.jai.LookupTableJAI
055: *
056: * @since EA2
057: *
058: */
059: final class ErrorDiffusionOpImage extends UntiledOpImage {
060: /**
061: * Smallest float value which when added to unity will yield something
062: * other than unity.
063: */
064: private static final float FLOAT_EPSILON = 1.192092896E-07F;
065:
066: /**
067: * Variables used in the optimized case of 3-band byte to 1-band byte
068: * with a ColorCube color map and a Floyd-Steinberg kernel.
069: */
070: private static final int NBANDS = 3;
071: private static final int NGRAYS = 256;
072: private static final int OVERSHOOT = 256;
073: private static final int UNDERSHOOT = 256;
074: private static final int TOTALGRAYS = (NGRAYS + UNDERSHOOT + OVERSHOOT);
075: private static final int ERR_SHIFT = 8;
076:
077: /**
078: * The color map which maps the <code>ErrorDiffusionOpImage</code> to
079: * its source.
080: */
081: protected LookupTableJAI colorMap;
082:
083: /**
084: * The kernel associated with the selected error filter.
085: */
086: protected KernelJAI errorKernel;
087:
088: /**
089: * The number of bands in the source image.
090: */
091: private int numBandsSource;
092:
093: /**
094: * Flag indicating whether this is an optimized case.
095: */
096: private boolean isOptimizedCase = false;
097:
098: /**
099: * Minimum valid pixel value
100: */
101: private float minPixelValue;
102:
103: /**
104: * Maximum valid pixel value
105: */
106: private float maxPixelValue;
107:
108: /**
109: * Determines whether a kernel is the Floyd-Steinberg kernel.
110: *
111: * @param kernel The <code>KernelJAI</code> to examine.
112: * @return Whether the kernel argument is the Floyd-Steinberg kernel.
113: */
114: private static boolean isFloydSteinbergKernel(KernelJAI kernel) {
115: int ky = kernel.getYOrigin();
116:
117: return (kernel.getWidth() == 3
118: && kernel.getXOrigin() == 1
119: && kernel.getHeight() - ky == 2
120: && Math.abs(kernel.getElement(2, ky) - 7.0F / 16.0F) < FLOAT_EPSILON
121: && Math
122: .abs(kernel.getElement(0, ky + 1) - 3.0F / 16.0F) < FLOAT_EPSILON
123: && Math
124: .abs(kernel.getElement(1, ky + 1) - 5.0F / 16.0F) < FLOAT_EPSILON && Math
125: .abs(kernel.getElement(2, ky + 1) - 1.0F / 16.0F) < FLOAT_EPSILON);
126: }
127:
128: /**
129: * Create the dither table for the 3-band to 1-band byte optimized case.
130: *
131: * @param colorCube The color cube to be used in dithering.
132: * @return The dither table of the optimized algorithm.
133: */
134: private static int[] initFloydSteinberg24To8(ColorCube colorCube) {
135: // Allocate memory for the dither table.
136: int[] ditherTable = new int[NBANDS * TOTALGRAYS];
137:
138: float[] thresh = new float[NGRAYS];
139:
140: //
141: // Get the colorcube parameters
142: //
143: int[] multipliers = colorCube.getMultipliers();
144: int[] dimsLessOne = colorCube.getDimsLessOne();
145: int offset = colorCube.getAdjustedOffset();
146:
147: //
148: // Construct tables for each band
149: //
150: for (int band = 0; band < NBANDS; band++) {
151: int pTab = band * TOTALGRAYS;
152:
153: //
154: // Calculate the binwidth for this band, i.e. the gray level step
155: // from one quantization level to the next. Do this in scaled
156: // integer to maintain precision.
157: //
158: float binWidth = 255.0F / dimsLessOne[band];
159:
160: //
161: // Pre-calculate the thresholds, so we don't have to do
162: // it in the inner loops. The threshold is always the
163: // midpoint of each bin, since, in error diffusion, the dithering
164: // is done by the error distribution process, not by varying
165: // the dither threshold as in ordered dither.
166: //
167: for (int i = 0; i < dimsLessOne[band]; i++) {
168: thresh[i] = (i + 0.5F) * binWidth;
169: }
170: thresh[dimsLessOne[band]] = 256.0F;
171:
172: //
173: // Populate the range below gray level zero with the same entry
174: // as that for zero. The error distribution can cause undershoots
175: // of as much as 255.
176: //
177: int tableInc = 1 << ERR_SHIFT;
178: int tableValue = (-UNDERSHOOT) << ERR_SHIFT;
179: for (int gray = -UNDERSHOOT; gray < 0; gray++) {
180: ditherTable[pTab++] = tableValue;
181: tableValue += tableInc;
182: }
183:
184: //
185: // Populate the main range of 0...255.
186: //
187: int indexContrib = 0;
188: float frepValue = 0.0F;
189: int repValue;
190: int binNum = 0;
191: float threshold = thresh[0];
192: int gray = 0;
193: while (gray < 256) {
194: //
195: // Populate all the table values up to the next threshold.
196: // Since the only thing which changes is the error,
197: // and it changes by one scaled gray level, we can
198: // just add the increment at each iteration.
199: //
200: int tableBase = indexContrib;
201: repValue = (int) (frepValue + 0.5F);
202: while ((float) gray < threshold) {
203: ditherTable[pTab++] = ((gray - repValue) << ERR_SHIFT)
204: + tableBase;
205: gray++;
206: }
207:
208: //
209: // Once the gray level crosses a threshold,
210: // move to the next bin threshold. Also update
211: // the color contribution index step and the
212: // representative value, needed to compute the error.
213: //
214: threshold = thresh[++binNum];
215: indexContrib += multipliers[band];
216: frepValue += binWidth;
217: }
218:
219: //
220: // Populate the range above gray level 255 with the same entry
221: // as that for 255. As in the under-range case, the error
222: // distribution can cause overshoots as high as 255 over max.
223: //
224: indexContrib -= multipliers[band];
225: repValue = 255;
226: tableValue = ((256 - repValue) << ERR_SHIFT) | indexContrib;
227:
228: for (gray = 256; gray < (256 + OVERSHOOT); gray++) {
229: ditherTable[pTab++] = tableValue;
230: tableValue += tableInc;
231: }
232:
233: } // End band loop
234:
235: //
236: // Add in the colormap offset value to the index contribution
237: // for the first band. This eliminates the need to add it in
238: // when we do the error diffusion.
239: //
240: int pTab = 0;
241: for (int count = TOTALGRAYS; count != 0; count--) {
242: ditherTable[pTab] += offset;
243: pTab++;
244: }
245:
246: return ditherTable;
247: }
248:
249: /**
250: * Force the destination image to be single-banded.
251: */
252: private static ImageLayout layoutHelper(ImageLayout layout,
253: RenderedImage source, LookupTableJAI colorMap) {
254: // Create or clone the layout.
255: ImageLayout il = layout == null ? new ImageLayout()
256: : (ImageLayout) layout.clone();
257:
258: // Force the destination and source origins and dimensions to coincide.
259: il.setMinX(source.getMinX());
260: il.setMinY(source.getMinY());
261: il.setWidth(source.getWidth());
262: il.setHeight(source.getHeight());
263:
264: // Get the SampleModel.
265: SampleModel sm = il.getSampleModel(source);
266:
267: // Ensure an appropriate SampleModel.
268: if (colorMap.getNumBands() == 1
269: && colorMap.getNumEntries() == 2
270: && !ImageUtil.isBinary(il.getSampleModel(source))) {
271: sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
272: il.getTileWidth(source), il.getTileHeight(source),
273: 1);
274: il.setSampleModel(sm);
275: }
276:
277: // Make sure that this OpImage is single-banded.
278: if (sm.getNumBands() != 1) {
279: sm = RasterFactory.createComponentSampleModel(sm, sm
280: .getTransferType(), sm.getWidth(), sm.getHeight(),
281: 1);
282: il.setSampleModel(sm);
283:
284: // Clear the ColorModel mask if needed.
285: ColorModel cm = il.getColorModel(null);
286: if (cm != null
287: && !JDKWorkarounds.areCompatibleDataModels(sm, cm)) {
288: // Clear the mask bit if incompatible.
289: il.unsetValid(ImageLayout.COLOR_MODEL_MASK);
290: }
291: }
292:
293: // Determine whether a larger bit depth is needed.
294: int numColorMapBands = colorMap.getNumBands();
295: int maxIndex = 0;
296: for (int i = 0; i < numColorMapBands; i++) {
297: maxIndex = Math.max(colorMap.getOffset(i)
298: + colorMap.getNumEntries() - 1, maxIndex);
299: }
300:
301: // Create a deeper SampleModel if needed.
302: if ((maxIndex > 255 && sm.getDataType() == DataBuffer.TYPE_BYTE)
303: || (maxIndex > 65535 && sm.getDataType() != DataBuffer.TYPE_INT)) {
304: int dataType = maxIndex > 65535 ? DataBuffer.TYPE_INT
305: : DataBuffer.TYPE_USHORT;
306: sm = RasterFactory.createComponentSampleModel(sm, dataType,
307: sm.getWidth(), sm.getHeight(), 1);
308: il.setSampleModel(sm);
309:
310: // Clear the ColorModel mask if needed.
311: ColorModel cm = il.getColorModel(null);
312: if (cm != null
313: && !JDKWorkarounds.areCompatibleDataModels(sm, cm)) {
314: // Clear the mask bit if incompatible.
315: il.unsetValid(ImageLayout.COLOR_MODEL_MASK);
316: }
317: }
318:
319: // Set an IndexColorModel on the image if:
320: // a. none is provided in the layout;
321: // b. source and colormap have byte data type;
322: // c. the colormap has 3 bands;
323: // d. destination has byte or ushort data type.
324: if ((layout == null || !il
325: .isValid(ImageLayout.COLOR_MODEL_MASK))
326: && source.getSampleModel().getDataType() == DataBuffer.TYPE_BYTE
327: && (sm.getDataType() == DataBuffer.TYPE_BYTE || sm
328: .getDataType() == DataBuffer.TYPE_USHORT)
329: && colorMap.getDataType() == DataBuffer.TYPE_BYTE
330: && colorMap.getNumBands() == 3) {
331: ColorModel cm = source.getColorModel();
332: if (cm == null
333: || (cm != null && cm.getColorSpace().isCS_sRGB())) {
334: int size = colorMap.getNumEntries();
335: byte[][] cmap = new byte[3][maxIndex + 1];
336: for (int i = 0; i < 3; i++) {
337: byte[] band = cmap[i];
338: byte[] data = colorMap.getByteData(i);
339: int offset = colorMap.getOffset(i);
340: int end = offset + size;
341: for (int j = offset; j < end; j++) {
342: band[j] = data[j - offset];
343: }
344: }
345:
346: int numBits = sm.getDataType() == DataBuffer.TYPE_BYTE ? 8
347: : 16;
348: il.setColorModel(new IndexColorModel(numBits,
349: maxIndex + 1, cmap[0], cmap[1], cmap[2]));
350: }
351: }
352:
353: return il;
354: }
355:
356: /**
357: * Constructs an ErrorDiffusionOpImage object.
358: *
359: * <p>The image dimensions are derived from the source image. The tile
360: * grid layout, SampleModel, and ColorModel may optionally be specified
361: * by an ImageLayout object. The calculation assumes that the entire
362: * color quantization error is distributed to the right and below the
363: * current pixel and the filter kernel values are handled appropriately.
364: *
365: * @param source A RenderedImage.
366: * @param layout An ImageLayout optionally containing the tile grid layout,
367: * SampleModel, and ColorModel, or null.
368: * @param colorMap The color map to use which must have a number of bands
369: * equal to the number of bands in the source image. The offset of this
370: * <code>LookupTableJAI</code> must be the same for all bands.
371: * @param errorKernel The error filter kernel. This must have values
372: * between 0.0 and 1.0. Only the entries to the right of and on the same
373: * row as the key entry, and those entries below of the row of the key
374: * entry are used; all other values are ignored. The values used must sum
375: * to 1.0. Note that if a 1-by-1 error filter kernel is supplied, the value
376: * of the unique kernel element is irrelevant and the output of the
377: * algorithm will simply be the index in the supplied color map of the
378: * nearest matching color to the source pixel at the same position.
379: */
380: public ErrorDiffusionOpImage(RenderedImage source, Map config,
381: ImageLayout layout, LookupTableJAI colorMap,
382: KernelJAI errorKernel) {
383: super (source, config, layoutHelper(layout, source, colorMap));
384:
385: // Get the source sample model.
386: SampleModel srcSampleModel = source.getSampleModel();
387:
388: // Cache the number of bands in the source.
389: numBandsSource = srcSampleModel.getNumBands();
390:
391: // Set a reference to the LookupTableJAI.
392: this .colorMap = colorMap;
393:
394: // Set a reference to the KernelJAI.
395: this .errorKernel = errorKernel;
396:
397: // Determine whether this is an (read "the") optimized case.
398: isOptimizedCase = (sampleModel.getTransferType() == DataBuffer.TYPE_BYTE
399: && srcSampleModel.getTransferType() == DataBuffer.TYPE_BYTE
400: && numBandsSource == 3 && colorMap instanceof ColorCube && isFloydSteinbergKernel(errorKernel));
401:
402: // Determine minumum and maximum valid pixel values
403: switch (colorMap.getDataType()) {
404: case DataBuffer.TYPE_BYTE:
405: // Treat byte types as unsigned bytes
406: minPixelValue = 0;
407: maxPixelValue = -Byte.MIN_VALUE + Byte.MAX_VALUE;
408: break;
409: case DataBuffer.TYPE_SHORT:
410: minPixelValue = Short.MIN_VALUE;
411: maxPixelValue = Short.MAX_VALUE;
412: break;
413: case DataBuffer.TYPE_USHORT:
414: minPixelValue = 0;
415: maxPixelValue = -Short.MIN_VALUE + Short.MAX_VALUE;
416: break;
417: case DataBuffer.TYPE_INT:
418: minPixelValue = Integer.MIN_VALUE;
419: maxPixelValue = Integer.MAX_VALUE;
420: break;
421: case DataBuffer.TYPE_FLOAT:
422: minPixelValue = 0;
423: maxPixelValue = Float.MAX_VALUE;
424: break;
425: case DataBuffer.TYPE_DOUBLE:
426: minPixelValue = 0;
427: maxPixelValue = Float.MAX_VALUE;
428: break;
429: default:
430: throw new RuntimeException(JaiI18N
431: .getString("ErrorDiffusionOpImage0"));
432: }
433:
434: }
435:
436: /**
437: * Performs error diffusion on a specified rectangle. The sources are
438: * cobbled. As error diffusion must be calculated on a line-by-line basis
439: * starting at the upper left corner of the image, all image lines through
440: * and including the last line of the tile containing the requested
441: * <code>Rectangle</code> are calculated.
442: *
443: * @param sources The source image Raster.
444: * @param dest A WritableRaster tile containing the area to be computed.
445: * @param destRect The rectangle within dest to be processed.
446: */
447: protected void computeImage(Raster[] sources, WritableRaster dest,
448: Rectangle destRect) {
449: Raster source = sources[0];
450:
451: if (isOptimizedCase) {
452: computeImageOptimized(source, dest, destRect);
453: } else {
454: computeImageDefault(source, dest, destRect);
455: }
456: }
457:
458: protected void computeImageDefault(Raster source,
459: WritableRaster dest, Rectangle destRect) {
460: // Set X-coordinate range.
461: int startX = minX;
462: int endX = startX + width - 1;
463:
464: // Set Y-coordinate range.
465: int startY = minY;
466: int endY = startY + height - 1;
467:
468: // Set the number of lines in the calculation buffer.
469: int numLinesBuffer = errorKernel.getHeight()
470: - errorKernel.getYOrigin();
471:
472: // Allocate memory for the calculation buffer.
473: float[][] bufMem = new float[numLinesBuffer][width
474: * numBandsSource];
475:
476: // Allocate memory for the buffer index array.
477: int[] bufIdx = new int[numLinesBuffer];
478:
479: // Initialize the buffer index array and the rolling buffer.
480: for (int idx = 0; idx < numLinesBuffer; idx++) {
481: bufIdx[idx] = idx;
482: source.getPixels(startX, startY + idx, width, 1,
483: bufMem[idx]);
484: }
485:
486: // Set variable to indicate index of last rolling buffer line.
487: int lastLineBuffer = numLinesBuffer - 1;
488:
489: // Initialize some kernel-dependent constants.
490: int kernelWidth = errorKernel.getWidth();
491: float[] kernelData = errorKernel.getKernelData();
492: int diffuseRight = kernelWidth - errorKernel.getXOrigin() - 1;
493: int diffuseBelow = errorKernel.getHeight()
494: - errorKernel.getYOrigin() - 1;
495: int kernelOffsetRight = errorKernel.getYOrigin() * kernelWidth
496: + errorKernel.getXOrigin() + 1;
497: int kernelOffsetBelow = (errorKernel.getYOrigin() + 1)
498: * kernelWidth;
499:
500: // Set up some arrays for looping.
501: float[] currentPixel = new float[numBandsSource];
502: int offset = colorMap.getOffset();
503: float[] qError = new float[numBandsSource];
504:
505: // Loop over lines.
506: int[] dstData = new int[width];
507: for (int y = startY; y <= endY; y++) {
508: int currentIndex = bufIdx[0];
509: float[] currentLine = bufMem[currentIndex];
510:
511: // Loop over pixels.
512: int dstOffset = 0;
513: for (int x = startX, z = 0; x <= endX; x++) {
514: // Copy all samples of the current pixel.
515: for (int b = 0; b < numBandsSource; b++) {
516: currentPixel[b] = currentLine[z++];
517:
518: // Clamp the current sample to the valid range
519: if (currentPixel[b] < minPixelValue
520: || currentPixel[b] > maxPixelValue) {
521: currentPixel[b] = java.lang.Math.max(
522: currentPixel[b], minPixelValue);
523: currentPixel[b] = java.lang.Math.min(
524: currentPixel[b], maxPixelValue);
525: }
526: }
527:
528: // Find the index of the nearest color in the map.
529: int nearestIndex = colorMap
530: .findNearestEntry(currentPixel);
531:
532: // Save the index in the output data buffer.
533: dstData[dstOffset++] = nearestIndex;
534:
535: // Calculate the error between the nearest and actual colors.
536: boolean isQuantizationError = false;
537: for (int b = 0; b < numBandsSource; b++) {
538: qError[b] = currentPixel[b]
539: - colorMap.lookupFloat(b, nearestIndex);
540: if (qError[b] != 0.0F) {
541: isQuantizationError = true;
542: }
543: }
544:
545: // If there was error in at least one band, distribute it.
546: if (isQuantizationError) {
547: // Distribute error to the right of key entry.
548: int rightCount = Math.min(diffuseRight, endX - x);
549: int kernelOffset = kernelOffsetRight;
550: int sampleOffset = z;
551: for (int u = 1; u <= rightCount; u++) {
552: for (int b = 0; b < numBandsSource; b++) {
553: currentLine[sampleOffset++] += qError[b]
554: * kernelData[kernelOffset];
555: }
556: kernelOffset++;
557: }
558:
559: // Distribute error below key entry.
560: int offsetLeft = Math.min(x - startX, diffuseRight);
561: int count = Math.min(x + diffuseRight, endX)
562: - Math.max(x - diffuseRight, startX) + 1;
563: for (int v = 1; v <= diffuseBelow; v++) {
564: float[] line = bufMem[bufIdx[v]];
565: kernelOffset = kernelOffsetBelow;
566: sampleOffset = z - (offsetLeft + 1)
567: * numBandsSource;
568: for (int u = 1; u <= count; u++) {
569: for (int b = 0; b < numBandsSource; b++) {
570: line[sampleOffset++] += qError[b]
571: * kernelData[kernelOffset];
572: }
573: kernelOffset++;
574: }
575: }
576: }
577: }
578:
579: //
580: // Save data for the current destination line.
581: //
582: dest.setSamples(startX, y, destRect.width, 1, 0, dstData);
583:
584: // Rotate the buffer indexes.
585: for (int k = 0; k < lastLineBuffer; k++) {
586: bufIdx[k] = bufIdx[k + 1];
587: }
588: bufIdx[lastLineBuffer] = currentIndex;
589:
590: // If available, load next image line into the last buffer line.
591: if (y + numLinesBuffer < getMaxY()) {
592: source.getPixels(startX, y + numLinesBuffer, width, 1,
593: bufMem[bufIdx[lastLineBuffer]]);
594: }
595: }
596: }
597:
598: protected void computeImageOptimized(Raster source,
599: WritableRaster dest, Rectangle destRect) {
600: // Set X-coordinate range.
601: int startX = minX;
602: int endX = startX + width - 1;
603:
604: // Set Y-coordinate range.
605: int startY = minY;
606: int endY = startY + height - 1;
607:
608: // Initialize the dither table.
609: int[] ditherTable = initFloydSteinberg24To8((ColorCube) colorMap);
610:
611: // Initialize the padded source width.
612: int sourceWidthPadded = source.getWidth() + 2;
613:
614: // Allocate memory for the error buffer.
615: int[] errBuf = new int[sourceWidthPadded * NBANDS];
616:
617: // Retrieve format tags.
618: RasterFormatTag[] formatTags = getFormatTags();
619:
620: RasterAccessor srcAccessor = new RasterAccessor(source,
621: new Rectangle(startX, startY, source.getWidth(), source
622: .getHeight()), formatTags[0], getSourceImage(0)
623: .getColorModel());
624: RasterAccessor dstAccessor = new RasterAccessor(dest, destRect,
625: formatTags[1], getColorModel());
626:
627: // Set pixel and line strides.
628: int srcPixelStride = srcAccessor.getPixelStride();
629: int srcScanlineStride = srcAccessor.getScanlineStride();
630: int dstPixelStride = dstAccessor.getPixelStride();
631: int dstScanlineStride = dstAccessor.getScanlineStride();
632:
633: // Set data arrays.
634: byte[] srcData0 = srcAccessor.getByteDataArray(0);
635: byte[] srcData1 = srcAccessor.getByteDataArray(1);
636: byte[] srcData2 = srcAccessor.getByteDataArray(2);
637: byte[] dstData = dstAccessor.getByteDataArray(0);
638:
639: // Initialize line offset in each band.
640: int srcLine0 = srcAccessor.getBandOffset(0);
641: int srcLine1 = srcAccessor.getBandOffset(1);
642: int srcLine2 = srcAccessor.getBandOffset(2);
643: int dstLine = dstAccessor.getBandOffset(0);
644:
645: //
646: // For each line, calculate and distribute the error into
647: // a 3 line error buffer (one line for each band).
648: // Also accumulate the contributions of the 3 bands
649: // into the same line of the temporary output buffer.
650: //
651: // The error buffer starts out with all zeroes as the
652: // amount of error to propagate forward.
653: //
654: for (int y = startY; y <= endY; y++) {
655: // Initialize pixel offset in each line in each band.
656: int srcPixel0 = srcLine0;
657: int srcPixel1 = srcLine1;
658: int srcPixel2 = srcLine2;
659: int dstPixel = dstLine;
660:
661: //
662: // Determine the error and index contribution for
663: // the each band. Keep the transitory errors
664: // (errA, errC and errD) in local variables
665: // (hopefully registers). The calculated value
666: // of errB gets put into the error buffer, to be used
667: // on the next line.
668: //
669: // This is the logic here. Floyd-Steinberg dithering
670: // distributes errors to four neighboring pixels,
671: // as shown below. X is the pixel being operated on.
672: //
673: // 7/16 of the error goes to pixel A
674: // 3/16 of the error goes to pixel B
675: // 5/16 of the error goes to pixel C
676: // 1/16 of the error goes to pixel D
677: //
678: // X A
679: // B C D
680: //
681: // The error distributed to pixel A is reused immediately
682: // in the calculation of the next pixel on the same line.
683: // The errors distributed to B, C and D will be used on the
684: // following line. As we move from left to right, the
685: // new error distributed to B gets added to the error
686: // at the previous C. Likewise, the new C error gets added
687: // to the previous D error. So only the errors propagating
688: // to position B survive in the saved error buffer. The
689: // only exception is at the line end, where error C must be
690: // saved. The scheme is shown below.
691: //
692: // XA
693: // BCD
694: // BCD
695: // BCD
696: // BCD
697: //
698: // Treat the error buffer as pixel sequential.
699: // This lets us use a single pointer with offsets
700: // for the entries for all three bands.
701: //
702:
703: //
704: // Zero the error holders for all bands
705: // The bands are called Red, Grn and Blu, but are
706: // really just the first, second and third bands.
707: //
708: int errRedA = 0;
709: int errRedC = 0;
710: int errRedD = 0;
711: int errGrnA = 0;
712: int errGrnC = 0;
713: int errGrnD = 0;
714: int errBluA = 0;
715: int errBluC = 0;
716: int errBluD = 0;
717:
718: int pErr = 0;
719: int dstOffset = 0;
720: for (int x = startX; x <= endX; x++) {
721: //
722: // First band (Red)
723: // The color index is initialized here.
724: // Set the table pointer to the "Red" band
725: //
726: int pTab = UNDERSHOOT;
727:
728: int adjVal = ((errRedA + errBuf[pErr + 3] + 8) >> 4)
729: + (int) (srcData0[srcPixel0] & 0xff);
730: srcPixel0 += srcPixelStride;
731: int tabval = ditherTable[pTab + adjVal];
732: int err = tabval >> 8;
733: int err1 = err;
734: int index = (tabval & 0xff);
735: int err2 = err + err;
736: errBuf[pErr] = errRedC + (err += err2); // 3/16 (B)
737: errRedC = errRedD + (err += err2); // 5/16 (C)
738: errRedD = err1; // 1/16 (D)
739: errRedA = (err += err2); // 7/16 (A)
740:
741: //
742: // Second band (Green)
743: // Set the table pointer to the "Green" band
744: // The color index is incremented here.
745: //
746: pTab += TOTALGRAYS;
747:
748: adjVal = ((errGrnA + errBuf[pErr + 4] + 8) >> 4)
749: + (int) (srcData1[srcPixel1] & 0xff);
750: srcPixel1 += srcPixelStride;
751: tabval = ditherTable[pTab + adjVal];
752: err = tabval >> 8;
753: err1 = err;
754: index += (tabval & 0xff);
755: err2 = err + err;
756: errBuf[pErr + 1] = errGrnC + (err += err2);
757: errGrnC = errGrnD + (err += err2);
758: errGrnD = err1;
759: errGrnA = (err += err2);
760:
761: pTab += TOTALGRAYS;
762:
763: //
764: // Third band (Blue)
765: // Set the table pointer to the "Blue" band
766: // The color index is incremented here.
767: //
768: adjVal = ((errBluA + errBuf[pErr + 5] + 8) >> 4)
769: + (int) (srcData2[srcPixel2] & 0xff);
770: srcPixel2 += srcPixelStride;
771: tabval = ditherTable[pTab + adjVal];
772: err = tabval >> 8;
773: err1 = err;
774: index += (tabval & 0xff);
775: err2 = err + err;
776: errBuf[pErr + 2] = errBluC + (err += err2);
777: errBluC = errBluD + (err += err2);
778: errBluD = err1;
779: errBluA = (err += err2);
780:
781: // Save the result in the output data buffer.
782: dstData[dstPixel] = (byte) (index & 0xff);
783: dstPixel += dstPixelStride;
784:
785: pErr += 3;
786:
787: } // End pixel loop
788:
789: //
790: // Save last error in line
791: //
792: int last = 3 * (sourceWidthPadded - 2);
793: errBuf[last] = errRedC;
794: errBuf[last + 1] = errGrnC;
795: errBuf[last + 2] = errBluC;
796:
797: // Increment offset in each band to next line.
798: srcLine0 += srcScanlineStride;
799: srcLine1 += srcScanlineStride;
800: srcLine2 += srcScanlineStride;
801: dstLine += dstScanlineStride;
802: } // End scanline loop
803:
804: // Make sure that the output data is copied to the destination.
805: dstAccessor.copyDataToRaster();
806: }
807: }
|