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