001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.tiff.datareaders;
018
019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D;
020import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3;
021import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4;
022import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW;
023import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS;
024import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED;
025import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T4_OPTIONS_2D;
026import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T4_OPTIONS_FILL;
027import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T4_OPTIONS_UNCOMPRESSED_MODE;
028import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE;
029
030import java.awt.Rectangle;
031import java.awt.image.BufferedImage;
032import java.io.ByteArrayInputStream;
033import java.io.IOException;
034import java.io.InputStream;
035import java.nio.ByteOrder;
036import java.util.Arrays;
037
038import org.apache.commons.imaging.ImageReadException;
039import org.apache.commons.imaging.common.ImageBuilder;
040import org.apache.commons.imaging.common.PackBits;
041import org.apache.commons.imaging.common.itu_t4.T4AndT6Compression;
042import org.apache.commons.imaging.common.mylzw.MyLzwDecompressor;
043import org.apache.commons.imaging.formats.tiff.TiffDirectory;
044import org.apache.commons.imaging.formats.tiff.TiffField;
045import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
046import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
047
048public abstract class ImageDataReader {
049    protected final TiffDirectory directory;
050    protected final PhotometricInterpreter photometricInterpreter;
051    private final int[] bitsPerSample;
052    protected final int bitsPerSampleLength;
053    private final int[] last;
054
055    protected final int predictor;
056    protected final int samplesPerPixel;
057    protected final int width;
058    protected final int height;
059
060    public ImageDataReader(final TiffDirectory directory,
061            final PhotometricInterpreter photometricInterpreter, final int[] bitsPerSample,
062            final int predictor, final int samplesPerPixel, final int width, final int height) {
063        this.directory = directory;
064        this.photometricInterpreter = photometricInterpreter;
065        this.bitsPerSample = bitsPerSample;
066        this.bitsPerSampleLength = bitsPerSample.length;
067        this.samplesPerPixel = samplesPerPixel;
068        this.predictor = predictor;
069        this.width = width;
070        this.height = height;
071        last = new int[samplesPerPixel];
072    }
073
074    // public abstract void readImageData(BufferedImage bi, ByteSource
075    // byteSource)
076    public abstract void readImageData(ImageBuilder imageBuilder)
077            throws ImageReadException, IOException;
078
079
080    public abstract BufferedImage readImageData(Rectangle subImage)
081            throws ImageReadException, IOException;
082
083    /**
084     * Checks if all the bits per sample entries are the same size
085     * @param size the size to check
086     * @return true if all the bits per sample entries are the same
087     */
088    protected boolean isHomogenous(final int size) {
089        for (final int element : bitsPerSample) {
090            if (element != size) {
091                return false;
092            }
093        }
094        return true;
095    }
096
097    /**
098     * Reads samples and returns them in an int array.
099     *
100     * @param bis
101     *            the stream to read from
102     * @param result
103     *            the samples array to populate, must be the same length as
104     *            bitsPerSample.length
105     * @throws IOException
106     */
107    void getSamplesAsBytes(final BitInputStream bis, final int[] result) throws IOException {
108        for (int i = 0; i < bitsPerSample.length; i++) {
109            final int bits = bitsPerSample[i];
110            int sample = bis.readBits(bits);
111            if (bits < 8) {
112                final int sign = sample & 1;
113                sample = sample << (8 - bits); // scale to byte.
114                if (sign > 0) {
115                    sample = sample | ((1 << (8 - bits)) - 1); // extend to byte
116                }
117            } else if (bits > 8) {
118                sample = sample >> (bits - 8); // extend to byte.
119            }
120            result[i] = sample;
121        }
122    }
123
124    protected void resetPredictor() {
125        Arrays.fill(last, 0);
126    }
127
128    protected int[] applyPredictor(final int[] samples) {
129        if (predictor == 2) {
130            // Horizontal differencing.
131            for (int i = 0; i < samples.length; i++) {
132                samples[i] = 0xff & (samples[i] + last[i]);
133                last[i] = samples[i];
134            }
135        }
136
137        return samples;
138    }
139
140    protected byte[] decompress(final byte[] compressedInput, final int compression,
141            final int expectedSize, final int tileWidth, final int tileHeight)
142            throws ImageReadException, IOException {
143        final TiffField fillOrderField = directory.findField(TiffTagConstants.TIFF_TAG_FILL_ORDER);
144        int fillOrder = TiffTagConstants.FILL_ORDER_VALUE_NORMAL;
145        if (fillOrderField != null) {
146            fillOrder = fillOrderField.getIntValue();
147        }
148        final byte[] compressedOrdered; // re-ordered bytes (if necessary)
149        if (fillOrder == TiffTagConstants.FILL_ORDER_VALUE_NORMAL) {
150            compressedOrdered = compressedInput;
151            // good
152        } else if (fillOrder == TiffTagConstants.FILL_ORDER_VALUE_REVERSED) {
153            compressedOrdered = new byte[compressedInput.length];
154            for (int i = 0; i < compressedInput.length; i++) {
155                compressedOrdered[i] = (byte) (Integer.reverse(0xff & compressedInput[i]) >>> 24);
156            }
157        } else {
158            throw new ImageReadException("TIFF FillOrder=" + fillOrder
159                    + " is invalid");
160        }
161
162        switch (compression) {
163        case TIFF_COMPRESSION_UNCOMPRESSED: // None;
164            return compressedOrdered;
165        case TIFF_COMPRESSION_CCITT_1D: // CCITT Group 3 1-Dimensional Modified
166                                        // Huffman run-length encoding.
167            return T4AndT6Compression.decompressModifiedHuffman(compressedOrdered,
168                    tileWidth, tileHeight);
169        case TIFF_COMPRESSION_CCITT_GROUP_3: {
170            int t4Options = 0;
171            final TiffField field = directory.findField(TiffTagConstants.TIFF_TAG_T4_OPTIONS);
172            if (field != null) {
173                t4Options = field.getIntValue();
174            }
175            final boolean is2D = (t4Options & TIFF_FLAG_T4_OPTIONS_2D) != 0;
176            final boolean usesUncompressedMode = (t4Options & TIFF_FLAG_T4_OPTIONS_UNCOMPRESSED_MODE) != 0;
177            if (usesUncompressedMode) {
178                throw new ImageReadException(
179                        "T.4 compression with the uncompressed mode extension is not yet supported");
180            }
181            final boolean hasFillBitsBeforeEOL = (t4Options & TIFF_FLAG_T4_OPTIONS_FILL) != 0;
182            if (is2D) {
183                return T4AndT6Compression.decompressT4_2D(compressedOrdered,
184                        tileWidth, tileHeight, hasFillBitsBeforeEOL);
185            }
186            return T4AndT6Compression.decompressT4_1D(compressedOrdered,
187                    tileWidth, tileHeight, hasFillBitsBeforeEOL);
188        }
189        case TIFF_COMPRESSION_CCITT_GROUP_4: {
190            int t6Options = 0;
191            final TiffField field = directory.findField(TiffTagConstants.TIFF_TAG_T6_OPTIONS);
192            if (field != null) {
193                t6Options = field.getIntValue();
194            }
195            final boolean usesUncompressedMode = (t6Options & TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE) != 0;
196            if (usesUncompressedMode) {
197                throw new ImageReadException(
198                        "T.6 compression with the uncompressed mode extension is not yet supported");
199            }
200            return T4AndT6Compression.decompressT6(compressedOrdered, tileWidth,
201                    tileHeight);
202        }
203        case TIFF_COMPRESSION_LZW: // LZW
204        {
205            final InputStream is = new ByteArrayInputStream(compressedOrdered);
206
207            final int lzwMinimumCodeSize = 8;
208
209            final MyLzwDecompressor myLzwDecompressor = new MyLzwDecompressor(
210                    lzwMinimumCodeSize, ByteOrder.BIG_ENDIAN);
211
212            myLzwDecompressor.setTiffLZWMode();
213
214            return myLzwDecompressor.decompress(is, expectedSize);
215        }
216
217        case TIFF_COMPRESSION_PACKBITS: // Packbits
218        {
219            return new PackBits().decompress(compressedOrdered, expectedSize);
220        }
221
222        default:
223            throw new ImageReadException("Tiff: unknown/unsupported compression: " + compression);
224        }
225    }
226}