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 @Override 071 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException { 072 readWbmpHeader(byteSource).dump(pw); 073 return true; 074 } 075 076 @Override 077 protected String[] getAcceptedExtensions() { 078 return ACCEPTED_EXTENSIONS; 079 } 080 081 @Override 082 protected ImageFormat[] getAcceptedTypes() { 083 return new ImageFormat[] { ImageFormats.WBMP, // 084 }; 085 } 086 087 @Override 088 public final BufferedImage getBufferedImage(final ByteSource byteSource, final WbmpImagingParameters params) throws ImagingException, IOException { 089 try (InputStream is = byteSource.getInputStream()) { 090 final WbmpHeader wbmpHeader = readWbmpHeader(is); 091 return readImage(wbmpHeader, is); 092 } 093 } 094 095 @Override 096 public String getDefaultExtension() { 097 return DEFAULT_EXTENSION; 098 } 099 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); // typeField 186 os.write(0); // fixHeaderField 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}