/* * #%L * BSD implementations of Bio-Formats readers and writers * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. 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. * * 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 HOLDERS 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. * #L% */ package loci.formats.tiff; import java.io.IOException; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import loci.common.DataTools; import loci.common.enumeration.CodedEnum; import loci.common.enumeration.EnumException; import loci.formats.FormatException; import loci.formats.UnsupportedCompressionException; import loci.formats.codec.Codec; import loci.formats.codec.CodecOptions; import loci.formats.codec.JPEG2000Codec; import loci.formats.codec.JPEG2000CodecOptions; import loci.formats.codec.JPEGCodec; import loci.formats.codec.LZWCodec; import loci.formats.codec.LuraWaveCodec; import loci.formats.codec.NikonCodec; import loci.formats.codec.PackbitsCodec; import loci.formats.codec.PassthroughCodec; import loci.formats.codec.ZlibCodec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class for performing compression operations with a TIFF file. * * @author Curtis Rueden ctrueden at wisc.edu * @author Eric Kjellman egkjellman at wisc.edu * @author Melissa Linkert melissa at glencoesoftware.com * @author Chris Allan callan at blackcat.ca */ public enum TiffCompression implements CodedEnum { // (TIFF code, codec, codec name) DEFAULT_UNCOMPRESSED(0, new PassthroughCodec(), "Uncompressed"), UNCOMPRESSED(1, new PassthroughCodec(), "Uncompressed"), CCITT_1D(2, null, "CCITT Group 3 1-Dimensional Modified Huffman"), GROUP_3_FAX(3, null, "CCITT T.4 bi-level encoding (Group 3 Fax)"), GROUP_4_FAX(4, null, "CCITT T.6 bi-level encoding (Group 4 Fax)"), LZW(5, new LZWCodec(), "LZW"), OLD_JPEG(6, new JPEGCodec(), "Old JPEG"), JPEG(7, new JPEGCodec(), "JPEG"), PACK_BITS(32773, new PackbitsCodec(), "PackBits"), PROPRIETARY_DEFLATE(32946, new ZlibCodec(), "Deflate (Zlib)"), DEFLATE(8, new ZlibCodec(), "Deflate (Zlib)"), THUNDERSCAN(32809, null, "Thunderscan"), JPEG_2000(33003, new JPEG2000Codec(), "JPEG-2000") { @Override public CodecOptions getCompressionCodecOptions(IFD ifd) throws FormatException { return getCompressionCodecOptions(ifd, null); } @Override public CodecOptions getCompressionCodecOptions(IFD ifd, CodecOptions opt) throws FormatException { CodecOptions options = super.getCompressionCodecOptions(ifd, opt); JPEG2000CodecOptions j2k = JPEG2000CodecOptions.getDefaultOptions(options); if (opt instanceof JPEG2000CodecOptions) { JPEG2000CodecOptions o = (JPEG2000CodecOptions) opt; j2k.numDecompositionLevels = o.numDecompositionLevels; j2k.resolution = o.resolution; if (o.codeBlockSize != null) { j2k.codeBlockSize = o.codeBlockSize; } if (o.quality > 0) { j2k.quality = o.quality; } } j2k.writeBox = false; j2k.lossless = false; return j2k; } }, JPEG_2000_LOSSY(33004, new JPEG2000Codec(), "JPEG-2000 Lossy") { @Override public CodecOptions getCompressionCodecOptions(IFD ifd) throws FormatException { return getCompressionCodecOptions(ifd, null); } @Override public CodecOptions getCompressionCodecOptions(IFD ifd, CodecOptions opt) throws FormatException { CodecOptions options = super.getCompressionCodecOptions(ifd, opt); options.lossless = false; JPEG2000CodecOptions j2k = JPEG2000CodecOptions.getDefaultOptions(options); if (opt instanceof JPEG2000CodecOptions) { JPEG2000CodecOptions o = (JPEG2000CodecOptions) opt; j2k.numDecompositionLevels = o.numDecompositionLevels; j2k.resolution = o.resolution; if (o.codeBlockSize != null) { j2k.codeBlockSize = o.codeBlockSize; } if (o.quality > 0) { j2k.quality = o.quality; } } j2k.writeBox = false; return j2k; } }, ALT_JPEG2000(33005, new JPEG2000Codec(), "JPEG-2000") { @Override public CodecOptions getCompressionCodecOptions(IFD ifd) throws FormatException { return getCompressionCodecOptions(ifd, null); } @Override public CodecOptions getCompressionCodecOptions(IFD ifd, CodecOptions opt) throws FormatException { CodecOptions options = super.getCompressionCodecOptions(ifd, opt); options.lossless = true; JPEG2000CodecOptions j2k = JPEG2000CodecOptions.getDefaultOptions(options); if (opt instanceof JPEG2000CodecOptions) { JPEG2000CodecOptions o = (JPEG2000CodecOptions) opt; j2k.numDecompositionLevels = o.numDecompositionLevels; j2k.resolution = o.resolution; if (o.codeBlockSize != null) j2k.codeBlockSize = o.codeBlockSize; if (o.quality > 0) j2k.quality = o.quality; } j2k.writeBox = false; j2k.lossless = false; return j2k; } }, ALT_JPEG(33007, new JPEGCodec(), "JPEG"), OLYMPUS_JPEG2000(34712, new JPEG2000Codec(), "JPEG-2000") { @Override public CodecOptions getCompressionCodecOptions(IFD ifd) throws FormatException { return getCompressionCodecOptions(ifd, null); } @Override public CodecOptions getCompressionCodecOptions(IFD ifd, CodecOptions opt) throws FormatException { CodecOptions options = super.getCompressionCodecOptions(ifd, opt); options.lossless = true; JPEG2000CodecOptions j2k = JPEG2000CodecOptions.getDefaultOptions(options); if (opt instanceof JPEG2000CodecOptions) { JPEG2000CodecOptions o = (JPEG2000CodecOptions) opt; j2k.numDecompositionLevels = o.numDecompositionLevels; j2k.resolution = o.resolution; if (o.codeBlockSize != null) j2k.codeBlockSize = o.codeBlockSize; if (o.quality > 0) j2k.quality = o.quality; } return j2k; } }, NIKON(34713, new NikonCodec(), "Nikon"), LURAWAVE(65535, new LuraWaveCodec(), "LuraWave"); // -- Constants -- private static final Logger LOGGER = LoggerFactory.getLogger(TiffCompression.class); /** Code for the TIFF compression in the actual TIFF file. */ private int code; /** TIFF compression codec. */ private Codec codec; /** Name of the TIFF compression codec. */ private String codecName; /** Reverse lookup of code to TIFF compression enumerate value. */ private static final Map<Integer, TiffCompression> lookup = getCompressionMap(); private static Map<Integer, TiffCompression> getCompressionMap() { Map<Integer, TiffCompression> lookup = new HashMap<Integer, TiffCompression>(); for (TiffCompression v : EnumSet.allOf(TiffCompression.class)) { lookup.put(v.getCode(), v); } return lookup; } // -- TiffCompression methods -- /** * Default constructor. * @param code Integer "code" for the TIFF compression type. * @param codec TIFF compression codec. * @param codecName String name of the compression type. */ private TiffCompression(int code, Codec codec, String codecName) { this.code = code; this.codec = codec; this.codecName = codecName; } /** * Retrieves a TIFF compression instance by code. * @param code Integer "code" for the TIFF compression type. * @return See above. */ public static TiffCompression get(int code) { TiffCompression toReturn = lookup.get(code); if (toReturn == null) { throw new EnumException( "Unable to find TiffCompresssion with code: " + code); } return toReturn; } /* (non-Javadoc) * @see loci.common.CodedEnum#getCode() */ @Override public int getCode() { return code; } /** * Retrieves the name of the TIFF compression codec. * @return See above. */ public String getCodecName() { return codecName; } // -- TiffCompression methods - decompression -- /** Decodes a strip of data. */ public byte[] decompress(byte[] input, CodecOptions options) throws FormatException, IOException { if (codec == null) { throw new UnsupportedCompressionException( "Sorry, " + getCodecName() + " compression mode is not supported"); } return codec.decompress(input, options); } /** Undoes in-place differencing according to the given predictor value. */ public static void undifference(byte[] input, IFD ifd) throws FormatException { int predictor = ifd.getIFDIntValue(IFD.PREDICTOR, 1); if (predictor == 2) { LOGGER.debug("reversing horizontal differencing"); int[] bitsPerSample = ifd.getBitsPerSample(); int len = bitsPerSample.length; long width = ifd.getImageWidth(); boolean little = ifd.isLittleEndian(); int planarConfig = ifd.getPlanarConfiguration(); int bytes = ifd.getBytesPerSample()[0]; if (planarConfig == 2 || bitsPerSample[len - 1] == 0) len = 1; len *= bytes; for (int b=0; b<=input.length-bytes; b+=bytes) { if (b / len % width == 0) continue; int value = DataTools.bytesToInt(input, b, bytes, little); value += DataTools.bytesToInt(input, b - len, bytes, little); DataTools.unpackBytes(value, input, b, bytes, little); } } else if (predictor != 1) { throw new FormatException("Unknown Predictor (" + predictor + ")"); } } // -- TiffCompression methods - compression -- /** * Creates a set of codec options for compression. * @param ifd The IFD to create codec options for. * @return A new codec options instance populated using metadata from * <code>ifd</code>. */ public CodecOptions getCompressionCodecOptions(IFD ifd) throws FormatException{ return getCompressionCodecOptions(ifd, null); } /** * Creates a set of codec options for compression. * @param ifd The IFD to create codec options for. * @return A new codec options instance populated using metadata from * <code>ifd</code>. * @param opt The codec options to copy. */ public CodecOptions getCompressionCodecOptions(IFD ifd, CodecOptions opt) throws FormatException{ if (ifd == null) throw new IllegalArgumentException("No IFD specified."); if (opt == null) opt = CodecOptions.getDefaultOptions(); CodecOptions options = new CodecOptions(opt); options.width = (int) ifd.getImageWidth(); options.height = (int) ifd.getImageLength(); options.bitsPerSample = ifd.getBitsPerSample()[0]; options.channels = ifd.getSamplesPerPixel(); options.littleEndian = ifd.isLittleEndian(); options.interleaved = true; options.signed = false; return options; } /** Encodes a strip of data. */ public byte[] compress(byte[] input, CodecOptions options) throws FormatException, IOException { if (codec == null) { throw new FormatException( "Sorry, " + getCodecName() + " compression mode is not supported"); } return codec.compress(input, options); } /** Performs in-place differencing according to the given predictor value. */ public static void difference(byte[] input, IFD ifd) throws FormatException { int predictor = ifd.getIFDIntValue(IFD.PREDICTOR, 1); if (predictor == 2) { LOGGER.debug("performing horizontal differencing"); int[] bitsPerSample = ifd.getBitsPerSample(); long width = ifd.getImageWidth(); boolean little = ifd.isLittleEndian(); int planarConfig = ifd.getPlanarConfiguration(); int bytes = ifd.getBytesPerSample()[0]; int len = bytes * (planarConfig == 2 ? 1 : bitsPerSample.length); for (int b=input.length-bytes; b>=0; b-=bytes) { if (b / len % width == 0) continue; int value = DataTools.bytesToInt(input, b, bytes, little); value -= DataTools.bytesToInt(input, b - len, bytes, little); DataTools.unpackBytes(value, input, b, bytes, little); } } else if (predictor != 1) { throw new FormatException("Unknown Predictor (" + predictor + ")"); } } }