1 /* 2 * Copyright (c) 2003, 2020, 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 com.sun.imageio.plugins.bmp; 27 28 import java.awt.Rectangle; 29 import java.awt.image.ColorModel; 30 import java.awt.image.ComponentSampleModel; 31 import java.awt.image.DataBuffer; 32 import java.awt.image.DataBufferByte; 33 import java.awt.image.DataBufferInt; 34 import java.awt.image.DataBufferShort; 35 import java.awt.image.DataBufferUShort; 36 import java.awt.image.DirectColorModel; 37 import java.awt.image.IndexColorModel; 38 import java.awt.image.MultiPixelPackedSampleModel; 39 import java.awt.image.BandedSampleModel; 40 import java.awt.image.Raster; 41 import java.awt.image.RenderedImage; 42 import java.awt.image.SampleModel; 43 import java.awt.image.SinglePixelPackedSampleModel; 44 import java.awt.image.BufferedImage; 45 46 import java.io.IOException; 47 import java.io.ByteArrayOutputStream; 48 import java.nio.ByteOrder; 49 import java.util.Iterator; 50 51 import javax.imageio.IIOImage; 52 import javax.imageio.ImageIO; 53 import javax.imageio.ImageTypeSpecifier; 54 import javax.imageio.ImageWriteParam; 55 import javax.imageio.ImageWriter; 56 import javax.imageio.metadata.IIOMetadata; 57 import javax.imageio.spi.ImageWriterSpi; 58 import javax.imageio.stream.ImageOutputStream; 59 import javax.imageio.event.IIOWriteProgressListener; 60 import javax.imageio.event.IIOWriteWarningListener; 61 62 63 import javax.imageio.plugins.bmp.BMPImageWriteParam; 64 import com.sun.imageio.plugins.common.ImageUtil; 65 import com.sun.imageio.plugins.common.I18N; 66 67 /** 68 * The Java Image IO plugin writer for encoding a binary RenderedImage into 69 * a BMP format. 70 * 71 * The encoding process may clip, subsample using the parameters 72 * specified in the {@code ImageWriteParam}. 73 * 74 * @see javax.imageio.plugins.bmp.BMPImageWriteParam 75 */ 76 public class BMPImageWriter extends ImageWriter implements BMPConstants { 77 /** The output stream to write into */ 78 private ImageOutputStream stream = null; 79 private ByteArrayOutputStream embedded_stream = null; 80 private int version; 81 private int compressionType; 82 private boolean isTopDown; 83 private int w, h; 84 private int compImageSize = 0; 85 private int[] bitMasks; 86 private int[] bitPos; 87 private byte[] bpixels; 88 private short[] spixels; 89 private int[] ipixels; 90 91 /** Constructs {@code BMPImageWriter} based on the provided 92 * {@code ImageWriterSpi}. 93 */ 94 public BMPImageWriter(ImageWriterSpi originator) { 95 super(originator); 96 } 97 98 @Override 99 public void setOutput(Object output) { 100 super.setOutput(output); // validates output 101 if (output != null) { 102 if (!(output instanceof ImageOutputStream)) 103 throw new IllegalArgumentException(I18N.getString("BMPImageWriter0")); 104 this.stream = (ImageOutputStream)output; 105 stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 106 } else 107 this.stream = null; 108 } 109 110 @Override 111 public ImageWriteParam getDefaultWriteParam() { 112 return new BMPImageWriteParam(); 113 } 114 115 @Override 116 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 117 return null; 118 } 119 120 @Override 121 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, 122 ImageWriteParam param) { 123 BMPMetadata meta = new BMPMetadata(); 124 meta.bmpVersion = VERSION_3; 125 meta.compression = getPreferredCompressionType(imageType); 126 if (param != null 127 && param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) { 128 meta.compression = BMPCompressionTypes.getType(param.getCompressionType()); 129 } 130 meta.bitsPerPixel = (short)imageType.getColorModel().getPixelSize(); 131 return meta; 132 } 133 134 @Override 135 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 136 ImageWriteParam param) { 137 return null; 138 } 139 140 @Override 141 public IIOMetadata convertImageMetadata(IIOMetadata metadata, 142 ImageTypeSpecifier type, 143 ImageWriteParam param) { 144 return null; 145 } 146 147 @Override 148 public boolean canWriteRasters() { 149 return true; 150 } 151 152 @Override 153 public void write(IIOMetadata streamMetadata, 154 IIOImage image, 155 ImageWriteParam param) throws IOException { 156 157 if (stream == null) { 158 throw new IllegalStateException(I18N.getString("BMPImageWriter7")); 159 } 160 161 if (image == null) { 162 throw new IllegalArgumentException(I18N.getString("BMPImageWriter8")); 163 } 164 165 clearAbortRequest(); 166 processImageStarted(0); 167 if (abortRequested()) { 168 processWriteAborted(); 169 return; 170 } 171 if (param == null) 172 param = getDefaultWriteParam(); 173 174 BMPImageWriteParam bmpParam = (BMPImageWriteParam)param; 175 176 // Default is using 24 bits per pixel. 177 int bitsPerPixel = 24; 178 boolean isPalette = false; 179 int paletteEntries = 0; 180 IndexColorModel icm = null; 181 182 RenderedImage input = null; 183 Raster inputRaster = null; 184 boolean writeRaster = image.hasRaster(); 185 Rectangle sourceRegion = param.getSourceRegion(); 186 SampleModel sampleModel = null; 187 ColorModel colorModel = null; 188 189 compImageSize = 0; 190 191 if (writeRaster) { 192 inputRaster = image.getRaster(); 193 sampleModel = inputRaster.getSampleModel(); 194 colorModel = ImageUtil.createColorModel(null, sampleModel); 195 if (sourceRegion == null) 196 sourceRegion = inputRaster.getBounds(); 197 else 198 sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); 199 } else { 200 input = image.getRenderedImage(); 201 sampleModel = input.getSampleModel(); 202 colorModel = input.getColorModel(); 203 Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(), 204 input.getWidth(), input.getHeight()); 205 if (sourceRegion == null) 206 sourceRegion = rect; 207 else 208 sourceRegion = sourceRegion.intersection(rect); 209 } 210 211 IIOMetadata imageMetadata = image.getMetadata(); 212 BMPMetadata bmpImageMetadata = null; 213 if (imageMetadata != null 214 && imageMetadata instanceof BMPMetadata) 215 { 216 bmpImageMetadata = (BMPMetadata)imageMetadata; 217 } else { 218 ImageTypeSpecifier imageType = 219 new ImageTypeSpecifier(colorModel, sampleModel); 220 221 bmpImageMetadata = (BMPMetadata)getDefaultImageMetadata(imageType, 222 param); 223 } 224 225 if (sourceRegion.isEmpty()) 226 throw new RuntimeException(I18N.getString("BMPImageWrite0")); 227 228 int scaleX = param.getSourceXSubsampling(); 229 int scaleY = param.getSourceYSubsampling(); 230 int xOffset = param.getSubsamplingXOffset(); 231 int yOffset = param.getSubsamplingYOffset(); 232 233 // cache the data type; 234 int dataType = sampleModel.getDataType(); 235 236 sourceRegion.translate(xOffset, yOffset); 237 sourceRegion.width -= xOffset; 238 sourceRegion.height -= yOffset; 239 240 int minX = sourceRegion.x / scaleX; 241 int minY = sourceRegion.y / scaleY; 242 w = (sourceRegion.width + scaleX - 1) / scaleX; 243 h = (sourceRegion.height + scaleY - 1) / scaleY; 244 xOffset = sourceRegion.x % scaleX; 245 yOffset = sourceRegion.y % scaleY; 246 247 Rectangle destinationRegion = new Rectangle(minX, minY, w, h); 248 boolean noTransform = destinationRegion.equals(sourceRegion); 249 250 // Raw data can only handle bytes, everything greater must be ASCII. 251 int[] sourceBands = param.getSourceBands(); 252 boolean noSubband = true; 253 int numBands = sampleModel.getNumBands(); 254 255 if (sourceBands != null) { 256 sampleModel = sampleModel.createSubsetSampleModel(sourceBands); 257 colorModel = null; 258 noSubband = false; 259 numBands = sampleModel.getNumBands(); 260 } else { 261 sourceBands = new int[numBands]; 262 for (int i = 0; i < numBands; i++) 263 sourceBands[i] = i; 264 } 265 266 int[] bandOffsets = null; 267 boolean bgrOrder = true; 268 269 if (sampleModel instanceof ComponentSampleModel) { 270 bandOffsets = ((ComponentSampleModel)sampleModel).getBandOffsets(); 271 if (sampleModel instanceof BandedSampleModel) { 272 // for images with BandedSampleModel we can not work 273 // with raster directly and must use writePixels() 274 bgrOrder = false; 275 } else { 276 // we can work with raster directly only in case of 277 // BGR component order. 278 // In any other case we must use writePixels() 279 for (int i = 0; i < bandOffsets.length; i++) { 280 bgrOrder &= (bandOffsets[i] == (bandOffsets.length - i - 1)); 281 } 282 } 283 } else { 284 if (sampleModel instanceof SinglePixelPackedSampleModel) { 285 286 // BugId 4892214: we can not work with raster directly 287 // if image have different color order than RGB. 288 // We should use writePixels() for such images. 289 int[] bitOffsets = ((SinglePixelPackedSampleModel)sampleModel).getBitOffsets(); 290 for (int i=0; i<bitOffsets.length-1; i++) { 291 bgrOrder &= bitOffsets[i] > bitOffsets[i+1]; 292 } 293 } 294 } 295 296 if (bandOffsets == null) { 297 // we will use getPixels() to extract pixel data for writePixels() 298 // Please note that getPixels() provides rgb bands order. 299 bandOffsets = new int[numBands]; 300 for (int i = 0; i < numBands; i++) 301 bandOffsets[i] = i; 302 } 303 304 noTransform &= bgrOrder; 305 306 int[] sampleSize = sampleModel.getSampleSize(); 307 308 //XXX: check more 309 310 // Number of bytes that a scanline for the image written out will have. 311 int destScanlineBytes = w * numBands; 312 313 switch(bmpParam.getCompressionMode()) { 314 case ImageWriteParam.MODE_EXPLICIT: 315 compressionType = BMPCompressionTypes.getType(bmpParam.getCompressionType()); 316 break; 317 case ImageWriteParam.MODE_COPY_FROM_METADATA: 318 compressionType = bmpImageMetadata.compression; 319 break; 320 case ImageWriteParam.MODE_DEFAULT: 321 compressionType = getPreferredCompressionType(colorModel, sampleModel); 322 break; 323 default: 324 // ImageWriteParam.MODE_DISABLED: 325 compressionType = BI_RGB; 326 } 327 328 if (!canEncodeImage(compressionType, colorModel, sampleModel)) { 329 throw new IOException("Image can not be encoded with compression type " 330 + BMPCompressionTypes.getName(compressionType)); 331 } 332 333 byte[] r = null, g = null, b = null, a = null; 334 335 if (compressionType == BI_BITFIELDS) { 336 bitsPerPixel = 337 DataBuffer.getDataTypeSize(sampleModel.getDataType()); 338 339 if (bitsPerPixel != 16 && bitsPerPixel != 32) { 340 // we should use 32bpp images in case of BI_BITFIELD 341 // compression to avoid color conversion artefacts 342 bitsPerPixel = 32; 343 344 // Setting this flag to false ensures that generic 345 // writePixels() will be used to store image data 346 noTransform = false; 347 } 348 349 destScanlineBytes = w * bitsPerPixel + 7 >> 3; 350 351 isPalette = true; 352 paletteEntries = 3; 353 r = new byte[paletteEntries]; 354 g = new byte[paletteEntries]; 355 b = new byte[paletteEntries]; 356 a = new byte[paletteEntries]; 357 358 int rmask = 0x00ff0000; 359 int gmask = 0x0000ff00; 360 int bmask = 0x000000ff; 361 362 if (bitsPerPixel == 16) { 363 /* NB: canEncodeImage() ensures we have image of 364 * either USHORT_565_RGB or USHORT_555_RGB type here. 365 * Technically, it should work for other direct color 366 * model types but it might be non compatible with win98 367 * and friends. 368 */ 369 if (colorModel instanceof DirectColorModel) { 370 DirectColorModel dcm = (DirectColorModel)colorModel; 371 rmask = dcm.getRedMask(); 372 gmask = dcm.getGreenMask(); 373 bmask = dcm.getBlueMask(); 374 } else { 375 // it is unlikely, but if it happens, we should throw 376 // an exception related to unsupported image format 377 throw new IOException("Image can not be encoded with " + 378 "compression type " + 379 BMPCompressionTypes.getName(compressionType)); 380 } 381 } 382 writeMaskToPalette(rmask, 0, r, g, b, a); 383 writeMaskToPalette(gmask, 1, r, g, b, a); 384 writeMaskToPalette(bmask, 2, r, g, b, a); 385 386 if (!noTransform) { 387 // prepare info for writePixels procedure 388 bitMasks = new int[3]; 389 bitMasks[0] = rmask; 390 bitMasks[1] = gmask; 391 bitMasks[2] = bmask; 392 393 bitPos = new int[3]; 394 bitPos[0] = firstLowBit(rmask); 395 bitPos[1] = firstLowBit(gmask); 396 bitPos[2] = firstLowBit(bmask); 397 } 398 399 if (colorModel instanceof IndexColorModel) { 400 icm = (IndexColorModel)colorModel; 401 } 402 } else { // handle BI_RGB compression 403 if (colorModel instanceof IndexColorModel) { 404 isPalette = true; 405 icm = (IndexColorModel)colorModel; 406 paletteEntries = icm.getMapSize(); 407 408 if (paletteEntries <= 2) { 409 bitsPerPixel = 1; 410 destScanlineBytes = w + 7 >> 3; 411 } else if (paletteEntries <= 16) { 412 bitsPerPixel = 4; 413 destScanlineBytes = w + 1 >> 1; 414 } else if (paletteEntries <= 256) { 415 bitsPerPixel = 8; 416 } else { 417 // Cannot be written as a Palette image. So write out as 418 // 24 bit image. 419 bitsPerPixel = 24; 420 isPalette = false; 421 paletteEntries = 0; 422 destScanlineBytes = w * 3; 423 } 424 425 if (isPalette == true) { 426 r = new byte[paletteEntries]; 427 g = new byte[paletteEntries]; 428 b = new byte[paletteEntries]; 429 a = new byte[paletteEntries]; 430 431 icm.getAlphas(a); 432 icm.getReds(r); 433 icm.getGreens(g); 434 icm.getBlues(b); 435 } 436 437 } else { 438 // Grey scale images 439 if (numBands == 1) { 440 441 isPalette = true; 442 paletteEntries = 256; 443 bitsPerPixel = sampleSize[0]; 444 445 destScanlineBytes = (w * bitsPerPixel + 7 >> 3); 446 447 r = new byte[256]; 448 g = new byte[256]; 449 b = new byte[256]; 450 a = new byte[256]; 451 452 for (int i = 0; i < 256; i++) { 453 r[i] = (byte)i; 454 g[i] = (byte)i; 455 b[i] = (byte)i; 456 a[i] = (byte)255; 457 } 458 459 } else { 460 if (sampleModel instanceof SinglePixelPackedSampleModel && 461 noSubband) 462 { 463 /* NB: the actual pixel size can be smaller than 464 * size of used DataBuffer element. 465 * For example: in case of TYPE_INT_RGB actual pixel 466 * size is 24 bits, but size of DataBuffere element 467 * is 32 bits 468 */ 469 int[] sample_sizes = sampleModel.getSampleSize(); 470 bitsPerPixel = 0; 471 for (int size : sample_sizes) { 472 bitsPerPixel += size; 473 } 474 bitsPerPixel = roundBpp(bitsPerPixel); 475 if (bitsPerPixel != DataBuffer.getDataTypeSize(sampleModel.getDataType())) { 476 noTransform = false; 477 } 478 destScanlineBytes = w * bitsPerPixel + 7 >> 3; 479 } 480 } 481 } 482 } 483 484 // actual writing of image data 485 int fileSize = 0; 486 int offset = 0; 487 int headerSize = 0; 488 int imageSize = 0; 489 int xPelsPerMeter = 0; 490 int yPelsPerMeter = 0; 491 int colorsUsed = 0; 492 int colorsImportant = paletteEntries; 493 494 // Calculate padding for each scanline 495 int padding = destScanlineBytes % 4; 496 if (padding != 0) { 497 padding = 4 - padding; 498 } 499 500 501 // FileHeader is 14 bytes, BitmapHeader is 40 bytes, 502 // add palette size and that is where the data will begin 503 offset = 54 + paletteEntries * 4; 504 505 imageSize = (destScanlineBytes + padding) * h; 506 fileSize = imageSize + offset; 507 headerSize = 40; 508 509 long headPos = stream.getStreamPosition(); 510 511 writeFileHeader(fileSize, offset); 512 513 /* According to MSDN description, the top-down image layout 514 * is allowed only if compression type is BI_RGB or BI_BITFIELDS. 515 * Images with any other compression type must be wrote in the 516 * bottom-up layout. 517 */ 518 if (compressionType == BI_RGB || 519 compressionType == BI_BITFIELDS) 520 { 521 isTopDown = bmpParam.isTopDown(); 522 } else { 523 isTopDown = false; 524 } 525 526 writeInfoHeader(headerSize, bitsPerPixel); 527 528 // compression 529 stream.writeInt(compressionType); 530 531 // imageSize 532 stream.writeInt(imageSize); 533 534 // xPelsPerMeter 535 stream.writeInt(xPelsPerMeter); 536 537 // yPelsPerMeter 538 stream.writeInt(yPelsPerMeter); 539 540 // Colors Used 541 stream.writeInt(colorsUsed); 542 543 // Colors Important 544 stream.writeInt(colorsImportant); 545 546 // palette 547 if (isPalette == true) { 548 549 // write palette 550 if (compressionType == BI_BITFIELDS) { 551 // write masks for red, green and blue components. 552 for (int i=0; i<3; i++) { 553 int mask = (a[i]&0xFF) + ((r[i]&0xFF)*0x100) + ((g[i]&0xFF)*0x10000) + ((b[i]&0xFF)*0x1000000); 554 stream.writeInt(mask); 555 } 556 } else { 557 for (int i=0; i<paletteEntries; i++) { 558 stream.writeByte(b[i]); 559 stream.writeByte(g[i]); 560 stream.writeByte(r[i]); 561 stream.writeByte(a[i]); 562 } 563 } 564 } 565 566 // Writing of actual image data 567 int scanlineBytes = w * numBands; 568 569 // Buffer for up to 8 rows of pixels 570 int[] pixels = new int[scanlineBytes * scaleX]; 571 572 // Also create a buffer to hold one line of the data 573 // to be written to the file, so we can use array writes. 574 bpixels = new byte[destScanlineBytes]; 575 576 int l; 577 578 if (compressionType == BI_JPEG || 579 compressionType == BI_PNG) { 580 581 // prepare embedded buffer 582 embedded_stream = new ByteArrayOutputStream(); 583 writeEmbedded(image, bmpParam); 584 // update the file/image Size 585 embedded_stream.flush(); 586 imageSize = embedded_stream.size(); 587 588 long endPos = stream.getStreamPosition(); 589 fileSize = offset + imageSize; 590 stream.seek(headPos); 591 writeSize(fileSize, 2); 592 stream.seek(headPos); 593 writeSize(imageSize, 34); 594 stream.seek(endPos); 595 stream.write(embedded_stream.toByteArray()); 596 embedded_stream = null; 597 598 processImageComplete(); 599 stream.flushBefore(stream.getStreamPosition()); 600 601 return; 602 } 603 604 int maxBandOffset = bandOffsets[0]; 605 for (int i = 1; i < bandOffsets.length; i++) 606 if (bandOffsets[i] > maxBandOffset) 607 maxBandOffset = bandOffsets[i]; 608 609 int[] pixel = new int[maxBandOffset + 1]; 610 611 int destScanlineLength = destScanlineBytes; 612 613 if (noTransform && noSubband) { 614 destScanlineLength = destScanlineBytes / (DataBuffer.getDataTypeSize(dataType)>>3); 615 } 616 for (int i = 0; i < h; i++) { 617 618 int row = minY + i; 619 620 if (!isTopDown) 621 row = minY + h - i -1; 622 623 // Get the pixels 624 Raster src = inputRaster; 625 626 Rectangle srcRect = 627 new Rectangle(minX * scaleX + xOffset, 628 row * scaleY + yOffset, 629 (w - 1)* scaleX + 1, 630 1); 631 if (!writeRaster) 632 src = input.getData(srcRect); 633 634 if (noTransform && noSubband) { 635 SampleModel sm = src.getSampleModel(); 636 int pos = 0; 637 int startX = srcRect.x - src.getSampleModelTranslateX(); 638 int startY = srcRect.y - src.getSampleModelTranslateY(); 639 if (sm instanceof ComponentSampleModel) { 640 ComponentSampleModel csm = (ComponentSampleModel)sm; 641 pos = csm.getOffset(startX, startY, 0); 642 for(int nb=1; nb < csm.getNumBands(); nb++) { 643 if (pos > csm.getOffset(startX, startY, nb)) { 644 pos = csm.getOffset(startX, startY, nb); 645 } 646 } 647 } else if (sm instanceof MultiPixelPackedSampleModel) { 648 MultiPixelPackedSampleModel mppsm = 649 (MultiPixelPackedSampleModel)sm; 650 pos = mppsm.getOffset(startX, startY); 651 } else if (sm instanceof SinglePixelPackedSampleModel) { 652 SinglePixelPackedSampleModel sppsm = 653 (SinglePixelPackedSampleModel)sm; 654 pos = sppsm.getOffset(startX, startY); 655 } 656 657 if (compressionType == BI_RGB || compressionType == BI_BITFIELDS){ 658 switch(dataType) { 659 case DataBuffer.TYPE_BYTE: 660 byte[] bdata = 661 ((DataBufferByte)src.getDataBuffer()).getData(); 662 stream.write(bdata, pos, destScanlineLength); 663 break; 664 665 case DataBuffer.TYPE_SHORT: 666 short[] sdata = 667 ((DataBufferShort)src.getDataBuffer()).getData(); 668 stream.writeShorts(sdata, pos, destScanlineLength); 669 break; 670 671 case DataBuffer.TYPE_USHORT: 672 short[] usdata = 673 ((DataBufferUShort)src.getDataBuffer()).getData(); 674 stream.writeShorts(usdata, pos, destScanlineLength); 675 break; 676 677 case DataBuffer.TYPE_INT: 678 int[] idata = 679 ((DataBufferInt)src.getDataBuffer()).getData(); 680 stream.writeInts(idata, pos, destScanlineLength); 681 break; 682 } 683 684 for(int k=0; k<padding; k++) { 685 stream.writeByte(0); 686 } 687 } else if (compressionType == BI_RLE4) { 688 if (bpixels == null || bpixels.length < scanlineBytes) 689 bpixels = new byte[scanlineBytes]; 690 src.getPixels(srcRect.x, srcRect.y, 691 srcRect.width, srcRect.height, pixels); 692 for (int h=0; h<scanlineBytes; h++) { 693 bpixels[h] = (byte)pixels[h]; 694 } 695 encodeRLE4(bpixels, scanlineBytes); 696 } else if (compressionType == BI_RLE8) { 697 //byte[] bdata = 698 // ((DataBufferByte)src.getDataBuffer()).getData(); 699 //System.out.println("bdata.length="+bdata.length); 700 //System.arraycopy(bdata, pos, bpixels, 0, scanlineBytes); 701 if (bpixels == null || bpixels.length < scanlineBytes) 702 bpixels = new byte[scanlineBytes]; 703 src.getPixels(srcRect.x, srcRect.y, 704 srcRect.width, srcRect.height, pixels); 705 for (int h=0; h<scanlineBytes; h++) { 706 bpixels[h] = (byte)pixels[h]; 707 } 708 709 encodeRLE8(bpixels, scanlineBytes); 710 } 711 } else { 712 src.getPixels(srcRect.x, srcRect.y, 713 srcRect.width, srcRect.height, pixels); 714 715 if (scaleX != 1 || maxBandOffset != numBands - 1) { 716 for (int j = 0, k = 0, n=0; j < w; 717 j++, k += scaleX * numBands, n += numBands) 718 { 719 System.arraycopy(pixels, k, pixel, 0, pixel.length); 720 721 for (int m = 0; m < numBands; m++) { 722 // pixel data is provided here in RGB order 723 pixels[n + m] = pixel[sourceBands[m]]; 724 } 725 } 726 } 727 writePixels(0, scanlineBytes, bitsPerPixel, pixels, 728 padding, numBands, icm); 729 } 730 731 processImageProgress(100.0f * (((float)i) / ((float)h))); 732 if (abortRequested()) { 733 break; 734 } 735 } 736 737 if (compressionType == BI_RLE4 || 738 compressionType == BI_RLE8) { 739 // Write the RLE EOF marker and 740 stream.writeByte(0); 741 stream.writeByte(1); 742 incCompImageSize(2); 743 // update the file/image Size 744 imageSize = compImageSize; 745 fileSize = compImageSize + offset; 746 long endPos = stream.getStreamPosition(); 747 stream.seek(headPos); 748 writeSize(fileSize, 2); 749 stream.seek(headPos); 750 writeSize(imageSize, 34); 751 stream.seek(endPos); 752 } 753 754 if (abortRequested()) { 755 processWriteAborted(); 756 } else { 757 processImageComplete(); 758 stream.flushBefore(stream.getStreamPosition()); 759 } 760 } 761 762 private void writePixels(int l, int scanlineBytes, int bitsPerPixel, 763 int[] pixels, 764 int padding, int numBands, 765 IndexColorModel icm) throws IOException { 766 int pixel = 0; 767 int k = 0; 768 switch (bitsPerPixel) { 769 770 case 1: 771 772 for (int j=0; j<scanlineBytes/8; j++) { 773 bpixels[k++] = (byte)((pixels[l++] << 7) | 774 (pixels[l++] << 6) | 775 (pixels[l++] << 5) | 776 (pixels[l++] << 4) | 777 (pixels[l++] << 3) | 778 (pixels[l++] << 2) | 779 (pixels[l++] << 1) | 780 pixels[l++]); 781 } 782 783 // Partially filled last byte, if any 784 if (scanlineBytes%8 > 0) { 785 pixel = 0; 786 for (int j=0; j<scanlineBytes%8; j++) { 787 pixel |= (pixels[l++] << (7 - j)); 788 } 789 bpixels[k++] = (byte)pixel; 790 } 791 stream.write(bpixels, 0, (scanlineBytes+7)/8); 792 793 break; 794 795 case 4: 796 if (compressionType == BI_RLE4){ 797 byte[] bipixels = new byte[scanlineBytes]; 798 for (int h=0; h<scanlineBytes; h++) { 799 bipixels[h] = (byte)pixels[l++]; 800 } 801 encodeRLE4(bipixels, scanlineBytes); 802 }else { 803 for (int j=0; j<scanlineBytes/2; j++) { 804 pixel = (pixels[l++] << 4) | pixels[l++]; 805 bpixels[k++] = (byte)pixel; 806 } 807 // Put the last pixel of odd-length lines in the 4 MSBs 808 if ((scanlineBytes%2) == 1) { 809 pixel = pixels[l] << 4; 810 bpixels[k++] = (byte)pixel; 811 } 812 stream.write(bpixels, 0, (scanlineBytes+1)/2); 813 } 814 break; 815 816 case 8: 817 if(compressionType == BI_RLE8) { 818 for (int h=0; h<scanlineBytes; h++) { 819 bpixels[h] = (byte)pixels[l++]; 820 } 821 encodeRLE8(bpixels, scanlineBytes); 822 }else { 823 for (int j=0; j<scanlineBytes; j++) { 824 bpixels[j] = (byte)pixels[l++]; 825 } 826 stream.write(bpixels, 0, scanlineBytes); 827 } 828 break; 829 830 case 16: 831 if (spixels == null) 832 spixels = new short[scanlineBytes / numBands]; 833 /* 834 * We expect that pixel data comes in RGB order. 835 * We will assemble short pixel taking into account 836 * the compression type: 837 * 838 * BI_RGB - the RGB order should be maintained. 839 * BI_BITFIELDS - use bitPos array that was built 840 * according to bitfields masks. 841 */ 842 for (int j = 0, m = 0; j < scanlineBytes; m++) { 843 spixels[m] = 0; 844 if (compressionType == BI_RGB) { 845 /* 846 * please note that despite other cases, 847 * the 16bpp BI_RGB requires the RGB data order 848 */ 849 spixels[m] = (short) 850 (((0x1f & pixels[j ]) << 10) | 851 ((0x1f & pixels[j + 1]) << 5) | 852 ((0x1f & pixels[j + 2]) )); 853 j += 3; 854 } else { 855 for(int i = 0 ; i < numBands; i++, j++) { 856 spixels[m] |= 857 (((pixels[j]) << bitPos[i]) & bitMasks[i]); 858 } 859 } 860 } 861 stream.writeShorts(spixels, 0, spixels.length); 862 break; 863 864 case 24: 865 if (numBands == 3) { 866 for (int j=0; j<scanlineBytes; j+=3) { 867 // Since BMP needs BGR format 868 bpixels[k++] = (byte)(pixels[l+2]); 869 bpixels[k++] = (byte)(pixels[l+1]); 870 bpixels[k++] = (byte)(pixels[l]); 871 l+=3; 872 } 873 stream.write(bpixels, 0, scanlineBytes); 874 } else { 875 // Case where IndexColorModel had > 256 colors. 876 int entries = icm.getMapSize(); 877 878 byte[] r = new byte[entries]; 879 byte[] g = new byte[entries]; 880 byte[] b = new byte[entries]; 881 882 icm.getReds(r); 883 icm.getGreens(g); 884 icm.getBlues(b); 885 int index; 886 887 for (int j=0; j<scanlineBytes; j++) { 888 index = pixels[l]; 889 bpixels[k++] = b[index]; 890 bpixels[k++] = g[index]; 891 bpixels[k++] = b[index]; 892 l++; 893 } 894 stream.write(bpixels, 0, scanlineBytes*3); 895 } 896 break; 897 898 case 32: 899 if (ipixels == null) 900 ipixels = new int[scanlineBytes / numBands]; 901 if (numBands == 3) { 902 /* 903 * We expect that pixel data comes in RGB order. 904 * We will assemble int pixel taking into account 905 * the compression type. 906 * 907 * BI_RGB - the BGR order should be used. 908 * BI_BITFIELDS - use bitPos array that was built 909 * according to bitfields masks. 910 */ 911 for (int j = 0, m = 0; j < scanlineBytes; m++) { 912 ipixels[m] = 0; 913 if (compressionType == BI_RGB) { 914 ipixels[m] = 915 ((0xff & pixels[j + 2]) << 16) | 916 ((0xff & pixels[j + 1]) << 8) | 917 ((0xff & pixels[j ]) ); 918 j += 3; 919 } else { 920 for(int i = 0 ; i < numBands; i++, j++) { 921 ipixels[m] |= 922 (((pixels[j]) << bitPos[i]) & bitMasks[i]); 923 } 924 } 925 } 926 } else { 927 // We have two possibilities here: 928 // 1. we are writing the indexed image with bitfields 929 // compression (this covers also the case of BYTE_BINARY) 930 // => use icm to get actual RGB color values. 931 // 2. we are writing the gray-scaled image with BI_BITFIELDS 932 // compression 933 // => just replicate the level of gray to color components. 934 for (int j = 0; j < scanlineBytes; j++) { 935 if (icm != null) { 936 ipixels[j] = icm.getRGB(pixels[j]); 937 } else { 938 ipixels[j] = 939 pixels[j] << 16 | pixels[j] << 8 | pixels[j]; 940 } 941 } 942 } 943 stream.writeInts(ipixels, 0, ipixels.length); 944 break; 945 } 946 947 // Write out the padding 948 if (compressionType == BI_RGB || 949 compressionType == BI_BITFIELDS) 950 { 951 for(k=0; k<padding; k++) { 952 stream.writeByte(0); 953 } 954 } 955 } 956 957 private void encodeRLE8(byte[] bpixels, int scanlineBytes) 958 throws IOException{ 959 960 int runCount = 1, absVal = -1, j = -1; 961 byte runVal = 0, nextVal =0 ; 962 963 runVal = bpixels[++j]; 964 byte[] absBuf = new byte[256]; 965 966 while (j < scanlineBytes-1) { 967 nextVal = bpixels[++j]; 968 if (nextVal == runVal ){ 969 if(absVal >= 3 ){ 970 /// Check if there was an existing Absolute Run 971 stream.writeByte(0); 972 stream.writeByte(absVal); 973 incCompImageSize(2); 974 for(int a=0; a<absVal;a++){ 975 stream.writeByte(absBuf[a]); 976 incCompImageSize(1); 977 } 978 if (!isEven(absVal)){ 979 //Padding 980 stream.writeByte(0); 981 incCompImageSize(1); 982 } 983 } 984 else if(absVal > -1){ 985 /// Absolute Encoding for less than 3 986 /// treated as regular encoding 987 /// Do not include the last element since it will 988 /// be inclued in the next encoding/run 989 for (int b=0;b<absVal;b++){ 990 stream.writeByte(1); 991 stream.writeByte(absBuf[b]); 992 incCompImageSize(2); 993 } 994 } 995 absVal = -1; 996 runCount++; 997 if (runCount == 256){ 998 /// Only 255 values permitted 999 stream.writeByte(runCount-1); 1000 stream.writeByte(runVal); 1001 incCompImageSize(2); 1002 runCount = 1; 1003 } 1004 } 1005 else { 1006 if (runCount > 1){ 1007 /// If there was an existing run 1008 stream.writeByte(runCount); 1009 stream.writeByte(runVal); 1010 incCompImageSize(2); 1011 } else if (absVal < 0){ 1012 // First time.. 1013 absBuf[++absVal] = runVal; 1014 absBuf[++absVal] = nextVal; 1015 } else if (absVal < 254){ 1016 // 0-254 only 1017 absBuf[++absVal] = nextVal; 1018 } else { 1019 stream.writeByte(0); 1020 stream.writeByte(absVal+1); 1021 incCompImageSize(2); 1022 for(int a=0; a<=absVal;a++){ 1023 stream.writeByte(absBuf[a]); 1024 incCompImageSize(1); 1025 } 1026 // padding since 255 elts is not even 1027 stream.writeByte(0); 1028 incCompImageSize(1); 1029 absVal = -1; 1030 } 1031 runVal = nextVal; 1032 runCount = 1; 1033 } 1034 1035 if (j == scanlineBytes-1){ // EOF scanline 1036 // Write the run 1037 if (absVal == -1){ 1038 stream.writeByte(runCount); 1039 stream.writeByte(runVal); 1040 incCompImageSize(2); 1041 runCount = 1; 1042 } 1043 else { 1044 // write the Absolute Run 1045 if(absVal >= 2){ 1046 stream.writeByte(0); 1047 stream.writeByte(absVal+1); 1048 incCompImageSize(2); 1049 for(int a=0; a<=absVal;a++){ 1050 stream.writeByte(absBuf[a]); 1051 incCompImageSize(1); 1052 } 1053 if (!isEven(absVal+1)){ 1054 //Padding 1055 stream.writeByte(0); 1056 incCompImageSize(1); 1057 } 1058 1059 } 1060 else if(absVal > -1){ 1061 for (int b=0;b<=absVal;b++){ 1062 stream.writeByte(1); 1063 stream.writeByte(absBuf[b]); 1064 incCompImageSize(2); 1065 } 1066 } 1067 } 1068 /// EOF scanline 1069 1070 stream.writeByte(0); 1071 stream.writeByte(0); 1072 incCompImageSize(2); 1073 } 1074 } 1075 } 1076 1077 private void encodeRLE4(byte[] bipixels, int scanlineBytes) 1078 throws IOException { 1079 1080 int runCount=2, absVal=-1, j=-1, pixel=0, q=0; 1081 byte runVal1=0, runVal2=0, nextVal1=0, nextVal2=0; 1082 byte[] absBuf = new byte[256]; 1083 1084 1085 runVal1 = bipixels[++j]; 1086 runVal2 = bipixels[++j]; 1087 1088 while (j < scanlineBytes-2){ 1089 nextVal1 = bipixels[++j]; 1090 nextVal2 = bipixels[++j]; 1091 1092 if (nextVal1 == runVal1 ) { 1093 1094 //Check if there was an existing Absolute Run 1095 if(absVal >= 4){ 1096 stream.writeByte(0); 1097 stream.writeByte(absVal - 1); 1098 incCompImageSize(2); 1099 // we need to exclude last 2 elts, similarity of 1100 // which caused to enter this part of the code 1101 for(int a=0; a<absVal-2;a+=2){ 1102 pixel = (absBuf[a] << 4) | absBuf[a+1]; 1103 stream.writeByte((byte)pixel); 1104 incCompImageSize(1); 1105 } 1106 // if # of elts is odd - read the last element 1107 if(!(isEven(absVal-1))){ 1108 q = absBuf[absVal-2] << 4| 0; 1109 stream.writeByte(q); 1110 incCompImageSize(1); 1111 } 1112 // Padding to word align absolute encoding 1113 if ( !isEven((int)Math.ceil((absVal-1)/2)) ) { 1114 stream.writeByte(0); 1115 incCompImageSize(1); 1116 } 1117 } else if (absVal > -1){ 1118 stream.writeByte(2); 1119 pixel = (absBuf[0] << 4) | absBuf[1]; 1120 stream.writeByte(pixel); 1121 incCompImageSize(2); 1122 } 1123 absVal = -1; 1124 1125 if (nextVal2 == runVal2){ 1126 // Even runlength 1127 runCount+=2; 1128 if(runCount == 256){ 1129 stream.writeByte(runCount-1); 1130 pixel = ( runVal1 << 4) | runVal2; 1131 stream.writeByte(pixel); 1132 incCompImageSize(2); 1133 runCount =2; 1134 if(j< scanlineBytes - 1){ 1135 runVal1 = runVal2; 1136 runVal2 = bipixels[++j]; 1137 } else { 1138 stream.writeByte(01); 1139 int r = runVal2 << 4 | 0; 1140 stream.writeByte(r); 1141 incCompImageSize(2); 1142 runCount = -1;/// Only EOF required now 1143 } 1144 } 1145 } else { 1146 // odd runlength and the run ends here 1147 // runCount wont be > 254 since 256/255 case will 1148 // be taken care of in above code. 1149 runCount++; 1150 pixel = ( runVal1 << 4) | runVal2; 1151 stream.writeByte(runCount); 1152 stream.writeByte(pixel); 1153 incCompImageSize(2); 1154 runCount = 2; 1155 runVal1 = nextVal2; 1156 // If end of scanline 1157 if (j < scanlineBytes -1){ 1158 runVal2 = bipixels[++j]; 1159 }else { 1160 stream.writeByte(01); 1161 int r = nextVal2 << 4 | 0; 1162 stream.writeByte(r); 1163 incCompImageSize(2); 1164 runCount = -1;/// Only EOF required now 1165 } 1166 1167 } 1168 } else{ 1169 // Check for existing run 1170 if (runCount > 2){ 1171 pixel = ( runVal1 << 4) | runVal2; 1172 stream.writeByte(runCount); 1173 stream.writeByte(pixel); 1174 incCompImageSize(2); 1175 } else if (absVal < 0){ // first time 1176 absBuf[++absVal] = runVal1; 1177 absBuf[++absVal] = runVal2; 1178 absBuf[++absVal] = nextVal1; 1179 absBuf[++absVal] = nextVal2; 1180 } else if (absVal < 253){ // only 255 elements 1181 absBuf[++absVal] = nextVal1; 1182 absBuf[++absVal] = nextVal2; 1183 } else { 1184 stream.writeByte(0); 1185 stream.writeByte(absVal+1); 1186 incCompImageSize(2); 1187 for(int a=0; a<absVal;a+=2){ 1188 pixel = (absBuf[a] << 4) | absBuf[a+1]; 1189 stream.writeByte((byte)pixel); 1190 incCompImageSize(1); 1191 } 1192 // Padding for word align 1193 // since it will fit into 127 bytes 1194 stream.writeByte(0); 1195 incCompImageSize(1); 1196 absVal = -1; 1197 } 1198 1199 runVal1 = nextVal1; 1200 runVal2 = nextVal2; 1201 runCount = 2; 1202 } 1203 // Handle the End of scanline for the last 2 4bits 1204 if (j >= scanlineBytes-2 ) { 1205 if (absVal == -1 && runCount >= 2){ 1206 if (j == scanlineBytes-2){ 1207 if(bipixels[++j] == runVal1){ 1208 runCount++; 1209 pixel = ( runVal1 << 4) | runVal2; 1210 stream.writeByte(runCount); 1211 stream.writeByte(pixel); 1212 incCompImageSize(2); 1213 } else { 1214 pixel = ( runVal1 << 4) | runVal2; 1215 stream.writeByte(runCount); 1216 stream.writeByte(pixel); 1217 stream.writeByte(01); 1218 pixel = bipixels[j]<<4 |0; 1219 stream.writeByte(pixel); 1220 int n = bipixels[j]<<4|0; 1221 incCompImageSize(4); 1222 } 1223 } else { 1224 stream.writeByte(runCount); 1225 pixel =( runVal1 << 4) | runVal2 ; 1226 stream.writeByte(pixel); 1227 incCompImageSize(2); 1228 } 1229 } else if(absVal > -1){ 1230 if (j == scanlineBytes-2){ 1231 absBuf[++absVal] = bipixels[++j]; 1232 } 1233 if (absVal >=2){ 1234 stream.writeByte(0); 1235 stream.writeByte(absVal+1); 1236 incCompImageSize(2); 1237 for(int a=0; a<absVal;a+=2){ 1238 pixel = (absBuf[a] << 4) | absBuf[a+1]; 1239 stream.writeByte((byte)pixel); 1240 incCompImageSize(1); 1241 } 1242 if(!(isEven(absVal+1))){ 1243 q = absBuf[absVal] << 4|0; 1244 stream.writeByte(q); 1245 incCompImageSize(1); 1246 } 1247 1248 // Padding 1249 if ( !isEven((int)Math.ceil((absVal+1)/2)) ) { 1250 stream.writeByte(0); 1251 incCompImageSize(1); 1252 } 1253 1254 } else { 1255 switch (absVal){ 1256 case 0: 1257 stream.writeByte(1); 1258 int n = absBuf[0]<<4 | 0; 1259 stream.writeByte(n); 1260 incCompImageSize(2); 1261 break; 1262 case 1: 1263 stream.writeByte(2); 1264 pixel = (absBuf[0] << 4) | absBuf[1]; 1265 stream.writeByte(pixel); 1266 incCompImageSize(2); 1267 break; 1268 } 1269 } 1270 1271 } 1272 stream.writeByte(0); 1273 stream.writeByte(0); 1274 incCompImageSize(2); 1275 } 1276 } 1277 } 1278 1279 1280 private synchronized void incCompImageSize(int value){ 1281 compImageSize = compImageSize + value; 1282 } 1283 1284 private boolean isEven(int number) { 1285 return (number%2 == 0 ? true : false); 1286 } 1287 1288 private void writeFileHeader(int fileSize, int offset) throws IOException { 1289 // magic value 1290 stream.writeByte('B'); 1291 stream.writeByte('M'); 1292 1293 // File size 1294 stream.writeInt(fileSize); 1295 1296 // reserved1 and reserved2 1297 stream.writeInt(0); 1298 1299 // offset to image data 1300 stream.writeInt(offset); 1301 } 1302 1303 1304 private void writeInfoHeader(int headerSize, 1305 int bitsPerPixel) throws IOException { 1306 // size of header 1307 stream.writeInt(headerSize); 1308 1309 // width 1310 stream.writeInt(w); 1311 1312 // height 1313 stream.writeInt(isTopDown ? -h : h); 1314 1315 // number of planes 1316 stream.writeShort(1); 1317 1318 // Bits Per Pixel 1319 stream.writeShort(bitsPerPixel); 1320 } 1321 1322 private void writeSize(int dword, int offset) throws IOException { 1323 stream.skipBytes(offset); 1324 stream.writeInt(dword); 1325 } 1326 1327 @Override 1328 public void reset() { 1329 super.reset(); 1330 stream = null; 1331 } 1332 1333 private void writeEmbedded(IIOImage image, 1334 ImageWriteParam bmpParam) throws IOException { 1335 String format = 1336 compressionType == BI_JPEG ? "jpeg" : "png"; 1337 Iterator<ImageWriter> iterator = 1338 ImageIO.getImageWritersByFormatName(format); 1339 ImageWriter writer = null; 1340 if (iterator.hasNext()) 1341 writer = iterator.next(); 1342 if (writer != null) { 1343 if (embedded_stream == null) { 1344 throw new RuntimeException("No stream for writing embedded image!"); 1345 } 1346 1347 writer.addIIOWriteProgressListener(new IIOWriteProgressAdapter() { 1348 public void imageProgress(ImageWriter source, float percentageDone) { 1349 processImageProgress(percentageDone); 1350 } 1351 }); 1352 1353 writer.addIIOWriteWarningListener(new IIOWriteWarningListener() { 1354 public void warningOccurred(ImageWriter source, int imageIndex, String warning) { 1355 processWarningOccurred(imageIndex, warning); 1356 } 1357 }); 1358 1359 writer.setOutput(ImageIO.createImageOutputStream(embedded_stream)); 1360 ImageWriteParam param = writer.getDefaultWriteParam(); 1361 //param.setDestinationBands(bmpParam.getDestinationBands()); 1362 param.setDestinationOffset(bmpParam.getDestinationOffset()); 1363 param.setSourceBands(bmpParam.getSourceBands()); 1364 param.setSourceRegion(bmpParam.getSourceRegion()); 1365 param.setSourceSubsampling(bmpParam.getSourceXSubsampling(), 1366 bmpParam.getSourceYSubsampling(), 1367 bmpParam.getSubsamplingXOffset(), 1368 bmpParam.getSubsamplingYOffset()); 1369 writer.write(null, image, param); 1370 } else 1371 throw new RuntimeException(I18N.getString("BMPImageWrite5") + " " + format); 1372 1373 } 1374 1375 private int firstLowBit(int num) { 1376 int count = 0; 1377 while ((num & 1) == 0) { 1378 count++; 1379 num >>>= 1; 1380 } 1381 return count; 1382 } 1383 1384 private class IIOWriteProgressAdapter implements IIOWriteProgressListener { 1385 1386 @Override 1387 public void imageComplete(ImageWriter source) { 1388 } 1389 1390 @Override 1391 public void imageProgress(ImageWriter source, float percentageDone) { 1392 } 1393 1394 @Override 1395 public void imageStarted(ImageWriter source, int imageIndex) { 1396 } 1397 1398 @Override 1399 public void thumbnailComplete(ImageWriter source) { 1400 } 1401 1402 @Override 1403 public void thumbnailProgress(ImageWriter source, float percentageDone) { 1404 } 1405 1406 @Override 1407 public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) { 1408 } 1409 1410 @Override 1411 public void writeAborted(ImageWriter source) { 1412 } 1413 } 1414 1415 /* 1416 * Returns preferred compression type for given image. 1417 * The default compression type is BI_RGB, but some image types can't be 1418 * encodeed with using default compression without cahnge color resolution. 1419 * For example, TYPE_USHORT_565_RGB may be encodeed only by using BI_BITFIELDS 1420 * compression type. 1421 * 1422 * NB: we probably need to extend this method if we encounter other image 1423 * types which can not be encoded with BI_RGB compression type. 1424 */ 1425 protected int getPreferredCompressionType(ColorModel cm, SampleModel sm) { 1426 ImageTypeSpecifier imageType = new ImageTypeSpecifier(cm, sm); 1427 return getPreferredCompressionType(imageType); 1428 } 1429 1430 protected int getPreferredCompressionType(ImageTypeSpecifier imageType) { 1431 if (imageType.getBufferedImageType() == BufferedImage.TYPE_USHORT_565_RGB) { 1432 return BI_BITFIELDS; 1433 } 1434 return BI_RGB; 1435 } 1436 1437 /* 1438 * Check whether we can encode image of given type using compression method in question. 1439 * 1440 * For example, TYPE_USHORT_565_RGB can be encodeed with BI_BITFIELDS compression only. 1441 * 1442 * NB: method should be extended if other cases when we can not encode 1443 * with given compression will be discovered. 1444 */ 1445 protected boolean canEncodeImage(int compression, ColorModel cm, SampleModel sm) { 1446 ImageTypeSpecifier imgType = new ImageTypeSpecifier(cm, sm); 1447 return canEncodeImage(compression, imgType); 1448 } 1449 1450 protected boolean canEncodeImage(int compression, ImageTypeSpecifier imgType) { 1451 ImageWriterSpi spi = this.getOriginatingProvider(); 1452 if (!spi.canEncodeImage(imgType)) { 1453 return false; 1454 } 1455 int biType = imgType.getBufferedImageType(); 1456 int bpp = imgType.getColorModel().getPixelSize(); 1457 if (compressionType == BI_RLE4 && bpp != 4) { 1458 // only 4bpp images can be encoded as BI_RLE4 1459 return false; 1460 } 1461 if (compressionType == BI_RLE8 && bpp != 8) { 1462 // only 8bpp images can be encoded as BI_RLE8 1463 return false; 1464 } 1465 if (bpp == 16) { 1466 /* 1467 * Technically we expect that we may be able to 1468 * encode only some of SinglePixelPackedSampleModel 1469 * images here. 1470 * 1471 * In addition we should take into account following: 1472 * 1473 * 1. BI_RGB case, according to the MSDN description: 1474 * 1475 * The bitmap has a maximum of 2^16 colors. If the 1476 * biCompression member of the BITMAPINFOHEADER is BI_RGB, 1477 * the bmiColors member of BITMAPINFO is NULL. Each WORD 1478 * in the bitmap array represents a single pixel. The 1479 * relative intensities of red, green, and blue are 1480 * represented with five bits for each color component. 1481 * 1482 * 2. BI_BITFIELDS case, according ot the MSDN description: 1483 * 1484 * Windows 95/98/Me: When the biCompression member is 1485 * BI_BITFIELDS, the system supports only the following 1486 * 16bpp color masks: A 5-5-5 16-bit image, where the blue 1487 * mask is 0x001F, the green mask is 0x03E0, and the red mask 1488 * is 0x7C00; and a 5-6-5 16-bit image, where the blue mask 1489 * is 0x001F, the green mask is 0x07E0, and the red mask is 1490 * 0xF800. 1491 */ 1492 boolean canUseRGB = false; 1493 boolean canUseBITFIELDS = false; 1494 1495 SampleModel sm = imgType.getSampleModel(); 1496 if (sm instanceof SinglePixelPackedSampleModel) { 1497 int[] sizes = 1498 ((SinglePixelPackedSampleModel)sm).getSampleSize(); 1499 1500 canUseRGB = true; 1501 canUseBITFIELDS = true; 1502 for (int i = 0; i < sizes.length; i++) { 1503 canUseRGB &= (sizes[i] == 5); 1504 canUseBITFIELDS &= ((sizes[i] == 5) || 1505 (i == 1 && sizes[i] == 6)); 1506 } 1507 } 1508 1509 return (((compressionType == BI_RGB) && canUseRGB) || 1510 ((compressionType == BI_BITFIELDS) && canUseBITFIELDS)); 1511 } 1512 return true; 1513 } 1514 1515 protected void writeMaskToPalette(int mask, int i, 1516 byte[] r, byte[]g, byte[] b, byte[]a) { 1517 b[i] = (byte)(0xff & (mask >> 24)); 1518 g[i] = (byte)(0xff & (mask >> 16)); 1519 r[i] = (byte)(0xff & (mask >> 8)); 1520 a[i] = (byte)(0xff & mask); 1521 } 1522 1523 private int roundBpp(int x) { 1524 if (x <= 8) { 1525 return 8; 1526 } else if (x <= 16) { 1527 return 16; 1528 } if (x <= 24) { 1529 return 24; 1530 } else { 1531 return 32; 1532 } 1533 } 1534 }