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