// Set a property "tiff_directory".
properties.put("tiff_directory", dir);
// Get the number of samples per pixel
TIFFField sfield =
dir.getField(TIFFImageDecoder.TIFF_SAMPLES_PER_PIXEL);
int samplesPerPixel = sfield == null ? 1 : (int)sfield.getAsLong(0);
// Read the TIFF_PLANAR_CONFIGURATION field
TIFFField planarConfigurationField =
dir.getField(TIFFImageDecoder.TIFF_PLANAR_CONFIGURATION);
char[] planarConfiguration = planarConfigurationField == null ?
new char[] {1} :
planarConfigurationField.getAsChars();
// Support planar format (band sequential) only for 1 sample/pixel.
if (planarConfiguration[0] != 1 && samplesPerPixel != 1) {
throw new RuntimeException(JaiI18N.getString("TIFFImage0"));
}
// Read the TIFF_BITS_PER_SAMPLE field
TIFFField bitsField =
dir.getField(TIFFImageDecoder.TIFF_BITS_PER_SAMPLE);
char[] bitsPerSample = null;
if(bitsField != null) {
bitsPerSample = bitsField.getAsChars();
} else {
bitsPerSample = new char[] {1};
// Ensure that all samples have the same bit depth.
for (int i = 1; i < bitsPerSample.length; i++) {
if (bitsPerSample[i] != bitsPerSample[0]) {
throw new RuntimeException(
JaiI18N.getString("TIFFImage1"));
}
}
}
sampleSize = (int)bitsPerSample[0];
// Read the TIFF_SAMPLE_FORMAT tag to see whether the data might be
// signed or floating point
TIFFField sampleFormatField =
dir.getField(TIFFImageDecoder.TIFF_SAMPLE_FORMAT);
char[] sampleFormat = null;
if (sampleFormatField != null) {
sampleFormat = sampleFormatField.getAsChars();
// Check that all the samples have the same format
for (int l=1; l<sampleFormat.length; l++) {
if (sampleFormat[l] != sampleFormat[0]) {
throw new RuntimeException(
JaiI18N.getString("TIFFImage2"));
}
}
} else {
sampleFormat = new char[] {1};
}
// Set the data type based on the sample size and format.
boolean isValidDataFormat = false;
switch(sampleSize) {
case 1:
case 4:
case 8:
if(sampleFormat[0] != 3) {
// Ignore whether signed or unsigned: treat all as unsigned.
dataType = DataBuffer.TYPE_BYTE;
isValidDataFormat = true;
}
break;
case 16:
if(sampleFormat[0] != 3) {
dataType = sampleFormat[0] == 2 ?
DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT;
isValidDataFormat = true;
}
break;
case 32:
dataType = sampleFormat[0] == 3 ?
DataBuffer.TYPE_FLOAT : DataBuffer.TYPE_INT;
isValidDataFormat = true;
break;
}
if(!isValidDataFormat) {
throw new RuntimeException(JaiI18N.getString("TIFFImage3"));
}
// Figure out what compression if any, is being used.
TIFFField compField = dir.getField(TIFFImageDecoder.TIFF_COMPRESSION);
compression = compField == null ? COMP_NONE : compField.getAsInt(0);
// Get the photometric interpretation field.
TIFFField photoInterpField =
dir.getField(TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION);
// Set the photometric interpretation variable.
int photometricType;
if(photoInterpField != null) {
// Set the variable from the photometric interpretation field.
photometricType = (int)photoInterpField.getAsLong(0);
} else {
// The photometric interpretation field is missing; attempt
// to infer the type from other information.
if(dir.getField(TIFFImageDecoder.TIFF_COLORMAP) != null) {
// There is a colormap so most likely a palette color image.
photometricType = 3; // RGB Palette
} else if(sampleSize == 1) {
// Bilevel image so most likely a document; switch based
// on the compression type of the image.
if(compression == COMP_FAX_G3_1D ||
compression == COMP_FAX_G3_2D ||
compression == COMP_FAX_G4_2D) {
photometricType = 0; // WhiteIsZero
} else {
photometricType = 1; // BlackIsZero
}
} else if(samplesPerPixel == 3 || samplesPerPixel == 4) {
// Assume 3 bands is RGB and 4 bands is RGBA.
photometricType = 2; // RGB
} else {
// Default to multi-band grayscale.
photometricType = 1; // BlackIsZero
}
}
// Determine which kind of image we are dealing with.
imageType = TYPE_UNSUPPORTED;
switch(photometricType) {
case 0: // WhiteIsZero
isWhiteZero = true;
case 1: // BlackIsZero
if(sampleSize == 1 && samplesPerPixel == 1) {
imageType = TYPE_BILEVEL;
} else if(sampleSize == 4 && samplesPerPixel == 1) {
imageType = TYPE_GRAY_4BIT;
} else if(sampleSize % 8 == 0) {
if(samplesPerPixel == 1) {
imageType = TYPE_GRAY;
} else if(samplesPerPixel == 2) {
imageType = TYPE_GRAY_ALPHA;
} else {
imageType = TYPE_GENERIC;
}
}
break;
case 2: // RGB
if(sampleSize % 8 == 0) {
if(samplesPerPixel == 3) {
imageType = TYPE_RGB;
} else if(samplesPerPixel == 4) {
imageType = TYPE_RGB_ALPHA;
} else {
imageType = TYPE_GENERIC;
}
}
break;
case 3: // RGB Palette
if(samplesPerPixel == 1 &&
(sampleSize == 4 || sampleSize == 8 || sampleSize == 16)) {
imageType = TYPE_PALETTE;
}
break;
case 4: // Transparency mask
if(sampleSize == 1 && samplesPerPixel == 1) {
imageType = TYPE_BILEVEL;
}
break;
case 5: // Separated image, usually CMYK
if (sampleSize == 8 && samplesPerPixel == 4) {
imageType = TYPE_CMYK;
}
case 6: // YCbCr
if(compression == COMP_JPEG_TTN2 &&
sampleSize == 8 && samplesPerPixel == 3) {
// Set color conversion flag.
colorConvertJPEG = param.getJPEGDecompressYCbCrToRGB();
// Set type to RGB if color converting.
imageType = colorConvertJPEG ? TYPE_RGB : TYPE_GENERIC;
} else {
TIFFField chromaField = dir.getField(TIFF_YCBCR_SUBSAMPLING);
if(chromaField != null) {
chromaSubH = chromaField.getAsInt(0);
chromaSubV = chromaField.getAsInt(1);
} else {
chromaSubH = chromaSubV = 2;
}
if(chromaSubH*chromaSubV == 1) {
imageType = TYPE_GENERIC;
} else if(sampleSize == 8 && samplesPerPixel == 3) {
imageType = TYPE_YCBCR_SUB;
}
}
break;
default: // Other including CIE L*a*b*, unknown.
if(sampleSize % 8 == 0) {
imageType = TYPE_GENERIC;
}
}
// Bail out if not one of the supported types.
if(imageType == TYPE_UNSUPPORTED) {
throw new RuntimeException(JaiI18N.getString("TIFFImage4"));
}
// Set basic image layout
minX = minY = 0;
width = (int)(getField(dir,
TIFFImageDecoder.TIFF_IMAGE_WIDTH,
"Image Width").getAsLong(0));
height = (int)(getField(dir,
TIFFImageDecoder.TIFF_IMAGE_LENGTH,
"Image Length").getAsLong(0));
// Set a preliminary band count. This may be changed later as needed.
numBands = samplesPerPixel;
// Figure out if any extra samples are present.
TIFFField efield = dir.getField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES);
int extraSamples = efield == null ? 0 : (int)efield.getAsLong(0);
if (dir.getField(TIFFImageDecoder.TIFF_TILE_OFFSETS) != null) {
// Image is in tiled format
isTiled = true;
tileWidth = (int)(getField(dir,
TIFFImageDecoder.TIFF_TILE_WIDTH,
"Tile Width").getAsLong(0));
tileHeight = (int)(getField(dir,
TIFFImageDecoder.TIFF_TILE_LENGTH,
"Tile Length").getAsLong(0));
tileOffsets =
(getField(dir,
TIFFImageDecoder.TIFF_TILE_OFFSETS,
"Tile Offsets")).getAsLongs();
tileByteCounts = getFieldAsLongs(
getField(dir,
TIFFImageDecoder.TIFF_TILE_BYTE_COUNTS,
"Tile Byte Counts"));
} else {
// Image is in stripped format, looks like tiles to us
isTiled = false;
// Note: Some legacy files may have tile width and height
// written but use the strip offsets and byte counts fields
// instead of the tile offsets and byte counts. Therefore
// we default here to the tile dimensions if they are written.
tileWidth =
dir.getField(TIFFImageDecoder.TIFF_TILE_WIDTH) != null ?
(int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_TILE_WIDTH) :
width;
TIFFField field =
dir.getField(TIFFImageDecoder.TIFF_ROWS_PER_STRIP);
if (field == null) {
// Default is infinity (2^32 -1), basically the entire image
// TODO: Can do a better job of tiling here
tileHeight =
dir.getField(TIFFImageDecoder.TIFF_TILE_LENGTH) != null ?
(int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_TILE_LENGTH):
height;
} else {
long l = field.getAsLong(0);
long infinity = 1;
infinity = (infinity << 32) - 1;
if (l == infinity || l > height) {
// 2^32 - 1 (effectively infinity, entire image is 1 strip)
// or RowsPerStrip > ImageLength so clamp as having a tile
// larger than the image is pointless.
tileHeight = height;
} else {
tileHeight = (int)l;
}
}
TIFFField tileOffsetsField =
getField(dir,
TIFFImageDecoder.TIFF_STRIP_OFFSETS,
"Strip Offsets");
tileOffsets = getFieldAsLongs(tileOffsetsField);
TIFFField tileByteCountsField =
dir.getField(TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS);
if(tileByteCountsField == null) {
// Attempt to infer the number of bytes in each strip.
int totalBytes = ((sampleSize+7)/8)*numBands*width*height;
int bytesPerStrip =
((sampleSize+7)/8)*numBands*width*tileHeight;
int cumulativeBytes = 0;
tileByteCounts = new long[tileOffsets.length];
for(int i = 0; i < tileOffsets.length; i++) {
tileByteCounts[i] =
Math.min(totalBytes - cumulativeBytes,
bytesPerStrip);
cumulativeBytes += bytesPerStrip;
}
if(compression != COMP_NONE) {
// Replace the stream with one that will not throw
// an EOFException when it runs past the end.
this.stream = new NoEOFStream(stream);
}
} else {
tileByteCounts = getFieldAsLongs(tileByteCountsField);
}
// Uncompressed image provided in a single tile: clamp to max bytes.
int maxBytes = width*height*numBands*((sampleSize + 7)/8);
if(tileByteCounts.length == 1 &&
compression == COMP_NONE &&
tileByteCounts[0] > maxBytes) {
tileByteCounts[0] = maxBytes;
}
}
// Calculate number of tiles and the tileSize in bytes
tilesX = (width + tileWidth - 1)/tileWidth;
tilesY = (height + tileHeight - 1)/tileHeight;
tileSize = tileWidth * tileHeight * numBands;
// Check whether big endian or little endian format is used.
isBigEndian = dir.isBigEndian();
TIFFField fillOrderField =
dir.getField(TIFFImageDecoder.TIFF_FILL_ORDER);
if (fillOrderField != null) {
fillOrder = fillOrderField.getAsInt(0);
} else {
// Default Fill Order
fillOrder = 1;
}
switch(compression) {
case COMP_NONE:
case COMP_PACKBITS:
// Do nothing.
break;
case COMP_DEFLATE:
inflater = new Inflater();
break;
case COMP_FAX_G3_1D:
case COMP_FAX_G3_2D:
case COMP_FAX_G4_2D:
if(sampleSize != 1) {
throw new RuntimeException(JaiI18N.getString("TIFFImage7"));
}
// Fax T.4 compression options
if (compression == 3) {
TIFFField t4OptionsField =
dir.getField(TIFFImageDecoder.TIFF_T4_OPTIONS);
if (t4OptionsField != null) {
tiffT4Options = t4OptionsField.getAsLong(0);
} else {
// Use default value
tiffT4Options = 0;
}
}
// Fax T.6 compression options
if (compression == 4) {
TIFFField t6OptionsField =
dir.getField(TIFFImageDecoder.TIFF_T6_OPTIONS);
if (t6OptionsField != null) {
tiffT6Options = t6OptionsField.getAsLong(0);
} else {
// Use default value
tiffT6Options = 0;
}
}
// Fax encoding, need to create the Fax decoder.
decoder = new TIFFFaxDecoder(fillOrder,
tileWidth, tileHeight);
break;
case COMP_LZW:
// LZW compression used, need to create the LZW decoder.
TIFFField predictorField =
dir.getField(TIFFImageDecoder.TIFF_PREDICTOR);
if (predictorField == null) {
predictor = 1;
} else {
predictor = predictorField.getAsInt(0);
if (predictor != 1 && predictor != 2) {
throw new RuntimeException(JaiI18N.getString("TIFFImage8"));
}
if (predictor == 2 && sampleSize != 8) {
throw new RuntimeException(sampleSize +
JaiI18N.getString("TIFFImage9"));
}
}
lzwDecoder = new TIFFLZWDecoder(tileWidth, predictor,
samplesPerPixel);
break;
case COMP_JPEG_OLD:
throw new RuntimeException(JaiI18N.getString("TIFFImage15"));
case COMP_JPEG_TTN2:
if(!(sampleSize == 8 &&
((imageType == TYPE_GRAY && samplesPerPixel == 1) ||
(imageType == TYPE_PALETTE && samplesPerPixel == 1) ||
(imageType == TYPE_RGB && samplesPerPixel == 3)))) {
throw new RuntimeException(JaiI18N.getString("TIFFImage16"));
}
// Create decodeParam from JPEGTables field if present.
if(dir.isTagPresent(TIFF_JPEG_TABLES)) {
TIFFField jpegTableField = dir.getField(TIFF_JPEG_TABLES);
byte[] jpegTable = jpegTableField.getAsBytes();
ByteArrayInputStream tableStream =
new ByteArrayInputStream(jpegTable);
JPEGImageDecoder decoder =
JPEGCodec.createJPEGDecoder(tableStream);
decoder.decodeAsRaster();
decodeParam = decoder.getJPEGDecodeParam();
}
break;
default:
throw new RuntimeException(JaiI18N.getString("TIFFImage10"));
}
switch(imageType) {
case TYPE_BILEVEL:
case TYPE_GRAY_4BIT:
sampleModel =
new MultiPixelPackedSampleModel(dataType,
tileWidth,
tileHeight,
sampleSize);
if(imageType == TYPE_BILEVEL) {
byte[] map = new byte[] {(byte)(isWhiteZero ? 255 : 0),
(byte)(isWhiteZero ? 0 : 255)};
colorModel = new IndexColorModel(1, 2, map, map, map);
} else {
colorModel =
ImageCodec.createGrayIndexColorModel(sampleModel,
!isWhiteZero);
}
break;
case TYPE_GRAY:
case TYPE_GRAY_ALPHA:
case TYPE_RGB:
case TYPE_RGB_ALPHA:
case TYPE_CMYK:
// Create a pixel interleaved SampleModel with decreasing
// band offsets.
int[] RGBOffsets = new int[numBands];
if(compression == COMP_JPEG_TTN2) {
for (int i=0; i<numBands; i++) {
RGBOffsets[i] = numBands - 1 - i;
}
} else {
for (int i=0; i<numBands; i++) {
RGBOffsets[i] = i;
}
}
sampleModel = createPixelInterleavedSampleModel(dataType,
tileWidth,
tileHeight,
numBands,
numBands*tileWidth,
RGBOffsets);
if(imageType == TYPE_GRAY || imageType == TYPE_RGB) {
colorModel =
ImageCodec.createComponentColorModel(sampleModel);
} else if (imageType == TYPE_CMYK) {
colorModel = ImageCodec.createComponentColorModel(sampleModel,
SimpleCMYKColorSpace.getInstance());
} else { // hasAlpha
// Transparency.OPAQUE signifies image data that is
// completely opaque, meaning that all pixels have an alpha
// value of 1.0. So the extra band gets ignored, which is
// what we want.
int transparency = Transparency.OPAQUE;
if(extraSamples == 1 || extraSamples == 2) {
// associated (premultiplied) alpha when == 1
// unassociated alpha when ==2
// Fix bug: 4699316
transparency = Transparency.TRANSLUCENT;
}
colorModel =
createAlphaComponentColorModel(dataType,
numBands,
extraSamples == 1,
transparency);
}
break;
case TYPE_GENERIC:
case TYPE_YCBCR_SUB:
// For this case we can't display the image, so we create a
// SampleModel with increasing bandOffsets, and keep the
// ColorModel as null, as there is no appropriate ColorModel.
int[] bandOffsets = new int[numBands];
for (int i=0; i<numBands; i++) {
bandOffsets[i] = i;
}
sampleModel =
createPixelInterleavedSampleModel(dataType,
tileWidth, tileHeight,
numBands, numBands * tileWidth,
bandOffsets);
colorModel = null;
break;
case TYPE_PALETTE:
// Get the colormap
TIFFField cfield = getField(dir, TIFFImageDecoder.TIFF_COLORMAP,
"Colormap");
colormap = cfield.getAsChars();
// Could be either 1 or 3 bands depending on whether we use
// IndexColorModel or not.
if (decodePaletteAsShorts) {
numBands = 3;