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