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