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