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}