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
26package javax.imageio;
27
28import java.awt.Point;
29import java.awt.Transparency;
30import java.awt.image.BandedSampleModel;
31import java.awt.image.BufferedImage;
32import java.awt.image.ColorModel;
33import java.awt.color.ColorSpace;
34import java.awt.image.IndexColorModel;
35import java.awt.image.ComponentColorModel;
36import java.awt.image.DataBuffer;
37import java.awt.image.DirectColorModel;
38import java.awt.image.MultiPixelPackedSampleModel;
39import java.awt.image.PixelInterleavedSampleModel;
40import java.awt.image.SinglePixelPackedSampleModel;
41import java.awt.image.Raster;
42import java.awt.image.RenderedImage;
43import java.awt.image.SampleModel;
44import java.awt.image.WritableRaster;
45import 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 */
53public 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 byte, short, 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 true} if 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 byte, short, 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 true} if an alpha channel is desired.
376     * @param isAlphaPremultiplied {@code true} if 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 true} if an alpha channel is desired.
530     * @param isAlphaPremultiplied {@code true} if 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 true} if 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 true} if negative values are to
701     * be represented.
702     * @param isAlphaPremultiplied {@code true} if 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 true} if 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 true} if 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