blob: 6908c4db51d6612482ff93ca9f62e0ba1a0b5de2 [file] [log] [blame]
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package org.apache.commons.imaging.formats.wbmp;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.imaging.ImageFormat;
import org.apache.commons.imaging.ImageFormats;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.ImageParser;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.util.IoUtils;
public class WbmpImageParser extends ImageParser {
public WbmpImageParser() {
}
@Override
public String getName() {
return "Wbmp-Custom";
}
@Override
public String getDefaultExtension() {
return DEFAULT_EXTENSION;
}
private static final String DEFAULT_EXTENSION = ".wbmp";
private static final String ACCEPTED_EXTENSIONS[] = { ".wbmp", };
@Override
protected String[] getAcceptedExtensions() {
return ACCEPTED_EXTENSIONS;
}
@Override
protected ImageFormat[] getAcceptedTypes() {
return new ImageFormat[] { ImageFormats.WBMP, //
};
}
@Override
public boolean embedICCProfile(final File src, final File dst, final byte profile[]) {
return false;
}
@Override
public IImageMetadata getMetadata(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
return null;
}
@Override
public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
return new ImageInfo("WBMP", 1, new ArrayList<String>(),
ImageFormats.WBMP,
"Wireless Application Protocol Bitmap", wbmpHeader.height,
"image/vnd.wap.wbmp", 1, 0, 0, 0, 0, wbmpHeader.width, false,
false, false, ImageInfo.COLOR_TYPE_BW,
ImageInfo.COMPRESSION_ALGORITHM_NONE);
}
@Override
public Dimension getImageSize(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final WbmpHeader wbmpHeader = readWbmpHeader(byteSource);
return new Dimension(wbmpHeader.width, wbmpHeader.height);
}
@Override
public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
return null;
}
static class WbmpHeader {
int typeField;
byte fixHeaderField;
int width;
int height;
public WbmpHeader(final int typeField, final byte fixHeaderField, final int width,
final int height) {
this.typeField = typeField;
this.fixHeaderField = fixHeaderField;
this.width = width;
this.height = height;
}
public void dump(final PrintWriter pw) {
pw.println("WbmpHeader");
pw.println("TypeField: " + typeField);
pw.println("FixHeaderField: 0x"
+ Integer.toHexString(0xff & fixHeaderField));
pw.println("Width: " + width);
pw.println("Height: " + height);
}
}
private int readMultiByteInteger(final InputStream is) throws ImageReadException,
IOException {
int value = 0;
int nextByte;
int totalBits = 0;
do {
nextByte = readByte("Header", is, "Error reading WBMP header");
value <<= 7;
value |= nextByte & 0x7f;
totalBits += 7;
if (totalBits > 31) {
throw new ImageReadException(
"Overflow reading WBMP multi-byte field");
}
} while ((nextByte & 0x80) != 0);
return value;
}
private void writeMultiByteInteger(final OutputStream os, final int value)
throws IOException {
boolean wroteYet = false;
for (int position = 4 * 7; position > 0; position -= 7) {
final int next7Bits = 0x7f & (value >>> position);
if (next7Bits != 0 || wroteYet) {
os.write(0x80 | next7Bits);
wroteYet = true;
}
}
os.write(0x7f & value);
}
private WbmpHeader readWbmpHeader(final ByteSource byteSource)
throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
final WbmpHeader ret = readWbmpHeader(is);
canThrow = true;
return ret;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
private WbmpHeader readWbmpHeader(final InputStream is)
throws ImageReadException, IOException {
final int typeField = readMultiByteInteger(is);
if (typeField != 0) {
throw new ImageReadException("Invalid/unsupported WBMP type "
+ typeField);
}
final byte fixHeaderField = readByte("FixHeaderField", is,
"Invalid WBMP File");
if ((fixHeaderField & 0x9f) != 0) {
throw new ImageReadException(
"Invalid/unsupported WBMP FixHeaderField 0x"
+ Integer.toHexString(0xff & fixHeaderField));
}
final int width = readMultiByteInteger(is);
final int height = readMultiByteInteger(is);
return new WbmpHeader(typeField, fixHeaderField, width, height);
}
@Override
public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
throws ImageReadException, IOException {
readWbmpHeader(byteSource).dump(pw);
return true;
}
private BufferedImage readImage(final WbmpHeader wbmpHeader, final InputStream is)
throws IOException {
final int rowLength = (wbmpHeader.width + 7) / 8;
final byte[] image = readBytes("Pixels", is,
rowLength * wbmpHeader.height, "Error reading image pixels");
final DataBufferByte dataBuffer = new DataBufferByte(image, image.length);
final WritableRaster raster = Raster.createPackedRaster(dataBuffer,
wbmpHeader.width, wbmpHeader.height, 1, null);
final int[] palette = { 0x000000, 0xffffff };
final IndexColorModel colorModel = new IndexColorModel(1, 2, palette, 0,
false, -1, DataBuffer.TYPE_BYTE);
return new BufferedImage(colorModel, raster,
colorModel.isAlphaPremultiplied(), new Properties());
}
@Override
public final BufferedImage getBufferedImage(final ByteSource byteSource,
final Map<String,Object> params) throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
final WbmpHeader wbmpHeader = readWbmpHeader(is);
final BufferedImage ret = readImage(wbmpHeader, is);
canThrow = true;
return ret;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
@Override
public void writeImage(final BufferedImage src, final OutputStream os, Map<String,Object> params)
throws ImageWriteException, IOException {
// make copy of params; we'll clear keys as we consume them.
params = (params == null) ? new HashMap<String,Object>() : new HashMap<String,Object>(params);
// clear format key.
if (params.containsKey(PARAM_KEY_FORMAT)) {
params.remove(PARAM_KEY_FORMAT);
}
if (params.size() > 0) {
final Object firstKey = params.keySet().iterator().next();
throw new ImageWriteException("Unknown parameter: " + firstKey);
}
writeMultiByteInteger(os, 0); // typeField
os.write(0); // fixHeaderField
writeMultiByteInteger(os, src.getWidth());
writeMultiByteInteger(os, src.getHeight());
for (int y = 0; y < src.getHeight(); y++) {
int pixel = 0;
int nextBit = 0x80;
for (int x = 0; x < src.getWidth(); x++) {
final int argb = src.getRGB(x, y);
final int red = 0xff & (argb >> 16);
final int green = 0xff & (argb >> 8);
final int blue = 0xff & (argb >> 0);
final int sample = (red + green + blue) / 3;
if (sample > 127) {
pixel |= nextBit;
}
nextBit >>>= 1;
if (nextBit == 0) {
os.write(pixel);
pixel = 0;
nextBit = 0x80;
}
}
if (nextBit != 0x80) {
os.write(pixel);
}
}
}
/**
* Extracts embedded XML metadata as XML string.
* <p>
*
* @param byteSource
* File containing image data.
* @param params
* Map of optional parameters, defined in ImagingConstants.
* @return Xmp Xml as String, if present. Otherwise, returns null.
*/
@Override
public String getXmpXml(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
return null;
}
}