View Javadoc
1   /*
2    * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.imageio;
27  
28  import java.awt.Point;
29  import java.awt.Transparency;
30  import java.awt.image.BandedSampleModel;
31  import java.awt.image.BufferedImage;
32  import java.awt.image.ColorModel;
33  import java.awt.color.ColorSpace;
34  import java.awt.image.IndexColorModel;
35  import java.awt.image.ComponentColorModel;
36  import java.awt.image.DataBuffer;
37  import java.awt.image.DirectColorModel;
38  import java.awt.image.MultiPixelPackedSampleModel;
39  import java.awt.image.PixelInterleavedSampleModel;
40  import java.awt.image.SinglePixelPackedSampleModel;
41  import java.awt.image.Raster;
42  import java.awt.image.RenderedImage;
43  import java.awt.image.SampleModel;
44  import java.awt.image.WritableRaster;
45  import java.util.Hashtable;
46  
47  /**
48   * A class that allows the format of an image (in particular, its
49   * <code>SampleModel</code> and <code>ColorModel</code>) to be
50   * specified in a convenient manner.
51   *
52   */
53  public class ImageTypeSpecifier {
54  
55      /**
56       * The <code>ColorModel</code> to be used as a prototype.
57       */
58      protected ColorModel colorModel;
59  
60      /**
61       * A <code>SampleModel</code> to be used as a prototype.
62       */
63      protected SampleModel sampleModel;
64  
65      /**
66       * Cached specifiers for all of the standard
67       * <code>BufferedImage</code> types.
68       */
69      private static ImageTypeSpecifier[] BISpecifier;
70      private static ColorSpace sRGB;
71      // Initialize the standard specifiers
72      static {
73          sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
74  
75          BISpecifier =
76              new ImageTypeSpecifier[BufferedImage.TYPE_BYTE_INDEXED + 1];
77      }
78  
79      /**
80       * A constructor to be used by inner subclasses only.
81       */
82      private ImageTypeSpecifier() {}
83  
84      /**
85       * Constructs an <code>ImageTypeSpecifier</code> directly
86       * from a <code>ColorModel</code> and a <code>SampleModel</code>.
87       * It is the caller's responsibility to supply compatible
88       * parameters.
89       *
90       * @param colorModel a <code>ColorModel</code>.
91       * @param sampleModel a <code>SampleModel</code>.
92       *
93       * @exception IllegalArgumentException if either parameter is
94       * <code>null</code>.
95       * @exception IllegalArgumentException if <code>sampleModel</code>
96       * is not compatible with <code>colorModel</code>.
97       */
98      public ImageTypeSpecifier(ColorModel colorModel, SampleModel sampleModel) {
99          if (colorModel == null) {
100             throw new IllegalArgumentException("colorModel == null!");
101         }
102         if (sampleModel == null) {
103             throw new IllegalArgumentException("sampleModel == null!");
104         }
105         if (!colorModel.isCompatibleSampleModel(sampleModel)) {
106             throw new IllegalArgumentException
107                 ("sampleModel is incompatible with colorModel!");
108         }
109         this.colorModel = colorModel;
110         this.sampleModel = sampleModel;
111     }
112 
113     /**
114      * Constructs an <code>ImageTypeSpecifier</code> from a
115      * <code>RenderedImage</code>.  If a <code>BufferedImage</code> is
116      * being used, one of the factory methods
117      * <code>createFromRenderedImage</code> or
118      * <code>createFromBufferedImageType</code> should be used instead in
119      * order to get a more accurate result.
120      *
121      * @param image a <code>RenderedImage</code>.
122      *
123      * @exception IllegalArgumentException if the argument is
124      * <code>null</code>.
125      */
126     public ImageTypeSpecifier(RenderedImage image) {
127         if (image == null) {
128             throw new IllegalArgumentException("image == null!");
129         }
130         colorModel = image.getColorModel();
131         sampleModel = image.getSampleModel();
132     }
133 
134     // Packed
135 
136     static class Packed extends ImageTypeSpecifier {
137         ColorSpace colorSpace;
138         int redMask;
139         int greenMask;
140         int blueMask;
141         int alphaMask;
142         int transferType;
143         boolean isAlphaPremultiplied;
144 
145         public Packed(ColorSpace colorSpace,
146                       int redMask,
147                       int greenMask,
148                       int blueMask,
149                       int alphaMask, // 0 if no alpha
150                       int transferType,
151                       boolean isAlphaPremultiplied) {
152             if (colorSpace == null) {
153                 throw new IllegalArgumentException("colorSpace == null!");
154             }
155             if (colorSpace.getType() != ColorSpace.TYPE_RGB) {
156                 throw new IllegalArgumentException
157                     ("colorSpace is not of type TYPE_RGB!");
158             }
159             if (transferType != DataBuffer.TYPE_BYTE &&
160                 transferType != DataBuffer.TYPE_USHORT &&
161                 transferType != DataBuffer.TYPE_INT) {
162                 throw new IllegalArgumentException
163                     ("Bad value for transferType!");
164             }
165             if (redMask == 0 && greenMask == 0 &&
166                 blueMask == 0 && alphaMask == 0) {
167                 throw new IllegalArgumentException
168                     ("No mask has at least 1 bit set!");
169             }
170             this.colorSpace = colorSpace;
171             this.redMask = redMask;
172             this.greenMask = greenMask;
173             this.blueMask = blueMask;
174             this.alphaMask = alphaMask;
175             this.transferType = transferType;
176             this.isAlphaPremultiplied = isAlphaPremultiplied;
177 
178             int bits = 32;
179             this.colorModel =
180                 new DirectColorModel(colorSpace,
181                                      bits,
182                                      redMask, greenMask, blueMask,
183                                      alphaMask, isAlphaPremultiplied,
184                                      transferType);
185             this.sampleModel = colorModel.createCompatibleSampleModel(1, 1);
186         }
187     }
188 
189     /**
190      * Returns a specifier for a packed image format that will use a
191      * <code>DirectColorModel</code> and a packed
192      * <code>SampleModel</code> to store each pixel packed into in a
193      * single byte, short, or int.
194      *
195      * @param colorSpace the desired <code>ColorSpace</code>.
196      * @param redMask a contiguous mask indicated the position of the
197      * red channel.
198      * @param greenMask a contiguous mask indicated the position of the
199      * green channel.
200      * @param blueMask a contiguous mask indicated the position of the
201      * blue channel.
202      * @param alphaMask a contiguous mask indicated the position of the
203      * alpha channel.
204      * @param transferType the desired <code>SampleModel</code> transfer type.
205      * @param isAlphaPremultiplied <code>true</code> if the color channels
206      * will be premultipled by the alpha channel.
207      *
208      * @return an <code>ImageTypeSpecifier</code> with the desired
209      * characteristics.
210      *
211      * @exception IllegalArgumentException if <code>colorSpace</code>
212      * is <code>null</code>.
213      * @exception IllegalArgumentException if <code>colorSpace</code>
214      * is not of type <code>TYPE_RGB</code>.
215      * @exception IllegalArgumentException if no mask has at least 1
216      * bit set.
217      * @exception IllegalArgumentException if
218      * <code>transferType</code> if not one of
219      * <code>DataBuffer.TYPE_BYTE</code>,
220      * <code>DataBuffer.TYPE_USHORT</code>, or
221      * <code>DataBuffer.TYPE_INT</code>.
222      */
223     public static ImageTypeSpecifier
224         createPacked(ColorSpace colorSpace,
225                      int redMask,
226                      int greenMask,
227                      int blueMask,
228                      int alphaMask, // 0 if no alpha
229                      int transferType,
230                      boolean isAlphaPremultiplied) {
231         return new ImageTypeSpecifier.Packed(colorSpace,
232                                              redMask,
233                                              greenMask,
234                                              blueMask,
235                                              alphaMask, // 0 if no alpha
236                                              transferType,
237                                              isAlphaPremultiplied);
238     }
239 
240     static ColorModel createComponentCM(ColorSpace colorSpace,
241                                         int numBands,
242                                         int dataType,
243                                         boolean hasAlpha,
244                                         boolean isAlphaPremultiplied) {
245         int transparency =
246             hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE;
247 
248         int[] numBits = new int[numBands];
249         int bits = DataBuffer.getDataTypeSize(dataType);
250 
251         for (int i = 0; i < numBands; i++) {
252             numBits[i] = bits;
253         }
254 
255         return new ComponentColorModel(colorSpace,
256                                        numBits,
257                                        hasAlpha,
258                                        isAlphaPremultiplied,
259                                        transparency,
260                                        dataType);
261     }
262 
263     // Interleaved
264 
265     static class Interleaved extends ImageTypeSpecifier {
266         ColorSpace colorSpace;
267         int[] bandOffsets;
268         int dataType;
269         boolean hasAlpha;
270         boolean isAlphaPremultiplied;
271 
272         public Interleaved(ColorSpace colorSpace,
273                            int[] bandOffsets,
274                            int dataType,
275                            boolean hasAlpha,
276                            boolean isAlphaPremultiplied) {
277             if (colorSpace == null) {
278                 throw new IllegalArgumentException("colorSpace == null!");
279             }
280             if (bandOffsets == null) {
281                 throw new IllegalArgumentException("bandOffsets == null!");
282             }
283             int numBands = colorSpace.getNumComponents() +
284                 (hasAlpha ? 1 : 0);
285             if (bandOffsets.length != numBands) {
286                 throw new IllegalArgumentException
287                     ("bandOffsets.length is wrong!");
288             }
289             if (dataType != DataBuffer.TYPE_BYTE &&
290                 dataType != DataBuffer.TYPE_SHORT &&
291                 dataType != DataBuffer.TYPE_USHORT &&
292                 dataType != DataBuffer.TYPE_INT &&
293                 dataType != DataBuffer.TYPE_FLOAT &&
294                 dataType != DataBuffer.TYPE_DOUBLE) {
295                 throw new IllegalArgumentException
296                     ("Bad value for dataType!");
297             }
298             this.colorSpace = colorSpace;
299             this.bandOffsets = (int[])bandOffsets.clone();
300             this.dataType = dataType;
301             this.hasAlpha = hasAlpha;
302             this.isAlphaPremultiplied = isAlphaPremultiplied;
303 
304             this.colorModel =
305                 ImageTypeSpecifier.createComponentCM(colorSpace,
306                                                      bandOffsets.length,
307                                                      dataType,
308                                                      hasAlpha,
309                                                      isAlphaPremultiplied);
310 
311             int minBandOffset = bandOffsets[0];
312             int maxBandOffset = minBandOffset;
313             for (int i = 0; i < bandOffsets.length; i++) {
314                 int offset = bandOffsets[i];
315                 minBandOffset = Math.min(offset, minBandOffset);
316                 maxBandOffset = Math.max(offset, maxBandOffset);
317             }
318             int pixelStride = maxBandOffset - minBandOffset + 1;
319 
320             int w = 1;
321             int h = 1;
322             this.sampleModel =
323                 new PixelInterleavedSampleModel(dataType,
324                                                 w, h,
325                                                 pixelStride,
326                                                 w*pixelStride,
327                                                 bandOffsets);
328         }
329 
330         public boolean equals(Object o) {
331             if ((o == null) ||
332                 !(o instanceof ImageTypeSpecifier.Interleaved)) {
333                 return false;
334             }
335 
336             ImageTypeSpecifier.Interleaved that =
337                 (ImageTypeSpecifier.Interleaved)o;
338 
339             if ((!(this.colorSpace.equals(that.colorSpace))) ||
340                 (this.dataType != that.dataType) ||
341                 (this.hasAlpha != that.hasAlpha) ||
342                 (this.isAlphaPremultiplied != that.isAlphaPremultiplied) ||
343                 (this.bandOffsets.length != that.bandOffsets.length)) {
344                 return false;
345             }
346 
347             for (int i = 0; i < bandOffsets.length; i++) {
348                 if (this.bandOffsets[i] != that.bandOffsets[i]) {
349                     return false;
350                 }
351             }
352 
353             return true;
354         }
355 
356         public int hashCode() {
357             return (super.hashCode() +
358                     (4 * bandOffsets.length) +
359                     (25 * dataType) +
360                     (hasAlpha ? 17 : 18));
361         }
362     }
363 
364     /**
365      * Returns a specifier for an interleaved image format that will
366      * use a <code>ComponentColorModel</code> and a
367      * <code>PixelInterleavedSampleModel</code> to store each pixel
368      * component in a separate byte, short, or int.
369      *
370      * @param colorSpace the desired <code>ColorSpace</code>.
371      * @param bandOffsets an array of <code>int</code>s indicating the
372      * offsets for each band.
373      * @param dataType the desired data type, as one of the enumerations
374      * from the <code>DataBuffer</code> class.
375      * @param hasAlpha <code>true</code> if an alpha channel is desired.
376      * @param isAlphaPremultiplied <code>true</code> if the color channels
377      * will be premultipled by the alpha channel.
378      *
379      * @return an <code>ImageTypeSpecifier</code> with the desired
380      * characteristics.
381      *
382      * @exception IllegalArgumentException if <code>colorSpace</code>
383      * is <code>null</code>.
384      * @exception IllegalArgumentException if <code>bandOffsets</code>
385      * is <code>null</code>.
386      * @exception IllegalArgumentException if <code>dataType</code> is
387      * not one of the legal <code>DataBuffer.TYPE_*</code> constants.
388      * @exception IllegalArgumentException if
389      * <code>bandOffsets.length</code> does not equal the number of
390      * color space components, plus 1 if <code>hasAlpha</code> is
391      * <code>true</code>.
392      */
393     public static ImageTypeSpecifier
394         createInterleaved(ColorSpace colorSpace,
395                           int[] bandOffsets,
396                           int dataType,
397                           boolean hasAlpha,
398                           boolean isAlphaPremultiplied) {
399         return new ImageTypeSpecifier.Interleaved(colorSpace,
400                                                   bandOffsets,
401                                                   dataType,
402                                                   hasAlpha,
403                                                   isAlphaPremultiplied);
404     }
405 
406     // Banded
407 
408     static class Banded extends ImageTypeSpecifier {
409         ColorSpace colorSpace;
410         int[] bankIndices;
411         int[] bandOffsets;
412         int dataType;
413         boolean hasAlpha;
414         boolean isAlphaPremultiplied;
415 
416         public Banded(ColorSpace colorSpace,
417                       int[] bankIndices,
418                       int[] bandOffsets,
419                       int dataType,
420                       boolean hasAlpha,
421                       boolean isAlphaPremultiplied) {
422             if (colorSpace == null) {
423                 throw new IllegalArgumentException("colorSpace == null!");
424             }
425             if (bankIndices == null) {
426                 throw new IllegalArgumentException("bankIndices == null!");
427             }
428             if (bandOffsets == null) {
429                 throw new IllegalArgumentException("bandOffsets == null!");
430             }
431             if (bankIndices.length != bandOffsets.length) {
432                 throw new IllegalArgumentException
433                     ("bankIndices.length != bandOffsets.length!");
434             }
435             if (dataType != DataBuffer.TYPE_BYTE &&
436                 dataType != DataBuffer.TYPE_SHORT &&
437                 dataType != DataBuffer.TYPE_USHORT &&
438                 dataType != DataBuffer.TYPE_INT &&
439                 dataType != DataBuffer.TYPE_FLOAT &&
440                 dataType != DataBuffer.TYPE_DOUBLE) {
441                 throw new IllegalArgumentException
442                     ("Bad value for dataType!");
443             }
444             int numBands = colorSpace.getNumComponents() +
445                 (hasAlpha ? 1 : 0);
446             if (bandOffsets.length != numBands) {
447                 throw new IllegalArgumentException
448                     ("bandOffsets.length is wrong!");
449             }
450 
451             this.colorSpace = colorSpace;
452             this.bankIndices = (int[])bankIndices.clone();
453             this.bandOffsets = (int[])bandOffsets.clone();
454             this.dataType = dataType;
455             this.hasAlpha = hasAlpha;
456             this.isAlphaPremultiplied = isAlphaPremultiplied;
457 
458             this.colorModel =
459                 ImageTypeSpecifier.createComponentCM(colorSpace,
460                                                      bankIndices.length,
461                                                      dataType,
462                                                      hasAlpha,
463                                                      isAlphaPremultiplied);
464 
465             int w = 1;
466             int h = 1;
467             this.sampleModel = new BandedSampleModel(dataType,
468                                                      w, h,
469                                                      w,
470                                                      bankIndices,
471                                                      bandOffsets);
472         }
473 
474         public boolean equals(Object o) {
475             if ((o == null) ||
476                 !(o instanceof ImageTypeSpecifier.Banded)) {
477                 return false;
478             }
479 
480             ImageTypeSpecifier.Banded that =
481                 (ImageTypeSpecifier.Banded)o;
482 
483             if ((!(this.colorSpace.equals(that.colorSpace))) ||
484                 (this.dataType != that.dataType) ||
485                 (this.hasAlpha != that.hasAlpha) ||
486                 (this.isAlphaPremultiplied != that.isAlphaPremultiplied) ||
487                 (this.bankIndices.length != that.bankIndices.length) ||
488                 (this.bandOffsets.length != that.bandOffsets.length)) {
489                 return false;
490             }
491 
492             for (int i = 0; i < bankIndices.length; i++) {
493                 if (this.bankIndices[i] != that.bankIndices[i]) {
494                     return false;
495                 }
496             }
497 
498             for (int i = 0; i < bandOffsets.length; i++) {
499                 if (this.bandOffsets[i] != that.bandOffsets[i]) {
500                     return false;
501                 }
502             }
503 
504             return true;
505         }
506 
507         public int hashCode() {
508             return (super.hashCode() +
509                     (3 * bandOffsets.length) +
510                     (7 * bankIndices.length) +
511                     (21 * dataType) +
512                     (hasAlpha ? 19 : 29));
513         }
514     }
515 
516     /**
517      * Returns a specifier for a banded image format that will use a
518      * <code>ComponentColorModel</code> and a
519      * <code>BandedSampleModel</code> to store each channel in a
520      * separate array.
521      *
522      * @param colorSpace the desired <code>ColorSpace</code>.
523      * @param bankIndices an array of <code>int</code>s indicating the
524      * bank in which each band will be stored.
525      * @param bandOffsets an array of <code>int</code>s indicating the
526      * starting offset of each band within its bank.
527      * @param dataType the desired data type, as one of the enumerations
528      * from the <code>DataBuffer</code> class.
529      * @param hasAlpha <code>true</code> if an alpha channel is desired.
530      * @param isAlphaPremultiplied <code>true</code> if the color channels
531      * will be premultipled by the alpha channel.
532      *
533      * @return an <code>ImageTypeSpecifier</code> with the desired
534      * characteristics.
535      *
536      * @exception IllegalArgumentException if <code>colorSpace</code>
537      * is <code>null</code>.
538      * @exception IllegalArgumentException if <code>bankIndices</code>
539      * is <code>null</code>.
540      * @exception IllegalArgumentException if <code>bandOffsets</code>
541      * is <code>null</code>.
542      * @exception IllegalArgumentException if the lengths of
543      * <code>bankIndices</code> and <code>bandOffsets</code> differ.
544      * @exception IllegalArgumentException if
545      * <code>bandOffsets.length</code> does not equal the number of
546      * color space components, plus 1 if <code>hasAlpha</code> is
547      * <code>true</code>.
548      * @exception IllegalArgumentException if <code>dataType</code> is
549      * not one of the legal <code>DataBuffer.TYPE_*</code> constants.
550      */
551     public static ImageTypeSpecifier
552         createBanded(ColorSpace colorSpace,
553                      int[] bankIndices,
554                      int[] bandOffsets,
555                      int dataType,
556                      boolean hasAlpha,
557                      boolean isAlphaPremultiplied) {
558         return new ImageTypeSpecifier.Banded(colorSpace,
559                                              bankIndices,
560                                              bandOffsets,
561                                              dataType,
562                                              hasAlpha,
563                                              isAlphaPremultiplied);
564     }
565 
566     // Grayscale
567 
568     static class Grayscale extends ImageTypeSpecifier {
569         int bits;
570         int dataType;
571         boolean isSigned;
572         boolean hasAlpha;
573         boolean isAlphaPremultiplied;
574 
575         public Grayscale(int bits,
576                          int dataType,
577                          boolean isSigned,
578                          boolean hasAlpha,
579                          boolean isAlphaPremultiplied)
580         {
581             if (bits != 1 && bits != 2 && bits != 4 &&
582                 bits != 8 && bits != 16)
583             {
584                 throw new IllegalArgumentException("Bad value for bits!");
585             }
586             if (dataType != DataBuffer.TYPE_BYTE &&
587                 dataType != DataBuffer.TYPE_SHORT &&
588                 dataType != DataBuffer.TYPE_USHORT)
589             {
590                 throw new IllegalArgumentException
591                     ("Bad value for dataType!");
592             }
593             if (bits > 8 && dataType == DataBuffer.TYPE_BYTE) {
594                 throw new IllegalArgumentException
595                     ("Too many bits for dataType!");
596             }
597 
598             this.bits = bits;
599             this.dataType = dataType;
600             this.isSigned = isSigned;
601             this.hasAlpha = hasAlpha;
602             this.isAlphaPremultiplied = isAlphaPremultiplied;
603 
604             ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
605 
606             if ((bits == 8 && dataType == DataBuffer.TYPE_BYTE) ||
607                 (bits == 16 &&
608                  (dataType == DataBuffer.TYPE_SHORT ||
609                   dataType == DataBuffer.TYPE_USHORT))) {
610                 // Use component color model & sample model
611 
612                 int numBands = hasAlpha ? 2 : 1;
613                 int transparency =
614                     hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE;
615 
616 
617                 int[] nBits = new int[numBands];
618                 nBits[0] = bits;
619                 if (numBands == 2) {
620                     nBits[1] = bits;
621                 }
622                 this.colorModel =
623                     new ComponentColorModel(colorSpace,
624                                             nBits,
625                                             hasAlpha,
626                                             isAlphaPremultiplied,
627                                             transparency,
628                                             dataType);
629 
630                 int[] bandOffsets = new int[numBands];
631                 bandOffsets[0] = 0;
632                 if (numBands == 2) {
633                     bandOffsets[1] = 1;
634                 }
635 
636                 int w = 1;
637                 int h = 1;
638                 this.sampleModel =
639                     new PixelInterleavedSampleModel(dataType,
640                                                     w, h,
641                                                     numBands, w*numBands,
642                                                     bandOffsets);
643             } else {
644                 int numEntries = 1 << bits;
645                 byte[] arr = new byte[numEntries];
646                 for (int i = 0; i < numEntries; i++) {
647                     arr[i] = (byte)(i*255/(numEntries - 1));
648                 }
649                 this.colorModel =
650                     new IndexColorModel(bits, numEntries, arr, arr, arr);
651 
652                 this.sampleModel =
653                     new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
654             }
655         }
656     }
657 
658     /**
659      * Returns a specifier for a grayscale image format that will pack
660      * pixels of the given bit depth into array elements of
661      * the specified data type.
662      *
663      * @param bits the number of bits per gray value (1, 2, 4, 8, or 16).
664      * @param dataType the desired data type, as one of the enumerations
665      * from the <code>DataBuffer</code> class.
666      * @param isSigned <code>true</code> if negative values are to
667      * be represented.
668      *
669      * @return an <code>ImageTypeSpecifier</code> with the desired
670      * characteristics.
671      *
672      * @exception IllegalArgumentException if <code>bits</code> is
673      * not one of 1, 2, 4, 8, or 16.
674      * @exception IllegalArgumentException if <code>dataType</code> is
675      * not one of <code>DataBuffer.TYPE_BYTE</code>,
676      * <code>DataBuffer.TYPE_SHORT</code>, or
677      * <code>DataBuffer.TYPE_USHORT</code>.
678      * @exception IllegalArgumentException if <code>bits</code> is
679      * larger than the bit size of the given <code>dataType</code>.
680      */
681     public static ImageTypeSpecifier
682         createGrayscale(int bits,
683                         int dataType,
684                         boolean isSigned) {
685         return new ImageTypeSpecifier.Grayscale(bits,
686                                                 dataType,
687                                                 isSigned,
688                                                 false,
689                                                 false);
690     }
691 
692     /**
693      * Returns a specifier for a grayscale plus alpha image format
694      * that will pack pixels of the given bit depth into array
695      * elements of the specified data type.
696      *
697      * @param bits the number of bits per gray value (1, 2, 4, 8, or 16).
698      * @param dataType the desired data type, as one of the enumerations
699      * from the <code>DataBuffer</code> class.
700      * @param isSigned <code>true</code> if negative values are to
701      * be represented.
702      * @param isAlphaPremultiplied <code>true</code> if the luminance channel
703      * will be premultipled by the alpha channel.
704      *
705      * @return an <code>ImageTypeSpecifier</code> with the desired
706      * characteristics.
707      *
708      * @exception IllegalArgumentException if <code>bits</code> is
709      * not one of 1, 2, 4, 8, or 16.
710      * @exception IllegalArgumentException if <code>dataType</code> is
711      * not one of <code>DataBuffer.TYPE_BYTE</code>,
712      * <code>DataBuffer.TYPE_SHORT</code>, or
713      * <code>DataBuffer.TYPE_USHORT</code>.
714      * @exception IllegalArgumentException if <code>bits</code> is
715      * larger than the bit size of the given <code>dataType</code>.
716      */
717     public static ImageTypeSpecifier
718         createGrayscale(int bits,
719                         int dataType,
720                         boolean isSigned,
721                         boolean isAlphaPremultiplied) {
722         return new ImageTypeSpecifier.Grayscale(bits,
723                                                 dataType,
724                                                 isSigned,
725                                                 true,
726                                                 isAlphaPremultiplied);
727     }
728 
729     // Indexed
730 
731     static class Indexed extends ImageTypeSpecifier {
732         byte[] redLUT;
733         byte[] greenLUT;
734         byte[] blueLUT;
735         byte[] alphaLUT = null;
736         int bits;
737         int dataType;
738 
739         public Indexed(byte[] redLUT,
740                        byte[] greenLUT,
741                        byte[] blueLUT,
742                        byte[] alphaLUT,
743                        int bits,
744                        int dataType) {
745             if (redLUT == null || greenLUT == null || blueLUT == null) {
746                 throw new IllegalArgumentException("LUT is null!");
747             }
748             if (bits != 1 && bits != 2 && bits != 4 &&
749                 bits != 8 && bits != 16) {
750                 throw new IllegalArgumentException("Bad value for bits!");
751             }
752             if (dataType != DataBuffer.TYPE_BYTE &&
753                 dataType != DataBuffer.TYPE_SHORT &&
754                 dataType != DataBuffer.TYPE_USHORT &&
755                 dataType != DataBuffer.TYPE_INT) {
756                 throw new IllegalArgumentException
757                     ("Bad value for dataType!");
758             }
759             if ((bits > 8 && dataType == DataBuffer.TYPE_BYTE) ||
760                 (bits > 16 && dataType != DataBuffer.TYPE_INT)) {
761                 throw new IllegalArgumentException
762                     ("Too many bits for dataType!");
763             }
764 
765             int len = 1 << bits;
766             if (redLUT.length != len ||
767                 greenLUT.length != len ||
768                 blueLUT.length != len ||
769                 (alphaLUT != null && alphaLUT.length != len)) {
770                 throw new IllegalArgumentException("LUT has improper length!");
771             }
772             this.redLUT = (byte[])redLUT.clone();
773             this.greenLUT = (byte[])greenLUT.clone();
774             this.blueLUT = (byte[])blueLUT.clone();
775             if (alphaLUT != null) {
776                 this.alphaLUT = (byte[])alphaLUT.clone();
777             }
778             this.bits = bits;
779             this.dataType = dataType;
780 
781             if (alphaLUT == null) {
782                 this.colorModel = new IndexColorModel(bits,
783                                                       redLUT.length,
784                                                       redLUT,
785                                                       greenLUT,
786                                                       blueLUT);
787             } else {
788                 this.colorModel = new IndexColorModel(bits,
789                                                       redLUT.length,
790                                                       redLUT,
791                                                       greenLUT,
792                                                       blueLUT,
793                                                       alphaLUT);
794             }
795 
796             if ((bits == 8 && dataType == DataBuffer.TYPE_BYTE) ||
797                 (bits == 16 &&
798                  (dataType == DataBuffer.TYPE_SHORT ||
799                   dataType == DataBuffer.TYPE_USHORT))) {
800                 int[] bandOffsets = { 0 };
801                 this.sampleModel =
802                     new PixelInterleavedSampleModel(dataType,
803                                                     1, 1, 1, 1,
804                                                     bandOffsets);
805             } else {
806                 this.sampleModel =
807                     new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
808             }
809         }
810     }
811 
812     /**
813      * Returns a specifier for an indexed-color image format that will pack
814      * index values of the given bit depth into array elements of
815      * the specified data type.
816      *
817      * @param redLUT an array of <code>byte</code>s containing
818      * the red values for each index.
819      * @param greenLUT an array of <code>byte</code>s containing * the
820      *  green values for each index.
821      * @param blueLUT an array of <code>byte</code>s containing the
822      * blue values for each index.
823      * @param alphaLUT an array of <code>byte</code>s containing the
824      * alpha values for each index, or <code>null</code> to create a
825      * fully opaque LUT.
826      * @param bits the number of bits in each index.
827      * @param dataType the desired output type, as one of the enumerations
828      * from the <code>DataBuffer</code> class.
829      *
830      * @return an <code>ImageTypeSpecifier</code> with the desired
831      * characteristics.
832      *
833      * @exception IllegalArgumentException if <code>redLUT</code> is
834      * <code>null</code>.
835      * @exception IllegalArgumentException if <code>greenLUT</code> is
836      * <code>null</code>.
837      * @exception IllegalArgumentException if <code>blueLUT</code> is
838      * <code>null</code>.
839      * @exception IllegalArgumentException if <code>bits</code> is
840      * not one of 1, 2, 4, 8, or 16.
841      * @exception IllegalArgumentException if the
842      * non-<code>null</code> LUT parameters do not have lengths of
843      * exactly {@code 1 << bits}.
844      * @exception IllegalArgumentException if <code>dataType</code> is
845      * not one of <code>DataBuffer.TYPE_BYTE</code>,
846      * <code>DataBuffer.TYPE_SHORT</code>,
847      * <code>DataBuffer.TYPE_USHORT</code>,
848      * or <code>DataBuffer.TYPE_INT</code>.
849      * @exception IllegalArgumentException if <code>bits</code> is
850      * larger than the bit size of the given <code>dataType</code>.
851      */
852     public static ImageTypeSpecifier
853         createIndexed(byte[] redLUT,
854                       byte[] greenLUT,
855                       byte[] blueLUT,
856                       byte[] alphaLUT,
857                       int bits,
858                       int dataType) {
859         return new ImageTypeSpecifier.Indexed(redLUT,
860                                               greenLUT,
861                                               blueLUT,
862                                               alphaLUT,
863                                               bits,
864                                               dataType);
865     }
866 
867     /**
868      * Returns an <code>ImageTypeSpecifier</code> that encodes
869      * one of the standard <code>BufferedImage</code> types
870      * (other than <code>TYPE_CUSTOM</code>).
871      *
872      * @param bufferedImageType an int representing one of the standard
873      * <code>BufferedImage</code> types.
874      *
875      * @return an <code>ImageTypeSpecifier</code> with the desired
876      * characteristics.
877      *
878      * @exception IllegalArgumentException if
879      * <code>bufferedImageType</code> is not one of the standard
880      * types, or is equal to <code>TYPE_CUSTOM</code>.
881      *
882      * @see java.awt.image.BufferedImage
883      * @see java.awt.image.BufferedImage#TYPE_INT_RGB
884      * @see java.awt.image.BufferedImage#TYPE_INT_ARGB
885      * @see java.awt.image.BufferedImage#TYPE_INT_ARGB_PRE
886      * @see java.awt.image.BufferedImage#TYPE_INT_BGR
887      * @see java.awt.image.BufferedImage#TYPE_3BYTE_BGR
888      * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR
889      * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR_PRE
890      * @see java.awt.image.BufferedImage#TYPE_USHORT_565_RGB
891      * @see java.awt.image.BufferedImage#TYPE_USHORT_555_RGB
892      * @see java.awt.image.BufferedImage#TYPE_BYTE_GRAY
893      * @see java.awt.image.BufferedImage#TYPE_USHORT_GRAY
894      * @see java.awt.image.BufferedImage#TYPE_BYTE_BINARY
895      * @see java.awt.image.BufferedImage#TYPE_BYTE_INDEXED
896      */
897     public static
898         ImageTypeSpecifier createFromBufferedImageType(int bufferedImageType) {
899         if (bufferedImageType >= BufferedImage.TYPE_INT_RGB &&
900             bufferedImageType <= BufferedImage.TYPE_BYTE_INDEXED) {
901             return getSpecifier(bufferedImageType);
902         } else if (bufferedImageType == BufferedImage.TYPE_CUSTOM) {
903             throw new IllegalArgumentException("Cannot create from TYPE_CUSTOM!");
904         } else {
905             throw new IllegalArgumentException("Invalid BufferedImage type!");
906         }
907     }
908 
909     /**
910      * Returns an <code>ImageTypeSpecifier</code> that encodes the
911      * layout of a <code>RenderedImage</code> (which may be a
912      * <code>BufferedImage</code>).
913      *
914      * @param image a <code>RenderedImage</code>.
915      *
916      * @return an <code>ImageTypeSpecifier</code> with the desired
917      * characteristics.
918      *
919      * @exception IllegalArgumentException if <code>image</code> is
920      * <code>null</code>.
921      */
922     public static
923         ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
924         if (image == null) {
925             throw new IllegalArgumentException("image == null!");
926         }
927 
928         if (image instanceof BufferedImage) {
929             int bufferedImageType = ((BufferedImage)image).getType();
930             if (bufferedImageType != BufferedImage.TYPE_CUSTOM) {
931                 return getSpecifier(bufferedImageType);
932             }
933         }
934 
935         return new ImageTypeSpecifier(image);
936     }
937 
938     /**
939      * Returns an int containing one of the enumerated constant values
940      * describing image formats from <code>BufferedImage</code>.
941      *
942      * @return an <code>int</code> representing a
943      * <code>BufferedImage</code> type.
944      *
945      * @see java.awt.image.BufferedImage
946      * @see java.awt.image.BufferedImage#TYPE_CUSTOM
947      * @see java.awt.image.BufferedImage#TYPE_INT_RGB
948      * @see java.awt.image.BufferedImage#TYPE_INT_ARGB
949      * @see java.awt.image.BufferedImage#TYPE_INT_ARGB_PRE
950      * @see java.awt.image.BufferedImage#TYPE_INT_BGR
951      * @see java.awt.image.BufferedImage#TYPE_3BYTE_BGR
952      * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR
953      * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR_PRE
954      * @see java.awt.image.BufferedImage#TYPE_USHORT_565_RGB
955      * @see java.awt.image.BufferedImage#TYPE_USHORT_555_RGB
956      * @see java.awt.image.BufferedImage#TYPE_BYTE_GRAY
957      * @see java.awt.image.BufferedImage#TYPE_USHORT_GRAY
958      * @see java.awt.image.BufferedImage#TYPE_BYTE_BINARY
959      * @see java.awt.image.BufferedImage#TYPE_BYTE_INDEXED
960      */
961     public int getBufferedImageType() {
962         BufferedImage bi = createBufferedImage(1, 1);
963         return bi.getType();
964     }
965 
966     /**
967      * Return the number of color components
968      * specified by this object.  This is the same value as returned by
969      * <code>ColorModel.getNumComponents</code>
970      *
971      * @return the number of components in the image.
972      */
973     public int getNumComponents() {
974         return colorModel.getNumComponents();
975     }
976 
977     /**
978      * Return the number of bands
979      * specified by this object.  This is the same value as returned by
980      * <code>SampleModel.getNumBands</code>
981      *
982      * @return the number of bands in the image.
983      */
984     public int getNumBands() {
985         return sampleModel.getNumBands();
986     }
987 
988     /**
989      * Return the number of bits used to represent samples of the given band.
990      *
991      * @param band the index of the band to be queried, as an
992      * int.
993      *
994      * @return an int specifying a number of bits.
995      *
996      * @exception IllegalArgumentException if <code>band</code> is
997      * negative or greater than the largest band index.
998      */
999     public int getBitsPerBand(int band) {
1000         if (band < 0 | band >= getNumBands()) {
1001             throw new IllegalArgumentException("band out of range!");
1002         }
1003         return sampleModel.getSampleSize(band);
1004     }
1005 
1006     /**
1007      * Returns a <code>SampleModel</code> based on the settings
1008      * encapsulated within this object.  The width and height of the
1009      * <code>SampleModel</code> will be set to arbitrary values.
1010      *
1011      * @return a <code>SampleModel</code> with arbitrary dimensions.
1012      */
1013     public SampleModel getSampleModel() {
1014         return sampleModel;
1015     }
1016 
1017     /**
1018      * Returns a <code>SampleModel</code> based on the settings
1019      * encapsulated within this object.  The width and height of the
1020      * <code>SampleModel</code> will be set to the supplied values.
1021      *
1022      * @param width the desired width of the returned <code>SampleModel</code>.
1023      * @param height the desired height of the returned
1024      * <code>SampleModel</code>.
1025      *
1026      * @return a <code>SampleModel</code> with the given dimensions.
1027      *
1028      * @exception IllegalArgumentException if either <code>width</code> or
1029      * <code>height</code> are negative or zero.
1030      * @exception IllegalArgumentException if the product of
1031      * <code>width</code> and <code>height</code> is greater than
1032      * <code>Integer.MAX_VALUE</code>
1033      */
1034     public SampleModel getSampleModel(int width, int height) {
1035         if ((long)width*height > Integer.MAX_VALUE) {
1036             throw new IllegalArgumentException
1037                 ("width*height > Integer.MAX_VALUE!");
1038         }
1039         return sampleModel.createCompatibleSampleModel(width, height);
1040     }
1041 
1042     /**
1043      * Returns the <code>ColorModel</code> specified by this object.
1044      *
1045      * @return a <code>ColorModel</code>.
1046      */
1047     public ColorModel getColorModel() {
1048         return colorModel;
1049     }
1050 
1051     /**
1052      * Creates a <code>BufferedImage</code> with a given width and
1053      * height according to the specification embodied in this object.
1054      *
1055      * @param width the desired width of the returned
1056      * <code>BufferedImage</code>.
1057      * @param height the desired height of the returned
1058      * <code>BufferedImage</code>.
1059      *
1060      * @return a new <code>BufferedImage</code>
1061      *
1062      * @exception IllegalArgumentException if either <code>width</code> or
1063      * <code>height</code> are negative or zero.
1064      * @exception IllegalArgumentException if the product of
1065      * <code>width</code> and <code>height</code> is greater than
1066      * <code>Integer.MAX_VALUE</code>, or if the number of array
1067      * elements needed to store the image is greater than
1068      * <code>Integer.MAX_VALUE</code>.
1069      */
1070     public BufferedImage createBufferedImage(int width, int height) {
1071         try {
1072             SampleModel sampleModel = getSampleModel(width, height);
1073             WritableRaster raster =
1074                 Raster.createWritableRaster(sampleModel,
1075                                             new Point(0, 0));
1076             return new BufferedImage(colorModel, raster,
1077                                      colorModel.isAlphaPremultiplied(),
1078                                      new Hashtable());
1079         } catch (NegativeArraySizeException e) {
1080             // Exception most likely thrown from a DataBuffer constructor
1081             throw new IllegalArgumentException
1082                 ("Array size > Integer.MAX_VALUE!");
1083         }
1084     }
1085 
1086     /**
1087      * Returns <code>true</code> if the given <code>Object</code> is
1088      * an <code>ImageTypeSpecifier</code> and has a
1089      * <code>SampleModel</code> and <code>ColorModel</code> that are
1090      * equal to those of this object.
1091      *
1092      * @param o the <code>Object</code> to be compared for equality.
1093      *
1094      * @return <code>true</code> if the given object is an equivalent
1095      * <code>ImageTypeSpecifier</code>.
1096      */
1097     public boolean equals(Object o) {
1098         if ((o == null) || !(o instanceof ImageTypeSpecifier)) {
1099             return false;
1100         }
1101 
1102         ImageTypeSpecifier that = (ImageTypeSpecifier)o;
1103         return (colorModel.equals(that.colorModel)) &&
1104             (sampleModel.equals(that.sampleModel));
1105     }
1106 
1107     /**
1108      * Returns the hash code for this ImageTypeSpecifier.
1109      *
1110      * @return a hash code for this ImageTypeSpecifier
1111      */
1112     public int hashCode() {
1113         return (9 * colorModel.hashCode()) + (14 * sampleModel.hashCode());
1114     }
1115 
1116     private static ImageTypeSpecifier getSpecifier(int type) {
1117         if (BISpecifier[type] == null) {
1118             BISpecifier[type] = createSpecifier(type);
1119         }
1120         return BISpecifier[type];
1121     }
1122 
1123     private static ImageTypeSpecifier createSpecifier(int type) {
1124         switch(type) {
1125           case BufferedImage.TYPE_INT_RGB:
1126               return createPacked(sRGB,
1127                                   0x00ff0000,
1128                                   0x0000ff00,
1129                                   0x000000ff,
1130                                   0x0,
1131                                   DataBuffer.TYPE_INT,
1132                                   false);
1133 
1134           case BufferedImage.TYPE_INT_ARGB:
1135               return createPacked(sRGB,
1136                                   0x00ff0000,
1137                                   0x0000ff00,
1138                                   0x000000ff,
1139                                   0xff000000,
1140                                   DataBuffer.TYPE_INT,
1141                                   false);
1142 
1143           case BufferedImage.TYPE_INT_ARGB_PRE:
1144               return createPacked(sRGB,
1145                                   0x00ff0000,
1146                                   0x0000ff00,
1147                                   0x000000ff,
1148                                   0xff000000,
1149                                   DataBuffer.TYPE_INT,
1150                                   true);
1151 
1152           case BufferedImage.TYPE_INT_BGR:
1153               return createPacked(sRGB,
1154                                   0x000000ff,
1155                                   0x0000ff00,
1156                                   0x00ff0000,
1157                                   0x0,
1158                                   DataBuffer.TYPE_INT,
1159                                   false);
1160 
1161           case BufferedImage.TYPE_3BYTE_BGR:
1162               return createInterleaved(sRGB,
1163                                        new int[] { 2, 1, 0 },
1164                                        DataBuffer.TYPE_BYTE,
1165                                        false,
1166                                        false);
1167 
1168           case BufferedImage.TYPE_4BYTE_ABGR:
1169               return createInterleaved(sRGB,
1170                                        new int[] { 3, 2, 1, 0 },
1171                                        DataBuffer.TYPE_BYTE,
1172                                        true,
1173                                        false);
1174 
1175           case BufferedImage.TYPE_4BYTE_ABGR_PRE:
1176               return createInterleaved(sRGB,
1177                                        new int[] { 3, 2, 1, 0 },
1178                                        DataBuffer.TYPE_BYTE,
1179                                        true,
1180                                        true);
1181 
1182           case BufferedImage.TYPE_USHORT_565_RGB:
1183               return createPacked(sRGB,
1184                                   0xF800,
1185                                   0x07E0,
1186                                   0x001F,
1187                                   0x0,
1188                                   DataBuffer.TYPE_USHORT,
1189                                   false);
1190 
1191           case BufferedImage.TYPE_USHORT_555_RGB:
1192               return createPacked(sRGB,
1193                                   0x7C00,
1194                                   0x03E0,
1195                                   0x001F,
1196                                   0x0,
1197                                   DataBuffer.TYPE_USHORT,
1198                                   false);
1199 
1200           case BufferedImage.TYPE_BYTE_GRAY:
1201             return createGrayscale(8,
1202                                    DataBuffer.TYPE_BYTE,
1203                                    false);
1204 
1205           case BufferedImage.TYPE_USHORT_GRAY:
1206             return createGrayscale(16,
1207                                    DataBuffer.TYPE_USHORT,
1208                                    false);
1209 
1210           case BufferedImage.TYPE_BYTE_BINARY:
1211               return createGrayscale(1,
1212                                      DataBuffer.TYPE_BYTE,
1213                                      false);
1214 
1215           case BufferedImage.TYPE_BYTE_INDEXED:
1216           {
1217 
1218               BufferedImage bi =
1219                   new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED);
1220               IndexColorModel icm = (IndexColorModel)bi.getColorModel();
1221               int mapSize = icm.getMapSize();
1222               byte[] redLUT = new byte[mapSize];
1223               byte[] greenLUT = new byte[mapSize];
1224               byte[] blueLUT = new byte[mapSize];
1225               byte[] alphaLUT = new byte[mapSize];
1226 
1227               icm.getReds(redLUT);
1228               icm.getGreens(greenLUT);
1229               icm.getBlues(blueLUT);
1230               icm.getAlphas(alphaLUT);
1231 
1232               return createIndexed(redLUT, greenLUT, blueLUT, alphaLUT,
1233                                    8,
1234                                    DataBuffer.TYPE_BYTE);
1235           }
1236           default:
1237               throw new IllegalArgumentException("Invalid BufferedImage type!");
1238         }
1239     }
1240 
1241 }