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      /**
71       * Constructs a new instance with the big-endian byte order.
72       */
73      public WbmpImageParser() {
74          // empty
75      }
76  
77      @Override
78      public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
79          readWbmpHeader(byteSource).dump(pw);
80          return true;
81      }
82  
83      @Override
84      protected String[] getAcceptedExtensions() {
85          return ACCEPTED_EXTENSIONS;
86      }
87  
88      @Override
89      protected ImageFormat[] getAcceptedTypes() {
90          return new ImageFormat[] { ImageFormats.WBMP, //
91          };
92      }
93  
94      @Override
95      public final BufferedImage getBufferedImage(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
96          try (InputStream is = byteSource.getInputStream()) {
97              final WbmpHeader wbmpHeader = readWbmpHeader(is);
98              return readImage(wbmpHeader, is);
99          }
100     }
101 
102     @Override
103     public String getDefaultExtension() {
104         return DEFAULT_EXTENSION;
105     }
106 
107     @Override
108     public WbmpImagingParameters getDefaultParameters() {
109         return new WbmpImagingParameters();
110     }
111 
112     @Override
113     public byte[] getIccProfileBytes(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
114         return null;
115     }
116 
117     @Override
118     public ImageInfo getImageInfo(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
119         final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
120         return new ImageInfo("WBMP", 1, new ArrayList<>(), ImageFormats.WBMP, "Wireless Application Protocol Bitmap", wbmpHeader.height, "image/vnd.wap.wbmp",
121                 1, 0, 0, 0, 0, wbmpHeader.width, false, false, false, ImageInfo.ColorType.BW, ImageInfo.CompressionAlgorithm.NONE);
122     }
123 
124     @Override
125     public Dimension getImageSize(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
126         final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
127         return new Dimension(wbmpHeader.width, wbmpHeader.height);
128     }
129 
130     @Override
131     public ImageMetadata getMetadata(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException {
132         return null;
133     }
134 
135     @Override
136     public String getName() {
137         return "Wireless Application Protocol Bitmap Format";
138     }
139 
140     private BufferedImage readImage(final WbmpHeader wbmpHeader, final InputStream is) throws IOException {
141         final int rowLength = (wbmpHeader.width + 7) / 8;
142         final byte[] image = readBytes("Pixels", is, rowLength * wbmpHeader.height, "Error reading image pixels");
143         final DataBufferByte dataBuffer = new DataBufferByte(image, image.length);
144         final WritableRaster raster = Raster.createPackedRaster(dataBuffer, wbmpHeader.width, wbmpHeader.height, 1, null);
145         final int[] palette = { 0x000000, 0xffffff };
146         final IndexColorModel colorModel = new IndexColorModel(1, 2, palette, 0, false, -1, DataBuffer.TYPE_BYTE);
147         return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
148     }
149 
150     private int readMultiByteInteger(final InputStream is) throws ImagingException, IOException {
151         int value = 0;
152         int nextByte;
153         int totalBits = 0;
154         do {
155             nextByte = readByte("Header", is, "Error reading WBMP header");
156             value <<= 7;
157             value |= nextByte & 0x7f;
158             totalBits += 7;
159             if (totalBits > 31) {
160                 throw new ImagingException("Overflow reading WBMP multi-byte field");
161             }
162         } while ((nextByte & 0x80) != 0);
163         return value;
164     }
165 
166     private WbmpHeader readWbmpHeader(final ByteSource byteSource) throws ImagingException, IOException {
167         try (InputStream is = byteSource.getInputStream()) {
168             return readWbmpHeader(is);
169         }
170     }
171 
172     private WbmpHeader readWbmpHeader(final InputStream is) throws ImagingException, IOException {
173         final int typeField = readMultiByteInteger(is);
174         if (typeField != 0) {
175             throw new ImagingException("Invalid/unsupported WBMP type " + typeField);
176         }
177 
178         final byte fixHeaderField = readByte("FixHeaderField", is, "Invalid WBMP File");
179         if ((fixHeaderField & 0x9f) != 0) {
180             throw new ImagingException("Invalid/unsupported WBMP FixHeaderField 0x" + Integer.toHexString(0xff & fixHeaderField));
181         }
182 
183         final int width = readMultiByteInteger(is);
184 
185         final int height = readMultiByteInteger(is);
186 
187         return new WbmpHeader(typeField, fixHeaderField, width, height);
188     }
189 
190     @Override
191     public void writeImage(final BufferedImage src, final OutputStream os, final WbmpImagingParameters params) throws ImagingException, IOException {
192         writeMultiByteInteger(os, 0); // typeField
193         os.write(0); // fixHeaderField
194         writeMultiByteInteger(os, src.getWidth());
195         writeMultiByteInteger(os, src.getHeight());
196 
197         for (int y = 0; y < src.getHeight(); y++) {
198             int pixel = 0;
199             int nextBit = 0x80;
200             for (int x = 0; x < src.getWidth(); x++) {
201                 final int argb = src.getRGB(x, y);
202                 final int red = 0xff & argb >> 16;
203                 final int green = 0xff & argb >> 8;
204                 final int blue = 0xff & argb >> 0;
205                 final int sample = (red + green + blue) / 3;
206                 if (sample > 127) {
207                     pixel |= nextBit;
208                 }
209                 nextBit >>>= 1;
210                 if (nextBit == 0) {
211                     os.write(pixel);
212                     pixel = 0;
213                     nextBit = 0x80;
214                 }
215             }
216             if (nextBit != 0x80) {
217                 os.write(pixel);
218             }
219         }
220     }
221 
222     private void writeMultiByteInteger(final OutputStream os, final int value) throws IOException {
223         boolean wroteYet = false;
224         for (int position = 4 * 7; position > 0; position -= 7) {
225             final int next7Bits = 0x7f & value >>> position;
226             if (next7Bits != 0 || wroteYet) {
227                 os.write(0x80 | next7Bits);
228                 wroteYet = true;
229             }
230         }
231         os.write(0x7f & value);
232     }
233 }