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