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