001: /*
002: * $RCSfile: ColorQuantizerOpImage.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/05/10 01:03:22 $
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.Raster;
020: import java.awt.image.RenderedImage;
021: import java.awt.image.SampleModel;
022: import java.awt.image.WritableRaster;
023: import java.util.Map;
024: import javax.media.jai.ImageLayout;
025: import javax.media.jai.LookupTableJAI;
026: import javax.media.jai.OpImage;
027: import javax.media.jai.PixelAccessor;
028: import javax.media.jai.PointOpImage;
029: import javax.media.jai.ROI;
030: import javax.media.jai.RasterFactory;
031: import javax.media.jai.UnpackedImageData;
032:
033: /**
034: * An <code>OpImage</code> implementing the color quantization operation as
035: * described in <code>javax.media.jai.operator.ColorQuantizerDescriptor</code>.
036: *
037: * <p>This <code>OpImage</code> generates an optimal lookup table from the
038: * source RGB image. This lookup table can also be used as a parameter of
039: * operators such as "errordiffusion" to convert the source image into
040: * a color-indexed image.
041: *
042: * <p> This <code>OpImage</code> contains the pixels of the result images
043: * from the nearest distance classification based on the lookup table
044: * generated from this <code>OpImage</code>.
045: *
046: * @see javax.media.jai.KernelJAI
047: * @see javax.media.jai.LookupTableJAI
048: *
049: * @JAI 1.1.2
050: *
051: */
052: abstract class ColorQuantizerOpImage extends PointOpImage {
053: /**
054: * Variables used in the optimized case of 3-band byte to 1-band byte
055: * with a ColorCube color map and a Floyd-Steinberg kernel.
056: */
057: private static final int NBANDS = 3;
058: private static final int NGRAYS = 256;
059:
060: /** Cache the <code>PixelAccessor</code> for computation. */
061: protected PixelAccessor srcPA;
062:
063: /** Cache the source type. */
064: protected int srcSampleType;
065:
066: protected boolean isInitialized = false;
067:
068: /** Cache the <code>PixelAccessor</code> for computation. */
069: protected PixelAccessor destPA;
070:
071: /**
072: * The color map which maps the <code>ErrorDiffusionOpImage</code> to
073: * its source.
074: */
075: protected LookupTableJAI colorMap;
076:
077: /**
078: * The expected maximum number of color, that is, the expected size of
079: * the lookup table.
080: */
081: protected int maxColorNum;
082:
083: /** The subsample rate in the x direction. */
084: protected int xPeriod;
085:
086: /** The subsample rate in y direction. */
087: protected int yPeriod;
088:
089: /** The ROI used to define the data set for training. */
090: protected ROI roi;
091:
092: /**
093: * The number of bands in the source image.
094: */
095: private int numBandsSource;
096:
097: /**
098: * Whether to check for skipped tiles.
099: */
100: protected boolean checkForSkippedTiles = false;
101:
102: /** Used by the subclasses to define the start pixel position. */
103: final static int startPosition(int pos, int start, int period) {
104: int t = (pos - start) % period;
105: return t == 0 ? pos : pos + (period - t);
106: }
107:
108: /**
109: * Force the destination image to be single-banded.
110: */
111: private static ImageLayout layoutHelper(ImageLayout layout,
112: RenderedImage source) {
113: // Create or clone the layout.
114: ImageLayout il = layout == null ? new ImageLayout()
115: : (ImageLayout) layout.clone();
116:
117: // Force the destination and source origins and dimensions to coincide.
118: il.setMinX(source.getMinX());
119: il.setMinY(source.getMinY());
120: il.setWidth(source.getWidth());
121: il.setHeight(source.getHeight());
122:
123: // Get the SampleModel.
124: SampleModel sm = il.getSampleModel(source);
125:
126: // Make sure that this OpImage is single-banded.
127: if (sm.getNumBands() != 1) {
128: sm = RasterFactory.createComponentSampleModel(sm, sm
129: .getTransferType(), sm.getWidth(), sm.getHeight(),
130: 1);
131: il.setSampleModel(sm);
132: }
133:
134: il.setColorModel(null);
135:
136: return il;
137: }
138:
139: /**
140: * Constructs a ColorQuantizerOpImage object.
141: *
142: * <p>The image dimensions are derived from the source image. The tile
143: * grid layout, SampleModel, and ColorModel may optionally be specified
144: * by an ImageLayout object.
145: *
146: * @param source A RenderedImage.
147: * @param config The rendering hints.
148: * @param layout An ImageLayout optionally containing the tile grid layout,
149: * SampleModel, and ColorModel, or null.
150: * @param maxColorNum The expected maximum number of colors.
151: */
152: public ColorQuantizerOpImage(RenderedImage source, Map config,
153: ImageLayout layout, int maxColorNum, ROI roi, int xPeriod,
154: int yPeriod) {
155: super (source, layoutHelper(layout, source), config, true);
156:
157: // Get the source sample model.
158: SampleModel srcSampleModel = source.getSampleModel();
159:
160: // Cache the number of bands in the source.
161: numBandsSource = srcSampleModel.getNumBands();
162:
163: this .maxColorNum = maxColorNum;
164: this .xPeriod = xPeriod;
165: this .yPeriod = yPeriod;
166: this .roi = roi;
167: this .checkForSkippedTiles = xPeriod > tileWidth
168: || yPeriod > tileHeight;
169: }
170:
171: protected void computeRect(Raster[] sources, WritableRaster dest,
172: Rectangle destRect) {
173: if (colorMap == null)
174: train();
175:
176: if (!isInitialized) {
177: srcPA = new PixelAccessor(getSourceImage(0));
178: srcSampleType = srcPA.sampleType == PixelAccessor.TYPE_BIT ? DataBuffer.TYPE_BYTE
179: : srcPA.sampleType;
180: isInitialized = true;
181: }
182:
183: UnpackedImageData uid = srcPA.getPixels(sources[0], destRect,
184: srcSampleType, false);
185: Rectangle rect = uid.rect;
186: byte[][] data = uid.getByteData();
187: int srcLineStride = uid.lineStride;
188: int srcPixelStride = uid.pixelStride;
189: byte[] rBand = data[0];
190: byte[] gBand = data[1];
191: byte[] bBand = data[2];
192:
193: int lastLine = rect.height * srcLineStride + uid.bandOffsets[0];
194:
195: if (destPA == null)
196: destPA = new PixelAccessor(this );
197:
198: UnpackedImageData destUid = destPA.getPixels(dest, destRect,
199: sampleModel.getDataType(), false);
200:
201: int destLineOffset = destUid.bandOffsets[0];
202: int destLineStride = destUid.lineStride;
203: byte[] d = destUid.getByteData(0);
204:
205: int[] currentPixel = new int[3];
206: for (int lo = uid.bandOffsets[0]; lo < lastLine; lo += srcLineStride) {
207: int lastPixel = lo + rect.width * srcPixelStride
208: - uid.bandOffsets[0];
209: int dstPixelOffset = destLineOffset;
210: for (int po = lo - uid.bandOffsets[0]; po < lastPixel; po += srcPixelStride) {
211: d[dstPixelOffset] = findNearestEntry(rBand[po
212: + uid.bandOffsets[0]] & 0xff, gBand[po
213: + uid.bandOffsets[1]] & 0xff, bBand[po
214: + uid.bandOffsets[2]] & 0xff);
215:
216: dstPixelOffset += destUid.pixelStride;
217: }
218: destLineOffset += destLineStride;
219: }
220: }
221:
222: /** Returns one of the available statistics as a property. */
223: public Object getProperty(String name) {
224: int numBands = sampleModel.getNumBands();
225:
226: if (name.equals("JAI.LookupTable") || name.equals("LUT")) {
227: if (colorMap == null)
228: train();
229: return colorMap;
230: }
231:
232: return super .getProperty(name);
233: }
234:
235: protected abstract void train();
236:
237: public ColorModel getColorModel() {
238: if (colorMap == null)
239: train();
240: if (colorModel == null)
241: colorModel = new IndexColorModel(8,
242: colorMap.getByteData(0).length, colorMap
243: .getByteData(0), colorMap.getByteData(1),
244: colorMap.getByteData(2));
245: return colorModel;
246: }
247:
248: protected byte findNearestEntry(int r, int g, int b) {
249: byte[] red = colorMap.getByteData(0);
250: byte[] green = colorMap.getByteData(1);
251: byte[] blue = colorMap.getByteData(2);
252: int index = 0;
253:
254: int dr = r - (red[0] & 0xFF);
255: int dg = g - (green[0] & 0xFF);
256: int db = b - (blue[0] & 0xFF);
257: int minDistance = dr * dr + dg * dg + db * db;
258:
259: // Find the distance to each entry and set the result to
260: // the index which is closest to the argument.
261: for (int i = 1; i < red.length; i++) {
262: dr = r - (red[i] & 0xFF);
263: int distance = dr * dr;
264: if (distance > minDistance)
265: continue;
266: dg = g - (green[i] & 0xFF);
267: distance += dg * dg;
268:
269: if (distance > minDistance)
270: continue;
271: db = b - (blue[i] & 0xFF);
272: distance += db * db;
273: if (distance < minDistance) {
274: minDistance = distance;
275: index = i;
276: }
277: }
278: return (byte) index;
279: }
280: }
|