diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java index b7bdf420..0f5eb4e4 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java @@ -37,20 +37,26 @@ import static com.twelvemonkeys.lang.Validate.notNull; /** * This class represents a hybrid between an {@link IndexColorModel} and a {@link ComponentColorModel}, - * having both a color map and a full, discrete alpha channel. + * having both a color map and a full, discrete alpha channel and/or one or more "extra" channels. * The color map entries are assumed to be fully opaque and should have no transparent index. *
* ColorSpace will always be the default sRGB color space (as with {@code IndexColorModel}). *
- * Component order is always P, A, where P is a palette index, and A is the alpha value.
+ * Component order is always I, A, X1, X2... Xn,
+ * where I is a palette index, A is the alpha value and Xn are extra samples (ignored for display).
*
* @see IndexColorModel
* @see ComponentColorModel
*/
+// TODO: ExtraSamplesIndexColorModel might be a better name?
+// TODO: Allow specifying which channel is the transparency mask?
public final class DiscreteAlphaIndexColorModel extends ColorModel {
// Our IndexColorModel delegate
private final IndexColorModel icm;
+ private final int samples;
+ private final boolean hasAlpha;
+
/**
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
* to the given {@code IndexColorModel}.
@@ -59,13 +65,34 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
* fully opaque, any transparency or transparent index will be ignored.
*/
public DiscreteAlphaIndexColorModel(final IndexColorModel icm) {
+ this(icm, 1, true);
+ }
+
+ /**
+ * Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
+ * to the given {@code IndexColorModel}.
+ *
+ * @param icm The {@code IndexColorModel} delegate. Color map entries are assumed to be
+ * fully opaque, any transparency or transparent index will be ignored.
+ * @param extraSamples the number of extra samples in the color model.
+ * @param hasAlpha {@code true} if the extra samples contains alpha, otherwise {@code false}.
+ */
+ public DiscreteAlphaIndexColorModel(final IndexColorModel icm, int extraSamples, boolean hasAlpha) {
super(
- notNull(icm, "IndexColorModel").getPixelSize() * 2,
+ notNull(icm, "IndexColorModel").getPixelSize() * (1 + extraSamples),
new int[] {icm.getPixelSize(), icm.getPixelSize(), icm.getPixelSize(), icm.getPixelSize()},
- icm.getColorSpace(), true, false, Transparency.TRANSLUCENT, icm.getTransferType()
+ icm.getColorSpace(), hasAlpha, false, hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE,
+ icm.getTransferType()
);
this.icm = icm;
+ this.samples = 1 + extraSamples;
+ this.hasAlpha = hasAlpha;
+ }
+
+ @Override
+ public int getNumComponents() {
+ return samples;
}
@Override
@@ -85,7 +112,7 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
@Override
public final int getAlpha(final int pixel) {
- return (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f);
+ return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff;
}
private int getSample(final Object inData, final int index) {
@@ -128,17 +155,27 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
@Override
public final int getAlpha(final Object inData) {
- return getAlpha(getSample(inData, 1));
+ return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff;
}
@Override
public final SampleModel createCompatibleSampleModel(final int w, final int h) {
- return new PixelInterleavedSampleModel(transferType, w, h, 2, w * 2, new int[] {0, 1});
+ return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples));
+ }
+
+ private int[] createOffsets(int samples) {
+ int[] offsets = new int[samples];
+
+ for (int i = 0; i < samples; i++) {
+ offsets[i] = i;
+ }
+
+ return offsets;
}
@Override
public final boolean isCompatibleSampleModel(final SampleModel sm) {
- return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == 2;
+ return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
}
@Override
@@ -150,7 +187,7 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
public final boolean isCompatibleRaster(final Raster raster) {
int size = raster.getSampleModel().getSampleSize(0);
return ((raster.getTransferType() == transferType) &&
- (raster.getNumBands() == 2) && ((1 << size) >= icm.getMapSize()));
+ (raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
}
@Override
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java
index 0f3f6259..aaaa618c 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java
@@ -44,7 +44,7 @@ import static com.twelvemonkeys.lang.Validate.notNull;
* Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
* in most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
*
- * @see javax.imageio.ImageTypeSpecifier
+ * @see ImageTypeSpecifier
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
* @version $Id: ImageTypeSpecifiers.java,v 1.0 24.01.11 17.51 haraldk Exp$
@@ -186,4 +186,9 @@ public final class ImageTypeSpecifiers {
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
+
+ public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel pColorModel, int extraSamples, boolean hasAlpha) {
+ ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
+ return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
+ }
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
index da664ce5..59466d61 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
@@ -55,11 +55,14 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.xml.XMLSerializer;
+import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.*;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.spi.IIORegistry;
@@ -71,6 +74,7 @@ import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
+import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
@@ -80,6 +84,7 @@ import java.util.zip.InflaterInputStream;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName;
+import static java.util.Arrays.asList;
/**
* ImageReader implementation for Aldus/Adobe Tagged Image File Format (TIFF).
@@ -111,7 +116,8 @@ import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName;
*
*
* @see Adobe TIFF developer resources
- * @see Wikipedia
+ * @see TIFF 6.0 specification
+ * @see Wikipedia TIFF
* @see AWare Systems TIFF pages
*
* @author Harald Kuhr
@@ -122,10 +128,6 @@ public final class TIFFImageReader extends ImageReaderBase {
// TODOs ImageIO basic functionality:
// TODO: Thumbnail support (what is a TIFF thumbnail anyway? Photoshop way? Or use subfiletype?)
- // TODOs Full BaseLine support:
- // TODO: Support ExtraSamples (an array, if multiple extra samples!)
- // (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
-
// TODOs ImageIO advanced functionality:
// TODO: Tiling support (readTile, readTileRaster)
// TODO: Implement readAsRenderedImage to allow tiled RenderedImage?
@@ -148,6 +150,8 @@ public final class TIFFImageReader extends ImageReaderBase {
// Support ICCProfile
// Support PlanarConfiguration 2
// Support Compression 3 & 4 (CCITT T.4 & T.6)
+ // Support ExtraSamples (an array, if multiple extra samples!)
+ // (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
@@ -469,6 +473,10 @@ public final class TIFFImageReader extends ImageReaderBase {
// as some software will treat black/white runs as-is, regardless of photometric.
// Special handling is also in the normalizeColor method
if (significantSamples == 1 && bitsPerSample == 1) {
+ if (profile != null) {
+ processWarningOccurred("Ignoring embedded ICC color profile for Bi-level/Gray TIFF");
+ }
+
byte[] lut = new byte[] {-1, 0};
return ImageTypeSpecifier.createIndexed(lut, lut, lut, null, bitsPerSample, dataType);
}
@@ -591,8 +599,8 @@ public final class TIFFImageReader extends ImageReaderBase {
IndexColorModel icm = createIndexColorModel(bitsPerSample, dataType, (int[]) colorMap.getValue());
- if (hasAlpha) {
- return ImageTypeSpecifiers.createDiscreteAlphaIndexedFromIndexColorModel(icm);
+ if (extraSamples != null) {
+ return ImageTypeSpecifiers.createDiscreteExtraSamplesIndexedFromIndexColorModel(icm, extraSamples.length, hasAlpha);
}
return ImageTypeSpecifiers.createFromIndexColorModel(icm);
@@ -921,6 +929,10 @@ public final class TIFFImageReader extends ImageReaderBase {
if (stripTileByteCounts == null) {
processWarningOccurred("Missing TileByteCounts for tiled TIFF with compression: " + compression);
}
+ else if (stripTileByteCounts.length == 0 || containsZero(stripTileByteCounts)) {
+ stripTileByteCounts = null;
+ processWarningOccurred("Ignoring all-zero TileByteCounts for tiled TIFF with compression: " + compression);
+ }
stripTileWidth = getValueAsInt(TIFF.TAG_TILE_WIDTH, "TileWidth");
stripTileHeight = getValueAsInt(TIFF.TAG_TILE_HEIGTH, "TileHeight");
@@ -931,6 +943,10 @@ public final class TIFFImageReader extends ImageReaderBase {
if (stripTileByteCounts == null) {
processWarningOccurred("Missing StripByteCounts for TIFF with compression: " + compression);
}
+ else if (stripTileByteCounts.length == 0 || containsZero(stripTileByteCounts)) {
+ stripTileByteCounts = null;
+ processWarningOccurred("Ignoring all-zero StripByteCounts for TIFF with compression: " + compression);
+ }
// NOTE: This is really against the spec, but libTiff seems to handle it. TIFF 6.0 says:
// "Do not use both strip- oriented and tile-oriented fields in the same TIFF file".
@@ -1309,13 +1325,11 @@ public final class TIFFImageReader extends ImageReaderBase {
int len = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Integer.MAX_VALUE;
imageInput.seek(stripTileOffsets != null ? stripTileOffsets[i] : realJPEGOffset);
- try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
- Arrays.asList(
- new ByteArrayInputStream(jpegHeader),
- createStreamAdapter(imageInput, len),
- new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
- )
- )))) {
+ try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(asList(
+ new ByteArrayInputStream(jpegHeader),
+ createStreamAdapter(imageInput, len),
+ new ByteArrayInputStream(new byte[]{(byte) 0xff, (byte) 0xd9}) // EOI
+ ))))) {
jpegReader.setInput(stream);
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
jpegParam.setSourceSubsampling(xSub, ySub, 0, 0);
@@ -1460,7 +1474,7 @@ public final class TIFFImageReader extends ImageReaderBase {
}
try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
- Arrays.asList(
+ asList(
createJFIFStream(destRaster.getNumBands(), stripTileWidth, stripTileHeight, qTables, dcTables, acTables, subsampling),
createStreamAdapter(imageInput, length),
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
@@ -1538,6 +1552,16 @@ public final class TIFFImageReader extends ImageReaderBase {
return destination;
}
+ private boolean containsZero(long[] byteCounts) {
+ for (long byteCount : byteCounts) {
+ if (byteCount <= 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private IIOMetadata readJPEGMetadataSafe(final ImageReader jpegReader) throws IOException {
try {
return jpegReader.getImageMetadata(0);
@@ -2026,7 +2050,7 @@ public final class TIFFImageReader extends ImageReaderBase {
}
}
- private void normalizeColor(int photometricInterpretation, byte[] data) throws IIOException {
+ private void normalizeColor(int photometricInterpretation, byte[] data) throws IOException {
switch (photometricInterpretation) {
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
// NOTE: Preserve WhiteIsZero for 1 bit monochrome, for CCITT compatibility
@@ -2543,6 +2567,13 @@ public final class TIFFImageReader extends ImageReaderBase {
try {
ImageReadParam param = reader.getDefaultReadParam();
+
+ if (param.getClass().getName().equals("com.twelvemonkeys.imageio.plugins.svg.SVGReadParam")) {
+ Method setBaseURI = param.getClass().getMethod("setBaseURI", String.class);
+ String uri = file.getAbsoluteFile().toURI().toString();
+ setBaseURI.invoke(param, uri);
+ }
+
int numImages = reader.getNumImages(true);
for (int imageNo = 0; imageNo < numImages; imageNo++) {
// if (args.length > 1) {
@@ -2557,6 +2588,7 @@ public final class TIFFImageReader extends ImageReaderBase {
// int height = reader.getHeight(imageNo);
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
// param.setSourceRegion(new Rectangle(100, 300, 400, 400));
+// param.setSourceRegion(new Rectangle(95, 105, 100, 100));
// param.setSourceRegion(new Rectangle(3, 3, 9, 9));
// param.setDestinationOffset(new Point(50, 150));
// param.setSourceSubsampling(2, 2, 0, 0);
@@ -2564,16 +2596,18 @@ public final class TIFFImageReader extends ImageReaderBase {
BufferedImage image = reader.read(imageNo, param);
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
-// IIOMetadata metadata = reader.getImageMetadata(imageNo);
-// if (metadata != null) {
-// if (metadata.getNativeMetadataFormatName() != null) {
-// new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false);
-// }
-// /*else*/
-// if (metadata.isStandardMetadataFormatSupported()) {
-// new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
-// }
-// }
+ IIOMetadata metadata = reader.getImageMetadata(imageNo);
+ if (metadata != null) {
+ if (metadata.getNativeMetadataFormatName() != null) {
+ Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
+ replaceBytesWithUndefined((IIOMetadataNode) tree);
+ new XMLSerializer(System.out, "UTF-8").serialize(tree, false);
+ }
+ /*else*/
+ if (metadata.isStandardMetadataFormatSupported()) {
+ new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
+ }
+ }
System.err.println("image: " + image);
@@ -2659,6 +2693,47 @@ public final class TIFFImageReader extends ImageReaderBase {
}
}
+ // XMP Spec says "The field type should be UNDEFINED (7) or BYTE (1)"
+ // Adobe Photoshop® TIFF Technical Notes says (for Image Source Data): "Type: UNDEFINED"
+ private static final Set