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

25
imageio/imageio-bmp/license.txt Executable file
View File

@@ -0,0 +1,25 @@
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.

View File

@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
<description>ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format.</description>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,140 @@
/*
* 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.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: AbstractRLEDecoder.java#1 $
*/
abstract class AbstractRLEDecoder implements Decoder {
protected final int width;
protected final int bitsPerSample;
protected final byte[] row;
protected int srcX;
protected int srcY;
protected int dstX;
protected int dstY;
/**
* 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 width width of the image
* @param bitsPerSample pits per sample
*/
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];
}
/**
* Decodes one full row of image 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 stream) throws IOException;
/**
* Decodes as much data as possible, from the stream into the buffer.
*
* @param stream the input stream containing RLE data
* @param buffer the buffer to decode the data to
*
* @return the number of bytes decoded from the stream, to the buffer
*
* @throws IOException if an I/O related exception occurs while reading
*/
public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
// 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 * bitsPerSample) / 8, buffer.remaining());
buffer.put(row, 0, length);
dstX += (length * 8) / bitsPerSample;
if (dstX == (row.length * 8) / bitsPerSample) {
dstX = 0;
dstY++;
// NOTE: If src Y is > dst Y, we have a delta, and have to fill the
// gap with zero-bytes
if (srcX > dstX) {
Arrays.fill(row, 0, (srcX * bitsPerSample) / 8, (byte) 0);
}
if (srcY > dstY) {
Arrays.fill(row, (byte) 0);
}
}
}
return buffer.position();
}
/**
* Checks a read byte for EOF marker.
*
* @param val the byte to check
* @return the value of {@code val} if positive.
*
* @throws EOFException if {@code val} is negative
*/
protected static int checkEOF(final int val) throws EOFException {
if (val < 0) {
throw new EOFException("Premature end of file");
}
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

@@ -0,0 +1,72 @@
/*
* 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 com.twelvemonkeys.lang.Validate;
import java.awt.image.BufferedImage;
/**
* Describes a bitmap structure.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: Bitmap.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
abstract class BitmapDescriptor {
protected final DirectoryEntry entry;
protected final DIBHeader header;
protected BufferedImage image;
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
Validate.notNull(pEntry, "entry");
Validate.notNull(pHeader, "header");
entry = pEntry;
header = pHeader;
}
abstract public BufferedImage getImage();
public final int getWidth() {
return entry.getWidth();
}
public final int getHeight() {
return entry.getHeight();
}
protected final int getColorCount() {
return entry.getColorCount() != 0 ? entry.getColorCount() : 1 << getBitCount();
}
protected final int getBitCount() {
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
}
}

View File

@@ -0,0 +1,181 @@
/*
* 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 com.twelvemonkeys.image.InverseColorMapIndexColorModel;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.util.Hashtable;
/**
* Describes an indexed bitmap structure (1, 4, or 8 bits per pixes).
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
class BitmapIndexed extends BitmapDescriptor {
protected final int[] bits;
protected final int[] colors;
private BitmapMask mask;
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
super(pEntry, pHeader);
bits = new int[getWidth() * getHeight()];
// NOTE: We're adding space for one extra color, for transparency
colors = new int[getColorCount() + 1];
}
public BufferedImage createImageIndexed() {
// TODO: This is very stupid, maybe we need a TYPE_CUSTOM image, with separate alphaRaster?!
// As ICO has a separate bitmask, not related to palette index (allows 256 colors + trans) :-P
IndexColorModel icm = createColorModel();
// This is slightly obscure, and should probably be moved..
Hashtable<String, Object> properties = null;
if (entry instanceof DirectoryEntry.CUREntry) {
properties = new Hashtable<String, Object>(1);
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
}
BufferedImage image = new BufferedImage(
icm,
icm.createCompatibleWritableRaster(getWidth(), getHeight()),
icm.isAlphaPremultiplied(), properties
);
WritableRaster raster = image.getRaster();
// Make pixels transparent according to mask
final int trans = icm.getTransparentPixel();
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
if (mask.isTransparent(x, y)) {
bits[x + getWidth() * y] = trans;
}
}
}
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
//System.out.println("Image: " + image);
return image;
}
/**
* @return Color model created from color palette in entry
*/
IndexColorModel createColorModel() {
// NOTE: This is a hack to make room for transparent pixel for mask
int bits = getBitCount();
int colors = this.colors.length;
int trans = -1;
// 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 = findTransIndexMaybeRemap(this.colors, this.bits);
if (index == -1) {
// No duplicate found, increase bitcount
bits++;
trans = this.colors.length - 1;
}
else {
// Found a duplicate, use it as trans
trans = index;
colors--;
}
}
// NOTE: Setting hasAlpha to true, makes things work on 1.2
return new InverseColorMapIndexColorModel(
bits, colors, this.colors, 0, true, trans,
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
);
}
private static int findTransIndexMaybeRemap(final int[] pColors, final int[] pBits) {
// Look for unused colors, to use as transparent
final boolean[] used = new boolean[pColors.length - 1];
for (int pBit : pBits) {
if (!used[pBit]) {
used[pBit] = true;
}
}
for (int i = 0; i < used.length; i++) {
if (!used[i]) {
return i;
}
}
// Try to find duplicates in colormap, and remap
int trans = -1;
int duplicate = -1;
for (int i = 0; trans == -1 && i < pColors.length - 1; i++) {
for (int j = i + 1; j < pColors.length - 1; j++) {
if (pColors[i] == pColors[j]) {
trans = j;
duplicate = i;
break;
}
}
}
if (trans != -1) {
// Remap duplicate
for (int i = 0; i < pBits.length; i++) {
if (pBits[i] == trans) {
pBits[i] = duplicate;
}
}
}
return trans;
}
public BufferedImage getImage() {
if (image == null) {
image = createImageIndexed();
}
return image;
}
public void setMask(final BitmapMask pMask) {
mask = pMask;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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 java.awt.image.BufferedImage;
/**
* Describes a transparency mask structure (1 bit).
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
class BitmapMask extends BitmapDescriptor {
protected final BitmapIndexed mask;
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
super(pParent, pHeader);
mask = new BitmapIndexed(pParent, pHeader);
}
boolean isTransparent(final int pX, final int pY) {
// NOTE: 1: Fully transparent, 0: Opaque...
return mask.bits[pX + pY * getWidth()] != 0;
}
public BufferedImage getImage() {
return mask.getImage();
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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 java.awt.image.BufferedImage;
/**
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapRGB.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
class BitmapRGB extends BitmapDescriptor {
public BitmapRGB(final DirectoryEntry pEntry, final DIBHeader pHeader) {
super(pEntry, pHeader);
}
public BufferedImage getImage() {
return image;
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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 java.awt.image.BufferedImage;
/**
* Represents bitmap structures we can't read.
* Allows for deferred exception handling, and allowing clients to read all images that can be read.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
class BitmapUnsupported extends BitmapDescriptor {
private String message;
public BitmapUnsupported(final DirectoryEntry pEntry, final String pMessage) {
super(pEntry, null);
message = pMessage;
}
public BufferedImage getImage() {
throw new IllegalStateException(message);
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
/**
* ImageReader for Microsoft Windows CUR (cursor) 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 ICOImageReader
*/
public final class CURImageReader extends DIBImageReader {
public CURImageReader() {
super(new CURImageReaderSpi());
}
protected CURImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
/**
* Returns the hot spot location for the cursor.
*
* @param pImageIndex the index of the cursor in the current input.
* @return the hot spot location for the cursor
*
* @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
*/
public final Point getHotSpot(final int pImageIndex) throws IOException {
DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) getEntry(pImageIndex);
return entry.getHotspot();
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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 com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
/**
* CURImageReaderSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: CURImageReaderSpi.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
public final class CURImageReaderSpi extends ImageReaderSpi {
public CURImageReaderSpi() {
this(IIOUtil.getProviderInfo(CURImageReaderSpi.class));
}
private CURImageReaderSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
new String[]{"cur", "CUR"},
new String[]{"cur"},
new String[]{
"image/vnd.microsoft.cursor", // Official IANA MIME
"image/x-cursor", // Common extension MIME
"image/cursor" // Unofficial, but common
},
"com.twelvemonkeys.imageio.plugins.bmp.CURImageReader",
new Class[] {ImageInputStream.class},
null,
true, null, null, null, null,
true,
null, null,
null, null
);
}
public boolean canDecodeInput(final Object pSource) throws IOException {
return pSource instanceof ImageInputStream && ICOImageReaderSpi.canDecode((ImageInputStream) pSource, DIB.TYPE_CUR);
}
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
return new CURImageReader(this);
}
public String getDescription(final Locale pLocale) {
return "Windows Cursor Format (CUR) Reader";
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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;
/**
* DIB
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: DIB.java,v 1.0 Apr 8, 2008 1:43:04 PM haraldk Exp$
*
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)">ICO file format (Wikipedia)</a>
*/
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 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.
*/
int BITMAP_INFO_HEADER_SIZE = 40;
int BITMAP_V2_INFO_HEADER_SIZE = 52; // Undocumented, written by Photoshop
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

@@ -0,0 +1,690 @@
/*
* 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 com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
import com.twelvemonkeys.util.WeakWeakMap;
import javax.imageio.*;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import java.util.*;
import java.util.List;
/**
* ImageReader for Microsoft Windows ICO (icon) format.
* 1, 4, 8 bit palette support with bitmask transparency, and 16, 24 and 32 bit
* true color support with alpha. Also supports Windows Vista PNG encoded icons.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: ICOImageReader.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>
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)">ICO file format (Wikipedia)</a>
*/
// SEE http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
// TODO: Support loading icons from DLLs, see
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
abstract class DIBImageReader extends ImageReaderBase {
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
private Directory directory;
// TODO: Review these, make sure we don't have a memory leak
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<DirectoryEntry, DIBHeader>();
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
private ImageReader pngImageReader;
protected DIBImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
protected void resetMembers() {
directory = null;
headers.clear();
descriptors.clear();
if (pngImageReader != null) {
pngImageReader.dispose();
pngImageReader = null;
}
}
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
DirectoryEntry entry = getEntry(pImageIndex);
// NOTE: Delegate to PNG reader
if (isPNG(entry)) {
return getImageTypesPNG(entry);
}
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
DIBHeader header = getHeader(entry);
// Use data from header to create specifier
ImageTypeSpecifier specifier;
switch (header.getBitCount()) {
case 1:
case 2:
case 4:
case 8:
// TODO: This is slightly QnD...
int offset = entry.getOffset() + header.getSize();
if (offset != imageInput.getStreamPosition()) {
imageInput.seek(offset);
}
BitmapIndexed indexed = new BitmapIndexed(entry, header);
readColorMap(indexed);
specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(indexed.createColorModel());
break;
case 16:
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
break;
case 24:
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
break;
case 32:
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
break;
default:
throw new IIOException(String.format("Unknown bit depth: %d", header.getBitCount()));
}
types.add(specifier);
return types.iterator();
}
@Override
public int getNumImages(final boolean allowSearch) throws IOException {
return getDirectory().count();
}
public int getWidth(final int pImageIndex) throws IOException {
return getEntry(pImageIndex).getWidth();
}
public int getHeight(final int pImageIndex) throws IOException {
return getEntry(pImageIndex).getHeight();
}
public BufferedImage read(final int pImageIndex, final ImageReadParam pParam) throws IOException {
checkBounds(pImageIndex);
processImageStarted(pImageIndex);
DirectoryEntry entry = getEntry(pImageIndex);
BufferedImage destination;
if (isPNG(entry)) {
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
destination = readPNG(entry, pParam);
}
else {
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
// to allow for storing the cursor hotspot for CUR images
destination = hasExplicitDestination(pParam) ?
getDestination(pParam, getImageTypes(pImageIndex), getWidth(pImageIndex), getHeight(pImageIndex)) : null;
BufferedImage image = readBitmap(entry);
// TODO: Handle AOI and subsampling inline, probably not of big importance...
if (pParam != null) {
image = fakeAOI(image, pParam);
image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
}
if (destination == null) {
// This is okay, as long as the client did not request explicit destination image/type
destination = image;
}
else {
Graphics2D g = destination.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(image, 0, 0, null);
}
finally {
g.dispose();
}
}
}
processImageProgress(100);
processImageComplete();
return destination;
}
private boolean isPNG(final DirectoryEntry pEntry) throws IOException {
long magic;
imageInput.seek(pEntry.getOffset());
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
try {
magic = imageInput.readLong();
}
finally {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
}
return magic == DIB.PNG_MAGIC;
}
private BufferedImage readPNG(final DirectoryEntry pEntry, final ImageReadParam pParam) throws IOException {
// TODO: Consider delegating listener calls
return initPNGReader(pEntry).read(0, pParam);
}
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry pEntry) throws IOException {
return initPNGReader(pEntry).getImageTypes(0);
}
private ImageReader initPNGReader(final DirectoryEntry pEntry) throws IOException {
ImageReader pngReader = getPNGReader();
imageInput.seek(pEntry.getOffset());
InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize());
ImageInputStream stream = ImageIO.createImageInputStream(inputStream);
// NOTE: Will throw IOException on later reads if input is not PNG
pngReader.setInput(stream);
return pngReader;
}
private ImageReader getPNGReader() throws IIOException {
// TODO: Prefer Sun's std JDK PNGImagerReader, because it has known behaviour?
if (pngImageReader == null) {
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("PNG");
if (readers.hasNext()) {
pngImageReader = readers.next();
}
else {
throw new IIOException("No PNGImageReader found using ImageIO, can't read PNG encoded ICO format.");
}
}
else {
pngImageReader.reset();
}
return pngImageReader;
}
private DIBHeader getHeader(final DirectoryEntry pEntry) throws IOException {
if (!headers.containsKey(pEntry)) {
imageInput.seek(pEntry.getOffset());
DIBHeader header = DIBHeader.read(imageInput);
headers.put(pEntry, header);
}
return headers.get(pEntry);
}
private BufferedImage readBitmap(final DirectoryEntry pEntry) throws IOException {
// TODO: Get rid of the caching, as the images are mutable
BitmapDescriptor descriptor = descriptors.get(pEntry);
if (descriptor == null || !descriptors.containsKey(pEntry)) {
DIBHeader header = getHeader(pEntry);
int offset = pEntry.getOffset() + header.getSize();
if (offset != imageInput.getStreamPosition()) {
imageInput.seek(offset);
}
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
if (header.getCompression() != 0) {
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported compression: %d", header.getCompression()));
}
else {
int bitCount = header.getBitCount();
switch (bitCount) {
// Palette style
case 1:
case 4:
case 8: // TODO: Gray!
descriptor = new BitmapIndexed(pEntry, header);
readBitmapIndexed((BitmapIndexed) descriptor);
break;
// RGB style
case 16:
descriptor = new BitmapRGB(pEntry, header);
readBitmap16(descriptor);
break;
case 24:
descriptor = new BitmapRGB(pEntry, header);
readBitmap24(descriptor);
break;
case 32:
descriptor = new BitmapRGB(pEntry, header);
readBitmap32(descriptor);
break;
default:
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported bit count %d", bitCount));
}
}
descriptors.put(pEntry, descriptor);
}
return descriptor.getImage();
}
private void readBitmapIndexed(final BitmapIndexed pBitmap) throws IOException {
readColorMap(pBitmap);
switch (pBitmap.getBitCount()) {
case 1:
readBitmapIndexed1(pBitmap, false);
break;
case 4:
readBitmapIndexed4(pBitmap);
break;
case 8:
readBitmapIndexed8(pBitmap);
break;
}
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
readBitmapIndexed1(mask.mask, true);
pBitmap.setMask(mask);
}
private void readColorMap(final BitmapIndexed pBitmap) throws IOException {
int colorCount = pBitmap.getColorCount();
for (int i = 0; i < colorCount; i++) {
// aRGB (a is "Reserved")
pBitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
}
}
private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
int width = adjustToPadding(pBitmap.getWidth() >> 3);
byte[] row = new byte[width];
for (int y = 0; y < pBitmap.getHeight(); y++) {
imageInput.readFully(row, 0, width);
int rowPos = 0;
int xOrVal = 0x80;
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
for (int x = 0; x < pBitmap.getWidth(); x++) {
pBitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
if (xOrVal == 1) {
xOrVal = 0x80;
rowPos++;
}
else {
xOrVal >>= 1;
}
}
// NOTE: If we are reading the mask, we don't abort or progress
if (!pAsMask) {
if (abortRequested()) {
processReadAborted();
break;
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
}
}
private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
int width = adjustToPadding(pBitmap.getWidth() >> 1);
byte[] row = new byte[width];
for (int y = 0; y < pBitmap.getHeight(); y++) {
imageInput.readFully(row, 0, width);
int rowPos = 0;
boolean high4 = true;
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
for (int x = 0; x < pBitmap.getWidth(); x++) {
int value;
if (high4) {
value = (row[rowPos] & 0xF0) >> 4;
}
else {
value = row[rowPos] & 0x0F;
rowPos++;
}
pBitmap.bits[pos++] = value & 0xFF;
high4 = !high4;
}
if (abortRequested()) {
processReadAborted();
break;
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
}
private void readBitmapIndexed8(final BitmapIndexed pBitmap) throws IOException {
int width = adjustToPadding(pBitmap.getWidth());
byte[] row = new byte[width];
for (int y = 0; y < pBitmap.getHeight(); y++) {
imageInput.readFully(row, 0, width);
int rowPos = 0;
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
for (int x = 0; x < pBitmap.getWidth(); x++) {
pBitmap.bits[pos++] = row[rowPos++] & 0xFF;
}
if (abortRequested()) {
processReadAborted();
break;
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
}
/**
* @param pWidth Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 1)
* @return padded width
*/
private static int adjustToPadding(final int pWidth) {
if ((pWidth & 0x03) != 0) {
return (pWidth & ~0x03) + 4;
}
return pWidth;
}
private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
// 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);
WritableRaster raster = Raster.createPackedRaster(
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
);
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
imageInput.readFully(pixels, offset, pBitmap.getWidth());
// Skip to 32 bit boundary
if (pBitmap.getWidth() % 2 != 0) {
imageInput.readShort();
}
if (abortRequested()) {
processReadAborted();
break;
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
}
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
byte[] pixels = new byte[pBitmap.getWidth() * pBitmap.getHeight() * 3];
// Create TYPE_3BYTE_BGR
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] nBits = {8, 8, 8};
int[] bOffs = {2, 1, 0};
ComponentColorModel cm = new ComponentColorModel(
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
);
WritableRaster raster = Raster.createInterleavedRaster(
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
);
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
// TODO: Possibly read padding byte here!
if (abortRequested()) {
processReadAborted();
break;
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
}
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
int[] pixels = new int[pBitmap.getWidth() * pBitmap.getHeight()];
// Will create TYPE_INT_ARGB
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
WritableRaster raster = Raster.createPackedRaster(
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
);
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
imageInput.readFully(pixels, offset, pBitmap.getWidth());
if (abortRequested()) {
processReadAborted();
break;
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
}
private Directory getDirectory() throws IOException {
assertInput();
if (directory == null) {
readFileHeader();
}
return directory;
}
private void readFileHeader() throws IOException {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
// Read file header
imageInput.readUnsignedShort(); // Reserved
// Should be same as type as the provider
int type = imageInput.readUnsignedShort();
int imageCount = imageInput.readUnsignedShort();
// Read directory
directory = Directory.read(type, imageCount, imageInput);
}
final DirectoryEntry getEntry(final int pImageIndex) throws IOException {
Directory directory = getDirectory();
if (pImageIndex < 0 || pImageIndex >= directory.count()) {
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", pImageIndex, directory.count()));
}
return directory.getEntry(pImageIndex);
}
/// Test code below, ignore.. :-)
public static void main(final String[] pArgs) throws IOException {
if (pArgs.length == 0) {
System.err.println("Please specify the icon file name");
System.exit(1);
}
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) {
// Ignore
}
String title = new File(pArgs[0]).getName();
JFrame frame = createWindow(title);
JPanel root = new JPanel(new FlowLayout());
JScrollPane scroll =
new JScrollPane(root, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scroll.setBorder(BorderFactory.createEmptyBorder());
frame.setContentPane(scroll);
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("ico");
if (!readers.hasNext()) {
System.err.println("No reader for format 'ico' found");
System.exit(1);
}
ImageReader reader = readers.next();
for (String arg : pArgs) {
JPanel panel = new JPanel(null);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
readImagesInFile(arg, reader, panel);
root.add(panel);
}
frame.pack();
frame.setVisible(true);
}
private static void readImagesInFile(String pFileName, ImageReader pReader, final Container pContainer) throws IOException {
File file = new File(pFileName);
if (!file.isFile()) {
System.err.println(pFileName + " not found, or is no file");
}
pReader.setInput(ImageIO.createImageInputStream(file));
int imageCount = pReader.getNumImages(true);
for (int i = 0; i < imageCount; i++) {
try {
addImage(pContainer, pReader, i);
}
catch (Exception e) {
System.err.println("FileName: " + pFileName);
System.err.println("Icon: " + i);
e.printStackTrace();
}
}
}
private static JFrame createWindow(final String pTitle) {
JFrame frame = new JFrame(pTitle);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
System.exit(0);
}
});
return frame;
}
private static void addImage(final Container pParent, final ImageReader pReader, final int pImageNo) throws IOException {
final JButton button = new JButton();
BufferedImage image = pReader.read(pImageNo);
button.setIcon(new ImageIcon(image) {
TexturePaint texture;
private void createTexture(final GraphicsConfiguration pGraphicsConfiguration) {
BufferedImage pattern = pGraphicsConfiguration.createCompatibleImage(20, 20);
Graphics2D g = pattern.createGraphics();
try {
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, pattern.getWidth(), pattern.getHeight());
g.setColor(Color.GRAY);
g.fillRect(0, 0, pattern.getWidth() / 2, pattern.getHeight() / 2);
g.fillRect(pattern.getWidth() / 2, pattern.getHeight() / 2, pattern.getWidth() / 2, pattern.getHeight() / 2);
}
finally {
g.dispose();
}
texture = new TexturePaint(pattern, new Rectangle(pattern.getWidth(), pattern.getHeight()));
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (texture == null) {
createTexture(c.getGraphicsConfiguration());
}
Graphics2D gr = (Graphics2D) g;
gr.setPaint(texture);
gr.fillRect(x, y, getIconWidth(), getIconHeight());
super.paintIcon(c, g, x, y);
}
});
button.setText("" + image.getWidth() + "x" +
image.getHeight() + ": "
+ ((image.getColorModel() instanceof IndexColorModel) ?
"" + ((IndexColorModel) image.getColorModel()).getMapSize() :
"TrueColor"));
pParent.add(button);
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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 java.io.DataInput;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* Directory
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: Directory.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
class Directory {
private final List<DirectoryEntry> entries;
private Directory(int pImageCount) {
entries = Arrays.asList(new DirectoryEntry[pImageCount]);
}
public static Directory read(final int pType, final int pImageCount, final DataInput pStream) throws IOException {
Directory directory = new Directory(pImageCount);
directory.readEntries(pType, pStream);
return directory;
}
private void readEntries(final int pType, final DataInput pStream) throws IOException {
for (int i = 0; i < entries.size(); i++) {
entries.set(i, DirectoryEntry.read(pType, pStream));
}
}
public DirectoryEntry getEntry(final int pEntryIndex) {
return entries.get(pEntryIndex);
}
public int count() {
return entries.size();
}
@Override
public String toString() {
return String.format("%s%s", getClass().getSimpleName(), entries);
}
}

View File

@@ -0,0 +1,164 @@
/*
* 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.awt.*;
import java.io.DataInput;
import java.io.IOException;
/**
* DirectoryEntry
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: DirectoryEntry.java,v 1.0 Apr 4, 2009 4:29:53 PM haraldk Exp$
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)#Directory">Wikipedia</a>
*/
abstract class DirectoryEntry {
private int width;
private int height;
private int colorCount;
int planes;
int bitCount;
private int size;
private int offset;
DirectoryEntry() {
}
public static DirectoryEntry read(final int pType, final DataInput pStream) throws IOException {
DirectoryEntry entry = createEntry(pType);
entry.read(pStream);
return entry;
}
private static DirectoryEntry createEntry(int pType) throws IIOException {
switch (pType) {
case DIB.TYPE_ICO:
return new ICOEntry();
case DIB.TYPE_CUR:
return new CUREntry();
default:
throw new IIOException(
String.format(
"Unknown DIB type: %s, expected: %s (ICO) or %s (CUR)",
pType, DIB.TYPE_ICO, DIB.TYPE_CUR
)
);
}
}
protected void read(final DataInput pStream) throws IOException {
// Width/height = 0, means 256
int w = pStream.readUnsignedByte();
width = w == 0 ? 256 : w;
int h = pStream.readUnsignedByte();
height = h == 0 ? 256 : h;
// Color count = 0, means 256 or more colors
colorCount = pStream.readUnsignedByte();
// Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia
pStream.readUnsignedByte();
planes = pStream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
bitCount = pStream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
// Size of bitmap in bytes
size = pStream.readInt();
offset = pStream.readInt();
}
public String toString() {
return String.format(
"%s: width: %d, height: %d, colors: %d, planes: %d, bit count: %d, size: %d, offset: %d",
getClass().getSimpleName(),
width, height, colorCount, planes, bitCount, size, offset
);
}
public int getBitCount() {
return bitCount;
}
public int getColorCount() {
return colorCount;
}
public int getHeight() {
return height;
}
public int getOffset() {
return offset;
}
public int getPlanes() {
return planes;
}
public int getSize() {
return size;
}
public int getWidth() {
return width;
}
/**
* Cursor directory entry.
*/
static class CUREntry extends DirectoryEntry {
private int xHotspot;
private int yHotspot;
@Override
protected void read(final DataInput pStream) throws IOException {
super.read(pStream);
// NOTE: This is a hack...
xHotspot = planes;
yHotspot = bitCount;
planes = 1; // Always 1 for all BMP types
bitCount = 0;
}
public Point getHotspot() {
return new Point(xHotspot, yHotspot);
}
}
/**
* Icon directory entry.
*/
static final class ICOEntry extends DirectoryEntry {
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2011, 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.spi.ImageReaderSpi;
/**
* ImageReader for Microsoft Windows CUR (cursor) 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 CURImageReader
*/
public final class ICOImageReader extends DIBImageReader {
public ICOImageReader() {
super(new ICOImageReaderSpi());
}
protected ICOImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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 com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
/**
* ICOImageReaderSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: ICOImageReaderSpi.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
public final class ICOImageReaderSpi extends ImageReaderSpi {
public ICOImageReaderSpi() {
this(IIOUtil.getProviderInfo(ICOImageReaderSpi.class));
}
private ICOImageReaderSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
new String[]{"ico", "ICO"},
new String[]{"ico"},
new String[]{
"image/vnd.microsoft.icon", // Official IANA MIME
"image/x-icon", // Common extension MIME
"image/ico" // Unofficial, but common
},
"com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader",
new Class[] {ImageInputStream.class},
null,
true, null, null, null, null,
true,
null, null,
null, null
);
}
public boolean canDecodeInput(final Object pSource) throws IOException {
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource, DIB.TYPE_ICO);
}
static boolean canDecode(final ImageInputStream pInput, final int pType) throws IOException {
byte[] signature = new byte[4];
try {
pInput.mark();
pInput.readFully(signature);
int count = pInput.readByte() + (pInput.readByte() << 8);
return (signature[0] == 0x0 && signature[1] == 0x0 && signature[2] == pType
&& signature[3] == 0x0 && count > 0);
}
finally {
pInput.reset();
}
}
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
return new ICOImageReader(this);
}
public String getDescription(final Locale pLocale) {
return "Windows Icon Format (ICO) Reader";
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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 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: RLE4Decoder.java#1 $
*/
final class RLE4Decoder extends AbstractRLEDecoder {
final static int BIT_MASKS[] = {0xf0, 0x0f};
final static int BIT_SHIFTS[] = {4, 0};
public RLE4Decoder(final int width) {
super(width, 4);
}
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 = stream.read();
int byte2 = checkEOF(stream.read());
if (byte1 == 0x00) {
switch (byte2) {
case 0x00:
// End of line
// NOTE: Some BMPs have double EOLs..
if (srcX != 0) {
srcX = row.length * 2;
}
break;
case 0x01:
// End of bitmap
srcX = row.length * 2;
srcY = -1;
break;
case 0x02:
// Delta
deltaX = srcX + stream.read();
deltaY = srcY + checkEOF(stream.read());
srcX = row.length * 2;
break;
default:
// Absolute mode
// 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 multiple of 2,
// an additional padding byte is in the stream and must be skipped
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
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(stream.read());
}
}
}
else {
// Encoded mode
// Replicate the two samples in byte2 as many times as byte1 says
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 * 2) {
// 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++;
break;
}
}
}
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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 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: RLE8Decoder.java#1 $
*/
final class RLE8Decoder extends AbstractRLEDecoder {
public RLE8Decoder(final int width) {
super(width, 8);
}
protected void decodeRow(final InputStream stream) throws IOException {
int deltaX = 0;
int deltaY = 0;
while (srcY >= 0) {
int byte1 = stream.read();
int byte2 = checkEOF(stream.read());
if (byte1 == 0x00) {
switch (byte2) {
case 0x00:
// 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 = -1; // TODO: Do we need to allow reading more (and thus re-introduce height parameter)..?
break;
case 0x02:
// Delta
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(stream.read());
}
if (paddingByte) {
checkEOF(stream.read());
}
}
}
else {
// Encoded mode
// Replicate byte2 as many times as byte1 says
byte value = (byte) byte2;
while (byte1-- > 0) {
row[srcX++] = value;
}
}
// If we're done with a complete row, copy the data
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++;
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

@@ -0,0 +1,121 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Ignore;
import org.junit.Test;
import javax.imageio.ImageReadParam;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
/**
* CURImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class CURImageReaderTest extends ImageReaderAbstractTestCase<CURImageReader> {
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)),
new TestData(getClassLoaderResource("/cur/zoom.cur"), new Dimension(32, 32))
);
}
protected ImageReaderSpi createProvider() {
return new CURImageReaderSpi();
}
@Override
protected CURImageReader createReader() {
return new CURImageReader();
}
protected Class<CURImageReader> getReaderClass() {
return CURImageReader.class;
}
protected List<String> getFormatNames() {
return Arrays.asList("cur");
}
protected List<String> getSuffixes() {
return Arrays.asList("cur");
}
protected List<String> getMIMETypes() {
return Arrays.asList("image/vnd.microsoft.cursor", "image/cursor", "image/x-cursor");
}
private void assertHotSpot(final TestData pTestData, final ImageReadParam pParam, final Point pExpected) throws IOException {
CURImageReader reader = createReader();
reader.setInput(pTestData.getInputStream());
BufferedImage image = reader.read(0, pParam);
// We can only be sure the hotspot is defined, if no param, but if defined, it must be correct
Object hotspot = image.getProperty("cursor_hotspot");
if (hotspot != Image.UndefinedProperty || pParam == null) {
// Typically never happens, because of weirdness with UndefinedProperty
assertNotNull("Hotspot for cursor not present", hotspot);
// Image weirdness
assertTrue("Hotspot for cursor undefined (java.awt.Image.UndefinedProperty)", Image.UndefinedProperty != hotspot);
assertTrue(String.format("Hotspot not a java.awt.Point: %s", hotspot.getClass()), hotspot instanceof Point);
assertEquals(pExpected, hotspot);
}
assertNotNull("Hotspot for cursor not present", reader.getHotSpot(0));
assertEquals(pExpected, reader.getHotSpot(0));
}
@Test
public void testHandHotspot() throws IOException {
assertHotSpot(getTestData().get(0), null, new Point(15, 15));
}
@Test
public void testZoomHotspot() throws IOException {
assertHotSpot(getTestData().get(1), null, new Point(13, 11));
}
@Test
public void testHandHotspotWithParam() throws IOException {
ImageReadParam param = new ImageReadParam();
assertHotSpot(getTestData().get(0), param, new Point(15, 15));
}
@Test
public void testHandHotspotExplicitDestination() throws IOException {
CURImageReader reader = createReader();
reader.setInput(getTestData().get(0).getInputStream());
BufferedImage image = reader.read(0);
// Create dest image with same data, except properties...
BufferedImage dest = new BufferedImage(
image.getColorModel(), image.getRaster(), image.getColorModel().isAlphaPremultiplied(), null
);
ImageReadParam param = new ImageReadParam();
param.setDestination(dest);
assertHotSpot(getTestData().get(0), param, new Point(15, 15));
}
// TODO: Test cursor is transparent
@Test
@Ignore("Known issue")
@Override
public void testNotBadCaching() throws IOException {
super.testNotBadCaching();
}
}

View File

@@ -0,0 +1,76 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Ignore;
import org.junit.Test;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* ICOImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class ICOImageReaderTest extends ImageReaderAbstractTestCase<ICOImageReader> {
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(
getClassLoaderResource("/ico/JavaCup.ico"),
new Dimension(48, 48), new Dimension(32, 32), new Dimension(16, 16),
new Dimension(48, 48), new Dimension(32, 32), new Dimension(16, 16),
new Dimension(48, 48), new Dimension(32, 32), new Dimension(16, 16)
),
new TestData(getClassLoaderResource("/ico/favicon.ico"), new Dimension(32, 32)),
new TestData(
getClassLoaderResource("/ico/joypad.ico"),
new Dimension(16, 16), new Dimension(24, 24), new Dimension(32, 32), new Dimension(48, 48),
new Dimension(16, 16), new Dimension(24, 24), new Dimension(32, 32), new Dimension(48, 48)
),
// Windows Vista icon, PNG encoded for 256x256 sizes
new TestData(
getClassLoaderResource("/ico/down.ico"),
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
)
);
}
protected ImageReaderSpi createProvider() {
return new ICOImageReaderSpi();
}
@Override
protected ICOImageReader createReader() {
return new ICOImageReader();
}
protected Class<ICOImageReader> getReaderClass() {
return ICOImageReader.class;
}
protected List<String> getFormatNames() {
return Arrays.asList("ico");
}
protected List<String> getSuffixes() {
return Arrays.asList("ico");
}
protected List<String> getMIMETypes() {
return Arrays.asList("image/vnd.microsoft.icon", "image/ico", "image/x-icon");
}
@Test
@Ignore("Known issue")
@Override
public void testNotBadCaching() throws IOException {
super.testNotBadCaching();
}
}

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