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.ImageFormat;
35  import org.apache.commons.imaging.ImageFormats;
36  import org.apache.commons.imaging.ImageInfo;
37  import org.apache.commons.imaging.ImageParser;
38  import org.apache.commons.imaging.ImageReadException;
39  import org.apache.commons.imaging.ImageWriteException;
40  import org.apache.commons.imaging.common.ImageMetadata;
41  import org.apache.commons.imaging.common.bytesource.ByteSource;
42  
43  public class WbmpImageParser extends ImageParser<WbmpImagingParameters> {
44      private static final String DEFAULT_EXTENSION = ImageFormats.WBMP.getDefaultExtension();
45      private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.WBMP.getExtensions();
46  
47      @Override
48      public WbmpImagingParameters getDefaultParameters() {
49          return new WbmpImagingParameters();
50      }
51  
52      @Override
53      public String getName() {
54          return "Wireless Application Protocol Bitmap Format";
55      }
56  
57      @Override
58      public String getDefaultExtension() {
59          return DEFAULT_EXTENSION;
60      }
61  
62      @Override
63      protected String[] getAcceptedExtensions() {
64          return ACCEPTED_EXTENSIONS;
65      }
66  
67      @Override
68      protected ImageFormat[] getAcceptedTypes() {
69          return new ImageFormat[] { ImageFormats.WBMP, //
70          };
71      }
72  
73      @Override
74      public ImageMetadata getMetadata(final ByteSource byteSource, final WbmpImagingParameters params)
75              throws ImageReadException, IOException {
76          return null;
77      }
78  
79      @Override
80      public ImageInfo getImageInfo(final ByteSource byteSource, final WbmpImagingParameters params)
81              throws ImageReadException, IOException {
82          final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
83          return new ImageInfo("WBMP", 1, new ArrayList<>(),
84                  ImageFormats.WBMP,
85                  "Wireless Application Protocol Bitmap", wbmpHeader.height,
86                  "image/vnd.wap.wbmp", 1, 0, 0, 0, 0, wbmpHeader.width, false,
87                  false, false, ImageInfo.ColorType.BW,
88                  ImageInfo.CompressionAlgorithm.NONE);
89      }
90  
91      @Override
92      public Dimension getImageSize(final ByteSource byteSource, final WbmpImagingParameters params)
93              throws ImageReadException, IOException {
94          final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
95          return new Dimension(wbmpHeader.width, wbmpHeader.height);
96      }
97  
98      @Override
99      public byte[] getICCProfileBytes(final ByteSource byteSource, final WbmpImagingParameters params)
100             throws ImageReadException, IOException {
101         return null;
102     }
103 
104     static class WbmpHeader {
105         final int typeField;
106         final byte fixHeaderField;
107         final int width;
108         final int height;
109 
110         WbmpHeader(final int typeField, final byte fixHeaderField, final int width,
111                 final int height) {
112             this.typeField = typeField;
113             this.fixHeaderField = fixHeaderField;
114             this.width = width;
115             this.height = height;
116         }
117 
118         public void dump(final PrintWriter pw) {
119             pw.println("WbmpHeader");
120             pw.println("TypeField: " + typeField);
121             pw.println("FixHeaderField: 0x"
122                     + Integer.toHexString(0xff & fixHeaderField));
123             pw.println("Width: " + width);
124             pw.println("Height: " + height);
125         }
126     }
127 
128     private int readMultiByteInteger(final InputStream is) throws ImageReadException,
129             IOException {
130         int value = 0;
131         int nextByte;
132         int totalBits = 0;
133         do {
134             nextByte = readByte("Header", is, "Error reading WBMP header");
135             value <<= 7;
136             value |= nextByte & 0x7f;
137             totalBits += 7;
138             if (totalBits > 31) {
139                 throw new ImageReadException(
140                         "Overflow reading WBMP multi-byte field");
141             }
142         } while ((nextByte & 0x80) != 0);
143         return value;
144     }
145 
146     private void writeMultiByteInteger(final OutputStream os, final int value)
147             throws IOException {
148         boolean wroteYet = false;
149         for (int position = 4 * 7; position > 0; position -= 7) {
150             final int next7Bits = 0x7f & (value >>> position);
151             if (next7Bits != 0 || wroteYet) {
152                 os.write(0x80 | next7Bits);
153                 wroteYet = true;
154             }
155         }
156         os.write(0x7f & value);
157     }
158 
159     private WbmpHeader readWbmpHeader(final ByteSource byteSource)
160             throws ImageReadException, IOException {
161         try (InputStream is = byteSource.getInputStream()) {
162             return readWbmpHeader(is);
163         }
164     }
165 
166     private WbmpHeader readWbmpHeader(final InputStream is)
167             throws ImageReadException, IOException {
168         final int typeField = readMultiByteInteger(is);
169         if (typeField != 0) {
170             throw new ImageReadException("Invalid/unsupported WBMP type "
171                     + typeField);
172         }
173 
174         final byte fixHeaderField = readByte("FixHeaderField", is,
175                 "Invalid WBMP File");
176         if ((fixHeaderField & 0x9f) != 0) {
177             throw new ImageReadException(
178                     "Invalid/unsupported WBMP FixHeaderField 0x"
179                             + Integer.toHexString(0xff & fixHeaderField));
180         }
181 
182         final int width = readMultiByteInteger(is);
183 
184         final int height = readMultiByteInteger(is);
185 
186         return new WbmpHeader(typeField, fixHeaderField, width, height);
187     }
188 
189     @Override
190     public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
191             throws ImageReadException, IOException {
192         readWbmpHeader(byteSource).dump(pw);
193         return true;
194     }
195 
196     private BufferedImage readImage(final WbmpHeader wbmpHeader, final InputStream is)
197             throws IOException {
198         final int rowLength = (wbmpHeader.width + 7) / 8;
199         final byte[] image = readBytes("Pixels", is,
200                 rowLength * wbmpHeader.height, "Error reading image pixels");
201         final DataBufferByte dataBuffer = new DataBufferByte(image, image.length);
202         final WritableRaster raster = Raster.createPackedRaster(dataBuffer,
203                 wbmpHeader.width, wbmpHeader.height, 1, null);
204         final int[] palette = { 0x000000, 0xffffff };
205         final IndexColorModel colorModel = new IndexColorModel(1, 2, palette, 0,
206                 false, -1, DataBuffer.TYPE_BYTE);
207         return new BufferedImage(colorModel, raster,
208                 colorModel.isAlphaPremultiplied(), new Properties());
209     }
210 
211     @Override
212     public final BufferedImage getBufferedImage(final ByteSource byteSource,
213             final WbmpImagingParameters params) throws ImageReadException, IOException {
214         try (InputStream is = byteSource.getInputStream()) {
215             final WbmpHeader wbmpHeader = readWbmpHeader(is);
216             return readImage(wbmpHeader, is);
217         }
218     }
219 
220     @Override
221     public void writeImage(final BufferedImage src, final OutputStream os, WbmpImagingParameters params)
222             throws ImageWriteException, IOException {
223         writeMultiByteInteger(os, 0); // typeField
224         os.write(0); // fixHeaderField
225         writeMultiByteInteger(os, src.getWidth());
226         writeMultiByteInteger(os, src.getHeight());
227 
228         for (int y = 0; y < src.getHeight(); y++) {
229             int pixel = 0;
230             int nextBit = 0x80;
231             for (int x = 0; x < src.getWidth(); x++) {
232                 final int argb = src.getRGB(x, y);
233                 final int red = 0xff & (argb >> 16);
234                 final int green = 0xff & (argb >> 8);
235                 final int blue = 0xff & (argb >> 0);
236                 final int sample = (red + green + blue) / 3;
237                 if (sample > 127) {
238                     pixel |= nextBit;
239                 }
240                 nextBit >>>= 1;
241                 if (nextBit == 0) {
242                     os.write(pixel);
243                     pixel = 0;
244                     nextBit = 0x80;
245                 }
246             }
247             if (nextBit != 0x80) {
248                 os.write(pixel);
249             }
250         }
251     }
252 }