001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 * under the License. 014 */ 015package org.apache.commons.imaging.formats.wbmp; 016 017import static org.apache.commons.imaging.ImagingConstants.PARAM_KEY_FORMAT; 018import static org.apache.commons.imaging.common.BinaryFunctions.readByte; 019import static org.apache.commons.imaging.common.BinaryFunctions.readBytes; 020 021import java.awt.Dimension; 022import java.awt.image.BufferedImage; 023import java.awt.image.DataBuffer; 024import java.awt.image.DataBufferByte; 025import java.awt.image.IndexColorModel; 026import java.awt.image.Raster; 027import java.awt.image.WritableRaster; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.OutputStream; 031import java.io.PrintWriter; 032import java.util.ArrayList; 033import java.util.HashMap; 034import java.util.Map; 035import java.util.Properties; 036 037import org.apache.commons.imaging.ImageFormat; 038import org.apache.commons.imaging.ImageFormats; 039import org.apache.commons.imaging.ImageInfo; 040import org.apache.commons.imaging.ImageParser; 041import org.apache.commons.imaging.ImageReadException; 042import org.apache.commons.imaging.ImageWriteException; 043import org.apache.commons.imaging.common.ImageMetadata; 044import org.apache.commons.imaging.common.bytesource.ByteSource; 045 046public class WbmpImageParser extends ImageParser { 047 private static final String DEFAULT_EXTENSION = ".wbmp"; 048 private static final String[] ACCEPTED_EXTENSIONS = { ".wbmp", }; 049 050 @Override 051 public String getName() { 052 return "Wireless Application Protocol Bitmap Format"; 053 } 054 055 @Override 056 public String getDefaultExtension() { 057 return DEFAULT_EXTENSION; 058 } 059 060 @Override 061 protected String[] getAcceptedExtensions() { 062 return ACCEPTED_EXTENSIONS; 063 } 064 065 @Override 066 protected ImageFormat[] getAcceptedTypes() { 067 return new ImageFormat[] { ImageFormats.WBMP, // 068 }; 069 } 070 071 @Override 072 public ImageMetadata getMetadata(final ByteSource byteSource, final Map<String, Object> params) 073 throws ImageReadException, IOException { 074 return null; 075 } 076 077 @Override 078 public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String, Object> params) 079 throws ImageReadException, IOException { 080 final WbmpHeader wbmpHeader = readWbmpHeader(byteSource); 081 return new ImageInfo("WBMP", 1, new ArrayList<String>(), 082 ImageFormats.WBMP, 083 "Wireless Application Protocol Bitmap", wbmpHeader.height, 084 "image/vnd.wap.wbmp", 1, 0, 0, 0, 0, wbmpHeader.width, false, 085 false, false, ImageInfo.ColorType.BW, 086 ImageInfo.CompressionAlgorithm.NONE); 087 } 088 089 @Override 090 public Dimension getImageSize(final ByteSource byteSource, final Map<String, Object> params) 091 throws ImageReadException, IOException { 092 final WbmpHeader wbmpHeader = readWbmpHeader(byteSource); 093 return new Dimension(wbmpHeader.width, wbmpHeader.height); 094 } 095 096 @Override 097 public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String, Object> params) 098 throws ImageReadException, IOException { 099 return null; 100 } 101 102 static class WbmpHeader { 103 int typeField; 104 byte fixHeaderField; 105 int width; 106 int height; 107 108 WbmpHeader(final int typeField, final byte fixHeaderField, final int width, 109 final int height) { 110 this.typeField = typeField; 111 this.fixHeaderField = fixHeaderField; 112 this.width = width; 113 this.height = height; 114 } 115 116 public void dump(final PrintWriter pw) { 117 pw.println("WbmpHeader"); 118 pw.println("TypeField: " + typeField); 119 pw.println("FixHeaderField: 0x" 120 + Integer.toHexString(0xff & fixHeaderField)); 121 pw.println("Width: " + width); 122 pw.println("Height: " + height); 123 } 124 } 125 126 private int readMultiByteInteger(final InputStream is) throws ImageReadException, 127 IOException { 128 int value = 0; 129 int nextByte; 130 int totalBits = 0; 131 do { 132 nextByte = readByte("Header", is, "Error reading WBMP header"); 133 value <<= 7; 134 value |= nextByte & 0x7f; 135 totalBits += 7; 136 if (totalBits > 31) { 137 throw new ImageReadException( 138 "Overflow reading WBMP multi-byte field"); 139 } 140 } while ((nextByte & 0x80) != 0); 141 return value; 142 } 143 144 private void writeMultiByteInteger(final OutputStream os, final int value) 145 throws IOException { 146 boolean wroteYet = false; 147 for (int position = 4 * 7; position > 0; position -= 7) { 148 final int next7Bits = 0x7f & (value >>> position); 149 if (next7Bits != 0 || wroteYet) { 150 os.write(0x80 | next7Bits); 151 wroteYet = true; 152 } 153 } 154 os.write(0x7f & value); 155 } 156 157 private WbmpHeader readWbmpHeader(final ByteSource byteSource) 158 throws ImageReadException, IOException { 159 try (InputStream is = byteSource.getInputStream()) { 160 return readWbmpHeader(is); 161 } 162 } 163 164 private WbmpHeader readWbmpHeader(final InputStream is) 165 throws ImageReadException, IOException { 166 final int typeField = readMultiByteInteger(is); 167 if (typeField != 0) { 168 throw new ImageReadException("Invalid/unsupported WBMP type " 169 + typeField); 170 } 171 172 final byte fixHeaderField = readByte("FixHeaderField", is, 173 "Invalid WBMP File"); 174 if ((fixHeaderField & 0x9f) != 0) { 175 throw new ImageReadException( 176 "Invalid/unsupported WBMP FixHeaderField 0x" 177 + Integer.toHexString(0xff & fixHeaderField)); 178 } 179 180 final int width = readMultiByteInteger(is); 181 182 final int height = readMultiByteInteger(is); 183 184 return new WbmpHeader(typeField, fixHeaderField, width, height); 185 } 186 187 @Override 188 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) 189 throws ImageReadException, IOException { 190 readWbmpHeader(byteSource).dump(pw); 191 return true; 192 } 193 194 private BufferedImage readImage(final WbmpHeader wbmpHeader, final InputStream is) 195 throws IOException { 196 final int rowLength = (wbmpHeader.width + 7) / 8; 197 final byte[] image = readBytes("Pixels", is, 198 rowLength * wbmpHeader.height, "Error reading image pixels"); 199 final DataBufferByte dataBuffer = new DataBufferByte(image, image.length); 200 final WritableRaster raster = Raster.createPackedRaster(dataBuffer, 201 wbmpHeader.width, wbmpHeader.height, 1, null); 202 final int[] palette = { 0x000000, 0xffffff }; 203 final IndexColorModel colorModel = new IndexColorModel(1, 2, palette, 0, 204 false, -1, DataBuffer.TYPE_BYTE); 205 return new BufferedImage(colorModel, raster, 206 colorModel.isAlphaPremultiplied(), new Properties()); 207 } 208 209 @Override 210 public final BufferedImage getBufferedImage(final ByteSource byteSource, 211 final Map<String, Object> params) throws ImageReadException, IOException { 212 try (InputStream is = byteSource.getInputStream()) { 213 final WbmpHeader wbmpHeader = readWbmpHeader(is); 214 final BufferedImage ret = readImage(wbmpHeader, is); 215 return ret; 216 } 217 } 218 219 @Override 220 public void writeImage(final BufferedImage src, final OutputStream os, Map<String, Object> params) 221 throws ImageWriteException, IOException { 222 // make copy of params; we'll clear keys as we consume them. 223 params = (params == null) ? new HashMap<>() : new HashMap<>(params); 224 225 // clear format key. 226 if (params.containsKey(PARAM_KEY_FORMAT)) { 227 params.remove(PARAM_KEY_FORMAT); 228 } 229 230 if (!params.isEmpty()) { 231 final Object firstKey = params.keySet().iterator().next(); 232 throw new ImageWriteException("Unknown parameter: " + firstKey); 233 } 234 235 writeMultiByteInteger(os, 0); // typeField 236 os.write(0); // fixHeaderField 237 writeMultiByteInteger(os, src.getWidth()); 238 writeMultiByteInteger(os, src.getHeight()); 239 240 for (int y = 0; y < src.getHeight(); y++) { 241 int pixel = 0; 242 int nextBit = 0x80; 243 for (int x = 0; x < src.getWidth(); x++) { 244 final int argb = src.getRGB(x, y); 245 final int red = 0xff & (argb >> 16); 246 final int green = 0xff & (argb >> 8); 247 final int blue = 0xff & (argb >> 0); 248 final int sample = (red + green + blue) / 3; 249 if (sample > 127) { 250 pixel |= nextBit; 251 } 252 nextBit >>>= 1; 253 if (nextBit == 0) { 254 os.write(pixel); 255 pixel = 0; 256 nextBit = 0x80; 257 } 258 } 259 if (nextBit != 0x80) { 260 os.write(pixel); 261 } 262 } 263 } 264}