mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-04-25 00:00:03 -04:00
TMI-TIFF: Initial commit. Major work in progress. :-)
This commit is contained in:
-189
@@ -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;
|
||||
}
|
||||
}
|
||||
-125
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-145
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-107
@@ -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
@@ -1,3 +1,2 @@
|
||||
com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi
|
||||
com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi
|
||||
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
|
||||
+6
@@ -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";
|
||||
|
||||
+17
-12
@@ -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(" ");
|
||||
|
||||
+10
@@ -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;
|
||||
}
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+22
-4
@@ -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) {
|
||||
|
||||
+15
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -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.
+1
-1
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
+157
@@ -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;
|
||||
}
|
||||
}
|
||||
+272
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* TIFFBaseline
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFBaseline.java,v 1.0 08.05.12 16:43 haraldk Exp$
|
||||
*/
|
||||
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;
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* TIFFCustom
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFCustom.java,v 1.0 10.05.12 17:35 haraldk Exp$
|
||||
*/
|
||||
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;
|
||||
}
|
||||
+93
@@ -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;
|
||||
}
|
||||
+990
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -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";
|
||||
}
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi
|
||||
+122
@@ -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;
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TIFFImageReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFImageReaderTest.java,v 1.0 08.05.12 15:25 haraldk Exp$
|
||||
*/
|
||||
public class TIFFImageReaderTest extends ImageReaderAbstractTestCase<TIFFImageReader> {
|
||||
|
||||
private static final TIFFImageReaderSpi SPI = new TIFFImageReaderSpi();
|
||||
|
||||
@Override
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/tiff/balloons.tif"), new Dimension(640, 480)), // RGB, uncompressed
|
||||
new TestData(getClassLoaderResource("/tiff/sm_colors_pb.tif"), new Dimension(64, 64)), // RGB, PackBits compressed
|
||||
new TestData(getClassLoaderResource("/tiff/sm_colors_tile.tif"), new Dimension(64, 64)), // RGB, uncompressed, tiled
|
||||
new TestData(getClassLoaderResource("/tiff/sm_colors_pb_tile.tif"), new Dimension(64, 64)), // RGB, PackBits compressed, tiled
|
||||
new TestData(getClassLoaderResource("/tiff/galaxy.tif"), new Dimension(965, 965)), // RGB, LZW compressed
|
||||
new TestData(getClassLoaderResource("/tiff/bali.tif"), new Dimension(725, 489)), // Palette-based, LZW compressed
|
||||
new TestData(getClassLoaderResource("/tiff/f14.tif"), new Dimension(640, 480)), // Gray, uncompressed
|
||||
new TestData(getClassLoaderResource("/tiff/marbles.tif"), new Dimension(1419, 1001)), // RGB, LZW compressed w/predictor
|
||||
new TestData(getClassLoaderResource("/tiff/chifley_logo.tif"), new Dimension(591, 177)) // CMYK, uncompressed
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return SPI;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<TIFFImageReader> getReaderClass() {
|
||||
return TIFFImageReader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TIFFImageReader createReader() {
|
||||
return SPI.createReaderInstance(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("tiff", "TIFF");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("tif", "tiff");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/tiff");
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user