1 /* 2 * Copyright (c) 2003, 2013, 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</code>. 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</code> based on the provided 92 * <code>ImageWriterSpi</code>. 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 (param == null) 160 param = getDefaultWriteParam(); 161 162 BMPImageWriteParam bmpParam = (BMPImageWriteParam)param; 163 164 // Default is using 24 bits per pixel. 165 int bitsPerPixel = 24; 166 boolean isPalette = false; 167 int paletteEntries = 0; 168 IndexColorModel icm = null; 169 170 RenderedImage input = null; 171 Raster inputRaster = null; 172 boolean writeRaster = image.hasRaster(); 173 Rectangle sourceRegion = param.getSourceRegion(); 174 SampleModel sampleModel = null; 175 ColorModel colorModel = null; 176 177 compImageSize = 0; 178 179 if (writeRaster) { 180 inputRaster = image.getRaster(); 181 sampleModel = inputRaster.getSampleModel(); 182 colorModel = ImageUtil.createColorModel(null, sampleModel); 183 if (sourceRegion == null) 184 sourceRegion = inputRaster.getBounds(); 185 else 186 sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); 187 } else { 188 input = image.getRenderedImage(); 189 sampleModel = input.getSampleModel(); 190 colorModel = input.getColorModel(); 191 Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(), 192 input.getWidth(), input.getHeight()); 193 if (sourceRegion == null) 194 sourceRegion = rect; 195 else 196 sourceRegion = sourceRegion.intersection(rect); 197 } 198 199 IIOMetadata imageMetadata = image.getMetadata(); 200 BMPMetadata bmpImageMetadata = null; 201 if (imageMetadata != null 202 && imageMetadata instanceof BMPMetadata) 203 { 204 bmpImageMetadata = (BMPMetadata)imageMetadata; 205 } else { 206 ImageTypeSpecifier imageType = 207 new ImageTypeSpecifier(colorModel, sampleModel); 208 209 bmpImageMetadata = (BMPMetadata)getDefaultImageMetadata(imageType, 210 param); 211 } 212 213 if (sourceRegion.isEmpty()) 214 throw new RuntimeException(I18N.getString("BMPImageWrite0")); 215 216 int scaleX = param.getSourceXSubsampling(); 217 int scaleY = param.getSourceYSubsampling(); 218 int xOffset = param.getSubsamplingXOffset(); 219 int yOffset = param.getSubsamplingYOffset(); 220 221 // cache the data type; 222 int dataType = sampleModel.getDataType(); 223 224 sourceRegion.translate(xOffset, yOffset); 225 sourceRegion.width -= xOffset; 226 sourceRegion.height -= yOffset; 227 228 int minX = sourceRegion.x / scaleX; 229 int minY = sourceRegion.y / scaleY; 230 w = (sourceRegion.width + scaleX - 1) / scaleX; 231 h = (sourceRegion.height + scaleY - 1) / scaleY; 232 xOffset = sourceRegion.x % scaleX; 233 yOffset = sourceRegion.y % scaleY; 234 235 Rectangle destinationRegion = new Rectangle(minX, minY, w, h); 236 boolean noTransform = destinationRegion.equals(sourceRegion); 237 238 // Raw data can only handle bytes, everything greater must be ASCII. 239 int[] sourceBands = param.getSourceBands(); 240 boolean noSubband = true; 241 int numBands = sampleModel.getNumBands(); 242 243 if (sourceBands != null) { 244 sampleModel = sampleModel.createSubsetSampleModel(sourceBands); 245 colorModel = null; 246 noSubband = false; 247 numBands = sampleModel.getNumBands(); 248 } else { 249 sourceBands = new int[numBands]; 250 for (int i = 0; i < numBands; i++) 251 sourceBands[i] = i; 252 } 253 254 int[] bandOffsets = null; 255 boolean bgrOrder = true; 256 257 if (sampleModel instanceof ComponentSampleModel) { 258 bandOffsets = ((ComponentSampleModel)sampleModel).getBandOffsets(); 259 if (sampleModel instanceof BandedSampleModel) { 260 // for images with BandedSampleModel we can not work 261 // with raster directly and must use writePixels() 262 bgrOrder = false; 263 } else { 264 // we can work with raster directly only in case of 265 // BGR component order. 266 // In any other case we must use writePixels() 267 for (int i = 0; i < bandOffsets.length; i++) { 268 bgrOrder &= (bandOffsets[i] == (bandOffsets.length - i - 1)); 269 } 270 } 271 } else { 272 if (sampleModel instanceof SinglePixelPackedSampleModel) { 273 274 // BugId 4892214: we can not work with raster directly 275 // if image have different color order than RGB. 276 // We should use writePixels() for such images. 277 int[] bitOffsets = ((SinglePixelPackedSampleModel)sampleModel).getBitOffsets(); 278 for (int i=0; i<bitOffsets.length-1; i++) { 279 bgrOrder &= bitOffsets[i] > bitOffsets[i+1]; 280 } 281 } 282 } 283 284 if (bandOffsets == null) { 285 // we will use getPixels() to extract pixel data for writePixels() 286 // Please note that getPixels() provides rgb bands order. 287 bandOffsets = new int[numBands]; 288 for (int i = 0; i < numBands; i++) 289 bandOffsets[i] = i; 290 } 291 292 noTransform &= bgrOrder; 293 294 int sampleSize[] = sampleModel.getSampleSize(); 295 296 //XXX: check more 297 298 // Number of bytes that a scanline for the image written out will have. 299 int destScanlineBytes = w * numBands; 300 301 switch(bmpParam.getCompressionMode()) { 302 case ImageWriteParam.MODE_EXPLICIT: 303 compressionType = BMPCompressionTypes.getType(bmpParam.getCompressionType()); 304 break; 305 case ImageWriteParam.MODE_COPY_FROM_METADATA: 306 compressionType = bmpImageMetadata.compression; 307 break; 308 case ImageWriteParam.MODE_DEFAULT: 309 compressionType = getPreferredCompressionType(colorModel, sampleModel); 310 break; 311 default: 312 // ImageWriteParam.MODE_DISABLED: 313 compressionType = BI_RGB; 314 } 315 316 if (!canEncodeImage(compressionType, colorModel, sampleModel)) { 317 throw new IOException("Image can not be encoded with compression type " 318 + BMPCompressionTypes.getName(compressionType)); 319 } 320 321 byte r[] = null, g[] = null, b[] = null, a[] = null; 322 323 if (compressionType == BI_BITFIELDS) { 324 bitsPerPixel = 325 DataBuffer.getDataTypeSize(sampleModel.getDataType()); 326 327 if (bitsPerPixel != 16 && bitsPerPixel != 32) { 328 // we should use 32bpp images in case of BI_BITFIELD 329 // compression to avoid color conversion artefacts 330 bitsPerPixel = 32; 331 332 // Setting this flag to false ensures that generic 333 // writePixels() will be used to store image data 334 noTransform = false; 335 } 336 337 destScanlineBytes = w * bitsPerPixel + 7 >> 3; 338 339 isPalette = true; 340 paletteEntries = 3; 341 r = new byte[paletteEntries]; 342 g = new byte[paletteEntries]; 343 b = new byte[paletteEntries]; 344 a = new byte[paletteEntries]; 345 346 int rmask = 0x00ff0000; 347 int gmask = 0x0000ff00; 348 int bmask = 0x000000ff; 349 350 if (bitsPerPixel == 16) { 351 /* NB: canEncodeImage() ensures we have image of 352 * either USHORT_565_RGB or USHORT_555_RGB type here. 353 * Technically, it should work for other direct color 354 * model types but it might be non compatible with win98 355 * and friends. 356 */ 357 if (colorModel instanceof DirectColorModel) { 358 DirectColorModel dcm = (DirectColorModel)colorModel; 359 rmask = dcm.getRedMask(); 360 gmask = dcm.getGreenMask(); 361 bmask = dcm.getBlueMask(); 362 } else { 363 // it is unlikely, but if it happens, we should throw 364 // an exception related to unsupported image format 365 throw new IOException("Image can not be encoded with " + 366 "compression type " + 367 BMPCompressionTypes.getName(compressionType)); 368 } 369 } 370 writeMaskToPalette(rmask, 0, r, g, b, a); 371 writeMaskToPalette(gmask, 1, r, g, b, a); 372 writeMaskToPalette(bmask, 2, r, g, b, a); 373 374 if (!noTransform) { 375 // prepare info for writePixels procedure 376 bitMasks = new int[3]; 377 bitMasks[0] = rmask; 378 bitMasks[1] = gmask; 379 bitMasks[2] = bmask; 380 381 bitPos = new int[3]; 382 bitPos[0] = firstLowBit(rmask); 383 bitPos[1] = firstLowBit(gmask); 384 bitPos[2] = firstLowBit(bmask); 385 } 386 387 if (colorModel instanceof IndexColorModel) { 388 icm = (IndexColorModel)colorModel; 389 } 390 } else { // handle BI_RGB compression 391 if (colorModel instanceof IndexColorModel) { 392 isPalette = true; 393 icm = (IndexColorModel)colorModel; 394 paletteEntries = icm.getMapSize(); 395 396 if (paletteEntries <= 2) { 397 bitsPerPixel = 1; 398 destScanlineBytes = w + 7 >> 3; 399 } else if (paletteEntries <= 16) { 400 bitsPerPixel = 4; 401 destScanlineBytes = w + 1 >> 1; 402 } else if (paletteEntries <= 256) { 403 bitsPerPixel = 8; 404 } else { 405 // Cannot be written as a Palette image. So write out as 406 // 24 bit image. 407 bitsPerPixel = 24; 408 isPalette = false; 409 paletteEntries = 0; 410 destScanlineBytes = w * 3; 411 } 412 413 if (isPalette == true) { 414 r = new byte[paletteEntries]; 415 g = new byte[paletteEntries]; 416 b = new byte[paletteEntries]; 417 a = new byte[paletteEntries]; 418 419 icm.getAlphas(a); 420 icm.getReds(r); 421 icm.getGreens(g); 422 icm.getBlues(b); 423 } 424 425 } else { 426 // Grey scale images 427 if (numBands == 1) { 428 429 isPalette = true; 430 paletteEntries = 256; 431 bitsPerPixel = sampleSize[0]; 432 433 destScanlineBytes = (w * bitsPerPixel + 7 >> 3); 434 435 r = new byte[256]; 436 g = new byte[256]; 437 b = new byte[256]; 438 a = new byte[256]; 439 440 for (int i = 0; i < 256; i++) { 441 r[i] = (byte)i; 442 g[i] = (byte)i; 443 b[i] = (byte)i; 444 a[i] = (byte)255; 445 } 446 447 } else { 448 if (sampleModel instanceof SinglePixelPackedSampleModel && 449 noSubband) 450 { 451 /* NB: the actual pixel size can be smaller than 452 * size of used DataBuffer element. 453 * For example: in case of TYPE_INT_RGB actual pixel 454 * size is 24 bits, but size of DataBuffere element 455 * is 32 bits 456 */ 457 int[] sample_sizes = sampleModel.getSampleSize(); 458 bitsPerPixel = 0; 459 for (int size : sample_sizes) { 460 bitsPerPixel += size; 461 } 462 bitsPerPixel = roundBpp(bitsPerPixel); 463 if (bitsPerPixel != DataBuffer.getDataTypeSize(sampleModel.getDataType())) { 464 noTransform = false; 465 } 466 destScanlineBytes = w * bitsPerPixel + 7 >> 3; 467 } 468 } 469 } 470 } 471 472 // actual writing of image data 473 int fileSize = 0; 474 int offset = 0; 475 int headerSize = 0; 476 int imageSize = 0; 477 int xPelsPerMeter = 0; 478 int yPelsPerMeter = 0; 479 int colorsUsed = 0; 480 int colorsImportant = paletteEntries; 481 482 // Calculate padding for each scanline 483 int padding = destScanlineBytes % 4; 484 if (padding != 0) { 485 padding = 4 - padding; 486 } 487 488 489 // FileHeader is 14 bytes, BitmapHeader is 40 bytes, 490 // add palette size and that is where the data will begin 491 offset = 54 + paletteEntries * 4; 492 493 imageSize = (destScanlineBytes + padding) * h; 494 fileSize = imageSize + offset; 495 headerSize = 40; 496 497 long headPos = stream.getStreamPosition(); 498 499 writeFileHeader(fileSize, offset); 500 501 /* According to MSDN description, the top-down image layout 502 * is allowed only if compression type is BI_RGB or BI_BITFIELDS. 503 * Images with any other compression type must be wrote in the 504 * bottom-up layout. 505 */ 506 if (compressionType == BI_RGB || 507 compressionType == BI_BITFIELDS) 508 { 509 isTopDown = bmpParam.isTopDown(); 510 } else { 511 isTopDown = false; 512 } 513 514 writeInfoHeader(headerSize, bitsPerPixel); 515 516 // compression 517 stream.writeInt(compressionType); 518 519 // imageSize 520 stream.writeInt(imageSize); 521 522 // xPelsPerMeter 523 stream.writeInt(xPelsPerMeter); 524 525 // yPelsPerMeter 526 stream.writeInt(yPelsPerMeter); 527 528 // Colors Used 529 stream.writeInt(colorsUsed); 530 531 // Colors Important 532 stream.writeInt(colorsImportant); 533 534 // palette 535 if (isPalette == true) { 536 537 // write palette 538 if (compressionType == BI_BITFIELDS) { 539 // write masks for red, green and blue components. 540 for (int i=0; i<3; i++) { 541 int mask = (a[i]&0xFF) + ((r[i]&0xFF)*0x100) + ((g[i]&0xFF)*0x10000) + ((b[i]&0xFF)*0x1000000); 542 stream.writeInt(mask); 543 } 544 } else { 545 for (int i=0; i<paletteEntries; i++) { 546 stream.writeByte(b[i]); 547 stream.writeByte(g[i]); 548 stream.writeByte(r[i]); 549 stream.writeByte(a[i]); 550 } 551 } 552 } 553 554 // Writing of actual image data 555 int scanlineBytes = w * numBands; 556 557 // Buffer for up to 8 rows of pixels 558 int[] pixels = new int[scanlineBytes * scaleX]; 559 560 // Also create a buffer to hold one line of the data 561 // to be written to the file, so we can use array writes. 562 bpixels = new byte[destScanlineBytes]; 563 564 int l; 565 566 if (compressionType == BI_JPEG || 567 compressionType == BI_PNG) { 568 569 // prepare embedded buffer 570 embedded_stream = new ByteArrayOutputStream(); 571 writeEmbedded(image, bmpParam); 572 // update the file/image Size 573 embedded_stream.flush(); 574 imageSize = embedded_stream.size(); 575 576 long endPos = stream.getStreamPosition(); 577 fileSize = (int)(offset + imageSize); 578 stream.seek(headPos); 579 writeSize(fileSize, 2); 580 stream.seek(headPos); 581 writeSize(imageSize, 34); 582 stream.seek(endPos); 583 stream.write(embedded_stream.toByteArray()); 584 embedded_stream = null; 585 586 if (abortRequested()) { 587 processWriteAborted(); 588 } else { 589 processImageComplete(); 590 stream.flushBefore(stream.getStreamPosition()); 591 } 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 if (abortRequested()) { 610 break; 611 } 612 613 int row = minY + i; 614 615 if (!isTopDown) 616 row = minY + h - i -1; 617 618 // Get the pixels 619 Raster src = inputRaster; 620 621 Rectangle srcRect = 622 new Rectangle(minX * scaleX + xOffset, 623 row * scaleY + yOffset, 624 (w - 1)* scaleX + 1, 625 1); 626 if (!writeRaster) 627 src = input.getData(srcRect); 628 629 if (noTransform && noSubband) { 630 SampleModel sm = src.getSampleModel(); 631 int pos = 0; 632 int startX = srcRect.x - src.getSampleModelTranslateX(); 633 int startY = srcRect.y - src.getSampleModelTranslateY(); 634 if (sm instanceof ComponentSampleModel) { 635 ComponentSampleModel csm = (ComponentSampleModel)sm; 636 pos = csm.getOffset(startX, startY, 0); 637 for(int nb=1; nb < csm.getNumBands(); nb++) { 638 if (pos > csm.getOffset(startX, startY, nb)) { 639 pos = csm.getOffset(startX, startY, nb); 640 } 641 } 642 } else if (sm instanceof MultiPixelPackedSampleModel) { 643 MultiPixelPackedSampleModel mppsm = 644 (MultiPixelPackedSampleModel)sm; 645 pos = mppsm.getOffset(startX, startY); 646 } else if (sm instanceof SinglePixelPackedSampleModel) { 647 SinglePixelPackedSampleModel sppsm = 648 (SinglePixelPackedSampleModel)sm; 649 pos = sppsm.getOffset(startX, startY); 650 } 651 652 if (compressionType == BI_RGB || compressionType == BI_BITFIELDS){ 653 switch(dataType) { 654 case DataBuffer.TYPE_BYTE: 655 byte[] bdata = 656 ((DataBufferByte)src.getDataBuffer()).getData(); 657 stream.write(bdata, pos, destScanlineLength); 658 break; 659 660 case DataBuffer.TYPE_SHORT: 661 short[] sdata = 662 ((DataBufferShort)src.getDataBuffer()).getData(); 663 stream.writeShorts(sdata, pos, destScanlineLength); 664 break; 665 666 case DataBuffer.TYPE_USHORT: 667 short[] usdata = 668 ((DataBufferUShort)src.getDataBuffer()).getData(); 669 stream.writeShorts(usdata, pos, destScanlineLength); 670 break; 671 672 case DataBuffer.TYPE_INT: 673 int[] idata = 674 ((DataBufferInt)src.getDataBuffer()).getData(); 675 stream.writeInts(idata, pos, destScanlineLength); 676 break; 677 } 678 679 for(int k=0; k<padding; k++) { 680 stream.writeByte(0); 681 } 682 } else if (compressionType == BI_RLE4) { 683 if (bpixels == null || bpixels.length < scanlineBytes) 684 bpixels = new byte[scanlineBytes]; 685 src.getPixels(srcRect.x, srcRect.y, 686 srcRect.width, srcRect.height, pixels); 687 for (int h=0; h<scanlineBytes; h++) { 688 bpixels[h] = (byte)pixels[h]; 689 } 690 encodeRLE4(bpixels, scanlineBytes); 691 } else if (compressionType == BI_RLE8) { 692 //byte[] bdata = 693 // ((DataBufferByte)src.getDataBuffer()).getData(); 694 //System.out.println("bdata.length="+bdata.length); 695 //System.arraycopy(bdata, pos, bpixels, 0, scanlineBytes); 696 if (bpixels == null || bpixels.length < scanlineBytes) 697 bpixels = new byte[scanlineBytes]; 698 src.getPixels(srcRect.x, srcRect.y, 699 srcRect.width, srcRect.height, pixels); 700 for (int h=0; h<scanlineBytes; h++) { 701 bpixels[h] = (byte)pixels[h]; 702 } 703 704 encodeRLE8(bpixels, scanlineBytes); 705 } 706 } else { 707 src.getPixels(srcRect.x, srcRect.y, 708 srcRect.width, srcRect.height, pixels); 709 710 if (scaleX != 1 || maxBandOffset != numBands - 1) { 711 for (int j = 0, k = 0, n=0; j < w; 712 j++, k += scaleX * numBands, n += numBands) 713 { 714 System.arraycopy(pixels, k, pixel, 0, pixel.length); 715 716 for (int m = 0; m < numBands; m++) { 717 // pixel data is provided here in RGB order 718 pixels[n + m] = pixel[sourceBands[m]]; 719 } 720 } 721 } 722 writePixels(0, scanlineBytes, bitsPerPixel, pixels, 723 padding, numBands, icm); 724 } 725 726 processImageProgress(100.0f * (((float)i) / ((float)h))); 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 iterator = ImageIO.getImageWritersByFormatName(format); 1329 ImageWriter writer = null; 1330 if (iterator.hasNext()) 1331 writer = (ImageWriter)iterator.next(); 1332 if (writer != null) { 1333 if (embedded_stream == null) { 1334 throw new RuntimeException("No stream for writing embedded image!"); 1335 } 1336 1337 writer.addIIOWriteProgressListener(new IIOWriteProgressAdapter() { 1338 public void imageProgress(ImageWriter source, float percentageDone) { 1339 processImageProgress(percentageDone); 1340 } 1341 }); 1342 1343 writer.addIIOWriteWarningListener(new IIOWriteWarningListener() { 1344 public void warningOccurred(ImageWriter source, int imageIndex, String warning) { 1345 processWarningOccurred(imageIndex, warning); 1346 } 1347 }); 1348 1349 writer.setOutput(ImageIO.createImageOutputStream(embedded_stream)); 1350 ImageWriteParam param = writer.getDefaultWriteParam(); 1351 //param.setDestinationBands(bmpParam.getDestinationBands()); 1352 param.setDestinationOffset(bmpParam.getDestinationOffset()); 1353 param.setSourceBands(bmpParam.getSourceBands()); 1354 param.setSourceRegion(bmpParam.getSourceRegion()); 1355 param.setSourceSubsampling(bmpParam.getSourceXSubsampling(), 1356 bmpParam.getSourceYSubsampling(), 1357 bmpParam.getSubsamplingXOffset(), 1358 bmpParam.getSubsamplingYOffset()); 1359 writer.write(null, image, param); 1360 } else 1361 throw new RuntimeException(I18N.getString("BMPImageWrite5") + " " + format); 1362 1363 } 1364 firstLowBit(int num)1365 private int firstLowBit(int num) { 1366 int count = 0; 1367 while ((num & 1) == 0) { 1368 count++; 1369 num >>>= 1; 1370 } 1371 return count; 1372 } 1373 1374 private class IIOWriteProgressAdapter implements IIOWriteProgressListener { 1375 imageComplete(ImageWriter source)1376 public void imageComplete(ImageWriter source) { 1377 } 1378 imageProgress(ImageWriter source, float percentageDone)1379 public void imageProgress(ImageWriter source, float percentageDone) { 1380 } 1381 imageStarted(ImageWriter source, int imageIndex)1382 public void imageStarted(ImageWriter source, int imageIndex) { 1383 } 1384 thumbnailComplete(ImageWriter source)1385 public void thumbnailComplete(ImageWriter source) { 1386 } 1387 thumbnailProgress(ImageWriter source, float percentageDone)1388 public void thumbnailProgress(ImageWriter source, float percentageDone) { 1389 } 1390 thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex)1391 public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) { 1392 } 1393 writeAborted(ImageWriter source)1394 public void writeAborted(ImageWriter source) { 1395 } 1396 } 1397 1398 /* 1399 * Returns preferred compression type for given image. 1400 * The default compression type is BI_RGB, but some image types can't be 1401 * encodeed with using default compression without cahnge color resolution. 1402 * For example, TYPE_USHORT_565_RGB may be encodeed only by using BI_BITFIELDS 1403 * compression type. 1404 * 1405 * NB: we probably need to extend this method if we encounter other image 1406 * types which can not be encoded with BI_RGB compression type. 1407 */ getPreferredCompressionType(ColorModel cm, SampleModel sm)1408 protected int getPreferredCompressionType(ColorModel cm, SampleModel sm) { 1409 ImageTypeSpecifier imageType = new ImageTypeSpecifier(cm, sm); 1410 return getPreferredCompressionType(imageType); 1411 } 1412 getPreferredCompressionType(ImageTypeSpecifier imageType)1413 protected int getPreferredCompressionType(ImageTypeSpecifier imageType) { 1414 if (imageType.getBufferedImageType() == BufferedImage.TYPE_USHORT_565_RGB) { 1415 return BI_BITFIELDS; 1416 } 1417 return BI_RGB; 1418 } 1419 1420 /* 1421 * Check whether we can encode image of given type using compression method in question. 1422 * 1423 * For example, TYPE_USHORT_565_RGB can be encodeed with BI_BITFIELDS compression only. 1424 * 1425 * NB: method should be extended if other cases when we can not encode 1426 * with given compression will be discovered. 1427 */ canEncodeImage(int compression, ColorModel cm, SampleModel sm)1428 protected boolean canEncodeImage(int compression, ColorModel cm, SampleModel sm) { 1429 ImageTypeSpecifier imgType = new ImageTypeSpecifier(cm, sm); 1430 return canEncodeImage(compression, imgType); 1431 } 1432 canEncodeImage(int compression, ImageTypeSpecifier imgType)1433 protected boolean canEncodeImage(int compression, ImageTypeSpecifier imgType) { 1434 ImageWriterSpi spi = this.getOriginatingProvider(); 1435 if (!spi.canEncodeImage(imgType)) { 1436 return false; 1437 } 1438 int biType = imgType.getBufferedImageType(); 1439 int bpp = imgType.getColorModel().getPixelSize(); 1440 if (compressionType == BI_RLE4 && bpp != 4) { 1441 // only 4bpp images can be encoded as BI_RLE4 1442 return false; 1443 } 1444 if (compressionType == BI_RLE8 && bpp != 8) { 1445 // only 8bpp images can be encoded as BI_RLE8 1446 return false; 1447 } 1448 if (bpp == 16) { 1449 /* 1450 * Technically we expect that we may be able to 1451 * encode only some of SinglePixelPackedSampleModel 1452 * images here. 1453 * 1454 * In addition we should take into account following: 1455 * 1456 * 1. BI_RGB case, according to the MSDN description: 1457 * 1458 * The bitmap has a maximum of 2^16 colors. If the 1459 * biCompression member of the BITMAPINFOHEADER is BI_RGB, 1460 * the bmiColors member of BITMAPINFO is NULL. Each WORD 1461 * in the bitmap array represents a single pixel. The 1462 * relative intensities of red, green, and blue are 1463 * represented with five bits for each color component. 1464 * 1465 * 2. BI_BITFIELDS case, according ot the MSDN description: 1466 * 1467 * Windows 95/98/Me: When the biCompression member is 1468 * BI_BITFIELDS, the system supports only the following 1469 * 16bpp color masks: A 5-5-5 16-bit image, where the blue 1470 * mask is 0x001F, the green mask is 0x03E0, and the red mask 1471 * is 0x7C00; and a 5-6-5 16-bit image, where the blue mask 1472 * is 0x001F, the green mask is 0x07E0, and the red mask is 1473 * 0xF800. 1474 */ 1475 boolean canUseRGB = false; 1476 boolean canUseBITFIELDS = false; 1477 1478 SampleModel sm = imgType.getSampleModel(); 1479 if (sm instanceof SinglePixelPackedSampleModel) { 1480 int[] sizes = 1481 ((SinglePixelPackedSampleModel)sm).getSampleSize(); 1482 1483 canUseRGB = true; 1484 canUseBITFIELDS = true; 1485 for (int i = 0; i < sizes.length; i++) { 1486 canUseRGB &= (sizes[i] == 5); 1487 canUseBITFIELDS &= ((sizes[i] == 5) || 1488 (i == 1 && sizes[i] == 6)); 1489 } 1490 } 1491 1492 return (((compressionType == BI_RGB) && canUseRGB) || 1493 ((compressionType == BI_BITFIELDS) && canUseBITFIELDS)); 1494 } 1495 return true; 1496 } 1497 writeMaskToPalette(int mask, int i, byte[] r, byte[]g, byte[] b, byte[]a)1498 protected void writeMaskToPalette(int mask, int i, 1499 byte[] r, byte[]g, byte[] b, byte[]a) { 1500 b[i] = (byte)(0xff & (mask >> 24)); 1501 g[i] = (byte)(0xff & (mask >> 16)); 1502 r[i] = (byte)(0xff & (mask >> 8)); 1503 a[i] = (byte)(0xff & mask); 1504 } 1505 roundBpp(int x)1506 private int roundBpp(int x) { 1507 if (x <= 8) { 1508 return 8; 1509 } else if (x <= 16) { 1510 return 16; 1511 } if (x <= 24) { 1512 return 24; 1513 } else { 1514 return 32; 1515 } 1516 } 1517 } 1518