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