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
@@ -1,4 +1,4 @@
Copyright (c) 2009, Harald Kuhr Copyright (c) 2014, Harald Kuhr
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -6,9 +6,9 @@
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.1-SNAPSHOT</version> <version>3.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-ico</artifactId> <artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICO plugin</name> <name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
<description>ImageIO plugin for Windows Icon (ICO) and Cursor (CUR) format.</description> <description>ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format.</description>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, Harald Kuhr * Copyright (c) 2014, Harald Kuhr
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -26,24 +26,29 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.io.enc.Decoder;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer; 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. * Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/> * <p/>
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java#1 $ * @version $Id: AbstractRLEDecoder.java#1 $
*/ */
// TODO: Move to other package or make public
abstract class AbstractRLEDecoder implements Decoder { abstract class AbstractRLEDecoder implements Decoder {
protected final byte[] row;
protected final int width; protected final int width;
protected final int bitsPerSample;
protected final byte[] row;
protected int srcX; protected int srcX;
protected int srcY; protected int srcY;
protected int dstX; protected int dstX;
@@ -52,36 +57,26 @@ abstract class AbstractRLEDecoder implements Decoder {
/** /**
* Creates an RLEDecoder. As RLE encoded BMPs may contain x and y deltas, * Creates an RLEDecoder. As RLE encoded BMPs may contain x and y deltas,
* etc, we need to know height and width of the image. * etc, we need to know height and width of the image.
* * @param width width of the image
* @param pWidth width of the image * @param bitsPerSample pits per sample
* @param pHeight height of the image
*/ */
AbstractRLEDecoder(final int pWidth, final int pHeight) { AbstractRLEDecoder(final int width, final int bitsPerSample) {
width = pWidth; this.width = width;
int bytesPerRow = width; this.bitsPerSample = bitsPerSample;
int mod = bytesPerRow % 4;
if (mod != 0) {
bytesPerRow += 4 - mod;
}
// Pad row to multiple of 4
int bytesPerRow = ((bitsPerSample * this.width + 31) / 32) * 4;
row = new byte[bytesPerRow]; row = new byte[bytesPerRow];
srcX = 0;
srcY = pHeight - 1;
dstX = srcX;
dstY = srcY;
} }
/** /**
* Decodes one full row of image data. * Decodes one full row of image data.
* *
* @param pStream the input stream containing RLE data * @param stream the input stream containing RLE data
* *
* @throws IOException if an I/O related exception occurs while reading * @throws IOException if an I/O related exception occurs while reading
*/ */
protected abstract void decodeRow(final InputStream pStream) throws IOException; protected abstract void decodeRow(final InputStream stream) throws IOException;
/** /**
* Decodes as much data as possible, from the stream into the buffer. * Decodes as much data as possible, from the stream into the buffer.
@@ -91,31 +86,35 @@ abstract class AbstractRLEDecoder implements Decoder {
* *
* @return the number of bytes decoded from the stream, to the buffer * @return the number of bytes decoded from the stream, to the buffer
* *
* @throws IOException if an I/O related exception ocurs while reading * @throws IOException if an I/O related exception occurs while reading
*/ */
public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining() && dstY >= 0) { // TODO: Allow decoding < row.length at a time and get rid of this assertion...
if (buffer.capacity() < row.length) {
throw new AssertionError("This decoder needs a buffer.capacity() of at least one row");
}
while (buffer.remaining() >= row.length && srcY >= 0) {
// NOTE: Decode only full rows, don't decode if y delta // NOTE: Decode only full rows, don't decode if y delta
if (dstX == 0 && srcY == dstY) { if (dstX == 0 && srcY == dstY) {
decodeRow(stream); decodeRow(stream);
} }
int length = Math.min(row.length - dstX, buffer.remaining()); int length = Math.min(row.length - (dstX * bitsPerSample) / 8, buffer.remaining());
// System.arraycopy(row, dstX, buffer, decoded, length);
buffer.put(row, 0, length); buffer.put(row, 0, length);
dstX += length; dstX += (length * 8) / bitsPerSample;
// decoded += length;
if (dstX == row.length) { if (dstX == (row.length * 8) / bitsPerSample) {
dstX = 0; dstX = 0;
dstY--; dstY++;
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the // NOTE: If src Y is > dst Y, we have a delta, and have to fill the
// gap with zero-bytes // gap with zero-bytes
if (dstY > srcY) { if (srcX > dstX) {
for (int i = 0; i < row.length; i++) { Arrays.fill(row, 0, (srcX * bitsPerSample) / 8, (byte) 0);
row[i] = 0x00; }
} if (srcY > dstY) {
Arrays.fill(row, (byte) 0);
} }
} }
} }
@@ -126,16 +125,16 @@ abstract class AbstractRLEDecoder implements Decoder {
/** /**
* Checks a read byte for EOF marker. * Checks a read byte for EOF marker.
* *
* @param pByte the byte to check * @param val the byte to check
* @return the value of {@code pByte} if positive. * @return the value of {@code val} if positive.
* *
* @throws EOFException if {@code pByte} is negative * @throws EOFException if {@code val} is negative
*/ */
protected static int checkEOF(final int pByte) throws EOFException { protected static int checkEOF(final int val) throws EOFException {
if (pByte < 0) { if (val < 0) {
throw new EOFException("Premature end of file"); throw new EOFException("Premature end of file");
} }
return pByte; return val;
} }
} }
@@ -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;
}
}
@@ -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";
}
}
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.lang.Validate;
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.InverseColorMapIndexColorModel; import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
@@ -107,7 +107,7 @@ class BitmapIndexed extends BitmapDescriptor {
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM // 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... // NOTE: This code assumes icons are small, and is NOT optimized for performance...
if (colors > (1 << getBitCount())) { if (colors > (1 << getBitCount())) {
int index = BitmapIndexed.findTransIndexMaybeRemap(this.colors, this.bits); int index = findTransIndexMaybeRemap(this.colors, this.bits);
if (index == -1) { if (index == -1) {
// No duplicate found, increase bitcount // No duplicate found, increase bitcount
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import java.awt.*; import java.awt.*;
@@ -56,7 +56,7 @@ public final class CURImageReader extends DIBImageReader {
* @param pImageIndex the index of the cursor in the current input. * @param pImageIndex the index of the cursor in the current input.
* @return the hot spot location for the cursor * @return the hot spot location for the cursor
* *
* @throws IOException if an I/O exception occurs during reading of image meta data * @throws java.io.IOException if an I/O exception occurs during reading of image meta data
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to * @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
* the number of cursors in the file * the number of cursors in the file
*/ */
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ProviderInfo; import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IIOUtil;
@@ -60,7 +60,7 @@ public final class CURImageReaderSpi extends ImageReaderSpi {
"image/x-cursor", // Common extension MIME "image/x-cursor", // Common extension MIME
"image/cursor" // Unofficial, but common "image/cursor" // Unofficial, but common
}, },
"com.twelvemonkeys.imageio.plugins.ico.CURImageReader", "com.twelvemonkeys.imageio.plugins.bmp.CURImageReader",
new Class[] {ImageInputStream.class}, new Class[] {ImageInputStream.class},
null, null,
true, null, null, null, null, true, null, null, null, null,
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
/** /**
* DIB * DIB
@@ -40,26 +40,54 @@ package com.twelvemonkeys.imageio.plugins.ico;
*/ */
interface DIB { interface DIB {
int TYPE_UNKNOWN = 0; int TYPE_UNKNOWN = 0;
int TYPE_ICO = 1; int TYPE_ICO = 1;
int TYPE_CUR = 2; int TYPE_CUR = 2;
int BMP_FILE_HEADER_SIZE = 14;
/** BITMAPCOREHEADER size, OS/2 V1 */ /** BITMAPCOREHEADER size, OS/2 V1 */
int OS2_V1_HEADER_SIZE = 12; int BITMAP_CORE_HEADER_SIZE = 12;
/** Strange BITMAPCOREHEADER size, OS/2 V2, but only first 16 bytes... */
int OS2_V2_HEADER_16_SIZE = 16;
/** BITMAPCOREHEADER size, OS/2 V2 */ /** BITMAPCOREHEADER size, OS/2 V2 */
int OS2_V2_HEADER_SIZE = 64; int OS2_V2_HEADER_SIZE = 64;
/** /**
* BITMAPINFOHEADER size, Windows 3.0 and later. * BITMAPINFOHEADER size, Windows 3.0 and later.
* This is the most commonly used header for persistent bitmaps * This is the most commonly used header for persistent bitmaps.
*/ */
int WINDOWS_V3_HEADER_SIZE = 40; int BITMAP_INFO_HEADER_SIZE = 40;
/** BITMAPV4HEADER size, Windows 95/NT4 and later */ int BITMAP_V2_INFO_HEADER_SIZE = 52; // Undocumented, written by Photoshop
int WINDOWS_V4_HEADER_SIZE = 108;
/** BITMAPV5HEADER size, Windows 98/2000 and later */ int BITMAP_V3_INFO_HEADER_SIZE = 56; // Undocumented, written by Photoshop
int WINDOWS_V5_HEADER_SIZE = 124;
/** 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 */ /** PNG "magic" identifier */
long PNG_MAGIC = 0x89l << 56 | (long) 'P' << 48 | (long) 'N' << 40 | (long) 'G' << 32 | 0x0dl << 24 | 0x0al << 16 | 0x1al << 8 | 0x0al; long PNG_MAGIC = 0x89l << 56 | (long) 'P' << 48 | (long) 'N' << 40 | (long) 'G' << 32 | 0x0dl << 24 | 0x0al << 16 | 0x1al << 8 | 0x0al;
@@ -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));
}
}
}
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.ImageUtil; import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.ImageReaderBase;
@@ -290,7 +290,7 @@ abstract class DIBImageReader extends ImageReaderBase {
// Palette style // Palette style
case 1: case 1:
case 4: case 4:
case 8: case 8: // TODO: Gray!
descriptor = new BitmapIndexed(pEntry, header); descriptor = new BitmapIndexed(pEntry, header);
readBitmapIndexed((BitmapIndexed) descriptor); readBitmapIndexed((BitmapIndexed) descriptor);
break; break;
@@ -454,6 +454,7 @@ abstract class DIBImageReader extends ImageReaderBase {
// TODO: No idea if this actually works.. // TODO: No idea if this actually works..
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()]; 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; // Will create TYPE_USHORT_555;
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F); DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
DataBuffer buffer = new DataBufferShort(pixels, pixels.length); DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import java.io.DataInput; import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
@@ -26,12 +26,12 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import java.awt.*;
import java.io.DataInput; import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
import java.awt.*;
/** /**
* DirectoryEntry * DirectoryEntry
@@ -50,7 +50,7 @@ abstract class DirectoryEntry {
private int size; private int size;
private int offset; private int offset;
private DirectoryEntry() { DirectoryEntry() {
} }
public static DirectoryEntry read(final int pType, final DataInput pStream) throws IOException { public static DirectoryEntry read(final int pType, final DataInput pStream) throws IOException {
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ProviderInfo; import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IIOUtil;
@@ -60,7 +60,7 @@ public final class ICOImageReaderSpi extends ImageReaderSpi {
"image/x-icon", // Common extension MIME "image/x-icon", // Common extension MIME
"image/ico" // Unofficial, but common "image/ico" // Unofficial, but common
}, },
"com.twelvemonkeys.imageio.plugins.ico.ICOImageReader", "com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader",
new Class[] {ImageInputStream.class}, new Class[] {ImageInputStream.class},
null, null,
true, null, null, null, null, true, null, null, null, null,
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, Harald Kuhr * Copyright (c) 2014, Harald Kuhr
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -26,32 +26,38 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.imageio.plugins.bmp;
import java.io.InputStream; import java.io.InputStream;
import java.io.IOException; 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. * Implements 4 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/> * <p/>
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE4Decoder.java#1 $ * @version $Id: RLE4Decoder.java#1 $
*/ */
// TODO: Move to other package or make public
final class RLE4Decoder extends AbstractRLEDecoder { final class RLE4Decoder extends AbstractRLEDecoder {
final static int BIT_MASKS[] = {0xf0, 0x0f};
final static int BIT_SHIFTS[] = {4, 0};
public RLE4Decoder(final int pWidth, final int pHeight) { public RLE4Decoder(final int width) {
super((pWidth + 1) / 2, pHeight); super(width, 4);
} }
protected void decodeRow(final InputStream pInput) throws IOException { protected void decodeRow(final InputStream stream) throws IOException {
// Just clear row now, and be done with it...
Arrays.fill(row, (byte) 0);
int deltaX = 0; int deltaX = 0;
int deltaY = 0; int deltaY = 0;
while (srcY >= 0) { while (srcY >= 0) {
int byte1 = pInput.read(); int byte1 = stream.read();
int byte2 = checkEOF(pInput.read()); int byte2 = checkEOF(stream.read());
if (byte1 == 0x00) { if (byte1 == 0x00) {
switch (byte2) { switch (byte2) {
@@ -59,64 +65,65 @@ final class RLE4Decoder extends AbstractRLEDecoder {
// End of line // End of line
// NOTE: Some BMPs have double EOLs.. // NOTE: Some BMPs have double EOLs..
if (srcX != 0) { if (srcX != 0) {
srcX = row.length; srcX = row.length * 2;
} }
break; break;
case 0x01: case 0x01:
// End of bitmap // End of bitmap
srcX = row.length; srcX = row.length * 2;
srcY = 0; srcY = -1;
break; break;
case 0x02: case 0x02:
// Delta // Delta
deltaX = srcX + pInput.read(); deltaX = srcX + stream.read();
deltaY = srcY - checkEOF(pInput.read()); deltaY = srcY + checkEOF(stream.read());
srcX = row.length;
srcX = row.length * 2;
break; break;
default: default:
// Absolute mode // Absolute mode
// Copy the next byte2 (3..255) bytes from file to output // Copy the next byte2 (3..255) nibbles from file to output
// Two samples are packed into one byte // Two samples are packed into one byte
// If the number of bytes used to pack is not a mulitple of 2, // If the *number of bytes* used to pack is not a multiple of 2,
// an additional padding byte is in the stream and must be skipped // an additional padding byte is in the stream and must be skipped
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0; boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
while (byte2 > 1) {
int packed = checkEOF(pInput.read()); int packed = 0;
row[srcX++] = (byte) packed; for (int i = 0; i < byte2; i++) {
byte2 -= 2; if (i % 2 == 0) {
} packed = checkEOF(stream.read());
if (byte2 == 1) { }
// TODO: Half byte alignment? Seems to be ok...
int packed = checkEOF(pInput.read()); row[srcX / 2] |= (byte) (((packed & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2])<< BIT_SHIFTS[srcX % 2]);
row[srcX++] = (byte) (packed & 0xf0); srcX++;
} }
if (paddingByte) { if (paddingByte) {
checkEOF(pInput.read()); checkEOF(stream.read());
} }
break;
} }
} }
else { else {
// Encoded mode // Encoded mode
// Replicate the two samples in byte2 as many times as byte1 says // Replicate the two samples in byte2 as many times as byte1 says
while (byte1 > 1) { for (int i = 0; i < byte1; i++) {
row[srcX++] = (byte) byte2; row[srcX / 2] |= (byte) (((byte2 & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2]) << BIT_SHIFTS[srcX % 2]);
byte1 -= 2; srcX++;
}
if (byte1 == 1) {
// TODO: Half byte alignment? Seems to be ok...
row[srcX++] = (byte) (byte2 & 0xf0);
} }
} }
// If we're done with a complete row, copy the data // If we're done with a complete row, copy the data
if (srcX == row.length) { if (srcX >= row.length * 2) {
// Move to new position, either absolute (delta) or next line // Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) { if (deltaX != 0 || deltaY != 0) {
srcX = (deltaX + 1) / 2; srcX = deltaX;
if (deltaY > srcY) { if (deltaY != srcY) {
srcY = deltaY; srcY = deltaY;
break; break;
} }
@@ -124,9 +131,12 @@ final class RLE4Decoder extends AbstractRLEDecoder {
deltaX = 0; deltaX = 0;
deltaY = 0; deltaY = 0;
} }
else if (srcY == -1) {
break;
}
else { else {
srcX = 0; srcX = 0;
srcY--; srcY++;
break; break;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, Harald Kuhr * Copyright (c) 2014, Harald Kuhr
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -26,32 +26,31 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.imageio.plugins.bmp;
import java.io.InputStream; import java.io.InputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
/** /**
* Implements 8 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format. * Implements 8 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/> * <p/>
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE8Decoder.java#1 $ * @version $Id: RLE8Decoder.java#1 $
*/ */
// TODO: Move to other package or make public
final class RLE8Decoder extends AbstractRLEDecoder { final class RLE8Decoder extends AbstractRLEDecoder {
public RLE8Decoder(final int width) {
public RLE8Decoder(final int pWidth, final int pHeight) { super(width, 8);
super(pWidth, pHeight);
} }
protected void decodeRow(final InputStream pInput) throws IOException { protected void decodeRow(final InputStream stream) throws IOException {
int deltaX = 0; int deltaX = 0;
int deltaY = 0; int deltaY = 0;
while (srcY >= 0) { while (srcY >= 0) {
int byte1 = pInput.read(); int byte1 = stream.read();
int byte2 = checkEOF(pInput.read()); int byte2 = checkEOF(stream.read());
if (byte1 == 0x00) { if (byte1 == 0x00) {
switch (byte2) { switch (byte2) {
@@ -59,29 +58,47 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// End of line // End of line
// NOTE: Some BMPs have double EOLs.. // NOTE: Some BMPs have double EOLs..
if (srcX != 0) { if (srcX != 0) {
Arrays.fill(row, srcX, row.length, (byte) 0);
srcX = row.length; srcX = row.length;
} }
break; break;
case 0x01: case 0x01:
// End of bitmap // End of bitmap
Arrays.fill(row, srcX, row.length, (byte) 0);
srcX = row.length; srcX = row.length;
srcY = 0; srcY = -1; // TODO: Do we need to allow reading more (and thus re-introduce height parameter)..?
break; break;
case 0x02: case 0x02:
// Delta // Delta
deltaX = srcX + pInput.read(); deltaX = srcX + stream.read();
deltaY = srcY - checkEOF(pInput.read()); deltaY = srcY + checkEOF(stream.read());
srcX = row.length;
Arrays.fill(row, srcX, deltaX, (byte) 0);
// TODO: Handle x delta inline!
// if (deltaY != srcY) {
srcX = row.length;
// }
break; break;
default: default:
// Absolute mode // Absolute mode
// Copy the next byte2 (3..255) bytes from file to output // 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; boolean paddingByte = (byte2 % 2) != 0;
while (byte2-- > 0) { while (byte2-- > 0) {
row[srcX++] = (byte) checkEOF(pInput.read()); row[srcX++] = (byte) checkEOF(stream.read());
} }
if (paddingByte) { if (paddingByte) {
checkEOF(pInput.read()); checkEOF(stream.read());
} }
} }
} }
@@ -95,21 +112,25 @@ final class RLE8Decoder extends AbstractRLEDecoder {
} }
// If we're done with a complete row, copy the data // If we're done with a complete row, copy the data
if (srcX == row.length) { if (srcX >= row.length) {
// Move to new position, either absolute (delta) or next line // Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) { if (deltaX != 0 || deltaY != 0) {
srcX = deltaX; srcX = deltaX;
if (deltaY != srcY) { if (deltaY != srcY) {
srcY = deltaY; srcY = deltaY;
break; break;
} }
deltaX = 0; deltaX = 0;
deltaY = 0; deltaY = 0;
} }
else if (srcY == -1) {
break;
}
else { else {
srcX = 0; srcX = 0;
srcY--; srcY++;
break; break;
} }
} }
@@ -0,0 +1,3 @@
com.twelvemonkeys.imageio.plugins.bmp.BMPImageReaderSpi
com.twelvemonkeys.imageio.plugins.bmp.CURImageReaderSpi
com.twelvemonkeys.imageio.plugins.bmp.ICOImageReaderSpi
@@ -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);
}
}
}
@@ -1,4 +1,4 @@
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Ignore; import org.junit.Ignore;
@@ -15,13 +15,13 @@ import java.util.List;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
* CURImageReaderTestCase * CURImageReaderTest
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: CURImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ * @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/ */
public class CURImageReaderTestCase extends ImageReaderAbstractTestCase<CURImageReader> { public class CURImageReaderTest extends ImageReaderAbstractTestCase<CURImageReader> {
protected List<TestData> getTestData() { protected List<TestData> getTestData() {
return Arrays.asList( return Arrays.asList(
new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)), new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)),
@@ -1,4 +1,4 @@
package com.twelvemonkeys.imageio.plugins.ico; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Ignore; import org.junit.Ignore;
@@ -11,13 +11,13 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
* ICOImageReaderTestCase * ICOImageReaderTest
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: ICOImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$ * @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/ */
public class ICOImageReaderTestCase extends ImageReaderAbstractTestCase<ICOImageReader> { public class ICOImageReaderTest extends ImageReaderAbstractTestCase<ICOImageReader> {
protected List<TestData> getTestData() { protected List<TestData> getTestData() {
return Arrays.asList( return Arrays.asList(
new TestData( new TestData(
@@ -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);
}
}
}
@@ -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