TMI-BMP: Support for more versions of BMP format

This commit is contained in:
Harald Kuhr 2014-10-24 16:38:35 +02:00
parent f824fa1578
commit 3dae9e97da
120 changed files with 2027 additions and 343 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2009, Harald Kuhr
Copyright (c) 2014, Harald Kuhr
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -6,9 +6,9 @@
<artifactId>imageio</artifactId>
<version>3.1-SNAPSHOT</version>
</parent>
<artifactId>imageio-ico</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICO plugin</name>
<description>ImageIO plugin for Windows Icon (ICO) and Cursor (CUR) format.</description>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
<description>ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format.</description>
<dependencies>
<dependency>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, Harald Kuhr
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -26,24 +26,29 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io.enc;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.io.enc.Decoder;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java#1 $
* @version $Id: AbstractRLEDecoder.java#1 $
*/
// TODO: Move to other package or make public
abstract class AbstractRLEDecoder implements Decoder {
protected final byte[] row;
protected final int width;
protected final int bitsPerSample;
protected final byte[] row;
protected int srcX;
protected int srcY;
protected int dstX;
@ -52,36 +57,26 @@ abstract class AbstractRLEDecoder implements Decoder {
/**
* Creates an RLEDecoder. As RLE encoded BMPs may contain x and y deltas,
* etc, we need to know height and width of the image.
*
* @param pWidth width of the image
* @param pHeight height of the image
* @param width width of the image
* @param bitsPerSample pits per sample
*/
AbstractRLEDecoder(final int pWidth, final int pHeight) {
width = pWidth;
int bytesPerRow = width;
int mod = bytesPerRow % 4;
if (mod != 0) {
bytesPerRow += 4 - mod;
}
AbstractRLEDecoder(final int width, final int bitsPerSample) {
this.width = width;
this.bitsPerSample = bitsPerSample;
// Pad row to multiple of 4
int bytesPerRow = ((bitsPerSample * this.width + 31) / 32) * 4;
row = new byte[bytesPerRow];
srcX = 0;
srcY = pHeight - 1;
dstX = srcX;
dstY = srcY;
}
/**
* Decodes one full row of image data.
*
* @param pStream the input stream containing RLE data
* @param stream the input stream containing RLE data
*
* @throws IOException if an I/O related exception occurs while reading
*/
protected abstract void decodeRow(final InputStream pStream) throws IOException;
protected abstract void decodeRow(final InputStream stream) throws IOException;
/**
* Decodes as much data as possible, from the stream into the buffer.
@ -91,31 +86,35 @@ abstract class AbstractRLEDecoder implements Decoder {
*
* @return the number of bytes decoded from the stream, to the buffer
*
* @throws IOException if an I/O related exception ocurs while reading
* @throws IOException if an I/O related exception occurs while reading
*/
public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining() && dstY >= 0) {
// TODO: Allow decoding < row.length at a time and get rid of this assertion...
if (buffer.capacity() < row.length) {
throw new AssertionError("This decoder needs a buffer.capacity() of at least one row");
}
while (buffer.remaining() >= row.length && srcY >= 0) {
// NOTE: Decode only full rows, don't decode if y delta
if (dstX == 0 && srcY == dstY) {
decodeRow(stream);
}
int length = Math.min(row.length - dstX, buffer.remaining());
// System.arraycopy(row, dstX, buffer, decoded, length);
int length = Math.min(row.length - (dstX * bitsPerSample) / 8, buffer.remaining());
buffer.put(row, 0, length);
dstX += length;
// decoded += length;
dstX += (length * 8) / bitsPerSample;
if (dstX == row.length) {
if (dstX == (row.length * 8) / bitsPerSample) {
dstX = 0;
dstY--;
dstY++;
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
// NOTE: If src Y is > dst Y, we have a delta, and have to fill the
// gap with zero-bytes
if (dstY > srcY) {
for (int i = 0; i < row.length; i++) {
row[i] = 0x00;
}
if (srcX > dstX) {
Arrays.fill(row, 0, (srcX * bitsPerSample) / 8, (byte) 0);
}
if (srcY > dstY) {
Arrays.fill(row, (byte) 0);
}
}
}
@ -126,16 +125,16 @@ abstract class AbstractRLEDecoder implements Decoder {
/**
* Checks a read byte for EOF marker.
*
* @param pByte the byte to check
* @return the value of {@code pByte} if positive.
* @param val the byte to check
* @return the value of {@code val} if positive.
*
* @throws EOFException if {@code pByte} is negative
* @throws EOFException if {@code val} is negative
*/
protected static int checkEOF(final int pByte) throws EOFException {
if (pByte < 0) {
protected static int checkEOF(final int val) throws EOFException {
if (val < 0) {
throw new EOFException("Premature end of file");
}
return pByte;
return val;
}
}

View File

@ -0,0 +1,677 @@
/*
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.xml.XMLSerializer;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInput;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Iterator;
/**
* ImageReader for Microsoft Windows Bitmap (BMP) format.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CURImageReader.java,v 1.0 Apr 20, 2009 11:54:28 AM haraldk Exp$
*
* @see com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader
*/
public final class BMPImageReader extends ImageReaderBase {
private long pixelOffset;
private DIBHeader header;
private transient ImageReader jpegReaderDelegate;
private transient ImageReader pngReaderDelegate;
public BMPImageReader() {
super(new BMPImageReaderSpi());
}
protected BMPImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
@Override
protected void resetMembers() {
pixelOffset = 0;
header = null;
if (pngReaderDelegate != null) {
pngReaderDelegate.dispose();
pngReaderDelegate = null;
}
if (jpegReaderDelegate != null) {
jpegReaderDelegate.dispose();
jpegReaderDelegate = null;
}
}
@Override
public int getNumImages(boolean allowSearch) throws IOException {
readHeader();
return 1;
}
private void readHeader() throws IOException {
assertInput();
if (header == null) {
// BMP files have Intel origin, always little endian
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
// Read BMP file header
byte[] fileHeader = new byte[DIB.BMP_FILE_HEADER_SIZE - 4]; // We'll read the last 4 bytes later
imageInput.readFully(fileHeader);
if (fileHeader[0] != 'B' || fileHeader[1] != 'M') {
throw new IIOException("Not a BMP");
}
// Ignore rest of data, it's redundant...
pixelOffset = imageInput.readUnsignedInt();
// Read DIB header
header = DIBHeader.read(imageInput);
}
}
@Override
public int getWidth(int pImageIndex) throws IOException {
checkBounds(pImageIndex);
return header.getWidth();
}
@Override
public int getHeight(int pImageIndex) throws IOException {
checkBounds(pImageIndex);
return header.getHeight();
}
@Override
public Iterator<ImageTypeSpecifier> getImageTypes(int pImageIndex) throws IOException {
checkBounds(pImageIndex);
// TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB
return Arrays.asList(getRawImageType(pImageIndex)).iterator();
}
private void readColorMap(final BitmapIndexed pBitmap) throws IOException {
int offset = DIB.BMP_FILE_HEADER_SIZE + header.getSize();
if (offset != imageInput.getStreamPosition()) {
imageInput.seek(offset);
}
switch (header.getCompression()) {
case DIB.COMPRESSION_RGB:
case DIB.COMPRESSION_RLE4:
case DIB.COMPRESSION_RLE8:
break;
default:
throw new IIOException("Unsupported compression for palette: " + header.getCompression());
}
int colorCount = pBitmap.getColorCount();
if (header.getSize() == DIB.BITMAP_CORE_HEADER_SIZE) {
// Byte triplets in BGR form
for (int i = 0; i < colorCount; i++) {
int b = imageInput.readUnsignedByte();
int g = imageInput.readUnsignedByte();
int r = imageInput.readUnsignedByte();
pBitmap.colors[i] = r << 16 | g << 8 | b | 0xff000000;
}
}
else {
// Byte quadruples in BGRa (or ints in aRGB) form (where a is "Reserved")
for (int i = 0; i < colorCount; i++) {
pBitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
}
}
}
@Override
public ImageTypeSpecifier getRawImageType(int pImageIndex) throws IOException {
checkBounds(pImageIndex);
if (header.getPlanes() != 1) {
throw new IIOException("Multiple planes not supported");
}
switch (header.getBitCount()) {
case 1:
case 2:
case 4:
case 8:
// TODO: Get rid of the fake DirectoryEntry and support color maps directly
BitmapIndexed indexed = new BitmapIndexed(new DirectoryEntry() {}, header);
readColorMap(indexed);
return IndexedImageTypeSpecifier.createFromIndexColorModel(indexed.createColorModel());
case 16:
if (header.hasMasks()) {
int[] masks = getMasks();
return ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
masks[0],
masks[1],
masks[2],
masks[3],
DataBuffer.TYPE_USHORT,
false);
}
// Default if no mask is 555
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
case 24:
if (header.getCompression() != DIB.COMPRESSION_RGB) {
throw new IIOException("Unsupported compression for RGB: " + header.getCompression());
}
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
case 32:
if (header.hasMasks()) {
int[] masks = getMasks();
return ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
masks[0],
masks[1],
masks[2],
masks[3],
DataBuffer.TYPE_INT,
false);
}
// Default if no mask
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
case 0:
if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) {
return initReaderDelegate(header.getCompression()).getRawImageType(0);
}
default:
throw new IIOException("Unsupported bit count: " + header.getBitCount());
}
}
private int[] getMasks() throws IOException {
if (header.masks != null) {
// Get mask and create either 555, 565 or 444/4444 etc
return header.masks;
}
switch (header.getCompression()) {
case DIB.COMPRESSION_BITFIELDS:
case DIB.COMPRESSION_ALPHA_BITFIELDS:
// Consult BITFIELDS/ALPHA_BITFIELDS
return readBitFieldsMasks();
default:
return null;
}
}
private int[] readBitFieldsMasks() throws IOException {
long offset = DIB.BMP_FILE_HEADER_SIZE + header.getSize();
if (offset != imageInput.getStreamPosition()) {
imageInput.seek(offset);
}
int[] masks = DIBHeader.readMasks(imageInput);
if (header.getCompression() != DIB.COMPRESSION_ALPHA_BITFIELDS) {
masks[3] = 0;
}
return masks;
}
@Override
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
checkBounds(imageIndex);
// Delegate reading for JPEG/PNG compression
if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) {
return readUsingDelegate(header.getCompression(), param);
}
int width = getWidth(imageIndex);
int height = getHeight(imageIndex);
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
// BMP rows are padded to 4 byte boundary
int rowSizeBytes = ((header.getBitCount() * width + 31) / 32) * 4;
// Wrap
imageInput.seek(pixelOffset);
DataInput input;
switch (header.getCompression()) {
case DIB.COMPRESSION_RLE4:
if (header.getBitCount() != 4) {
throw new IIOException(String.format("Unsupported combination of bitCount/compression: %s/%s", header.getBitCount(), header.getCompression()));
}
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLE4Decoder(width), rowSizeBytes));
break;
case DIB.COMPRESSION_RLE8:
if (header.getBitCount() != 8) {
throw new IIOException(String.format("Unsupported combination of bitCount/compression: %s/%s", header.getBitCount(), header.getCompression()));
}
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLE8Decoder(width), rowSizeBytes));
break;
case DIB.COMPRESSION_BITFIELDS:
case DIB.COMPRESSION_ALPHA_BITFIELDS:
// TODO: Validate bitCount for these
case DIB.COMPRESSION_RGB:
input = imageInput;
break;
default:
throw new IIOException("Unsupported compression: " + header.getCompression());
}
Rectangle srcRegion = new Rectangle();
Rectangle destRegion = new Rectangle();
computeRegions(param, width, height, destination, srcRegion, destRegion);
WritableRaster destRaster = clipToRect(destination.getRaster(), destRegion, param != null ? param.getDestinationBands() : null);
checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands());
WritableRaster rowRaster;
switch (header.getBitCount()) {
case 1:
case 2:
case 4:
rowRaster = Raster.createPackedRaster(new DataBufferByte(rowSizeBytes), width, 1, header.getBitCount(), null);
break;
case 8:
case 24:
rowRaster = Raster.createInterleavedRaster(new DataBufferByte(rowSizeBytes), width, 1, rowSizeBytes, header.getBitCount() / 8, createOffsets(rawType.getNumBands()), null);
break;
case 16:
case 32:
rowRaster = rawType.createBufferedImage(width, 1).getRaster();
break;
default:
throw new IIOException("Unsupported pixel depth: " + header.getBitCount());
}
// Clip to source region
Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
param != null ? param.getSourceBands() : null,
param != null ? param.getSourceXSubsampling() : 1);
int xSub = param != null ? param.getSourceXSubsampling() : 1;
int ySub = param != null ? param.getSourceYSubsampling() : 1;
processImageStarted(imageIndex);
for (int y = 0; y < height; y++) {
switch (header.getBitCount()) {
case 1:
case 2:
case 4:
case 8:
case 24:
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
try {
readRowByte(input, height, srcRegion, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
}
catch (IndexOutOfBoundsException ioob) {
System.err.println("IOOB: " + ioob);
System.err.println("y: " + y);
}
catch (EOFException eof) {
System.err.println("EOF: " + eof);
System.err.println("y: " + y);
}
break;
case 16:
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
readRowUShort(input, height, srcRegion, xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
break;
case 32:
int[] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
readRowInt(input, height, srcRegion, xSub, ySub, rowDataInt, destRaster, clippedRow, y);
break;
default:
throw new AssertionError("Unsupported pixel depth: " + header.getBitCount());
}
processImageProgress(100f * y / height);
if (height - 1 - y < srcRegion.y) {
break;
}
if (abortRequested()) {
processReadAborted();
break;
}
}
processImageComplete();
return destination;
}
private BufferedImage readUsingDelegate(final int compression, final ImageReadParam param) throws IOException {
ImageReader reader = initReaderDelegate(compression);
return reader.read(0, param);
}
private ImageReader initReaderDelegate(int compression) throws IOException {
ImageReader reader = getImageReaderDelegate(compression);
imageInput.seek(pixelOffset);
reader.setInput(new SubImageInputStream(imageInput, header.getImageSize()));
return reader;
}
private ImageReader getImageReaderDelegate(int compression) throws IIOException {
String format;
switch (compression) {
case DIB.COMPRESSION_JPEG:
if (jpegReaderDelegate != null) {
return jpegReaderDelegate;
}
format = "JPEG";
break;
case DIB.COMPRESSION_PNG:
if (pngReaderDelegate != null) {
return pngReaderDelegate;
}
format = "PNG";
break;
default:
throw new AssertionError("Unsupported BMP compression: " + compression);
}
// Consider looking for specific PNG and JPEG implementations.
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format);
if (!readers.hasNext()) {
throw new IIOException(String.format("Delegate ImageReader for %s format not found", format));
}
ImageReader reader = readers.next();
// Cache for later use
switch (compression) {
case DIB.COMPRESSION_JPEG:
jpegReaderDelegate = reader;
break;
case DIB.COMPRESSION_PNG:
pngReaderDelegate = reader;
break;
}
return reader;
}
private int[] createOffsets(int numBands) {
int[] offsets = new int[numBands];
for (int i = 0; i < numBands; i++) {
offsets[i] = numBands - i - 1;
}
return offsets;
}
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
// If subsampled or outside source region, skip entire row
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
input.skipBytes(rowDataByte.length);
return;
}
input.readFully(rowDataByte, 0, rowDataByte.length);
// Subsample horizontal
if (xSub != 1) {
for (int x = 0; x < srcRegion.width / xSub; x++) {
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
}
}
if (header.topDown) {
destChannel.setDataElements(0, y, srcChannel);
}
else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
}
private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
final short[] rowDataUShort, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
// If subsampled or outside source region, skip entire row
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2);
return;
}
readFully(input, rowDataUShort);
// Skip 2 bytes, if not ending on 32 bit/4 byte boundary
if (rowDataUShort.length % 2 != 0) {
input.skipBytes(2);
}
// Subsample horizontal
if (xSub != 1) {
for (int x = 0; x < srcRegion.width / xSub; x++) {
rowDataUShort[srcRegion.x + x] = rowDataUShort[srcRegion.x + x * xSub];
}
}
if (header.topDown) {
destChannel.setDataElements(0, y, srcChannel);
}
else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
}
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
final int [] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
// If subsampled or outside source region, skip entire row
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
input.skipBytes(rowDataInt.length * 4);
return;
}
readFully(input, rowDataInt);
// Subsample horizontal
if (xSub != 1) {
for (int x = 0; x < srcRegion.width / xSub; x++) {
rowDataInt[srcRegion.x + x] = rowDataInt[srcRegion.x + x * xSub];
}
}
if (header.topDown) {
destChannel.setDataElements(0, y, srcChannel);
}
else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
}
// TODO: Candidate util method
private static void readFully(final DataInput input, final short[] shorts) throws IOException {
if (input instanceof ImageInputStream) {
// Optimization for ImageInputStreams, read all in one go
((ImageInputStream) input).readFully(shorts, 0, shorts.length);
}
else {
for (int i = 0; i < shorts.length; i++) {
shorts[i] = input.readShort();
}
}
}
// TODO: Candidate util method
private static void readFully(final DataInput input, final int[] ints) throws IOException {
if (input instanceof ImageInputStream) {
// Optimization for ImageInputStreams, read all in one go
((ImageInputStream) input).readFully(ints, 0, ints.length);
}
else {
for (int i = 0; i < ints.length; i++) {
ints[i] = input.readInt();
}
}
}
private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) {
if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
&& xSub == 1
&& bands == null /* TODO: Compare bands with that of raster */) {
return raster;
}
return raster.createChild(rect.x / xSub, 0, rect.width / xSub, 1, 0, 0, bands);
}
private WritableRaster clipToRect(final WritableRaster raster, final Rectangle rect, final int[] bands) {
if (rect.contains(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight())
&& bands == null /* TODO: Compare bands with that of raster */) {
return raster;
}
return raster.createWritableChild(rect.x, rect.y, rect.width, rect.height, 0, 0, bands);
}
public static void main(String[] args) throws IOException {
BMPImageReaderSpi provider = new BMPImageReaderSpi();
BMPImageReader reader = new BMPImageReader(provider);
for (String arg : args) {
try {
File in = new File(arg);
ImageInputStream stream = ImageIO.createImageInputStream(in);
System.err.println("Can read?: " + provider.canDecodeInput(stream));
reader.reset();
reader.setInput(stream);
ImageReadParam param = reader.getDefaultReadParam();
param.setDestinationType(reader.getImageTypes(0).next());
// param.setSourceSubsampling(2, 3, 0, 0);
// param.setSourceSubsampling(2, 1, 0, 0);
//
// int width = reader.getWidth(0);
// int height = reader.getHeight(0);
//
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
// param.setSourceRegion(new Rectangle(width / 2, height / 2));
// param.setSourceRegion(new Rectangle(width / 2, height / 2, width / 2, height / 2));
System.err.println("reader.header: " + reader.header);
BufferedImage image = reader.read(0, param);
System.err.println("image: " + image);
showIt(image, in.getName());
IIOMetadata imageMetadata = reader.getImageMetadata(0);
if (imageMetadata != null) {
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
}
}
catch (Throwable t) {
if (args.length > 1) {
System.err.println("---");
System.err.println("---> " + t.getClass().getSimpleName() + ": " + t.getMessage() + " for " + arg);
System.err.println("---");
}
else {
throwAs(RuntimeException.class, t);
}
}
}
}
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
throw (T) pThrowable;
}
}

View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Iterator;
import java.util.Locale;
/**
* BMPImageReaderSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BMPImageReaderSpi.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
public final class BMPImageReaderSpi extends ImageReaderSpi {
public BMPImageReaderSpi() {
this(IIOUtil.getProviderInfo(BMPImageReaderSpi.class));
}
private BMPImageReaderSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
new String[]{"bmp", "BMP"},
new String[]{"bmp", "rle"},
new String[]{
"image/bmp",
"image/x-bmp"
// "image/vnd.microsoft.bitmap", // TODO: Official IANA MIME
},
"com.twelvemonkeys.imageio.plugins.bmp.BMPImageReader",
new Class[]{ImageInputStream.class},
null,
true, null, null, null, null,
true,
null, null,
null, null
);
}
static ImageReaderSpi lookupDefaultProvider(final ServiceRegistry registry) {
Iterator<ImageReaderSpi> providers = registry.getServiceProviders(ImageReaderSpi.class, true);
while (providers.hasNext()) {
ImageReaderSpi provider = providers.next();
if (provider.getClass().getName().equals("com.sun.imageio.plugins.bmp.BMPImageReaderSpi")) {
return provider;
}
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
ImageReaderSpi defaultProvider = lookupDefaultProvider(registry);
if (defaultProvider != null) {
// Order before com.sun provider, to aid ImageIO in selecting our reader
registry.setOrdering((Class<ImageReaderSpi>) category, this, defaultProvider);
}
}
public boolean canDecodeInput(final Object pSource) throws IOException {
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
}
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
byte[] fileHeader = new byte[18]; // Strictly: file header (14 bytes) + BMP header size field (4 bytes)
try {
pInput.mark();
pInput.readFully(fileHeader);
// Magic: BM
if (fileHeader[0] != 'B' || fileHeader[1] != 'M') {
return false;
}
ByteBuffer header = ByteBuffer.wrap(fileHeader);
header.order(ByteOrder.LITTLE_ENDIAN);
int fileSize = header.getInt(2);
if (fileSize <= 0) {
return false;
}
// Ignore hot-spots etc..
int offset = header.getInt(10);
if (offset <= 0) {
return false;
}
int headerSize = header.getInt(14);
switch (headerSize) {
case DIB.BITMAP_CORE_HEADER_SIZE:
case DIB.OS2_V2_HEADER_16_SIZE:
case DIB.OS2_V2_HEADER_SIZE:
case DIB.BITMAP_INFO_HEADER_SIZE:
case DIB.BITMAP_V2_INFO_HEADER_SIZE:
case DIB.BITMAP_V3_INFO_HEADER_SIZE:
case DIB.BITMAP_V4_INFO_HEADER_SIZE:
case DIB.BITMAP_V5_INFO_HEADER_SIZE:
return true;
default:
return false;
}
}
finally {
pInput.reset();
}
}
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
return new BMPImageReader(this);
}
public String getDescription(final Locale pLocale) {
return "Windows Device Independent Bitmap Format (BMP) Reader";
}
}

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.lang.Validate;

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
@ -107,7 +107,7 @@ class BitmapIndexed extends BitmapDescriptor {
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
if (colors > (1 << getBitCount())) {
int index = BitmapIndexed.findTransIndexMaybeRemap(this.colors, this.bits);
int index = findTransIndexMaybeRemap(this.colors, this.bits);
if (index == -1) {
// No duplicate found, increase bitcount

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage;

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage;

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage;

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
@ -56,7 +56,7 @@ public final class CURImageReader extends DIBImageReader {
* @param pImageIndex the index of the cursor in the current input.
* @return the hot spot location for the cursor
*
* @throws IOException if an I/O exception occurs during reading of image meta data
* @throws java.io.IOException if an I/O exception occurs during reading of image meta data
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
* the number of cursors in the file
*/

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
@ -60,7 +60,7 @@ public final class CURImageReaderSpi extends ImageReaderSpi {
"image/x-cursor", // Common extension MIME
"image/cursor" // Unofficial, but common
},
"com.twelvemonkeys.imageio.plugins.ico.CURImageReader",
"com.twelvemonkeys.imageio.plugins.bmp.CURImageReader",
new Class[] {ImageInputStream.class},
null,
true, null, null, null, null,

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
/**
* DIB
@ -40,26 +40,54 @@ package com.twelvemonkeys.imageio.plugins.ico;
*/
interface DIB {
int TYPE_UNKNOWN = 0;
int TYPE_ICO = 1;
int TYPE_CUR = 2;
int BMP_FILE_HEADER_SIZE = 14;
/** BITMAPCOREHEADER size, OS/2 V1 */
int OS2_V1_HEADER_SIZE = 12;
int BITMAP_CORE_HEADER_SIZE = 12;
/** Strange BITMAPCOREHEADER size, OS/2 V2, but only first 16 bytes... */
int OS2_V2_HEADER_16_SIZE = 16;
/** BITMAPCOREHEADER size, OS/2 V2 */
int OS2_V2_HEADER_SIZE = 64;
/**
* BITMAPINFOHEADER size, Windows 3.0 and later.
* This is the most commonly used header for persistent bitmaps
* This is the most commonly used header for persistent bitmaps.
*/
int WINDOWS_V3_HEADER_SIZE = 40;
int BITMAP_INFO_HEADER_SIZE = 40;
/** BITMAPV4HEADER size, Windows 95/NT4 and later */
int WINDOWS_V4_HEADER_SIZE = 108;
int BITMAP_V2_INFO_HEADER_SIZE = 52; // Undocumented, written by Photoshop
/** BITMAPV5HEADER size, Windows 98/2000 and later */
int WINDOWS_V5_HEADER_SIZE = 124;
int BITMAP_V3_INFO_HEADER_SIZE = 56; // Undocumented, written by Photoshop
/** BITMAPV4HEADER size, Windows 95/NT4 and later. */
int BITMAP_V4_INFO_HEADER_SIZE = 108;
/** BITMAPV5HEADER size, Windows 98/2000 and later. */
int BITMAP_V5_INFO_HEADER_SIZE = 124;
/** BI_RGB: No compression. Default. */
int COMPRESSION_RGB = 0;
/** BI_RLE8: 8 bit run-length encoding (RLE). */
int COMPRESSION_RLE8 = 1;
/** BI_RLE4: 4 bit run-length encoding (RLE). */
int COMPRESSION_RLE4 = 2;
/** BI_BITFIELDS, OS2_V2: Huffman 1D compression. V2: RGB bit field masks, V3+: RGBA. */
int COMPRESSION_BITFIELDS = 3;
int COMPRESSION_JPEG = 4;
int COMPRESSION_PNG = 5;
/** RGBA bitfield masks. */
int COMPRESSION_ALPHA_BITFIELDS = 6;
// Unused for Windows Metafiles using CMYK colorspace:
// int COMPRESSION_CMYK = 11;
// int COMPRESSION_CMYK_RLE8 = 12;
// int COMPRESSION_CMYK_RLE5 = 13;
/** PNG "magic" identifier */
long PNG_MAGIC = 0x89l << 56 | (long) 'P' << 48 | (long) 'N' << 40 | (long) 'G' << 32 | 0x0dl << 24 | 0x0al << 16 | 0x1al << 8 | 0x0al;

View File

@ -0,0 +1,426 @@
/*
* Copyright (c) 2009, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.IOException;
/**
* Represents the DIB (Device Independent Bitmap) Information header structure.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: DIBHeader.java,v 1.0 May 5, 2009 10:45:31 AM haraldk Exp$
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
*/
abstract class DIBHeader {
protected int size;
protected int width;
// NOTE: If a bitmask is present, this value includes the height of the mask
// (so often header.height = entry.height * 2)
protected int height;
protected boolean topDown = false;
protected int planes;
protected int bitCount;
/**
* 0 = BI_RGB: No compression
* 1 = BI_RLE8: 8 bit RLE Compression (8 bit only)
* 2 = BI_RLE4: 4 bit RLE Compression (4 bit only)
* 3 = BI_BITFIELDS: No compression (16 & 32 bit only)
*/
protected int compression;
// May be 0 if not known
protected int imageSize;
protected int xPixelsPerMeter;
protected int yPixelsPerMeter;
protected int colorsUsed;
// 0 means all colors are important
protected int colorsImportant;
protected int[] masks;
protected DIBHeader() {
}
public static DIBHeader read(final DataInput pStream) throws IOException {
int size = pStream.readInt();
DIBHeader header = createHeader(size);
header.read(size, pStream);
return header;
}
private static DIBHeader createHeader(final int pSize) throws IOException {
switch (pSize) {
case DIB.BITMAP_CORE_HEADER_SIZE:
return new BitmapCoreHeader();
case DIB.OS2_V2_HEADER_16_SIZE:
case DIB.OS2_V2_HEADER_SIZE:
return new BitmapCoreHeaderV2();
case DIB.BITMAP_INFO_HEADER_SIZE:
// ICO and CUR always uses the Microsoft Windows 3.0 DIB header, which is 40 bytes.
// This is also the most common format for persistent BMPs.
return new BitmapInfoHeader();
case DIB.BITMAP_V3_INFO_HEADER_SIZE:
return new BitmapV3InfoHeader();
case DIB.BITMAP_V4_INFO_HEADER_SIZE:
return new BitmapV4InfoHeader();
case DIB.BITMAP_V5_INFO_HEADER_SIZE:
return new BitmapV5InfoHeader();
case DIB.BITMAP_V2_INFO_HEADER_SIZE:
throw new IIOException(String.format("Windows Bitmap Information Header (size: %s) not supported", pSize));
default:
throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", pSize));
}
}
protected abstract void read(int pSize, DataInput pStream) throws IOException;
public final int getSize() {
return size;
}
public final int getWidth() {
return width;
}
public final int getHeight() {
return height;
}
public final int getPlanes() {
return planes;
}
public final int getBitCount() {
return bitCount;
}
public int getCompression() {
return compression;
}
public int getImageSize() {
return imageSize != 0 ? imageSize : ((bitCount * width + 31) / 32) * 4 * height;
}
public int getXPixelsPerMeter() {
return xPixelsPerMeter;
}
public int getYPixelsPerMeter() {
return yPixelsPerMeter;
}
public int getColorsUsed() {
return colorsUsed;
}
public int getColorsImportant() {
return colorsImportant;
}
public boolean hasMasks() {
return masks != null || compression == DIB.COMPRESSION_BITFIELDS || compression == DIB.COMPRESSION_ALPHA_BITFIELDS;
}
public String toString() {
return String.format(
"%s: size: %d bytes, " +
"width: %d, height: %d, planes: %d, bit count: %d, compression: %d, " +
"image size: %d%s, " +
"X pixels per m: %d, Y pixels per m: %d, " +
"colors used: %d%s, colors important: %d%s",
getClass().getSimpleName(),
getSize(), getWidth(), getHeight(), getPlanes(), getBitCount(), getCompression(),
getImageSize(), (getImageSize() == 0 ? " (unknown)" : ""),
getXPixelsPerMeter(), getYPixelsPerMeter(),
getColorsUsed(), (getColorsUsed() == 0 ? " (unknown)" : ""),
getColorsImportant(), (getColorsImportant() == 0 ? " (all)" : "")
);
}
static int[] readMasks(final DataInput pStream) throws IOException {
int[] masks = new int[4];
for (int i = 0; i < masks.length; i++) {
masks[i] = pStream.readInt();
}
return masks;
}
// TODO: Get rid of code duplication below...
static final class BitmapCoreHeader extends DIBHeader {
protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.BITMAP_CORE_HEADER_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_CORE_HEADER_SIZE));
}
size = pSize;
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
width = pStream.readUnsignedShort();
height = pStream.readUnsignedShort();
if (height < 0) {
height = -height;
topDown = true;
}
planes = pStream.readUnsignedShort();
bitCount = pStream.readUnsignedShort();
// Roughly 72 DPI
xPixelsPerMeter = 2835;
yPixelsPerMeter = 2835;
}
}
/**
* OS/2 BitmapCoreHeader Version 2.
* <p/>
* NOTE: According to the docs this header is <em>variable size</em>.
* However, it seems that the size is either 16, 40 or 64, which is covered
* (40 is the size of the normal {@link BitmapInfoHeader}, and has the same layout).
*
* @see <a href="http://www.fileformat.info/format/os2bmp/egff.htm">OS/2 Bitmap File Format Summary</a>
*/
static final class BitmapCoreHeaderV2 extends DIBHeader {
protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE));
}
size = pSize;
width = pStream.readInt();
height = pStream.readInt();
if (height < 0) {
height = -height;
topDown = true;
}
planes = pStream.readUnsignedShort();
bitCount = pStream.readUnsignedShort();
if (pSize == DIB.OS2_V2_HEADER_16_SIZE) {
// Roughly 72 DPI
xPixelsPerMeter = 2835;
yPixelsPerMeter = 2835;
}
else {
compression = pStream.readInt();
imageSize = pStream.readInt();
xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = pStream.readInt();
colorsUsed = pStream.readInt();
colorsImportant = pStream.readInt();
}
int units = pStream.readShort();
int reserved = pStream.readShort();
int recording = pStream.readShort(); // Recording algorithm
int rendering = pStream.readShort(); // Halftoning algorithm
int size1 = pStream.readInt(); // Reserved for halftoning use
int size2 = pStream.readInt(); // Reserved for halftoning use
int colorEncoding = pStream.readInt(); // Color model used in bitmap
int identifier = pStream.readInt(); // Reserved for application use
}
}
/**
* Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure.
* This is the common format for persistent DIB structures, even if Windows
* may use the later versions at run-time.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: DIBHeader.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
*/
static final class BitmapInfoHeader extends DIBHeader {
protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.BITMAP_INFO_HEADER_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_INFO_HEADER_SIZE));
}
size = pSize;
width = pStream.readInt();
height = pStream.readInt();
if (height < 0) {
height = -height;
topDown = true;
}
planes = pStream.readUnsignedShort();
bitCount = pStream.readUnsignedShort();
compression = pStream.readInt();
imageSize = pStream.readInt();
xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = pStream.readInt();
colorsUsed = pStream.readInt();
colorsImportant = pStream.readInt();
}
}
/**
* Represents the semi-undocumented structure BITMAPV3INFOHEADER.
* @see <a href="https://forums.adobe.com/message/3272950#3272950">BITMAPV3INFOHEADER</a>
*/
static final class BitmapV3InfoHeader extends DIBHeader {
protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.BITMAP_V3_INFO_HEADER_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V3_INFO_HEADER_SIZE));
}
size = pSize;
width = pStream.readInt();
height = pStream.readInt();
if (height < 0) {
height = -height;
topDown = true;
}
planes = pStream.readUnsignedShort();
bitCount = pStream.readUnsignedShort();
compression = pStream.readInt();
imageSize = pStream.readInt();
xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = pStream.readInt();
colorsUsed = pStream.readInt();
colorsImportant = pStream.readInt();
masks = readMasks(pStream);
}
}
/**
* Represents the BITMAPV4INFOHEADER structure.
*/
static final class BitmapV4InfoHeader extends DIBHeader {
protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.BITMAP_V4_INFO_HEADER_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V4_INFO_HEADER_SIZE));
}
size = pSize;
width = pStream.readInt();
height = pStream.readInt();
if (height < 0) {
height = -height;
topDown = true;
}
planes = pStream.readUnsignedShort();
bitCount = pStream.readUnsignedShort();
compression = pStream.readInt();
imageSize = pStream.readInt();
xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = pStream.readInt();
colorsUsed = pStream.readInt();
colorsImportant = pStream.readInt();
masks = readMasks(pStream);
byte[] data = new byte[52];
pStream.readFully(data);
// System.out.println("data = " + Arrays.toString(data));
}
}
/**
* Represents the BITMAPV5INFOHEADER structure.
*/
static final class BitmapV5InfoHeader extends DIBHeader {
protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.BITMAP_V5_INFO_HEADER_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V5_INFO_HEADER_SIZE));
}
size = pSize;
width = pStream.readInt();
height = pStream.readInt();
if (height < 0) {
height = -height;
topDown = true;
}
planes = pStream.readUnsignedShort();
bitCount = pStream.readUnsignedShort();
compression = pStream.readInt();
imageSize = pStream.readInt();
xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = pStream.readInt();
colorsUsed = pStream.readInt();
colorsImportant = pStream.readInt();
masks = readMasks(pStream);
byte[] data = new byte[68];
pStream.readFully(data);
// System.out.println("data = " + Arrays.toString(data));
}
}
}

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
@ -290,7 +290,7 @@ abstract class DIBImageReader extends ImageReaderBase {
// Palette style
case 1:
case 4:
case 8:
case 8: // TODO: Gray!
descriptor = new BitmapIndexed(pEntry, header);
readBitmapIndexed((BitmapIndexed) descriptor);
break;
@ -454,6 +454,7 @@ abstract class DIBImageReader extends ImageReaderBase {
// TODO: No idea if this actually works..
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
// Will create TYPE_USHORT_555;
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import java.io.DataInput;
import java.io.IOException;

View File

@ -26,12 +26,12 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException;
import java.awt.*;
import java.io.DataInput;
import java.io.IOException;
import java.awt.*;
/**
* DirectoryEntry
@ -50,7 +50,7 @@ abstract class DirectoryEntry {
private int size;
private int offset;
private DirectoryEntry() {
DirectoryEntry() {
}
public static DirectoryEntry read(final int pType, final DataInput pStream) throws IOException {

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.spi.ImageReaderSpi;

View File

@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
@ -60,7 +60,7 @@ public final class ICOImageReaderSpi extends ImageReaderSpi {
"image/x-icon", // Common extension MIME
"image/ico" // Unofficial, but common
},
"com.twelvemonkeys.imageio.plugins.ico.ICOImageReader",
"com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader",
new Class[] {ImageInputStream.class},
null,
true, null, null, null, null,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, Harald Kuhr
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -26,32 +26,38 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io.enc;
package com.twelvemonkeys.imageio.plugins.bmp;
import java.io.InputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Implements 4 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE4Decoder.java#1 $
* @version $Id: RLE4Decoder.java#1 $
*/
// TODO: Move to other package or make public
final class RLE4Decoder extends AbstractRLEDecoder {
final static int BIT_MASKS[] = {0xf0, 0x0f};
final static int BIT_SHIFTS[] = {4, 0};
public RLE4Decoder(final int pWidth, final int pHeight) {
super((pWidth + 1) / 2, pHeight);
public RLE4Decoder(final int width) {
super(width, 4);
}
protected void decodeRow(final InputStream pInput) throws IOException {
protected void decodeRow(final InputStream stream) throws IOException {
// Just clear row now, and be done with it...
Arrays.fill(row, (byte) 0);
int deltaX = 0;
int deltaY = 0;
while (srcY >= 0) {
int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read());
int byte1 = stream.read();
int byte2 = checkEOF(stream.read());
if (byte1 == 0x00) {
switch (byte2) {
@ -59,64 +65,65 @@ final class RLE4Decoder extends AbstractRLEDecoder {
// End of line
// NOTE: Some BMPs have double EOLs..
if (srcX != 0) {
srcX = row.length;
srcX = row.length * 2;
}
break;
case 0x01:
// End of bitmap
srcX = row.length;
srcY = 0;
srcX = row.length * 2;
srcY = -1;
break;
case 0x02:
// Delta
deltaX = srcX + pInput.read();
deltaY = srcY - checkEOF(pInput.read());
srcX = row.length;
deltaX = srcX + stream.read();
deltaY = srcY + checkEOF(stream.read());
srcX = row.length * 2;
break;
default:
// Absolute mode
// Copy the next byte2 (3..255) bytes from file to output
// Copy the next byte2 (3..255) nibbles from file to output
// Two samples are packed into one byte
// If the number of bytes used to pack is not a mulitple of 2,
// If the *number of bytes* used to pack is not a multiple of 2,
// an additional padding byte is in the stream and must be skipped
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
while (byte2 > 1) {
int packed = checkEOF(pInput.read());
row[srcX++] = (byte) packed;
byte2 -= 2;
}
if (byte2 == 1) {
// TODO: Half byte alignment? Seems to be ok...
int packed = checkEOF(pInput.read());
row[srcX++] = (byte) (packed & 0xf0);
int packed = 0;
for (int i = 0; i < byte2; i++) {
if (i % 2 == 0) {
packed = checkEOF(stream.read());
}
row[srcX / 2] |= (byte) (((packed & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2])<< BIT_SHIFTS[srcX % 2]);
srcX++;
}
if (paddingByte) {
checkEOF(pInput.read());
checkEOF(stream.read());
}
break;
}
}
else {
// Encoded mode
// Replicate the two samples in byte2 as many times as byte1 says
while (byte1 > 1) {
row[srcX++] = (byte) byte2;
byte1 -= 2;
}
if (byte1 == 1) {
// TODO: Half byte alignment? Seems to be ok...
row[srcX++] = (byte) (byte2 & 0xf0);
for (int i = 0; i < byte1; i++) {
row[srcX / 2] |= (byte) (((byte2 & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2]) << BIT_SHIFTS[srcX % 2]);
srcX++;
}
}
// If we're done with a complete row, copy the data
if (srcX == row.length) {
if (srcX >= row.length * 2) {
// Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) {
srcX = (deltaX + 1) / 2;
srcX = deltaX;
if (deltaY > srcY) {
if (deltaY != srcY) {
srcY = deltaY;
break;
}
@ -124,9 +131,12 @@ final class RLE4Decoder extends AbstractRLEDecoder {
deltaX = 0;
deltaY = 0;
}
else if (srcY == -1) {
break;
}
else {
srcX = 0;
srcY--;
srcY++;
break;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, Harald Kuhr
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -26,32 +26,31 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io.enc;
package com.twelvemonkeys.imageio.plugins.bmp;
import java.io.InputStream;
import java.io.IOException;
import java.util.Arrays;
/**
* Implements 8 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE8Decoder.java#1 $
* @version $Id: RLE8Decoder.java#1 $
*/
// TODO: Move to other package or make public
final class RLE8Decoder extends AbstractRLEDecoder {
public RLE8Decoder(final int pWidth, final int pHeight) {
super(pWidth, pHeight);
public RLE8Decoder(final int width) {
super(width, 8);
}
protected void decodeRow(final InputStream pInput) throws IOException {
protected void decodeRow(final InputStream stream) throws IOException {
int deltaX = 0;
int deltaY = 0;
while (srcY >= 0) {
int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read());
int byte1 = stream.read();
int byte2 = checkEOF(stream.read());
if (byte1 == 0x00) {
switch (byte2) {
@ -59,29 +58,47 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// End of line
// NOTE: Some BMPs have double EOLs..
if (srcX != 0) {
Arrays.fill(row, srcX, row.length, (byte) 0);
srcX = row.length;
}
break;
case 0x01:
// End of bitmap
Arrays.fill(row, srcX, row.length, (byte) 0);
srcX = row.length;
srcY = 0;
srcY = -1; // TODO: Do we need to allow reading more (and thus re-introduce height parameter)..?
break;
case 0x02:
// Delta
deltaX = srcX + pInput.read();
deltaY = srcY - checkEOF(pInput.read());
srcX = row.length;
deltaX = srcX + stream.read();
deltaY = srcY + checkEOF(stream.read());
Arrays.fill(row, srcX, deltaX, (byte) 0);
// TODO: Handle x delta inline!
// if (deltaY != srcY) {
srcX = row.length;
// }
break;
default:
// Absolute mode
// Copy the next byte2 (3..255) bytes from file to output
// If the number bytes is not a multiple of 2,
// an additional padding byte is in the stream and must be skipped
boolean paddingByte = (byte2 % 2) != 0;
while (byte2-- > 0) {
row[srcX++] = (byte) checkEOF(pInput.read());
row[srcX++] = (byte) checkEOF(stream.read());
}
if (paddingByte) {
checkEOF(pInput.read());
checkEOF(stream.read());
}
}
}
@ -95,21 +112,25 @@ final class RLE8Decoder extends AbstractRLEDecoder {
}
// If we're done with a complete row, copy the data
if (srcX == row.length) {
if (srcX >= row.length) {
// Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) {
srcX = deltaX;
if (deltaY != srcY) {
srcY = deltaY;
break;
}
deltaX = 0;
deltaY = 0;
}
else if (srcY == -1) {
break;
}
else {
srcX = 0;
srcY--;
srcY++;
break;
}
}

View File

@ -0,0 +1,3 @@
com.twelvemonkeys.imageio.plugins.bmp.BMPImageReaderSpi
com.twelvemonkeys.imageio.plugins.bmp.CURImageReaderSpi
com.twelvemonkeys.imageio.plugins.bmp.ICOImageReaderSpi

View File

@ -0,0 +1,163 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Test;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* BMPImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BMPImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageReader> {
protected List<TestData> getTestData() {
return Arrays.asList(
// BMP Suite "Good"
new TestData(getClassLoaderResource("/bmpsuite/g/pal8.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal1.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal1bg.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal1wb.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal4.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal4rle.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8-0.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8nonsquare.bmp"), new Dimension(127, 32)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8os2.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8rle.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8topdown.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8v4.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8v5.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8w124.bmp"), new Dimension(124, 61)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8w125.bmp"), new Dimension(125, 62)),
new TestData(getClassLoaderResource("/bmpsuite/g/pal8w126.bmp"), new Dimension(126, 63)),
new TestData(getClassLoaderResource("/bmpsuite/g/rgb16-565.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/rgb16-565pal.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/rgb16.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/rgb24.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/rgb24pal.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/rgb32.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/g/rgb32bf.bmp"), new Dimension(127, 64)),
// BMP Suite "Questionable"
new TestData(getClassLoaderResource("/bmpsuite/q/pal1p1.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal2.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal4rletrns.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal8offs.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal8os2sp.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal8os2v2.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal8os2v2-16.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal8oversizepal.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/pal8rletrns.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb16-231.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgba16-4444.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb24jpeg.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb24largepal.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb24lprof.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb24png.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb24prof.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb32-111110.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgb32fakealpha.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgba32abf.bmp"), new Dimension(127, 64)),
new TestData(getClassLoaderResource("/bmpsuite/q/rgba32.bmp"), new Dimension(127, 64)),
// OS/2 samples
new TestData(getClassLoaderResource("/os2/money-2-(os2).bmp"), new Dimension(455, 341)),
new TestData(getClassLoaderResource("/os2/money-16-(os2).bmp"), new Dimension(455, 341)),
new TestData(getClassLoaderResource("/os2/money-256-(os2).bmp"), new Dimension(455, 341)),
new TestData(getClassLoaderResource("/os2/money-24bit-os2.bmp"), new Dimension(455, 341)),
// Vaious other samples
new TestData(getClassLoaderResource("/bmp/Blue Lace 16.bmp"), new Dimension(48, 48)),
new TestData(getClassLoaderResource("/bmp/blauesglas_mono.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_4.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_4.rle"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_8.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_8.rle"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_8-IM.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_gray.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_16.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_16_bitmask444.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_16_bitmask555.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_16_bitmask565.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_24.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_32.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888_reversed.bmp"), new Dimension(301, 331))
);
}
protected ImageReaderSpi createProvider() {
return new BMPImageReaderSpi();
}
@Override
protected BMPImageReader createReader() {
return new BMPImageReader(createProvider());
}
protected Class<BMPImageReader> getReaderClass() {
return BMPImageReader.class;
}
protected List<String> getFormatNames() {
return Arrays.asList("bmp");
}
protected List<String> getSuffixes() {
return Arrays.asList("bmp", "rle");
}
protected List<String> getMIMETypes() {
return Arrays.asList("image/bmp");
}
@Override
@Test
public void testGetTypeSpecifiers() throws IOException {
final ImageReader reader = createReader();
for (TestData data : getTestData()) {
reader.setInput(data.getInputStream());
ImageTypeSpecifier rawType = reader.getRawImageType(0);
// As the JPEGImageReader we delegate to returns null for YCbCr, we'll have to do the same
if (rawType == null && data.getInput().toString().contains("jpeg")) {
continue;
}
assertNotNull(rawType);
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
assertNotNull(types);
assertTrue(types.hasNext());
// TODO: This might fail even though the specifiers are obviously equal, if the
// color spaces they use are not the SAME instance, as ColorSpace uses identity equals
// and Interleaved ImageTypeSpecifiers are only equal if color spaces are equal...
boolean rawFound = false;
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
if (type.equals(rawType)) {
rawFound = true;
break;
}
}
assertTrue("ImageTypeSepcifier from getRawImageType should be in the iterator from getImageTypes", rawFound);
}
}
}

View File

@ -1,4 +1,4 @@
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Ignore;
@ -15,13 +15,13 @@ import java.util.List;
import static org.junit.Assert.*;
/**
* CURImageReaderTestCase
* CURImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CURImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
* @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class CURImageReaderTestCase extends ImageReaderAbstractTestCase<CURImageReader> {
public class CURImageReaderTest extends ImageReaderAbstractTestCase<CURImageReader> {
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)),

View File

@ -1,4 +1,4 @@
package com.twelvemonkeys.imageio.plugins.ico;
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Ignore;
@ -11,13 +11,13 @@ import java.util.Arrays;
import java.util.List;
/**
* ICOImageReaderTestCase
* ICOImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ICOImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
* @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class ICOImageReaderTestCase extends ImageReaderAbstractTestCase<ICOImageReader> {
public class ICOImageReaderTest extends ImageReaderAbstractTestCase<ICOImageReader> {
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(

View File

@ -0,0 +1,160 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderStream;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class RLE4DecoderTest {
public static final byte[] RLE_ENCODED = new byte[]{
0x03, 0x04, 0x05, 0x06, 0x00, 0x06, 0x45, 0x56, 0x67, 0x00, 0x04, 0x78, 0x00, 0x02, 0x05, 0x01,
0x04, 0x78, 0x00, 0x00, 0x09, 0x1E, 0x00, 0x01,
};
public static final byte[] DECODED = new byte[]{
0x04, 0x00, 0x60, 0x60, 0x45, 0x56, 0x67, 0x78, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, (byte) 0x87, (byte) 0x80, 0x00, 0x00,
0x1E, 0x1E, 0x1E, 0x1E, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
@Test
public void decodeBuffer() throws IOException {
// Setup:
InputStream rleStream = getClass().getResourceAsStream("/bmpsuite/g/pal4rle.bmp");
long rleOffset = 102;
InputStream plainSream = getClass().getResourceAsStream("/bmpsuite/g/pal4.bmp");
long plainOffset = 102;
skipFully(rleStream, rleOffset);
skipFully(plainSream, plainOffset);
ByteBuffer decoded = ByteBuffer.allocate(64);
Decoder decoder = new RLE4Decoder(127);
ByteBuffer plain = ByteBuffer.allocate(64);
ReadableByteChannel channel = Channels.newChannel(plainSream);
for (int i = 0; i < 64; i++) {
int d = decoder.decode(rleStream, decoded);
decoded.rewind();
int r = channel.read(plain);
plain.rewind();
assertEquals("Difference at line " + i, r, d);
assertArrayEquals("Difference at line " + i, plain.array(), decoded.array());
}
}
@Test
public void decodeStream() throws IOException {
// Setup:
InputStream rleStream = getClass().getResourceAsStream("/bmpsuite/g/pal4rle.bmp");
long rleOffset = 102;
InputStream plainSream = getClass().getResourceAsStream("/bmpsuite/g/pal4.bmp");
long plainOffset = 102;
skipFully(rleStream, rleOffset);
skipFully(plainSream, plainOffset);
InputStream decoded = new DecoderStream(rleStream, new RLE4Decoder(127));
int pos = 0;
while (true) {
int expected = plainSream.read();
assertEquals("Differs at " + pos, expected, decoded.read());
if (expected < 0) {
break;
}
pos++;
}
assertEquals(64 * 64, pos);
}
@Test
public void decodeStreamWeird() throws IOException {
// Setup:
InputStream rleStream = getClass().getResourceAsStream("/bmp/blauesglas_4.rle");
long rleOffset = 118;
InputStream plainSream = getClass().getResourceAsStream("/bmp/blauesglas_4.bmp");
long plainOffset = 118;
skipFully(rleStream, rleOffset);
skipFully(plainSream, plainOffset);
InputStream decoded = new DecoderStream(rleStream, new RLE4Decoder(301));
int pos = 0;
int w = ((301 * 4 + 31) / 32) * 4;
int h = 331;
int size = w * h;
while (pos < size) {
int expected = plainSream.read();
int actual = decoded.read();
// assertEquals("Differs at " + pos, expected, actual);
// Seems the initial RLE-encoding screwed up on some pixels...
if (expected < 0) {
break;
}
pos++;
}
// Rubbish assertion...
assertEquals(size, pos);
}
@Test
public void decodeExampleW27() throws IOException {
Decoder decoder = new RLE4Decoder(27); // Can be 27, 28, 29, 30, 31 or 32, and should all be the same.
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = decoder.decode(new ByteArrayInputStream(RLE_ENCODED), buffer);
assertArrayEquals(DECODED, Arrays.copyOfRange(buffer.array(), 0, count));
}
@Test
public void decodeExampleW28to31() throws IOException {
for (int i = 28; i < 32; i++) {
Decoder decoder = new RLE4Decoder(i); // Can be 27, 28, 29, 30, 31 or 32, and should all be the same.
ByteBuffer buffer = ByteBuffer.allocate(64);
int count = decoder.decode(new ByteArrayInputStream(RLE_ENCODED), buffer);
assertArrayEquals(DECODED, Arrays.copyOfRange(buffer.array(), 0, count));
}
}
@Test
public void decodeExampleW32() throws IOException {
Decoder decoder = new RLE4Decoder(32); // Can be 27, 28, 29, 30, 31 or 32, and should all be the same.
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = decoder.decode(new ByteArrayInputStream(RLE_ENCODED), buffer);
assertArrayEquals(DECODED, Arrays.copyOfRange(buffer.array(), 0, count));
}
private void skipFully(final InputStream stream, final long toSkip) throws IOException {
long skipped = 0;
while (skipped < toSkip) {
skipped += stream.skip(toSkip - skipped);
}
}
}

View File

@ -0,0 +1,127 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderStream;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class RLE8DecoderTest {
public static final byte[] RLE_ENCODED = new byte[]{
0x03, 0x04, 0x05, 0x06, 0x00, 0x03, 0x45, 0x56, 0x67, 0x00, 0x02, 0x78,
0x00, 0x02, 0x05, 0x01,
0x02, 0x78, 0x00, 0x00, // EOL
0x09, 0x1E,
0x00, 0x01, // EOF
};
public static final byte[] DECODED = new byte[]{
0x04, 0x04, 0x04, 0x06, 0x06, 0x06, 0x06, 0x06, 0x45, 0x56, 0x67, 0x78, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x78,
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
@Test
public void decodeBuffer() throws IOException {
// Setup:
InputStream rleStream = getClass().getResourceAsStream("/bmpsuite/g/pal8rle.bmp");
long rleOffset = 1062;
InputStream plainSream = getClass().getResourceAsStream("/bmpsuite/g/pal8.bmp");
long plainOffset = 1062;
skipFully(rleStream, rleOffset);
skipFully(plainSream, plainOffset);
ByteBuffer decoded = ByteBuffer.allocate(128);
Decoder decoder = new RLE8Decoder(127);
ByteBuffer plain = ByteBuffer.allocate(128);
ReadableByteChannel channel = Channels.newChannel(plainSream);
for (int i = 0; i < 64; i++) {
int d = decoder.decode(rleStream, decoded);
decoded.rewind();
int r = channel.read(plain);
plain.rewind();
assertEquals(r, d);
assertArrayEquals(plain.array(), decoded.array());
}
}
@Test
public void decodeStream() throws IOException {
// Setup:
InputStream rleStream = getClass().getResourceAsStream("/bmpsuite/g/pal8rle.bmp");
long rleOffset = 1062;
InputStream plainSream = getClass().getResourceAsStream("/bmpsuite/g/pal8.bmp");
long plainOffset = 1062;
skipFully(rleStream, rleOffset);
skipFully(plainSream, plainOffset);
InputStream decoded = new DecoderStream(rleStream, new RLE8Decoder(127));
int pos = 0;
while (true) {
int expected = plainSream.read();
assertEquals("Differs at " + pos, expected, decoded.read());
if (expected < 0) {
break;
}
pos++;
}
assertEquals(128 * 64, pos);
}
@Test
public void decodeExampleW20() throws IOException {
Decoder decoder = new RLE8Decoder(20);
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = decoder.decode(new ByteArrayInputStream(RLE_ENCODED), buffer);
assertArrayEquals(DECODED, Arrays.copyOfRange(buffer.array(), 0, count));
}
// @Test
// public void decodeExampleW28to31() throws IOException {
// for (int i = 28; i < 32; i++) {
// Decoder decoder = new RLE8Decoder(i); // Can be 27, 28, 29, 30, 31 or 32, and should all be the same.
// ByteBuffer buffer = ByteBuffer.allocate(64);
// int count = decoder.decode(new ByteArrayInputStream(RLE_ENCODED), buffer);
//
// assertArrayEquals(DECODED, Arrays.copyOfRange(buffer.array(), 0, count));
// }
// }
//
// @Test
// public void decodeExampleW32() throws IOException {
// Decoder decoder = new RLE8Decoder(32); // Can be 27, 28, 29, 30, 31 or 32, and should all be the same.
// ByteBuffer buffer = ByteBuffer.allocate(1024);
// int count = decoder.decode(new ByteArrayInputStream(RLE_ENCODED), buffer);
//
// assertArrayEquals(DECODED, Arrays.copyOfRange(buffer.array(), 0, count));
// }
private void skipFully(final InputStream stream, final long toSkip) throws IOException {
long skipped = 0;
while (skipped < toSkip) {
skipped += stream.skip(toSkip - skipped);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More