TMI-TIFF: Initial commit. Major work in progress. :-)

This commit is contained in:
Harald Kuhr 2012-05-22 00:00:11 +02:00
parent 9492ed67f1
commit 98361194ea
36 changed files with 2070 additions and 678 deletions

View File

@ -1,189 +0,0 @@
/*
* Copyright (c) 2008, 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.tiff;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import org.apache.batik.ext.awt.image.codec.SeekableStream;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFDecodeParam;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageDecoder;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* TIFFImageReader class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: TIFFImageReader.java,v 1.0 29.jul.2004 12:52:33 haku Exp $
*/
// TODO: Massive clean-up
// TODO: Support raster decoding...
public class TIFFImageReader extends ImageReaderBase {
private TIFFImageDecoder decoder = null;
private List<RenderedImage> images = new ArrayList<RenderedImage>();
protected TIFFImageReader(final ImageReaderSpi pOriginatingProvider) {
super(pOriginatingProvider);
}
protected void resetMembers() {
decoder = null;
}
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
// Decode image, convert and return as BufferedImage
RenderedImage image = readAsRenderedImage(pIndex, pParam);
return ImageUtil.toBuffered(image);
}
public RenderedImage readAsRenderedImage(int pIndex, ImageReadParam pParam) throws IOException {
init(pIndex);
processImageStarted(pIndex);
if (pParam == null) {
// Cache image for use by getWidth and getHeight methods
RenderedImage image;
if (images.size() > pIndex && images.get(pIndex) != null) {
image = images.get(pIndex);
}
else {
// Decode
image = decoder.decodeAsRenderedImage(pIndex);
// Make room
for (int i = images.size(); i < pIndex; i++) {
images.add(pIndex, null);
}
images.add(pIndex, image);
}
if (abortRequested()) {
processReadAborted();
return image;
}
processImageComplete();
return image;
}
else {
// TODO: Parameter conversion
decoder.setParam(new TIFFDecodeParam());
RenderedImage image = decoder.decodeAsRenderedImage(pIndex);
// Subsample and apply AOI
if (pParam.getSourceRegion() != null) {
image = fakeAOI(ImageUtil.toBuffered(image), pParam);
}
if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
image = ImageUtil.toBuffered(fakeSubsampling(ImageUtil.toBuffered(image), pParam));
}
processImageComplete();
return image;
}
}
private void init(int pIndex) throws IOException {
init();
checkBounds(pIndex);
}
protected void checkBounds(int index) throws IOException {
if (index < getMinIndex()){
throw new IndexOutOfBoundsException("index < minIndex");
}
else if (index >= getNumImages(true)) {
throw new IndexOutOfBoundsException("index > numImages");
}
}
private synchronized void init() {
if (decoder == null) {
if (imageInput == null) {
throw new IllegalStateException("input == null");
}
decoder = new TIFFImageDecoder(new SeekableStream() {
public int read() throws IOException {
return imageInput.read();
}
public int read(final byte[] pBytes, final int pStart, final int pLength) throws IOException {
return imageInput.read(pBytes, pStart, pLength);
}
public long getFilePointer() throws IOException {
return imageInput.getStreamPosition();
}
public void seek(final long pPos) throws IOException {
imageInput.seek(pPos);
}
}, null);
}
}
public int getWidth(int pIndex) throws IOException {
init(pIndex);
// TODO: Use cache...
return decoder.decodeAsRenderedImage(pIndex).getWidth();
}
public int getHeight(int pIndex) throws IOException {
init(pIndex);
// TODO: Use cache...
return decoder.decodeAsRenderedImage(pIndex).getHeight();
}
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
throw new UnsupportedOperationException("Method getImageTypes not implemented");// TODO: Implement
}
public int getNumImages(boolean allowSearch) throws IOException {
init();
if (allowSearch) {
return decoder.getNumPages();
}
return -1;
}
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (c) 2008, 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.tiff;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.lang.SystemUtil;
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.util.Locale;
/**
* TIFFImageReaderSpi
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: TIFFImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 wmhakur Exp $
*/
public class TIFFImageReaderSpi extends ImageReaderSpi {
final static boolean TIFF_CLASSES_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader");
/**
* Creates a {@code TIFFImageReaderSpi}.
*/
public TIFFImageReaderSpi() {
this(IIOUtil.getProviderInfo(TIFFImageReaderSpi.class));
}
private TIFFImageReaderSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(), // Vendor name
pProviderInfo.getVersion(), // Version
TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "TIFF"} : new String[] {""}, // Names
TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "tif"} : null, // Suffixes
TIFF_CLASSES_AVAILABLE ? new String[]{"image/tiff", "image/x-tiff"} : null, // Mime-types
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader", // Writer class name..?
ImageReaderSpi.STANDARD_INPUT_TYPE, // Output types
new String[]{"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi"}, // Writer SPI names
true, // Supports standard stream metadata format
null, // Native stream metadata format name
null, // Native stream metadata format class name
null, // Extra stream metadata format names
null, // Extra stream metadata format class names
true, // Supports standard image metadata format
null, // Native image metadata format name
null, // Native image metadata format class name
null, // Extra image metadata format names
null // Extra image metadata format class names
);
}
public boolean canDecodeInput(Object source) throws IOException {
return source instanceof ImageInputStream && TIFF_CLASSES_AVAILABLE && canDecode((ImageInputStream) source);
}
static boolean canDecode(ImageInputStream pInput) throws IOException {
try {
pInput.mark();
int byte0 = pInput.read(); // Byte order 1 (M or I)
int byte1 = pInput.read(); // Byte order 2 (always same as 1)
int byte2 = pInput.read(); // Version number 1 (M: 0, I: 42)
int byte3 = pInput.read(); // Version number 2 (M: 42, I: 0)
// Test for Motorola or Intel byte order, and version number == 42
if ((byte0 == 'M' && byte1 == 'M' && byte2 == 0 && byte3 == 42)
|| (byte0 == 'I' && byte1 == 'I' && byte2 == 42 && byte3 == 0)) {
return true;
}
}
finally {
pInput.reset();
}
return false;
}
public ImageReader createReaderInstance(Object extension) throws IOException {
return new TIFFImageReader(this);
}
public String getDescription(Locale locale) {
return "Tagged Image File Format (TIFF) image reader";
}
@SuppressWarnings({"deprecation"})
@Override
public void onRegistration(ServiceRegistry registry, Class<?> category) {
if (!TIFF_CLASSES_AVAILABLE) {
IIOUtil.deregisterProvider(registry, this, category);
}
}
}

View File

@ -1,145 +0,0 @@
/*
* Copyright (c) 2008, 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.tiff;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import org.apache.batik.ext.awt.image.codec.ImageEncodeParam;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFEncodeParam;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageEncoder;
import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
/**
* TIFFImageWriter class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: TIFFImageWriter.java,v 1.0 29.jul.2004 12:52:54 haku Exp $
*/
public class TIFFImageWriter extends ImageWriterBase {
private TIFFImageEncoder encoder;
protected TIFFImageWriter(final ImageWriterSpi pProvider) {
super(pProvider);
}
@Override
public void setOutput(final Object output) {
encoder = null;
super.setOutput(output);
}
public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
throw new UnsupportedOperationException("Method getDefaultImageMetadata not implemented");// TODO: Implement
}
public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
throw new UnsupportedOperationException("Method convertImageMetadata not implemented");// TODO: Implement
}
public void write(final IIOMetadata pStreamMetadata, final IIOImage pImage, final ImageWriteParam pParam) throws IOException {
RenderedImage renderedImage = pImage.getRenderedImage();
init();
ImageEncodeParam param;
if (pParam != null) {
param = new TIFFEncodeParam();
// TODO: Convert params
encoder.setParam(param);
}
BufferedImage image;
// FIX: TIFFEnocder chokes on a any of the TYPE_INT_* types...
// (The TIFFEncoder expects int types to have 1 sample of size 32
// while there actually is 4 samples of size 8, according to the
// SampleModel...)
if (renderedImage instanceof BufferedImage && (
((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_ARGB
|| ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_ARGB_PRE)) {
image = ImageUtil.toBuffered(renderedImage, BufferedImage.TYPE_4BYTE_ABGR);
}
else if (renderedImage instanceof BufferedImage && (
((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_BGR
|| ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_RGB)) {
image = ImageUtil.toBuffered(renderedImage, BufferedImage.TYPE_3BYTE_BGR);
}
else {
image = ImageUtil.toBuffered(renderedImage);
}
image = fakeAOI(image, pParam);
image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
/*
System.out.println("Image: " + pImage);
SampleModel sampleModel = pImage.getSampleModel();
System.out.println("SampleModel: " + sampleModel);
int sampleSize[] = sampleModel.getSampleSize();
System.out.println("Samples: " + sampleSize.length);
for (int i = 0; i < sampleSize.length; i++) {
System.out.println("SampleSize[" + i + "]: " + sampleSize[i]);
}
int dataType = sampleModel.getDataType();
System.out.println("DataType: " + dataType);
*/
processImageStarted(0);
encoder.encode(image);
imageOutput.flush();
processImageComplete();
}
public void dispose() {
super.dispose();
encoder = null;
}
private synchronized void init() {
if (encoder == null) {
if (imageOutput == null) {
throw new IllegalStateException("output == null");
}
encoder = new TIFFImageEncoder(IIOUtil.createStreamAdapter(imageOutput), null);
}
}
}

View File

@ -1,107 +0,0 @@
/*
* Copyright (c) 2008, 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.tiff;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.spi.ServiceRegistry;
import java.io.IOException;
import java.util.Locale;
/**
* TIFFmageWriterSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: TIFFImageWriterSpi.java,v 1.2 2004/01/14 15:21:44 wmhakur Exp $
*/
public class TIFFImageWriterSpi extends ImageWriterSpi {
/**
* Creates a {@code TIFFImageWriterSpi}.
*/
public TIFFImageWriterSpi() {
this(IIOUtil.getProviderInfo(TIFFImageWriterSpi.class));
}
private TIFFImageWriterSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(), // Vendor name
pProviderInfo.getVersion(), // Version
new String[]{"tiff", "TIFF"}, // Names
new String[]{"tif", "tiff"}, // Suffixes
new String[]{"image/tiff", "image/x-tiff"}, // Mime-types
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter", // Writer class name..?
STANDARD_OUTPUT_TYPE, // Output types
new String[]{"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"}, // Reader SPI names
true, // Supports standard stream metadata format
null, // Native stream metadata format name
null, // Native stream metadata format class name
null, // Extra stream metadata format names
null, // Extra stream metadata format class names
true, // Supports standard image metadata format
null, // Native image metadata format name
null, // Native image metadata format class name
null, // Extra image metadata format names
null // Extra image metadata format class names
);
}
public boolean canEncodeImage(ImageTypeSpecifier type) {
return true;
}
public ImageWriter createWriterInstance(Object extension) throws IOException {
try {
return new TIFFImageWriter(this);
}
catch (Throwable t) {
// Wrap in IOException if the writer can't be instantiated.
// This makes the IIORegistry deregister this service provider
IOException exception = new IOException(t.getMessage());
exception.initCause(t);
throw exception;
}
}
public String getDescription(Locale locale) {
return "Tagged Image File Format (TIFF) image writer";
}
@SuppressWarnings({"deprecation"})
@Override
public void onRegistration(ServiceRegistry registry, Class<?> category) {
if (!TIFFImageReaderSpi.TIFF_CLASSES_AVAILABLE) {
IIOUtil.deregisterProvider(registry, this, category);
}
}
}

View File

@ -1,3 +1,2 @@
com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi
com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi

View File

@ -1 +0,0 @@
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi

View File

@ -90,6 +90,8 @@ final class EXIFEntry extends AbstractEntry {
return "Orientation"; return "Orientation";
case TIFF.TAG_SAMPLES_PER_PIXELS: case TIFF.TAG_SAMPLES_PER_PIXELS:
return "SamplesPerPixels"; return "SamplesPerPixels";
case TIFF.TAG_ROWS_PER_STRIP:
return "RowsPerStrip";
case TIFF.TAG_X_RESOLUTION: case TIFF.TAG_X_RESOLUTION:
return "XResolution"; return "XResolution";
case TIFF.TAG_Y_RESOLUTION: case TIFF.TAG_Y_RESOLUTION:
@ -120,6 +122,10 @@ final class EXIFEntry extends AbstractEntry {
return "YCbCrSubSampling"; return "YCbCrSubSampling";
case TIFF.TAG_YCBCR_POSITIONING: case TIFF.TAG_YCBCR_POSITIONING:
return "YCbCrPositioning"; return "YCbCrPositioning";
case TIFF.TAG_COLOR_MAP:
return "ColorMap";
case TIFF.TAG_EXTRA_SAMPLES:
return "ExtraSamples";
case EXIF.TAG_EXPOSURE_TIME: case EXIF.TAG_EXPOSURE_TIME:
return "ExposureTime"; return "ExposureTime";

View File

@ -95,6 +95,7 @@ public final class EXIFReader extends MetadataReader {
EXIFEntry entry = readEntry(pInput); EXIFEntry entry = readEntry(pInput);
if (entry == null) { if (entry == null) {
// System.err.println("Expected: " + entryCount + " values, found only " + i);
// TODO: Log warning? // TODO: Log warning?
nextOffset = 0; nextOffset = 0;
break; break;
@ -199,13 +200,13 @@ public final class EXIFReader extends MetadataReader {
Object value = entry.getValue(); Object value = entry.getValue();
if (value instanceof Byte) { if (value instanceof Byte) {
offset = ((Byte) value & 0xff); offset = (Byte) value & 0xff;
} }
else if (value instanceof Short) { else if (value instanceof Short) {
offset = ((Short) value & 0xffff); offset = (Short) value & 0xffff;
} }
else if (value instanceof Integer) { else if (value instanceof Integer) {
offset = ((Integer) value & 0xffffffffL); offset = (Integer) value & 0xffffffffL;
} }
else if (value instanceof Long) { else if (value instanceof Long) {
offset = (Long) value; offset = (Long) value;
@ -222,7 +223,7 @@ public final class EXIFReader extends MetadataReader {
int tagId = pInput.readUnsignedShort(); int tagId = pInput.readUnsignedShort();
short type = pInput.readShort(); short type = pInput.readShort();
// This isn't really an entry, and the directory entry count was wront // This isn't really an entry, and the directory entry count was wrong OR bad data...
if (tagId == 0 && type == 0) { if (tagId == 0 && type == 0) {
return null; return null;
} }
@ -236,24 +237,28 @@ public final class EXIFReader extends MetadataReader {
if (type <= 0 || type > 13) { if (type <= 0 || type > 13) {
// Invalid tag, this is just for debugging // Invalid tag, this is just for debugging
System.err.printf("Bad EXIF data at offset: %08x\n", pInput.getStreamPosition() - 8l); long offset = pInput.getStreamPosition() - 8l;
System.err.println("tagId: " + tagId);
System.err.printf("Bad EXIF");
System.err.println("tagId: " + tagId + (tagId <= 0 ? "(INVALID)" : ""));
System.err.println("type: " + type + " (INVALID)"); System.err.println("type: " + type + " (INVALID)");
System.err.println("count: " + count); System.err.println("count: " + count);
pInput.mark(); pInput.mark();
pInput.seek(pInput.getStreamPosition() - 8); pInput.seek(offset);
try { try {
byte[] bytes = new byte[8 + Math.max(20, count)]; byte[] bytes = new byte[8 + Math.max(20, count)];
int len = pInput.read(bytes); int len = pInput.read(bytes);
System.err.print("data: " + HexDump.dump(bytes, 0, len)); System.err.print(HexDump.dump(offset, bytes, 0, len));
System.err.println(len < count ? "[...]" : ""); System.err.println(len < count ? "[...]" : "");
} }
finally { finally {
pInput.reset(); pInput.reset();
} }
return null;
} }
int valueLength = getValueLength(type, count); int valueLength = getValueLength(type, count);
@ -484,7 +489,7 @@ public final class EXIFReader extends MetadataReader {
Object value = entry.getValue(); Object value = entry.getValue();
if (value instanceof byte[]) { if (value instanceof byte[]) {
byte[] bytes = (byte[]) value; byte[] bytes = (byte[]) value;
System.err.println(HexDump.dump(bytes, 0, Math.min(bytes.length, 128))); System.err.println(HexDump.dump(0, bytes, 0, Math.min(bytes.length, 128)));
} }
} }
} }
@ -501,10 +506,10 @@ public final class EXIFReader extends MetadataReader {
private static final int WIDTH = 32; private static final int WIDTH = 32;
public static String dump(byte[] bytes) { public static String dump(byte[] bytes) {
return dump(bytes, 0, bytes.length); return dump(0, bytes, 0, bytes.length);
} }
public static String dump(byte[] bytes, int off, int len) { public static String dump(long offset, byte[] bytes, int off, int len) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
int i; int i;
@ -513,7 +518,7 @@ public final class EXIFReader extends MetadataReader {
if (i > 0 ) { if (i > 0 ) {
builder.append("\n"); builder.append("\n");
} }
builder.append(String.format("%08x: ", i + off)); builder.append(String.format("%08x: ", i + off + offset));
} }
else if (i > 0 && i % 2 == 0) { else if (i > 0 && i % 2 == 0) {
builder.append(" "); builder.append(" ");

View File

@ -118,8 +118,11 @@ public interface TIFF {
/// C. Tags relating to image data characteristics /// C. Tags relating to image data characteristics
int TAG_TRANSFER_FUNCTION = 301; int TAG_TRANSFER_FUNCTION = 301;
int TAG_PREDICTOR = 317;
int TAG_WHITE_POINT = 318; int TAG_WHITE_POINT = 318;
int TAG_PRIMARY_CHROMATICITIES = 319; int TAG_PRIMARY_CHROMATICITIES = 319;
int TAG_COLOR_MAP = 320;
int TAG_EXTRA_SAMPLES = 338;
int TAG_YCBCR_COEFFICIENTS = 529; int TAG_YCBCR_COEFFICIENTS = 529;
int TAG_REFERENCE_BLACK_WHITE = 532; int TAG_REFERENCE_BLACK_WHITE = 532;
@ -151,4 +154,11 @@ public interface TIFF {
int TAG_MODI_PLAIN_TEXT = 37679; int TAG_MODI_PLAIN_TEXT = 37679;
int TAG_MODI_OLE_PROPERTY_SET = 37680; int TAG_MODI_OLE_PROPERTY_SET = 37680;
int TAG_MODI_TEXT_POS_INFO = 37681; int TAG_MODI_TEXT_POS_INFO = 37681;
int TAG_TILE_WIDTH = 322;
int TAG_TILE_HEIGTH = 323;
int TAG_TILE_OFFSETS = 324;
int TAG_TILE_BYTE_COUNTS = 325;
int TAG_JPEG_TABLES = 347;
} }

View File

@ -45,6 +45,8 @@ public interface JPEG {
/** Define Quantization Tables segment marker (DQT). */ /** Define Quantization Tables segment marker (DQT). */
int DQT = 0xFFDB; int DQT = 0xFFDB;
/** Define Huffman Tables segment marker (DHT). */
int DHT = 0xFFC4;
// App segment markers (APPn). // App segment markers (APPn).
int APP0 = 0xFFE0; int APP0 = 0xFFE0;

View File

@ -30,10 +30,12 @@ package com.twelvemonkeys.imageio.metadata.jpeg;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.plugins.jpeg.JPEGQTable;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -83,7 +85,7 @@ public final class JPEGQuality {
} }
// Adapted from ImageMagick coders/jpeg.c & http://blog.apokalyptik.com/2009/09/16/quality-time-with-your-jpegs/ // Adapted from ImageMagick coders/jpeg.c & http://blog.apokalyptik.com/2009/09/16/quality-time-with-your-jpegs/
private static int getJPEGQuality(final short[][] quantizationTables) throws IOException { private static int getJPEGQuality(final int[][] quantizationTables) throws IOException {
// System.err.println("tables: " + Arrays.deepToString(tables)); // System.err.println("tables: " + Arrays.deepToString(tables));
// TODO: Determine lossless JPEG // TODO: Determine lossless JPEG
@ -188,8 +190,24 @@ public final class JPEGQuality {
return -1; return -1;
} }
private static short[][] getQuantizationTables(List<JPEGSegment> dqtSegments) throws IOException { public static JPEGQTable[] getQTables(final List<JPEGSegment> dqtSegments) throws IOException {
short[][] tables = new short[4][]; int[][] tables = getQuantizationTables(dqtSegments);
List<JPEGQTable> qTables = new ArrayList<JPEGQTable>();
for (int[] table : tables) {
if (table != null) {
qTables.add(new JPEGQTable(table));
}
else {
break;
}
}
return qTables.toArray(new JPEGQTable[qTables.size()]);
}
private static int[][] getQuantizationTables(final List<JPEGSegment> dqtSegments) throws IOException {
int[][] tables = new int[4][];
// JPEG may contain multiple DQT marker segments // JPEG may contain multiple DQT marker segments
for (JPEGSegment segment : dqtSegments) { for (JPEGSegment segment : dqtSegments) {
@ -223,7 +241,7 @@ public final class JPEGQuality {
byte[] qtData = new byte[DCT_SIZE_2 * (bits + 1)]; byte[] qtData = new byte[DCT_SIZE_2 * (bits + 1)];
data.readFully(qtData); data.readFully(qtData);
read += qtData.length; read += qtData.length;
tables[num] = new short[DCT_SIZE_2]; tables[num] = new int[DCT_SIZE_2];
// Expand (this is slightly inefficient) // Expand (this is slightly inefficient)
switch (bits) { switch (bits) {

View File

@ -175,4 +175,19 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
assertNotNull(exif); assertNotNull(exif);
assertEquals(3, exif.size()); assertEquals(3, exif.size());
} }
@Test
public void testTIFFWithBadExifIFD() throws IOException {
// This image seems to contain bad TIFF data. But as other tools are able to read, so should we..
// It seems that the EXIF data (at offset 494196 or 0x78a74) overlaps with a custom
// Microsoft 'OLE Property Set' entry at 0x78a70 (UNDEFINED, count 5632)...
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/tiff/chifley_logo.tif"));
Directory directory = createReader().read(stream);
assertEquals(22, directory.size());
// Some (all?) of the EXIF data is duplicated in the XMP, meaning PhotoShop can probably re-create it
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
assertNotNull(exif);
assertEquals(0, exif.size()); // EXIFTool reports "Warning: Bad ExifIFD directory"
}
} }

View File

@ -145,6 +145,10 @@ public class JPEGQualityTest {
} }
} }
@Test
public void testGetQTables() {
fail("Not implemented");
}
private BufferedImage createTestImage() { private BufferedImage createTestImage() {
BufferedImage image = new BufferedImage(90, 60, BufferedImage.TYPE_3BYTE_BGR); BufferedImage image = new BufferedImage(90, 60, BufferedImage.TYPE_3BYTE_BGR);

View File

@ -441,7 +441,7 @@ public class PSDImageReader extends ImageReaderBase {
case PSD.COMPRESSION_ZIP: case PSD.COMPRESSION_ZIP:
// TODO: Could probably use the ZIPDecoder (DeflateDecoder) here.. // TODO: Could probably use the ZIPDecoder (DeflateDecoder) here..
case PSD.COMPRESSION_ZIP_PREDICTION: case PSD.COMPRESSION_ZIP_PREDICTION:
// TODO: Need to find out if the normal java.util.zip can handle this... // TODO: Look at TIFF prediction reading
// Could be same as PNG prediction? Read up... // Could be same as PNG prediction? Read up...
throw new IIOException("PSD with ZIP compression not supported"); throw new IIOException("PSD with ZIP compression not supported");
default: default:

View File

@ -0,0 +1,32 @@
<?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.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
<description>
ImageIO plugin for Aldus/Adobe Tagged Image File Format (TIFF).
</description>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-metadata</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,157 @@
/*
* Copyright (c) 2012, 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.tiff;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGQuality;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
import javax.imageio.IIOException;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import javax.imageio.plugins.jpeg.JPEGQTable;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.*;
/**
* JPEGTables
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: JPEGTables.java,v 1.0 11.05.12 09:13 haraldk Exp$
*/
class JPEGTables {
private static final int DHT_LENGTH = 16;
private static final Map<Integer, List<String>> SEGMENT_IDS = createSegmentIdsMap();
private JPEGQTable[] qTables;
private JPEGHuffmanTable[] dcHTables;
private JPEGHuffmanTable[] acHTables;
private static Map<Integer, List<String>> createSegmentIdsMap() {
Map<Integer, List<String>> segmentIds = new HashMap<Integer, List<String>>();
segmentIds.put(JPEG.DQT, null);
segmentIds.put(JPEG.DHT, null);
return Collections.unmodifiableMap(segmentIds);
}
private final List<JPEGSegment> segments;
public JPEGTables(ImageInputStream input) throws IOException {
segments = JPEGSegmentUtil.readSegments(input, SEGMENT_IDS);
}
public JPEGQTable[] getQTables() throws IOException {
if (qTables == null) {
qTables = JPEGQuality.getQTables(segments);
}
return qTables;
}
private void getHuffmanTables() throws IOException {
if (dcHTables == null || acHTables == null) {
List<JPEGHuffmanTable> dc = new ArrayList<JPEGHuffmanTable>();
List<JPEGHuffmanTable> ac = new ArrayList<JPEGHuffmanTable>();
// JPEG may contain multiple DHT marker segments
for (JPEGSegment segment : segments) {
if (segment.marker() != JPEG.DHT) {
continue;
}
DataInputStream data = new DataInputStream(segment.data());
int read = 0;
// A single DHT marker segment may contain multiple tables
while (read < segment.length()) {
int htInfo = data.read();
read++;
int num = htInfo & 0x0f; // 0-3
int type = htInfo >> 4; // 0 == DC, 1 == AC
if (type > 1) {
throw new IIOException("Bad DHT type: " + type);
}
if (num >= 4) {
throw new IIOException("Bad DHT table index: " + num);
}
else if (type == 0 ? dc.size() > num : ac.size() > num) {
throw new IIOException("Duplicate DHT table index: " + num);
}
// Read lengths as short array
short[] lengths = new short[DHT_LENGTH];
for (int i = 0, lengthsLength = lengths.length; i < lengthsLength; i++) {
lengths[i] = (short) data.readUnsignedByte();
}
read += lengths.length;
int sum = 0;
for (short length : lengths) {
sum += length;
}
// Expand table to short array
short[] table = new short[sum];
for (int j = 0; j < sum; j++) {
table[j] = (short) data.readUnsignedByte();
}
JPEGHuffmanTable hTable = new JPEGHuffmanTable(lengths, table);
if (type == 0) {
dc.add(num, hTable);
}
else {
ac.add(num, hTable);
}
read += sum;
}
}
dcHTables = dc.toArray(new JPEGHuffmanTable[dc.size()]);
acHTables = ac.toArray(new JPEGHuffmanTable[ac.size()]);
}
}
public JPEGHuffmanTable[] getDCHuffmanTables() throws IOException {
getHuffmanTables();
return dcHTables;
}
public JPEGHuffmanTable[] getACHuffmanTables() throws IOException {
getHuffmanTables();
return acHTables;
}
}

View File

@ -0,0 +1,272 @@
/*
* Copyright (c) 2012, 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.tiff;
import com.twelvemonkeys.io.enc.DecodeException;
import com.twelvemonkeys.io.enc.Decoder;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
* LZWDecoder
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$
*/
final class LZWDecoder implements Decoder {
/** Clear: Re-initialize tables. */
static final int CLEAR_CODE = 256;
/** End of Information. */
static final int EOI_CODE = 257;
private static final int MIN_BITS = 9;
private static final int MAX_BITS = 12;
private final boolean reverseBitOrder;
private int currentByte = -1;
private int bitPos;
// TODO: Consider speeding things up with a "string" type (instead of the inner byte[]),
// that uses variable size/dynamic allocation, to avoid the excessive array copying?
// private final byte[][] table = new byte[4096][0]; // libTiff adds another 1024 "for compatibility"...
private final byte[][] table = new byte[4096 + 1024][0]; // libTiff adds another 1024 "for compatibility"...
private int tableLength;
private int bitsPerCode;
private int oldCode = CLEAR_CODE;
private int maxCode;
private int maxString;
private boolean eofReached;
LZWDecoder(final boolean reverseBitOrder) {
this.reverseBitOrder = reverseBitOrder;
for (int i = 0; i < 256; i++) {
table[i] = new byte[] {(byte) i};
}
init();
}
LZWDecoder() {
this(false);
}
private int maxCodeFor(final int bits) {
return reverseBitOrder ? (1 << bits) - 2 : (1 << bits) - 1;
}
private void init() {
tableLength = 258;
bitsPerCode = MIN_BITS;
maxCode = maxCodeFor(bitsPerCode);
maxString = 1;
}
public int decode(final InputStream stream, final byte[] buffer) throws IOException {
// Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992.
// See Section 13: "LZW Compression"/"LZW Decoding", page 61+
int bufferPos = 0;
int code;
while ((code = getNextCode(stream)) != EOI_CODE) {
if (code == CLEAR_CODE) {
init();
code = getNextCode(stream);
if (code == EOI_CODE) {
break;
}
bufferPos += writeString(table[code], buffer, bufferPos);
}
else {
if (code > tableLength + 1 || oldCode >= tableLength) {
// TODO: FixMe for old, borked streams
System.err.println("code: " + code);
System.err.println("oldCode: " + oldCode);
System.err.println("tableLength: " + tableLength);
throw new DecodeException("Corrupted LZW table");
}
if (isInTable(code)) {
bufferPos += writeString(table[code], buffer, bufferPos);
addStringToTable(concatenate(table[oldCode], table[code][0]));
}
else {
byte[] outString = concatenate(table[oldCode], table[oldCode][0]);
bufferPos += writeString(outString, buffer, bufferPos);
addStringToTable(outString);
}
}
oldCode = code;
if (bufferPos >= buffer.length - maxString - 1) {
// Buffer full, stop decoding for now
break;
}
}
return bufferPos;
}
private byte[] concatenate(final byte[] string, final byte firstChar) {
byte[] result = Arrays.copyOf(string, string.length + 1);
result[string.length] = firstChar;
return result;
}
private void addStringToTable(final byte[] string) throws IOException {
table[tableLength++] = string;
if (tableLength >= maxCode) {
bitsPerCode++;
if (bitsPerCode > MAX_BITS) {
if (reverseBitOrder) {
bitsPerCode--;
}
else {
throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
}
}
maxCode = maxCodeFor(bitsPerCode);
}
if (string.length > maxString) {
maxString = string.length;
}
}
private int writeString(final byte[] string, final byte[] buffer, final int bufferPos) {
if (string.length == 0) {
return 0;
}
else if (string.length == 1) {
buffer[bufferPos] = string[0];
return 1;
}
else {
System.arraycopy(string, 0, buffer, bufferPos, string.length);
return string.length;
}
}
private boolean isInTable(int code) {
return code < tableLength;
}
private int getNextCode(final InputStream stream) throws IOException {
if (eofReached) {
return EOI_CODE;
}
int bitsToFill = bitsPerCode;
int value = 0;
while (bitsToFill > 0) {
int nextBits;
if (bitPos == 0) {
nextBits = stream.read();
if (nextBits == -1) {
// This is really a bad stream, but should be safe to handle this way, rather than throwing an EOFException.
// An EOFException will be thrown by the decoder stream later, if further reading is attempted.
eofReached = true;
return EOI_CODE;
}
}
else {
nextBits = currentByte;
}
int bitsFromHere = 8 - bitPos;
if (bitsFromHere > bitsToFill) {
bitsFromHere = bitsToFill;
}
if (reverseBitOrder) {
// NOTE: This is a spec violation. However, libTiff reads such files.
// TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says:
// "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder
// is assumed to be 1. The compressed codes are written as bytes (not words) so that the
// compressed data will be identical whether it is an II or MM file."
// Fill bytes from right-to-left
for (int i = 0; i < bitsFromHere; i++) {
int destBitPos = bitsPerCode - bitsToFill + i;
int srcBitPos = bitPos + i;
value |= ((nextBits & (1 << srcBitPos)) >> srcBitPos) << destBitPos;
}
}
else {
value |= (nextBits >> 8 - bitPos - bitsFromHere & 0xff >> 8 - bitsFromHere) << bitsToFill - bitsFromHere;
}
bitsToFill -= bitsFromHere;
bitPos += bitsFromHere;
if (bitPos >= 8) {
bitPos = 0;
}
currentByte = nextBits;
}
if (value == EOI_CODE) {
eofReached = true;
}
return value;
}
static boolean isOldBitReversedStream(final InputStream stream) throws IOException {
stream.mark(2);
try {
int one = stream.read();
int two = stream.read();
return one == 0 && (two & 0x1) == 1; // => (reversed) 1 00000000 == 256 (CLEAR_CODE)
}
finally {
stream.reset();
}
}
}

View File

@ -1,46 +1,59 @@
/* /*
* Copyright (c) 2008, Harald Kuhr * Copyright (c) 2012, Harald Kuhr
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright * * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright * * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the * notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution. * documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the * * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products * names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission. * derived from this software without specific prior written permission.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.imageio.plugins.tiff;
import java.io.OutputStream; /**
import java.io.IOException; * TIFFBaseline
*
/** * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* LZWEncoder. * @author last modified by $Author: haraldk$
* <p/> * @version $Id: TIFFBaseline.java,v 1.0 08.05.12 16:43 haraldk Exp$
* */
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> interface TIFFBaseline {
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java#2 $ int COMPRESSION_NONE = 1;
*/ int COMPRESSION_CCITT_HUFFMAN = 2;
final class LZWEncoder implements Encoder { int COMPRESSION_PACKBITS = 32773;
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
// TODO: Implement int PHOTOMETRIC_WHITE_IS_ZERO = 0;
// TODO: We probably need a GIF specific subclass int PHOTOMETRIC_BLACK_IS_ZERO = 1;
} int PHOTOMETRIC_RGB = 2;
} int PHOTOMETRIC_PALETTE = 3;
int PHOTOMETRIC_MASK = 4;
int SAMPLEFORMAT_UINT = 1;
int SAMPLEFORMAT_INT = 2;
int SAMPLEFORMAT_FP = 3;
int SAMPLEFORMAT_UNDEFINED = 4;
int PLANARCONFIG_CHUNKY = 1;
int EXTRASAMPLE_UNSPECIFIED = 0;
int EXTRASAMPLE_ASSOCALPHA = 1;
int EXTRASAMPLE_UNASSALPHA = 2;
}

View File

@ -1,46 +1,49 @@
/* /*
* Copyright (c) 2008, Harald Kuhr * Copyright (c) 2012, Harald Kuhr
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright * * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright * * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the * notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution. * documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the * * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products * names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission. * derived from this software without specific prior written permission.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.io.enc; package com.twelvemonkeys.imageio.plugins.tiff;
import java.io.InputStream; /**
import java.io.IOException; * TIFFCustom
*
/** * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* LZWDecoder. * @author last modified by $Author: haraldk$
* <p/> * @version $Id: TIFFCustom.java,v 1.0 10.05.12 17:35 haraldk Exp$
* */
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> interface TIFFCustom {
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java#2 $ int PHOTOMETRIC_LOGL = 32844;
*/ int PHOTOMETRIC_LOGLUV = 32845;
final class LZWDecoder implements Decoder {
public int decode(InputStream pStream, byte[] pBuffer) throws IOException { /** DNG: CFA (Color Filter Array)*/
return 0; // TODO: Implement int PHOTOMETRIC_CFA = 32803;
// TODO: We probably need a GIF specific subclass /** DNG: LinearRaw*/
} int PHOTOMETRIC_LINEAR_RAW = 34892;
}
int SAMPLEFORMAT_COMPLEXINT = 5;
int SAMPLEFORMAT_COMPLEXIEEEFP = 6;
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2012, 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.tiff;
/**
* TIFFExtension
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: TIFFExtension.java,v 1.0 08.05.12 16:45 haraldk Exp$
*/
interface TIFFExtension {
/** CCITT T.4/Group 3 Fax compression. */
int COMPRESSION_CCITT_T4 = 3;
/** CCITT T.6/Group 4 Fax compression. */
int COMPRESSION_CCITT_T6 = 4;
/** LZW Compression. Was baseline, but moved to extension due to license issues in the LZW algorithm. */
int COMPRESSION_LZW = 5;
/** Deprecated. */
int COMPRESSION_OLD_JPEG = 6;
/** JPEG Compression (lossy). */
int COMPRESSION_JPEG = 7;
/** Custom: PKZIP-style Deflate. */
int COMPRESSION_DEFLATE = 32946;
/** Adobe-style Deflate. */
int COMPRESSION_ZLIB = 8;
/*
LibTIFF:
COMPRESSION_NONE = 1;
COMPRESSION_CCITTRLE = 2;
COMPRESSION_CCITTFAX3 = COMPRESSION_CCITT_T4 = 3;
COMPRESSION_CCITTFAX4 = COMPRESSION_CCITT_T6 = 4;
COMPRESSION_LZW = 5;
COMPRESSION_OJPEG = 6;
COMPRESSION_JPEG = 7;
COMPRESSION_NEXT = 32766;
COMPRESSION_CCITTRLEW = 32771;
COMPRESSION_PACKBITS = 32773;
COMPRESSION_THUNDERSCAN = 32809;
COMPRESSION_IT8CTPAD = 32895;
COMPRESSION_IT8LW = 32896;
COMPRESSION_IT8MP = 32897;
COMPRESSION_IT8BL = 32898;
COMPRESSION_PIXARFILM = 32908;
COMPRESSION_PIXARLOG = 32909;
COMPRESSION_DEFLATE = 32946;
COMPRESSION_ADOBE_DEFLATE = 8;
COMPRESSION_DCS = 32947;
COMPRESSION_JBIG = 34661;
COMPRESSION_SGILOG = 34676;
COMPRESSION_SGILOG24 = 34677;
COMPRESSION_JP2000 = 34712;
*/
int PHOTOMETRIC_SEPARATED = 5;
int PHOTOMETRIC_YCBCR = 6;
int PHOTOMETRIC_CIELAB = 8;
int PHOTOMETRIC_ICCLAB = 9;
int PHOTOMETRIC_ITULAB = 10;
int PLANARCONFIG_PLANAR = 2;
int PREDICTOR_NONE = 1;
int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
}

View File

@ -0,0 +1,990 @@
/*
* Copyright (c) 2012, 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.tiff;
import com.sun.imageio.plugins.jpeg.JPEGImageReader;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import javax.imageio.*;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipInputStream;
/**
* ImageReader implementation for Aldus/Adobe Tagged Image File Format (TIFF).
*
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
* @see <a href="http://en.wikipedia.org/wiki/Tagged_Image_File_Format">Wikipedia</a>
* @see <a href="http://www.awaresystems.be/imaging/tiff.html">AWare Systems TIFF pages</a>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: TIFFImageReader.java,v 1.0 08.05.12 15:14 haraldk Exp$
*/
public class TIFFImageReader extends ImageReaderBase {
// TODO: Full BaseLine support
// TODO: Support ExtraSamples (an array!) (1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
// TODO: Handle SampleFormat (and give up if not == 1)
// TODO: Support Compression 2 (CCITT Modified Huffman) for bi-level images
// TODO: ImageIO functionality
// TODO: Subsampling
// TODO: Source region
// TODO: Extension support
// TODO: Support PlanarConfiguration 2
// TODO: Support ICCProfile (fully)
// TODO: Support Compression 3 & 4 (CCITT T.4 & T.6)
// TODO: Support Compression 6 ('Old-style' JPEG)
// TODO: Support Compression 34712 (JPEG2000)? Depends on JPEG2000 ImageReader
// TODO: Support Compression 34661 (JBIG)? Depends on JBIG ImageReader
// DONE:
// Delete the old Batik-based TIFFImageReader/Spi
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
private CompoundDirectory ifds;
private Directory currentIFD;
TIFFImageReader(final TIFFImageReaderSpi provider) {
super(provider);
}
@Override
protected void resetMembers() {
ifds = null;
currentIFD = null;
}
private void readMetadata() throws IOException {
if (imageInput == null) {
throw new IllegalStateException("input not set");
}
if (ifds == null) {
ifds = (CompoundDirectory) new EXIFReader().read(imageInput); // NOTE: Sets byte order as a side effect
if (DEBUG) {
for (int i = 0; i < ifds.directoryCount(); i++) {
System.err.printf("ifd[%d]: %s\n", i, ifds.getDirectory(i));
}
System.err.println("Byte order: " + imageInput.getByteOrder());
System.err.println("numImages: " + ifds.directoryCount());
}
}
}
private void readIFD(final int imageIndex) throws IOException {
readMetadata();
checkBounds(imageIndex);
currentIFD = ifds.getDirectory(imageIndex);
}
@Override
public int getNumImages(final boolean allowSearch) throws IOException {
readMetadata();
return ifds.directoryCount();
}
private int getValueAsIntWithDefault(final int tag, String tagName, Integer defaultValue) throws IIOException {
Entry entry = currentIFD.getEntryById(tag);
if (entry == null) {
if (defaultValue != null) {
return defaultValue;
}
throw new IIOException("Missing TIFF tag: " + (tagName != null ? tagName : tag));
}
return ((Number) entry.getValue()).intValue();
}
private int getValueAsIntWithDefault(final int tag, Integer defaultValue) throws IIOException {
return getValueAsIntWithDefault(tag, null, defaultValue);
}
private int getValueAsInt(final int tag, String tagName) throws IIOException {
return getValueAsIntWithDefault(tag, tagName, null);
}
@Override
public int getWidth(int imageIndex) throws IOException {
readIFD(imageIndex);
return getValueAsInt(TIFF.TAG_IMAGE_WIDTH, "ImageWidth");
}
@Override
public int getHeight(int imageIndex) throws IOException {
readIFD(imageIndex);
return getValueAsInt(TIFF.TAG_IMAGE_HEIGHT, "ImageHeight");
}
@Override
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
readIFD(imageIndex);
int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR);
int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXELS, 1);
int bitsPerSample = getBitsPerSample();
int dataType = bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
// Read embedded cs
ICC_Profile profile = getICCProfile();
ColorSpace cs;
switch (interpretation) {
// TIFF 6.0 baseline
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
// WhiteIsZero
// NOTE: We handle this by inverting the values when reading, as Java has no ColorModel that easily supports this.
// TODO: Consider returning null?
case TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO:
// BlackIsZero
// Gray scale or B/W
switch (samplesPerPixel) {
case 1:
// TIFF 6.0 Spec says: 1, 4 or 8 for baseline (1 for bi-level, 4/8 for gray)
// ImageTypeSpecifier supports only 1, 2, 4, 8 or 16 bits, we'll go with that for now
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_GRAY) : ColorSpaces.createColorSpace(profile);
if (cs == ColorSpace.getInstance(ColorSpace.CS_GRAY) && (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16)) {
return ImageTypeSpecifier.createGrayscale(bitsPerSample, dataType, false);
}
else if (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0}, dataType, false, false);
}
default:
throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for Bi-level/Gray TIFF (expected 1/1, 1/2, 1/4, 1/8 or 1/16): %d/%d", samplesPerPixel, bitsPerSample));
}
case TIFFExtension.PHOTOMETRIC_YCBCR:
// JPEG reader will handle YCbCr to RGB for us, we'll have to do it ourselves if not JPEG...
// TODO: Handle YCbCrSubsampling (up-scaler stream, or read data as-is + up-sample (sub-)raster after read? Apply smoothing?)
// TODO: We might want to handle USHORT_565 type, and allow different samplesPerPixel in that case especially
case TIFFBaseline.PHOTOMETRIC_RGB:
// RGB
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
switch (samplesPerPixel) {
case 3:
if (bitsPerSample == 8 || bitsPerSample == 16) {
switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY:
if (bitsPerSample == 8 && cs.isCS_sRGB()) {
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
}
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2}, dataType, false, false);
case TIFFExtension.PLANARCONFIG_PLANAR:
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, dataType, false, false);
}
}
case 4:
// TODO: Consult ExtraSamples!
if (bitsPerSample == 8 || bitsPerSample == 16) {
switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY:
if (bitsPerSample == 8 && cs.isCS_sRGB()) {
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
}
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, false, false);
case TIFFExtension.PLANARCONFIG_PLANAR:
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
}
}
// TODO: More samples might be ok, if multiple alpha or unknown samples
default:
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
}
case TIFFBaseline.PHOTOMETRIC_PALETTE:
// Palette
if (samplesPerPixel != 1) {
throw new IIOException("Bad SamplesPerPixel value for Palette TIFF (expected 1): " + samplesPerPixel);
}
else if (bitsPerSample <= 0 || bitsPerSample > 16) {
throw new IIOException("Bad BitsPerSample value for Palette TIFF (expected <= 16): " + bitsPerSample);
}
Entry colorMap = currentIFD.getEntryById(TIFF.TAG_COLOR_MAP);
if (colorMap == null) {
throw new IIOException("Missing ColorMap for Palette TIFF");
}
int[] cmapShort = (int[]) colorMap.getValue();
int[] cmap = new int[colorMap.valueCount() / 3];
// All reds, then greens, and finally blues
for (int i = 0; i < cmap.length; i++) {
cmap[i] = (cmapShort[i ] / 256) << 16
| (cmapShort[i + cmap.length] / 256) << 8
| (cmapShort[i + 2 * cmap.length] / 256);
}
IndexColorModel icm = new IndexColorModel(bitsPerSample, cmap.length, cmap, 0, false, -1, dataType);
return IndexedImageTypeSpecifier.createFromIndexColorModel(icm);
case TIFFExtension.PHOTOMETRIC_SEPARATED:
// Separated (CMYK etc)
cs = profile == null ? ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK) : ColorSpaces.createColorSpace(profile);
switch (samplesPerPixel) {
case 4:
// TODO: Consult the 332/InkSet (1=CMYK, 2=Not CMYK; see InkNames), 334/NumberOfInks (def=4) and optionally 333/InkNames
if (bitsPerSample == 8 || bitsPerSample == 16) {
switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY:
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, false, false);
case TIFFExtension.PLANARCONFIG_PLANAR:
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
}
}
// TODO: More samples might be ok, if multiple alpha or unknown samples, consult ExtraSamples
default:
throw new IIOException(
String.format("Unsupported TIFF SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8 or 4/16): %d/%s", samplesPerPixel, bitsPerSample)
);
}
case TIFFBaseline.PHOTOMETRIC_MASK:
// Transparency mask
// TODO: Known extensions
throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation);
default:
throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + interpretation);
}
}
private int getBitsPerSample() throws IIOException {
long[] value = getValueAsLongArray(TIFF.TAG_BITS_PER_SAMPLE, "BitsPerSample", false);
if (value == null || value.length == 0) {
return 1;
}
else {
int bitsPerSample = (int) value[0];
if (value.length > 1) {
for (long bps : value) {
if (bps != bitsPerSample) {
throw new IIOException("Varying BitsPerSample not supported: " + Arrays.toString(value));
}
}
}
return bitsPerSample;
}
}
@Override
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
readIFD(imageIndex);
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
List<ImageTypeSpecifier> specs = new ArrayList<ImageTypeSpecifier>();
// TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc
// TODO: Planar to chunky by default
specs.add(rawType);
return specs.iterator();
}
@Override
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
readIFD(imageIndex);
int width = getWidth(imageIndex);
int height = getHeight(imageIndex);
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
checkReadParamBandSettings(param, rawType.getNumBands(), destination.getSampleModel().getNumBands());
final Rectangle source = new Rectangle();
final Rectangle dest = new Rectangle();
computeRegions(param, width, height, destination, source, dest);
WritableRaster raster = destination.getRaster();
final int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
final int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
final int predictor = getValueAsIntWithDefault(TIFF.TAG_PREDICTOR, 1);
final int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFBaseline.PLANARCONFIG_CHUNKY);
final int numBands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? 1 : raster.getNumBands();
// NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip
// Strips are top/down, tiles are left/right, top/down
int stripTileWidth = width;
int stripTileHeight = getValueAsIntWithDefault(TIFF.TAG_ROWS_PER_STRIP, height);
long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false);
long[] stripTileByteCounts;
if (stripTileOffsets != null) {
stripTileByteCounts = getValueAsLongArray(TIFF.TAG_TILE_BYTE_COUNTS, "TileByteCounts", false);
if (stripTileByteCounts == null) {
processWarningOccurred("Missing TileByteCounts for tiled TIFF with compression: " + compression);
}
stripTileWidth = getValueAsInt(TIFF.TAG_TILE_WIDTH, "TileWidth");
stripTileHeight = getValueAsInt(TIFF.TAG_TILE_HEIGTH, "TileHeight");
}
else {
stripTileOffsets = getValueAsLongArray(TIFF.TAG_STRIP_OFFSETS, "StripOffsets", true);
stripTileByteCounts = getValueAsLongArray(TIFF.TAG_STRIP_BYTE_COUNTS, "StripByteCounts", false);
if (stripTileByteCounts == null) {
processWarningOccurred("Missing 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".
stripTileWidth = getValueAsIntWithDefault(TIFF.TAG_TILE_WIDTH, "TileWidth", stripTileWidth);
stripTileHeight = getValueAsIntWithDefault(TIFF.TAG_TILE_HEIGTH, "TileHeight", stripTileHeight);
}
int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth;
int tilesDown = (height + stripTileHeight - 1) / stripTileHeight;
WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
int row = 0;
// Read data
processImageStarted(imageIndex);
switch (compression) {
// TIFF Baseline
case TIFFBaseline.COMPRESSION_NONE:
// No compression
case TIFFExtension.COMPRESSION_DEFLATE:
// 'PKZIP-style' Deflate
case TIFFBaseline.COMPRESSION_PACKBITS:
// PackBits
case TIFFExtension.COMPRESSION_LZW:
// LZW
case TIFFExtension.COMPRESSION_ZLIB:
// 'Adobe-style' Deflate
// General uncompressed/compressed reading
for (int y = 0; y < tilesDown; y++) {
int col = 0;
int rowsInTile = Math.min(stripTileHeight, height - row);
for (int x = 0; x < tilesAcross; x++) {
int colsInTile = Math.min(stripTileWidth, width - col);
int i = y * tilesAcross + x;
imageInput.seek(stripTileOffsets[i]);
DataInput input;
if (compression == TIFFBaseline.COMPRESSION_NONE) {
input = imageInput;
}
else {
InputStream adapter = stripTileByteCounts != null
? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
: IIOUtil.createStreamAdapter(imageInput);
// According to the spec, short/long/etc should follow order of containing stream
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
? new DataInputStream(createDecoderInputStream(compression, adapter))
: new LittleEndianDataInputStream(createDecoderInputStream(compression, adapter));
}
// Read a full strip/tile
readStripTileData(rowRaster, interpretation, predictor, raster, numBands, col, row, colsInTile, rowsInTile, input);
if (abortRequested()) {
break;
}
col += colsInTile;
}
processImageProgress(100f * row / (float) height);
if (abortRequested()) {
processReadAborted();
break;
}
row += rowsInTile;
}
break;
case TIFFExtension.COMPRESSION_JPEG:
// JPEG ('new-style' JPEG)
// TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
ImageReader jpegReader = new JPEGImageReader(getOriginatingProvider());
JPEGImageReadParam jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
// SOI, DQT, DHT, (optional markers that we ignore)..., EOI
Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
byte[] tablesValue = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
if (tablesValue != null) {
// TODO: Work this out...
// Whatever values I pass the reader as the read param, it never gets the same quality as if
// I just invoke jpegReader.getStreamMetadata...
// Might have something to do with subsampling?
// How do we pass the chroma-subsampling parameter from the TIFF structure to the JPEG reader?
jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
// NOTE: This initializes the tables AND MORE for the reader (as if by magic).
// This is probably a bug, as later setInput calls should clear/override the tables
// However, it would be extremely convenient, not having to actually fiddle with the stream meta data (as below)
/*IIOMetadata streamMetadata = */jpegReader.getStreamMetadata();
/*
IIOMetadataNode root = (IIOMetadataNode) streamMetadata.getAsTree(streamMetadata.getNativeMetadataFormatName());
NodeList dqt = root.getElementsByTagName("dqt");
NodeList dqtables = ((IIOMetadataNode) dqt.item(0)).getElementsByTagName("dqtable");
JPEGQTable[] qTables = new JPEGQTable[dqtables.getLength()];
for (int i = 0; i < dqtables.getLength(); i++) {
qTables[i] = (JPEGQTable) ((IIOMetadataNode) dqtables.item(i)).getUserObject();
System.err.println("qTables: " + qTables[i]);
}
List<JPEGHuffmanTable> acHTables = new ArrayList<JPEGHuffmanTable>();
List<JPEGHuffmanTable> dcHTables = new ArrayList<JPEGHuffmanTable>();
NodeList dht = root.getElementsByTagName("dht");
for (int i = 0; i < dht.getLength(); i++) {
NodeList dhtables = ((IIOMetadataNode) dht.item(i)).getElementsByTagName("dhtable");
for (int j = 0; j < dhtables.getLength(); j++) {
System.err.println("dhtables.getLength(): " + dhtables.getLength());
IIOMetadataNode dhtable = (IIOMetadataNode) dhtables.item(j);
JPEGHuffmanTable userObject = (JPEGHuffmanTable) dhtable.getUserObject();
if ("0".equals(dhtable.getAttribute("class"))) {
dcHTables.add(userObject);
}
else {
acHTables.add(userObject);
}
}
}
JPEGHuffmanTable[] dcTables = dcHTables.toArray(new JPEGHuffmanTable[dcHTables.size()]);
JPEGHuffmanTable[] acTables = acHTables.toArray(new JPEGHuffmanTable[acHTables.size()]);
*/
// JPEGTables tables = new JPEGTables(new ByteArrayImageInputStream(tablesValue));
// JPEGQTable[] qTables = tables.getQTables();
// JPEGHuffmanTable[] dcTables = tables.getDCHuffmanTables();
// JPEGHuffmanTable[] acTables = tables.getACHuffmanTables();
// System.err.println("qTables: " + Arrays.toString(qTables));
// System.err.println("dcTables: " + Arrays.toString(dcTables));
// System.err.println("acTables: " + Arrays.toString(acTables));
// jpegParam.setDecodeTables(qTables, dcTables, acTables);
}
else {
processWarningOccurred("Missing JPEGTables for TIFF with compression: 7 (JPEG)");
// ...and the JPEG reader will probably choke on missing tables...
}
for (int y = 0; y < tilesDown; y++) {
int col = 0;
int rowsInTile = Math.min(stripTileHeight, height - row);
for (int x = 0; x < tilesAcross; x++) {
int i = y * tilesAcross + x;
int colsInTile = Math.min(stripTileWidth, width - col);
imageInput.seek(stripTileOffsets[i]);
SubImageInputStream subStream = new SubImageInputStream(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE);
try {
jpegReader.setInput(subStream);
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
jpegParam.setDestinationOffset(new Point(col, row));
jpegParam.setDestination(destination);
// TODO: This works only if Gray/YCbCr/RGB, not CMYK...
jpegReader.read(0, jpegParam);
}
finally {
subStream.close();
}
if (abortRequested()) {
break;
}
col += colsInTile;
}
processImageProgress(100f * row / (float) height);
if (abortRequested()) {
processReadAborted();
break;
}
row += rowsInTile;
}
break;
case TIFFBaseline.COMPRESSION_CCITT_HUFFMAN:
// CCITT modified Huffman
// Additionally, the specification defines these values as part of the TIFF extensions:
case TIFFExtension.COMPRESSION_CCITT_T4:
// CCITT Group 3 fax encoding
case TIFFExtension.COMPRESSION_CCITT_T6:
// CCITT Group 4 fax encoding
case TIFFExtension.COMPRESSION_OLD_JPEG:
// JPEG ('old-style' JPEG, later overridden in Technote2)
throw new IIOException("Unsupported TIFF Compression value: " + compression);
default:
throw new IIOException("Unknown TIFF Compression value: " + compression);
}
processImageComplete();
return destination;
}
private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor,
final WritableRaster raster, final int numBands, final int col, final int startRow,
final int colsInStrip, final int rowsInStrip, final DataInput input)
throws IOException {
switch (rowRaster.getTransferType()) {
case DataBuffer.TYPE_BYTE:
byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
for (int j = 0; j < rowsInStrip; j++) {
int row = startRow + j;
// input.readFully(rowData);
for (int k = 0; k < rowData.length; k++) {
try {
rowData[k] = input.readByte();
}
catch (IOException e) {
Arrays.fill(rowData, k, rowData.length, (byte) -1);
System.err.printf("Unexpected EOF or bad data at [%d %d]\n", col + k, row);
break;
}
}
unPredict(predictor, colsInStrip, 1, numBands, rowData);
normalizeBlack(interpretation, rowData);
if (colsInStrip == rowRaster.getWidth()) {
raster.setDataElements(col, row, rowRaster);
}
else {
raster.setDataElements(col, row, rowRaster.createChild(0, 0, colsInStrip, 1, 0, 0, null));
}
}
break;
case DataBuffer.TYPE_USHORT:
short [] rowDataShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
for (int j = 0; j < rowsInStrip; j++) {
int row = startRow + j;
for (int k = 0; k < rowDataShort.length; k++) {
rowDataShort[k] = input.readShort();
}
// TODO: Not sure how this works for USHORT... unpredict on byte level? In that case, we'll have to rewrite...
unPredict(predictor, colsInStrip, 1, numBands, rowDataShort);
normalizeBlack(interpretation, rowDataShort);
if (colsInStrip == rowRaster.getWidth()) {
raster.setDataElements(col, row, rowRaster);
}
else {
raster.setDataElements(col, row, rowRaster.createChild(0, 0, colsInStrip, 1, 0, 0, null));
}
}
break;
case DataBuffer.TYPE_INT:
int [] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
for (int j = 0; j < rowsInStrip; j++) {
int row = startRow + j;
for (int k = 0; k < rowDataInt.length; k++) {
rowDataInt[k] = input.readInt();
}
// TODO: Not sure how this works for USHORT... unpredict on byte level? In that case, we'll have to rewrite...
// unPredict(predictor, colsInStrip, 1, numBands, rowDataInt);
normalizeBlack(interpretation, rowDataInt);
if (colsInStrip == rowRaster.getWidth()) {
raster.setDataElements(col, row, rowRaster);
}
else {
raster.setDataElements(col, row, rowRaster.createChild(0, 0, colsInStrip, 1, 0, 0, null));
}
}
break;
}
}
private void normalizeBlack(int photometricInterpretation, short[] data) {
if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
// Inverse values
for (int i = 0; i < data.length; i++) {
data[i] = (short) (0xffff - data[i] & 0xffff);
}
}
}
private void normalizeBlack(int photometricInterpretation, int[] data) {
if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
// Inverse values
for (int i = 0; i < data.length; i++) {
data[i] = (0xffffffff - data[i]);
}
}
}
private void normalizeBlack(int photometricInterpretation, byte[] data) {
if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
// Inverse values
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (0xff - data[i] & 0xff);
}
}
}
@SuppressWarnings("UnusedParameters")
private void unPredict(final int predictor, int scanLine, int rows, int bands, int[] data) throws IIOException {
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
switch (predictor) {
case TIFFExtension.PREDICTOR_NONE:
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
// TODO: Implement
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
}
}
@SuppressWarnings("UnusedParameters")
private void unPredict(final int predictor, int scanLine, int rows, int bands, short[] data) throws IIOException {
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
switch (predictor) {
case TIFFExtension.PREDICTOR_NONE:
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
// TODO: Implement
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
}
}
private void unPredict(final int predictor, int scanLine, int rows, final int bands, byte[] data) throws IIOException {
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
switch (predictor) {
case TIFFExtension.PREDICTOR_NONE:
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
for (int y = 0; y < rows; y++) {
for (int x = 1; x < scanLine; x++) {
// TODO: For planar data (PlanarConfiguration == 2), treat as bands == 1
for (int b = 0; b < bands; b++) {
int off = y * scanLine + x;
data[off * bands + b] = (byte) (data[(off - 1) * bands + b] + data[off * bands + b]);
}
}
}
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
}
}
private InputStream createDecoderInputStream(final int compression, final InputStream stream) throws IOException {
switch (compression) {
case TIFFBaseline.COMPRESSION_PACKBITS:
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
case TIFFExtension.COMPRESSION_LZW:
return new DecoderStream(stream, new LZWDecoder(LZWDecoder.isOldBitReversedStream(stream)), 1024);
case TIFFExtension.COMPRESSION_ZLIB:
return new InflaterInputStream(stream, new Inflater(), 1024);
case TIFFExtension.COMPRESSION_DEFLATE:
return new ZipInputStream(stream);
default:
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
}
}
private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException {
Entry entry = currentIFD.getEntryById(tag);
if (entry == null) {
if (required) {
throw new IIOException("Missing TIFF tag " + tagName);
}
return null;
}
long[] value;
if (entry.valueCount() == 1) {
// For single entries, this will be a boxed type
value = new long[] {((Number) entry.getValue()).longValue()};
}
else if (entry.getValue() instanceof short[]) {
short[] shorts = (short[]) entry.getValue();
value = new long[shorts.length];
for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
value[i] = shorts[i];
}
}
else if (entry.getValue() instanceof int[]) {
int[] ints = (int[]) entry.getValue();
value = new long[ints.length];
for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
value[i] = ints[i];
}
}
else if (entry.getValue() instanceof long[]) {
value = (long[]) entry.getValue();
}
else {
throw new IIOException(String.format("Unsupported %s type: %s (%s)", tagName, entry.getTypeName(), entry.getValue().getClass()));
}
return value;
}
public ICC_Profile getICCProfile() {
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
if (entry == null) {
return null;
}
byte[] value = (byte[]) entry.getValue();
return ICC_Profile.getInstance(value);
}
public static void main(final String[] args) throws IOException {
for (final String arg : args) {
File file = new File(arg);
ImageInputStream input = ImageIO.createImageInputStream(file);
if (input == null) {
System.err.println("Could not read file: " + file);
continue;
}
deregisterOSXTIFFImageReaderSpi();
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (!readers.hasNext()) {
System.err.println("No reader for: " + file);
continue;
}
ImageReader reader = readers.next();
System.err.println("Reading using: " + reader);
reader.addIIOReadWarningListener(new IIOReadWarningListener() {
public void warningOccurred(ImageReader source, String warning) {
System.err.println("Warning: " + arg + ": " + warning);
}
});
reader.addIIOReadProgressListener(new ProgressListenerBase() {
private static final int MAX_W = 78;
int lastProgress = 0;
@Override
public void imageStarted(ImageReader source, int imageIndex) {
System.out.print("[");
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
int steps = ((int) (percentageDone * MAX_W) / 100);
for (int i = lastProgress; i < steps; i++) {
System.out.print(".");
}
System.out.flush();
lastProgress = steps;
}
@Override
public void imageComplete(ImageReader source) {
for (int i = lastProgress; i < MAX_W; i++) {
System.out.print(".");
}
System.out.println("]");
}
});
reader.setInput(input);
try {
ImageReadParam param = reader.getDefaultReadParam();
int numImages = reader.getNumImages(true);
for (int imageNo = 0; imageNo < numImages; imageNo++) {
// if (args.length > 1) {
// int sub = Integer.parseInt(args[1]);
// int sub = 4;
// param.setSourceSubsampling(sub, sub, 0, 0);
// }
long start = System.currentTimeMillis();
BufferedImage image = reader.read(imageNo, param);
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
// System.err.println("image: " + image);
// File tempFile = File.createTempFile("lzw-", ".bin");
// byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
// FileOutputStream stream = new FileOutputStream(tempFile);
// try {
// FileUtil.copy(new ByteArrayInputStream(data, 45 * image.getWidth() * 3, 5 * image.getWidth() * 3), stream);
//
// showIt(image.getSubimage(0, 45, image.getWidth(), 5), tempFile.getAbsolutePath());
// }
// finally {
// stream.close();
// }
//
// System.err.println("tempFile: " + tempFile.getAbsolutePath());
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
//
// int maxW = 800;
// int maxH = 800;
//
// if (image.getWidth() > maxW || image.getHeight() > maxH) {
// start = System.currentTimeMillis();
// float aspect = reader.getAspectRatio(0);
// if (aspect >= 1f) {
// image = ImageUtil.createResampled(image, maxW, Math.round(maxW / aspect), Image.SCALE_DEFAULT);
// }
// else {
// image = ImageUtil.createResampled(image, Math.round(maxH * aspect), maxH, Image.SCALE_DEFAULT);
// }
// // System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
// }
showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
try {
int numThumbnails = reader.getNumThumbnails(0);
for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
// System.err.println("thumbnail: " + thumbnail);
showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
}
}
catch (IIOException e) {
System.err.println("Could not read thumbnails: " + e.getMessage());
e.printStackTrace();
}
}
}
catch (Throwable t) {
System.err.println(file);
t.printStackTrace();
}
finally {
input.close();
}
}
}
private static void deregisterOSXTIFFImageReaderSpi() {
IIORegistry registry = IIORegistry.getDefaultInstance();
Iterator<ImageReaderSpi> providers = registry.getServiceProviders(ImageReaderSpi.class, new ServiceRegistry.Filter() {
public boolean filter(Object provider) {
return provider.getClass().getName().equals("com.sun.imageio.plugins.tiff.TIFFImageReaderSpi");
}
}, false);
while (providers.hasNext()) {
ImageReaderSpi next = providers.next();
registry.deregisterServiceProvider(next);
}
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2012, 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.tiff;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Locale;
/**
* TIFFImageReaderSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: TIFFImageReaderSpi.java,v 1.0 08.05.12 15:14 haraldk Exp$
*/
public class TIFFImageReaderSpi extends ImageReaderSpi {
// TODO: Should we make sure we register (order) before the com.sun.imageio thing (that isn't what is says) provided by Apple?
/**
* Creates a {@code TIFFImageReaderSpi}.
*/
public TIFFImageReaderSpi() {
this(IIOUtil.getProviderInfo(TIFFImageReaderSpi.class));
}
private TIFFImageReaderSpi(final ProviderInfo providerInfo) {
super(
providerInfo.getVendorName(),
providerInfo.getVersion(),
new String[]{"tiff", "TIFF"},
new String[]{"tif", "tiff"},
new String[]{
"image/tiff", "image/x-tiff"
},
"com.twelvemkonkeys.imageio.plugins.tiff.TIFFImageReader",
STANDARD_INPUT_TYPE,
// new String[]{"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"},
null,
true, // supports standard stream metadata
null, null, // native stream format name and class
null, null, // extra stream formats
true, // supports standard image metadata
null, null,
null, null // extra image metadata formats
);
}
public boolean canDecodeInput(final Object pSource) throws IOException {
if (!(pSource instanceof ImageInputStream)) {
return false;
}
ImageInputStream stream = (ImageInputStream) pSource;
stream.mark();
try {
byte[] bom = new byte[2];
stream.readFully(bom);
ByteOrder originalOrder = stream.getByteOrder();
try {
if (bom[0] == 'I' && bom[1] == 'I') {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
}
else if (bom[0] == 'M' && bom[1] == 'M') {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
}
else {
return false;
}
// TODO: BigTiff uses version 43 instead of TIFF's 42, and header is slightly different, see
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
int magic = stream.readUnsignedShort();
return magic == TIFF.TIFF_MAGIC;
}
finally {
stream.setByteOrder(originalOrder);
}
}
finally {
stream.reset();
}
}
public TIFFImageReader createReaderInstance(final Object pExtension) {
return new TIFFImageReader(this);
}
public String getDescription(final Locale pLocale) {
return "Aldus/Adobe Tagged Image File Format (TIFF) image reader";
}
}

View File

@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi

View File

@ -0,0 +1,122 @@
package com.twelvemonkeys.imageio.plugins.tiff;/*
* Copyright (c) 2012, 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.
*/
import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderAbstractTestCase;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.Encoder;
import org.junit.Ignore;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.*;
/**
* LZWDecoderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: LZWDecoderTest.java,v 1.0 08.05.12 23:44 haraldk Exp$
*/
public class LZWDecoderTest extends DecoderAbstractTestCase {
@Test
public void testIsOldBitReversedStreamTrue() throws IOException {
assertTrue(LZWDecoder.isOldBitReversedStream(getClass().getResourceAsStream("/lzw/lzw-short.bin")));
}
@Test
public void testIsOldBitReversedStreamFalse() throws IOException {
assertFalse(LZWDecoder.isOldBitReversedStream(getClass().getResourceAsStream("/lzw/lzw-long.bin")));
}
@Test
public void testShortBitReversedStream() throws IOException {
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short.bin"), new LZWDecoder(true), 128);
InputStream unpacked = new ByteArrayInputStream(new byte[512 * 3 * 5]); // Should be all 0's
assertSameStreamContents(unpacked, stream);
}
@Ignore("Known issue")
@Test
public void testShortBitReversedStreamLine45To49() throws IOException {
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short-45-49.bin"), new LZWDecoder(true), 128);
InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-short-45-49.bin");
assertSameStreamContents(unpacked, stream);
}
@Test
public void testLongStream() throws IOException {
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024);
InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-long.bin");
assertSameStreamContents(unpacked, stream);
}
private void assertSameStreamContents(InputStream expected, InputStream actual) {
int count = 0;
int data;
try {
// long toSkip = 3800;
// while ((toSkip -= expected.skip(toSkip)) > 0) {
// }
// toSkip = 3800;
// while ((toSkip -= actual.skip(toSkip)) > 0) {
// }
while ((data = actual.read()) != -1) {
count++;
assertEquals(String.format("Incorrect data at offset 0x%04x", count), expected.read(), data);
}
assertEquals(-1, data);
assertEquals(expected.read(), actual.read());
}
catch (IOException e) {
fail(String.format("Bad/corrupted data or EOF at offset 0x%04x: %s", count, e.getMessage()));
}
}
@Override
public Decoder createDecoder() {
return new LZWDecoder();
}
@Override
public Encoder createCompatibleEncoder() {
// Don't have an encoder yet
return null;
}
}

View File

@ -0,0 +1,91 @@
package com.twelvemonkeys.imageio.plugins.tiff;/*
* Copyright (c) 2012, 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.
*/
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.util.Arrays;
import java.util.List;
/**
* TIFFImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: TIFFImageReaderTest.java,v 1.0 08.05.12 15:25 haraldk Exp$
*/
public class TIFFImageReaderTest extends ImageReaderAbstractTestCase<TIFFImageReader> {
private static final TIFFImageReaderSpi SPI = new TIFFImageReaderSpi();
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/tiff/balloons.tif"), new Dimension(640, 480)), // RGB, uncompressed
new TestData(getClassLoaderResource("/tiff/sm_colors_pb.tif"), new Dimension(64, 64)), // RGB, PackBits compressed
new TestData(getClassLoaderResource("/tiff/sm_colors_tile.tif"), new Dimension(64, 64)), // RGB, uncompressed, tiled
new TestData(getClassLoaderResource("/tiff/sm_colors_pb_tile.tif"), new Dimension(64, 64)), // RGB, PackBits compressed, tiled
new TestData(getClassLoaderResource("/tiff/galaxy.tif"), new Dimension(965, 965)), // RGB, LZW compressed
new TestData(getClassLoaderResource("/tiff/bali.tif"), new Dimension(725, 489)), // Palette-based, LZW compressed
new TestData(getClassLoaderResource("/tiff/f14.tif"), new Dimension(640, 480)), // Gray, uncompressed
new TestData(getClassLoaderResource("/tiff/marbles.tif"), new Dimension(1419, 1001)), // RGB, LZW compressed w/predictor
new TestData(getClassLoaderResource("/tiff/chifley_logo.tif"), new Dimension(591, 177)) // CMYK, uncompressed
);
}
@Override
protected ImageReaderSpi createProvider() {
return SPI;
}
@Override
protected Class<TIFFImageReader> getReaderClass() {
return TIFFImageReader.class;
}
@Override
protected TIFFImageReader createReader() {
return SPI.createReaderInstance(null);
}
@Override
protected List<String> getFormatNames() {
return Arrays.asList("tiff", "TIFF");
}
@Override
protected List<String> getSuffixes() {
return Arrays.asList("tif", "tiff");
}
@Override
protected List<String> getMIMETypes() {
return Arrays.asList("image/tiff");
}
}

Binary file not shown.

Binary file not shown.

View File

@ -10,7 +10,6 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>TwelveMonkeys :: ImageIO</name> <name>TwelveMonkeys :: ImageIO</name>
@ -39,6 +38,7 @@
<module>imageio-pict</module> <module>imageio-pict</module>
<module>imageio-psd</module> <module>imageio-psd</module>
<module>imageio-thumbsdb</module> <module>imageio-thumbsdb</module>
<module>imageio-tiff</module>
<!-- Wrappers for 3rd party libs --> <!-- Wrappers for 3rd party libs -->
<module>imageio-batik</module> <module>imageio-batik</module>