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