001: /*
002: * $RCSfile: ImageCodec.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.3 $
009: * $Date: 2005/12/07 00:25:26 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codec;
013:
014: import java.awt.RenderingHints;
015: import java.awt.Transparency;
016: import java.awt.color.ColorSpace;
017: import java.awt.image.ColorModel;
018: import java.awt.image.DataBuffer;
019: import java.awt.image.ComponentColorModel;
020: import java.awt.image.IndexColorModel;
021: import java.awt.image.RenderedImage;
022: import java.awt.image.SampleModel;
023: import java.io.File;
024: import java.io.InputStream;
025: import java.io.IOException;
026: import java.io.OutputStream;
027: import java.util.Enumeration;
028: import java.util.Hashtable;
029: import java.util.Vector;
030: import com.sun.media.jai.codecimpl.BMPCodec;
031: import com.sun.media.jai.codecimpl.FPXCodec;
032: import com.sun.media.jai.codecimpl.GIFCodec;
033: import com.sun.media.jai.codecimpl.JPEGCodec;
034: import com.sun.media.jai.codecimpl.PNGCodec;
035: import com.sun.media.jai.codecimpl.PNMCodec;
036: import com.sun.media.jai.codecimpl.TIFFCodec;
037: import com.sun.media.jai.codecimpl.WBMPCodec;
038: import com.sun.media.jai.codecimpl.ImagingListenerProxy;
039: import com.sun.media.jai.codecimpl.util.FloatDoubleColorModel;
040: import com.sun.media.jai.util.SimpleCMYKColorSpace;
041:
042: /**
043: * An abstract class allowing the creation of image decoders and
044: * encoders. Instances of <code>ImageCodec</code> may be registered.
045: * Once a codec has been registered, the format name associated with
046: * it may be used as the <code>name</code> parameter in the
047: * <code>createImageEncoder()</code> and <code>createImageDecoder()</code>
048: * methods.
049: *
050: * <p> Additionally, subclasses of <code>ImageCodec</code>
051: * are able to perform recognition of their particular format,
052: * wither by inspection of a fixed-length file header or by
053: * arbitrary access to the source data stream.
054: *
055: * <p> Format recognition is performed by two variants of the
056: * <code>isFormatRecognized()</code> method. Which variant should be
057: * called is determined by the output of the codec's
058: * <codec>getNumHeaderBytes()</code> method, which returns 0 if
059: * arbitrary access to the stream is required, and otherwise returns
060: * the number of header bytes required to recognize the format.
061: * Each subclass of <code>ImageCodec</code> needs to implement only
062: * one of the two variants.
063: *
064: * <p><b> This class is not a committed part of the JAI API. It may
065: * be removed or changed in future releases of JAI.</b>
066: */
067: public abstract class ImageCodec {
068:
069: private static Hashtable codecs = new Hashtable();
070:
071: /** Allow only subclasses to instantiate this class. */
072: protected ImageCodec() {
073: }
074:
075: /**
076: * Load the JPEG and PNM codecs.
077: */
078: static {
079: registerCodec(new BMPCodec());
080: registerCodec(new GIFCodec());
081: registerCodec(new FPXCodec());
082: registerCodec(new JPEGCodec());
083: registerCodec(new PNGCodec());
084: registerCodec(new PNMCodec());
085: registerCodec(new TIFFCodec());
086: registerCodec(new WBMPCodec());
087: }
088:
089: /**
090: * Returns the <code>ImageCodec</code> associated with the given
091: * name. <code>null</code> is returned if no codec is registered
092: * with the given name. Case is not significant.
093: *
094: * @param name The name associated with the codec.
095: * @return The associated <code>ImageCodec</code>, or <code>null</code>.
096: */
097: public static ImageCodec getCodec(String name) {
098: return (ImageCodec) codecs.get(name.toLowerCase());
099: }
100:
101: /**
102: * Associates an <code>ImageCodec</code> with its format name, as
103: * determined by its <code>getFormatName()</code> method. Case is
104: * not significant. Any codec previously associated with the name
105: * is discarded.
106: *
107: * @param codec The <code>ImageCodec</code> object to be registered.
108: */
109: public static void registerCodec(ImageCodec codec) {
110: codecs.put(codec.getFormatName().toLowerCase(), codec);
111: }
112:
113: /**
114: * Unregisters the <code>ImageCodec</code> object currently
115: * responsible for handling the named format. Case is not
116: * significant.
117: *
118: * @param name The name associated with the codec to be removed.
119: */
120: public static void unregisterCodec(String name) {
121: codecs.remove(name.toLowerCase());
122: }
123:
124: /**
125: * Returns an <code>Enumeration</code> of all regstered
126: * <code>ImageCodec</code> objects.
127: */
128: public static Enumeration getCodecs() {
129: return codecs.elements();
130: }
131:
132: /**
133: * Returns an <code>ImageEncoder</code> object suitable for
134: * encoding to the supplied <code>OutputStream</code>, using the
135: * supplied <code>ImageEncoderParam</code> object.
136: *
137: * @param name The name associated with the codec.
138: * @param dst An <code>OutputStream</code> to write to.
139: * @param param An instance of <code>ImageEncoderParam</code> suitable
140: * for use with the named codec, or <code>null</code>.
141: * @return An instance of <code>ImageEncoder</code>, or <code>null</code>.
142: */
143: public static ImageEncoder createImageEncoder(String name,
144: OutputStream dst, ImageEncodeParam param) {
145: ImageCodec codec = getCodec(name);
146: if (codec == null) {
147: return null;
148: }
149: return codec.createImageEncoder(dst, param);
150: }
151:
152: /**
153: * Returns an <code>ImageDecoder</code> object suitable for
154: * decoding from the supplied <code>InputStream</code>, using the
155: * supplied <code>ImageDecodeParam</code> object.
156: *
157: * @param name The name associated with the codec.
158: * @param src An <code>InputStream</code> to read from.
159: * @param param An instance of <code>ImageDecodeParam</code> suitable
160: * for use with the named codec, or <code>null</code>.
161: * @return An instance of <code>ImageDecoder</code>, or <code>null</code>.
162: */
163: public static ImageDecoder createImageDecoder(String name,
164: InputStream src, ImageDecodeParam param) {
165: ImageCodec codec = getCodec(name);
166: if (codec == null) {
167: return null;
168: }
169: return codec.createImageDecoder(src, param);
170: }
171:
172: /**
173: * Returns an <code>ImageDecoder</code> object suitable for
174: * decoding from the supplied <code>File</code>, using the
175: * supplied <code>ImageDecodeParam</code> object.
176: *
177: * @param name The name associated with the codec.
178: * @param src A <code>File</code> to read from.
179: * @param param An instance of <code>ImageDecodeParam</code> suitable
180: * for use with the named codec, or <code>null</code>.
181: * @return An instance of <code>ImageDecoder</code>, or <code>null</code>.
182: */
183: public static ImageDecoder createImageDecoder(String name,
184: File src, ImageDecodeParam param) throws IOException {
185: ImageCodec codec = getCodec(name);
186: if (codec == null) {
187: return null;
188: }
189: return codec.createImageDecoder(src, param);
190: }
191:
192: /**
193: * Returns an <code>ImageDecoder</code> object suitable for
194: * decoding from the supplied <code>SeekableStream</code>, using the
195: * supplied <code>ImageDecodeParam</code> object.
196: *
197: * @param name The name associated with the codec.
198: * @param src A <code>SeekableStream</code> to read from.
199: * @param param An instance of <code>ImageDecodeParam</code> suitable
200: * for use with the named codec, or <code>null</code>.
201: * @return An instance of <code>ImageDecoder</code>, or <code>null</code>.
202: */
203: public static ImageDecoder createImageDecoder(String name,
204: SeekableStream src, ImageDecodeParam param) {
205: ImageCodec codec = getCodec(name);
206: if (codec == null) {
207: return null;
208: }
209: return codec.createImageDecoder(src, param);
210: }
211:
212: private static String[] vectorToStrings(Vector nameVec) {
213: int count = nameVec.size();
214: String[] names = new String[count];
215: for (int i = 0; i < count; i++) {
216: names[i] = (String) nameVec.elementAt(i);
217: }
218: return names;
219: }
220:
221: /**
222: * Returns an array of <code>String</code>s indicating the names
223: * of registered <code>ImageCodec</code>s that may be appropriate
224: * for reading the given <code>SeekableStream</code>.
225: *
226: * <p> If the <code>src</code> <code>SeekableStream</code> does
227: * not support seeking backwards (that is, its
228: * <code>canSeekBackwards()</code> method returns
229: * <code>false</code>) then only <code>FormatRecognizer</code>s
230: * that require only a fixed-length header will be checked.
231: *
232: * <p> If the <code>src</code> stream does not support seeking
233: * backwards, it must support marking, as determined by its
234: * <code>markSupported()</code> method.
235: *
236: * @param src A <code>SeekableStream</code> which optionally supports
237: * seeking backwards.
238: * @return An array of <code>String</code>s.
239: *
240: * @throws IllegalArgumentException if <code>src</code> supports
241: * neither seeking backwards nor marking.
242: */
243: public static String[] getDecoderNames(SeekableStream src) {
244: if (!src.canSeekBackwards() && !src.markSupported()) {
245: throw new IllegalArgumentException(JaiI18N
246: .getString("ImageCodec2"));
247: }
248:
249: Enumeration enumeration = codecs.elements();
250: Vector nameVec = new Vector();
251:
252: String opName = null;
253: while (enumeration.hasMoreElements()) {
254: ImageCodec codec = (ImageCodec) enumeration.nextElement();
255:
256: int bytesNeeded = codec.getNumHeaderBytes();
257: if ((bytesNeeded == 0) && !src.canSeekBackwards()) {
258: continue;
259: }
260:
261: try {
262: if (bytesNeeded > 0) {
263: src.mark(bytesNeeded);
264: byte[] header = new byte[bytesNeeded];
265: src.readFully(header);
266: src.reset();
267:
268: if (codec.isFormatRecognized(header)) {
269: nameVec.add(codec.getFormatName());
270: }
271: } else {
272: long pointer = src.getFilePointer();
273: src.seek(0L);
274: if (codec.isFormatRecognized(src)) {
275: nameVec.add(codec.getFormatName());
276: }
277: src.seek(pointer);
278: }
279: } catch (IOException e) {
280: ImagingListenerProxy.errorOccurred(JaiI18N
281: .getString("ImageCodec3"), e, ImageCodec.class,
282: false);
283: // e.printStackTrace();
284: }
285: }
286:
287: return vectorToStrings(nameVec);
288: }
289:
290: /**
291: * Returns an array of <code>String</code>s indicating the names
292: * of registered <code>ImageCodec</code>s that may be appropriate
293: * for writing the given <code>RenderedImage</code>, using the
294: * optional <code>ImageEncodeParam</code>, which may be
295: * <code>null</code>.
296: *
297: * @param im A <code>RenderedImage</code> to be encodec.
298: * @param param An <code>ImageEncodeParam</code>, or null.
299: * @return An array of <code>String</code>s.
300: */
301: public static String[] getEncoderNames(RenderedImage im,
302: ImageEncodeParam param) {
303: Enumeration enumeration = codecs.elements();
304: Vector nameVec = new Vector();
305:
306: String opName = null;
307: while (enumeration.hasMoreElements()) {
308: ImageCodec codec = (ImageCodec) enumeration.nextElement();
309:
310: if (codec.canEncodeImage(im, param)) {
311: nameVec.add(codec.getFormatName());
312: }
313: }
314:
315: return vectorToStrings(nameVec);
316: }
317:
318: /**
319: * Returns the name of this image format.
320: *
321: * @return A <code>String</code> containing the name of the
322: * image format supported by this codec.
323: */
324: public abstract String getFormatName();
325:
326: /**
327: * Returns the number of bytes of header needed to recognize the
328: * format, or 0 if an arbitrary number of bytes may be needed.
329: * The default implementation returns 0.
330: *
331: * <p> The return value must be a constant for all instances of
332: * each particular subclass of <code>ImageCodec</code>.
333: *
334: * <p> Although it is legal to always return 0, in some cases
335: * processing may be more efficient if the number of bytes needed
336: * is known in advance.
337: */
338: public int getNumHeaderBytes() {
339: return 0;
340: }
341:
342: /**
343: * Returns <code>true</code> if the format is recognized in the
344: * initial portion of a stream. The header will be passed in as a
345: * <code>byte</code> array of length <code>getNumHeaderBytes()</code>.
346: * This method should be called only if <code>getNumHeaderBytes()</code>
347: * returns a value greater than 0.
348: *
349: * <p> The default implementation throws an exception to indicate
350: * that it should never be called.
351: *
352: * @param header An array of <code>byte</code>s containing the input
353: * stream header.
354: * @return <code>true</code> if the format is recognized.
355: */
356: public boolean isFormatRecognized(byte[] header) {
357: throw new RuntimeException(JaiI18N.getString("ImageCodec0"));
358: }
359:
360: /**
361: * Returns <code>true</code> if the format is recognized in the
362: * input data stream. This method should be called only if
363: * <code>getNumHeaderBytesNeeded()</code> returns 0.
364: *
365: * <p> The source <code>SeekableStream</code> is guaranteed to
366: * support seeking backwards, and should be seeked to 0 prior
367: * to calling this method.
368: *
369: * <p> The default implementation throws an exception to indicate
370: * that it should never be called.
371: *
372: * @param src A <code>SeekableStream</code> containing the input
373: * data.
374: * @return <code>true</code> if the format is recognized.
375: */
376: public boolean isFormatRecognized(SeekableStream src)
377: throws IOException {
378: throw new RuntimeException(JaiI18N.getString("ImageCodec1"));
379: }
380:
381: /**
382: * Returns a <code>Class</code> object indicating the proper
383: * subclass of <code>ImageEncodeParam</code> to be used with this
384: * <code>ImageCodec</code>. If encoding is not supported by this
385: * codec, <code>null</code> is returned. If encoding is
386: * supported, but a parameter object is not used during encoding,
387: * Object.class is returned to signal this fact.
388: */
389: protected abstract Class getEncodeParamClass();
390:
391: /**
392: * Returns a <code>Class</code> object indicating the proper
393: * subclass of <code>ImageDecodeParam</code> to be used with this
394: * <code>ImageCodec</code>. If encoding is not supported by this
395: * codec, <code>null</code> is returned. If decoding is
396: * supported, but a parameter object is not used during decoding,
397: * Object.class is returned to signal this fact.
398: */
399: protected abstract Class getDecodeParamClass();
400:
401: /**
402: * In a concrete subclass of <code>ImageCodec</code>, returns an
403: * implementation of the <code>ImageEncoder</code> interface
404: * appropriate for that codec.
405: *
406: * @param dst An <code>OutputStream</code> to write to.
407: * @param param An instance of <code>ImageEncoderParam</code>
408: * suitable for use with the <code>ImageCodec</code>
409: * subclass, or <code>null</code>.
410: * @return An instance of <code>ImageEncoder</code>.
411: */
412: protected abstract ImageEncoder createImageEncoder(
413: OutputStream dst, ImageEncodeParam param);
414:
415: /**
416: * Returns <code>true</code> if the given image and encoder param
417: * object are suitable for encoding by this <code>ImageCodec</code>.
418: * For example, some codecs may only deal with images with a certain
419: * number of bands; an attempt to encode an image with an unsupported
420: * number of bands will fail.
421: *
422: * @param im a RenderedImage whose ability to be encoded is to be
423: * determined.
424: * @param param a suitable <code>ImageEncodeParam</code> object,
425: * or <code>null</code>.
426: */
427: public abstract boolean canEncodeImage(RenderedImage im,
428: ImageEncodeParam param);
429:
430: /**
431: * Returns an implementation of the <code>ImageDecoder</code>
432: * interface appropriate for that codec. Subclasses of
433: * <code>ImageCodec</code> may override this method if they wish
434: * to accept data directly from an <code>InputStream</code>;
435: * otherwise, this method will convert the source into a
436: * backwards-seekable <code>SeekableStream</code> and call the
437: * appropriate version of <code>createImageDecoder</code> for that
438: * data type.
439: *
440: * <p> Instances of <code>ImageCodec</code> that do not require
441: * the ability to seek backwards in their source
442: * <code>SeekableStream</code> should override this method in
443: * order to avoid the default call to
444: * <code>SeekableStream.wrapInputStream(src, true)</code>.
445: *
446: * @param dst An <code>InputStream</code> to read from.
447: * @param param An instance of <code>ImageDecodeParam</code>
448: * suitable for use with the <code>ImageCodec</code>
449: * subclass, or <code>null</code>.
450: * @return An instance of <code>ImageDecoder</code>.
451: */
452: protected ImageDecoder createImageDecoder(InputStream src,
453: ImageDecodeParam param) {
454: SeekableStream stream = SeekableStream.wrapInputStream(src,
455: true);
456: return createImageDecoder(stream, param);
457: }
458:
459: /**
460: * Returns an implementation of the <code>ImageDecoder</code>
461: * interface appropriate for that codec. Subclasses of
462: * <code>ImageCodec</code> may override this method if they wish
463: * to accept data directly from a <code>File</code>;
464: * otherwise, this method will convert the source into a
465: * <code>SeekableStream</code> and call the appropriate
466: * version of <code>createImageDecoder</code> for that data type.
467: *
468: * @param dst A <code>File</code> to read from.
469: * @param param An instance of <code>ImageDecodeParam</code>
470: * suitable for use with the <code>ImageCodec</code>
471: * subclass, or <code>null</code>.
472: * @return An instance of <code>ImageDecoder</code>.
473: */
474: protected ImageDecoder createImageDecoder(File src,
475: ImageDecodeParam param) throws IOException {
476: return createImageDecoder(new FileSeekableStream(src), param);
477: }
478:
479: /**
480: * In a concrete subclass of <code>ImageCodec</code>, returns an
481: * implementation of the <code>ImageDecoder</code> interface
482: * appropriate for that codec.
483: *
484: * @param dst A <code>SeekableStream</code> to read from.
485: * @param param An instance of <code>ImageDecodeParam</code>
486: * suitable for use with the <code>ImageCodec</code>
487: * subclass, or <code>null</code>.
488: * @return An instance of <code>ImageDecoder</code>.
489: */
490: protected abstract ImageDecoder createImageDecoder(
491: SeekableStream src, ImageDecodeParam param);
492:
493: // ColorModel utility functions
494:
495: private static final byte[][] grayIndexCmaps = { null,
496: // 1 bit
497: { (byte) 0x00, (byte) 0xff },
498: // 2 bits
499: { (byte) 0x00, (byte) 0x55, (byte) 0xaa, (byte) 0xff },
500: null,
501: // 4 bits
502: { (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
503: (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77,
504: (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb,
505: (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff } };
506:
507: /**
508: * A convenience methods to create an instance of
509: * <code>IndexColorModel</code> suitable for the given 1-banded
510: * <code>SampleModel</code>.
511: *
512: * @param sm a 1-banded <code>SampleModel</code>.
513: * @param blackIsZero <code>true</code> if the gray ramp should
514: * go from black to white, <code>false</code>otherwise.
515: */
516: public static ColorModel createGrayIndexColorModel(SampleModel sm,
517: boolean blackIsZero) {
518: if (sm.getNumBands() != 1) {
519: throw new IllegalArgumentException();
520: }
521: int sampleSize = sm.getSampleSize(0);
522:
523: byte[] cmap = null;
524: if (sampleSize < 8) {
525: cmap = grayIndexCmaps[sampleSize];
526: if (!blackIsZero) {
527: int length = cmap.length;
528: byte[] newCmap = new byte[length];
529: for (int i = 0; i < length; i++) {
530: newCmap[i] = cmap[length - i - 1];
531: }
532: cmap = newCmap;
533: }
534: } else {
535: cmap = new byte[256];
536: if (blackIsZero) {
537: for (int i = 0; i < 256; i++) {
538: cmap[i] = (byte) i;
539: }
540: } else {
541: for (int i = 0; i < 256; i++) {
542: cmap[i] = (byte) (255 - i);
543: }
544: }
545: }
546:
547: return new IndexColorModel(sampleSize, cmap.length, cmap, cmap,
548: cmap);
549: }
550:
551: private static final int[] GrayBits8 = { 8 };
552: private static final ComponentColorModel colorModelGray8 = new ComponentColorModel(
553: ColorSpace.getInstance(ColorSpace.CS_GRAY), GrayBits8,
554: false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
555:
556: private static final int[] GrayAlphaBits8 = { 8, 8 };
557: private static final ComponentColorModel colorModelGrayAlpha8 = new ComponentColorModel(
558: ColorSpace.getInstance(ColorSpace.CS_GRAY), GrayAlphaBits8,
559: true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
560:
561: private static final int[] GrayBits16 = { 16 };
562: private static final ComponentColorModel colorModelGray16 = new ComponentColorModel(
563: ColorSpace.getInstance(ColorSpace.CS_GRAY), GrayBits16,
564: false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
565:
566: private static final int[] GrayAlphaBits16 = { 16, 16 };
567: private static final ComponentColorModel colorModelGrayAlpha16 = new ComponentColorModel(
568: ColorSpace.getInstance(ColorSpace.CS_GRAY),
569: GrayAlphaBits16, true, false, Transparency.TRANSLUCENT,
570: DataBuffer.TYPE_USHORT);
571:
572: private static final int[] GrayBits32 = { 32 };
573: private static final ComponentColorModel colorModelGray32 = new ComponentColorModel(
574: ColorSpace.getInstance(ColorSpace.CS_GRAY), GrayBits32,
575: false, false, Transparency.OPAQUE, DataBuffer.TYPE_INT);
576:
577: private static final int[] GrayAlphaBits32 = { 32, 32 };
578: private static final ComponentColorModel colorModelGrayAlpha32 = new ComponentColorModel(
579: ColorSpace.getInstance(ColorSpace.CS_GRAY),
580: GrayAlphaBits32, true, false, Transparency.TRANSLUCENT,
581: DataBuffer.TYPE_INT);
582:
583: private static final int[] RGBBits8 = { 8, 8, 8 };
584: private static final ComponentColorModel colorModelRGB8 = new ComponentColorModel(
585: ColorSpace.getInstance(ColorSpace.CS_sRGB), RGBBits8,
586: false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
587:
588: private static final int[] RGBABits8 = { 8, 8, 8, 8 };
589: private static final ComponentColorModel colorModelRGBA8 = new ComponentColorModel(
590: ColorSpace.getInstance(ColorSpace.CS_sRGB), RGBABits8,
591: true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
592:
593: private static final int[] RGBBits16 = { 16, 16, 16 };
594: private static final ComponentColorModel colorModelRGB16 = new ComponentColorModel(
595: ColorSpace.getInstance(ColorSpace.CS_sRGB), RGBBits16,
596: false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
597:
598: private static final int[] RGBABits16 = { 16, 16, 16, 16 };
599: private static final ComponentColorModel colorModelRGBA16 = new ComponentColorModel(
600: ColorSpace.getInstance(ColorSpace.CS_sRGB), RGBABits16,
601: true, false, Transparency.TRANSLUCENT,
602: DataBuffer.TYPE_USHORT);
603:
604: private static final int[] RGBBits32 = { 32, 32, 32 };
605: private static final ComponentColorModel colorModelRGB32 = new ComponentColorModel(
606: ColorSpace.getInstance(ColorSpace.CS_sRGB), RGBBits32,
607: false, false, Transparency.OPAQUE, DataBuffer.TYPE_INT);
608:
609: private static final int[] RGBABits32 = { 32, 32, 32, 32 };
610: private static final ComponentColorModel colorModelRGBA32 = new ComponentColorModel(
611: ColorSpace.getInstance(ColorSpace.CS_sRGB), RGBABits32,
612: true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_INT);
613:
614: /**
615: * A convenience method to create an instance of
616: * <code>ComponentColorModel</code> suitable for use with the
617: * given <code>SampleModel</code>. The <code>SampleModel</code>
618: * should have a data type of <code>DataBuffer.TYPE_BYTE</code>,
619: * <code>TYPE_USHORT</code>, or <code>TYPE_INT</code> and between
620: * 1 and 4 bands. Depending on the number of bands of the
621: * <code>SampleModel</code>, either a gray, gray+alpha, rgb, or
622: * rgb+alpha <code>ColorModel</code> is returned.
623: */
624: public static ColorModel createComponentColorModel(SampleModel sm) {
625: int type = sm.getDataType();
626: int bands = sm.getNumBands();
627: ComponentColorModel cm = null;
628:
629: if (type == DataBuffer.TYPE_BYTE) {
630: switch (bands) {
631: case 1:
632: cm = colorModelGray8;
633: break;
634: case 2:
635: cm = colorModelGrayAlpha8;
636: break;
637: case 3:
638: cm = colorModelRGB8;
639: break;
640: case 4:
641: cm = colorModelRGBA8;
642: break;
643: }
644: } else if (type == DataBuffer.TYPE_USHORT) {
645: switch (bands) {
646: case 1:
647: cm = colorModelGray16;
648: break;
649: case 2:
650: cm = colorModelGrayAlpha16;
651: break;
652: case 3:
653: cm = colorModelRGB16;
654: break;
655: case 4:
656: cm = colorModelRGBA16;
657: break;
658: }
659: } else if (type == DataBuffer.TYPE_INT) {
660: switch (bands) {
661: case 1:
662: cm = colorModelGray32;
663: break;
664: case 2:
665: cm = colorModelGrayAlpha32;
666: break;
667: case 3:
668: cm = colorModelRGB32;
669: break;
670: case 4:
671: cm = colorModelRGBA32;
672: break;
673: }
674: } else if (type == DataBuffer.TYPE_FLOAT && bands >= 1
675: && bands <= 4) {
676: ColorSpace cs = bands <= 2 ? ColorSpace
677: .getInstance(ColorSpace.CS_GRAY) : ColorSpace
678: .getInstance(ColorSpace.CS_sRGB);
679: boolean hasAlpha = bands % 2 == 0;
680: cm = new FloatDoubleColorModel(cs, hasAlpha, false,
681: hasAlpha ? Transparency.TRANSLUCENT
682: : Transparency.OPAQUE,
683: DataBuffer.TYPE_FLOAT);
684: }
685:
686: return cm;
687: }
688:
689: /**
690: * A convenience method to create an instance of
691: * <code>ComponentColorModel</code> suitable for use with the
692: * given <code>SampleModel</code> and <ColorSpace</code>. The
693: * <code>SampleModel</code>
694: * should have a data type of <code>DataBuffer.TYPE_BYTE</code>,
695: * <code>TYPE_USHORT</code>, or <code>TYPE_INT</code> and between
696: * 1 and 4 bands. Depending on the number of bands of the
697: * <code>SampleModel</code>, either a gray, gray+alpha, rgb, or
698: * rgb+alpha <code>ColorModel</code> is returned.
699: */
700: public static ColorModel createComponentColorModel(SampleModel sm,
701: ColorSpace cp) {
702: if (cp == null)
703: return createComponentColorModel(sm);
704: int type = sm.getDataType();
705: int bands = sm.getNumBands();
706: ComponentColorModel cm = null;
707:
708: int[] bits = null;
709: int transferType = -1;
710: boolean hasAlpha = (bands % 2 == 0);
711: if (cp instanceof SimpleCMYKColorSpace)
712: hasAlpha = false;
713: int transparency = hasAlpha ? Transparency.TRANSLUCENT
714: : Transparency.OPAQUE;
715: if (type == DataBuffer.TYPE_BYTE) {
716: transferType = DataBuffer.TYPE_BYTE;
717: switch (bands) {
718: case 1:
719: bits = GrayBits8;
720: break;
721: case 2:
722: bits = GrayAlphaBits8;
723: break;
724: case 3:
725: bits = RGBBits8;
726: break;
727: case 4:
728: bits = RGBABits8;
729: break;
730: }
731: } else if (type == DataBuffer.TYPE_USHORT) {
732: transferType = DataBuffer.TYPE_USHORT;
733: switch (bands) {
734: case 1:
735: bits = GrayBits16;
736: break;
737: case 2:
738: bits = GrayAlphaBits16;
739: break;
740: case 3:
741: bits = RGBBits16;
742: break;
743: case 4:
744: bits = RGBABits16;
745: break;
746: }
747: } else if (type == DataBuffer.TYPE_INT) {
748: transferType = DataBuffer.TYPE_INT;
749: switch (bands) {
750: case 1:
751: bits = GrayBits32;
752: break;
753: case 2:
754: bits = GrayAlphaBits32;
755: break;
756: case 3:
757: bits = RGBBits32;
758: break;
759: case 4:
760: bits = RGBABits32;
761: break;
762: }
763: }
764:
765: if (type == DataBuffer.TYPE_FLOAT && bands >= 1 && bands <= 4) {
766: cm = new FloatDoubleColorModel(cp, hasAlpha, false,
767: transparency, DataBuffer.TYPE_FLOAT);
768: } else {
769: cm = new ComponentColorModel(cp, bits, hasAlpha, false,
770: transparency, transferType);
771: }
772:
773: return cm;
774: }
775:
776: /**
777: * Tests whether the color indices represent a gray-scale image.
778: *
779: * @param r The red channel color indices.
780: * @param g The green channel color indices.
781: * @param b The blue channel color indices.
782: * @return If all the indices have 256 entries, and are identical mappings,
783: * return <code>true</code>; otherwise, return <code>false</code>.
784: */
785: public static boolean isIndicesForGrayscale(byte[] r, byte[] g,
786: byte[] b) {
787: if (r.length != g.length || r.length != b.length)
788: return false;
789:
790: int size = r.length;
791:
792: if (size != 256)
793: return false;
794:
795: for (int i = 0; i < size; i++) {
796: byte temp = (byte) i;
797:
798: if (r[i] != temp || g[i] != temp || b[i] != temp)
799: return false;
800: }
801:
802: return true;
803: }
804: }
|