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 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