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 @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);
186 os.write(0);
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 }