TMI-BMP: Support for more versions of BMP format
25
imageio/imageio-bmp/license.txt
Executable 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.
|
24
imageio/imageio-bmp/pom.xml
Normal 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>
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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 {
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
BIN
imageio/imageio-bmp/src/test/resources/bmp/Blue Lace 16.bmp
Executable file
After Width: | Height: | Size: 1.2 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_16.bmp
Executable file
After Width: | Height: | Size: 195 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_16_bitmask444.bmp
Executable file
After Width: | Height: | Size: 195 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_16_bitmask555.bmp
Executable file
After Width: | Height: | Size: 195 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_16_bitmask565.bmp
Executable file
After Width: | Height: | Size: 195 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_24.bmp
Executable file
After Width: | Height: | Size: 292 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_32.bmp
Executable file
After Width: | Height: | Size: 389 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_32_bitmask888.bmp
Executable file
After Width: | Height: | Size: 389 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_32_bitmask888_reversed.bmp
Executable file
After Width: | Height: | Size: 389 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_4-IM.bmp
Executable file
After Width: | Height: | Size: 19 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_4.bmp
Executable file
After Width: | Height: | Size: 49 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_4.rle
Executable file
After Width: | Height: | Size: 20 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_8-IM.bmp
Executable file
After Width: | Height: | Size: 63 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_8.bmp
Executable file
After Width: | Height: | Size: 99 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_8.rle
Executable file
After Width: | Height: | Size: 74 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_gray.bmp
Executable file
After Width: | Height: | Size: 99 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/blauesglas_mono.bmp
Executable file
After Width: | Height: | Size: 13 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmp/lena512.bmp
Normal file
After Width: | Height: | Size: 257 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/baddens1.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/baddens2.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 9.0 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/badplanes.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/badrle.bmp
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/badwidth.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 8.4 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/reallybig.bmp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/rletopdown.bmp
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/b/shortfile.bmp
Normal file
After Width: | Height: | Size: 273 B |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal1.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal1bg.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal1wb.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal4.bmp
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal4rle.bmp
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8-0.bmp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8.bmp
Normal file
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 5.0 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8os2.bmp
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8rle.bmp
Normal file
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 9.0 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8v4.bmp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8v5.bmp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8w124.bmp
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8w125.bmp
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/pal8w126.bmp
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/rgb16-565.bmp
Normal file
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 17 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/rgb16.bmp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/rgb24.bmp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/rgb24pal.bmp
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/rgb32.bmp
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/g/rgb32bf.bmp
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/pal1p1.bmp
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/pal2.bmp
Normal file
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.2 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/pal8offs.bmp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/pal8os2sp.bmp
Normal file
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 9.0 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/pal8os2v2.bmp
Normal file
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 9.0 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/rgb16-231.bmp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/rgb24jpeg.bmp
Normal file
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 25 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/rgb24lprof.bmp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/rgb24png.bmp
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
imageio/imageio-bmp/src/test/resources/bmpsuite/q/rgb24prof.bmp
Normal file
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 16 KiB |