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