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 */ ImageTypeSpecifier()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 */ ImageTypeSpecifier(ColorModel colorModel, SampleModel sampleModel)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 */ ImageTypeSpecifier(RenderedImage image)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 Packed(ColorSpace colorSpace, int redMask, int greenMask, int blueMask, int alphaMask, int transferType, boolean isAlphaPremultiplied)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 createPacked(ColorSpace colorSpace, int redMask, int greenMask, int blueMask, int alphaMask, int transferType, boolean isAlphaPremultiplied)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 createComponentCM(ColorSpace colorSpace, int numBands, int dataType, boolean hasAlpha, boolean isAlphaPremultiplied)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 Interleaved(ColorSpace colorSpace, int[] bandOffsets, int dataType, boolean hasAlpha, boolean isAlphaPremultiplied)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 equals(Object o)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 hashCode()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 createInterleaved(ColorSpace colorSpace, int[] bandOffsets, int dataType, boolean hasAlpha, boolean isAlphaPremultiplied)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 Banded(ColorSpace colorSpace, int[] bankIndices, int[] bandOffsets, int dataType, boolean hasAlpha, boolean isAlphaPremultiplied)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 equals(Object o)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 hashCode()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 createBanded(ColorSpace colorSpace, int[] bankIndices, int[] bandOffsets, int dataType, boolean hasAlpha, boolean isAlphaPremultiplied)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 Grayscale(int bits, int dataType, boolean isSigned, boolean hasAlpha, boolean isAlphaPremultiplied)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 createGrayscale(int bits, int dataType, boolean isSigned)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 createGrayscale(int bits, int dataType, boolean isSigned, boolean isAlphaPremultiplied)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 Indexed(byte[] redLUT, byte[] greenLUT, byte[] blueLUT, byte[] alphaLUT, int bits, int dataType)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 createIndexed(byte[] redLUT, byte[] greenLUT, byte[] blueLUT, byte[] alphaLUT, int bits, int dataType)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 createFromBufferedImageType(int bufferedImageType)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 createFromRenderedImage(RenderedImage image)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 */ getBufferedImageType()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 */ getNumComponents()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 */ getNumBands()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 */ getBitsPerBand(int band)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 */ getSampleModel()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 */ getSampleModel(int width, int height)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 */ getColorModel()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 */ createBufferedImage(int width, int height)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 */ equals(Object o)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 */ hashCode()1112 public int hashCode() { 1113 return (9 * colorModel.hashCode()) + (14 * sampleModel.hashCode()); 1114 } 1115 getSpecifier(int type)1116 private static ImageTypeSpecifier getSpecifier(int type) { 1117 if (BISpecifier[type] == null) { 1118 BISpecifier[type] = createSpecifier(type); 1119 } 1120 return BISpecifier[type]; 1121 } 1122 createSpecifier(int type)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