1 /*
2  * Copyright (c) 2003, 2017, 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.wbmp;
27 
28 import java.awt.Point;
29 import java.awt.Rectangle;
30 import java.awt.image.ColorModel;
31 import java.awt.image.DataBuffer;
32 import java.awt.image.DataBufferByte;
33 import java.awt.image.IndexColorModel;
34 import java.awt.image.MultiPixelPackedSampleModel;
35 import java.awt.image.Raster;
36 import java.awt.image.RenderedImage;
37 import java.awt.image.SampleModel;
38 import java.awt.image.WritableRaster;
39 
40 import java.io.IOException;
41 
42 import javax.imageio.IIOImage;
43 import javax.imageio.IIOException;
44 import javax.imageio.ImageTypeSpecifier;
45 import javax.imageio.ImageWriteParam;
46 import javax.imageio.ImageWriter;
47 import javax.imageio.metadata.IIOMetadata;
48 import javax.imageio.metadata.IIOMetadataFormatImpl;
49 import javax.imageio.metadata.IIOInvalidTreeException;
50 import javax.imageio.spi.ImageWriterSpi;
51 import javax.imageio.stream.ImageOutputStream;
52 
53 import com.sun.imageio.plugins.common.I18N;
54 
55 /**
56  * The Java Image IO plugin writer for encoding a binary RenderedImage into
57  * a WBMP format.
58  *
59  * The encoding process may clip, subsample using the parameters
60  * specified in the {@code ImageWriteParam}.
61  */
62 public class WBMPImageWriter extends ImageWriter {
63     /** The output stream to write into */
64     private ImageOutputStream stream = null;
65 
66     // Get the number of bits required to represent an int.
getNumBits(int intValue)67     private static int getNumBits(int intValue) {
68         int numBits = 32;
69         int mask = 0x80000000;
70         while(mask != 0 && (intValue & mask) == 0) {
71             numBits--;
72             mask >>>= 1;
73         }
74         return numBits;
75     }
76 
77     // Convert an int value to WBMP multi-byte format.
intToMultiByte(int intValue)78     private static byte[] intToMultiByte(int intValue) {
79         int numBitsLeft = getNumBits(intValue);
80         byte[] multiBytes = new byte[(numBitsLeft + 6)/7];
81 
82         int maxIndex = multiBytes.length - 1;
83         for(int b = 0; b <= maxIndex; b++) {
84             multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f);
85             if(b != maxIndex) {
86                 multiBytes[b] |= (byte)0x80;
87             }
88         }
89 
90         return multiBytes;
91     }
92 
93     /** Constructs {@code WBMPImageWriter} based on the provided
94      *  {@code ImageWriterSpi}.
95      */
WBMPImageWriter(ImageWriterSpi originator)96     public WBMPImageWriter(ImageWriterSpi originator) {
97         super(originator);
98     }
99 
setOutput(Object output)100     public void setOutput(Object output) {
101         super.setOutput(output); // validates output
102         if (output != null) {
103             if (!(output instanceof ImageOutputStream))
104                 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter"));
105             this.stream = (ImageOutputStream)output;
106         } else
107             this.stream = null;
108     }
109 
getDefaultStreamMetadata(ImageWriteParam param)110     public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
111         return null;
112     }
113 
getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param)114     public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
115                                                ImageWriteParam param) {
116         WBMPMetadata meta = new WBMPMetadata();
117         meta.wbmpType = 0; // default wbmp level
118         return meta;
119     }
120 
convertStreamMetadata(IIOMetadata inData, ImageWriteParam param)121     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
122                                              ImageWriteParam param) {
123         return null;
124     }
125 
convertImageMetadata(IIOMetadata metadata, ImageTypeSpecifier type, ImageWriteParam param)126     public IIOMetadata convertImageMetadata(IIOMetadata metadata,
127                                             ImageTypeSpecifier type,
128                                             ImageWriteParam param) {
129         return null;
130     }
131 
canWriteRasters()132     public boolean canWriteRasters() {
133         return true;
134     }
135 
write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param)136     public void write(IIOMetadata streamMetadata,
137                       IIOImage image,
138                       ImageWriteParam param) throws IOException {
139 
140         if (stream == null) {
141             throw new IllegalStateException(I18N.getString("WBMPImageWriter3"));
142         }
143 
144         if (image == null) {
145             throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4"));
146         }
147 
148         clearAbortRequest();
149         processImageStarted(0);
150         if (param == null)
151             param = getDefaultWriteParam();
152 
153         RenderedImage input = null;
154         Raster inputRaster = null;
155         boolean writeRaster = image.hasRaster();
156         Rectangle sourceRegion = param.getSourceRegion();
157         SampleModel sampleModel = null;
158 
159         if (writeRaster) {
160             inputRaster = image.getRaster();
161             sampleModel = inputRaster.getSampleModel();
162         } else {
163             input = image.getRenderedImage();
164             sampleModel = input.getSampleModel();
165 
166             inputRaster = input.getData();
167         }
168 
169         checkSampleModel(sampleModel);
170         if (sourceRegion == null)
171             sourceRegion = inputRaster.getBounds();
172         else
173             sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
174 
175         if (sourceRegion.isEmpty())
176             throw new RuntimeException(I18N.getString("WBMPImageWriter1"));
177 
178         int scaleX = param.getSourceXSubsampling();
179         int scaleY = param.getSourceYSubsampling();
180         int xOffset = param.getSubsamplingXOffset();
181         int yOffset = param.getSubsamplingYOffset();
182 
183         sourceRegion.translate(xOffset, yOffset);
184         sourceRegion.width -= xOffset;
185         sourceRegion.height -= yOffset;
186 
187         int minX = sourceRegion.x / scaleX;
188         int minY = sourceRegion.y / scaleY;
189         int w = (sourceRegion.width + scaleX - 1) / scaleX;
190         int h = (sourceRegion.height + scaleY - 1) / scaleY;
191 
192         Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
193         sampleModel = sampleModel.createCompatibleSampleModel(w, h);
194 
195         SampleModel destSM= sampleModel;
196 
197         // If the data are not formatted nominally then reformat.
198         if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE ||
199            !(sampleModel instanceof MultiPixelPackedSampleModel) ||
200            ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) {
201            destSM =
202                 new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
203                                                 w, h, 1,
204                                                 w + 7 >> 3, 0);
205         }
206 
207         if (!destinationRegion.equals(sourceRegion)) {
208             if (scaleX == 1 && scaleY == 1)
209                 inputRaster = inputRaster.createChild(inputRaster.getMinX(),
210                                                       inputRaster.getMinY(),
211                                                       w, h, minX, minY, null);
212             else {
213                 WritableRaster ras = Raster.createWritableRaster(destSM,
214                                                                  new Point(minX, minY));
215 
216                 byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData();
217 
218                 for(int j = minY, y = sourceRegion.y, k = 0;
219                     j < minY + h; j++, y += scaleY) {
220 
221                     for (int i = 0, x = sourceRegion.x;
222                         i <w; i++, x +=scaleX) {
223                         int v = inputRaster.getSample(x, y, 0);
224                         data[k + (i >> 3)] |= v << (7 - (i & 7));
225                     }
226                     k += w + 7 >> 3;
227                 }
228                 inputRaster = ras;
229             }
230         }
231 
232         // If the data are not formatted nominally then reformat.
233         if(!destSM.equals(inputRaster.getSampleModel())) {
234             WritableRaster raster =
235                 Raster.createWritableRaster(destSM,
236                                             new Point(inputRaster.getMinX(),
237                                                       inputRaster.getMinY()));
238             raster.setRect(inputRaster);
239             inputRaster = raster;
240         }
241 
242         // Check whether the image is white-is-zero.
243         boolean isWhiteZero = false;
244         if(!writeRaster && input.getColorModel() instanceof IndexColorModel) {
245             IndexColorModel icm = (IndexColorModel)input.getColorModel();
246             isWhiteZero = icm.getRed(0) > icm.getRed(1);
247         }
248 
249         // Get the line stride, bytes per row, and data array.
250         int lineStride =
251             ((MultiPixelPackedSampleModel)destSM).getScanlineStride();
252         int bytesPerRow = (w + 7)/8;
253         byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData();
254 
255         // Write WBMP header.
256         stream.write(0); // TypeField
257         stream.write(0); // FixHeaderField
258         stream.write(intToMultiByte(w)); // width
259         stream.write(intToMultiByte(h)); // height
260 
261         // Write the data.
262         if(!isWhiteZero && lineStride == bytesPerRow) {
263             // Write the entire image.
264             stream.write(bdata, 0, h * bytesPerRow);
265             processImageProgress(100.0F);
266         } else {
267             // Write the image row-by-row.
268             int offset = 0;
269             if(!isWhiteZero) {
270                 // Black-is-zero
271                 for(int row = 0; row < h; row++) {
272                     if (abortRequested())
273                         break;
274                     stream.write(bdata, offset, bytesPerRow);
275                     offset += lineStride;
276                     processImageProgress(100.0F * row / h);
277                 }
278             } else {
279                 // White-is-zero: need to invert data.
280                 byte[] inverted = new byte[bytesPerRow];
281                 for(int row = 0; row < h; row++) {
282                     if (abortRequested())
283                         break;
284                     for(int col = 0; col < bytesPerRow; col++) {
285                         inverted[col] = (byte)(~(bdata[col+offset]));
286                     }
287                     stream.write(inverted, 0, bytesPerRow);
288                     offset += lineStride;
289                     processImageProgress(100.0F * row / h);
290                 }
291             }
292         }
293 
294         if (abortRequested())
295             processWriteAborted();
296         else {
297             processImageComplete();
298             stream.flushBefore(stream.getStreamPosition());
299         }
300     }
301 
reset()302     public void reset() {
303         super.reset();
304         stream = null;
305     }
306 
checkSampleModel(SampleModel sm)307     private void checkSampleModel(SampleModel sm) {
308         int type = sm.getDataType();
309         if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT
310             || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1)
311             throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2"));
312     }
313 }
314