View Javadoc
1   /*
2    *  Licensed under the Apache License, Version 2.0 (the "License");
3    *  you may not use this file except in compliance with the License.
4    *  You may obtain a copy of the License at
5    *
6    *       http://www.apache.org/licenses/LICENSE-2.0
7    *
8    *  Unless required by applicable law or agreed to in writing, software
9    *  distributed under the License is distributed on an "AS IS" BASIS,
10   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   *  See the License for the specific language governing permissions and
12   *  limitations under the License.
13   *  under the License.
14   */
15  package org.apache.commons.imaging.formats.wbmp;
16  
17  import static org.apache.commons.imaging.common.BinaryFunctions.readByte;
18  import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
19  
20  import java.awt.Dimension;
21  import java.awt.image.BufferedImage;
22  import java.awt.image.DataBuffer;
23  import java.awt.image.DataBufferByte;
24  import java.awt.image.IndexColorModel;
25  import java.awt.image.Raster;
26  import java.awt.image.WritableRaster;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.OutputStream;
30  import java.io.PrintWriter;
31  import java.util.ArrayList;
32  import java.util.Properties;
33  
34  import org.apache.commons.imaging.AbstractImageParser;
35  import org.apache.commons.imaging.ImageFormat;
36  import org.apache.commons.imaging.ImageFormats;
37  import org.apache.commons.imaging.ImageInfo;
38  import org.apache.commons.imaging.ImagingException;
39  import org.apache.commons.imaging.bytesource.ByteSource;
40  import org.apache.commons.imaging.common.ImageMetadata;
41  
42  public class WbmpImageParser extends AbstractImageParser<WbmpImagingParameters> {
43  
44      static class WbmpHeader {
45          final int typeField;
46          final byte fixHeaderField;
47          final int width;
48          final int height;
49  
50          WbmpHeader(final int typeField, final byte fixHeaderField, final int width, final int height) {
51              this.typeField = typeField;
52              this.fixHeaderField = fixHeaderField;
53              this.width = width;
54              this.height = height;
55          }
56  
57          public void dump(final PrintWriter pw) {
58              pw.println("WbmpHeader");
59              pw.println("TypeField: " + typeField);
60              pw.println("FixHeaderField: 0x" + Integer.toHexString(0xff & fixHeaderField));
61              pw.println("Width: " + width);
62              pw.println("Height: " + height);
63          }
64      }
65  
66      private static final String DEFAULT_EXTENSION = ImageFormats.WBMP.getDefaultExtension();
67  
68      private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.WBMP.getExtensions();
69  
70      @Override
71      public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
72          readWbmpHeader(byteSource).dump(pw);
73          return true;
74      }
75  
76      @Override
77      protected String[] getAcceptedExtensions() {
78          return ACCEPTED_EXTENSIONS;
79      }
80  
81      @Override
82      protected ImageFormat[] getAcceptedTypes() {
83          return new ImageFormat[] { ImageFormats.WBMP, //
84          };
85      }
86  
87      @Override
88      public final BufferedImage getBufferedImage(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
89          try (InputStream is = byteSource.getInputStream()) {
90              final WbmpHeader wbmpHeader = readWbmpHeader(is);
91              return readImage(wbmpHeader, is);
92          }
93      }
94  
95      @Override
96      public String getDefaultExtension() {
97          return DEFAULT_EXTENSION;
98      }
99  
100     @Override
101     public WbmpImagingParameters getDefaultParameters() {
102         return new WbmpImagingParameters();
103     }
104 
105     @Override
106     public byte[] getIccProfileBytes(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
107         return null;
108     }
109 
110     @Override
111     public ImageInfo getImageInfo(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
112         final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
113         return new ImageInfo("WBMP", 1, new ArrayList<>(), ImageFormats.WBMP, "Wireless Application Protocol Bitmap", wbmpHeader.height, "image/vnd.wap.wbmp",
114                 1, 0, 0, 0, 0, wbmpHeader.width, false, false, false, ImageInfo.ColorType.BW, ImageInfo.CompressionAlgorithm.NONE);
115     }
116 
117     @Override
118     public Dimension getImageSize(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
119         final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
120         return new Dimension(wbmpHeader.width, wbmpHeader.height);
121     }
122 
123     @Override
124     public ImageMetadata getMetadata(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
125         return null;
126     }
127 
128     @Override
129     public String getName() {
130         return "Wireless Application Protocol Bitmap Format";
131     }
132 
133     private BufferedImage readImage(final WbmpHeader wbmpHeader, final InputStream is) throws IOException {
134         final int rowLength = (wbmpHeader.width + 7) / 8;
135         final byte[] image = readBytes("Pixels", is, rowLength * wbmpHeader.height, "Error reading image pixels");
136         final DataBufferByte dataBuffer = new DataBufferByte(image, image.length);
137         final WritableRaster raster = Raster.createPackedRaster(dataBuffer, wbmpHeader.width, wbmpHeader.height, 1, null);
138         final int[] palette = { 0x000000, 0xffffff };
139         final IndexColorModel colorModel = new IndexColorModel(1, 2, palette, 0, false, -1, DataBuffer.TYPE_BYTE);
140         return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
141     }
142 
143     private int readMultiByteInteger(final InputStream is) throws ImagingException, IOException {
144         int value = 0;
145         int nextByte;
146         int totalBits = 0;
147         do {
148             nextByte = readByte("Header", is, "Error reading WBMP header");
149             value <<= 7;
150             value |= nextByte & 0x7f;
151             totalBits += 7;
152             if (totalBits > 31) {
153                 throw new ImagingException("Overflow reading WBMP multi-byte field");
154             }
155         } while ((nextByte & 0x80) != 0);
156         return value;
157     }
158 
159     private WbmpHeader readWbmpHeader(final ByteSource byteSource) throws ImagingException, IOException {
160         try (InputStream is = byteSource.getInputStream()) {
161             return readWbmpHeader(is);
162         }
163     }
164 
165     private WbmpHeader readWbmpHeader(final InputStream is) throws ImagingException, IOException {
166         final int typeField = readMultiByteInteger(is);
167         if (typeField != 0) {
168             throw new ImagingException("Invalid/unsupported WBMP type " + typeField);
169         }
170 
171         final byte fixHeaderField = readByte("FixHeaderField", is, "Invalid WBMP File");
172         if ((fixHeaderField & 0x9f) != 0) {
173             throw new ImagingException("Invalid/unsupported WBMP FixHeaderField 0x" + Integer.toHexString(0xff & fixHeaderField));
174         }
175 
176         final int width = readMultiByteInteger(is);
177 
178         final int height = readMultiByteInteger(is);
179 
180         return new WbmpHeader(typeField, fixHeaderField, width, height);
181     }
182 
183     @Override
184     public void writeImage(final BufferedImage src, final OutputStream os, final WbmpImagingParameters params) throws ImagingException, IOException {
185         writeMultiByteInteger(os, 0); // typeField
186         os.write(0); // fixHeaderField
187         writeMultiByteInteger(os, src.getWidth());
188         writeMultiByteInteger(os, src.getHeight());
189 
190         for (int y = 0; y < src.getHeight(); y++) {
191             int pixel = 0;
192             int nextBit = 0x80;
193             for (int x = 0; x < src.getWidth(); x++) {
194                 final int argb = src.getRGB(x, y);
195                 final int red = 0xff & argb >> 16;
196                 final int green = 0xff & argb >> 8;
197                 final int blue = 0xff & argb >> 0;
198                 final int sample = (red + green + blue) / 3;
199                 if (sample > 127) {
200                     pixel |= nextBit;
201                 }
202                 nextBit >>>= 1;
203                 if (nextBit == 0) {
204                     os.write(pixel);
205                     pixel = 0;
206                     nextBit = 0x80;
207                 }
208             }
209             if (nextBit != 0x80) {
210                 os.write(pixel);
211             }
212         }
213     }
214 
215     private void writeMultiByteInteger(final OutputStream os, final int value) throws IOException {
216         boolean wroteYet = false;
217         for (int position = 4 * 7; position > 0; position -= 7) {
218             final int next7Bits = 0x7f & value >>> position;
219             if (next7Bits != 0 || wroteYet) {
220                 os.write(0x80 | next7Bits);
221                 wroteYet = true;
222             }
223         }
224         os.write(0x7f & value);
225     }
226 }