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