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.AbstractImageParser; 035import org.apache.commons.imaging.ImageFormat; 036import org.apache.commons.imaging.ImageFormats; 037import org.apache.commons.imaging.ImageInfo; 038import org.apache.commons.imaging.ImagingException; 039import org.apache.commons.imaging.bytesource.ByteSource; 040import org.apache.commons.imaging.common.ImageMetadata; 041 042public class WbmpImageParser extends AbstractImageParser<WbmpImagingParameters> { 043 044 static class WbmpHeader { 045 final int typeField; 046 final byte fixHeaderField; 047 final int width; 048 final int height; 049 050 WbmpHeader(final int typeField, final byte fixHeaderField, final int width, final int height) { 051 this.typeField = typeField; 052 this.fixHeaderField = fixHeaderField; 053 this.width = width; 054 this.height = height; 055 } 056 057 public void dump(final PrintWriter pw) { 058 pw.println("WbmpHeader"); 059 pw.println("TypeField: " + typeField); 060 pw.println("FixHeaderField: 0x" + Integer.toHexString(0xff & fixHeaderField)); 061 pw.println("Width: " + width); 062 pw.println("Height: " + height); 063 } 064 } 065 066 private static final String DEFAULT_EXTENSION = ImageFormats.WBMP.getDefaultExtension(); 067 068 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.WBMP.getExtensions(); 069 070 /** 071 * Constructs a new instance with the big-endian byte order. 072 */ 073 public WbmpImageParser() { 074 // empty 075 } 076 077 @Override 078 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException { 079 readWbmpHeader(byteSource).dump(pw); 080 return true; 081 } 082 083 @Override 084 protected String[] getAcceptedExtensions() { 085 return ACCEPTED_EXTENSIONS; 086 } 087 088 @Override 089 protected ImageFormat[] getAcceptedTypes() { 090 return new ImageFormat[] { ImageFormats.WBMP, // 091 }; 092 } 093 094 @Override 095 public final BufferedImage getBufferedImage(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException { 096 try (InputStream is = byteSource.getInputStream()) { 097 final WbmpHeader wbmpHeader = readWbmpHeader(is); 098 return readImage(wbmpHeader, is); 099 } 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); // typeField 193 os.write(0); // fixHeaderField 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}