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.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
72
73 public WbmpImageParser() {
74
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);
193 os.write(0);
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 }