blob: 47d65773fd2bdc78356831e08e303d69b6db8b66 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.commons.imaging.formats.bmp;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
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.List;
import java.util.Map;
import org.apache.commons.imaging.FormatCompliance;
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.PixelDensity;
import org.apache.commons.imaging.common.BinaryOutputStream;
import org.apache.commons.imaging.common.ByteOrder;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.common.ImageBuilder;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.formats.bmp.pixelparsers.PixelParser;
import org.apache.commons.imaging.formats.bmp.pixelparsers.PixelParserBitFields;
import org.apache.commons.imaging.formats.bmp.pixelparsers.PixelParserRgb;
import org.apache.commons.imaging.formats.bmp.pixelparsers.PixelParserRle;
import org.apache.commons.imaging.formats.bmp.writers.BmpWriter;
import org.apache.commons.imaging.formats.bmp.writers.BmpWriterPalette;
import org.apache.commons.imaging.formats.bmp.writers.BmpWriterRgb;
import org.apache.commons.imaging.palette.PaletteFactory;
import org.apache.commons.imaging.palette.SimplePalette;
import org.apache.commons.imaging.util.IoUtils;
import org.apache.commons.imaging.util.ParamMap;
public class BmpImageParser extends ImageParser {
public BmpImageParser() {
super.setByteOrder(ByteOrder.INTEL);
}
@Override
public String getName() {
return "Bmp-Custom";
}
@Override
public String getDefaultExtension() {
return DEFAULT_EXTENSION;
}
private static final String DEFAULT_EXTENSION = ".bmp";
private static final String ACCEPTED_EXTENSIONS[] = { DEFAULT_EXTENSION, };
@Override
protected String[] getAcceptedExtensions() {
return ACCEPTED_EXTENSIONS;
}
@Override
protected ImageFormat[] getAcceptedTypes() {
return new ImageFormat[] { ImageFormats.BMP, //
};
}
private static final byte BMP_HEADER_SIGNATURE[] = { 0x42, 0x4d, };
private BmpHeaderInfo readBmpHeaderInfo(final InputStream is,
final FormatCompliance formatCompliance, final boolean verbose)
throws ImageReadException, IOException {
final byte identifier1 = readByte("Identifier1", is, "Not a Valid BMP File");
final byte identifier2 = readByte("Identifier2", is, "Not a Valid BMP File");
if (formatCompliance != null) {
formatCompliance.compare_bytes("Signature", BMP_HEADER_SIGNATURE,
new byte[] { identifier1, identifier2, });
}
final int fileSize = read4Bytes("File Size", is, "Not a Valid BMP File");
final int reserved = read4Bytes("Reserved", is, "Not a Valid BMP File");
final int bitmapDataOffset = read4Bytes("Bitmap Data Offset", is,
"Not a Valid BMP File");
final int bitmapHeaderSize = read4Bytes("Bitmap Header Size", is,
"Not a Valid BMP File");
int width = 0;
int height = 0;
int planes = 0;
int bitsPerPixel = 0;
int compression = 0;
int bitmapDataSize = 0;
int hResolution = 0;
int vResolution = 0;
int colorsUsed = 0;
int colorsImportant = 0;
int redMask = 0;
int greenMask = 0;
int blueMask = 0;
int alphaMask = 0;
int colorSpaceType = 0;
final BmpHeaderInfo.ColorSpace colorSpace = new BmpHeaderInfo.ColorSpace();
colorSpace.red = new BmpHeaderInfo.ColorSpaceCoordinate();
colorSpace.green = new BmpHeaderInfo.ColorSpaceCoordinate();
colorSpace.blue = new BmpHeaderInfo.ColorSpaceCoordinate();
int gammaRed = 0;
int gammaGreen = 0;
int gammaBlue = 0;
int intent = 0;
int profileData = 0;
int profileSize = 0;
int reservedV5 = 0;
if (bitmapHeaderSize >= 40) {
// BITMAPINFOHEADER
width = read4Bytes("Width", is, "Not a Valid BMP File");
height = read4Bytes("Height", is, "Not a Valid BMP File");
planes = read2Bytes("Planes", is, "Not a Valid BMP File");
bitsPerPixel = read2Bytes("Bits Per Pixel", is,
"Not a Valid BMP File");
compression = read4Bytes("Compression", is, "Not a Valid BMP File");
bitmapDataSize = read4Bytes("Bitmap Data Size", is,
"Not a Valid BMP File");
hResolution = read4Bytes("HResolution", is, "Not a Valid BMP File");
vResolution = read4Bytes("VResolution", is, "Not a Valid BMP File");
colorsUsed = read4Bytes("ColorsUsed", is, "Not a Valid BMP File");
colorsImportant = read4Bytes("ColorsImportant", is,
"Not a Valid BMP File");
if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) {
// 52 = BITMAPV2INFOHEADER, now undocumented
// see http://en.wikipedia.org/wiki/BMP_file_format
redMask = read4Bytes("RedMask", is, "Not a Valid BMP File");
greenMask = read4Bytes("GreenMask", is, "Not a Valid BMP File");
blueMask = read4Bytes("BlueMask", is, "Not a Valid BMP File");
}
if (bitmapHeaderSize >= 56) {
// 56 = the now undocumented BITMAPV3HEADER sometimes used by
// Photoshop
// see http://forums.adobe.com/thread/751592?tstart=1
alphaMask = read4Bytes("AlphaMask", is, "Not a Valid BMP File");
}
if (bitmapHeaderSize >= 108) {
// BITMAPV4HEADER
colorSpaceType = read4Bytes("ColorSpaceType", is,
"Not a Valid BMP File");
colorSpace.red.x = read4Bytes("ColorSpaceRedX", is,
"Not a Valid BMP File");
colorSpace.red.y = read4Bytes("ColorSpaceRedY", is,
"Not a Valid BMP File");
colorSpace.red.z = read4Bytes("ColorSpaceRedZ", is,
"Not a Valid BMP File");
colorSpace.green.x = read4Bytes("ColorSpaceGreenX", is,
"Not a Valid BMP File");
colorSpace.green.y = read4Bytes("ColorSpaceGreenY", is,
"Not a Valid BMP File");
colorSpace.green.z = read4Bytes("ColorSpaceGreenZ", is,
"Not a Valid BMP File");
colorSpace.blue.x = read4Bytes("ColorSpaceBlueX", is,
"Not a Valid BMP File");
colorSpace.blue.y = read4Bytes("ColorSpaceBlueY", is,
"Not a Valid BMP File");
colorSpace.blue.z = read4Bytes("ColorSpaceBlueZ", is,
"Not a Valid BMP File");
gammaRed = read4Bytes("GammaRed", is, "Not a Valid BMP File");
gammaGreen = read4Bytes("GammaGreen", is,
"Not a Valid BMP File");
gammaBlue = read4Bytes("GammaBlue", is, "Not a Valid BMP File");
}
if (bitmapHeaderSize >= 124) {
// BITMAPV5HEADER
intent = read4Bytes("Intent", is, "Not a Valid BMP File");
profileData = read4Bytes("ProfileData", is,
"Not a Valid BMP File");
profileSize = read4Bytes("ProfileSize", is,
"Not a Valid BMP File");
reservedV5 = read4Bytes("Reserved", is, "Not a Valid BMP File");
}
} else {
throw new ImageReadException("Invalid/unsupported BMP file");
}
if (verbose) {
debugNumber("identifier1", identifier1, 1);
debugNumber("identifier2", identifier2, 1);
debugNumber("fileSize", fileSize, 4);
debugNumber("reserved", reserved, 4);
debugNumber("bitmapDataOffset", bitmapDataOffset, 4);
debugNumber("bitmapHeaderSize", bitmapHeaderSize, 4);
debugNumber("width", width, 4);
debugNumber("height", height, 4);
debugNumber("planes", planes, 2);
debugNumber("bitsPerPixel", bitsPerPixel, 2);
debugNumber("compression", compression, 4);
debugNumber("bitmapDataSize", bitmapDataSize, 4);
debugNumber("hResolution", hResolution, 4);
debugNumber("vResolution", vResolution, 4);
debugNumber("colorsUsed", colorsUsed, 4);
debugNumber("colorsImportant", colorsImportant, 4);
if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) {
debugNumber("redMask", redMask, 4);
debugNumber("greenMask", greenMask, 4);
debugNumber("blueMask", blueMask, 4);
}
if (bitmapHeaderSize >= 56) {
debugNumber("alphaMask", alphaMask, 4);
}
if (bitmapHeaderSize >= 108) {
debugNumber("colorSpaceType", colorSpaceType, 4);
debugNumber("colorSpace.red.x", colorSpace.red.x);
debugNumber("colorSpace.red.y", colorSpace.red.y);
debugNumber("colorSpace.red.z", colorSpace.red.z);
debugNumber("colorSpace.green.x", colorSpace.green.x);
debugNumber("colorSpace.green.y", colorSpace.green.y);
debugNumber("colorSpace.green.z", colorSpace.green.z);
debugNumber("colorSpace.blue.x", colorSpace.blue.x);
debugNumber("colorSpace.blue.y", colorSpace.blue.y);
debugNumber("colorSpace.blue.z", colorSpace.blue.z);
debugNumber("gammaRed", gammaRed, 4);
debugNumber("gammaGreen", gammaGreen, 4);
debugNumber("gammaBlue", gammaBlue, 4);
}
if (bitmapHeaderSize >= 124) {
debugNumber("intent", intent, 4);
debugNumber("profileData", profileData, 4);
debugNumber("profileSize", profileSize, 4);
debugNumber("reservedV5", reservedV5, 4);
}
}
final BmpHeaderInfo result = new BmpHeaderInfo(identifier1, identifier2,
fileSize, reserved, bitmapDataOffset, bitmapHeaderSize, width,
height, planes, bitsPerPixel, compression, bitmapDataSize,
hResolution, vResolution, colorsUsed, colorsImportant, redMask,
greenMask, blueMask, alphaMask, colorSpaceType, colorSpace,
gammaRed, gammaGreen, gammaBlue, intent, profileData,
profileSize, reservedV5);
return result;
}
private static final int BI_RGB = 0;
private static final int BI_RLE4 = 2;
private static final int BI_RLE8 = 1;
private static final int BI_BITFIELDS = 3;
private byte[] getRLEBytes(final InputStream is, final int RLESamplesPerByte)
throws IOException {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
// this.setDebug(true);
boolean done = false;
while (!done) {
final int a = 0xff & this.readByte("RLE a", is, "BMP: Bad RLE");
baos.write(a);
final int b = 0xff & this.readByte("RLE b", is, "BMP: Bad RLE");
baos.write(b);
if (a == 0) {
switch (b) {
case 0: // EOL
break;
case 1: // EOF
// System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
// );
done = true;
break;
case 2: {
// System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
// );
final int c = 0xff & this.readByte("RLE c", is, "BMP: Bad RLE");
baos.write(c);
final int d = 0xff & this.readByte("RLE d", is, "BMP: Bad RLE");
baos.write(d);
}
break;
default: {
int size = b / RLESamplesPerByte;
if ((b % RLESamplesPerByte) > 0) {
size++;
}
if ((size % 2) != 0) {
size++;
}
// System.out.println("b: " + b);
// System.out.println("size: " + size);
// System.out.println("RLESamplesPerByte: " +
// RLESamplesPerByte);
// System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
// );
final byte bytes[] = this.readBytes("bytes", is, size,
"RLE: Absolute Mode");
baos.write(bytes);
}
break;
}
}
}
return baos.toByteArray();
}
private ImageContents readImageContents(final InputStream is,
final FormatCompliance formatCompliance, final boolean verbose)
throws ImageReadException, IOException {
final BmpHeaderInfo bhi = readBmpHeaderInfo(is, formatCompliance, verbose);
int colorTableSize = bhi.colorsUsed;
if (colorTableSize == 0) {
colorTableSize = (1 << bhi.bitsPerPixel);
}
if (verbose) {
debugNumber("ColorsUsed", bhi.colorsUsed, 4);
debugNumber("BitsPerPixel", bhi.bitsPerPixel, 4);
debugNumber("ColorTableSize", colorTableSize, 4);
debugNumber("bhi.colorsUsed", bhi.colorsUsed, 4);
debugNumber("Compression", bhi.compression, 4);
}
// A palette is always valid, even for images that don't need it
// (like 32 bpp), it specifies the "optimal color palette" for
// when the image is displayed on a <= 256 color graphics card.
int paletteLength;
int rleSamplesPerByte = 0;
boolean rle = false;
switch (bhi.compression) {
case BI_RGB:
if (verbose) {
System.out.println("Compression: BI_RGB");
}
if (bhi.bitsPerPixel <= 8) {
paletteLength = 4 * colorTableSize;
} else {
paletteLength = 0;
}
// BytesPerPaletteEntry = 0;
// System.out.println("Compression: BI_RGBx2: " + bhi.BitsPerPixel);
// System.out.println("Compression: BI_RGBx2: " + (bhi.BitsPerPixel
// <= 16));
break;
case BI_RLE4:
if (verbose) {
System.out.println("Compression: BI_RLE4");
}
paletteLength = 4 * colorTableSize;
rleSamplesPerByte = 2;
// ExtraBitsPerPixel = 4;
rle = true;
// // BytesPerPixel = 2;
// // BytesPerPaletteEntry = 0;
break;
//
case BI_RLE8:
if (verbose) {
System.out.println("Compression: BI_RLE8");
}
paletteLength = 4 * colorTableSize;
rleSamplesPerByte = 1;
// ExtraBitsPerPixel = 8;
rle = true;
// BytesPerPixel = 2;
// BytesPerPaletteEntry = 0;
break;
//
case BI_BITFIELDS:
if (verbose) {
System.out.println("Compression: BI_BITFIELDS");
}
if (bhi.bitsPerPixel <= 8) {
paletteLength = 4 * colorTableSize;
} else {
paletteLength = 0;
}
// BytesPerPixel = 2;
// BytesPerPaletteEntry = 4;
break;
default:
throw new ImageReadException("BMP: Unknown Compression: "
+ bhi.compression);
}
byte colorTable[] = null;
if (paletteLength > 0) {
colorTable = this.readBytes("ColorTable", is, paletteLength,
"Not a Valid BMP File");
}
if (verbose) {
debugNumber("paletteLength", paletteLength, 4);
System.out.println("ColorTable: "
+ ((colorTable == null) ? "null" : "" + colorTable.length));
}
final int pixelCount = bhi.width * bhi.height;
int imageLineLength = ((((bhi.bitsPerPixel) * bhi.width) + 7) / 8);
if (verbose) {
// this.debugNumber("Total BitsPerPixel",
// (ExtraBitsPerPixel + bhi.BitsPerPixel), 4);
// this.debugNumber("Total Bit Per Line",
// ((ExtraBitsPerPixel + bhi.BitsPerPixel) * bhi.Width), 4);
// this.debugNumber("ExtraBitsPerPixel", ExtraBitsPerPixel, 4);
debugNumber("bhi.Width", bhi.width, 4);
debugNumber("bhi.Height", bhi.height, 4);
debugNumber("ImageLineLength", imageLineLength, 4);
// this.debugNumber("imageDataSize", imageDataSize, 4);
debugNumber("PixelCount", pixelCount, 4);
}
// int ImageLineLength = BytesPerPixel * bhi.Width;
while ((imageLineLength % 4) != 0) {
imageLineLength++;
}
final int headerSize = BITMAP_FILE_HEADER_SIZE
+ bhi.bitmapHeaderSize
+ (bhi.bitmapHeaderSize == 40
&& bhi.compression == BI_BITFIELDS ? 3 * 4 : 0);
final int expectedDataOffset = headerSize + paletteLength;
if (verbose) {
debugNumber("bhi.BitmapDataOffset", bhi.bitmapDataOffset, 4);
debugNumber("expectedDataOffset", expectedDataOffset, 4);
}
final int extraBytes = bhi.bitmapDataOffset - expectedDataOffset;
if (extraBytes < 0) {
throw new ImageReadException("BMP has invalid image data offset: "
+ bhi.bitmapDataOffset + " (expected: "
+ expectedDataOffset + ", paletteLength: " + paletteLength
+ ", headerSize: " + headerSize + ")");
} else if (extraBytes > 0) {
this.readBytes("BitmapDataOffset", is, extraBytes,
"Not a Valid BMP File");
}
final int imageDataSize = bhi.height * imageLineLength;
if (verbose) {
debugNumber("imageDataSize", imageDataSize, 4);
}
byte imageData[];
if (rle) {
imageData = getRLEBytes(is, rleSamplesPerByte);
} else {
imageData = this.readBytes("ImageData", is, imageDataSize,
"Not a Valid BMP File");
}
if (verbose) {
debugNumber("ImageData.length", imageData.length, 4);
}
PixelParser pixelParser;
switch (bhi.compression) {
case BI_RLE4:
case BI_RLE8:
pixelParser = new PixelParserRle(bhi, colorTable, imageData);
break;
case BI_RGB:
pixelParser = new PixelParserRgb(bhi, colorTable, imageData);
break;
case BI_BITFIELDS:
pixelParser = new PixelParserBitFields(bhi, colorTable, imageData);
break;
default:
throw new ImageReadException("BMP: Unknown Compression: "
+ bhi.compression);
}
return new ImageContents(bhi, colorTable, imageData, pixelParser);
}
private BmpHeaderInfo readBmpHeaderInfo(final ByteSource byteSource,
final boolean verbose) throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
// readSignature(is);
final BmpHeaderInfo ret = readBmpHeaderInfo(is, null, verbose);
canThrow = true;
return ret;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
@Override
public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
return null;
}
@Override
public Dimension getImageSize(final ByteSource byteSource, Map<String,Object> params)
throws ImageReadException, 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);
final boolean verbose = ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE,
false);
if (params.containsKey(PARAM_KEY_VERBOSE)) {
params.remove(PARAM_KEY_VERBOSE);
}
if (params.size() > 0) {
final Object firstKey = params.keySet().iterator().next();
throw new ImageReadException("Unknown parameter: " + firstKey);
}
final BmpHeaderInfo bhi = readBmpHeaderInfo(byteSource, verbose);
if (bhi == null) {
throw new ImageReadException("BMP: couldn't read header");
}
return new Dimension(bhi.width, bhi.height);
}
public byte[] embedICCProfile(final byte image[], final byte profile[]) {
return null;
}
@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;
}
private String getBmpTypeDescription(final int Identifier1, final int Identifier2) {
if ((Identifier1 == 'B') && (Identifier2 == 'M')) {
return "Windows 3.1x, 95, NT,";
}
if ((Identifier1 == 'B') && (Identifier2 == 'A')) {
return "OS/2 Bitmap Array";
}
if ((Identifier1 == 'C') && (Identifier2 == 'I')) {
return "OS/2 Color Icon";
}
if ((Identifier1 == 'C') && (Identifier2 == 'P')) {
return "OS/2 Color Pointer";
}
if ((Identifier1 == 'I') && (Identifier2 == 'C')) {
return "OS/2 Icon";
}
if ((Identifier1 == 'P') && (Identifier2 == 'T')) {
return "OS/2 Pointer";
}
return "Unknown";
}
@Override
public ImageInfo getImageInfo(final ByteSource byteSource, Map<String,Object> params)
throws ImageReadException, 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);
final boolean verbose = ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE,
false);
if (params.containsKey(PARAM_KEY_VERBOSE)) {
params.remove(PARAM_KEY_VERBOSE);
}
if (params.size() > 0) {
final Object firstKey = params.keySet().iterator().next();
throw new ImageReadException("Unknown parameter: " + firstKey);
}
InputStream is = null;
ImageContents ic = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
ic = readImageContents(is, FormatCompliance.getDefault(), verbose);
canThrow = true;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
if (ic == null) {
throw new ImageReadException("Couldn't read BMP Data");
}
final BmpHeaderInfo bhi = ic.bhi;
final byte colorTable[] = ic.colorTable;
if (bhi == null) {
throw new ImageReadException("BMP: couldn't read header");
}
final int height = bhi.height;
final int width = bhi.width;
final List<String> comments = new ArrayList<String>();
// TODO: comments...
final int bitsPerPixel = bhi.bitsPerPixel;
final ImageFormat format = ImageFormats.BMP;
final String name = "BMP Windows Bitmap";
final String mimeType = "image/x-ms-bmp";
// we ought to count images, but don't yet.
final int numberOfImages = -1;
// not accurate ... only reflects first
final boolean isProgressive = false;
// boolean isProgressive = (fPNGChunkIHDR.InterlaceMethod != 0);
//
// pixels per meter
final int physicalWidthDpi = (int) (bhi.hResolution * .0254);
final float physicalWidthInch = (float) ((double) width / (double) physicalWidthDpi);
// int physicalHeightDpi = 72;
final int physicalHeightDpi = (int) (bhi.vResolution * .0254);
final float physicalHeightInch = (float) ((double) height / (double) physicalHeightDpi);
final String formatDetails = "Bmp (" + (char) bhi.identifier1
+ (char) bhi.identifier2 + ": "
+ getBmpTypeDescription(bhi.identifier1, bhi.identifier2) + ")";
final boolean isTransparent = false;
final boolean usesPalette = colorTable != null;
final int colorType = ImageInfo.COLOR_TYPE_RGB;
final String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_RLE;
final ImageInfo result = new ImageInfo(formatDetails, bitsPerPixel, comments,
format, name, height, mimeType, numberOfImages,
physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
physicalWidthInch, width, isProgressive, isTransparent,
usesPalette, colorType, compressionAlgorithm);
return result;
}
@Override
public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
throws ImageReadException, IOException {
pw.println("bmp.dumpImageFile");
final ImageInfo imageData = getImageInfo(byteSource, null);
imageData.toString(pw, "");
pw.println("");
return true;
}
@Override
public FormatCompliance getFormatCompliance(final ByteSource byteSource)
throws ImageReadException, IOException {
final boolean verbose = false;
final FormatCompliance result = new FormatCompliance(
byteSource.getDescription());
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
readImageContents(is, result, verbose);
canThrow = true;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
return result;
}
@Override
public BufferedImage getBufferedImage(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
final BufferedImage ret = getBufferedImage(is, params);
canThrow = true;
return ret;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
public BufferedImage getBufferedImage(final InputStream inputStream, Map<String,Object> params)
throws ImageReadException, 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);
final boolean verbose = ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE,
false);
if (params.containsKey(PARAM_KEY_VERBOSE)) {
params.remove(PARAM_KEY_VERBOSE);
}
if (params.containsKey(BUFFERED_IMAGE_FACTORY)) {
params.remove(BUFFERED_IMAGE_FACTORY);
}
if (params.size() > 0) {
final Object firstKey = params.keySet().iterator().next();
throw new ImageReadException("Unknown parameter: " + firstKey);
}
final ImageContents ic = readImageContents(inputStream,
FormatCompliance.getDefault(), verbose);
if (ic == null) {
throw new ImageReadException("Couldn't read BMP Data");
}
final BmpHeaderInfo bhi = ic.bhi;
// byte colorTable[] = ic.colorTable;
// byte imageData[] = ic.imageData;
final int width = bhi.width;
final int height = bhi.height;
if (verbose) {
System.out.println("width: " + width);
System.out.println("height: " + height);
System.out.println("width*height: " + width * height);
System.out.println("width*height*4: " + width * height * 4);
}
final PixelParser pixelParser = ic.pixelParser;
final ImageBuilder imageBuilder = new ImageBuilder(width, height, true);
pixelParser.processImage(imageBuilder);
return imageBuilder.getBufferedImage();
}
private static final int BITMAP_FILE_HEADER_SIZE = 14;
private static final int BITMAP_INFO_HEADER_SIZE = 40;
@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);
PixelDensity pixelDensity = null;
// clear format key.
if (params.containsKey(PARAM_KEY_FORMAT)) {
params.remove(PARAM_KEY_FORMAT);
}
if (params.containsKey(PARAM_KEY_PIXEL_DENSITY)) {
pixelDensity = (PixelDensity) params
.remove(PARAM_KEY_PIXEL_DENSITY);
}
if (params.size() > 0) {
final Object firstKey = params.keySet().iterator().next();
throw new ImageWriteException("Unknown parameter: " + firstKey);
}
final SimplePalette palette = new PaletteFactory().makeExactRgbPaletteSimple(
src, 256);
BmpWriter writer = null;
if (palette == null) {
writer = new BmpWriterRgb();
} else {
writer = new BmpWriterPalette(palette);
}
final byte imagedata[] = writer.getImageData(src);
final BinaryOutputStream bos = new BinaryOutputStream(os, ByteOrder.INTEL);
// write BitmapFileHeader
os.write(0x42); // B, Windows 3.1x, 95, NT, Bitmap
os.write(0x4d); // M
final int filesize = BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE + // header
// size
4 * writer.getPaletteSize() + // palette size in bytes
imagedata.length;
bos.write4Bytes(filesize);
bos.write4Bytes(0); // reserved
bos.write4Bytes(BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE
+ 4 * writer.getPaletteSize()); // Bitmap Data Offset
final int width = src.getWidth();
final int height = src.getHeight();
// write BitmapInfoHeader
bos.write4Bytes(BITMAP_INFO_HEADER_SIZE); // Bitmap Info Header Size
bos.write4Bytes(width); // width
bos.write4Bytes(height); // height
bos.write2Bytes(1); // Number of Planes
bos.write2Bytes(writer.getBitsPerPixel()); // Bits Per Pixel
bos.write4Bytes(BI_RGB); // Compression
bos.write4Bytes(imagedata.length); // Bitmap Data Size
bos.write4Bytes(pixelDensity != null ? (int) Math
.round(pixelDensity.horizontalDensityMetres()) : 0); // HResolution
bos.write4Bytes(pixelDensity != null ? (int) Math
.round(pixelDensity.verticalDensityMetres()) : 0); // VResolution
if (palette == null) {
bos.write4Bytes(0); // Colors
} else {
bos.write4Bytes(palette.length()); // Colors
}
bos.write4Bytes(0); // Important Colors
// bos.write_4_bytes(0); // Compression
// write Palette
writer.writePalette(bos);
// write Image Data
bos.write(imagedata);
}
/**
* 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;
}
}