001 /*
002 * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.awt.image;
027
028 import java.awt.color.ColorSpace;
029 import java.awt.geom.Rectangle2D;
030 import java.awt.Rectangle;
031 import java.awt.RenderingHints;
032 import java.awt.geom.Point2D;
033 import sun.awt.image.ImagingLib;
034
035 /**
036 * This class implements a lookup operation from the source
037 * to the destination. The LookupTable object may contain a single array
038 * or multiple arrays, subject to the restrictions below.
039 * <p>
040 * For Rasters, the lookup operates on bands. The number of
041 * lookup arrays may be one, in which case the same array is
042 * applied to all bands, or it must equal the number of Source
043 * Raster bands.
044 * <p>
045 * For BufferedImages, the lookup operates on color and alpha components.
046 * The number of lookup arrays may be one, in which case the
047 * same array is applied to all color (but not alpha) components.
048 * Otherwise, the number of lookup arrays may
049 * equal the number of Source color components, in which case no
050 * lookup of the alpha component (if present) is performed.
051 * If neither of these cases apply, the number of lookup arrays
052 * must equal the number of Source color components plus alpha components,
053 * in which case lookup is performed for all color and alpha components.
054 * This allows non-uniform rescaling of multi-band BufferedImages.
055 * <p>
056 * BufferedImage sources with premultiplied alpha data are treated in the same
057 * manner as non-premultiplied images for purposes of the lookup. That is,
058 * the lookup is done per band on the raw data of the BufferedImage source
059 * without regard to whether the data is premultiplied. If a color conversion
060 * is required to the destination ColorModel, the premultiplied state of
061 * both source and destination will be taken into account for this step.
062 * <p>
063 * Images with an IndexColorModel cannot be used.
064 * <p>
065 * If a RenderingHints object is specified in the constructor, the
066 * color rendering hint and the dithering hint may be used when color
067 * conversion is required.
068 * <p>
069 * This class allows the Source to be the same as the Destination.
070 *
071 * @version 10 Feb 1997
072 * @see LookupTable
073 * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
074 * @see java.awt.RenderingHints#KEY_DITHERING
075 */
076
077 public class LookupOp implements BufferedImageOp, RasterOp {
078 private LookupTable ltable;
079 private int numComponents;
080 RenderingHints hints;
081
082 /**
083 * Constructs a <code>LookupOp</code> object given the lookup
084 * table and a <code>RenderingHints</code> object, which might
085 * be <code>null</code>.
086 * @param lookup the specified <code>LookupTable</code>
087 * @param hints the specified <code>RenderingHints</code>,
088 * or <code>null</code>
089 */
090 public LookupOp(LookupTable lookup, RenderingHints hints) {
091 this .ltable = lookup;
092 this .hints = hints;
093 numComponents = ltable.getNumComponents();
094 }
095
096 /**
097 * Returns the <code>LookupTable</code>.
098 * @return the <code>LookupTable</code> of this
099 * <code>LookupOp</code>.
100 */
101 public final LookupTable getTable() {
102 return ltable;
103 }
104
105 /**
106 * Performs a lookup operation on a <code>BufferedImage</code>.
107 * If the color model in the source image is not the same as that
108 * in the destination image, the pixels will be converted
109 * in the destination. If the destination image is <code>null</code>,
110 * a <code>BufferedImage</code> will be created with an appropriate
111 * <code>ColorModel</code>. An <code>IllegalArgumentException</code>
112 * might be thrown if the number of arrays in the
113 * <code>LookupTable</code> does not meet the restrictions
114 * stated in the class comment above, or if the source image
115 * has an <code>IndexColorModel</code>.
116 * @param src the <code>BufferedImage</code> to be filtered
117 * @param dst the <code>BufferedImage</code> in which to
118 * store the results of the filter operation
119 * @return the filtered <code>BufferedImage</code>.
120 * @throws IllegalArgumentException if the number of arrays in the
121 * <code>LookupTable</code> does not meet the restrictions
122 * described in the class comments, or if the source image
123 * has an <code>IndexColorModel</code>.
124 */
125 public final BufferedImage filter(BufferedImage src,
126 BufferedImage dst) {
127 ColorModel srcCM = src.getColorModel();
128 int numBands = srcCM.getNumColorComponents();
129 ColorModel dstCM;
130 if (srcCM instanceof IndexColorModel) {
131 throw new IllegalArgumentException("LookupOp cannot be "
132 + "performed on an indexed image");
133 }
134 int numComponents = ltable.getNumComponents();
135 if (numComponents != 1
136 && numComponents != srcCM.getNumComponents()
137 && numComponents != srcCM.getNumColorComponents()) {
138 throw new IllegalArgumentException(
139 "Number of arrays in the " + " lookup table ("
140 + numComponents
141 + " is not compatible with the "
142 + " src image: " + src);
143 }
144
145 boolean needToConvert = false;
146
147 int width = src.getWidth();
148 int height = src.getHeight();
149
150 if (dst == null) {
151 dst = createCompatibleDestImage(src, null);
152 dstCM = srcCM;
153 } else {
154 if (width != dst.getWidth()) {
155 throw new IllegalArgumentException("Src width ("
156 + width + ") not equal to dst width ("
157 + dst.getWidth() + ")");
158 }
159 if (height != dst.getHeight()) {
160 throw new IllegalArgumentException("Src height ("
161 + height + ") not equal to dst height ("
162 + dst.getHeight() + ")");
163 }
164
165 dstCM = dst.getColorModel();
166 if (srcCM.getColorSpace().getType() != dstCM
167 .getColorSpace().getType()) {
168 needToConvert = true;
169 dst = createCompatibleDestImage(src, null);
170 }
171
172 }
173
174 BufferedImage origDst = dst;
175
176 if (ImagingLib.filter(this , src, dst) == null) {
177 // Do it the slow way
178 WritableRaster srcRaster = src.getRaster();
179 WritableRaster dstRaster = dst.getRaster();
180
181 if (srcCM.hasAlpha()) {
182 if (numBands - 1 == numComponents || numComponents == 1) {
183 int minx = srcRaster.getMinX();
184 int miny = srcRaster.getMinY();
185 int[] bands = new int[numBands - 1];
186 for (int i = 0; i < numBands - 1; i++) {
187 bands[i] = i;
188 }
189 srcRaster = srcRaster.createWritableChild(minx,
190 miny, srcRaster.getWidth(), srcRaster
191 .getHeight(), minx, miny, bands);
192 }
193 }
194 if (dstCM.hasAlpha()) {
195 int dstNumBands = dstRaster.getNumBands();
196 if (dstNumBands - 1 == numComponents
197 || numComponents == 1) {
198 int minx = dstRaster.getMinX();
199 int miny = dstRaster.getMinY();
200 int[] bands = new int[numBands - 1];
201 for (int i = 0; i < numBands - 1; i++) {
202 bands[i] = i;
203 }
204 dstRaster = dstRaster.createWritableChild(minx,
205 miny, dstRaster.getWidth(), dstRaster
206 .getHeight(), minx, miny, bands);
207 }
208 }
209
210 filter(srcRaster, dstRaster);
211 }
212
213 if (needToConvert) {
214 // ColorModels are not the same
215 ColorConvertOp ccop = new ColorConvertOp(hints);
216 ccop.filter(dst, origDst);
217 }
218
219 return origDst;
220 }
221
222 /**
223 * Performs a lookup operation on a <code>Raster</code>.
224 * If the destination <code>Raster</code> is <code>null</code>,
225 * a new <code>Raster</code> will be created.
226 * The <code>IllegalArgumentException</code> might be thrown
227 * if the source <code>Raster</code> and the destination
228 * <code>Raster</code> do not have the same
229 * number of bands or if the number of arrays in the
230 * <code>LookupTable</code> does not meet the
231 * restrictions stated in the class comment above.
232 * @param src the source <code>Raster</code> to filter
233 * @param dst the destination <code>WritableRaster</code> for the
234 * filtered <code>src</code>
235 * @return the filtered <code>WritableRaster</code>.
236 * @throws IllegalArgumentException if the source and destinations
237 * rasters do not have the same number of bands, or the
238 * number of arrays in the <code>LookupTable</code> does
239 * not meet the restrictions described in the class comments.
240 *
241 */
242 public final WritableRaster filter(Raster src, WritableRaster dst) {
243 int numBands = src.getNumBands();
244 int dstLength = dst.getNumBands();
245 int height = src.getHeight();
246 int width = src.getWidth();
247 int srcPix[] = new int[numBands];
248
249 // Create a new destination Raster, if needed
250
251 if (dst == null) {
252 dst = createCompatibleDestRaster(src);
253 } else if (height != dst.getHeight() || width != dst.getWidth()) {
254 throw new IllegalArgumentException(
255 "Width or height of Rasters do not " + "match");
256 }
257 dstLength = dst.getNumBands();
258
259 if (numBands != dstLength) {
260 throw new IllegalArgumentException(
261 "Number of channels in the src (" + numBands
262 + ") does not match number of channels"
263 + " in the destination (" + dstLength + ")");
264 }
265 int numComponents = ltable.getNumComponents();
266 if (numComponents != 1 && numComponents != src.getNumBands()) {
267 throw new IllegalArgumentException(
268 "Number of arrays in the " + " lookup table ("
269 + numComponents
270 + " is not compatible with the "
271 + " src Raster: " + src);
272 }
273
274 if (ImagingLib.filter(this , src, dst) != null) {
275 return dst;
276 }
277
278 // Optimize for cases we know about
279 if (ltable instanceof ByteLookupTable) {
280 byteFilter((ByteLookupTable) ltable, src, dst, width,
281 height, numBands);
282 } else if (ltable instanceof ShortLookupTable) {
283 shortFilter((ShortLookupTable) ltable, src, dst, width,
284 height, numBands);
285 } else {
286 // Not one we recognize so do it slowly
287 int sminX = src.getMinX();
288 int sY = src.getMinY();
289 int dminX = dst.getMinX();
290 int dY = dst.getMinY();
291 for (int y = 0; y < height; y++, sY++, dY++) {
292 int sX = sminX;
293 int dX = dminX;
294 for (int x = 0; x < width; x++, sX++, dX++) {
295 // Find data for all bands at this x,y position
296 src.getPixel(sX, sY, srcPix);
297
298 // Lookup the data for all bands at this x,y position
299 ltable.lookupPixel(srcPix, srcPix);
300
301 // Put it back for all bands
302 dst.setPixel(dX, dY, srcPix);
303 }
304 }
305 }
306
307 return dst;
308 }
309
310 /**
311 * Returns the bounding box of the filtered destination image. Since
312 * this is not a geometric operation, the bounding box does not
313 * change.
314 * @param src the <code>BufferedImage</code> to be filtered
315 * @return the bounds of the filtered definition image.
316 */
317 public final Rectangle2D getBounds2D(BufferedImage src) {
318 return getBounds2D(src.getRaster());
319 }
320
321 /**
322 * Returns the bounding box of the filtered destination Raster. Since
323 * this is not a geometric operation, the bounding box does not
324 * change.
325 * @param src the <code>Raster</code> to be filtered
326 * @return the bounds of the filtered definition <code>Raster</code>.
327 */
328 public final Rectangle2D getBounds2D(Raster src) {
329 return src.getBounds();
330
331 }
332
333 /**
334 * Creates a zeroed destination image with the correct size and number of
335 * bands. If destCM is <code>null</code>, an appropriate
336 * <code>ColorModel</code> will be used.
337 * @param src Source image for the filter operation.
338 * @param destCM the destination's <code>ColorModel</code>, which
339 * can be <code>null</code>.
340 * @return a filtered destination <code>BufferedImage</code>.
341 */
342 public BufferedImage createCompatibleDestImage(BufferedImage src,
343 ColorModel destCM) {
344 BufferedImage image;
345 int w = src.getWidth();
346 int h = src.getHeight();
347 int transferType = DataBuffer.TYPE_BYTE;
348 if (destCM == null) {
349 ColorModel cm = src.getColorModel();
350 Raster raster = src.getRaster();
351 if (cm instanceof ComponentColorModel) {
352 DataBuffer db = raster.getDataBuffer();
353 boolean hasAlpha = cm.hasAlpha();
354 boolean isPre = cm.isAlphaPremultiplied();
355 int trans = cm.getTransparency();
356 int[] nbits = null;
357 if (ltable instanceof ByteLookupTable) {
358 if (db.getDataType() == db.TYPE_USHORT) {
359 // Dst raster should be of type byte
360 if (hasAlpha) {
361 nbits = new int[2];
362 if (trans == cm.BITMASK) {
363 nbits[1] = 1;
364 } else {
365 nbits[1] = 8;
366 }
367 } else {
368 nbits = new int[1];
369 }
370 nbits[0] = 8;
371 }
372 // For byte, no need to change the cm
373 } else if (ltable instanceof ShortLookupTable) {
374 transferType = DataBuffer.TYPE_USHORT;
375 if (db.getDataType() == db.TYPE_BYTE) {
376 if (hasAlpha) {
377 nbits = new int[2];
378 if (trans == cm.BITMASK) {
379 nbits[1] = 1;
380 } else {
381 nbits[1] = 16;
382 }
383 } else {
384 nbits = new int[1];
385 }
386 nbits[0] = 16;
387 }
388 }
389 if (nbits != null) {
390 cm = new ComponentColorModel(cm.getColorSpace(),
391 nbits, hasAlpha, isPre, trans, transferType);
392 }
393 }
394 image = new BufferedImage(cm, cm
395 .createCompatibleWritableRaster(w, h), cm
396 .isAlphaPremultiplied(), null);
397 } else {
398 image = new BufferedImage(destCM, destCM
399 .createCompatibleWritableRaster(w, h), destCM
400 .isAlphaPremultiplied(), null);
401 }
402
403 return image;
404 }
405
406 /**
407 * Creates a zeroed-destination <code>Raster</code> with the
408 * correct size and number of bands, given this source.
409 * @param src the <code>Raster</code> to be transformed
410 * @return the zeroed-destination <code>Raster</code>.
411 */
412 public WritableRaster createCompatibleDestRaster(Raster src) {
413 return src.createCompatibleWritableRaster();
414 }
415
416 /**
417 * Returns the location of the destination point given a
418 * point in the source. If <code>dstPt</code> is not
419 * <code>null</code>, it will be used to hold the return value.
420 * Since this is not a geometric operation, the <code>srcPt</code>
421 * will equal the <code>dstPt</code>.
422 * @param srcPt a <code>Point2D</code> that represents a point
423 * in the source image
424 * @param dstPt a <code>Point2D</code>that represents the location
425 * in the destination
426 * @return the <code>Point2D</code> in the destination that
427 * corresponds to the specified point in the source.
428 */
429 public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
430 if (dstPt == null) {
431 dstPt = new Point2D.Float();
432 }
433 dstPt.setLocation(srcPt.getX(), srcPt.getY());
434
435 return dstPt;
436 }
437
438 /**
439 * Returns the rendering hints for this op.
440 * @return the <code>RenderingHints</code> object associated
441 * with this op.
442 */
443 public final RenderingHints getRenderingHints() {
444 return hints;
445 }
446
447 private final void byteFilter(ByteLookupTable lookup, Raster src,
448 WritableRaster dst, int width, int height, int numBands) {
449 int[] srcPix = null;
450
451 // Find the ref to the table and the offset
452 byte[][] table = lookup.getTable();
453 int offset = lookup.getOffset();
454 int tidx;
455 int step = 1;
456
457 // Check if it is one lookup applied to all bands
458 if (table.length == 1) {
459 step = 0;
460 }
461
462 int x;
463 int y;
464 int band;
465 int len = table[0].length;
466
467 // Loop through the data
468 for (y = 0; y < height; y++) {
469 tidx = 0;
470 for (band = 0; band < numBands; band++, tidx += step) {
471 // Find data for this band, scanline
472 srcPix = src.getSamples(0, y, width, 1, band, srcPix);
473
474 for (x = 0; x < width; x++) {
475 int index = srcPix[x] - offset;
476 if (index < 0 || index > len) {
477 throw new IllegalArgumentException("index ("
478 + index + "(out of range: "
479 + " srcPix[" + x + "]=" + srcPix[x]
480 + " offset=" + offset);
481 }
482 // Do the lookup
483 srcPix[x] = table[tidx][index];
484 }
485 // Put it back
486 dst.setSamples(0, y, width, 1, band, srcPix);
487 }
488 }
489 }
490
491 private final void shortFilter(ShortLookupTable lookup, Raster src,
492 WritableRaster dst, int width, int height, int numBands) {
493 int band;
494 int[] srcPix = null;
495
496 // Find the ref to the table and the offset
497 short[][] table = lookup.getTable();
498 int offset = lookup.getOffset();
499 int tidx;
500 int step = 1;
501
502 // Check if it is one lookup applied to all bands
503 if (table.length == 1) {
504 step = 0;
505 }
506
507 int x = 0;
508 int y = 0;
509 int index;
510 int maxShort = (1 << 16) - 1;
511 // Loop through the data
512 for (y = 0; y < height; y++) {
513 tidx = 0;
514 for (band = 0; band < numBands; band++, tidx += step) {
515 // Find data for this band, scanline
516 srcPix = src.getSamples(0, y, width, 1, band, srcPix);
517
518 for (x = 0; x < width; x++) {
519 index = srcPix[x] - offset;
520 if (index < 0 || index > maxShort) {
521 throw new IllegalArgumentException(
522 "index out of range " + index
523 + " x is " + x + "srcPix[x]="
524 + srcPix[x] + " offset="
525 + offset);
526 }
527 // Do the lookup
528 srcPix[x] = table[tidx][index];
529 }
530 // Put it back
531 dst.setSamples(0, y, width, 1, band, srcPix);
532 }
533 }
534 }
535 }
|