1
2
3
4
5
6
7
8
9
10
11
12
13
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);
224 os.write(0);
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 }