001 /* 002 * TIFFDecoder 003 * 004 * Copyright (c) 2002, 2003 Marco Schmidt. 005 * All rights reserved. 006 */ 007 008 package net.sourceforge.jiu.codecs.tiff; 009 010 import java.io.DataInput; 011 import java.io.IOException; 012 import java.io.RandomAccessFile; 013 import net.sourceforge.jiu.codecs.InvalidFileStructureException; 014 import net.sourceforge.jiu.color.conversion.CMYKConversion; 015 import net.sourceforge.jiu.color.conversion.LogLuvConversion; 016 import net.sourceforge.jiu.data.BilevelImage; 017 import net.sourceforge.jiu.data.ByteChannelImage; 018 import net.sourceforge.jiu.data.RGBIndex; 019 import net.sourceforge.jiu.data.ShortChannelImage; 020 import net.sourceforge.jiu.ops.MissingParameterException; 021 import net.sourceforge.jiu.util.ArrayConverter; 022 023 /** 024 * The abstract base class for a TIFF decoder, a class that decompresses one tile or 025 * strip of image data and understands one or more compression types. 026 * Each child class implements the decoding of a particular TIFF compression type 027 * in its {@link #decode} method. 028 * <p> 029 * This class does all the work of storing decompressed data (given as a byte array) 030 * in the image object. 031 * Given the many variants (sample order, color depth, color space etc.) this is 032 * a larger portion of code. 033 * @author Marco Schmidt 034 * @since 0.7.0 035 */ 036 public abstract class TIFFDecoder 037 { 038 private TIFFCodec codec; 039 private TIFFImageFileDirectory ifd; 040 private int currentRow; 041 private int leftColumn; 042 private int rightColumn; 043 private int topRow; 044 private int bottomRow; 045 private byte[] rowBuffer; 046 private int bufferIndex; 047 private int tileIndex; 048 private int processedTileRows; 049 private int totalTileRows; 050 051 public TIFFDecoder() 052 { 053 tileIndex = -1; 054 } 055 056 /** 057 * Decode data from input and write the decompressed pixel data to 058 * the image associated with this decoder. 059 * Child classes must override this method to implement the decoding 060 * for a particular compression type. 061 */ 062 public abstract void decode() throws 063 InvalidFileStructureException, 064 IOException; 065 066 /** 067 * Returns the number of bytes per row for the strip or tile 068 * that this decoder deals with. 069 * So with a tiled TIFF and an image width of 500 and a tile width of 100, 070 * for an eight bit grayscale image this would return 100 (not 500). 071 * @return number of bytes per row 072 */ 073 public int getBytesPerRow() 074 { 075 return ifd.getBytesPerRow(); 076 } 077 078 /** 079 * Returns the codec from which this decoder is used. 080 * @return TIFFCodec object using this decoder 081 */ 082 public TIFFCodec getCodec() 083 { 084 return codec; 085 } 086 087 /** 088 * Returns an array with Integer values of all compression types supported by 089 * this decoder (see the COMPRESSION_xyz constants in {@link TIFFConstants}. 090 * Normally, this is only one value, but some compression types got assigned more than one constant 091 * (e.g. deflated). 092 * Also, a decoder could be capable of dealing with more than one type of compression 093 * if the compression types are similar enough to justify that. 094 * However, typically a decoder can only deal with one type of compression. 095 * @return array with Integer objects of all TIFF compression constants supported by this decoder 096 */ 097 public abstract Integer[] getCompressionTypes(); 098 099 /** 100 * Returns the IFD for the image this decoder is supposed to uncompress 101 * (partially). 102 * @return IFD object 103 */ 104 public TIFFImageFileDirectory getImageFileDirectory() 105 { 106 return ifd; 107 } 108 109 /** 110 * Returns the input stream from which this decoder is supposed 111 * to read data. 112 */ 113 public DataInput getInput() 114 { 115 return codec.getRandomAccessFile(); 116 } 117 118 /** 119 * Returns the zero-based index of the tile or strip this decoder 120 * is supposed to be decompressing. 121 * @return tile index 122 */ 123 public int getTileIndex() 124 { 125 return tileIndex; 126 } 127 128 /** 129 * Returns the leftmost column of the image strip / tile to be read 130 * by this decoder. 131 */ 132 public int getX1() 133 { 134 return leftColumn; 135 } 136 137 /** 138 * Returns the rightmost column of the image strip / tile to be read 139 * by this decoder. 140 */ 141 public int getX2() 142 { 143 return rightColumn; 144 } 145 146 /** 147 * Returns the top row of the image strip / tile to be read 148 * by this decoder. 149 */ 150 public int getY1() 151 { 152 return topRow; 153 } 154 155 /** 156 * Returns the bottom row of the image strip / tile to be read 157 * by this decoder. 158 */ 159 public int getY2() 160 { 161 return bottomRow; 162 } 163 164 /** 165 * Check if all necessary parameters have been given to this decoder 166 * and initialize several internal fields from them. 167 * Required parameters are a TIFFCodec object, a TIFFImageFileDirectory object and 168 * a tile index. 169 */ 170 public void initialize() throws 171 IOException, 172 MissingParameterException 173 { 174 if (tileIndex < 0) 175 { 176 throw new MissingParameterException("Tile index was not initialized."); 177 } 178 if (codec == null) 179 { 180 throw new MissingParameterException("No TIFFCodec object was given to this decoder."); 181 } 182 if (ifd == null) 183 { 184 throw new MissingParameterException("No TIFFImageFileDirectory object was given to this decoder."); 185 } 186 187 RandomAccessFile raf = codec.getRandomAccessFile(); 188 long offset = ifd.getTileOffset(tileIndex) & 0x00000000ffffffffL; 189 raf.seek(offset); 190 191 leftColumn = ifd.getTileX1(tileIndex); 192 rightColumn = ifd.getTileX2(tileIndex); 193 topRow = ifd.getTileY1(tileIndex); 194 bottomRow = ifd.getTileY2(tileIndex); 195 currentRow = topRow; 196 processedTileRows = tileIndex * ifd.getTileHeight(); 197 totalTileRows = ifd.getTileHeight() * ifd.getNumTiles(); 198 rowBuffer = new byte[ifd.getBytesPerRow()]; 199 } 200 201 /** 202 * Adds a number of bytes to the internal row buffer. 203 * If the row buffer gets full (a complete line is available) 204 * that data will be copied to the image. 205 * Note that more than one line, exactly one line or only part 206 * of a line can be stored in the <code>number</code> bytes 207 * in <code>data</code>. 208 * @param data byte array with image data that has been decoded 209 * @param offset int index into data where the first byte to be stored is situated 210 * @param number int number of bytes to be stored 211 */ 212 public void putBytes(byte[] data, int offset, int number) 213 { 214 // assert(bufferIndex < rowBuffer.length); 215 while (number > 0) 216 { 217 int remaining = rowBuffer.length - bufferIndex; 218 int numCopy; 219 if (number > remaining) 220 { 221 numCopy = remaining; 222 } 223 else 224 { 225 numCopy = number; 226 } 227 System.arraycopy(data, offset, rowBuffer, bufferIndex, numCopy); 228 number -= numCopy; 229 offset += numCopy; 230 bufferIndex += numCopy; 231 if (bufferIndex == getBytesPerRow()) 232 { 233 storeRow(rowBuffer, 0); 234 bufferIndex = 0; 235 } 236 } 237 } 238 239 /** 240 * Specify the codec to be used with this decoder. 241 * This is a mandatory parameter - without it, {@link #initialize} 242 * will throw an exception. 243 * @param tiffCodec TIFFCodec object to be used by this decoder 244 * @see #getCodec 245 */ 246 public void setCodec(TIFFCodec tiffCodec) 247 { 248 codec = tiffCodec; 249 } 250 251 /** 252 * Specify the IFD to be used with this decoder. 253 * This is a mandatory parameter - without it, {@link #initialize} 254 * will throw an exception. 255 * @param tiffIfd object to be used by this decoder 256 * @see #getImageFileDirectory 257 */ 258 public void setImageFileDirectory(TIFFImageFileDirectory tiffIfd) 259 { 260 ifd = tiffIfd; 261 } 262 263 /** 264 * Specify the zero-based tile index for the tile or strip to be decompressed 265 * by this decoder. 266 * This is a mandatory parameter - without it, {@link #initialize} 267 * will throw an exception. 268 * @param index zero-based tile / strip index 269 * @see #getTileIndex 270 */ 271 public void setTileIndex(int index) 272 { 273 if (index < 0) 274 { 275 throw new IllegalArgumentException("Tile index must be 0 or larger."); 276 } 277 tileIndex = index; 278 } 279 280 private void storeRow(byte[] data, int offset) 281 { 282 codec.setProgress(processedTileRows++, totalTileRows); 283 // get current row number and increase field currentRow by one 284 int y = currentRow++; 285 // buffer index field is reset to zero so that putBytes will start at the beginning of the buffer next time 286 bufferIndex = 0; 287 // leave if we don't need that row because of bounds 288 if (!codec.isRowRequired(y)) 289 { 290 return; 291 } 292 // adjust y so that it will be in bounds coordinate space 293 y -= codec.getBoundsY1(); 294 // get leftmost and rightmost pixel index of the current tile 295 int x1 = getX1(); 296 int x2 = getX2(); 297 // compute number of pixels, adjust for bounds 298 int numPixels = x2 - x1 + 1; 299 int leftPixels = 0; 300 if (getX1() < codec.getBoundsX1()) 301 { 302 leftPixels = codec.getBoundsX1() - getX1(); 303 } 304 int rightPixels = 0; 305 if (getX2() > codec.getBoundsX2()) 306 { 307 rightPixels = getX2() - codec.getBoundsX2(); 308 } 309 numPixels -= (rightPixels + leftPixels); 310 switch(ifd.getImageType()) 311 { 312 case(TIFFImageFileDirectory.TYPE_BILEVEL_BYTE): 313 { 314 BilevelImage image = (BilevelImage)codec.getImage(); 315 int index = offset + leftPixels; 316 int x = getX1() - codec.getBoundsX1() + leftPixels; 317 while (numPixels-- > 0) 318 { 319 if (data[index++] == (byte)BilevelImage.BLACK) 320 { 321 image.putBlack(x++, y); 322 } 323 else 324 { 325 image.putWhite(x++, y); 326 } 327 } 328 break; 329 } 330 case(TIFFImageFileDirectory.TYPE_BILEVEL_PACKED): 331 { 332 BilevelImage image = (BilevelImage)codec.getImage(); 333 int x = getX1() - codec.getBoundsX1() + leftPixels; 334 image.putPackedBytes(x, y, numPixels, data, offset + (leftPixels / 8), leftPixels % 8); 335 break; 336 } 337 case(TIFFImageFileDirectory.TYPE_GRAY4): 338 { 339 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 340 byte[] dest = new byte[data.length * 2]; 341 ArrayConverter.decodePacked4Bit(data, 0, dest, 0, data.length); 342 for (int i = 0; i < dest.length; i++) 343 { 344 int value = dest[i] & 15; 345 value = (value << 4) | value; 346 dest[i] = (byte)value; 347 } 348 image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, offset + leftPixels); 349 break; 350 } 351 case(TIFFImageFileDirectory.TYPE_PALETTED4): 352 { 353 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 354 byte[] dest = new byte[data.length * 2]; 355 ArrayConverter.decodePacked4Bit(data, 0, dest, 0, data.length); 356 image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, offset + leftPixels); 357 break; 358 } 359 case(TIFFImageFileDirectory.TYPE_GRAY8): 360 case(TIFFImageFileDirectory.TYPE_PALETTED8): 361 { 362 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 363 image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, data, offset + leftPixels); 364 break; 365 } 366 case(TIFFImageFileDirectory.TYPE_CMYK32_INTERLEAVED): 367 { 368 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 369 byte[] dest = new byte[data.length]; 370 int numSamples = ifd.getTileWidth(); 371 CMYKConversion.convertCMYK32InterleavedToRGB24Planar( 372 data, 0, 373 dest, 0, 374 dest, numSamples, 375 dest, numSamples * 2, 376 numSamples); 377 image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, leftPixels); 378 image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, numSamples + leftPixels); 379 image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, 2 * numSamples + leftPixels); 380 break; 381 } 382 /* case(TIFFImageFileDirectory.TYPE_CMYK32_PLANAR): 383 { 384 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 385 byte[] dest = new byte[data.length]; 386 int numSamples = ifd.getTileWidth(); 387 CMYKConversion.convertCMYK32PlanarToRGB24Planar( 388 data, 0, 389 data, numPixels, 390 data, numPixels * 2, 391 data, numPixels * 3, 392 dest, 0, 393 dest, numSamples, 394 dest, numSamples * 2, 395 numSamples); 396 image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, leftPixels); 397 image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, numSamples + leftPixels); 398 image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, dest, 2 * numSamples + leftPixels); 399 break; 400 }*/ 401 case(TIFFImageFileDirectory.TYPE_RGB24_INTERLEAVED): 402 { 403 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 404 offset += leftPixels * 3; 405 for (int i = 0, x = getX1() - codec.getBoundsX1() + leftPixels; i < numPixels; i++, x++) 406 { 407 image.putByteSample(RGBIndex.INDEX_RED, x, y, data[offset++]); 408 image.putByteSample(RGBIndex.INDEX_GREEN, x, y, data[offset++]); 409 image.putByteSample(RGBIndex.INDEX_BLUE, x, y, data[offset++]); 410 } 411 break; 412 } 413 case(TIFFImageFileDirectory.TYPE_RGB48_INTERLEAVED): 414 { 415 ShortChannelImage image = (ShortChannelImage)codec.getImage(); 416 offset += leftPixels * 3; 417 short[] triplet = new short[3]; 418 boolean littleEndian = codec.getByteOrder() == TIFFCodec.BYTE_ORDER_INTEL; 419 for (int i = 0, x = getX1() - codec.getBoundsX1() + leftPixels; i < numPixels; i++, x++) 420 { 421 for (int j = 0; j < 3; j++, offset += 2) 422 { 423 if (littleEndian) 424 { 425 triplet[j] = ArrayConverter.getShortLE(data, offset); 426 } 427 else 428 { 429 triplet[j] = ArrayConverter.getShortBE(data, offset); 430 } 431 } 432 image.putShortSample(RGBIndex.INDEX_RED, x, y, triplet[0]); 433 image.putShortSample(RGBIndex.INDEX_GREEN, x, y, triplet[1]); 434 image.putShortSample(RGBIndex.INDEX_BLUE, x, y, triplet[2]); 435 } 436 break; 437 } 438 case(TIFFImageFileDirectory.TYPE_LOGLUV32_INTERLEAVED): 439 { 440 if (getImageFileDirectory().getCompression() == TIFFConstants.COMPRESSION_SGI_LOG_RLE) 441 { 442 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 443 int numSamples = ifd.getTileWidth(); 444 byte[] red = new byte[numSamples]; 445 byte[] green = new byte[numSamples]; 446 byte[] blue = new byte[numSamples]; 447 LogLuvConversion.convertLogLuv32InterleavedtoRGB24Planar(data, red, green, blue, numSamples); 448 image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, red, leftPixels); 449 image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, green, leftPixels); 450 image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, blue, leftPixels); 451 } 452 else 453 if (getImageFileDirectory().getCompression() == TIFFConstants.COMPRESSION_SGI_LOG_24_PACKED) 454 { 455 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 456 int numSamples = ifd.getTileWidth(); 457 byte[] red = new byte[numSamples]; 458 byte[] green = new byte[numSamples]; 459 byte[] blue = new byte[numSamples]; 460 LogLuvConversion.convertLogLuv24InterleavedtoRGB24Planar(data, red, green, blue, numSamples); 461 image.putByteSamples(RGBIndex.INDEX_RED, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, red, leftPixels); 462 image.putByteSamples(RGBIndex.INDEX_GREEN, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, green, leftPixels); 463 image.putByteSamples(RGBIndex.INDEX_BLUE, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, blue, leftPixels); 464 } 465 break; 466 } 467 case(TIFFImageFileDirectory.TYPE_LOGL): 468 { 469 ByteChannelImage image = (ByteChannelImage)codec.getImage(); 470 int numSamples = ifd.getTileWidth(); 471 byte[] gray = new byte[numSamples]; 472 LogLuvConversion.convertLogL16toGray8(data, gray, numSamples); 473 image.putByteSamples(0, getX1() - codec.getBoundsX1() + leftPixels, y, numPixels, 1, gray, leftPixels); 474 break; 475 } 476 } 477 } 478 }