diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java new file mode 100644 index 00000000..372e5758 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java @@ -0,0 +1,55 @@ +package com.twelvemonkeys.imageio.stream; + +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.ImageOutputStreamImpl; +import java.io.IOException; + +/** + * SubImageOutputStream. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: SubImageOutputStream.java,v 1.0 30/03/15 harald.kuhr Exp$ + */ +public class SubImageOutputStream extends ImageOutputStreamImpl { + private final ImageOutputStream stream; + + public SubImageOutputStream(final ImageOutputStream stream) { + this.stream = stream; + } + + @Override + public void write(int b) throws IOException { + stream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + stream.write(b, off, len); + } + + @Override + public int read() throws IOException { + return stream.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return stream.read(b, off, len); + } + + @Override + public boolean isCached() { + return stream.isCached(); + } + + @Override + public boolean isCachedMemory() { + return stream.isCachedMemory(); + } + + @Override + public boolean isCachedFile() { + return stream.isCachedFile(); + } +} diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java index 69eba0c1..637ea6f3 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java @@ -39,16 +39,20 @@ import java.util.Locale; * @version $Id: TIFFImageWriteParam.java,v 1.0 18.09.13 12:47 haraldk Exp$ */ public final class TIFFImageWriteParam extends ImageWriteParam { - // TODO: Support no compression (None/1) - // TODO: Support ZLIB (/Deflate) compression (8) - // TODO: Support PackBits compression (32773) - // TODO: Support JPEG compression (7) - // TODO: Support CCITT Modified Huffman compression (2) - // TODO: Support LZW compression (5)? + // TODO: Support CCITT Modified Huffman compression (2) BASELINE!! + // TODO: Support CCITT T.4 (3) + // TODO: Support CCITT T.6 (4) // TODO: Support JBIG compression via ImageIO plugin/delegate? // TODO: Support JPEG2000 compression via ImageIO plugin/delegate? // TODO: Support tiling - // TODO: Support predictor. See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64. + // TODO: Support OPTIONAL predictor. See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64. + + // DONE: + // Support no compression (None/1) + // Support ZLIB (/Deflate) compression (8) + // Support PackBits compression (32773) + // Support LZW compression (5)? + // Support JPEG compression (7) TIFFImageWriteParam() { this(Locale.getDefault()); @@ -59,7 +63,12 @@ public final class TIFFImageWriteParam extends ImageWriteParam { // NOTE: We use the same spelling/casing as the JAI equivalent to be as compatible as possible // See: http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/TIFFImageWriteParam.html - compressionTypes = new String[] {"None", /* "CCITT RLE", "CCITT T.4", "CCITT T.6", */ "LZW", "JPEG", "ZLib", "PackBits", "Deflate", /* "EXIF JPEG" */ }; + compressionTypes = new String[] { + "None", + null, null, null,/* "CCITT RLE", "CCITT T.4", "CCITT T.6", */ + "LZW", "JPEG", "ZLib", "PackBits", "Deflate", + null/* "EXIF JPEG" */ // A well-defined form of "Old-style JPEG", no tables/process, only 513 (offset) and 514 (length) + }; compressionType = compressionTypes[0]; canWriteCompressed = true; } @@ -102,6 +111,9 @@ public final class TIFFImageWriteParam extends ImageWriteParam { else if (param.getCompressionType().equals("JPEG")) { return TIFFExtension.COMPRESSION_JPEG; } +// else if (param.getCompressionType().equals("EXIF JPEG")) { +// return TIFFExtension.COMPRESSION_OLD_JPEG; +// } throw new IllegalArgumentException(String.format("Unsupported compression type: %s", param.getCompressionType())); } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java index 93eac986..9f672cfb 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java @@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter; import com.twelvemonkeys.imageio.metadata.exif.Rational; import com.twelvemonkeys.imageio.metadata.exif.TIFF; +import com.twelvemonkeys.imageio.stream.SubImageOutputStream; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.io.enc.EncoderStream; import com.twelvemonkeys.io.enc.PackBitsEncoder; @@ -157,10 +158,11 @@ public final class TIFFImageWriter extends ImageWriterBase { entries.add(new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, TIFFBaseline.EXTRASAMPLE_UNSPECIFIED)); } } + // Write compression field from param or metadata int compression = TIFFImageWriteParam.getCompressionType(param); entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression)); - // TODO: Let param control + // TODO: Let param control predictor switch (compression) { case TIFFExtension.COMPRESSION_ZLIB: case TIFFExtension.COMPRESSION_DEFLATE: @@ -169,7 +171,9 @@ public final class TIFFImageWriter extends ImageWriterBase { default: } - int photometric = getPhotometricInterpretation(colorModel); + int photometric = compression == TIFFExtension.COMPRESSION_JPEG ? + TIFFExtension.PHOTOMETRIC_YCBCR : + getPhotometricInterpretation(colorModel); entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric)); if (photometric == TIFFBaseline.PHOTOMETRIC_PALETTE && colorModel instanceof IndexColorModel) { @@ -179,9 +183,9 @@ public final class TIFFImageWriter extends ImageWriterBase { else { entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numComponents)); - // TODO: What is the default TIFF color space? + // Note: Assuming sRGB to be the default RGB interpretation ColorSpace colorSpace = colorModel.getColorSpace(); - if (colorSpace instanceof ICC_ColorSpace) { + if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB()) { entries.add(new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData())); } } @@ -228,8 +232,26 @@ public final class TIFFImageWriter extends ImageWriterBase { } // TODO: Create compressor stream per Tile/Strip - // Write image data - writeImageData(createCompressorStream(renderedImage, param), renderedImage, numComponents, bandOffsets, bitOffsets); + if (compression == TIFFExtension.COMPRESSION_JPEG) { + Iterator writers = ImageIO.getImageWritersByFormatName("JPEG"); + if (!writers.hasNext()) { + // This can only happen if someone deliberately uninstalled it + throw new IIOException("No JPEG ImageWriter found!"); + } + + ImageWriter jpegWriter = writers.next(); + try { + jpegWriter.setOutput(new SubImageOutputStream(imageOutput)); + jpegWriter.write(renderedImage); + } + finally { + jpegWriter.dispose(); + } + } + else { + // Write image data + writeImageData(createCompressorStream(renderedImage, param), renderedImage, numComponents, bandOffsets, bitOffsets); + } // TODO: Update IFD0-pointer, and write IFD if (compression != TIFFBaseline.COMPRESSION_NONE) { @@ -304,7 +326,8 @@ public final class TIFFImageWriter extends ImageWriterBase { output.length: 12600399 */ - // TODO: Use predictor only by default for -PackBits,- LZW and ZLib/Deflate, unless explicitly disabled (ImageWriteParam) + // Use predictor by default for LZW and ZLib/Deflate + // TODO: Unless explicitly disabled in TIFFImageWriteParam int compression = TIFFImageWriteParam.getCompressionType(param); OutputStream stream; @@ -321,7 +344,7 @@ public final class TIFFImageWriter extends ImageWriterBase { case TIFFExtension.COMPRESSION_ZLIB: case TIFFExtension.COMPRESSION_DEFLATE: - int deflateSetting = Deflater.BEST_SPEED; // This is consistent with default compression quality being 1.0 and 0 meaning max compression.... + int deflateSetting = Deflater.BEST_SPEED; // This is consistent with default compression quality being 1.0 and 0 meaning max compression... if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) { // TODO: Determine how to interpret compression quality... // Docs says: @@ -740,4 +763,5 @@ public final class TIFFImageWriter extends ImageWriterBase { TIFFImageReader.showIt(read, output.getName()); } + }