1 /*
2  * Copyright (c) 2003, 2013, 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</code>.
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</code> based on the provided
92      *  <code>ImageWriterSpi</code>.
93      */
BMPImageWriter(ImageWriterSpi originator)94     public BMPImageWriter(ImageWriterSpi originator) {
95         super(originator);
96     }
97 
setOutput(Object output)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 
getDefaultWriteParam()109     public ImageWriteParam getDefaultWriteParam() {
110         return new BMPImageWriteParam();
111     }
112 
getDefaultStreamMetadata(ImageWriteParam param)113     public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
114         return null;
115     }
116 
getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param)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 
convertStreamMetadata(IIOMetadata inData, ImageWriteParam param)130     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
131                                              ImageWriteParam param) {
132         return null;
133     }
134 
convertImageMetadata(IIOMetadata metadata, ImageTypeSpecifier type, ImageWriteParam param)135     public IIOMetadata convertImageMetadata(IIOMetadata metadata,
136                                             ImageTypeSpecifier type,
137                                             ImageWriteParam param) {
138         return null;
139     }
140 
canWriteRasters()141     public boolean canWriteRasters() {
142         return true;
143     }
144 
write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param)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 (param == null)
160             param = getDefaultWriteParam();
161 
162         BMPImageWriteParam bmpParam = (BMPImageWriteParam)param;
163 
164         // Default is using 24 bits per pixel.
165         int bitsPerPixel = 24;
166         boolean isPalette = false;
167         int paletteEntries = 0;
168         IndexColorModel icm = null;
169 
170         RenderedImage input = null;
171         Raster inputRaster = null;
172         boolean writeRaster = image.hasRaster();
173         Rectangle sourceRegion = param.getSourceRegion();
174         SampleModel sampleModel = null;
175         ColorModel colorModel = null;
176 
177         compImageSize = 0;
178 
179         if (writeRaster) {
180             inputRaster = image.getRaster();
181             sampleModel = inputRaster.getSampleModel();
182             colorModel = ImageUtil.createColorModel(null, sampleModel);
183             if (sourceRegion == null)
184                 sourceRegion = inputRaster.getBounds();
185             else
186                 sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
187         } else {
188             input = image.getRenderedImage();
189             sampleModel = input.getSampleModel();
190             colorModel = input.getColorModel();
191             Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(),
192                                            input.getWidth(), input.getHeight());
193             if (sourceRegion == null)
194                 sourceRegion = rect;
195             else
196                 sourceRegion = sourceRegion.intersection(rect);
197         }
198 
199         IIOMetadata imageMetadata = image.getMetadata();
200         BMPMetadata bmpImageMetadata = null;
201         if (imageMetadata != null
202             && imageMetadata instanceof BMPMetadata)
203         {
204             bmpImageMetadata = (BMPMetadata)imageMetadata;
205         } else {
206             ImageTypeSpecifier imageType =
207                 new ImageTypeSpecifier(colorModel, sampleModel);
208 
209             bmpImageMetadata = (BMPMetadata)getDefaultImageMetadata(imageType,
210                                                                     param);
211         }
212 
213         if (sourceRegion.isEmpty())
214             throw new RuntimeException(I18N.getString("BMPImageWrite0"));
215 
216         int scaleX = param.getSourceXSubsampling();
217         int scaleY = param.getSourceYSubsampling();
218         int xOffset = param.getSubsamplingXOffset();
219         int yOffset = param.getSubsamplingYOffset();
220 
221         // cache the data type;
222         int dataType = sampleModel.getDataType();
223 
224         sourceRegion.translate(xOffset, yOffset);
225         sourceRegion.width -= xOffset;
226         sourceRegion.height -= yOffset;
227 
228         int minX = sourceRegion.x / scaleX;
229         int minY = sourceRegion.y / scaleY;
230         w = (sourceRegion.width + scaleX - 1) / scaleX;
231         h = (sourceRegion.height + scaleY - 1) / scaleY;
232         xOffset = sourceRegion.x % scaleX;
233         yOffset = sourceRegion.y % scaleY;
234 
235         Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
236         boolean noTransform = destinationRegion.equals(sourceRegion);
237 
238         // Raw data can only handle bytes, everything greater must be ASCII.
239         int[] sourceBands = param.getSourceBands();
240         boolean noSubband = true;
241         int numBands = sampleModel.getNumBands();
242 
243         if (sourceBands != null) {
244             sampleModel = sampleModel.createSubsetSampleModel(sourceBands);
245             colorModel = null;
246             noSubband = false;
247             numBands = sampleModel.getNumBands();
248         } else {
249             sourceBands = new int[numBands];
250             for (int i = 0; i < numBands; i++)
251                 sourceBands[i] = i;
252         }
253 
254         int[] bandOffsets = null;
255         boolean bgrOrder = true;
256 
257         if (sampleModel instanceof ComponentSampleModel) {
258             bandOffsets = ((ComponentSampleModel)sampleModel).getBandOffsets();
259             if (sampleModel instanceof BandedSampleModel) {
260                 // for images with BandedSampleModel we can not work
261                 //  with raster directly and must use writePixels()
262                 bgrOrder = false;
263             } else {
264                 // we can work with raster directly only in case of
265                 // BGR component order.
266                 // In any other case we must use writePixels()
267                 for (int i = 0; i < bandOffsets.length; i++) {
268                     bgrOrder &= (bandOffsets[i] == (bandOffsets.length - i - 1));
269                 }
270             }
271         } else {
272             if (sampleModel instanceof SinglePixelPackedSampleModel) {
273 
274                 // BugId 4892214: we can not work with raster directly
275                 // if image have different color order than RGB.
276                 // We should use writePixels() for such images.
277                 int[] bitOffsets = ((SinglePixelPackedSampleModel)sampleModel).getBitOffsets();
278                 for (int i=0; i<bitOffsets.length-1; i++) {
279                     bgrOrder &= bitOffsets[i] > bitOffsets[i+1];
280                 }
281             }
282         }
283 
284         if (bandOffsets == null) {
285             // we will use getPixels() to extract pixel data for writePixels()
286             // Please note that getPixels() provides rgb bands order.
287             bandOffsets = new int[numBands];
288             for (int i = 0; i < numBands; i++)
289                 bandOffsets[i] = i;
290         }
291 
292         noTransform &= bgrOrder;
293 
294         int sampleSize[] = sampleModel.getSampleSize();
295 
296         //XXX: check more
297 
298         // Number of bytes that a scanline for the image written out will have.
299         int destScanlineBytes = w * numBands;
300 
301         switch(bmpParam.getCompressionMode()) {
302         case ImageWriteParam.MODE_EXPLICIT:
303             compressionType = BMPCompressionTypes.getType(bmpParam.getCompressionType());
304             break;
305         case ImageWriteParam.MODE_COPY_FROM_METADATA:
306             compressionType = bmpImageMetadata.compression;
307             break;
308         case ImageWriteParam.MODE_DEFAULT:
309             compressionType = getPreferredCompressionType(colorModel, sampleModel);
310             break;
311         default:
312             // ImageWriteParam.MODE_DISABLED:
313             compressionType = BI_RGB;
314         }
315 
316         if (!canEncodeImage(compressionType, colorModel, sampleModel)) {
317             throw new IOException("Image can not be encoded with compression type "
318                                   + BMPCompressionTypes.getName(compressionType));
319         }
320 
321         byte r[] = null, g[] = null, b[] = null, a[] = null;
322 
323         if (compressionType == BI_BITFIELDS) {
324             bitsPerPixel =
325                 DataBuffer.getDataTypeSize(sampleModel.getDataType());
326 
327             if (bitsPerPixel != 16 && bitsPerPixel != 32) {
328                 // we should use 32bpp images in case of BI_BITFIELD
329                 // compression to avoid color conversion artefacts
330                 bitsPerPixel = 32;
331 
332                 // Setting this flag to false ensures that generic
333                 // writePixels() will be used to store image data
334                 noTransform = false;
335             }
336 
337             destScanlineBytes = w * bitsPerPixel + 7 >> 3;
338 
339             isPalette = true;
340             paletteEntries = 3;
341             r = new byte[paletteEntries];
342             g = new byte[paletteEntries];
343             b = new byte[paletteEntries];
344             a = new byte[paletteEntries];
345 
346             int rmask = 0x00ff0000;
347             int gmask = 0x0000ff00;
348             int bmask = 0x000000ff;
349 
350             if (bitsPerPixel == 16) {
351                 /* NB: canEncodeImage() ensures we have image of
352                  * either USHORT_565_RGB or USHORT_555_RGB type here.
353                  * Technically, it should work for other direct color
354                  * model types but it might be non compatible with win98
355                  * and friends.
356                  */
357                 if (colorModel instanceof DirectColorModel) {
358                     DirectColorModel dcm = (DirectColorModel)colorModel;
359                     rmask = dcm.getRedMask();
360                     gmask = dcm.getGreenMask();
361                     bmask = dcm.getBlueMask();
362                 } else {
363                     // it is unlikely, but if it happens, we should throw
364                     // an exception related to unsupported image format
365                     throw new IOException("Image can not be encoded with " +
366                                           "compression type " +
367                                           BMPCompressionTypes.getName(compressionType));
368                 }
369             }
370             writeMaskToPalette(rmask, 0, r, g, b, a);
371             writeMaskToPalette(gmask, 1, r, g, b, a);
372             writeMaskToPalette(bmask, 2, r, g, b, a);
373 
374             if (!noTransform) {
375                 // prepare info for writePixels procedure
376                 bitMasks = new int[3];
377                 bitMasks[0] = rmask;
378                 bitMasks[1] = gmask;
379                 bitMasks[2] = bmask;
380 
381                 bitPos = new int[3];
382                 bitPos[0] = firstLowBit(rmask);
383                 bitPos[1] = firstLowBit(gmask);
384                 bitPos[2] = firstLowBit(bmask);
385             }
386 
387             if (colorModel instanceof IndexColorModel) {
388                 icm = (IndexColorModel)colorModel;
389             }
390         } else { // handle BI_RGB compression
391             if (colorModel instanceof IndexColorModel) {
392                 isPalette = true;
393                 icm = (IndexColorModel)colorModel;
394                 paletteEntries = icm.getMapSize();
395 
396                 if (paletteEntries <= 2) {
397                     bitsPerPixel = 1;
398                     destScanlineBytes = w + 7 >> 3;
399                 } else if (paletteEntries <= 16) {
400                     bitsPerPixel = 4;
401                     destScanlineBytes = w + 1 >> 1;
402                 } else if (paletteEntries <= 256) {
403                     bitsPerPixel = 8;
404                 } else {
405                     // Cannot be written as a Palette image. So write out as
406                     // 24 bit image.
407                     bitsPerPixel = 24;
408                     isPalette = false;
409                     paletteEntries = 0;
410                     destScanlineBytes = w * 3;
411                 }
412 
413                 if (isPalette == true) {
414                     r = new byte[paletteEntries];
415                     g = new byte[paletteEntries];
416                     b = new byte[paletteEntries];
417                     a = new byte[paletteEntries];
418 
419                     icm.getAlphas(a);
420                     icm.getReds(r);
421                     icm.getGreens(g);
422                     icm.getBlues(b);
423                 }
424 
425             } else {
426                 // Grey scale images
427                 if (numBands == 1) {
428 
429                     isPalette = true;
430                     paletteEntries = 256;
431                     bitsPerPixel = sampleSize[0];
432 
433                     destScanlineBytes = (w * bitsPerPixel + 7 >> 3);
434 
435                     r = new byte[256];
436                     g = new byte[256];
437                     b = new byte[256];
438                     a = new byte[256];
439 
440                     for (int i = 0; i < 256; i++) {
441                         r[i] = (byte)i;
442                         g[i] = (byte)i;
443                         b[i] = (byte)i;
444                         a[i] = (byte)255;
445                     }
446 
447                 } else {
448                     if (sampleModel instanceof SinglePixelPackedSampleModel &&
449                         noSubband)
450                     {
451                         /* NB: the actual pixel size can be smaller than
452                          * size of used DataBuffer element.
453                          * For example: in case of TYPE_INT_RGB actual pixel
454                          * size is 24 bits, but size of DataBuffere element
455                          * is 32 bits
456                          */
457                         int[] sample_sizes = sampleModel.getSampleSize();
458                         bitsPerPixel = 0;
459                         for (int size : sample_sizes) {
460                             bitsPerPixel += size;
461                         }
462                         bitsPerPixel = roundBpp(bitsPerPixel);
463                         if (bitsPerPixel != DataBuffer.getDataTypeSize(sampleModel.getDataType())) {
464                             noTransform = false;
465                         }
466                         destScanlineBytes = w * bitsPerPixel + 7 >> 3;
467                     }
468                 }
469             }
470         }
471 
472         // actual writing of image data
473         int fileSize = 0;
474         int offset = 0;
475         int headerSize = 0;
476         int imageSize = 0;
477         int xPelsPerMeter = 0;
478         int yPelsPerMeter = 0;
479         int colorsUsed = 0;
480         int colorsImportant = paletteEntries;
481 
482         // Calculate padding for each scanline
483         int padding = destScanlineBytes % 4;
484         if (padding != 0) {
485             padding = 4 - padding;
486         }
487 
488 
489         // FileHeader is 14 bytes, BitmapHeader is 40 bytes,
490         // add palette size and that is where the data will begin
491         offset = 54 + paletteEntries * 4;
492 
493         imageSize = (destScanlineBytes + padding) * h;
494         fileSize = imageSize + offset;
495         headerSize = 40;
496 
497         long headPos = stream.getStreamPosition();
498 
499         writeFileHeader(fileSize, offset);
500 
501         /* According to MSDN description, the top-down image layout
502          * is allowed only if compression type is BI_RGB or BI_BITFIELDS.
503          * Images with any other compression type must be wrote in the
504          * bottom-up layout.
505          */
506         if (compressionType == BI_RGB ||
507             compressionType == BI_BITFIELDS)
508         {
509             isTopDown = bmpParam.isTopDown();
510         } else {
511             isTopDown = false;
512         }
513 
514         writeInfoHeader(headerSize, bitsPerPixel);
515 
516         // compression
517         stream.writeInt(compressionType);
518 
519         // imageSize
520         stream.writeInt(imageSize);
521 
522         // xPelsPerMeter
523         stream.writeInt(xPelsPerMeter);
524 
525         // yPelsPerMeter
526         stream.writeInt(yPelsPerMeter);
527 
528         // Colors Used
529         stream.writeInt(colorsUsed);
530 
531         // Colors Important
532         stream.writeInt(colorsImportant);
533 
534         // palette
535         if (isPalette == true) {
536 
537             // write palette
538             if (compressionType == BI_BITFIELDS) {
539                 // write masks for red, green and blue components.
540                 for (int i=0; i<3; i++) {
541                     int mask = (a[i]&0xFF) + ((r[i]&0xFF)*0x100) + ((g[i]&0xFF)*0x10000) + ((b[i]&0xFF)*0x1000000);
542                     stream.writeInt(mask);
543                 }
544             } else {
545                 for (int i=0; i<paletteEntries; i++) {
546                     stream.writeByte(b[i]);
547                     stream.writeByte(g[i]);
548                     stream.writeByte(r[i]);
549                     stream.writeByte(a[i]);
550                 }
551             }
552         }
553 
554         // Writing of actual image data
555         int scanlineBytes = w * numBands;
556 
557         // Buffer for up to 8 rows of pixels
558         int[] pixels = new int[scanlineBytes * scaleX];
559 
560         // Also create a buffer to hold one line of the data
561         // to be written to the file, so we can use array writes.
562         bpixels = new byte[destScanlineBytes];
563 
564         int l;
565 
566         if (compressionType == BI_JPEG ||
567             compressionType == BI_PNG) {
568 
569             // prepare embedded buffer
570             embedded_stream = new ByteArrayOutputStream();
571             writeEmbedded(image, bmpParam);
572             // update the file/image Size
573             embedded_stream.flush();
574             imageSize = embedded_stream.size();
575 
576             long endPos = stream.getStreamPosition();
577             fileSize = (int)(offset + imageSize);
578             stream.seek(headPos);
579             writeSize(fileSize, 2);
580             stream.seek(headPos);
581             writeSize(imageSize, 34);
582             stream.seek(endPos);
583             stream.write(embedded_stream.toByteArray());
584             embedded_stream = null;
585 
586             if (abortRequested()) {
587                 processWriteAborted();
588             } else {
589                 processImageComplete();
590                 stream.flushBefore(stream.getStreamPosition());
591             }
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             if (abortRequested()) {
610                 break;
611             }
612 
613             int row = minY + i;
614 
615             if (!isTopDown)
616                 row = minY + h - i -1;
617 
618             // Get the pixels
619             Raster src = inputRaster;
620 
621             Rectangle srcRect =
622                 new Rectangle(minX * scaleX + xOffset,
623                               row * scaleY + yOffset,
624                               (w - 1)* scaleX + 1,
625                               1);
626             if (!writeRaster)
627                 src = input.getData(srcRect);
628 
629             if (noTransform && noSubband) {
630                 SampleModel sm = src.getSampleModel();
631                 int pos = 0;
632                 int startX = srcRect.x - src.getSampleModelTranslateX();
633                 int startY = srcRect.y - src.getSampleModelTranslateY();
634                 if (sm instanceof ComponentSampleModel) {
635                     ComponentSampleModel csm = (ComponentSampleModel)sm;
636                     pos = csm.getOffset(startX, startY, 0);
637                     for(int nb=1; nb < csm.getNumBands(); nb++) {
638                         if (pos > csm.getOffset(startX, startY, nb)) {
639                             pos = csm.getOffset(startX, startY, nb);
640                         }
641                     }
642                 } else if (sm instanceof MultiPixelPackedSampleModel) {
643                     MultiPixelPackedSampleModel mppsm =
644                         (MultiPixelPackedSampleModel)sm;
645                     pos = mppsm.getOffset(startX, startY);
646                 } else if (sm instanceof SinglePixelPackedSampleModel) {
647                     SinglePixelPackedSampleModel sppsm =
648                         (SinglePixelPackedSampleModel)sm;
649                     pos = sppsm.getOffset(startX, startY);
650                 }
651 
652                 if (compressionType == BI_RGB || compressionType == BI_BITFIELDS){
653                     switch(dataType) {
654                     case DataBuffer.TYPE_BYTE:
655                         byte[] bdata =
656                             ((DataBufferByte)src.getDataBuffer()).getData();
657                         stream.write(bdata, pos, destScanlineLength);
658                         break;
659 
660                     case DataBuffer.TYPE_SHORT:
661                         short[] sdata =
662                             ((DataBufferShort)src.getDataBuffer()).getData();
663                         stream.writeShorts(sdata, pos, destScanlineLength);
664                         break;
665 
666                     case DataBuffer.TYPE_USHORT:
667                         short[] usdata =
668                             ((DataBufferUShort)src.getDataBuffer()).getData();
669                         stream.writeShorts(usdata, pos, destScanlineLength);
670                         break;
671 
672                     case DataBuffer.TYPE_INT:
673                         int[] idata =
674                             ((DataBufferInt)src.getDataBuffer()).getData();
675                         stream.writeInts(idata, pos, destScanlineLength);
676                         break;
677                     }
678 
679                     for(int k=0; k<padding; k++) {
680                         stream.writeByte(0);
681                     }
682                 } else if (compressionType == BI_RLE4) {
683                     if (bpixels == null || bpixels.length < scanlineBytes)
684                         bpixels = new byte[scanlineBytes];
685                     src.getPixels(srcRect.x, srcRect.y,
686                                   srcRect.width, srcRect.height, pixels);
687                     for (int h=0; h<scanlineBytes; h++) {
688                         bpixels[h] = (byte)pixels[h];
689                     }
690                     encodeRLE4(bpixels, scanlineBytes);
691                 } else if (compressionType == BI_RLE8) {
692                     //byte[] bdata =
693                     //    ((DataBufferByte)src.getDataBuffer()).getData();
694                     //System.out.println("bdata.length="+bdata.length);
695                     //System.arraycopy(bdata, pos, bpixels, 0, scanlineBytes);
696                     if (bpixels == null || bpixels.length < scanlineBytes)
697                         bpixels = new byte[scanlineBytes];
698                     src.getPixels(srcRect.x, srcRect.y,
699                                   srcRect.width, srcRect.height, pixels);
700                     for (int h=0; h<scanlineBytes; h++) {
701                         bpixels[h] = (byte)pixels[h];
702                     }
703 
704                     encodeRLE8(bpixels, scanlineBytes);
705                 }
706             } else {
707                 src.getPixels(srcRect.x, srcRect.y,
708                               srcRect.width, srcRect.height, pixels);
709 
710                 if (scaleX != 1 || maxBandOffset != numBands - 1) {
711                     for (int j = 0, k = 0, n=0; j < w;
712                          j++, k += scaleX * numBands, n += numBands)
713                     {
714                         System.arraycopy(pixels, k, pixel, 0, pixel.length);
715 
716                         for (int m = 0; m < numBands; m++) {
717                             // pixel data is provided here in RGB order
718                             pixels[n + m] = pixel[sourceBands[m]];
719                         }
720                     }
721                 }
722                 writePixels(0, scanlineBytes, bitsPerPixel, pixels,
723                             padding, numBands, icm);
724             }
725 
726             processImageProgress(100.0f * (((float)i) / ((float)h)));
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 
writePixels(int l, int scanlineBytes, int bitsPerPixel, int pixels[], int padding, int numBands, IndexColorModel icm)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 
encodeRLE8(byte[] bpixels, int scanlineBytes)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 
encodeRLE4(byte[] bipixels, int scanlineBytes)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 
incCompImageSize(int value)1272     private synchronized void incCompImageSize(int value){
1273         compImageSize = compImageSize + value;
1274     }
1275 
isEven(int number)1276     private boolean isEven(int number) {
1277         return (number%2 == 0 ? true : false);
1278     }
1279 
writeFileHeader(int fileSize, int offset)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 
writeInfoHeader(int headerSize, int bitsPerPixel)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 
writeSize(int dword, int offset)1314     private void writeSize(int dword, int offset) throws IOException {
1315         stream.skipBytes(offset);
1316         stream.writeInt(dword);
1317     }
1318 
reset()1319     public void reset() {
1320         super.reset();
1321         stream = null;
1322     }
1323 
writeEmbedded(IIOImage image, ImageWriteParam bmpParam)1324     private void writeEmbedded(IIOImage image,
1325                                ImageWriteParam bmpParam) throws IOException {
1326         String format =
1327             compressionType == BI_JPEG ? "jpeg" : "png";
1328         Iterator iterator = ImageIO.getImageWritersByFormatName(format);
1329         ImageWriter writer = null;
1330         if (iterator.hasNext())
1331             writer = (ImageWriter)iterator.next();
1332         if (writer != null) {
1333             if (embedded_stream == null) {
1334                 throw new RuntimeException("No stream for writing embedded image!");
1335             }
1336 
1337             writer.addIIOWriteProgressListener(new IIOWriteProgressAdapter() {
1338                     public void imageProgress(ImageWriter source, float percentageDone) {
1339                         processImageProgress(percentageDone);
1340                     }
1341                 });
1342 
1343             writer.addIIOWriteWarningListener(new IIOWriteWarningListener() {
1344                     public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
1345                         processWarningOccurred(imageIndex, warning);
1346                     }
1347                 });
1348 
1349             writer.setOutput(ImageIO.createImageOutputStream(embedded_stream));
1350             ImageWriteParam param = writer.getDefaultWriteParam();
1351             //param.setDestinationBands(bmpParam.getDestinationBands());
1352             param.setDestinationOffset(bmpParam.getDestinationOffset());
1353             param.setSourceBands(bmpParam.getSourceBands());
1354             param.setSourceRegion(bmpParam.getSourceRegion());
1355             param.setSourceSubsampling(bmpParam.getSourceXSubsampling(),
1356                                        bmpParam.getSourceYSubsampling(),
1357                                        bmpParam.getSubsamplingXOffset(),
1358                                        bmpParam.getSubsamplingYOffset());
1359             writer.write(null, image, param);
1360         } else
1361             throw new RuntimeException(I18N.getString("BMPImageWrite5") + " " + format);
1362 
1363     }
1364 
firstLowBit(int num)1365     private int firstLowBit(int num) {
1366         int count = 0;
1367         while ((num & 1) == 0) {
1368             count++;
1369             num >>>= 1;
1370         }
1371         return count;
1372     }
1373 
1374     private class IIOWriteProgressAdapter implements IIOWriteProgressListener {
1375 
imageComplete(ImageWriter source)1376         public void imageComplete(ImageWriter source) {
1377         }
1378 
imageProgress(ImageWriter source, float percentageDone)1379         public void imageProgress(ImageWriter source, float percentageDone) {
1380         }
1381 
imageStarted(ImageWriter source, int imageIndex)1382         public void imageStarted(ImageWriter source, int imageIndex) {
1383         }
1384 
thumbnailComplete(ImageWriter source)1385         public void thumbnailComplete(ImageWriter source) {
1386         }
1387 
thumbnailProgress(ImageWriter source, float percentageDone)1388         public void thumbnailProgress(ImageWriter source, float percentageDone) {
1389         }
1390 
thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex)1391         public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) {
1392         }
1393 
writeAborted(ImageWriter source)1394         public void writeAborted(ImageWriter source) {
1395         }
1396     }
1397 
1398     /*
1399      * Returns preferred compression type for given image.
1400      * The default compression type is BI_RGB, but some image types can't be
1401      * encodeed with using default compression without cahnge color resolution.
1402      * For example, TYPE_USHORT_565_RGB may be encodeed only by using BI_BITFIELDS
1403      * compression type.
1404      *
1405      * NB: we probably need to extend this method if we encounter other image
1406      * types which can not be encoded with BI_RGB compression type.
1407      */
getPreferredCompressionType(ColorModel cm, SampleModel sm)1408     protected int getPreferredCompressionType(ColorModel cm, SampleModel sm) {
1409         ImageTypeSpecifier imageType = new ImageTypeSpecifier(cm, sm);
1410         return getPreferredCompressionType(imageType);
1411     }
1412 
getPreferredCompressionType(ImageTypeSpecifier imageType)1413     protected int getPreferredCompressionType(ImageTypeSpecifier imageType) {
1414         if (imageType.getBufferedImageType() == BufferedImage.TYPE_USHORT_565_RGB) {
1415             return  BI_BITFIELDS;
1416         }
1417         return BI_RGB;
1418     }
1419 
1420     /*
1421      * Check whether we can encode image of given type using compression method in question.
1422      *
1423      * For example, TYPE_USHORT_565_RGB can be encodeed with BI_BITFIELDS compression only.
1424      *
1425      * NB: method should be extended if other cases when we can not encode
1426      *     with given compression will be discovered.
1427      */
canEncodeImage(int compression, ColorModel cm, SampleModel sm)1428     protected boolean canEncodeImage(int compression, ColorModel cm, SampleModel sm) {
1429         ImageTypeSpecifier imgType = new ImageTypeSpecifier(cm, sm);
1430         return canEncodeImage(compression, imgType);
1431     }
1432 
canEncodeImage(int compression, ImageTypeSpecifier imgType)1433     protected boolean canEncodeImage(int compression, ImageTypeSpecifier imgType) {
1434         ImageWriterSpi spi = this.getOriginatingProvider();
1435         if (!spi.canEncodeImage(imgType)) {
1436             return false;
1437         }
1438         int biType = imgType.getBufferedImageType();
1439         int bpp = imgType.getColorModel().getPixelSize();
1440         if (compressionType == BI_RLE4 && bpp != 4) {
1441             // only 4bpp images can be encoded as BI_RLE4
1442             return false;
1443         }
1444         if (compressionType == BI_RLE8 && bpp != 8) {
1445             // only 8bpp images can be encoded as BI_RLE8
1446             return false;
1447         }
1448         if (bpp == 16) {
1449             /*
1450              * Technically we expect that we may be able to
1451              * encode only some of SinglePixelPackedSampleModel
1452              * images here.
1453              *
1454              * In addition we should take into account following:
1455              *
1456              * 1. BI_RGB case, according to the MSDN description:
1457              *
1458              *     The bitmap has a maximum of 2^16 colors. If the
1459              *     biCompression member of the BITMAPINFOHEADER is BI_RGB,
1460              *     the bmiColors member of BITMAPINFO is NULL. Each WORD
1461              *     in the bitmap array represents a single pixel. The
1462              *     relative intensities of red, green, and blue are
1463              *     represented with five bits for each color component.
1464              *
1465              * 2. BI_BITFIELDS case, according ot the MSDN description:
1466              *
1467              *     Windows 95/98/Me: When the biCompression member is
1468              *     BI_BITFIELDS, the system supports only the following
1469              *     16bpp color masks: A 5-5-5 16-bit image, where the blue
1470              *     mask is 0x001F, the green mask is 0x03E0, and the red mask
1471              *     is 0x7C00; and a 5-6-5 16-bit image, where the blue mask
1472              *     is 0x001F, the green mask is 0x07E0, and the red mask is
1473              *     0xF800.
1474              */
1475             boolean canUseRGB = false;
1476             boolean canUseBITFIELDS = false;
1477 
1478             SampleModel sm = imgType.getSampleModel();
1479             if (sm instanceof SinglePixelPackedSampleModel) {
1480                 int[] sizes =
1481                     ((SinglePixelPackedSampleModel)sm).getSampleSize();
1482 
1483                 canUseRGB = true;
1484                 canUseBITFIELDS = true;
1485                 for (int i = 0; i < sizes.length; i++) {
1486                     canUseRGB       &=  (sizes[i] == 5);
1487                     canUseBITFIELDS &= ((sizes[i] == 5) ||
1488                                         (i == 1 && sizes[i] == 6));
1489                 }
1490             }
1491 
1492             return (((compressionType == BI_RGB) && canUseRGB) ||
1493                     ((compressionType == BI_BITFIELDS) && canUseBITFIELDS));
1494         }
1495         return true;
1496     }
1497 
writeMaskToPalette(int mask, int i, byte[] r, byte[]g, byte[] b, byte[]a)1498     protected void writeMaskToPalette(int mask, int i,
1499                                       byte[] r, byte[]g, byte[] b, byte[]a) {
1500         b[i] = (byte)(0xff & (mask >> 24));
1501         g[i] = (byte)(0xff & (mask >> 16));
1502         r[i] = (byte)(0xff & (mask >> 8));
1503         a[i] = (byte)(0xff & mask);
1504     }
1505 
roundBpp(int x)1506     private int roundBpp(int x) {
1507         if (x <= 8) {
1508             return 8;
1509         } else if (x <= 16) {
1510             return 16;
1511         } if (x <= 24) {
1512             return 24;
1513         } else {
1514             return 32;
1515         }
1516     }
1517 }
1518