mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 11:05:29 -04:00
TMI-TIFF: Initial commit. Major work in progress. :-)
This commit is contained in:
parent
9492ed67f1
commit
98361194ea
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi
|
||||
com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi
|
||||
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi
|
||||
|
@ -1 +0,0 @@
|
||||
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
|
@ -90,6 +90,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "Orientation";
|
||||
case TIFF.TAG_SAMPLES_PER_PIXELS:
|
||||
return "SamplesPerPixels";
|
||||
case TIFF.TAG_ROWS_PER_STRIP:
|
||||
return "RowsPerStrip";
|
||||
case TIFF.TAG_X_RESOLUTION:
|
||||
return "XResolution";
|
||||
case TIFF.TAG_Y_RESOLUTION:
|
||||
@ -120,6 +122,10 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "YCbCrSubSampling";
|
||||
case TIFF.TAG_YCBCR_POSITIONING:
|
||||
return "YCbCrPositioning";
|
||||
case TIFF.TAG_COLOR_MAP:
|
||||
return "ColorMap";
|
||||
case TIFF.TAG_EXTRA_SAMPLES:
|
||||
return "ExtraSamples";
|
||||
|
||||
case EXIF.TAG_EXPOSURE_TIME:
|
||||
return "ExposureTime";
|
||||
|
@ -95,6 +95,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
EXIFEntry entry = readEntry(pInput);
|
||||
|
||||
if (entry == null) {
|
||||
// System.err.println("Expected: " + entryCount + " values, found only " + i);
|
||||
// TODO: Log warning?
|
||||
nextOffset = 0;
|
||||
break;
|
||||
@ -199,13 +200,13 @@ public final class EXIFReader extends MetadataReader {
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value instanceof Byte) {
|
||||
offset = ((Byte) value & 0xff);
|
||||
offset = (Byte) value & 0xff;
|
||||
}
|
||||
else if (value instanceof Short) {
|
||||
offset = ((Short) value & 0xffff);
|
||||
offset = (Short) value & 0xffff;
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
offset = ((Integer) value & 0xffffffffL);
|
||||
offset = (Integer) value & 0xffffffffL;
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
offset = (Long) value;
|
||||
@ -222,7 +223,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
int tagId = pInput.readUnsignedShort();
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@ -236,24 +237,28 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
if (type <= 0 || type > 13) {
|
||||
// Invalid tag, this is just for debugging
|
||||
System.err.printf("Bad EXIF data at offset: %08x\n", pInput.getStreamPosition() - 8l);
|
||||
System.err.println("tagId: " + tagId);
|
||||
long offset = pInput.getStreamPosition() - 8l;
|
||||
|
||||
System.err.printf("Bad EXIF");
|
||||
System.err.println("tagId: " + tagId + (tagId <= 0 ? "(INVALID)" : ""));
|
||||
System.err.println("type: " + type + " (INVALID)");
|
||||
System.err.println("count: " + count);
|
||||
|
||||
pInput.mark();
|
||||
pInput.seek(pInput.getStreamPosition() - 8);
|
||||
pInput.seek(offset);
|
||||
|
||||
try {
|
||||
byte[] bytes = new byte[8 + Math.max(20, count)];
|
||||
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 ? "[...]" : "");
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
int valueLength = getValueLength(type, count);
|
||||
@ -484,7 +489,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof byte[]) {
|
||||
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;
|
||||
|
||||
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();
|
||||
|
||||
int i;
|
||||
@ -513,7 +518,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
if (i > 0 ) {
|
||||
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) {
|
||||
builder.append(" ");
|
||||
|
@ -118,8 +118,11 @@ public interface TIFF {
|
||||
/// C. Tags relating to image data characteristics
|
||||
|
||||
int TAG_TRANSFER_FUNCTION = 301;
|
||||
int TAG_PREDICTOR = 317;
|
||||
int TAG_WHITE_POINT = 318;
|
||||
int TAG_PRIMARY_CHROMATICITIES = 319;
|
||||
int TAG_COLOR_MAP = 320;
|
||||
int TAG_EXTRA_SAMPLES = 338;
|
||||
int TAG_YCBCR_COEFFICIENTS = 529;
|
||||
int TAG_REFERENCE_BLACK_WHITE = 532;
|
||||
|
||||
@ -151,4 +154,11 @@ public interface TIFF {
|
||||
int TAG_MODI_PLAIN_TEXT = 37679;
|
||||
int TAG_MODI_OLE_PROPERTY_SET = 37680;
|
||||
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;
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ public interface JPEG {
|
||||
|
||||
/** Define Quantization Tables segment marker (DQT). */
|
||||
int DQT = 0xFFDB;
|
||||
/** Define Huffman Tables segment marker (DHT). */
|
||||
int DHT = 0xFFC4;
|
||||
|
||||
// App segment markers (APPn).
|
||||
int APP0 = 0xFFE0;
|
||||
|
@ -30,10 +30,12 @@ package com.twelvemonkeys.imageio.metadata.jpeg;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.plugins.jpeg.JPEGQTable;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
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/
|
||||
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));
|
||||
|
||||
// TODO: Determine lossless JPEG
|
||||
@ -188,8 +190,24 @@ public final class JPEGQuality {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static short[][] getQuantizationTables(List<JPEGSegment> dqtSegments) throws IOException {
|
||||
short[][] tables = new short[4][];
|
||||
public static JPEGQTable[] getQTables(final List<JPEGSegment> dqtSegments) throws IOException {
|
||||
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
|
||||
for (JPEGSegment segment : dqtSegments) {
|
||||
@ -223,7 +241,7 @@ public final class JPEGQuality {
|
||||
byte[] qtData = new byte[DCT_SIZE_2 * (bits + 1)];
|
||||
data.readFully(qtData);
|
||||
read += qtData.length;
|
||||
tables[num] = new short[DCT_SIZE_2];
|
||||
tables[num] = new int[DCT_SIZE_2];
|
||||
|
||||
// Expand (this is slightly inefficient)
|
||||
switch (bits) {
|
||||
|
@ -175,4 +175,19 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
|
||||
assertNotNull(exif);
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,10 @@ public class JPEGQualityTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQTables() {
|
||||
fail("Not implemented");
|
||||
}
|
||||
|
||||
private BufferedImage createTestImage() {
|
||||
BufferedImage image = new BufferedImage(90, 60, BufferedImage.TYPE_3BYTE_BGR);
|
||||
|
Binary file not shown.
@ -441,7 +441,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
// TODO: Could probably use the ZIPDecoder (DeflateDecoder) here..
|
||||
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...
|
||||
throw new IIOException("PSD with ZIP compression not supported");
|
||||
default:
|
||||
|
32
imageio/imageio-tiff/pom.xml
Normal file
32
imageio/imageio-tiff/pom.xml
Normal 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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -26,21 +26,34 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
/**
|
||||
* LZWEncoder.
|
||||
* <p/>
|
||||
* TIFFBaseline
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java#2 $
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFBaseline.java,v 1.0 08.05.12 16:43 haraldk Exp$
|
||||
*/
|
||||
final class LZWEncoder implements Encoder {
|
||||
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||
// TODO: Implement
|
||||
// TODO: We probably need a GIF specific subclass
|
||||
}
|
||||
interface TIFFBaseline {
|
||||
int COMPRESSION_NONE = 1;
|
||||
int COMPRESSION_CCITT_HUFFMAN = 2;
|
||||
int COMPRESSION_PACKBITS = 32773;
|
||||
|
||||
int PHOTOMETRIC_WHITE_IS_ZERO = 0;
|
||||
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;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -26,21 +26,24 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
/**
|
||||
* LZWDecoder.
|
||||
* <p/>
|
||||
* TIFFCustom
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java#2 $
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFCustom.java,v 1.0 10.05.12 17:35 haraldk Exp$
|
||||
*/
|
||||
final class LZWDecoder implements Decoder {
|
||||
public int decode(InputStream pStream, byte[] pBuffer) throws IOException {
|
||||
return 0; // TODO: Implement
|
||||
// TODO: We probably need a GIF specific subclass
|
||||
}
|
||||
interface TIFFCustom {
|
||||
int PHOTOMETRIC_LOGL = 32844;
|
||||
int PHOTOMETRIC_LOGLUV = 32845;
|
||||
|
||||
/** DNG: CFA (Color Filter Array)*/
|
||||
int PHOTOMETRIC_CFA = 32803;
|
||||
/** DNG: LinearRaw*/
|
||||
int PHOTOMETRIC_LINEAR_RAW = 34892;
|
||||
|
||||
int SAMPLEFORMAT_COMPLEXINT = 5;
|
||||
int SAMPLEFORMAT_COMPLEXIEEEFP = 6;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
BIN
imageio/imageio-tiff/src/test/resources/tiff/bali.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/bali.tif
Normal file
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/balloons.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/balloons.tif
Normal file
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/chifley_logo.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/chifley_logo.tif
Normal file
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/f14.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/f14.tif
Normal file
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/galaxy.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/galaxy.tif
Normal file
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/marbles.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/marbles.tif
Normal file
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb.tif
Normal file
Binary file not shown.
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/sm_colors_tile.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/sm_colors_tile.tif
Normal file
Binary file not shown.
@ -10,7 +10,6 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>TwelveMonkeys :: ImageIO</name>
|
||||
|
||||
@ -39,6 +38,7 @@
|
||||
<module>imageio-pict</module>
|
||||
<module>imageio-psd</module>
|
||||
<module>imageio-thumbsdb</module>
|
||||
<module>imageio-tiff</module>
|
||||
|
||||
<!-- Wrappers for 3rd party libs -->
|
||||
<module>imageio-batik</module>
|
||||
|
Loading…
x
Reference in New Issue
Block a user