mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
TMI-70: BMP image metadata + bonus listener delegation.
This commit is contained in:
parent
711d2bdc32
commit
98d82fb093
@ -459,6 +459,16 @@ public class XMLSerializer {
|
|||||||
pOut.print(pNode.getTagName());
|
pOut.print(pNode.getTagName());
|
||||||
pOut.println(">");
|
pOut.println(">");
|
||||||
}
|
}
|
||||||
|
else if (pNode.getNodeValue() != null) {
|
||||||
|
// NOTE: This is NOT AS SPECIFIED, but we do this to support
|
||||||
|
// the weirdness that is the javax.imageio.metadata.IIOMetadataNode.
|
||||||
|
// According to the spec, the nodeValue of an Element is null.
|
||||||
|
pOut.print(">");
|
||||||
|
pOut.print(pNode.getNodeValue());
|
||||||
|
pOut.print("</");
|
||||||
|
pOut.print(pNode.getTagName());
|
||||||
|
pOut.println(">");
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
pOut.println("/>");
|
pOut.println("/>");
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,15 @@ import com.twelvemonkeys.imageio.ImageReaderBase;
|
|||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||||
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
|
import javax.imageio.event.IIOReadUpdateListener;
|
||||||
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
@ -45,7 +49,6 @@ import java.awt.*;
|
|||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
@ -58,15 +61,16 @@ import java.util.Iterator;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: CURImageReader.java,v 1.0 Apr 20, 2009 11:54:28 AM haraldk Exp$
|
* @version $Id: CURImageReader.java,v 1.0 Apr 20, 2009 11:54:28 AM haraldk Exp$
|
||||||
*
|
|
||||||
* @see com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader
|
* @see com.twelvemonkeys.imageio.plugins.bmp.ICOImageReader
|
||||||
*/
|
*/
|
||||||
public final class BMPImageReader extends ImageReaderBase {
|
public final class BMPImageReader extends ImageReaderBase {
|
||||||
private long pixelOffset;
|
private long pixelOffset;
|
||||||
private DIBHeader header;
|
private DIBHeader header;
|
||||||
|
private int[] colors;
|
||||||
|
private IndexColorModel colorMap;
|
||||||
|
|
||||||
private transient ImageReader jpegReaderDelegate;
|
private ImageReader jpegReaderDelegate;
|
||||||
private transient ImageReader pngReaderDelegate;
|
private ImageReader pngReaderDelegate;
|
||||||
|
|
||||||
public BMPImageReader() {
|
public BMPImageReader() {
|
||||||
super(new BMPImageReaderSpi());
|
super(new BMPImageReaderSpi());
|
||||||
@ -80,6 +84,8 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
pixelOffset = 0;
|
pixelOffset = 0;
|
||||||
header = null;
|
header = null;
|
||||||
|
colors = null;
|
||||||
|
colorMap = null;
|
||||||
|
|
||||||
if (pngReaderDelegate != null) {
|
if (pngReaderDelegate != null) {
|
||||||
pngReaderDelegate.dispose();
|
pngReaderDelegate.dispose();
|
||||||
@ -121,6 +127,53 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IndexColorModel readColorMap() throws IOException {
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
if (colors == null) {
|
||||||
|
if (header.getBitCount() > 8 && header.colorsUsed == 0) {
|
||||||
|
// RGB without color map
|
||||||
|
colors = new int[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int offset = DIB.BMP_FILE_HEADER_SIZE + header.getSize();
|
||||||
|
if (offset != imageInput.getStreamPosition()) {
|
||||||
|
imageInput.seek(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.getSize() == DIB.BITMAP_CORE_HEADER_SIZE) {
|
||||||
|
colors = new int[Math.min(header.getColorsUsed(), (int) (pixelOffset - DIB.BMP_FILE_HEADER_SIZE - header.getSize()) / 3)];
|
||||||
|
|
||||||
|
// Byte triplets in BGR form
|
||||||
|
for (int i = 0; i < colors.length; i++) {
|
||||||
|
int b = imageInput.readUnsignedByte();
|
||||||
|
int g = imageInput.readUnsignedByte();
|
||||||
|
int r = imageInput.readUnsignedByte();
|
||||||
|
colors[i] = r << 16 | g << 8 | b | 0xff000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
colors = new int[Math.min(header.getColorsUsed(), (int) (pixelOffset - DIB.BMP_FILE_HEADER_SIZE - header.getSize()) / 4)];
|
||||||
|
|
||||||
|
// Byte quadruples in BGRa (or little-endian ints in aRGB) form, where a is "Reserved"
|
||||||
|
for (int i = 0; i < colors.length; i++) {
|
||||||
|
colors[i] = imageInput.readInt() & 0x00ffffff | 0xff000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There might be more entries in the color map, but we ignore these for reading
|
||||||
|
int mapSize = Math.min(colors.length, 1 << header.getBitCount());
|
||||||
|
|
||||||
|
// Compute bits for > 8 bits (used only for meta data)
|
||||||
|
int bits = header.getBitCount() <= 8 ? header.getBitCount() : mapSize <= 256 ? 8 : 16;
|
||||||
|
|
||||||
|
colorMap = new IndexColorModel(bits, mapSize, colors, 0, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorMap;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidth(int pImageIndex) throws IOException {
|
public int getWidth(int pImageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(pImageIndex);
|
||||||
@ -143,40 +196,6 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
return Arrays.asList(getRawImageType(pImageIndex)).iterator();
|
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
|
@Override
|
||||||
public ImageTypeSpecifier getRawImageType(int pImageIndex) throws IOException {
|
public ImageTypeSpecifier getRawImageType(int pImageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(pImageIndex);
|
||||||
@ -190,22 +209,17 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
case 2:
|
case 2:
|
||||||
case 4:
|
case 4:
|
||||||
case 8:
|
case 8:
|
||||||
// TODO: Get rid of the fake DirectoryEntry and support color maps directly
|
return IndexedImageTypeSpecifier.createFromIndexColorModel(readColorMap());
|
||||||
BitmapIndexed indexed = new BitmapIndexed(new DirectoryEntry() {}, header);
|
|
||||||
readColorMap(indexed);
|
|
||||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(indexed.createColorModel());
|
|
||||||
|
|
||||||
case 16:
|
case 16:
|
||||||
if (header.hasMasks()) {
|
if (header.hasMasks()) {
|
||||||
int[] masks = getMasks();
|
int[] masks = getMasks();
|
||||||
|
|
||||||
return ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
return ImageTypeSpecifier.createPacked(
|
||||||
masks[0],
|
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||||
masks[1],
|
masks[0], masks[1], masks[2], masks[3],
|
||||||
masks[2],
|
DataBuffer.TYPE_USHORT, false
|
||||||
masks[3],
|
);
|
||||||
DataBuffer.TYPE_USHORT,
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default if no mask is 555
|
// Default if no mask is 555
|
||||||
@ -222,13 +236,11 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
if (header.hasMasks()) {
|
if (header.hasMasks()) {
|
||||||
int[] masks = getMasks();
|
int[] masks = getMasks();
|
||||||
|
|
||||||
return ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
return ImageTypeSpecifier.createPacked(
|
||||||
masks[0],
|
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||||
masks[1],
|
masks[0], masks[1], masks[2], masks[3],
|
||||||
masks[2],
|
DataBuffer.TYPE_INT, false
|
||||||
masks[3],
|
);
|
||||||
DataBuffer.TYPE_INT,
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default if no mask
|
// Default if no mask
|
||||||
@ -290,10 +302,18 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
|
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
|
||||||
|
|
||||||
|
ColorModel colorModel = destination.getColorModel();
|
||||||
|
if (colorModel instanceof IndexColorModel && ((IndexColorModel) colorModel).getMapSize() < header.getColorsUsed()) {
|
||||||
|
processWarningOccurred(
|
||||||
|
String.format("Color map contains more colors than raster allows (%d). Ignoring entries above %d.",
|
||||||
|
header.getColorsUsed(), ((IndexColorModel) colorModel).getMapSize())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// BMP rows are padded to 4 byte boundary
|
// BMP rows are padded to 4 byte boundary
|
||||||
int rowSizeBytes = ((header.getBitCount() * width + 31) / 32) * 4;
|
int rowSizeBytes = ((header.getBitCount() * width + 31) / 32) * 4;
|
||||||
|
|
||||||
// Wrap
|
// Wrap input according to compression
|
||||||
imageInput.seek(pixelOffset);
|
imageInput.seek(pixelOffset);
|
||||||
DataInput input;
|
DataInput input;
|
||||||
|
|
||||||
@ -369,17 +389,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
case 8:
|
case 8:
|
||||||
case 24:
|
case 24:
|
||||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
try {
|
|
||||||
readRowByte(input, height, srcRegion, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
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;
|
break;
|
||||||
|
|
||||||
case 16:
|
case 16:
|
||||||
@ -414,9 +424,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readUsingDelegate(final int compression, final ImageReadParam param) throws IOException {
|
private BufferedImage readUsingDelegate(final int compression, final ImageReadParam param) throws IOException {
|
||||||
ImageReader reader = initReaderDelegate(compression);
|
return initReaderDelegate(compression).read(0, param);
|
||||||
|
|
||||||
return reader.read(0, param);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageReader initReaderDelegate(int compression) throws IOException {
|
private ImageReader initReaderDelegate(int compression) throws IOException {
|
||||||
@ -454,12 +462,19 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// Consider looking for specific PNG and JPEG implementations.
|
// Consider looking for specific PNG and JPEG implementations.
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format);
|
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format);
|
||||||
|
|
||||||
if (!readers.hasNext()) {
|
if (!readers.hasNext()) {
|
||||||
throw new IIOException(String.format("Delegate ImageReader for %s format not found", format));
|
throw new IIOException(String.format("Delegate ImageReader for %s format not found", format));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageReader reader = readers.next();
|
ImageReader reader = readers.next();
|
||||||
|
|
||||||
|
// Install listener
|
||||||
|
ListenerDelegator listenerDelegator = new ListenerDelegator();
|
||||||
|
reader.addIIOReadWarningListener(listenerDelegator);
|
||||||
|
reader.addIIOReadProgressListener(listenerDelegator);
|
||||||
|
reader.addIIOReadUpdateListener(listenerDelegator);
|
||||||
|
|
||||||
// Cache for later use
|
// Cache for later use
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case DIB.COMPRESSION_JPEG:
|
case DIB.COMPRESSION_JPEG:
|
||||||
@ -503,8 +518,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (header.topDown) {
|
if (header.topDown) {
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
destChannel.setDataElements(0, y, srcChannel);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Flip into position
|
// Flip into position
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
@ -536,8 +550,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (header.topDown) {
|
if (header.topDown) {
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
destChannel.setDataElements(0, y, srcChannel);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Flip into position
|
// Flip into position
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
@ -545,7 +558,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
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 {
|
final int[] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
// If subsampled or outside source region, skip entire row
|
// 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) {
|
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataInt.length * 4);
|
input.skipBytes(rowDataInt.length * 4);
|
||||||
@ -564,8 +577,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (header.topDown) {
|
if (header.topDown) {
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
destChannel.setDataElements(0, y, srcChannel);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Flip into position
|
// Flip into position
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
@ -577,8 +589,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
if (input instanceof ImageInputStream) {
|
if (input instanceof ImageInputStream) {
|
||||||
// Optimization for ImageInputStreams, read all in one go
|
// Optimization for ImageInputStreams, read all in one go
|
||||||
((ImageInputStream) input).readFully(shorts, 0, shorts.length);
|
((ImageInputStream) input).readFully(shorts, 0, shorts.length);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
for (int i = 0; i < shorts.length; i++) {
|
for (int i = 0; i < shorts.length; i++) {
|
||||||
shorts[i] = input.readShort();
|
shorts[i] = input.readShort();
|
||||||
}
|
}
|
||||||
@ -590,8 +601,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
if (input instanceof ImageInputStream) {
|
if (input instanceof ImageInputStream) {
|
||||||
// Optimization for ImageInputStreams, read all in one go
|
// Optimization for ImageInputStreams, read all in one go
|
||||||
((ImageInputStream) input).readFully(ints, 0, ints.length);
|
((ImageInputStream) input).readFully(ints, 0, ints.length);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
for (int i = 0; i < ints.length; i++) {
|
for (int i = 0; i < ints.length; i++) {
|
||||||
ints[i] = input.readInt();
|
ints[i] = input.readInt();
|
||||||
}
|
}
|
||||||
@ -617,6 +627,31 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
return raster.createWritableChild(rect.x, rect.y, rect.width, rect.height, 0, 0, bands);
|
return raster.createWritableChild(rect.x, rect.y, rect.width, rect.height, 0, 0, bands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
switch (header.getBitCount()) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
readColorMap();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (header.colorsUsed > 0) {
|
||||||
|
readColorMap();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Why, oh why..? Instead of accepting it's own native format as it should,
|
||||||
|
// The BMPImageWriter only accepts instances of com.sun.imageio.plugins.bmp.BMPMetadata...
|
||||||
|
// TODO: Consider reflectively construct a BMPMetadata and inject fields
|
||||||
|
return new BMPMetadata(header, colors);
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
BMPImageReaderSpi provider = new BMPImageReaderSpi();
|
BMPImageReaderSpi provider = new BMPImageReaderSpi();
|
||||||
BMPImageReader reader = new BMPImageReader(provider);
|
BMPImageReader reader = new BMPImageReader(provider);
|
||||||
@ -656,14 +691,12 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
if (imageMetadata != null) {
|
if (imageMetadata != null) {
|
||||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||||
}
|
}
|
||||||
}
|
} catch (Throwable t) {
|
||||||
catch (Throwable t) {
|
|
||||||
if (args.length > 1) {
|
if (args.length > 1) {
|
||||||
System.err.println("---");
|
System.err.println("---");
|
||||||
System.err.println("---> " + t.getClass().getSimpleName() + ": " + t.getMessage() + " for " + arg);
|
System.err.println("---> " + t.getClass().getSimpleName() + ": " + t.getMessage() + " for " + arg);
|
||||||
System.err.println("---");
|
System.err.println("---");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
throwAs(RuntimeException.class, t);
|
throwAs(RuntimeException.class, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -674,4 +707,79 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
|
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
|
||||||
throw (T) pThrowable;
|
throw (T) pThrowable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ListenerDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
|
||||||
|
@Override
|
||||||
|
public void imageComplete(ImageReader source) {
|
||||||
|
processImageComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void imageProgress(ImageReader source, float percentageDone) {
|
||||||
|
processImageProgress(percentageDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void imageStarted(ImageReader source, int imageIndex) {
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readAborted(ImageReader source) {
|
||||||
|
processReadAborted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sequenceComplete(ImageReader source) {
|
||||||
|
processSequenceComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sequenceStarted(ImageReader source, int minIndex) {
|
||||||
|
processSequenceStarted(minIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void thumbnailComplete(ImageReader source) {
|
||||||
|
processThumbnailComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void thumbnailProgress(ImageReader source, float percentageDone) {
|
||||||
|
processThumbnailProgress(percentageDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
|
||||||
|
processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void passStarted(ImageReader source, BufferedImage theImage, int pass, int minPass, int maxPass, int minX, int minY, int periodX, int periodY, int[] bands) {
|
||||||
|
processPassStarted(theImage, pass, minPass, maxPass, minX, minY, periodX, periodY, bands);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void imageUpdate(ImageReader source, BufferedImage theImage, int minX, int minY, int width, int height, int periodX, int periodY, int[] bands) {
|
||||||
|
processImageUpdate(theImage, minX, minY, width, height, periodX, periodY, bands);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void passComplete(ImageReader source, BufferedImage theImage) {
|
||||||
|
processPassComplete(theImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void thumbnailPassStarted(ImageReader source, BufferedImage theThumbnail, int pass, int minPass, int maxPass, int minX, int minY, int periodX, int periodY, int[] bands) {
|
||||||
|
processThumbnailPassStarted(theThumbnail, pass, minPass, maxPass, minX, minY, periodX, periodY, bands);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void thumbnailUpdate(ImageReader source, BufferedImage theThumbnail, int minX, int minY, int width, int height, int periodX, int periodY, int[] bands) {
|
||||||
|
processThumbnailUpdate(theThumbnail, minX, minY, width, height, periodX, periodY, bands);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void thumbnailPassComplete(ImageReader source, BufferedImage theThumbnail) {
|
||||||
|
processThumbnailPassComplete(theThumbnail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warningOccurred(ImageReader source, String warning) {
|
||||||
|
processWarningOccurred(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,10 +65,10 @@ public final class BMPImageReaderSpi extends ImageReaderSpi {
|
|||||||
},
|
},
|
||||||
"com.twelvemonkeys.imageio.plugins.bmp.BMPImageReader",
|
"com.twelvemonkeys.imageio.plugins.bmp.BMPImageReader",
|
||||||
new Class[]{ImageInputStream.class},
|
new Class[]{ImageInputStream.class},
|
||||||
null,
|
new String[]{"com.sun.imageio.plugins.bmp.BMPImageWriterSpi"}, // We support the same native metadata format
|
||||||
true, null, null, null, null,
|
false, null, null, null, null,
|
||||||
true,
|
true,
|
||||||
null, null,
|
BMPMetadata.nativeMetadataFormatName, "com.sun.imageio.plugins.bmp.BMPMetadataFormat",
|
||||||
null, null
|
null, null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,342 @@
|
|||||||
|
/*
|
||||||
|
* 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.lang.Validate;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import java.awt.image.IndexColorModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BMPMetadata.
|
||||||
|
*/
|
||||||
|
final class BMPMetadata extends IIOMetadata {
|
||||||
|
/** We return metadata in the exact same form as the JRE built-in, to be compatible with the BMPImageWriter. */
|
||||||
|
public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0";
|
||||||
|
|
||||||
|
private final DIBHeader header;
|
||||||
|
private final int[] colorMap;
|
||||||
|
|
||||||
|
BMPMetadata(final DIBHeader header, final int[] colorMap) {
|
||||||
|
this.header = Validate.notNull(header, "header");
|
||||||
|
this.colorMap = colorMap == null || colorMap.length == 0 ? null : colorMap;
|
||||||
|
|
||||||
|
standardFormatSupported = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean isReadOnly() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Node getAsTree(final String formatName) {
|
||||||
|
if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||||
|
return getStandardTree();
|
||||||
|
}
|
||||||
|
else if (formatName.equals(nativeMetadataFormatName)) {
|
||||||
|
return getNativeTree();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void mergeTree(final String formatName, final Node root) {
|
||||||
|
if (isReadOnly()) {
|
||||||
|
throw new IllegalStateException("Metadata is read-only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void reset() {
|
||||||
|
if (isReadOnly()) {
|
||||||
|
throw new IllegalStateException("Metadata is read-only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNativeMetadataFormatName() {
|
||||||
|
return nativeMetadataFormatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node getNativeTree() {
|
||||||
|
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
|
||||||
|
|
||||||
|
addChildNode(root, "BMPVersion", header.getBMPVersion());
|
||||||
|
addChildNode(root, "Width", header.getWidth());
|
||||||
|
addChildNode(root, "Height", header.getHeight());
|
||||||
|
addChildNode(root, "BitsPerPixel", (short) header.getBitCount());
|
||||||
|
addChildNode(root, "Compression", header.getCompression());
|
||||||
|
addChildNode(root, "ImageSize", header.getImageSize());
|
||||||
|
|
||||||
|
IIOMetadataNode pixelsPerMeter = addChildNode(root, "PixelsPerMeter", null);
|
||||||
|
addChildNode(pixelsPerMeter, "X", header.xPixelsPerMeter);
|
||||||
|
addChildNode(pixelsPerMeter, "Y", header.yPixelsPerMeter);
|
||||||
|
|
||||||
|
addChildNode(root, "ColorsUsed", header.colorsUsed);
|
||||||
|
addChildNode(root, "ColorsImportant", header.colorsImportant);
|
||||||
|
|
||||||
|
if (header.getSize() == DIB.BITMAP_V4_INFO_HEADER_SIZE || header.getSize() == DIB.BITMAP_V5_INFO_HEADER_SIZE) {
|
||||||
|
IIOMetadataNode mask = addChildNode(root, "Mask", null);
|
||||||
|
addChildNode(mask, "Red", header.masks[0]);
|
||||||
|
addChildNode(mask, "Green", header.masks[1]);
|
||||||
|
addChildNode(mask, "Blue", header.masks[2]);
|
||||||
|
addChildNode(mask, "Alpha", header.masks[3]);
|
||||||
|
|
||||||
|
addChildNode(root, "ColorSpaceType", header.colorSpaceType);
|
||||||
|
|
||||||
|
// It makes no sense to include these if colorSpaceType != 0, but native format does it...
|
||||||
|
IIOMetadataNode cieXYZEndPoints = addChildNode(root, "CIEXYZEndPoints", null);
|
||||||
|
addXYZPoints(cieXYZEndPoints, "Red", header.cieXYZEndpoints[0], header.cieXYZEndpoints[1], header.cieXYZEndpoints[2]);
|
||||||
|
addXYZPoints(cieXYZEndPoints, "Green", header.cieXYZEndpoints[3], header.cieXYZEndpoints[4], header.cieXYZEndpoints[5]);
|
||||||
|
addXYZPoints(cieXYZEndPoints, "Blue", header.cieXYZEndpoints[6], header.cieXYZEndpoints[7], header.cieXYZEndpoints[8]);
|
||||||
|
|
||||||
|
// TODO: Gamma?! Will need a new native format version...
|
||||||
|
|
||||||
|
addChildNode(root, "Intent", header.intent);
|
||||||
|
|
||||||
|
// TODO: Profile data & profile size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Palette
|
||||||
|
if (colorMap != null) {
|
||||||
|
IIOMetadataNode paletteNode = addChildNode(root, "Palette", null);
|
||||||
|
|
||||||
|
// The original BitmapCoreHeader has only RGB values in the palette, all others have RGBA
|
||||||
|
boolean hasAlpha = header.getSize() != DIB.BITMAP_CORE_HEADER_SIZE;
|
||||||
|
|
||||||
|
for (int color : colorMap) {
|
||||||
|
// NOTE: The native format has the red and blue values mixed up, we'll report the correct values
|
||||||
|
IIOMetadataNode paletteEntry = addChildNode(paletteNode, "PaletteEntry", null);
|
||||||
|
addChildNode(paletteEntry, "Red", (byte) ((color >> 16) & 0xff));
|
||||||
|
addChildNode(paletteEntry, "Green", (byte) ((color >> 8) & 0xff));
|
||||||
|
addChildNode(paletteEntry, "Blue", (byte) (color & 0xff));
|
||||||
|
|
||||||
|
// Not sure why the native format specifies this, as no palette-based BMP has alpha
|
||||||
|
if (hasAlpha) {
|
||||||
|
addChildNode(paletteEntry, "Alpha", (byte) ((color >>> 24) & 0xff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addXYZPoints(IIOMetadataNode cieXYZNode, String color, double colorX, double colorY, double colorZ) {
|
||||||
|
IIOMetadataNode colorNode = addChildNode(cieXYZNode, color, null);
|
||||||
|
addChildNode(colorNode, "X", colorX);
|
||||||
|
addChildNode(colorNode, "Y", colorY);
|
||||||
|
addChildNode(colorNode, "Z", colorZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IIOMetadataNode addChildNode(final IIOMetadataNode parent,
|
||||||
|
final String name,
|
||||||
|
final Object object) {
|
||||||
|
IIOMetadataNode child = new IIOMetadataNode(name);
|
||||||
|
|
||||||
|
if (object != null) {
|
||||||
|
child.setUserObject(object); // TODO: Should we always store user object?!?!
|
||||||
|
child.setNodeValue(object.toString()); // TODO: Fix this line
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.appendChild(child);
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||||
|
// NOTE: BMP files may contain a color map, even if true color...
|
||||||
|
// Not sure if this is a good idea to expose to the meta data,
|
||||||
|
// as it might be unexpected... Then again...
|
||||||
|
if (colorMap != null) {
|
||||||
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
|
IIOMetadataNode palette = new IIOMetadataNode("Palette");
|
||||||
|
chroma.appendChild(palette);
|
||||||
|
|
||||||
|
for (int i = 0; i < colorMap.length; i++) {
|
||||||
|
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
||||||
|
paletteEntry.setAttribute("index", Integer.toString(i));
|
||||||
|
|
||||||
|
paletteEntry.setAttribute("red", Integer.toString((colorMap[i] >> 16) & 0xff));
|
||||||
|
paletteEntry.setAttribute("green", Integer.toString((colorMap[i] >> 8) & 0xff));
|
||||||
|
paletteEntry.setAttribute("blue", Integer.toString(colorMap[i] & 0xff));
|
||||||
|
|
||||||
|
palette.appendChild(paletteEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chroma;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected IIOMetadataNode getStandardCompressionNode() {
|
||||||
|
IIOMetadataNode compression = new IIOMetadataNode("Compression");
|
||||||
|
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
|
||||||
|
compressionTypeName.setAttribute("value", "NONE");
|
||||||
|
|
||||||
|
return compression;
|
||||||
|
// switch (header.getImageType()) {
|
||||||
|
// case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||||
|
// case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||||
|
// case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||||
|
// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||||
|
// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||||
|
// IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||||
|
// IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||||
|
//
|
||||||
|
// // Compression can be RLE4, RLE8, PNG, JPEG or NONE
|
||||||
|
// String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
||||||
|
// ? "Uknown" : "RLE";
|
||||||
|
// compressionTypeName.setAttribute("value", value);
|
||||||
|
// node.appendChild(compressionTypeName);
|
||||||
|
//
|
||||||
|
// IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
|
// lossless.setAttribute("value", "TRUE"); // TODO: Unless JPEG!
|
||||||
|
// node.appendChild(lossless);
|
||||||
|
//
|
||||||
|
// return node;
|
||||||
|
// default:
|
||||||
|
// // No compression
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||||
|
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||||
|
|
||||||
|
// IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||||
|
// planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||||
|
// node.appendChild(planarConfiguration);
|
||||||
|
|
||||||
|
// IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||||
|
// switch (header.getImageType()) {
|
||||||
|
// case TGA.IMAGETYPE_COLORMAPPED:
|
||||||
|
// case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||||
|
// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||||
|
// case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||||
|
// sampleFormat.setAttribute("value", "Index");
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// node.appendChild(sampleFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||||
|
switch (header.getBitCount()) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getBitCount())));
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
// TODO: Consult masks here!
|
||||||
|
bitsPerSample.setAttribute("value", createListValue(4, Integer.toString(4)));
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8)));
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
bitsPerSample.setAttribute("value", createListValue(4, Integer.toString(8)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.appendChild(bitsPerSample);
|
||||||
|
|
||||||
|
// TODO: Do we need MSB?
|
||||||
|
// IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||||
|
// sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createListValue(final int itemCount, final String... values) {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
if (buffer.length() > 0) {
|
||||||
|
buffer.append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append(values[i % values.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||||
|
if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) {
|
||||||
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
|
addChildNode(dimension, "PixelAspectRatio", null);
|
||||||
|
addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null);
|
||||||
|
addChildNode(dimension, "VerticalPhysicalPixelSpacing", null);
|
||||||
|
|
||||||
|
// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||||
|
//
|
||||||
|
// if (header.topDown) {
|
||||||
|
// imageOrientation.setAttribute("value", "FlipH");
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// imageOrientation.setAttribute("value", "Normal");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// dimension.appendChild(imageOrientation);
|
||||||
|
|
||||||
|
return dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No document node
|
||||||
|
|
||||||
|
// No text node
|
||||||
|
|
||||||
|
// No tiling
|
||||||
|
|
||||||
|
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||||
|
//
|
||||||
|
// IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||||
|
//
|
||||||
|
// // TODO: Consult masks
|
||||||
|
// alpha.setAttribute("value", header.getBitCount() == 32 ? "nonpremultiplied" : "none");
|
||||||
|
// transparency.appendChild(alpha);
|
||||||
|
//
|
||||||
|
// return transparency;
|
||||||
|
}
|
||||||
|
}
|
@ -39,8 +39,6 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)">ICO file format (Wikipedia)</a>
|
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)">ICO file format (Wikipedia)</a>
|
||||||
*/
|
*/
|
||||||
interface DIB {
|
interface DIB {
|
||||||
int TYPE_UNKNOWN = 0;
|
|
||||||
|
|
||||||
int TYPE_ICO = 1;
|
int TYPE_ICO = 1;
|
||||||
int TYPE_CUR = 2;
|
int TYPE_CUR = 2;
|
||||||
|
|
||||||
@ -89,6 +87,13 @@ interface DIB {
|
|||||||
// int COMPRESSION_CMYK_RLE8 = 12;
|
// int COMPRESSION_CMYK_RLE8 = 12;
|
||||||
// int COMPRESSION_CMYK_RLE5 = 13;
|
// int COMPRESSION_CMYK_RLE5 = 13;
|
||||||
|
|
||||||
|
/* Color space types. */
|
||||||
|
int LCS_CALIBRATED_RGB = 0;
|
||||||
|
int LCS_sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B'; // 0x73524742
|
||||||
|
int LCS_WINDOWS_COLOR_SPACE = 'W' << 24 | 'i' << 16 | 'n' << 8 | ' '; // 0x57696e20
|
||||||
|
int PROFILE_LINKED = 'L' << 24 | 'I' << 16 | 'N' << 8 | 'K'; // 0x4c494e4b
|
||||||
|
int PROFILE_EMBEDDED = 'M' << 24 | 'B' << 16 | 'E' << 8 | 'D'; // 0x4d424544
|
||||||
|
|
||||||
/** PNG "magic" identifier */
|
/** PNG "magic" identifier */
|
||||||
long PNG_MAGIC = 0x89l << 56 | (long) 'P' << 48 | (long) 'N' << 40 | (long) 'G' << 32 | 0x0dl << 24 | 0x0al << 16 | 0x1al << 8 | 0x0al;
|
long PNG_MAGIC = 0x89l << 56 | (long) 'P' << 48 | (long) 'N' << 40 | (long) 'G' << 32 | 0x0dl << 24 | 0x0al << 16 | 0x1al << 8 | 0x0al;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,9 @@ import java.io.IOException;
|
|||||||
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
|
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
|
||||||
*/
|
*/
|
||||||
abstract class DIBHeader {
|
abstract class DIBHeader {
|
||||||
|
// Roughly 72 DPI
|
||||||
|
private final int DEFAULT_PIXELS_PER_METER = 2835;
|
||||||
|
|
||||||
protected int size;
|
protected int size;
|
||||||
protected int width;
|
protected int width;
|
||||||
// NOTE: If a bitmask is present, this value includes the height of the mask
|
// NOTE: If a bitmask is present, this value includes the height of the mask
|
||||||
@ -70,7 +73,16 @@ abstract class DIBHeader {
|
|||||||
// 0 means all colors are important
|
// 0 means all colors are important
|
||||||
protected int colorsImportant;
|
protected int colorsImportant;
|
||||||
|
|
||||||
|
// V4+ members below
|
||||||
protected int[] masks;
|
protected int[] masks;
|
||||||
|
protected int colorSpaceType;
|
||||||
|
protected double[] cieXYZEndpoints;
|
||||||
|
protected int[] gamma;
|
||||||
|
|
||||||
|
// V5+ members below
|
||||||
|
protected int intent;
|
||||||
|
protected long profileData;
|
||||||
|
protected long profileSize;
|
||||||
|
|
||||||
protected DIBHeader() {
|
protected DIBHeader() {
|
||||||
}
|
}
|
||||||
@ -139,19 +151,19 @@ abstract class DIBHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getXPixelsPerMeter() {
|
public int getXPixelsPerMeter() {
|
||||||
return xPixelsPerMeter;
|
return xPixelsPerMeter != 0 ? xPixelsPerMeter : DEFAULT_PIXELS_PER_METER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getYPixelsPerMeter() {
|
public int getYPixelsPerMeter() {
|
||||||
return yPixelsPerMeter;
|
return yPixelsPerMeter != 0 ? yPixelsPerMeter : DEFAULT_PIXELS_PER_METER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getColorsUsed() {
|
public int getColorsUsed() {
|
||||||
return colorsUsed;
|
return colorsUsed != 0 ? colorsUsed : 1 << bitCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getColorsImportant() {
|
public int getColorsImportant() {
|
||||||
return colorsImportant;
|
return colorsImportant != 0 ? colorsImportant : getColorsUsed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasMasks() {
|
public boolean hasMasks() {
|
||||||
@ -167,10 +179,11 @@ abstract class DIBHeader {
|
|||||||
"colors used: %d%s, colors important: %d%s",
|
"colors used: %d%s, colors important: %d%s",
|
||||||
getClass().getSimpleName(),
|
getClass().getSimpleName(),
|
||||||
getSize(), getWidth(), getHeight(), getPlanes(), getBitCount(), getCompression(),
|
getSize(), getWidth(), getHeight(), getPlanes(), getBitCount(), getCompression(),
|
||||||
getImageSize(), (getImageSize() == 0 ? " (unknown)" : ""),
|
getImageSize(), (imageSize == 0 ? " (calculated)" : ""),
|
||||||
getXPixelsPerMeter(), getYPixelsPerMeter(),
|
getXPixelsPerMeter(),
|
||||||
getColorsUsed(), (getColorsUsed() == 0 ? " (unknown)" : ""),
|
getYPixelsPerMeter(),
|
||||||
getColorsImportant(), (getColorsImportant() == 0 ? " (all)" : "")
|
getColorsUsed(), (colorsUsed == 0 ? " (unknown)" : ""),
|
||||||
|
getColorsImportant(), (colorsImportant == 0 ? " (all)" : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,6 +196,8 @@ abstract class DIBHeader {
|
|||||||
return masks;
|
return masks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract String getBMPVersion();
|
||||||
|
|
||||||
// TODO: Get rid of code duplication below...
|
// TODO: Get rid of code duplication below...
|
||||||
|
|
||||||
static final class BitmapCoreHeader extends DIBHeader {
|
static final class BitmapCoreHeader extends DIBHeader {
|
||||||
@ -204,10 +219,10 @@ abstract class DIBHeader {
|
|||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = pStream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = pStream.readUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
// Roughly 72 DPI
|
public String getBMPVersion() {
|
||||||
xPixelsPerMeter = 2835;
|
return "BMP v. 2.x";
|
||||||
yPixelsPerMeter = 2835;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,12 +254,7 @@ abstract class DIBHeader {
|
|||||||
planes = pStream.readUnsignedShort();
|
planes = pStream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = pStream.readUnsignedShort();
|
||||||
|
|
||||||
if (pSize == DIB.OS2_V2_HEADER_16_SIZE) {
|
if (pSize != DIB.OS2_V2_HEADER_16_SIZE) {
|
||||||
// Roughly 72 DPI
|
|
||||||
xPixelsPerMeter = 2835;
|
|
||||||
yPixelsPerMeter = 2835;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
compression = pStream.readInt();
|
compression = pStream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = pStream.readInt();
|
||||||
@ -256,6 +266,7 @@ abstract class DIBHeader {
|
|||||||
colorsImportant = pStream.readInt();
|
colorsImportant = pStream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use? These fields are not reflected in metadata as per now...
|
||||||
int units = pStream.readShort();
|
int units = pStream.readShort();
|
||||||
int reserved = pStream.readShort();
|
int reserved = pStream.readShort();
|
||||||
int recording = pStream.readShort(); // Recording algorithm
|
int recording = pStream.readShort(); // Recording algorithm
|
||||||
@ -265,6 +276,10 @@ abstract class DIBHeader {
|
|||||||
int colorEncoding = pStream.readInt(); // Color model used in bitmap
|
int colorEncoding = pStream.readInt(); // Color model used in bitmap
|
||||||
int identifier = pStream.readInt(); // Reserved for application use
|
int identifier = pStream.readInt(); // Reserved for application use
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBMPVersion() {
|
||||||
|
return "BMP v. 2.2";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -306,6 +321,11 @@ abstract class DIBHeader {
|
|||||||
colorsUsed = pStream.readInt();
|
colorsUsed = pStream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = pStream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBMPVersion() {
|
||||||
|
// This is to be compatible with the native metadata of the original com.sun....BMPMetadata
|
||||||
|
return compression == DIB.COMPRESSION_BITFIELDS ? "BMP v. 3.x NT" : "BMP v. 3.x";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,6 +362,10 @@ abstract class DIBHeader {
|
|||||||
|
|
||||||
masks = readMasks(pStream);
|
masks = readMasks(pStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBMPVersion() {
|
||||||
|
return "BMP v. 3.x Photoshop";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -377,10 +401,22 @@ abstract class DIBHeader {
|
|||||||
|
|
||||||
masks = readMasks(pStream);
|
masks = readMasks(pStream);
|
||||||
|
|
||||||
byte[] data = new byte[52];
|
colorSpaceType = pStream.readInt(); // Should be 0 for V4
|
||||||
pStream.readFully(data);
|
cieXYZEndpoints = new double[9];
|
||||||
|
|
||||||
// System.out.println("data = " + Arrays.toString(data));
|
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
||||||
|
cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
|
||||||
|
}
|
||||||
|
|
||||||
|
gamma = new int[3];
|
||||||
|
|
||||||
|
for (int i = 0; i < gamma.length; i++) {
|
||||||
|
gamma[i] = pStream.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBMPVersion() {
|
||||||
|
return "BMP v. 4.x";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,10 +453,28 @@ abstract class DIBHeader {
|
|||||||
|
|
||||||
masks = readMasks(pStream);
|
masks = readMasks(pStream);
|
||||||
|
|
||||||
byte[] data = new byte[68];
|
colorSpaceType = pStream.readInt();
|
||||||
pStream.readFully(data);
|
|
||||||
|
|
||||||
// System.out.println("data = " + Arrays.toString(data));
|
cieXYZEndpoints = new double[9];
|
||||||
|
|
||||||
|
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
||||||
|
cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
|
||||||
|
}
|
||||||
|
|
||||||
|
gamma = new int[3];
|
||||||
|
|
||||||
|
for (int i = 0; i < gamma.length; i++) {
|
||||||
|
gamma[i] = pStream.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
intent = pStream.readInt(); // TODO: Verify if this is same as ICC intent
|
||||||
|
profileData = pStream.readInt() & 0xffffffffL;
|
||||||
|
profileSize = pStream.readInt() & 0xffffffffL;
|
||||||
|
pStream.readInt(); // Reserved
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBMPVersion() {
|
||||||
|
return "BMP v. 5.x";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,19 +2,30 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.imageio.ImageReader;
|
import javax.imageio.ImageReader;
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.event.IIOReadProgressListener;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.mockito.Matchers.anyInt;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BMPImageReaderTest
|
* BMPImageReaderTest
|
||||||
@ -160,4 +171,189 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageRead
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddIIOReadProgressListenerCallbacksJPEG() {
|
||||||
|
ImageReader reader = createReader();
|
||||||
|
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24jpeg.bmp"), new Dimension(127, 64));
|
||||||
|
reader.setInput(data.getInputStream());
|
||||||
|
|
||||||
|
IIOReadProgressListener listener = mock(IIOReadProgressListener.class);
|
||||||
|
reader.addIIOReadProgressListener(listener);
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.read(0);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("Could not read image");
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
|
InOrder ordered = inOrder(listener);
|
||||||
|
ordered.verify(listener).imageStarted(reader, 0);
|
||||||
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
||||||
|
ordered.verify(listener).imageComplete(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddIIOReadProgressListenerCallbacksPNG() {
|
||||||
|
ImageReader reader = createReader();
|
||||||
|
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24png.bmp"), new Dimension(127, 64));
|
||||||
|
reader.setInput(data.getInputStream());
|
||||||
|
|
||||||
|
IIOReadProgressListener listener = mock(IIOReadProgressListener.class);
|
||||||
|
reader.addIIOReadProgressListener(listener);
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.read(0);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("Could not read image");
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
|
InOrder ordered = inOrder(listener);
|
||||||
|
ordered.verify(listener).imageStarted(reader, 0);
|
||||||
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
||||||
|
ordered.verify(listener).imageComplete(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMetadataEqualsJRE() throws IOException, URISyntaxException {
|
||||||
|
// Ignore this test if not on an Oracle JRE (com.sun...BMPImageReader not available)
|
||||||
|
ImageReader jreReader;
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<ImageReader> jreReaderClass = (Class<ImageReader>) Class.forName("com.sun.imageio.plugins.bmp.BMPImageReader");
|
||||||
|
Constructor<ImageReader> constructor = jreReaderClass.getConstructor(ImageReaderSpi.class);
|
||||||
|
jreReader = constructor.newInstance(new Object[] {null});
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
System.err.println("WARNING: Skipping metadata tests: " + e);
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageReader reader = createReader();
|
||||||
|
|
||||||
|
for (TestData data : getTestData()) {
|
||||||
|
if (data.getInput().toString().contains("pal8offs")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.setInput(data.getInputStream());
|
||||||
|
jreReader.setInput(data.getInputStream());
|
||||||
|
|
||||||
|
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||||
|
|
||||||
|
// WORKAROUND: JRE reader does not reset metadata on setInput. Invoking getWidth forces re-read of header and metadata.
|
||||||
|
try {
|
||||||
|
jreReader.getWidth(0);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
System.err.println("WARNING: Reading " + data + " caused exception: " + e.getMessage());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IIOMetadata jreMetadata = jreReader.getImageMetadata(0);
|
||||||
|
|
||||||
|
assertEquals(true, metadata.isStandardMetadataFormatSupported());
|
||||||
|
assertEquals(jreMetadata.getNativeMetadataFormatName(), metadata.getNativeMetadataFormatName());
|
||||||
|
assertArrayEquals(jreMetadata.getExtraMetadataFormatNames(), metadata.getExtraMetadataFormatNames());
|
||||||
|
|
||||||
|
// TODO: Allow our standard metadata to be richer, but contain at least the information from the JRE impl
|
||||||
|
|
||||||
|
for (String format : jreMetadata.getMetadataFormatNames()) {
|
||||||
|
String absolutePath = data.toString();
|
||||||
|
String localPath = absolutePath.substring(absolutePath.lastIndexOf("test-classes") + 12);
|
||||||
|
|
||||||
|
Node expectedTree = jreMetadata.getAsTree(format);
|
||||||
|
Node actualTree = metadata.getAsTree(format);
|
||||||
|
|
||||||
|
// try {
|
||||||
|
assertNodeEquals(localPath + " - " + format, expectedTree, actualTree);
|
||||||
|
// }
|
||||||
|
// catch (AssertionError e) {
|
||||||
|
// ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||||
|
// ByteArrayOutputStream actual = new ByteArrayOutputStream();
|
||||||
|
//
|
||||||
|
// new XMLSerializer(expected, "UTF-8").serialize(expectedTree, false);
|
||||||
|
// new XMLSerializer(actual, "UTF-8").serialize(actualTree, false);
|
||||||
|
//
|
||||||
|
// assertEquals(e.getMessage(), new String(expected.toByteArray(), "UTF-8"), new String(actual.toByteArray(), "UTF-8"));
|
||||||
|
//
|
||||||
|
// throw e;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNodeEquals(final String message, final Node expected, final Node actual) {
|
||||||
|
assertEquals(message + " class differs", expected.getClass(), actual.getClass());
|
||||||
|
|
||||||
|
if (!excludeEqualValueTest(expected)) {
|
||||||
|
assertEquals(message, expected.getNodeValue(), actual.getNodeValue());
|
||||||
|
|
||||||
|
if (expected instanceof IIOMetadataNode) {
|
||||||
|
IIOMetadataNode expectedIIO = (IIOMetadataNode) expected;
|
||||||
|
IIOMetadataNode actualIIO = (IIOMetadataNode) actual;
|
||||||
|
|
||||||
|
assertEquals(message, expectedIIO.getUserObject(), actualIIO.getUserObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeList expectedChildNodes = expected.getChildNodes();
|
||||||
|
NodeList actualChildNodes = actual.getChildNodes();
|
||||||
|
|
||||||
|
assertEquals(message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes),
|
||||||
|
expectedChildNodes.getLength(), actualChildNodes.getLength());
|
||||||
|
|
||||||
|
for (int i = 0; i < expectedChildNodes.getLength(); i++) {
|
||||||
|
Node expectedChild = expectedChildNodes.item(i);
|
||||||
|
Node actualChild = actualChildNodes.item(i);
|
||||||
|
|
||||||
|
assertEquals(message + " node name differs", expectedChild.getLocalName(), actualChild.getLocalName());
|
||||||
|
assertNodeEquals(message + "/" + expectedChild.getLocalName(), expectedChild, actualChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean excludeEqualValueTest(final Node expected) {
|
||||||
|
if (expected.getLocalName().equals("ImageSize")) {
|
||||||
|
// JRE metadata returns 0, even if known in reader...
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (expected.getLocalName().equals("ColorsImportant")) {
|
||||||
|
// JRE metadata returns 0, even if known in reader...
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (expected.getParentNode() != null && expected.getParentNode().getLocalName().equals("PaletteEntry") && !expected.getNodeValue().equals("Green")) {
|
||||||
|
// JRE metadata returns RGB colors in BGR order
|
||||||
|
// JRE metadata returns 0 for alpha, when -1 (0xff) is at least just as correct (why contain alpha at all?)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (expected.getLocalName().equals("Height") && expected.getNodeValue().startsWith("-")) {
|
||||||
|
// JRE metadata returns negative height for bottom/up images
|
||||||
|
// TODO: Decide if we should do the same, as there is no "orientation" or flag for bottom/up
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toString(final NodeList list) {
|
||||||
|
if (list.getLength() == 0) {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder("[");
|
||||||
|
for (int i = 0; i < list.getLength(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
builder.append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Node node = list.item(i);
|
||||||
|
builder.append(node.getLocalName());
|
||||||
|
}
|
||||||
|
builder.append("]");
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user