001: /*
002: * $RCSfile: SubsampleBinaryToGrayOpImage.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.1 $
009: * $Date: 2005/02/11 04:56:44 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Rectangle;
015: import java.awt.RenderingHints;
016: import java.awt.geom.Point2D;
017: import java.awt.image.Raster;
018: import java.awt.image.RenderedImage;
019: import java.awt.image.WritableRaster;
020: import java.awt.Point;
021: import java.util.Hashtable;
022: import java.awt.Rectangle;
023: import java.awt.image.ColorModel;
024: import java.awt.image.IndexColorModel;
025: import java.awt.image.DataBuffer;
026: import java.awt.image.DataBufferByte;
027: import java.awt.image.DataBufferInt;
028: import java.awt.image.DataBufferUShort;
029: import java.awt.image.IndexColorModel;
030: import java.awt.image.SampleModel;
031: import java.awt.image.MultiPixelPackedSampleModel;
032: import java.awt.image.PixelInterleavedSampleModel;
033: import java.awt.image.SinglePixelPackedSampleModel;
034: import java.awt.image.BandedSampleModel;
035: import java.awt.image.PixelInterleavedSampleModel;
036: import java.awt.image.renderable.ParameterBlock;
037: import com.sun.media.jai.util.ImageUtil;
038: import com.sun.media.jai.util.JDKWorkarounds;
039: import javax.media.jai.ImageLayout;
040: import java.util.Map;
041: import javax.media.jai.GeometricOpImage;
042: import javax.media.jai.JAI;
043: import javax.media.jai.PackedImageData;
044: import javax.media.jai.PixelAccessor;
045: import javax.media.jai.PlanarImage;
046:
047: /**
048: * A class extending <code>GeometricOpImage</code> to
049: * subsample binary images to gray scale images. Image scaling operations
050: * require rectilinear backwards mapping and padding by the resampling
051: * filter dimensions.
052: *
053: * <p> When applying scale factors of scaleX, scaleY to a source image
054: * with width of src_width and height of src_height, the resulting image
055: * is defined to have the following bounds:
056: *
057: * <code></pre>
058: * dst minX = floor(src minX * scaleX)
059: * dst minY = floor(src minY * scaleY)
060: * dst width = floor(src width * scaleX)
061: * dst height = floor(src height * scaleY)
062: * </pre></code>
063: *
064: * @see ScaleOpImage
065: *
066: */
067: public class SubsampleBinaryToGrayOpImage extends GeometricOpImage {
068:
069: /** The horizontal scale factor. */
070: protected float scaleX;
071:
072: /** The vertical scale factor. */
073: protected float scaleY;
074:
075: /** Cached value equal to 1/scaleX. */
076: protected float invScaleX;
077:
078: /** Cached value equal to 1/scaleY. */
079: protected float invScaleY;
080:
081: /** Used to determine whether a float is close to an int */
082: private float floatTol;
083:
084: /** same as ceil(invScaleX), ceil(invScaleY) */
085: private int blockX;
086: private int blockY;
087:
088: /** destination image width */
089: private int dWidth;
090: /** destination image height*/
091: private int dHeight;
092:
093: /** the 1st pixel location for destination pixels, i.e.,
094: * the source pixel matrix
095: * Note the index runs from 0..dstWidth-1 and 0..dstHeight-1
096: * [yValues[j] yValues[j]+blockY-1] by [xValues[i] xValues[i]+blockX-1]
097: * will be condensed to form pixel <code>i</code>th pixel in row <code>j</code>
098: */
099: private int[] xValues;
100: private int[] yValues;
101:
102: // a look up table; lut[i] counts 1s in binary expression of i
103: private int[] lut = new int[256];
104:
105: /**
106: * Convert from number of bits on count to gray value, with
107: * scaling, i.e. if invScaleX,Y=3,3, then the possible bit
108: * counts are 0..9, hence the lookup tables are [0..9] * 255/9.
109: */
110: protected byte[] lutGray;
111:
112: // package accessible for SubsampleBinaryToGrayOpImage4x4, etc...
113: static ImageLayout layoutHelper(RenderedImage source, float scaleX,
114: float scaleY, ImageLayout il, Map config) {
115:
116: ImageLayout layout = (il == null) ? new ImageLayout()
117: : (ImageLayout) il.clone();
118:
119: // to compute dWidth and dHeight
120: // fTol and dWi, dHi must be the same as in computeDestInfo(..)
121: // due to static method, a few lines of coding are repeated
122: int srcWidth = source.getWidth();
123: int srcHeight = source.getHeight();
124:
125: float f_dw = scaleX * srcWidth;
126: float f_dh = scaleY * srcHeight;
127: float fTol = .1F * Math.min(scaleX / (f_dw + 1.0F), scaleY
128: / (f_dh + 1.0F));
129:
130: int dWi = (int) (f_dw);
131: int dHi = (int) (f_dh);
132:
133: // let it be int in the almost int case
134: // espacially in the true int case with float calculation errors
135: if (Math.abs(Math.round(f_dw) - f_dw) < fTol) {
136: dWi = Math.round(f_dw);
137: }
138:
139: if (Math.abs(Math.round(f_dh) - f_dh) < fTol) {
140: dHi = Math.round(f_dh);
141: }
142:
143: // Set the top left coordinate of the destination
144: layout.setMinX((int) (scaleX * source.getMinX()));
145: layout.setMinY((int) (scaleY * source.getMinY()));
146:
147: layout.setWidth(dWi);
148: layout.setHeight(dHi);
149:
150: // sample model
151: SampleModel sm = layout.getSampleModel(null);
152:
153: if (sm == null
154: || sm.getDataType() != DataBuffer.TYPE_BYTE
155: || !(sm instanceof PixelInterleavedSampleModel || sm instanceof SinglePixelPackedSampleModel
156: && sm.getNumBands() == 1)) {
157:
158: // Width and height will be corrected in OpImage.layoutHelper
159: sm = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
160: 1, 1, 1, 1, new int[] { 0 });
161: }
162:
163: layout.setSampleModel(sm);
164:
165: ColorModel cm = layout.getColorModel(null);
166:
167: if (cm == null
168: || !JDKWorkarounds.areCompatibleDataModels(sm, cm)) {
169:
170: layout.setColorModel(ImageUtil.getCompatibleColorModel(sm,
171: config));
172: }
173:
174: return layout;
175: }
176:
177: // Since this operation deals with packed bits in a binary image, we
178: // do not need to expand the IndexColorModel
179: private static Map configHelper(Map configuration) {
180:
181: Map config;
182:
183: if (configuration == null) {
184: config = new RenderingHints(
185: JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
186: } else {
187:
188: config = configuration;
189:
190: if (!config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL)) {
191: RenderingHints hints = (RenderingHints) configuration;
192: config = (RenderingHints) hints.clone();
193: config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
194: Boolean.FALSE);
195: }
196: }
197:
198: return config;
199: }
200:
201: /**
202: * Constructs a <code>SubsampleBinaryToGrayOpImage</code>
203: * from a <code>RenderedImage</code> source, x and y scale
204: * object. The image dimensions are determined by forward-mapping
205: * the source bounds, and are passed to the superclass constructor
206: * by means of the <code>layout</code> parameter. Other fields of
207: * the layout are passed through unchanged. If
208: * <code>layout</code> is <code>null</code>, a new
209: * <code>ImageLayout</code> will be constructor to hold the bounds
210: * information.
211: *
212: * The float rounding errors, such as 1.2 being
213: * internally represented as 1.200001, are dealt with
214: * the floatTol, which is set up so that only 1/10 of pixel
215: * error will occur at the end of a line, which yields correct
216: * results with Math.round() operation.
217: * The repeatability is guaranteed with a one-time computed
218: * table xvalues and yvalues.
219: *
220: * @param layout an <code>ImageLayout</code> optionally containing
221: * the tile grid layout, <code>SampleModel</code>, and
222: * <code>ColorModel</code>, or <code>null</code>.
223: * @param source a <code>RenderedImage</code>.
224: * from this <code>OpImage</code>, or <code>null</code>. If
225: * <code>null</code>, no caching will be performed.
226: * @param cobbleSources a boolean indicating whether
227: * <code>computeRect</code> expects contiguous sources.
228: * @param extender a <code>BorderExtender</code>, or <code>null</code>.
229: * @param interp an <code>Interpolation</code> object to use for
230: * resampling.
231: * @param scaleX scale factor along x axis.
232: * @param scaleY scale factor along y axis.
233: *
234: * @throws IllegalArgumentException if combining the
235: * source bounds with the layout parameter results in negative
236: * output width or height.
237: */
238: public SubsampleBinaryToGrayOpImage(RenderedImage source,
239: ImageLayout layout, Map config, float scaleX, float scaleY) {
240:
241: super (vectorize(source), layoutHelper(source, scaleX, scaleY,
242: layout, config), configHelper(config), true, // cobbleSources,
243: null, // extender
244: null, // interpolation
245: null);
246:
247: this .scaleX = scaleX;
248: this .scaleY = scaleY;
249: int srcMinX = source.getMinX();
250: int srcMinY = source.getMinY();
251: int srcWidth = source.getWidth();
252: int srcHeight = source.getHeight();
253:
254: // compute floatTol, invScaleX, blockX, dWidth, dHeight,...
255: computeDestInfo(srcWidth, srcHeight);
256:
257: if (extender == null) {
258: computableBounds = new Rectangle(0, 0, dWidth, dHeight);
259: } else {
260: // If extender is present we can write the entire destination.
261: computableBounds = getBounds();
262: }
263:
264: // these can be delayed, such as placed in computeRect()
265: buildLookupTables();
266:
267: // compute the begining bit position of each row and column
268: computeXYValues(srcWidth, srcHeight, srcMinX, srcMinY);
269: }
270:
271: /**
272: * Computes the source point corresponding to the supplied point.
273: *
274: * @param destPt the position in destination image coordinates
275: * to map to source image coordinates.
276: *
277: * @return a <code>Point2D</code> of the same class as
278: * <code>destPt</code>.
279: *
280: * @throws IllegalArgumentException if <code>destPt</code> is
281: * <code>null</code>.
282: *
283: * @since JAI 1.1.2
284: */
285: public Point2D mapDestPoint(Point2D destPt) {
286: if (destPt == null) {
287: throw new IllegalArgumentException(JaiI18N
288: .getString("Generic0"));
289: }
290:
291: Point2D pt = (Point2D) destPt.clone();
292:
293: pt.setLocation(destPt.getX() / scaleX, destPt.getY() / scaleY);
294:
295: return pt;
296: }
297:
298: /**
299: * Computes the destination point corresponding to the supplied point.
300: *
301: * @param sourcePt the position in source image coordinates
302: * to map to destination image coordinates.
303: *
304: * @return a <code>Point2D</code> of the same class as
305: * <code>sourcePt</code>.
306: *
307: * @throws IllegalArgumentException if <code>sourcePt</code> is
308: * <code>null</code>.
309: *
310: * @since JAI 1.1.2
311: */
312: public Point2D mapSourcePoint(Point2D sourcePt) {
313: if (sourcePt == null) {
314: throw new IllegalArgumentException(JaiI18N
315: .getString("Generic0"));
316: }
317:
318: Point2D pt = (Point2D) sourcePt.clone();
319:
320: pt.setLocation(sourcePt.getX() * scaleX, sourcePt.getY()
321: * scaleY);
322:
323: return pt;
324: }
325:
326: /**
327: * Returns the minimum bounding box of the region of the destination
328: * to which a particular <code>Rectangle</code> of the specified source
329: * will be mapped.
330: *
331: * @param sourceRect the <code>Rectangle</code> in source coordinates.
332: * @param sourceIndex the index of the source image.
333: *
334: * @return a <code>Rectangle</code> indicating the destination
335: * bounding box, or <code>null</code> if the bounding box
336: * is unknown.
337: *
338: * @throws IllegalArgumentException if <code>sourceIndex</code> is
339: * negative or greater than the index of the last source.
340: * @throws IllegalArgumentException if <code>sourceRect</code> is
341: * <code>null</code>.
342: */
343: protected Rectangle forwardMapRect(Rectangle sourceRect,
344: int sourceIndex) {
345:
346: if (sourceRect == null) {
347: throw new IllegalArgumentException(JaiI18N
348: .getString("Generic0"));
349: }
350:
351: if (sourceIndex != 0) {
352: throw new IllegalArgumentException(JaiI18N
353: .getString("Generic1"));
354: }
355:
356: // Get the source dimensions
357: int x0 = sourceRect.x - blockX + 1;
358: int y0 = sourceRect.y - blockY + 1;
359: x0 = x0 < 0 ? 0 : x0;
360: y0 = y0 < 0 ? 0 : y0;
361:
362: int dx0 = (int) (x0 * scaleX);
363: int dy0 = (int) (y0 * scaleY);
364: while (xValues[dx0] > x0 && dx0 > 0) {
365: dx0--;
366: }
367: while (yValues[dy0] > y0 && dy0 > 0) {
368: dy0--;
369: }
370:
371: int x1 = sourceRect.x + sourceRect.width - 1;
372: int y1 = sourceRect.y + sourceRect.height - 1;
373:
374: int dx1 = (int) Math.round(x1 * scaleX);
375: int dy1 = (int) Math.round(y1 * scaleY);
376: dx1 = dx1 >= dWidth ? dWidth - 1 : dx1;
377: dy1 = dy1 >= dHeight ? dHeight - 1 : dy1;
378: while (xValues[dx1] < x1 && dx1 < dWidth - 1) {
379: dx1++;
380: }
381: while (yValues[dy1] < y1 && dy1 < dHeight - 1) {
382: dy1++;
383: }
384:
385: dx0 += this .minX;
386: dy0 += this .minY;
387: dx1 += this .minX;
388: dy1 += this .minY;
389:
390: // Return the writable destination area
391: return new Rectangle(dx0, dy0, dx1 - dx0 + 1, dy1 - dy0 + 1);
392: }
393:
394: /**
395: * Returns the minimum bounding box of the region of the specified
396: * source to which a particular <code>Rectangle</code> of the
397: * destination will be mapped.
398: *
399: * @param destRect the <code>Rectangle</code> in destination coordinates.
400: * @param sourceIndex the index of the source image.
401: *
402: * @return a <code>Rectangle</code> indicating the source bounding box,
403: * or <code>null</code> if the bounding box is unknown.
404: *
405: * @throws IllegalArgumentException if <code>sourceIndex</code> is
406: * negative or greater than the index of the last source.
407: * @throws IllegalArgumentException if <code>destRect</code> is
408: * <code>null</code>.
409: */
410: protected Rectangle backwardMapRect(Rectangle destRect,
411: int sourceIndex) {
412:
413: if (destRect == null) {
414: throw new IllegalArgumentException(JaiI18N
415: .getString("Generic0"));
416: }
417:
418: if (sourceIndex != 0) {
419: throw new IllegalArgumentException(JaiI18N
420: .getString("Generic1"));
421: }
422:
423: // Get the destination rectangle coordinates and dimensions
424: // Note: indices starting from 0, thus minX/Y should be considered
425: int sx0 = xValues[destRect.x - this .minX];
426: int sy0 = yValues[destRect.y - this .minY];
427: int sx1 = xValues[destRect.x - this .minX + destRect.width - 1];
428: int sy1 = yValues[destRect.y - this .minY + destRect.height - 1];
429:
430: return new Rectangle(sx0, sy0, sx1 - sx0 + blockX, sy1 - sy0
431: + blockY);
432: }
433:
434: /**
435: * Performs a subsamplebinarytogray operation on a specified rectangle.
436: * The sources are cobbled.
437: *
438: * @param sources an array of source Rasters, guaranteed to provide all
439: * necessary source data for computing the output.
440: * @param dest a WritableRaster containing the area to be computed.
441: * @param destRect the rectangle within dest to be processed.
442: */
443: protected void computeRect(Raster[] sources, WritableRaster dest,
444: Rectangle destRect) {
445: Raster source = sources[0];
446:
447: switch (source.getSampleModel().getDataType()) {
448: case DataBuffer.TYPE_BYTE:
449: case DataBuffer.TYPE_SHORT:
450: case DataBuffer.TYPE_USHORT:
451: case DataBuffer.TYPE_INT:
452: byteLoop(source, dest, destRect);
453: break;
454: default:
455: throw new RuntimeException(JaiI18N
456: .getString("SubsampleBinaryToGrayOpImage0"));
457: }
458: }
459:
460: private void byteLoop(Raster source, WritableRaster dest,
461: Rectangle destRect) {
462: PixelAccessor pa = new PixelAccessor(source.getSampleModel(),
463: null);
464: PackedImageData pid = pa.getPackedPixels(source, source
465: .getBounds(), false, false);
466: byte[] sourceData = pid.data;
467: int sourceDBOffset = pid.offset;
468: int dx = destRect.x;
469: int dy = destRect.y;
470: int dwi = destRect.width;
471: int dhi = destRect.height;
472: int sourceTransX = pid.rect.x; // source.getSampleModelTranslateX();
473: int sourceTransY = pid.rect.y; // source.getSampleModelTranslateY();
474:
475: PixelInterleavedSampleModel destSM = (PixelInterleavedSampleModel) dest
476: .getSampleModel();
477: DataBufferByte destDB = (DataBufferByte) dest.getDataBuffer();
478: int destTransX = dest.getSampleModelTranslateX();
479: int destTransY = dest.getSampleModelTranslateY();
480: int destScanlineStride = destSM.getScanlineStride();
481:
482: byte[] destData = destDB.getData();
483: int destDBOffset = destDB.getOffset();
484:
485: int[] sbytenum = new int[dwi];
486: int[] sstartbit = new int[dwi];
487:
488: int[] sAreaBitsOn = new int[dwi];
489: for (int i = 0; i < dwi; i++) {
490: int x = xValues[dx + i - this .minX];
491: int sbitnum = pid.bitOffset + (x - sourceTransX);
492: sbytenum[i] = sbitnum >> 3;
493: sstartbit[i] = sbitnum % 8;
494: }
495:
496: for (int j = 0; j < dhi; j++) {
497:
498: for (int i = 0; i < dwi; i++) {
499: sAreaBitsOn[i] = 0;
500: }
501:
502: for (int y = yValues[dy + j - this .minY]; y < yValues[dy
503: + j - this .minY]
504: + blockY; y++) {
505:
506: int sourceYOffset = (y - sourceTransY) * pid.lineStride
507: + sourceDBOffset;
508:
509: int delement = 0, selement, sendbiti, sendbytenumi;
510: for (int i = 0; i < dwi; i++) {
511: delement = 0;
512: sendbiti = sstartbit[i] + blockX - 1;
513: sendbytenumi = sbytenum[i] + (sendbiti >> 3);
514: sendbiti %= 8;
515:
516: selement = 0x00ff & (int) sourceData[sourceYOffset
517: + sbytenum[i]];
518:
519: if (sbytenum[i] == sendbytenumi) {
520: selement <<= 24 + sstartbit[i];
521: selement >>>= 31 - sendbiti + sstartbit[i];
522: delement += lut[selement];
523: } else {
524: selement <<= 24 + sstartbit[i];
525: selement >>>= 24;
526: delement += lut[selement];
527: for (int b = sbytenum[i] + 1; b < sendbytenumi; b++) {
528: selement = 0x00ff & (int) sourceData[sourceYOffset
529: + b];
530: delement += lut[selement];
531: }
532:
533: selement = 0x00ff & (int) sourceData[sourceYOffset
534: + sendbytenumi];
535: selement >>>= 7 - sendbiti;
536: delement += lut[selement];
537: }
538: sAreaBitsOn[i] += delement;
539: }
540: }
541: int destYOffset = (j + dy - destTransY)
542: * destScanlineStride + destDBOffset;
543:
544: destYOffset += dx - destTransX;
545:
546: // update dest values for row j in raster
547: for (int i = 0; i < dwi; i++) {
548: destData[destYOffset + i] = lutGray[sAreaBitsOn[i]];
549: }
550: }
551: }
552:
553: private void computeDestInfo(int srcWidth, int srcHeight) {
554:
555: // Inverse scale factors
556: invScaleX = 1.0F / scaleX;
557: invScaleY = 1.0F / scaleY;
558: blockX = (int) Math.ceil(invScaleX);
559: blockY = (int) Math.ceil(invScaleY);
560:
561: // calculate dst width and height
562: float f_dw = scaleX * srcWidth;
563: float f_dh = scaleY * srcHeight;
564: floatTol = .1F * Math.min(scaleX / (f_dw + 1.0F), scaleY
565: / (f_dh + 1.0F));
566:
567: dWidth = (int) (f_dw);
568: dHeight = (int) (f_dh);
569:
570: // let it be int in the almost int case
571: // espacially in the true int case with float calculation errors
572: if (Math.abs(Math.round(f_dw) - f_dw) < floatTol) {
573: dWidth = Math.round(f_dw);
574: }
575:
576: if (Math.abs(Math.round(f_dh) - f_dh) < floatTol) {
577: dHeight = Math.round(f_dh);
578: }
579:
580: if (Math.abs(Math.round(invScaleX) - invScaleX) < floatTol) {
581: invScaleX = Math.round(invScaleX);
582: blockX = (int) invScaleX;
583: }
584:
585: if (Math.abs(Math.round(invScaleY) - invScaleY) < floatTol) {
586: invScaleY = Math.round(invScaleY);
587: blockY = (int) invScaleY;
588: }
589:
590: }
591:
592: // buildLookupTables()
593: // initializes variabes bitSet and lut
594: // to be called mainly in the constructor
595: private final void buildLookupTables() {
596: // lut
597: lut[0] = 0;
598: lut[1] = 1;
599: lut[2] = 1;
600: lut[3] = 2;
601: lut[4] = 1;
602: lut[5] = 2;
603: lut[6] = 2;
604: lut[7] = 3;
605: lut[8] = 1;
606: lut[9] = 2;
607: lut[10] = 2;
608: lut[11] = 3;
609: lut[12] = 2;
610: lut[13] = 3;
611: lut[14] = 3;
612: lut[15] = 4;
613: for (int i = 16; i < 256; i++) {
614: lut[i] = lut[i & (0x0f)] + lut[(i >> 4) & (0x0f)];
615: }
616:
617: // lutGray
618: if (lutGray != null)
619: return;
620: lutGray = new byte[blockX * blockY + 1];
621: for (int i = 0; i < lutGray.length; i++) {
622: int tmp = (int) Math.round(255.0F * i
623: / (lutGray.length - 1.0F));
624: lutGray[i] = tmp > 255 ? (byte) 0xff : (byte) tmp;
625: }
626:
627: // switch black-white if needed
628: if (isMinWhite(this .getSourceImage(0).getColorModel()))
629: for (int i = 0; i < lutGray.length; i++)
630: lutGray[i] = (byte) (255 - (0xff & lutGray[i]));
631: }
632:
633: // this function can be called
634: // only after dWidth and dHeight has been set in the constructor
635: // XY values should be computed and stored for repeatable behavior
636: // taking care of non-zero minX, minY
637: private void computeXYValues(int srcWidth, int srcHeight,
638: int srcMinX, int srcMinY) {
639: if (xValues == null || yValues == null) {
640: xValues = new int[dWidth];
641: yValues = new int[dHeight];
642: }
643:
644: float tmp;
645: for (int i = 0; i < dWidth; i++) {
646: tmp = invScaleX * i;
647: xValues[i] = (int) Math.round(tmp);
648: }
649: if (xValues[dWidth - 1] + blockX > srcWidth) {
650: xValues[dWidth - 1]--;
651: }
652:
653: for (int i = 0; i < dHeight; i++) {
654: tmp = invScaleY * i;
655: yValues[i] = Math.round(tmp);
656: }
657: if (yValues[dHeight - 1] + blockY > srcHeight) {
658: yValues[dHeight - 1]--;
659: }
660:
661: // if case the source MinX/Y are not zeros
662: if (srcMinX != 0)
663: for (int i = 0; i < dWidth; i++)
664: xValues[i] += srcMinX;
665: if (srcMinY != 0)
666: for (int i = 0; i < dHeight; i++)
667: yValues[i] += srcMinY;
668: }
669:
670: // check to see whether an indexed colormodel is inverted
671: // returns false if cm not IndexColorModel
672: // red[0] = 0 returns false
673: // red[0] = 255 returns true
674: static boolean isMinWhite(ColorModel cm) {
675: if (cm == null || !(cm instanceof IndexColorModel))
676: return false;
677:
678: byte[] red = new byte[256];
679: ((IndexColorModel) cm).getReds(red);
680: return (red[0] == (byte) 255 ? true : false);
681: }
682: }
|