1 /*
2  * Copyright (c) 2000, 2014, 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} and {@code ColorModel}) to be
50  * specified in a convenient manner.
51  *
52  */

53 public class ImageTypeSpecifier {
54
55     /**
56      * The {@code ColorModel} to be used as a prototype.
57      */

58     protected ColorModel colorModel;
59
60     /**
61      * A {@code SampleModel} to be used as a prototype.
62      */

63     protected SampleModel sampleModel;
64
65     /**
66      * Cached specifiers for all of the standard
67      * {@code BufferedImage} 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} directly
86      * from a {@code ColorModel} and a {@code SampleModel}.
87      * It is the caller's responsibility to supply compatible
88      * parameters.
89      *
90      * @param colorModel a {@code ColorModel}.
91      * @param sampleModel a {@code SampleModel}.
92      *
93      * @exception IllegalArgumentException if either parameter is
94      * {@code null}.
95      * @exception IllegalArgumentException if {@code sampleModel}
96      * is not compatible with {@code colorModel}.
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} from a
115      * {@code RenderedImage}.  If a {@code BufferedImage} is
116      * being used, one of the factory methods
117      * {@code createFromRenderedImage} or
118      * {@code createFromBufferedImageType} should be used instead in
119      * order to get a more accurate result.
120      *
121      * @param image a {@code RenderedImage}.
122      *
123      * @exception IllegalArgumentException if the argument is
124      * {@code null}.
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} and a packed
192      * {@code SampleModel} to store each pixel packed into in a
193      * single byteshort, or int.
194      *
195      * @param colorSpace the desired {@code ColorSpace}.
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} transfer type.
205      * @param isAlphaPremultiplied {@code trueif the color channels
206      * will be premultipled by the alpha channel.
207      *
208      * @return an {@code ImageTypeSpecifier} with the desired
209      * characteristics.
210      *
211      * @exception IllegalArgumentException if {@code colorSpace}
212      * is {@code null}.
213      * @exception IllegalArgumentException if {@code colorSpace}
214      * is not of type {@code TYPE_RGB}.
215      * @exception IllegalArgumentException if no mask has at least 1
216      * bit set.
217      * @exception IllegalArgumentException if
218      * {@code transferType} if not one of
219      * {@code DataBuffer.TYPE_BYTE},
220      * {@code DataBuffer.TYPE_USHORT}, or
221      * {@code DataBuffer.TYPE_INT}.
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 = 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} and a
367      * {@code PixelInterleavedSampleModel} to store each pixel
368      * component in a separate byteshort, or int.
369      *
370      * @param colorSpace the desired {@code ColorSpace}.
371      * @param bandOffsets an array of {@code int}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} class.
375      * @param hasAlpha {@code trueif an alpha channel is desired.
376      * @param isAlphaPremultiplied {@code trueif the color channels
377      * will be premultipled by the alpha channel.
378      *
379      * @return an {@code ImageTypeSpecifier} with the desired
380      * characteristics.
381      *
382      * @exception IllegalArgumentException if {@code colorSpace}
383      * is {@code null}.
384      * @exception IllegalArgumentException if {@code bandOffsets}
385      * is {@code null}.
386      * @exception IllegalArgumentException if {@code dataType} is
387      * not one of the legal {@code DataBuffer.TYPE_*} constants.
388      * @exception IllegalArgumentException if
389      * {@code bandOffsets.length} does not equal the number of
390      * color space components, plus 1 if {@code hasAlpha} is
391      * {@code true}.
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 = bankIndices.clone();
453             this.bandOffsets = 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} and a
519      * {@code BandedSampleModel} to store each channel in a
520      * separate array.
521      *
522      * @param colorSpace the desired {@code ColorSpace}.
523      * @param bankIndices an array of {@code int}s indicating the
524      * bank in which each band will be stored.
525      * @param bandOffsets an array of {@code int}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} class.
529      * @param hasAlpha {@code trueif an alpha channel is desired.
530      * @param isAlphaPremultiplied {@code trueif the color channels
531      * will be premultipled by the alpha channel.
532      *
533      * @return an {@code ImageTypeSpecifier} with the desired
534      * characteristics.
535      *
536      * @exception IllegalArgumentException if {@code colorSpace}
537      * is {@code null}.
538      * @exception IllegalArgumentException if {@code bankIndices}
539      * is {@code null}.
540      * @exception IllegalArgumentException if {@code bandOffsets}
541      * is {@code null}.
542      * @exception IllegalArgumentException if the lengths of
543      * {@code bankIndices} and {@code bandOffsets} differ.
544      * @exception IllegalArgumentException if
545      * {@code bandOffsets.length} does not equal the number of
546      * color space components, plus 1 if {@code hasAlpha} is
547      * {@code true}.
548      * @exception IllegalArgumentException if {@code dataType} is
549      * not one of the legal {@code DataBuffer.TYPE_*} 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} class.
666      * @param isSigned {@code trueif negative values are to
667      * be represented.
668      *
669      * @return an {@code ImageTypeSpecifier} with the desired
670      * characteristics.
671      *
672      * @exception IllegalArgumentException if {@code bits} is
673      * not one of 1, 2, 4, 8, or 16.
674      * @exception IllegalArgumentException if {@code dataType} is
675      * not one of {@code DataBuffer.TYPE_BYTE},
676      * {@code DataBuffer.TYPE_SHORT}, or
677      * {@code DataBuffer.TYPE_USHORT}.
678      * @exception IllegalArgumentException if {@code bits} is
679      * larger than the bit size of the given {@code dataType}.
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} class.
700      * @param isSigned {@code trueif negative values are to
701      * be represented.
702      * @param isAlphaPremultiplied {@code trueif the luminance channel
703      * will be premultipled by the alpha channel.
704      *
705      * @return an {@code ImageTypeSpecifier} with the desired
706      * characteristics.
707      *
708      * @exception IllegalArgumentException if {@code bits} is
709      * not one of 1, 2, 4, 8, or 16.
710      * @exception IllegalArgumentException if {@code dataType} is
711      * not one of {@code DataBuffer.TYPE_BYTE},
712      * {@code DataBuffer.TYPE_SHORT}, or
713      * {@code DataBuffer.TYPE_USHORT}.
714      * @exception IllegalArgumentException if {@code bits} is
715      * larger than the bit size of the given {@code dataType}.
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 = redLUT.clone();
773             this.greenLUT = greenLUT.clone();
774             this.blueLUT = blueLUT.clone();
775             if (alphaLUT != null) {
776                 this.alphaLUT = 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}s containing
818      * the red values for each index.
819      * @param greenLUT an array of {@code byte}s containing * the
820      *  green values for each index.
821      * @param blueLUT an array of {@code byte}s containing the
822      * blue values for each index.
823      * @param alphaLUT an array of {@code byte}s containing the
824      * alpha values for each index, or {@code null} 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} class.
829      *
830      * @return an {@code ImageTypeSpecifier} with the desired
831      * characteristics.
832      *
833      * @exception IllegalArgumentException if {@code redLUT} is
834      * {@code null}.
835      * @exception IllegalArgumentException if {@code greenLUT} is
836      * {@code null}.
837      * @exception IllegalArgumentException if {@code blueLUT} is
838      * {@code null}.
839      * @exception IllegalArgumentException if {@code bits} is
840      * not one of 1, 2, 4, 8, or 16.
841      * @exception IllegalArgumentException if the
842      * non-{@code null} LUT parameters do not have lengths of
843      * exactly {@code 1 << bits}.
844      * @exception IllegalArgumentException if {@code dataType} is
845      * not one of {@code DataBuffer.TYPE_BYTE},
846      * {@code DataBuffer.TYPE_SHORT},
847      * {@code DataBuffer.TYPE_USHORT},
848      * or {@code DataBuffer.TYPE_INT}.
849      * @exception IllegalArgumentException if {@code bits} is
850      * larger than the bit size of the given {@code dataType}.
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} that encodes
869      * one of the standard {@code BufferedImage} types
870      * (other than {@code TYPE_CUSTOM}).
871      *
872      * @param bufferedImageType an int representing one of the standard
873      * {@code BufferedImage} types.
874      *
875      * @return an {@code ImageTypeSpecifier} with the desired
876      * characteristics.
877      *
878      * @exception IllegalArgumentException if
879      * {@code bufferedImageType} is not one of the standard
880      * types, or is equal to {@code TYPE_CUSTOM}.
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} that encodes the
911      * layout of a {@code RenderedImage} (which may be a
912      * {@code BufferedImage}).
913      *
914      * @param image a {@code RenderedImage}.
915      *
916      * @return an {@code ImageTypeSpecifier} with the desired
917      * characteristics.
918      *
919      * @exception IllegalArgumentException if {@code image} is
920      * {@code null}.
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}.
941      *
942      * @return an {@code int} representing a
943      * {@code BufferedImage} 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}
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}
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} 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} based on the settings
1008      * encapsulated within this object.  The width and height of the
1009      * {@code SampleModel} will be set to arbitrary values.
1010      *
1011      * @return a {@code SampleModel} with arbitrary dimensions.
1012      */

1013     public SampleModel getSampleModel() {
1014         return sampleModel;
1015     }
1016
1017     /**
1018      * Returns a {@code SampleModel} based on the settings
1019      * encapsulated within this object.  The width and height of the
1020      * {@code SampleModel} will be set to the supplied values.
1021      *
1022      * @param width the desired width of the returned {@code SampleModel}.
1023      * @param height the desired height of the returned
1024      * {@code SampleModel}.
1025      *
1026      * @return a {@code SampleModel} with the given dimensions.
1027      *
1028      * @exception IllegalArgumentException if either {@code width} or
1029      * {@code height} are negative or zero.
1030      * @exception IllegalArgumentException if the product of
1031      * {@code width} and {@code height} is greater than
1032      * {@code Integer.MAX_VALUE}
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} specified by this object.
1044      *
1045      * @return a {@code ColorModel}.
1046      */

1047     public ColorModel getColorModel() {
1048         return colorModel;
1049     }
1050
1051     /**
1052      * Creates a {@code BufferedImage} 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}.
1057      * @param height the desired height of the returned
1058      * {@code BufferedImage}.
1059      *
1060      * @return a new {@code BufferedImage}
1061      *
1062      * @exception IllegalArgumentException if either {@code width} or
1063      * {@code height} are negative or zero.
1064      * @exception IllegalArgumentException if the product of
1065      * {@code width} and {@code height} is greater than
1066      * {@code Integer.MAX_VALUE}, or if the number of array
1067      * elements needed to store the image is greater than
1068      * {@code Integer.MAX_VALUE}.
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 trueif the given {@code Object} is
1088      * an {@code ImageTypeSpecifier} and has a
1089      * {@code SampleModel} and {@code ColorModel} that are
1090      * equal to those of this object.
1091      *
1092      * @param o the {@code Object} to be compared for equality.
1093      *
1094      * @return {@code trueif the given object is an equivalent
1095      * {@code ImageTypeSpecifier}.
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 }
1242