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.common.BinaryFunctions.readByte; 018import static org.apache.commons.imaging.common.BinaryFunctions.readBytes; 019 020import java.awt.Dimension; 021import java.awt.image.BufferedImage; 022import java.awt.image.DataBuffer; 023import java.awt.image.DataBufferByte; 024import java.awt.image.IndexColorModel; 025import java.awt.image.Raster; 026import java.awt.image.WritableRaster; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.OutputStream; 030import java.io.PrintWriter; 031import java.util.ArrayList; 032import java.util.Properties; 033 034import org.apache.commons.imaging.ImageFormat; 035import org.apache.commons.imaging.ImageFormats; 036import org.apache.commons.imaging.ImageInfo; 037import org.apache.commons.imaging.ImageParser; 038import org.apache.commons.imaging.ImageReadException; 039import org.apache.commons.imaging.ImageWriteException; 040import org.apache.commons.imaging.common.ImageMetadata; 041import org.apache.commons.imaging.common.bytesource.ByteSource; 042 043public class WbmpImageParser extends ImageParser<WbmpImagingParameters> { 044 private static final String DEFAULT_EXTENSION = ImageFormats.WBMP.getDefaultExtension(); 045 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.WBMP.getExtensions(); 046 047 @Override 048 public WbmpImagingParameters getDefaultParameters() { 049 return new WbmpImagingParameters(); 050 } 051 052 @Override 053 public String getName() { 054 return "Wireless Application Protocol Bitmap Format"; 055 } 056 057 @Override 058 public String getDefaultExtension() { 059 return DEFAULT_EXTENSION; 060 } 061 062 @Override 063 protected String[] getAcceptedExtensions() { 064 return ACCEPTED_EXTENSIONS; 065 } 066 067 @Override 068 protected ImageFormat[] getAcceptedTypes() { 069 return new ImageFormat[] { ImageFormats.WBMP, // 070 }; 071 } 072 073 @Override 074 public ImageMetadata getMetadata(final ByteSource byteSource, final WbmpImagingParameters params) 075 throws ImageReadException, IOException { 076 return null; 077 } 078 079 @Override 080 public ImageInfo getImageInfo(final ByteSource byteSource, final WbmpImagingParameters params) 081 throws ImageReadException, IOException { 082 final WbmpHeader wbmpHeader = readWbmpHeader(byteSource); 083 return new ImageInfo("WBMP", 1, new ArrayList<>(), 084 ImageFormats.WBMP, 085 "Wireless Application Protocol Bitmap", wbmpHeader.height, 086 "image/vnd.wap.wbmp", 1, 0, 0, 0, 0, wbmpHeader.width, false, 087 false, false, ImageInfo.ColorType.BW, 088 ImageInfo.CompressionAlgorithm.NONE); 089 } 090 091 @Override 092 public Dimension getImageSize(final ByteSource byteSource, final WbmpImagingParameters params) 093 throws ImageReadException, IOException { 094 final WbmpHeader wbmpHeader = readWbmpHeader(byteSource); 095 return new Dimension(wbmpHeader.width, wbmpHeader.height); 096 } 097 098 @Override 099 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}