You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1942 lines
67 KiB
1942 lines
67 KiB
/* |
|
* Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions are met: |
|
* |
|
* -Redistributions of source code must retain the above copyright notice, this |
|
* list of conditions and the following disclaimer. |
|
* |
|
* -Redistribution in binary form must reproduct the above copyright notice, |
|
* this list of conditions and the following disclaimer in the documentation |
|
* and/or other materials provided with the distribution. |
|
* |
|
* Neither the name of Sun Microsystems, Inc. or the names of contributors may |
|
* be used to endorse or promote products derived from this software without |
|
* specific prior written permission. |
|
* |
|
* This software is provided "AS IS," without a warranty of any kind. ALL |
|
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY |
|
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR |
|
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE |
|
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING |
|
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS |
|
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, |
|
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER |
|
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF |
|
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGES. |
|
* |
|
* You acknowledge that Software is not designed,licensed or intended for use in |
|
* the design, construction, operation or maintenance of any nuclear facility. |
|
*/ |
|
package com.fr.third.JAI; |
|
import java.awt.Point; |
|
import java.awt.Rectangle; |
|
import java.awt.Transparency; |
|
import java.awt.color.ColorSpace; |
|
import java.awt.image.ColorModel; |
|
import java.awt.image.DataBuffer; |
|
import java.awt.image.DataBufferByte; |
|
import java.awt.image.DataBufferShort; |
|
import java.awt.image.DataBufferUShort; |
|
import java.awt.image.DataBufferInt; |
|
import java.awt.image.Raster; |
|
import java.awt.image.WritableRaster; |
|
import java.awt.image.RenderedImage; |
|
import java.awt.image.SampleModel; |
|
import java.awt.image.IndexColorModel; |
|
import java.awt.image.MultiPixelPackedSampleModel; |
|
import java.awt.image.PixelInterleavedSampleModel; |
|
import java.awt.image.ComponentColorModel; |
|
import java.io.ByteArrayInputStream; |
|
import java.io.File; |
|
import java.io.InputStream; |
|
import java.io.IOException; |
|
import java.lang.reflect.Constructor; |
|
import java.lang.reflect.Method; |
|
import java.text.MessageFormat; |
|
import java.util.Locale; |
|
import java.util.zip.DataFormatException; |
|
import java.util.zip.Inflater; |
|
import com.sun.image.codec.jpeg.JPEGCodec; |
|
import com.sun.image.codec.jpeg.JPEGDecodeParam; |
|
import com.sun.image.codec.jpeg.JPEGImageDecoder; |
|
|
|
public class TIFFImage extends SimpleRenderedImage { |
|
|
|
// Compression types |
|
public static final int COMP_NONE = 1; |
|
public static final int COMP_FAX_G3_1D = 2; |
|
public static final int COMP_FAX_G3_2D = 3; |
|
public static final int COMP_FAX_G4_2D = 4; |
|
public static final int COMP_LZW = 5; |
|
public static final int COMP_JPEG_OLD = 6; |
|
public static final int COMP_JPEG_TTN2 = 7; |
|
public static final int COMP_PACKBITS = 32773; |
|
public static final int COMP_DEFLATE = 32946; |
|
|
|
// Image types |
|
private static final int TYPE_UNSUPPORTED = -1; |
|
private static final int TYPE_BILEVEL = 0; |
|
private static final int TYPE_GRAY_4BIT = 1; |
|
private static final int TYPE_GRAY = 2; |
|
private static final int TYPE_GRAY_ALPHA = 3; |
|
private static final int TYPE_PALETTE = 4; |
|
private static final int TYPE_RGB = 5; |
|
private static final int TYPE_RGB_ALPHA = 6; |
|
private static final int TYPE_YCBCR_SUB = 7; |
|
private static final int TYPE_GENERIC = 8; |
|
|
|
// Incidental tags |
|
private static final int TIFF_JPEG_TABLES = 347; |
|
private static final int TIFF_YCBCR_SUBSAMPLING = 530; |
|
|
|
SeekableStream stream; |
|
private boolean isTiled; |
|
int tileSize; |
|
int tilesX, tilesY; |
|
long[] tileOffsets; |
|
long[] tileByteCounts; |
|
char[] colormap; |
|
int sampleSize; |
|
int compression; |
|
byte[] palette; |
|
int numBands; |
|
|
|
int chromaSubH; |
|
int chromaSubV; |
|
|
|
// Fax compression related variables |
|
long tiffT4Options; |
|
long tiffT6Options; |
|
int fillOrder; |
|
|
|
// LZW compression related variable |
|
int predictor; |
|
|
|
// TTN2 JPEG related variables |
|
JPEGDecodeParam decodeParam = null; |
|
boolean colorConvertJPEG = false; |
|
|
|
// DEFLATE variables |
|
Inflater inflater = null; |
|
|
|
// Endian-ness indicator |
|
boolean isBigEndian; |
|
|
|
int imageType; |
|
boolean isWhiteZero = false; |
|
int dataType; |
|
|
|
boolean decodePaletteAsShorts; |
|
|
|
// Decoders |
|
private TIFFFaxDecoder decoder = null; |
|
private TIFFLZWDecoder lzwDecoder = null; |
|
|
|
/** |
|
* Decode a buffer of data into a Raster with the specified location. |
|
* |
|
* @param data buffer contain an interchange or abbreviated datastream. |
|
* @param decodeParam decoding parameters; may be null unless the |
|
* data buffer contains an abbreviated datastream in which case |
|
* it may not be null or an error will occur. |
|
* @param colorConvert whether to perform color conversion; in this |
|
* case that would be limited to YCbCr-to-RGB. |
|
* @param minX the X position of the returned Raster. |
|
* @param minY the Y position of the returned Raster. |
|
*/ |
|
private static final Raster decodeJPEG(byte[] data, |
|
JPEGDecodeParam decodeParam, |
|
boolean colorConvert, |
|
int minX, |
|
int minY) { |
|
// Create an InputStream from the compressed data array. |
|
ByteArrayInputStream jpegStream = new ByteArrayInputStream(data); |
|
|
|
// Create a decoder. |
|
JPEGImageDecoder decoder = decodeParam == null ? |
|
JPEGCodec.createJPEGDecoder(jpegStream) : |
|
JPEGCodec.createJPEGDecoder(jpegStream, |
|
decodeParam); |
|
|
|
// Decode the compressed data into a Raster. |
|
Raster jpegRaster; |
|
try { |
|
jpegRaster = colorConvert ? |
|
decoder.decodeAsBufferedImage().getWritableTile(0, 0) : |
|
decoder.decodeAsRaster(); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
// Translate the decoded Raster to the specified location and return. |
|
return jpegRaster.createTranslatedChild(minX, minY); |
|
} |
|
|
|
/** |
|
* Inflates <code>deflated</code> into <code>inflated</code> using the |
|
* <code>Inflater</code> constructed during class instantiation. |
|
*/ |
|
private final void inflate(byte[] deflated, byte[] inflated) { |
|
inflater.setInput(deflated); |
|
try { |
|
inflater.inflate(inflated); |
|
} catch(DataFormatException dfe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage17")+": "+ |
|
dfe.getMessage()); |
|
} |
|
inflater.reset(); |
|
} |
|
|
|
/** |
|
* Creates a pixel-interleaved <code>SampleModel</code>. This is a hack |
|
* to work around a cast exception when using JAI with float data. |
|
*/ |
|
private final static SampleModel |
|
createPixelInterleavedSampleModel(int dataType, |
|
int tileWidth, |
|
int tileHeight, |
|
int pixelStride, |
|
int scanlineStride, |
|
int bandOffsets[]) { |
|
SampleModel sampleModel = null; |
|
|
|
if(dataType == DataBuffer.TYPE_FLOAT) { |
|
// This is a hack to make this work with JAI which in some |
|
// cases downcasts the DataBuffer to a type-specific class. |
|
// In the case of float data this current means the JAI class |
|
// javax.media.jai.DataBufferFloat. |
|
try { |
|
Class rfClass = |
|
Class.forName("javax.media.jai.RasterFactory"); |
|
Class[] paramTypes = new Class[] {int.class, int.class, |
|
int.class, int.class, |
|
int.class, int[].class}; |
|
Method rfMthd = |
|
rfClass.getMethod("createPixelInterleavedSampleModel", |
|
paramTypes); |
|
Object[] params = |
|
new Object[] {new Integer(dataType), |
|
new Integer(tileWidth), |
|
new Integer(tileHeight), |
|
new Integer(pixelStride), |
|
new Integer(scanlineStride), |
|
bandOffsets}; |
|
sampleModel = (SampleModel)rfMthd.invoke(null, params); |
|
} catch(Exception e) { |
|
// Deliberately ignore the Exception. |
|
} |
|
} |
|
|
|
// Create a SampleModel for non-float data or, in the case of |
|
// float data, if it is still null. This latter case should occur |
|
// if and only if the decoder is being used without JAI. |
|
if(dataType != DataBuffer.TYPE_FLOAT || sampleModel == null) { |
|
sampleModel = |
|
RasterFactory.createPixelInterleavedSampleModel(dataType, |
|
tileWidth, |
|
tileHeight, |
|
pixelStride, |
|
scanlineStride, |
|
bandOffsets); |
|
} |
|
|
|
return sampleModel; |
|
} |
|
|
|
/** |
|
* Return as a long[] the value of a TIFF_LONG or TIFF_SHORT field. |
|
*/ |
|
private final long[] getFieldAsLongs(TIFFField field) { |
|
long[] value = null; |
|
|
|
if(field.getType() == TIFFField.TIFF_SHORT) { |
|
char[] charValue = field.getAsChars(); |
|
value = new long[charValue.length]; |
|
for(int i = 0; i < charValue.length; i++) { |
|
value[i] = charValue[i] & 0xffff; |
|
} |
|
} else if(field.getType() == TIFFField.TIFF_LONG) { |
|
value = field.getAsLongs(); |
|
} else { |
|
throw new RuntimeException(); |
|
} |
|
|
|
return value; |
|
} |
|
|
|
/* |
|
* Check whether the specified tag exists in the specified |
|
* TIFFDirectory. If not, throw an error message. Otherwise |
|
* return the TIFFField. |
|
*/ |
|
private TIFFField getField(TIFFDirectory dir, int tagID, String tagName) { |
|
TIFFField field = dir.getField(tagID); |
|
if (field == null) { |
|
MessageFormat mf = |
|
new MessageFormat(JaiI18N.getString("TIFFImage5")); |
|
mf.setLocale(Locale.getDefault()); |
|
throw new RuntimeException(mf.format(new Object[] {tagName})); |
|
} else { |
|
return field; |
|
} |
|
} |
|
|
|
/** |
|
* Constructs a TIFFImage that acquires its data from a given |
|
* SeekableStream and reads from a particular IFD of the stream. |
|
* The index of the first IFD is 0. |
|
* |
|
* @param stream the SeekableStream to read from. |
|
* @param param an instance of TIFFDecodeParam, or null. |
|
* @param directory the index of the IFD to read from. |
|
*/ |
|
public TIFFImage(SeekableStream stream, |
|
TIFFDecodeParam param, |
|
int directory) |
|
throws IOException { |
|
|
|
this.stream = stream; |
|
if (param == null) { |
|
param = new TIFFDecodeParam(); |
|
} |
|
|
|
decodePaletteAsShorts = param.getDecodePaletteAsShorts(); |
|
|
|
// Read the specified directory. |
|
TIFFDirectory dir = param.getIFDOffset() == null ? |
|
new TIFFDirectory(stream, directory) : |
|
new TIFFDirectory(stream, param.getIFDOffset().longValue(), |
|
directory); |
|
|
|
// 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. |
|
TIFFField photoInterpField = |
|
getField(dir, |
|
TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION, |
|
"Photometric Interpretation"); |
|
int photometricType = (int)photoInterpField.getAsLong(0); |
|
|
|
// 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 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 CMYK, 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 |
|
|
|
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) { |
|
// 2^32 - 1 (effectively infinity, entire image is 1 strip) |
|
tileHeight = height; |
|
} else { |
|
tileHeight = (int)l; |
|
} |
|
} |
|
|
|
TIFFField tileOffsetsField = |
|
getField(dir, |
|
TIFFImageDecoder.TIFF_STRIP_OFFSETS, |
|
"Strip Offsets"); |
|
tileOffsets = getFieldAsLongs(tileOffsetsField); |
|
|
|
TIFFField tileByteCountsField = |
|
getField(dir, |
|
TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS, |
|
"Strip Byte Counts"); |
|
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: |
|
// 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 { // 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) { // associated (premultiplied) alpha |
|
transparency = Transparency.TRANSLUCENT; |
|
} else if(extraSamples == 2) { // unassociated alpha |
|
transparency = Transparency.BITMASK; |
|
} |
|
|
|
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; |
|
|
|
// If no SampleFormat tag was specified and if the |
|
// sampleSize is less than or equal to 8, then the |
|
// dataType was initially set to byte, but now we want to |
|
// expand the palette as shorts, so the dataType should |
|
// be ushort. |
|
if (dataType == DataBuffer.TYPE_BYTE) { |
|
dataType = DataBuffer.TYPE_USHORT; |
|
} |
|
|
|
// Data will have to be unpacked into a 3 band short image |
|
// as we do not have a IndexColorModel that can deal with |
|
// a colormodel whose entries are of short data type. |
|
sampleModel = |
|
RasterFactory.createPixelInterleavedSampleModel(dataType, |
|
tileWidth, |
|
tileHeight, |
|
numBands); |
|
colorModel = ImageCodec.createComponentColorModel(sampleModel); |
|
|
|
} else { |
|
|
|
numBands = 1; |
|
|
|
if (sampleSize == 4) { |
|
// Pixel data will not be unpacked, will use MPPSM to store |
|
// packed data and IndexColorModel to do the unpacking. |
|
sampleModel = |
|
new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, |
|
tileWidth, |
|
tileHeight, |
|
sampleSize); |
|
} else if (sampleSize == 8) { |
|
sampleModel = |
|
RasterFactory.createPixelInterleavedSampleModel( |
|
DataBuffer.TYPE_BYTE, |
|
tileWidth, |
|
tileHeight, |
|
numBands); |
|
} else if (sampleSize == 16) { |
|
|
|
// Here datatype has to be unsigned since we are storing |
|
// indices into the IndexColorModel palette. Ofcourse |
|
// the actual palette entries are allowed to be negative. |
|
dataType = DataBuffer.TYPE_USHORT; |
|
sampleModel = |
|
RasterFactory.createPixelInterleavedSampleModel( |
|
DataBuffer.TYPE_USHORT, |
|
tileWidth, |
|
tileHeight, |
|
numBands); |
|
} |
|
|
|
int bandLength = colormap.length/3; |
|
byte r[] = new byte[bandLength]; |
|
byte g[] = new byte[bandLength]; |
|
byte b[] = new byte[bandLength]; |
|
|
|
int gIndex = bandLength; |
|
int bIndex = bandLength * 2; |
|
|
|
if (dataType == DataBuffer.TYPE_SHORT) { |
|
|
|
for (int i=0; i<bandLength; i++) { |
|
r[i] = param.decodeSigned16BitsTo8Bits( |
|
(short)colormap[i]); |
|
g[i] = param.decodeSigned16BitsTo8Bits( |
|
(short)colormap[gIndex+i]); |
|
b[i] = param.decodeSigned16BitsTo8Bits( |
|
(short)colormap[bIndex+i]); |
|
} |
|
|
|
} else { |
|
|
|
for (int i=0; i<bandLength; i++) { |
|
r[i] = param.decode16BitsTo8Bits(colormap[i] & 0xffff); |
|
g[i] = param.decode16BitsTo8Bits(colormap[gIndex+i] & |
|
0xffff); |
|
b[i] = param.decode16BitsTo8Bits(colormap[bIndex+i] & |
|
0xffff); |
|
} |
|
|
|
} |
|
|
|
colorModel = new IndexColorModel(sampleSize, |
|
bandLength, r, g, b); |
|
} |
|
break; |
|
|
|
default: |
|
throw new RuntimeException("TIFFImage4"); |
|
} |
|
} |
|
|
|
/** |
|
* Reads a private IFD from a given offset in the stream. This |
|
* method may be used to obtain IFDs that are referenced |
|
* only by private tag values. |
|
*/ |
|
public TIFFDirectory getPrivateIFD(long offset) throws IOException { |
|
return new TIFFDirectory(stream, offset, 0); |
|
} |
|
|
|
/** |
|
* Returns tile (tileX, tileY) as a Raster. |
|
*/ |
|
public synchronized Raster getTile(int tileX, int tileY) { |
|
if ((tileX < 0) || (tileX >= tilesX) || |
|
(tileY < 0) || (tileY >= tilesY)) { |
|
throw new IllegalArgumentException(JaiI18N.getString("TIFFImage12")); |
|
} |
|
|
|
// Get the data array out of the DataBuffer |
|
byte bdata[] = null; |
|
short sdata[] = null; |
|
int idata[] = null; |
|
float fdata[] = null; |
|
DataBuffer buffer = sampleModel.createDataBuffer(); |
|
|
|
int dataType = sampleModel.getDataType(); |
|
if (dataType == DataBuffer.TYPE_BYTE) { |
|
bdata = ((DataBufferByte)buffer).getData(); |
|
} else if (dataType == DataBuffer.TYPE_USHORT) { |
|
sdata = ((DataBufferUShort)buffer).getData(); |
|
} else if (dataType == DataBuffer.TYPE_SHORT) { |
|
sdata = ((DataBufferShort)buffer).getData(); |
|
} else if (dataType == DataBuffer.TYPE_INT) { |
|
idata = ((DataBufferInt)buffer).getData(); |
|
} else if (dataType == DataBuffer.TYPE_FLOAT) { |
|
if(buffer instanceof DataBufferFloat) { |
|
fdata = ((DataBufferFloat)buffer).getData(); |
|
} else { |
|
// This is a hack to make this work with JAI which in some |
|
// cases downcasts the DataBuffer to a type-specific class. |
|
// In the case of float data this current means the JAI class |
|
// javax.media.jai.DataBufferFloat. |
|
try { |
|
Method getDataMethod = |
|
buffer.getClass().getMethod("getData", null); |
|
fdata = (float[])getDataMethod.invoke(buffer, null); |
|
} catch(Exception e) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage18")); |
|
} |
|
} |
|
} |
|
|
|
WritableRaster tile = |
|
(WritableRaster)RasterFactory.createWritableRaster(sampleModel, |
|
buffer, |
|
new Point(tileXToX(tileX), |
|
tileYToY(tileY))); |
|
|
|
// Save original file pointer position and seek to tile data location. |
|
long save_offset = 0; |
|
try { |
|
save_offset = stream.getFilePointer(); |
|
stream.seek(tileOffsets[tileY*tilesX + tileX]); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
// Number of bytes in this tile (strip) after compression. |
|
int byteCount = (int)tileByteCounts[tileY*tilesX + tileX]; |
|
|
|
// Find out the number of bytes in the current tile. If the image is |
|
// tiled this may include pixels which are outside of the image bounds |
|
// if the image width and height are not multiples of the tile width |
|
// and height respectively. |
|
Rectangle tileRect = new Rectangle(tileXToX(tileX), tileYToY(tileY), |
|
tileWidth, tileHeight); |
|
Rectangle newRect = isTiled ? |
|
tileRect : tileRect.intersection(getBounds()); |
|
int unitsInThisTile = newRect.width * newRect.height * numBands; |
|
|
|
// Allocate read buffer if needed. |
|
byte data[] = compression != COMP_NONE || imageType == TYPE_PALETTE ? |
|
new byte[byteCount] : null; |
|
|
|
// Read the data, uncompressing as needed. There are four cases: |
|
// bilevel, palette-RGB, 4-bit grayscale, and everything else. |
|
if(imageType == TYPE_BILEVEL) { // bilevel |
|
try { |
|
if (compression == COMP_PACKBITS) { |
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since the decompressed data will still be packed |
|
// 8 pixels into 1 byte, calculate bytesInThisTile |
|
int bytesInThisTile; |
|
if ((newRect.width % 8) == 0) { |
|
bytesInThisTile = (newRect.width/8) * newRect.height; |
|
} else { |
|
bytesInThisTile = |
|
(newRect.width/8 + 1) * newRect.height; |
|
} |
|
decodePackbits(data, bytesInThisTile, bdata); |
|
} else if (compression == COMP_LZW) { |
|
stream.readFully(data, 0, byteCount); |
|
lzwDecoder.decode(data, bdata, newRect.height); |
|
} else if (compression == COMP_FAX_G3_1D) { |
|
stream.readFully(data, 0, byteCount); |
|
decoder.decode1D(bdata, data, 0, newRect.height); |
|
} else if (compression == COMP_FAX_G3_2D) { |
|
stream.readFully(data, 0, byteCount); |
|
decoder.decode2D(bdata, data, 0, newRect.height, |
|
tiffT4Options); |
|
} else if (compression == COMP_FAX_G4_2D) { |
|
stream.readFully(data, 0, byteCount); |
|
decoder.decodeT6(bdata, data, 0, newRect.height, |
|
tiffT6Options); |
|
} else if (compression == COMP_DEFLATE) { |
|
stream.readFully(data, 0, byteCount); |
|
inflate(data, bdata); |
|
} else if (compression == COMP_NONE) { |
|
stream.readFully(bdata, 0, byteCount); |
|
} |
|
|
|
stream.seek(save_offset); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
} else if(imageType == TYPE_PALETTE) { // palette-RGB |
|
if (sampleSize == 16) { |
|
|
|
if (decodePaletteAsShorts) { |
|
|
|
short tempData[]= null; |
|
|
|
// At this point the data is 1 banded and will |
|
// become 3 banded only after we've done the palette |
|
// lookup, since unitsInThisTile was calculated with |
|
// 3 bands, we need to divide this by 3. |
|
int unitsBeforeLookup = unitsInThisTile / 3; |
|
|
|
// Since unitsBeforeLookup is the number of shorts, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply it by 2 in order to figure out |
|
// how many bytes we'll get after decompression. |
|
int entries = unitsBeforeLookup * 2; |
|
|
|
// Read the data, if compressed, decode it, reset the pointer |
|
try { |
|
|
|
if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
byte byteArray[] = new byte[entries]; |
|
decodePackbits(data, entries, byteArray); |
|
tempData = new short[unitsBeforeLookup]; |
|
interpretBytesAsShorts(byteArray, tempData, |
|
unitsBeforeLookup); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
// Read in all the compressed data for this tile |
|
stream.readFully(data, 0, byteCount); |
|
|
|
byte byteArray[] = new byte[entries]; |
|
lzwDecoder.decode(data, byteArray, newRect.height); |
|
tempData = new short[unitsBeforeLookup]; |
|
interpretBytesAsShorts(byteArray, tempData, |
|
unitsBeforeLookup); |
|
|
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
byte byteArray[] = new byte[entries]; |
|
inflate(data, byteArray); |
|
tempData = new short[unitsBeforeLookup]; |
|
interpretBytesAsShorts(byteArray, tempData, |
|
unitsBeforeLookup); |
|
|
|
} else if (compression == COMP_NONE) { |
|
|
|
// byteCount tells us how many bytes are there |
|
// in this tile, but we need to read in shorts, |
|
// which will take half the space, so while |
|
// allocating we divide byteCount by 2. |
|
tempData = new short[byteCount/2]; |
|
readShorts(byteCount/2, tempData); |
|
} |
|
|
|
stream.seek(save_offset); |
|
|
|
} catch (IOException ioe) { |
|
throw new RuntimeException( |
|
JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
if (dataType == DataBuffer.TYPE_USHORT) { |
|
|
|
// Expand the palette image into an rgb image with ushort |
|
// data type. |
|
int cmapValue; |
|
int count = 0, lookup, len = colormap.length/3; |
|
int len2 = len * 2; |
|
for (int i=0; i<unitsBeforeLookup; i++) { |
|
// Get the index into the colormap |
|
lookup = tempData[i] & 0xffff; |
|
// Get the blue value |
|
cmapValue = colormap[lookup+len2]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
// Get the green value |
|
cmapValue = colormap[lookup+len]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
// Get the red value |
|
cmapValue = colormap[lookup]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
} |
|
|
|
} else if (dataType == DataBuffer.TYPE_SHORT) { |
|
|
|
// Expand the palette image into an rgb image with |
|
// short data type. |
|
int cmapValue; |
|
int count = 0, lookup, len = colormap.length/3; |
|
int len2 = len * 2; |
|
for (int i=0; i<unitsBeforeLookup; i++) { |
|
// Get the index into the colormap |
|
lookup = tempData[i] & 0xffff; |
|
// Get the blue value |
|
cmapValue = colormap[lookup+len2]; |
|
sdata[count++] = (short)cmapValue; |
|
// Get the green value |
|
cmapValue = colormap[lookup+len]; |
|
sdata[count++] = (short)cmapValue; |
|
// Get the red value |
|
cmapValue = colormap[lookup]; |
|
sdata[count++] = (short)cmapValue; |
|
} |
|
} |
|
|
|
} else { |
|
|
|
// No lookup being done here, when RGB values are needed, |
|
// the associated IndexColorModel can be used to get them. |
|
|
|
try { |
|
|
|
if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of shorts, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 2 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
int bytesInThisTile = unitsInThisTile * 2; |
|
|
|
byte byteArray[] = new byte[bytesInThisTile]; |
|
decodePackbits(data, bytesInThisTile, byteArray); |
|
interpretBytesAsShorts(byteArray, sdata, |
|
unitsInThisTile); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of shorts, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 2 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
byte byteArray[] = new byte[unitsInThisTile * 2]; |
|
lzwDecoder.decode(data, byteArray, newRect.height); |
|
interpretBytesAsShorts(byteArray, sdata, |
|
unitsInThisTile); |
|
|
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
byte byteArray[] = new byte[unitsInThisTile * 2]; |
|
inflate(data, byteArray); |
|
interpretBytesAsShorts(byteArray, sdata, |
|
unitsInThisTile); |
|
|
|
} else if (compression == COMP_NONE) { |
|
|
|
readShorts(byteCount/2, sdata); |
|
} |
|
|
|
stream.seek(save_offset); |
|
|
|
} catch (IOException ioe) { |
|
throw new RuntimeException( |
|
JaiI18N.getString("TIFFImage13")); |
|
} |
|
} |
|
|
|
} else if (sampleSize == 8) { |
|
|
|
if (decodePaletteAsShorts) { |
|
|
|
byte tempData[]= null; |
|
|
|
// At this point the data is 1 banded and will |
|
// become 3 banded only after we've done the palette |
|
// lookup, since unitsInThisTile was calculated with |
|
// 3 bands, we need to divide this by 3. |
|
int unitsBeforeLookup = unitsInThisTile / 3; |
|
|
|
// Read the data, if compressed, decode it, reset the pointer |
|
try { |
|
|
|
if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
tempData = new byte[unitsBeforeLookup]; |
|
decodePackbits(data, unitsBeforeLookup, tempData); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
tempData = new byte[unitsBeforeLookup]; |
|
lzwDecoder.decode(data, tempData, newRect.height); |
|
|
|
} else if (compression == COMP_JPEG_TTN2) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
Raster tempTile = decodeJPEG(data, |
|
decodeParam, |
|
colorConvertJPEG, |
|
tile.getMinX(), |
|
tile.getMinY()); |
|
int[] tempPixels = new int[unitsBeforeLookup]; |
|
tempTile.getPixels(tile.getMinX(), |
|
tile.getMinY(), |
|
tile.getWidth(), |
|
tile.getHeight(), |
|
tempPixels); |
|
tempData = new byte[unitsBeforeLookup]; |
|
for(int i = 0; i < unitsBeforeLookup; i++) { |
|
tempData[i] = (byte)tempPixels[i]; |
|
} |
|
|
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
tempData = new byte[unitsBeforeLookup]; |
|
inflate(data, tempData); |
|
|
|
} else if (compression == COMP_NONE) { |
|
|
|
tempData = new byte[byteCount]; |
|
stream.readFully(tempData, 0, byteCount); |
|
} |
|
|
|
stream.seek(save_offset); |
|
|
|
} catch (IOException ioe) { |
|
throw new RuntimeException( |
|
JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
// Expand the palette image into an rgb image with ushort |
|
// data type. |
|
int cmapValue; |
|
int count = 0, lookup, len = colormap.length/3; |
|
int len2 = len * 2; |
|
for (int i=0; i<unitsBeforeLookup; i++) { |
|
// Get the index into the colormap |
|
lookup = tempData[i] & 0xff; |
|
// Get the blue value |
|
cmapValue = colormap[lookup+len2]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
// Get the green value |
|
cmapValue = colormap[lookup+len]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
// Get the red value |
|
cmapValue = colormap[lookup]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
} |
|
} else { |
|
|
|
// No lookup being done here, when RGB values are needed, |
|
// the associated IndexColorModel can be used to get them. |
|
|
|
try { |
|
|
|
if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
decodePackbits(data, unitsInThisTile, bdata); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
lzwDecoder.decode(data, bdata, newRect.height); |
|
|
|
} else if (compression == COMP_JPEG_TTN2) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
tile.setRect(decodeJPEG(data, |
|
decodeParam, |
|
colorConvertJPEG, |
|
tile.getMinX(), |
|
tile.getMinY())); |
|
|
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
inflate(data, bdata); |
|
|
|
} else if (compression == COMP_NONE) { |
|
|
|
stream.readFully(bdata, 0, byteCount); |
|
} |
|
|
|
stream.seek(save_offset); |
|
|
|
} catch (IOException ioe) { |
|
throw new RuntimeException( |
|
JaiI18N.getString("TIFFImage13")); |
|
} |
|
} |
|
|
|
} else if (sampleSize == 4) { |
|
|
|
int padding = (newRect.width % 2 == 0) ? 0 : 1; |
|
int bytesPostDecoding = ((newRect.width/2 + padding) * |
|
newRect.height); |
|
|
|
// Output short images |
|
if (decodePaletteAsShorts) { |
|
|
|
byte tempData[] = null; |
|
|
|
try { |
|
stream.readFully(data, 0, byteCount); |
|
stream.seek(save_offset); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException( |
|
JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
// If compressed, decode the data. |
|
if (compression == COMP_PACKBITS) { |
|
|
|
tempData = new byte[bytesPostDecoding]; |
|
decodePackbits(data, bytesPostDecoding, tempData); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
tempData = new byte[bytesPostDecoding]; |
|
lzwDecoder.decode(data, tempData, newRect.height); |
|
|
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
tempData = new byte[bytesPostDecoding]; |
|
inflate(data, tempData); |
|
|
|
} else if (compression == COMP_NONE) { |
|
|
|
tempData = data; |
|
} |
|
|
|
int bytes = unitsInThisTile / 3; |
|
|
|
// Unpack the 2 pixels packed into each byte. |
|
data = new byte[bytes]; |
|
|
|
int srcCount = 0, dstCount = 0; |
|
for (int j=0; j<newRect.height; j++) { |
|
for (int i=0; i<newRect.width/2; i++) { |
|
data[dstCount++] = |
|
(byte)((tempData[srcCount] & 0xf0) >> 4); |
|
data[dstCount++] = |
|
(byte)(tempData[srcCount++] & 0x0f); |
|
} |
|
|
|
if (padding == 1) { |
|
data[dstCount++] = |
|
(byte)((tempData[srcCount++] & 0xf0) >> 4); |
|
} |
|
} |
|
|
|
int len = colormap.length/3; |
|
int len2 = len*2; |
|
int cmapValue, lookup; |
|
int count = 0; |
|
for (int i=0; i<bytes; i++) { |
|
lookup = data[i] & 0xff; |
|
cmapValue = colormap[lookup+len2]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
cmapValue = colormap[lookup+len]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
cmapValue = colormap[lookup]; |
|
sdata[count++] = (short)(cmapValue & 0xffff); |
|
} |
|
} else { |
|
|
|
// Output byte values, use IndexColorModel for unpacking |
|
try { |
|
|
|
// If compressed, decode the data. |
|
if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
decodePackbits(data, bytesPostDecoding, bdata); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
lzwDecoder.decode(data, bdata, newRect.height); |
|
|
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
inflate(data, bdata); |
|
|
|
} else if (compression == COMP_NONE) { |
|
|
|
stream.readFully(bdata, 0, byteCount); |
|
} |
|
|
|
stream.seek(save_offset); |
|
|
|
} catch (IOException ioe) { |
|
throw new RuntimeException( |
|
JaiI18N.getString("TIFFImage13")); |
|
} |
|
} |
|
} |
|
} else if(imageType == TYPE_GRAY_4BIT) { // 4-bit gray |
|
try { |
|
if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since the decompressed data will still be packed |
|
// 2 pixels into 1 byte, calculate bytesInThisTile |
|
int bytesInThisTile; |
|
if ((newRect.width % 8) == 0) { |
|
bytesInThisTile = (newRect.width/2) * newRect.height; |
|
} else { |
|
bytesInThisTile = (newRect.width/2 + 1) * |
|
newRect.height; |
|
} |
|
|
|
decodePackbits(data, bytesInThisTile, bdata); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
lzwDecoder.decode(data, bdata, newRect.height); |
|
|
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
inflate(data, bdata); |
|
|
|
} else { |
|
|
|
stream.readFully(bdata, 0, byteCount); |
|
} |
|
|
|
stream.seek(save_offset); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
} else { // everything else |
|
try { |
|
|
|
if (sampleSize == 8) { |
|
|
|
if (compression == COMP_NONE) { |
|
|
|
stream.readFully(bdata, 0, byteCount); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
lzwDecoder.decode(data, bdata, newRect.height); |
|
|
|
} else if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
decodePackbits(data, unitsInThisTile, bdata); |
|
|
|
} else if (compression == COMP_JPEG_TTN2) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
tile.setRect(decodeJPEG(data, |
|
decodeParam, |
|
colorConvertJPEG, |
|
tile.getMinX(), |
|
tile.getMinY())); |
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
inflate(data, bdata); |
|
} |
|
|
|
} else if (sampleSize == 16) { |
|
|
|
if (compression == COMP_NONE) { |
|
|
|
readShorts(byteCount/2, sdata); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of shorts, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 2 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
byte byteArray[] = new byte[unitsInThisTile * 2]; |
|
lzwDecoder.decode(data, byteArray, newRect.height); |
|
interpretBytesAsShorts(byteArray, sdata, |
|
unitsInThisTile); |
|
|
|
} else if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of shorts, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 2 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
int bytesInThisTile = unitsInThisTile * 2; |
|
|
|
byte byteArray[] = new byte[bytesInThisTile]; |
|
decodePackbits(data, bytesInThisTile, byteArray); |
|
interpretBytesAsShorts(byteArray, sdata, |
|
unitsInThisTile); |
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
byte byteArray[] = new byte[unitsInThisTile * 2]; |
|
inflate(data, byteArray); |
|
interpretBytesAsShorts(byteArray, sdata, |
|
unitsInThisTile); |
|
|
|
} |
|
} else if (sampleSize == 32 && |
|
dataType == DataBuffer.TYPE_INT) { // redundant |
|
if (compression == COMP_NONE) { |
|
|
|
readInts(byteCount/4, idata); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of ints, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 4 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
byte byteArray[] = new byte[unitsInThisTile * 4]; |
|
lzwDecoder.decode(data, byteArray, newRect.height); |
|
interpretBytesAsInts(byteArray, idata, |
|
unitsInThisTile); |
|
|
|
} else if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of ints, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 4 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
int bytesInThisTile = unitsInThisTile * 4; |
|
|
|
byte byteArray[] = new byte[bytesInThisTile]; |
|
decodePackbits(data, bytesInThisTile, byteArray); |
|
interpretBytesAsInts(byteArray, idata, |
|
unitsInThisTile); |
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
byte byteArray[] = new byte[unitsInThisTile * 4]; |
|
inflate(data, byteArray); |
|
interpretBytesAsInts(byteArray, idata, |
|
unitsInThisTile); |
|
|
|
} |
|
} else if (sampleSize == 32 && |
|
dataType == DataBuffer.TYPE_FLOAT) { // redundant |
|
if (compression == COMP_NONE) { |
|
|
|
readFloats(byteCount/4, fdata); |
|
|
|
} else if (compression == COMP_LZW) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of floats, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 4 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
byte byteArray[] = new byte[unitsInThisTile * 4]; |
|
lzwDecoder.decode(data, byteArray, newRect.height); |
|
interpretBytesAsFloats(byteArray, fdata, |
|
unitsInThisTile); |
|
|
|
} else if (compression == COMP_PACKBITS) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
|
|
// Since unitsInThisTile is the number of floats, |
|
// but we do our decompression in terms of bytes, we |
|
// need to multiply unitsInThisTile by 4 in order to |
|
// figure out how many bytes we'll get after |
|
// decompression. |
|
int bytesInThisTile = unitsInThisTile * 4; |
|
|
|
byte byteArray[] = new byte[bytesInThisTile]; |
|
decodePackbits(data, bytesInThisTile, byteArray); |
|
interpretBytesAsFloats(byteArray, fdata, |
|
unitsInThisTile); |
|
} else if (compression == COMP_DEFLATE) { |
|
|
|
stream.readFully(data, 0, byteCount); |
|
byte byteArray[] = new byte[unitsInThisTile * 4]; |
|
inflate(data, byteArray); |
|
interpretBytesAsFloats(byteArray, fdata, |
|
unitsInThisTile); |
|
|
|
} |
|
} |
|
|
|
stream.seek(save_offset); |
|
|
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
// Modify the data for certain special cases. |
|
switch(imageType) { |
|
case TYPE_GRAY: |
|
case TYPE_GRAY_ALPHA: |
|
if(isWhiteZero) { |
|
// Since we are using a ComponentColorModel with this |
|
// image, we need to change the WhiteIsZero data to |
|
// BlackIsZero data so it will display properly. |
|
if (dataType == DataBuffer.TYPE_BYTE && |
|
!(colorModel instanceof IndexColorModel)) { |
|
|
|
for (int l = 0; l < bdata.length; l += numBands) { |
|
bdata[l] = (byte)(255 - bdata[l]); |
|
} |
|
} else if (dataType == DataBuffer.TYPE_USHORT) { |
|
|
|
int ushortMax = Short.MAX_VALUE - Short.MIN_VALUE; |
|
for (int l = 0; l < sdata.length; l += numBands) { |
|
sdata[l] = (short)(ushortMax - sdata[l]); |
|
} |
|
|
|
} else if (dataType == DataBuffer.TYPE_SHORT) { |
|
|
|
for (int l = 0; l < sdata.length; l += numBands) { |
|
sdata[l] = (short)(~sdata[l]); |
|
} |
|
} else if (dataType == DataBuffer.TYPE_INT) { |
|
|
|
long uintMax = Integer.MAX_VALUE - Integer.MIN_VALUE; |
|
for (int l = 0; l < idata.length; l += numBands) { |
|
idata[l] = (int)(uintMax - (long)idata[l]); |
|
} |
|
} |
|
} |
|
break; |
|
case TYPE_YCBCR_SUB: |
|
// Post-processing for YCbCr with subsampled chrominance: |
|
// simply replicate the chroma channels for displayability. |
|
int pixelsPerDataUnit = chromaSubH*chromaSubV; |
|
|
|
int numH = newRect.width/chromaSubH; |
|
int numV = newRect.height/chromaSubV; |
|
|
|
byte[] tempData = new byte[numH*numV*(pixelsPerDataUnit + 2)]; |
|
System.arraycopy(bdata, 0, tempData, 0, tempData.length); |
|
|
|
int samplesPerDataUnit = pixelsPerDataUnit*3; |
|
int[] pixels = new int[samplesPerDataUnit]; |
|
|
|
int bOffset = 0; |
|
int offsetCb = pixelsPerDataUnit; |
|
int offsetCr = offsetCb + 1; |
|
|
|
int y = newRect.y; |
|
for(int j = 0; j < numV; j++) { |
|
int x = newRect.x; |
|
for(int i = 0; i < numH; i++) { |
|
int Cb = tempData[bOffset + offsetCb]; |
|
int Cr = tempData[bOffset + offsetCr]; |
|
int k = 0; |
|
while(k < samplesPerDataUnit) { |
|
pixels[k++] = tempData[bOffset++]; |
|
pixels[k++] = Cb; |
|
pixels[k++] = Cr; |
|
} |
|
bOffset += 2; |
|
tile.setPixels(x, y, chromaSubH, chromaSubV, pixels); |
|
x += chromaSubH; |
|
} |
|
y += chromaSubV; |
|
} |
|
|
|
break; |
|
} |
|
} |
|
|
|
return tile; |
|
} |
|
|
|
private void readShorts(int shortCount, short shortArray[]) { |
|
|
|
// Since each short consists of 2 bytes, we need a |
|
// byte array of double size |
|
int byteCount = 2 * shortCount; |
|
byte byteArray[] = new byte[byteCount]; |
|
|
|
try { |
|
stream.readFully(byteArray, 0, byteCount); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
interpretBytesAsShorts(byteArray, shortArray, shortCount); |
|
} |
|
|
|
private void readInts(int intCount, int intArray[]) { |
|
|
|
// Since each int consists of 4 bytes, we need a |
|
// byte array of quadruple size |
|
int byteCount = 4 * intCount; |
|
byte byteArray[] = new byte[byteCount]; |
|
|
|
try { |
|
stream.readFully(byteArray, 0, byteCount); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
interpretBytesAsInts(byteArray, intArray, intCount); |
|
} |
|
|
|
private void readFloats(int floatCount, float floatArray[]) { |
|
|
|
// Since each float consists of 4 bytes, we need a |
|
// byte array of quadruple size |
|
int byteCount = 4 * floatCount; |
|
byte byteArray[] = new byte[byteCount]; |
|
|
|
try { |
|
stream.readFully(byteArray, 0, byteCount); |
|
} catch (IOException ioe) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage13")); |
|
} |
|
|
|
interpretBytesAsFloats(byteArray, floatArray, floatCount); |
|
} |
|
|
|
// Method to interpret a byte array to a short array, depending on |
|
// whether the bytes are stored in a big endian or little endian format. |
|
private void interpretBytesAsShorts(byte byteArray[], |
|
short shortArray[], |
|
int shortCount) { |
|
|
|
int j = 0; |
|
int firstByte, secondByte; |
|
|
|
if (isBigEndian) { |
|
|
|
for (int i=0; i<shortCount; i++) { |
|
firstByte = byteArray[j++] & 0xff; |
|
secondByte = byteArray[j++] & 0xff; |
|
shortArray[i] = (short)((firstByte << 8) + secondByte); |
|
} |
|
|
|
} else { |
|
|
|
for (int i=0; i<shortCount; i++) { |
|
firstByte = byteArray[j++] & 0xff; |
|
secondByte = byteArray[j++] & 0xff; |
|
shortArray[i] = (short)((secondByte << 8) + firstByte); |
|
} |
|
} |
|
} |
|
|
|
// Method to interpret a byte array to a int array, depending on |
|
// whether the bytes are stored in a big endian or little endian format. |
|
private void interpretBytesAsInts(byte byteArray[], |
|
int intArray[], |
|
int intCount) { |
|
|
|
int j = 0; |
|
|
|
if (isBigEndian) { |
|
|
|
for (int i=0; i<intCount; i++) { |
|
intArray[i] = |
|
(int)(((byteArray[j++] & 0xff) << 24) | |
|
((byteArray[j++] & 0xff) << 16) | |
|
((byteArray[j++] & 0xff) << 8) | |
|
(byteArray[j++] & 0xff)); |
|
} |
|
|
|
} else { |
|
|
|
for (int i=0; i<intCount; i++) { |
|
intArray[i] = |
|
(int)((byteArray[j++] & 0xff) | |
|
((byteArray[j++] & 0xff) << 8) | |
|
((byteArray[j++] & 0xff) << 16) | |
|
((byteArray[j++] & 0xff) << 24)); |
|
} |
|
} |
|
} |
|
|
|
// Method to interpret a byte array to a float array, depending on |
|
// whether the bytes are stored in a big endian or little endian format. |
|
private void interpretBytesAsFloats(byte byteArray[], |
|
float floatArray[], |
|
int floatCount) { |
|
|
|
int j = 0; |
|
|
|
if (isBigEndian) { |
|
|
|
for (int i=0; i<floatCount; i++) { |
|
int value = (int)(((byteArray[j++] & 0xff) << 24) | |
|
((byteArray[j++] & 0xff) << 16) | |
|
((byteArray[j++] & 0xff) << 8) | |
|
(byteArray[j++] & 0xff)); |
|
floatArray[i] = Float.intBitsToFloat(value); |
|
} |
|
|
|
} else { |
|
|
|
for (int i=0; i<floatCount; i++) { |
|
int value = (int)((byteArray[j++] & 0xff) | |
|
((byteArray[j++] & 0xff) << 8) | |
|
((byteArray[j++] & 0xff) << 16) | |
|
((byteArray[j++] & 0xff) << 24)); |
|
floatArray[i] = Float.intBitsToFloat(value); |
|
} |
|
} |
|
} |
|
|
|
// Uncompress packbits compressed image data. |
|
private byte[] decodePackbits(byte data[], int arraySize, byte[] dst) { |
|
|
|
if (dst == null) { |
|
dst = new byte[arraySize]; |
|
} |
|
|
|
int srcCount = 0, dstCount = 0; |
|
byte repeat, b; |
|
|
|
try { |
|
|
|
while (dstCount < arraySize) { |
|
|
|
b = data[srcCount++]; |
|
|
|
if (b >= 0 && b <= 127) { |
|
|
|
// literal run packet |
|
for (int i=0; i<(b + 1); i++) { |
|
dst[dstCount++] = data[srcCount++]; |
|
} |
|
|
|
} else if (b <= -1 && b >= -127) { |
|
|
|
// 2 byte encoded run packet |
|
repeat = data[srcCount++]; |
|
for (int i=0; i<(-b + 1); i++) { |
|
dst[dstCount++] = repeat; |
|
} |
|
|
|
} else { |
|
// no-op packet. Do nothing |
|
srcCount++; |
|
} |
|
} |
|
} catch (java.lang.ArrayIndexOutOfBoundsException ae) { |
|
throw new RuntimeException(JaiI18N.getString("TIFFImage14")); |
|
} |
|
|
|
return dst; |
|
} |
|
|
|
// Need a createColorModel(). |
|
// Create ComponentColorModel for TYPE_RGB images |
|
private ComponentColorModel createAlphaComponentColorModel( |
|
int dataType, |
|
int numBands, |
|
boolean isAlphaPremultiplied, |
|
int transparency) { |
|
|
|
ComponentColorModel ccm = null; |
|
int RGBBits[] = null; |
|
ColorSpace cs = null; |
|
switch(numBands) { |
|
case 2: // gray+alpha |
|
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); |
|
break; |
|
case 4: // RGB+alpha |
|
cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); |
|
break; |
|
default: |
|
throw new IllegalArgumentException(); |
|
} |
|
|
|
if(dataType == DataBuffer.TYPE_FLOAT) { |
|
ccm = new FloatDoubleColorModel(cs, |
|
true, |
|
isAlphaPremultiplied, |
|
transparency, |
|
dataType); |
|
} else { // all other types |
|
int componentSize = 0; |
|
switch(dataType) { |
|
case DataBuffer.TYPE_BYTE: |
|
componentSize = 8; |
|
break; |
|
case DataBuffer.TYPE_USHORT: |
|
case DataBuffer.TYPE_SHORT: |
|
componentSize = 16; |
|
break; |
|
case DataBuffer.TYPE_INT: |
|
componentSize = 32; |
|
break; |
|
default: |
|
throw new IllegalArgumentException(); |
|
} |
|
|
|
RGBBits = new int[numBands]; |
|
for(int i = 0; i < numBands; i++) { |
|
RGBBits[i] = componentSize; |
|
} |
|
|
|
ccm = new ComponentColorModel(cs, |
|
RGBBits, |
|
true, |
|
isAlphaPremultiplied, |
|
transparency, |
|
dataType); |
|
} |
|
|
|
return ccm; |
|
} |
|
}
|
|
|