blob: acfa6985f3c4e89dff4fce285f1b9dbc70d9d0b1 [file] [log] [blame]
/*
* Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.imageio.plugins.tiff;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferDouble;
import java.awt.image.DataBufferFloat;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import javax.imageio.IIOException;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
import com.sun.imageio.plugins.common.ImageUtil;
import com.sun.imageio.plugins.common.BogusColorSpace;
import com.sun.imageio.plugins.common.SimpleCMYKColorSpace;
/**
* A class defining a pluggable TIFF decompressor.
*
* <p> The mapping between source and destination Y coordinates is
* given by the equations:
*
* <pre>
* dx = (sx - sourceXOffset)/subsampleX + dstXOffset;
* dy = (sy - sourceYOffset)/subsampleY + dstYOffset;
* </pre>
*
* Note that the mapping from source coordinates to destination
* coordinates is not one-to-one if subsampling is being used, since
* only certain source pixels are to be copied to the
* destination. However, * the inverse mapping is always one-to-one:
*
* <pre>
* sx = (dx - dstXOffset)*subsampleX + sourceXOffset;
* sy = (dy - dstYOffset)*subsampleY + sourceYOffset;
* </pre>
*
* <p> Decompressors may be written with various levels of complexity.
* The most complex decompressors will override the
* {@code decode} method, and will perform all the work of
* decoding, subsampling, offsetting, clipping, and format conversion.
* This approach may be the most efficient, since it is possible to
* avoid the use of extra image buffers, and it may be possible to
* avoid decoding portions of the image that will not be copied into
* the destination.
*
* <p> Less ambitious decompressors may override the
* {@code decodeRaw} method, which is responsible for
* decompressing the entire tile or strip into a byte array (or other
* appropriate datatype). The default implementation of
* {@code decode} will perform all necessary setup of buffers,
* call {@code decodeRaw} to perform the actual decoding, perform
* subsampling, and copy the results into the final destination image.
* Where possible, it will pass the real image buffer to
* {@code decodeRaw} in order to avoid making an extra copy.
*
* <p> Slightly more ambitious decompressors may override
* {@code decodeRaw}, but avoid writing pixels that will be
* discarded in the subsampling phase.
*/
public abstract class TIFFDecompressor {
/**
* The {@code ImageReader} calling this
* {@code TIFFDecompressor}.
*/
protected ImageReader reader;
/**
* The {@code IIOMetadata} object containing metadata for the
* current image.
*/
protected IIOMetadata metadata;
/**
* The value of the {@code PhotometricInterpretation} tag.
* Legal values are {@link
* BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO },
* {@link
* BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO},
* {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_RGB},
* {@link
* BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR},
* {@link
* BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK},
* {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_Y_CB_CR},
* {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_CIELAB},
* {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_ICCLAB},
* or other value defined by a TIFF extension.
*/
protected int photometricInterpretation;
/**
* The value of the {@code Compression} tag. Legal values are
* {@link BaselineTIFFTagSet#COMPRESSION_NONE}, {@link
* BaselineTIFFTagSet#COMPRESSION_CCITT_RLE}, {@link
* BaselineTIFFTagSet#COMPRESSION_CCITT_T_4}, {@link
* BaselineTIFFTagSet#COMPRESSION_CCITT_T_6}, {@link
* BaselineTIFFTagSet#COMPRESSION_LZW}, {@link
* BaselineTIFFTagSet#COMPRESSION_OLD_JPEG}, {@link
* BaselineTIFFTagSet#COMPRESSION_JPEG}, {@link
* BaselineTIFFTagSet#COMPRESSION_ZLIB}, {@link
* BaselineTIFFTagSet#COMPRESSION_PACKBITS}, {@link
* BaselineTIFFTagSet#COMPRESSION_DEFLATE}, or other value
* defined by a TIFF extension.
*/
protected int compression;
/**
* {@code true} if the image is encoded using separate planes.
*/
protected boolean planar;
/**
* The planar band to decode; ignored for chunky (interleaved) images.
*/
protected int planarBand = 0;
/**
* The value of the {@code SamplesPerPixel} tag.
*/
protected int samplesPerPixel;
/**
* The value of the {@code BitsPerSample} tag.
*
*/
protected int[] bitsPerSample;
/**
* The value of the {@code SampleFormat} tag. Legal values
* are {@link BaselineTIFFTagSet#SAMPLE_FORMAT_UNSIGNED_INTEGER},
* {@link BaselineTIFFTagSet#SAMPLE_FORMAT_SIGNED_INTEGER}, {@link
* BaselineTIFFTagSet#SAMPLE_FORMAT_FLOATING_POINT}, {@link
* BaselineTIFFTagSet#SAMPLE_FORMAT_UNDEFINED}, or other value
* defined by a TIFF extension.
*/
protected int[] sampleFormat =
new int[] {BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER};
/**
* The value of the {@code ExtraSamples} tag. Legal values
* are {@link BaselineTIFFTagSet#EXTRA_SAMPLES_UNSPECIFIED},
* {@link BaselineTIFFTagSet#EXTRA_SAMPLES_ASSOCIATED_ALPHA},
* {@link BaselineTIFFTagSet#EXTRA_SAMPLES_UNASSOCIATED_ALPHA},
* or other value defined by a TIFF extension.
*/
protected int[] extraSamples;
/**
* The value of the {@code ColorMap} tag.
*
*/
protected char[] colorMap;
// Region of input stream containing the data
/**
* The {@code ImageInputStream} containing the TIFF source
* data.
*/
protected ImageInputStream stream;
/**
* The offset in the source {@code ImageInputStream} of the
* start of the data to be decompressed.
*/
protected long offset;
/**
* The number of bytes of data from the source
* {@code ImageInputStream} to be decompressed.
*/
protected int byteCount;
// Region of the file image represented in the stream
// This is unaffected by subsampling
/**
* The X coordinate of the upper-left pixel of the source region
* being decoded from the source stream. This value is not affected
* by source subsampling.
*/
protected int srcMinX;
/**
* The Y coordinate of the upper-left pixel of the source region
* being decoded from the source stream. This value is not affected
* by source subsampling.
*/
protected int srcMinY;
/**
* The width of the source region being decoded from the source
* stream. This value is not affected by source subsampling.
*/
protected int srcWidth;
/**
* The height of the source region being decoded from the source
* stream. This value is not affected by source subsampling.
*/
protected int srcHeight;
// Subsampling to be performed
/**
* The source X offset used, along with {@code dstXOffset}
* and {@code subsampleX}, to map between horizontal source
* and destination pixel coordinates.
*/
protected int sourceXOffset;
/**
* The horizontal destination offset used, along with
* {@code sourceXOffset} and {@code subsampleX}, to map
* between horizontal source and destination pixel coordinates.
* See the comment for {@link #sourceXOffset sourceXOffset} for
* the mapping equations.
*/
protected int dstXOffset;
/**
* The source Y offset used, along with {@code dstYOffset}
* and {@code subsampleY}, to map between vertical source and
* destination pixel coordinates.
*/
protected int sourceYOffset;
/**
* The vertical destination offset used, along with
* {@code sourceYOffset} and {@code subsampleY}, to map
* between horizontal source and destination pixel coordinates.
* See the comment for {@link #sourceYOffset sourceYOffset} for
* the mapping equations.
*/
protected int dstYOffset;
/**
* The horizontal subsampling factor. A factor of 1 means that
* every column is copied to the destination; a factor of 2 means
* that every second column is copied, etc.
*/
protected int subsampleX;
/**
* The vertical subsampling factor. A factor of 1 means that
* every row is copied to the destination; a factor of 2 means
* that every second row is copied, etc.
*/
protected int subsampleY;
// Band subsetting/rearrangement
/**
* The sequence of source bands that are to be copied into the
* destination.
*/
protected int[] sourceBands;
/**
* The sequence of destination bands to receive the source data.
*/
protected int[] destinationBands;
// Destination for decodeRaw
/**
* A {@code BufferedImage} for the {@code decodeRaw}
* method to write into.
*/
protected BufferedImage rawImage;
// Destination
/**
* The final destination image.
*/
protected BufferedImage image;
/**
* The X coordinate of the upper left pixel to be written in the
* destination image.
*/
protected int dstMinX;
/**
* The Y coordinate of the upper left pixel to be written in the
* destination image.
*/
protected int dstMinY;
/**
* The width of the region of the destination image to be written.
*/
protected int dstWidth;
/**
* The height of the region of the destination image to be written.
*/
protected int dstHeight;
// Region of source contributing to the destination
/**
* The X coordinate of the upper-left source pixel that will
* actually be copied into the destination image, taking into
* account all subsampling, offsetting, and clipping. That is,
* the pixel at ({@code activeSrcMinX},
* {@code activeSrcMinY}) is to be copied into the
* destination pixel at ({@code dstMinX},
* {@code dstMinY}).
*
* <p> The pixels in the source region to be copied are
* those with X coordinates of the form {@code activeSrcMinX +
* k*subsampleX}, where {@code k} is an integer such
* that {@code 0 <= k < dstWidth}.
*/
protected int activeSrcMinX;
/**
* The Y coordinate of the upper-left source pixel that will
* actually be copied into the destination image, taking into account
* all subsampling, offsetting, and clipping.
*
* <p> The pixels in the source region to be copied are
* those with Y coordinates of the form {@code activeSrcMinY +
* k*subsampleY}, where {@code k} is an integer such
* that {@code 0 <= k < dstHeight}.
*/
protected int activeSrcMinY;
/**
* The width of the source region that will actually be copied
* into the destination image, taking into account all
* susbampling, offsetting, and clipping.
*
* <p> The active source width will always be equal to
* {@code (dstWidth - 1)*subsampleX + 1}.
*/
protected int activeSrcWidth;
/**
* The height of the source region that will actually be copied
* into the destination image, taking into account all
* susbampling, offsetting, and clipping.
*
* <p> The active source height will always be equal to
* {@code (dstHeight - 1)*subsampleY + 1}.
*/
protected int activeSrcHeight;
/**
* A {@code TIFFColorConverter} object describing the color space of
* the encoded pixel data, or {@code null}.
*/
protected TIFFColorConverter colorConverter;
private boolean isBilevel;
private boolean isContiguous;
private boolean isImageSimple;
private boolean adjustBitDepths;
private int[][] bitDepthScale;
// source pixel at (sx, sy) should map to dst pixel (dx, dy), where:
//
// dx = (sx - sourceXOffset)/subsampleX + dstXOffset;
// dy = (sy - sourceYOffset)/subsampleY + dstYOffset;
//
// Note that this mapping is many-to-one. Source pixels such that
// (sx - sourceXOffset) % subsampleX != 0 should not be copied
// (and similarly for y).
//
// The backwards mapping from dest to source is one-to-one:
//
// sx = (dx - dstXOffset)*subsampleX + sourceXOffset;
// sy = (dy - dstYOffset)*subsampleY + sourceYOffset;
//
// The reader will always hand us the full source region as it
// exists in the file. It will take care of clipping the dest region
// to exactly those dest pixels that are present in the source region.
/**
* Create a {@code PixelInterleavedSampleModel} for use in creating
* an {@code ImageTypeSpecifier}. Its dimensions will be 1x1 and
* it will have ascending band offsets as {0, 1, 2, ..., numBands}.
*
* @param dataType The data type (DataBuffer.TYPE_*).
* @param numBands The number of bands.
* @return A {@code PixelInterleavedSampleModel}.
*/
static SampleModel createInterleavedSM(int dataType,
int numBands) {
int[] bandOffsets = new int[numBands];
for(int i = 0; i < numBands; i++) {
bandOffsets[i] = i;
}
return new PixelInterleavedSampleModel(dataType,
1, // width
1, // height
numBands, // pixelStride,
numBands, // scanlineStride
bandOffsets);
}
/**
* Create a {@code ComponentColorModel} for use in creating
* an {@code ImageTypeSpecifier}.
*/
// This code was inspired by the method of the same name in
// javax.imageio.ImageTypeSpecifier
static ColorModel createComponentCM(ColorSpace colorSpace,
int numBands,
int[] bitsPerSample,
int dataType,
boolean hasAlpha,
boolean isAlphaPremultiplied) {
int transparency =
hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE;
return new ComponentColorModel(colorSpace,
bitsPerSample,
hasAlpha,
isAlphaPremultiplied,
transparency,
dataType);
}
private static int createMask(int[] bitsPerSample, int band) {
int mask = (1 << bitsPerSample[band]) - 1;
for (int i = band + 1; i < bitsPerSample.length; i++) {
mask <<= bitsPerSample[i];
}
return mask;
}
private static int getDataTypeFromNumBits(int numBits, boolean isSigned) {
int dataType;
if (numBits <= 8) {
dataType = DataBuffer.TYPE_BYTE;
} else if (numBits <= 16) {
dataType = isSigned ?
DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT;
} else {
dataType = DataBuffer.TYPE_INT;
}
return dataType;
}
private static boolean areIntArraysEqual(int[] a, int[] b) {
if(a == null || b == null) {
if(a == null && b == null) {
return true;
} else { // one is null and one is not
return false;
}
}
if(a.length != b.length) {
return false;
}
int length = a.length;
for(int i = 0; i < length; i++) {
if(a[i] != b[i]) {
return false;
}
}
return true;
}
/**
* Return the number of bits occupied by {@code dataType}
* which must be one of the {@code DataBuffer} {@code TYPE}s.
*/
private static int getDataTypeSize(int dataType) throws IIOException {
int dataTypeSize = 0;
switch(dataType) {
case DataBuffer.TYPE_BYTE:
dataTypeSize = 8;
break;
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT:
dataTypeSize = 16;
break;
case DataBuffer.TYPE_INT:
case DataBuffer.TYPE_FLOAT:
dataTypeSize = 32;
break;
case DataBuffer.TYPE_DOUBLE:
dataTypeSize = 64;
break;
default:
throw new IIOException("Unknown data type "+dataType);
}
return dataTypeSize;
}
/**
* Returns the number of bits per pixel.
*/
private static int getBitsPerPixel(SampleModel sm) {
int bitsPerPixel = 0;
int[] sampleSize = sm.getSampleSize();
int numBands = sampleSize.length;
for(int i = 0; i < numBands; i++) {
bitsPerPixel += sampleSize[i];
}
return bitsPerPixel;
}
/**
* Returns whether all samples have the same number of bits.
*/
private static boolean areSampleSizesEqual(SampleModel sm) {
boolean allSameSize = true;
int[] sampleSize = sm.getSampleSize();
int sampleSize0 = sampleSize[0];
int numBands = sampleSize.length;
for(int i = 1; i < numBands; i++) {
if(sampleSize[i] != sampleSize0) {
allSameSize = false;
break;
}
}
return allSameSize;
}
/**
* Determines whether the {@code DataBuffer} is filled without
* any interspersed padding bits.
*/
private static boolean isDataBufferBitContiguous(SampleModel sm,
int[] bitsPerSample)
throws IIOException {
int dataTypeSize = getDataTypeSize(sm.getDataType());
if(sm instanceof ComponentSampleModel) {
int numBands = sm.getNumBands();
for(int i = 0; i < numBands; i++) {
if(bitsPerSample[i] != dataTypeSize) {
// Sample does not fill data element.
return false;
}
}
} else if(sm instanceof MultiPixelPackedSampleModel) {
MultiPixelPackedSampleModel mppsm =
(MultiPixelPackedSampleModel)sm;
if(dataTypeSize % mppsm.getPixelBitStride() != 0) {
// Pixels do not fill the data element.
return false;
}
} else if(sm instanceof SinglePixelPackedSampleModel) {
SinglePixelPackedSampleModel sppsm =
(SinglePixelPackedSampleModel)sm;
int numBands = sm.getNumBands();
int numBits = 0;
for(int i = 0; i < numBands; i++) {
numBits += sm.getSampleSize(i);
}
if(numBits != dataTypeSize) {
// Pixel does not fill the data element.
return false;
}
} else {
// Unknown SampleModel class.
return false;
}
return true;
}
/**
* Reformats data read as bytes into a short or int buffer.
*/
private static void reformatData(byte[] buf,
int bytesPerRow,
int numRows,
short[] shortData,
int[] intData,
int outOffset,
int outStride)
throws IIOException {
if(shortData != null) {
int inOffset = 0;
int shortsPerRow = bytesPerRow/2;
int numExtraBytes = bytesPerRow % 2;
for(int j = 0; j < numRows; j++) {
int k = outOffset;
for(int i = 0; i < shortsPerRow; i++) {
shortData[k++] =
(short)(((buf[inOffset++]&0xff) << 8) |
(buf[inOffset++]&0xff));
}
if(numExtraBytes != 0) {
shortData[k++] = (short)((buf[inOffset++]&0xff) << 8);
}
outOffset += outStride;
}
} else if(intData != null) {
int inOffset = 0;
int intsPerRow = bytesPerRow/4;
int numExtraBytes = bytesPerRow % 4;
for(int j = 0; j < numRows; j++) {
int k = outOffset;
for(int i = 0; i < intsPerRow; i++) {
intData[k++] =
((buf[inOffset++]&0xff) << 24) |
((buf[inOffset++]&0xff) << 16) |
((buf[inOffset++]&0xff) << 8) |
(buf[inOffset++]&0xff);
}
if(numExtraBytes != 0) {
int shift = 24;
int ival = 0;
for(int b = 0; b < numExtraBytes; b++) {
ival |= (buf[inOffset++]&0xff) << shift;
shift -= 8;
}
intData[k++] = ival;
}
outOffset += outStride;
}
} else {
throw new IIOException("shortData == null && intData == null!");
}
}
/**
* Reformats bit-discontiguous data into the {@code DataBuffer}
* of the supplied {@code WritableRaster}.
*/
private static void reformatDiscontiguousData(byte[] buf,
int[] bitsPerSample,
int stride,
int w,
int h,
WritableRaster raster)
throws IOException {
// Get SampleModel info.
SampleModel sm = raster.getSampleModel();
int numBands = sm.getNumBands();
// Initialize input stream.
ByteArrayInputStream is = new ByteArrayInputStream(buf);
ImageInputStream iis = new MemoryCacheImageInputStream(is);
// Reformat.
long iisPosition = 0L;
int y = raster.getMinY();
for(int j = 0; j < h; j++, y++) {
iis.seek(iisPosition);
int x = raster.getMinX();
for(int i = 0; i < w; i++, x++) {
for(int b = 0; b < numBands; b++) {
long bits = iis.readBits(bitsPerSample[b]);
raster.setSample(x, y, b, (int)bits);
}
}
iisPosition += stride;
}
}
/**
* A utility method that returns an
* {@code ImageTypeSpecifier} suitable for decoding an image
* with the given parameters.
*
* @param photometricInterpretation the value of the
* {@code PhotometricInterpretation} field.
* @param compression the value of the {@code Compression} field.
* @param samplesPerPixel the value of the
* {@code SamplesPerPixel} field.
* @param bitsPerSample the value of the {@code BitsPerSample} field.
* @param sampleFormat the value of the {@code SampleFormat} field.
* @param extraSamples the value of the {@code ExtraSamples} field.
* @param colorMap the value of the {@code ColorMap} field.
*
* @return a suitable {@code ImageTypeSpecifier}, or
* {@code null} if it is not possible to create one.
*/
public static ImageTypeSpecifier
getRawImageTypeSpecifier(int photometricInterpretation,
int compression,
int samplesPerPixel,
int[] bitsPerSample,
int[] sampleFormat,
int[] extraSamples,
char[] colorMap) {
//
// Types to support:
//
// 1, 2, 4, 8, or 16 bit grayscale or indexed
// 8,8-bit gray+alpha
// 16,16-bit gray+alpha
// 8,8,8-bit RGB
// 8,8,8,8-bit RGB+alpha
// 16,16,16-bit RGB
// 16,16,16,16-bit RGB+alpha
// R+G+B = 8-bit RGB
// R+G+B+A = 8-bit RGB
// R+G+B = 16-bit RGB
// R+G+B+A = 16-bit RGB
// 8X-bits/sample, arbitrary numBands.
// Arbitrary non-indexed, non-float layouts (discontiguous).
//
// Band-sequential
// 1, 2, 4, 8, or 16 bit grayscale or indexed images
if (samplesPerPixel == 1 &&
(bitsPerSample[0] == 1 ||
bitsPerSample[0] == 2 ||
bitsPerSample[0] == 4 ||
bitsPerSample[0] == 8 ||
bitsPerSample[0] == 16)) {
// 2 and 16 bits images are not in the baseline
// specification, but we will allow them anyway
// since they fit well into Java2D
//
// this raises the issue of how to write such images...
if (colorMap == null) {
// Grayscale
boolean isSigned = (sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER);
int dataType;
if (bitsPerSample[0] <= 8) {
dataType = DataBuffer.TYPE_BYTE;
} else {
dataType = sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ?
DataBuffer.TYPE_SHORT :
DataBuffer.TYPE_USHORT;
}
return ImageTypeSpecifier.createGrayscale(bitsPerSample[0],
dataType,
isSigned);
} else {
// Indexed
int mapSize = 1 << bitsPerSample[0];
byte[] redLut = new byte[mapSize];
byte[] greenLut = new byte[mapSize];
byte[] blueLut = new byte[mapSize];
byte[] alphaLut = null;
int idx = 0;
for (int i = 0; i < mapSize; i++) {
redLut[i] = (byte)((colorMap[i]*255)/65535);
greenLut[i] = (byte)((colorMap[mapSize + i]*255)/65535);
blueLut[i] = (byte)((colorMap[2*mapSize + i]*255)/65535);
}
int dataType;
if (bitsPerSample[0] <= 8) {
dataType = DataBuffer.TYPE_BYTE;
} else if (sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) {
dataType = DataBuffer.TYPE_SHORT;
} else {
dataType = DataBuffer.TYPE_USHORT;
}
return ImageTypeSpecifier.createIndexed(redLut,
greenLut,
blueLut,
alphaLut,
bitsPerSample[0],
dataType);
}
}
// 8-bit gray-alpha
if (samplesPerPixel == 2 &&
bitsPerSample[0] == 8 &&
bitsPerSample[1] == 8) {
int dataType = DataBuffer.TYPE_BYTE;
boolean alphaPremultiplied = false;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
return ImageTypeSpecifier.createGrayscale(8,
dataType,
false,
alphaPremultiplied);
}
// 16-bit gray-alpha
if (samplesPerPixel == 2 &&
bitsPerSample[0] == 16 &&
bitsPerSample[1] == 16) {
int dataType = sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ?
DataBuffer.TYPE_SHORT :
DataBuffer.TYPE_USHORT;
boolean alphaPremultiplied = false;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
boolean isSigned = dataType == DataBuffer.TYPE_SHORT;
return ImageTypeSpecifier.createGrayscale(16,
dataType,
isSigned,
alphaPremultiplied);
}
ColorSpace rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
// 8-bit RGB
if (samplesPerPixel == 3 &&
bitsPerSample[0] == 8 &&
bitsPerSample[1] == 8 &&
bitsPerSample[2] == 8) {
int[] bandOffsets = new int[3];
bandOffsets[0] = 0;
bandOffsets[1] = 1;
bandOffsets[2] = 2;
int dataType = DataBuffer.TYPE_BYTE;
ColorSpace theColorSpace;
if((photometricInterpretation ==
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
compression != BaselineTIFFTagSet.COMPRESSION_JPEG &&
compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) ||
photometricInterpretation ==
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) {
theColorSpace =
ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
} else {
theColorSpace = rgb;
}
return ImageTypeSpecifier.createInterleaved(theColorSpace,
bandOffsets,
dataType,
false,
false);
}
// 8-bit RGBA
if (samplesPerPixel == 4 &&
bitsPerSample[0] == 8 &&
bitsPerSample[1] == 8 &&
bitsPerSample[2] == 8 &&
bitsPerSample[3] == 8) {
int[] bandOffsets = new int[4];
bandOffsets[0] = 0;
bandOffsets[1] = 1;
bandOffsets[2] = 2;
bandOffsets[3] = 3;
int dataType = DataBuffer.TYPE_BYTE;
ColorSpace theColorSpace;
boolean hasAlpha;
boolean alphaPremultiplied = false;
if(photometricInterpretation ==
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK) {
theColorSpace = SimpleCMYKColorSpace.getInstance();
hasAlpha = false;
} else {
theColorSpace = rgb;
hasAlpha = true;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
}
return ImageTypeSpecifier.createInterleaved(theColorSpace,
bandOffsets,
dataType,
hasAlpha,
alphaPremultiplied);
}
// 16-bit RGB
if (samplesPerPixel == 3 &&
bitsPerSample[0] == 16 &&
bitsPerSample[1] == 16 &&
bitsPerSample[2] == 16) {
int[] bandOffsets = new int[3];
bandOffsets[0] = 0;
bandOffsets[1] = 1;
bandOffsets[2] = 2;
int dataType = sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ?
DataBuffer.TYPE_SHORT :
DataBuffer.TYPE_USHORT;
return ImageTypeSpecifier.createInterleaved(rgb,
bandOffsets,
dataType,
false,
false);
}
// 16-bit RGBA
if (samplesPerPixel == 4 &&
bitsPerSample[0] == 16 &&
bitsPerSample[1] == 16 &&
bitsPerSample[2] == 16 &&
bitsPerSample[3] == 16) {
int[] bandOffsets = new int[4];
bandOffsets[0] = 0;
bandOffsets[1] = 1;
bandOffsets[2] = 2;
bandOffsets[3] = 3;
int dataType = sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ?
DataBuffer.TYPE_SHORT :
DataBuffer.TYPE_USHORT;
boolean alphaPremultiplied = false;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
return ImageTypeSpecifier.createInterleaved(rgb,
bandOffsets,
dataType,
true,
alphaPremultiplied);
}
// Compute bits per pixel.
int totalBits = 0;
for (int i = 0; i < bitsPerSample.length; i++) {
totalBits += bitsPerSample[i];
}
// Packed: 3- or 4-band, 8- or 16-bit.
if ((samplesPerPixel == 3 || samplesPerPixel == 4) &&
(totalBits == 8 || totalBits == 16)) {
int redMask = createMask(bitsPerSample, 0);
int greenMask = createMask(bitsPerSample, 1);
int blueMask = createMask(bitsPerSample, 2);
int alphaMask = (samplesPerPixel == 4) ?
createMask(bitsPerSample, 3) : 0;
int transferType = totalBits == 8 ?
DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT;
boolean alphaPremultiplied = false;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
return ImageTypeSpecifier.createPacked(rgb,
redMask,
greenMask,
blueMask,
alphaMask,
transferType,
alphaPremultiplied);
}
// Generic components with 8X bits per sample.
if(bitsPerSample[0] % 8 == 0) {
// Check whether all bands have same bit depth.
boolean allSameBitDepth = true;
for(int i = 1; i < bitsPerSample.length; i++) {
if(bitsPerSample[i] != bitsPerSample[i-1]) {
allSameBitDepth = false;
break;
}
}
// Proceed if all bands have same bit depth.
if(allSameBitDepth) {
// Determine the data type.
int dataType = -1;
boolean isDataTypeSet = false;
switch(bitsPerSample[0]) {
case 8:
if(sampleFormat[0] !=
BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
// Ignore whether signed or unsigned:
// treat all as unsigned.
dataType = DataBuffer.TYPE_BYTE;
isDataTypeSet = true;
}
break;
case 16:
if(sampleFormat[0] !=
BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
if(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) {
dataType = DataBuffer.TYPE_SHORT;
} else {
dataType = DataBuffer.TYPE_USHORT;
}
isDataTypeSet = true;
}
break;
case 32:
if(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
dataType = DataBuffer.TYPE_FLOAT;
} else {
dataType = DataBuffer.TYPE_INT;
}
isDataTypeSet = true;
break;
case 64:
if(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
dataType = DataBuffer.TYPE_DOUBLE;
isDataTypeSet = true;
}
break;
}
if(isDataTypeSet) {
// Create the SampleModel.
SampleModel sm = createInterleavedSM(dataType,
samplesPerPixel);
// Create the ColorModel.
ColorModel cm;
if(samplesPerPixel >= 1 && samplesPerPixel <= 4 &&
(dataType == DataBuffer.TYPE_INT ||
dataType == DataBuffer.TYPE_FLOAT)) {
// Handle the 32-bit cases for 1-4 bands.
ColorSpace cs = samplesPerPixel <= 2 ?
ColorSpace.getInstance(ColorSpace.CS_GRAY) : rgb;
boolean hasAlpha = ((samplesPerPixel % 2) == 0);
boolean alphaPremultiplied = false;
if(hasAlpha && extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
cm = createComponentCM(cs,
samplesPerPixel,
bitsPerSample,
dataType,
hasAlpha,
alphaPremultiplied);
} else {
ColorSpace cs = new BogusColorSpace(samplesPerPixel);
cm = createComponentCM(cs,
samplesPerPixel,
bitsPerSample,
dataType,
false, // hasAlpha
false); // alphaPremultiplied
}
return new ImageTypeSpecifier(cm, sm);
}
}
}
// Other more bizarre cases including discontiguous DataBuffers
// such as for the image in bug 4918959.
if(colorMap == null &&
sampleFormat[0] !=
BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
// Determine size of largest sample.
int maxBitsPerSample = 0;
for(int i = 0; i < bitsPerSample.length; i++) {
if(bitsPerSample[i] > maxBitsPerSample) {
maxBitsPerSample = bitsPerSample[i];
}
}
// Determine whether data are signed.
boolean isSigned =
(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER);
// Grayscale
if(samplesPerPixel == 1 &&
(bitsPerSample[0] == 1 || bitsPerSample[0] == 2 ||
bitsPerSample[0] == 4 || bitsPerSample[0] == 8 ||
bitsPerSample[0] == 16)) {
int dataType = getDataTypeFromNumBits(maxBitsPerSample, isSigned);
return ImageTypeSpecifier.createGrayscale(bitsPerSample[0],
dataType,
isSigned);
}
// Gray-alpha
if (samplesPerPixel == 2 &&
bitsPerSample[0] == bitsPerSample[1] &&
(bitsPerSample[0] == 1 || bitsPerSample[0] == 2 ||
bitsPerSample[0] == 4 || bitsPerSample[0] == 8 ||
bitsPerSample[0] == 16)) {
boolean alphaPremultiplied = false;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
int dataType =
getDataTypeFromNumBits(maxBitsPerSample, isSigned);
return ImageTypeSpecifier.createGrayscale(maxBitsPerSample,
dataType,
false,
alphaPremultiplied);
}
if (samplesPerPixel == 3 || samplesPerPixel == 4) {
int dataType = getDataTypeFromNumBits(maxBitsPerSample, isSigned);
int dataTypeSize;
try {
dataTypeSize = getDataTypeSize(dataType);
} catch (IIOException ignored) {
dataTypeSize = maxBitsPerSample;
}
if(totalBits <= 32 && !isSigned) {
// Packed RGB or RGBA
int redMask = createMask(bitsPerSample, 0);
int greenMask = createMask(bitsPerSample, 1);
int blueMask = createMask(bitsPerSample, 2);
int alphaMask = (samplesPerPixel == 4) ?
createMask(bitsPerSample, 3) : 0;
int transferType =
getDataTypeFromNumBits(totalBits, false);
boolean alphaPremultiplied = false;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
return ImageTypeSpecifier.createPacked(rgb,
redMask,
greenMask,
blueMask,
alphaMask,
transferType,
alphaPremultiplied);
} else if(samplesPerPixel == 3 &&
dataTypeSize == bitsPerSample[0] &&
bitsPerSample[0] == bitsPerSample[1] &&
bitsPerSample[1] == bitsPerSample[2]) {
// Interleaved RGB
int[] bandOffsets = new int[] {0, 1, 2};
return ImageTypeSpecifier.createInterleaved(rgb,
bandOffsets,
dataType,
false,
false);
} else if(samplesPerPixel == 4 &&
dataTypeSize == bitsPerSample[0] &&
bitsPerSample[0] == bitsPerSample[1] &&
bitsPerSample[1] == bitsPerSample[2] &&
bitsPerSample[2] == bitsPerSample[3]) {
// Interleaved RGBA
int[] bandOffsets = new int[] {0, 1, 2, 3};
boolean alphaPremultiplied = false;
if (extraSamples != null &&
extraSamples[0] ==
BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) {
alphaPremultiplied = true;
}
return ImageTypeSpecifier.createInterleaved(rgb,
bandOffsets,
dataType,
true,
alphaPremultiplied);
}
}
// Arbitrary Interleaved.
int dataType =
getDataTypeFromNumBits(maxBitsPerSample, isSigned);
SampleModel sm = createInterleavedSM(dataType,
samplesPerPixel);
ColorSpace cs;
if (samplesPerPixel <= 2) {
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
} else if (samplesPerPixel <= 4) {
cs = rgb;
} else {
cs = new BogusColorSpace(samplesPerPixel);
}
ColorModel cm = createComponentCM(cs,
samplesPerPixel,
bitsPerSample,
dataType,
false, // hasAlpha
false); // alphaPremultiplied
return new ImageTypeSpecifier(cm, sm);
}
return null;
}
/**
* Sets the value of the {@code reader} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param reader the current {@code ImageReader}.
*/
public void setReader(ImageReader reader) {
this.reader = reader;
}
/**
* Sets the value of the {@code metadata} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param metadata the {@code IIOMetadata} object for the
* image being read.
*/
public void setMetadata(IIOMetadata metadata) {
this.metadata = metadata;
}
/**
* Sets the value of the {@code photometricInterpretation}
* field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param photometricInterpretation the photometric interpretation
* value.
*/
public void setPhotometricInterpretation(int photometricInterpretation) {
this.photometricInterpretation = photometricInterpretation;
}
/**
* Sets the value of the {@code compression} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param compression the compression type.
*/
public void setCompression(int compression) {
this.compression = compression;
}
/**
* Sets the value of the {@code planar} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param planar {@code true} if the image to be decoded is
* stored in planar format.
*/
public void setPlanar(boolean planar) {
this.planar = planar;
}
/**
* Sets the index of the planar configuration band to be decoded. This value
* is ignored for chunky (interleaved) images.
*
* @param the index of the planar band to decode
*/
public void setPlanarBand(int planarBand) { this.planarBand = planarBand; }
/**
* Sets the value of the {@code samplesPerPixel} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param samplesPerPixel the number of samples in each source
* pixel.
*/
public void setSamplesPerPixel(int samplesPerPixel) {
this.samplesPerPixel = samplesPerPixel;
}
/**
* Sets the value of the {@code bitsPerSample} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param bitsPerSample the number of bits for each source image
* sample.
*/
public void setBitsPerSample(int[] bitsPerSample) {
this.bitsPerSample = bitsPerSample == null ?
null : bitsPerSample.clone();
}
/**
* Sets the value of the {@code sampleFormat} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param sampleFormat the format of the source image data,
* for example unsigned integer or floating-point.
*/
public void setSampleFormat(int[] sampleFormat) {
this.sampleFormat = sampleFormat == null ?
new int[] {BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER} :
sampleFormat.clone();
}
/**
* Sets the value of the {@code extraSamples} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param extraSamples the interpretation of any samples in the
* source file beyond those used for basic color or grayscale
* information.
*/
public void setExtraSamples(int[] extraSamples) {
this.extraSamples = extraSamples == null ?
null : extraSamples.clone();
}
/**
* Sets the value of the {@code colorMap} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param colorMap the color map to apply to the source data,
* as an array of {@code char}s.
*/
public void setColorMap(char[] colorMap) {
this.colorMap = colorMap == null ?
null : colorMap.clone();
}
/**
* Sets the value of the {@code stream} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param stream the {@code ImageInputStream} to be read.
*/
public void setStream(ImageInputStream stream) {
this.stream = stream;
}
/**
* Sets the value of the {@code offset} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param offset the offset of the beginning of the compressed
* data.
*/
public void setOffset(long offset) {
this.offset = offset;
}
/**
* Sets the value of the {@code byteCount} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param byteCount the number of bytes of compressed data.
*/
public void setByteCount(int byteCount) {
this.byteCount = byteCount;
}
// Region of the file image represented in the stream
/**
* Sets the value of the {@code srcMinX} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param srcMinX the minimum X coordinate of the source region
* being decoded, irrespective of how it will be copied into the
* destination.
*/
public void setSrcMinX(int srcMinX) {
this.srcMinX = srcMinX;
}
/**
* Sets the value of the {@code srcMinY} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param srcMinY the minimum Y coordinate of the source region
* being decoded, irrespective of how it will be copied into the
* destination.
*/
public void setSrcMinY(int srcMinY) {
this.srcMinY = srcMinY;
}
/**
* Sets the value of the {@code srcWidth} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param srcWidth the width of the source region being decoded,
* irrespective of how it will be copied into the destination.
*/
public void setSrcWidth(int srcWidth) {
this.srcWidth = srcWidth;
}
/**
* Sets the value of the {@code srcHeight} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param srcHeight the height of the source region being decoded,
* irrespective of how it will be copied into the destination.
*/
public void setSrcHeight(int srcHeight) {
this.srcHeight = srcHeight;
}
// First source pixel to be read
/**
* Sets the value of the {@code sourceXOffset} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param sourceXOffset the horizontal source offset to be used when
* mapping between source and destination coordinates.
*/
public void setSourceXOffset(int sourceXOffset) {
this.sourceXOffset = sourceXOffset;
}
/**
* Sets the value of the {@code dstXOffset} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param dstXOffset the horizontal destination offset to be
* used when mapping between source and destination coordinates.
*/
public void setDstXOffset(int dstXOffset) {
this.dstXOffset = dstXOffset;
}
/**
* Sets the value of the {@code sourceYOffset}.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param sourceYOffset the vertical source offset to be used when
* mapping between source and destination coordinates.
*/
public void setSourceYOffset(int sourceYOffset) {
this.sourceYOffset = sourceYOffset;
}
/**
* Sets the value of the {@code dstYOffset} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param dstYOffset the vertical destination offset to be
* used when mapping between source and destination coordinates.
*/
public void setDstYOffset(int dstYOffset) {
this.dstYOffset = dstYOffset;
}
// Subsampling to be performed
/**
* Sets the value of the {@code subsampleX} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param subsampleX the horizontal subsampling factor.
*
* @throws IllegalArgumentException if {@code subsampleX} is
* less than or equal to 0.
*/
public void setSubsampleX(int subsampleX) {
if (subsampleX <= 0) {
throw new IllegalArgumentException("subsampleX <= 0!");
}
this.subsampleX = subsampleX;
}
/**
* Sets the value of the {@code subsampleY} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param subsampleY the vertical subsampling factor.
*
* @throws IllegalArgumentException if {@code subsampleY} is
* less than or equal to 0.
*/
public void setSubsampleY(int subsampleY) {
if (subsampleY <= 0) {
throw new IllegalArgumentException("subsampleY <= 0!");
}
this.subsampleY = subsampleY;
}
// Band subsetting/rearrangement
/**
* Sets the value of the {@code sourceBands} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param sourceBands an array of {@code int}s
* specifying the source bands to be read.
*/
public void setSourceBands(int[] sourceBands) {
this.sourceBands = sourceBands == null ?
null : sourceBands.clone();
}
/**
* Sets the value of the {@code destinationBands} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param destinationBands an array of {@code int}s
* specifying the destination bands to be written.
*/
public void setDestinationBands(int[] destinationBands) {
this.destinationBands = destinationBands == null ?
null : destinationBands.clone();
}
// Destination image and region
/**
* Sets the value of the {@code image} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param image the destination {@code BufferedImage}.
*/
public void setImage(BufferedImage image) {
this.image = image;
}
/**
* Sets the value of the {@code dstMinX} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param dstMinX the minimum X coordinate of the destination
* region.
*/
public void setDstMinX(int dstMinX) {
this.dstMinX = dstMinX;
}
/**
* Sets the value of the {@code dstMinY} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param dstMinY the minimum Y coordinate of the destination
* region.
*/
public void setDstMinY(int dstMinY) {
this.dstMinY = dstMinY;
}
/**
* Sets the value of the {@code dstWidth} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param dstWidth the width of the destination region.
*/
public void setDstWidth(int dstWidth) {
this.dstWidth = dstWidth;
}
/**
* Sets the value of the {@code dstHeight} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param dstHeight the height of the destination region.
*/
public void setDstHeight(int dstHeight) {
this.dstHeight = dstHeight;
}
// Active source region
/**
* Sets the value of the {@code activeSrcMinX} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param activeSrcMinX the minimum X coordinate of the active
* source region.
*/
public void setActiveSrcMinX(int activeSrcMinX) {
this.activeSrcMinX = activeSrcMinX;
}
/**
* Sets the value of the {@code activeSrcMinY} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param activeSrcMinY the minimum Y coordinate of the active
* source region.
*/
public void setActiveSrcMinY(int activeSrcMinY) {
this.activeSrcMinY = activeSrcMinY;
}
/**
* Sets the value of the {@code activeSrcWidth} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param activeSrcWidth the width of the active source region.
*/
public void setActiveSrcWidth(int activeSrcWidth) {
this.activeSrcWidth = activeSrcWidth;
}
/**
* Sets the value of the {@code activeSrcHeight} field.
*
* <p> If this method is called, the {@code beginDecoding}
* method must be called prior to calling any of the decode
* methods.
*
* @param activeSrcHeight the height of the active source region.
*/
public void setActiveSrcHeight(int activeSrcHeight) {
this.activeSrcHeight = activeSrcHeight;
}
/**
* Sets the {@code TIFFColorConverter} object describing the color
* space of the encoded data in the input stream. If no
* {@code TIFFColorConverter} is set, no conversion will be performed.
*
* @param colorConverter a {@code TIFFColorConverter} object, or
* {@code null}.
*/
public void setColorConverter(TIFFColorConverter colorConverter) {
this.colorConverter = colorConverter;
}
/**
* Returns an {@code ImageTypeSpecifier} describing an image
* whose underlying data array has the same format as the raw
* source pixel data.
*
* @return an {@code ImageTypeSpecifier}.
*/
public ImageTypeSpecifier getRawImageType() {
ImageTypeSpecifier its =
getRawImageTypeSpecifier(photometricInterpretation,
compression,
samplesPerPixel,
bitsPerSample,
sampleFormat,
extraSamples,
colorMap);
return its;
}
/**
* Creates a {@code BufferedImage} whose underlying data
* array will be suitable for holding the raw decoded output of
* the {@code decodeRaw} method.
*
* <p> The default implementation calls
* {@code getRawImageType}, and calls the resulting
* {@code ImageTypeSpecifier}'s
* {@code createBufferedImage} method.
*
* @return a {@code BufferedImage} whose underlying data
* array has the same format as the raw source pixel data, or
* {@code null} if it is not possible to create such an
* image.
*/
public BufferedImage createRawImage() {
if (planar) {
// Create a single-banded image of the appropriate data type.
// Get the number of bits per sample.
int bps = bitsPerSample[sourceBands[0]];
// Determine the data type.
int dataType;
if(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
if(bps <= 32) {
dataType = DataBuffer.TYPE_FLOAT;
} else {
dataType = DataBuffer.TYPE_DOUBLE;
}
} else if(bps <= 8) {
dataType = DataBuffer.TYPE_BYTE;
} else if(bps <= 16) {
if(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) {
dataType = DataBuffer.TYPE_SHORT;
} else {
dataType = DataBuffer.TYPE_USHORT;
}
} else {
dataType = DataBuffer.TYPE_INT;
}
ColorSpace csGray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
ImageTypeSpecifier its =
ImageTypeSpecifier.createInterleaved(csGray,
new int[] {0},
dataType,
false,
false);
return its.createBufferedImage(srcWidth, srcHeight);
} else {
ImageTypeSpecifier its = getRawImageType();
if (its == null) {
return null;
}
BufferedImage bi = its.createBufferedImage(srcWidth, srcHeight);
return bi;
}
}
/**
* Decodes the source data into the provided {@code byte}
* array {@code b}, starting at the offset given by
* {@code dstOffset}. Each pixel occupies
* {@code bitsPerPixel} bits, with no padding between pixels.
* Scanlines are separated by {@code scanlineStride}
* {@code byte}s.
*
* @param b a {@code byte} array to be written.
* @param dstOffset the starting offset in {@code b} to be
* written.
* @param bitsPerPixel the number of bits for each pixel.
* @param scanlineStride the number of {@code byte}s to
* advance between that starting pixels of each scanline.
*
* @throws IOException if an error occurs reading from the source
* {@code ImageInputStream}.
*/
public abstract void decodeRaw(byte[] b,
int dstOffset,
int bitsPerPixel,
int scanlineStride) throws IOException;
/**
* Decodes the source data into the provided {@code short}
* array {@code s}, starting at the offset given by
* {@code dstOffset}. Each pixel occupies
* {@code bitsPerPixel} bits, with no padding between pixels.
* Scanlines are separated by {@code scanlineStride}
* {@code short}s
*
* <p> The default implementation calls {@code decodeRaw(byte[] b,
* ...)} and copies the resulting data into {@code s}.
*
* @param s a {@code short} array to be written.
* @param dstOffset the starting offset in {@code s} to be
* written.
* @param bitsPerPixel the number of bits for each pixel.
* @param scanlineStride the number of {@code short}s to
* advance between that starting pixels of each scanline.
*
* @throws IOException if an error occurs reading from the source
* {@code ImageInputStream}.
*/
public void decodeRaw(short[] s,
int dstOffset,
int bitsPerPixel,
int scanlineStride) throws IOException {
int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8;
int shortsPerRow = bytesPerRow/2;
byte[] b = new byte[bytesPerRow*srcHeight];
decodeRaw(b, 0, bitsPerPixel, bytesPerRow);
int bOffset = 0;
if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
for (int j = 0; j < srcHeight; j++) {
for (int i = 0; i < shortsPerRow; i++) {
short hiVal = b[bOffset++];
short loVal = b[bOffset++];
short sval = (short)((hiVal << 8) | (loVal & 0xff));
s[dstOffset + i] = sval;
}
dstOffset += scanlineStride;
}
} else { // ByteOrder.LITLE_ENDIAN
for (int j = 0; j < srcHeight; j++) {
for (int i = 0; i < shortsPerRow; i++) {
short loVal = b[bOffset++];
short hiVal = b[bOffset++];
short sval = (short)((hiVal << 8) | (loVal & 0xff));
s[dstOffset + i] = sval;
}
dstOffset += scanlineStride;
}
}
}
/**
* Decodes the source data into the provided {@code int}
* array {@code i}, starting at the offset given by
* {@code dstOffset}. Each pixel occupies
* {@code bitsPerPixel} bits, with no padding between pixels.
* Scanlines are separated by {@code scanlineStride}
* {@code int}s.
*
* <p> The default implementation calls {@code decodeRaw(byte[] b,
* ...)} and copies the resulting data into {@code i}.
*
* @param i an {@code int} array to be written.
* @param dstOffset the starting offset in {@code i} to be
* written.
* @param bitsPerPixel the number of bits for each pixel.
* @param scanlineStride the number of {@code int}s to
* advance between that starting pixels of each scanline.
*
* @throws IOException if an error occurs reading from the source
* {@code ImageInputStream}.
*/
public void decodeRaw(int[] i,
int dstOffset,
int bitsPerPixel,
int scanlineStride) throws IOException {
int numBands = bitsPerPixel/32;
int intsPerRow = srcWidth*numBands;
int bytesPerRow = intsPerRow*4;
byte[] b = new byte[bytesPerRow*srcHeight];
decodeRaw(b, 0, bitsPerPixel, bytesPerRow);
int bOffset = 0;
if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
for (int j = 0; j < srcHeight; j++) {
for (int k = 0; k < intsPerRow; k++) {
int v0 = b[bOffset++] & 0xff;
int v1 = b[bOffset++] & 0xff;
int v2 = b[bOffset++] & 0xff;
int v3 = b[bOffset++] & 0xff;
int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3;
i[dstOffset + k] = ival;
}
dstOffset += scanlineStride;
}
} else { // ByteOrder.LITLE_ENDIAN
for (int j = 0; j < srcHeight; j++) {
for (int k = 0; k < intsPerRow; k++) {
int v3 = b[bOffset++] & 0xff;
int v2 = b[bOffset++] & 0xff;
int v1 = b[bOffset++] & 0xff;
int v0 = b[bOffset++] & 0xff;
int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3;
i[dstOffset + k] = ival;
}
dstOffset += scanlineStride;
}
}
}
/**
* Decodes the source data into the provided {@code float}
* array {@code f}, starting at the offset given by
* {@code dstOffset}. Each pixel occupies
* {@code bitsPerPixel} bits, with no padding between pixels.
* Scanlines are separated by {@code scanlineStride}
* {@code float}s.
*
* <p> The default implementation calls {@code decodeRaw(byte[] b,
* ...)} and copies the resulting data into {@code f}.
*
* @param f a {@code float} array to be written.
* @param dstOffset the starting offset in {@code f} to be
* written.
* @param bitsPerPixel the number of bits for each pixel.
* @param scanlineStride the number of {@code float}s to
* advance between that starting pixels of each scanline.
*
* @throws IOException if an error occurs reading from the source
* {@code ImageInputStream}.
*/
public void decodeRaw(float[] f,
int dstOffset,
int bitsPerPixel,
int scanlineStride) throws IOException {
int numBands = bitsPerPixel/32;
int floatsPerRow = srcWidth*numBands;
int bytesPerRow = floatsPerRow*4;
byte[] b = new byte[bytesPerRow*srcHeight];
decodeRaw(b, 0, bitsPerPixel, bytesPerRow);
int bOffset = 0;
if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
for (int j = 0; j < srcHeight; j++) {
for (int i = 0; i < floatsPerRow; i++) {
int v0 = b[bOffset++] & 0xff;
int v1 = b[bOffset++] & 0xff;
int v2 = b[bOffset++] & 0xff;
int v3 = b[bOffset++] & 0xff;
int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3;
float fval = Float.intBitsToFloat(ival);
f[dstOffset + i] = fval;
}
dstOffset += scanlineStride;
}
} else { // ByteOrder.LITLE_ENDIAN
for (int j = 0; j < srcHeight; j++) {
for (int i = 0; i < floatsPerRow; i++) {
int v3 = b[bOffset++] & 0xff;
int v2 = b[bOffset++] & 0xff;
int v1 = b[bOffset++] & 0xff;
int v0 = b[bOffset++] & 0xff;
int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3;
float fval = Float.intBitsToFloat(ival);
f[dstOffset + i] = fval;
}
dstOffset += scanlineStride;
}
}
}
/**
* Decodes the source data into the provided {@code double}
* array {@code f}, starting at the offset given by
* {@code dstOffset}. Each pixel occupies
* {@code bitsPerPixel} bits, with no padding between pixels.
* Scanlines are separated by {@code scanlineStride}
* {@code double}s.
*
* <p> The default implementation calls {@code decodeRaw(byte[] b,
* ...)} and copies the resulting data into {@code f}.
*
* @param f a {@code double} array to be written.
* @param dstOffset the starting offset in {@code f} to be
* written.
* @param bitsPerPixel the number of bits for each pixel.
* @param scanlineStride the number of {@code double}s to
* advance between that starting pixels of each scanline.
*
* @throws IOException if an error occurs reading from the source
* {@code ImageInputStream}.
*/
public void decodeRaw(double[] d,
int dstOffset,
int bitsPerPixel,
int scanlineStride) throws IOException {
int numBands = bitsPerPixel/64;
int doublesPerRow = srcWidth*numBands;
int bytesPerRow = doublesPerRow*8;
byte[] b = new byte[bytesPerRow*srcHeight];
decodeRaw(b, 0, bitsPerPixel, bytesPerRow);
int bOffset = 0;
if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
for (int j = 0; j < srcHeight; j++) {
for (int i = 0; i < doublesPerRow; i++) {
long v0 = b[bOffset++] & 0xff;
long v1 = b[bOffset++] & 0xff;
long v2 = b[bOffset++] & 0xff;
long v3 = b[bOffset++] & 0xff;
long v4 = b[bOffset++] & 0xff;
long v5 = b[bOffset++] & 0xff;
long v6 = b[bOffset++] & 0xff;
long v7 = b[bOffset++] & 0xff;
long lval =
(v0 << 56) | (v1 << 48) | (v2 << 40) | (v3 << 32)
| (v4 << 24) | (v5 << 16) | (v6 << 8) | v7;
double dval = Double.longBitsToDouble(lval);
d[dstOffset + i] = dval;
}
dstOffset += scanlineStride;
}
} else { // ByteOrder.LITLE_ENDIAN
for (int j = 0; j < srcHeight; j++) {
for (int i = 0; i < doublesPerRow; i++) {
long v7 = b[bOffset++] & 0xff;
long v6 = b[bOffset++] & 0xff;
long v5 = b[bOffset++] & 0xff;
long v4 = b[bOffset++] & 0xff;
long v3 = b[bOffset++] & 0xff;
long v2 = b[bOffset++] & 0xff;
long v1 = b[bOffset++] & 0xff;
long v0 = b[bOffset++] & 0xff;
long lval =
(v0 << 56) | (v1 << 48) | (v2 << 40) | (v3 << 32)
| (v4 << 24) | (v5 << 16) | (v6 << 8) | v7;
double dval = Double.longBitsToDouble(lval);
d[dstOffset + i] = dval;
}
dstOffset += scanlineStride;
}
}
}
//
// Values used to prevent unneeded recalculation of bit adjustment table.
//
private boolean isFirstBitDepthTable = true;
private boolean planarCache = false;
private int[] destBitsPerSampleCache = null;
private int[] sourceBandsCache = null;
private int[] bitsPerSampleCache = null;
private int[] destinationBandsCache = null;
/**
* This routine is called prior to a sequence of calls to the
* {@code decode} method, in order to allow any necessary
* tables or other structures to be initialized based on metadata
* values. This routine is guaranteed to be called any time the
* metadata values have changed.
*
* <p> The default implementation computes tables used by the
* {@code decode} method to rescale components to different
* bit depths. Thus, if this method is overridden, it is
* important for the subclass method to call {@code super()},
* unless it overrides {@code decode} as well.
*/
public void beginDecoding() {
// Note: This method assumes that sourceBands, destinationBands,
// and bitsPerSample are all non-null which is true as they are
// set up that way in TIFFImageReader. Also the lengths and content
// of sourceBands and destinationBands are checked in TIFFImageReader
// before the present method is invoked.
// Determine if all of the relevant output bands have the
// same bit depth as the source data
this.adjustBitDepths = false;
int numBands = destinationBands.length;
int[] destBitsPerSample = null;
if(planar) {
int totalNumBands = bitsPerSample.length;
destBitsPerSample = new int[totalNumBands];
int dbps = image.getSampleModel().getSampleSize(0);
for(int b = 0; b < totalNumBands; b++) {
destBitsPerSample[b] = dbps;
}
} else {
destBitsPerSample = image.getSampleModel().getSampleSize();
}
for (int b = 0; b < numBands; b++) {
if (destBitsPerSample[destinationBands[b]] !=
bitsPerSample[sourceBands[b]]) {
adjustBitDepths = true;
break;
}
}
// If the bit depths differ, create a lookup table
// per band to perform the conversion
if(adjustBitDepths) {
// Compute the table only if this is the first time one is
// being computed or if any of the variables on which the
// table is based have changed.
if(this.isFirstBitDepthTable ||
planar != planarCache ||
!areIntArraysEqual(destBitsPerSample,
destBitsPerSampleCache) ||
!areIntArraysEqual(sourceBands,
sourceBandsCache) ||
!areIntArraysEqual(bitsPerSample,
bitsPerSampleCache) ||
!areIntArraysEqual(destinationBands,
destinationBandsCache)) {
this.isFirstBitDepthTable = false;
// Cache some variables.
this.planarCache = planar;
this.destBitsPerSampleCache =
destBitsPerSample.clone(); // never null ...
this.sourceBandsCache = sourceBands == null ?
null : sourceBands.clone();
this.bitsPerSampleCache = bitsPerSample == null ?
null : bitsPerSample.clone();
this.destinationBandsCache = destinationBands.clone();
// Allocate and fill the table.
bitDepthScale = new int[numBands][];
for (int b = 0; b < numBands; b++) {
int maxInSample = (1 << bitsPerSample[sourceBands[b]]) - 1;
int halfMaxInSample = maxInSample/2;
int maxOutSample =
(1 << destBitsPerSample[destinationBands[b]]) - 1;
bitDepthScale[b] = new int[maxInSample + 1];
for (int s = 0; s <= maxInSample; s++) {
bitDepthScale[b][s] =
(s*maxOutSample + halfMaxInSample)/
maxInSample;
}
}
}
} else { // !adjustBitDepths
// Clear any prior table.
this.bitDepthScale = null;
}
// Determine whether source and destination band lists are ramps.
// Note that these conditions will be true for planar images if
// and only if samplesPerPixel == 1, sourceBands[0] == 0, and
// destinationBands[0] == 0. For the purposes of this method, the
// only difference between such a planar image and a chunky image
// is the setting of the PlanarConfiguration field.
boolean sourceBandsNormal = false;
boolean destinationBandsNormal = false;
if (numBands == samplesPerPixel) {
sourceBandsNormal = true;
destinationBandsNormal = true;
for (int i = 0; i < numBands; i++) {
if (sourceBands[i] != i) {
sourceBandsNormal = false;
}
if (destinationBands[i] != i) {
destinationBandsNormal = false;
}
}
}
// Determine whether the image is bilevel and/or contiguous.
// Note that a planar image could be bilevel but it will not
// be contiguous unless it has a single component band stored
// in a single bank.
this.isBilevel =
ImageUtil.isBinary(this.image.getRaster().getSampleModel());
this.isContiguous = this.isBilevel ?
true : ImageUtil.imageIsContiguous(this.image);
// Analyze destination image to see if we can copy into it
// directly
this.isImageSimple =
(colorConverter == null) &&
(subsampleX == 1) && (subsampleY == 1) &&
(srcWidth == dstWidth) && (srcHeight == dstHeight) &&
((dstMinX + dstWidth) <= image.getWidth()) &&
((dstMinY + dstHeight) <= image.getHeight()) &&
sourceBandsNormal && destinationBandsNormal &&
!adjustBitDepths;
}
/**
* Decodes the input bit stream (located in the
* {@code ImageInputStream} {@code stream}, at offset
* {@code offset}, and continuing for {@code byteCount}
* bytes) into the output {@code BufferedImage}
* {@code image}.
*
* <p> The default implementation analyzes the destination image
* to determine if it is suitable as the destination for the
* {@code decodeRaw} method. If not, a suitable image is
* created. Next, {@code decodeRaw} is called to perform the
* actual decoding, and the results are copied into the
* destination image if necessary. Subsampling and offsetting are
* performed automatically.
*
* <p> The precise responsibilities of this routine are as
* follows. The input bit stream is defined by the instance
* variables {@code stream}, {@code offset}, and
* {@code byteCount}. These bits contain the data for the
* region of the source image defined by {@code srcMinX},
* {@code srcMinY}, {@code srcWidth}, and
* {@code srcHeight}.
*
* <p> The source data is required to be subsampling, starting at
* the {@code sourceXOffset}th column and including
* every {@code subsampleX}th pixel thereafter (and similarly
* for {@code sourceYOffset} and
* {@code subsampleY}).
*
* <p> Pixels are copied into the destination with an addition shift of
* ({@code dstXOffset}, {@code dstYOffset}). The complete
* set of formulas relating the source and destination coordinate spaces
* are:
*
* <pre>
* dx = (sx - sourceXOffset)/subsampleX + dstXOffset;
* dy = (sy - sourceYOffset)/subsampleY + dstYOffset;
* </pre>
*
* Only source pixels such that {@code (sx - sourceXOffset) %
* subsampleX == 0} and {@code (sy - sourceYOffset) %
* subsampleY == 0} are copied.
*
* <p> The inverse mapping, from destination to source coordinates,
* is one-to-one:
*
* <pre>
* sx = (dx - dstXOffset)*subsampleX + sourceXOffset;
* sy = (dy - dstYOffset)*subsampleY + sourceYOffset;
* </pre>
*
* <p> The region of the destination image to be updated is given
* by the instance variables {@code dstMinX},
* {@code dstMinY}, {@code dstWidth}, and
* {@code dstHeight}.
*
* <p> It is possible that not all of the source data being read
* will contribute to the destination image. For example, the
* destination offsets could be set such that some of the source
* pixels land outside of the bounds of the image. As a
* convenience, the bounds of the active source region (that is,
* the region of the strip or tile being read that actually
* contributes to the destination image, taking clipping into
* account) are available as {@code activeSrcMinX},
* {@code activeSrcMinY}, {@code activeSrcWidth} and
* {@code activeSrcHeight}. Thus, the source pixel at
* ({@code activeSrcMinX}, {@code activeSrcMinY}) will
* map to the destination pixel ({@code dstMinX},
* {@code dstMinY}).
*
* <p> The sequence of source bands given by
* {@code sourceBands} are to be copied into the sequence of
* bands in the destination given by
* {@code destinationBands}.
*
* <p> Some standard tag information is provided the instance
* variables {@code photometricInterpretation},
* {@code compression}, {@code samplesPerPixel},
* {@code bitsPerSample}, {@code sampleFormat},
* {@code extraSamples}, and {@code colorMap}.
*
* <p> In practice, unless there is a significant performance
* advantage to be gained by overriding this routine, most users
* will prefer to use the default implementation of this routine,
* and instead override the {@code decodeRaw} and/or
* {@code getRawImageType} methods.
*
* @exception IOException if an error occurs in
* {@code decodeRaw}.
*/
public void decode() throws IOException {
byte[] byteData = null;
short[] shortData = null;
int[] intData = null;
float[] floatData = null;
double[] doubleData = null;
int dstOffset = 0;
int pixelBitStride = 1;
int scanlineStride = 0;
// Analyze raw image
this.rawImage = null;
if(isImageSimple) {
if(isBilevel) {
rawImage = this.image;
} else if (isContiguous) {
rawImage =
image.getSubimage(dstMinX, dstMinY, dstWidth, dstHeight);
}
}
boolean isDirectCopy = rawImage != null;
if(rawImage == null) {
rawImage = createRawImage();
if (rawImage == null) {
throw new IIOException("Couldn't create image buffer!");
}
}
WritableRaster ras = rawImage.getRaster();
if(isBilevel) {
Rectangle rect = isImageSimple ?
new Rectangle(dstMinX, dstMinY, dstWidth, dstHeight) :
ras.getBounds();
byteData = ImageUtil.getPackedBinaryData(ras, rect);
dstOffset = 0;
pixelBitStride = 1;
scanlineStride = (rect.width + 7)/8;
} else {
SampleModel sm = ras.getSampleModel();
DataBuffer db = ras.getDataBuffer();
boolean isSupportedType = false;
if (sm instanceof ComponentSampleModel) {
ComponentSampleModel csm = (ComponentSampleModel)sm;
dstOffset = csm.getOffset(-ras.getSampleModelTranslateX(),
-ras.getSampleModelTranslateY());
scanlineStride = csm.getScanlineStride();
if(db instanceof DataBufferByte) {
DataBufferByte dbb = (DataBufferByte)db;
byteData = dbb.getData();
pixelBitStride = csm.getPixelStride()*8;
isSupportedType = true;
} else if(db instanceof DataBufferUShort) {
DataBufferUShort dbus = (DataBufferUShort)db;
shortData = dbus.getData();
pixelBitStride = csm.getPixelStride()*16;
isSupportedType = true;
} else if(db instanceof DataBufferShort) {
DataBufferShort dbs = (DataBufferShort)db;
shortData = dbs.getData();
pixelBitStride = csm.getPixelStride()*16;
isSupportedType = true;
} else if(db instanceof DataBufferInt) {
DataBufferInt dbi = (DataBufferInt)db;
intData = dbi.getData();
pixelBitStride = csm.getPixelStride()*32;
isSupportedType = true;
} else if(db instanceof DataBufferFloat) {
DataBufferFloat dbf = (DataBufferFloat)db;
floatData = dbf.getData();
pixelBitStride = csm.getPixelStride()*32;
isSupportedType = true;
} else if(db instanceof DataBufferDouble) {
DataBufferDouble dbd = (DataBufferDouble)db;
doubleData = dbd.getData();
pixelBitStride = csm.getPixelStride()*64;
isSupportedType = true;
}
} else if (sm instanceof MultiPixelPackedSampleModel) {
MultiPixelPackedSampleModel mppsm =
(MultiPixelPackedSampleModel)sm;
dstOffset =
mppsm.getOffset(-ras.getSampleModelTranslateX(),
-ras.getSampleModelTranslateY());
pixelBitStride = mppsm.getPixelBitStride();
scanlineStride = mppsm.getScanlineStride();
if(db instanceof DataBufferByte) {
DataBufferByte dbb = (DataBufferByte)db;
byteData = dbb.getData();
isSupportedType = true;
} else if(db instanceof DataBufferUShort) {
DataBufferUShort dbus = (DataBufferUShort)db;
shortData = dbus.getData();
isSupportedType = true;
} else if(db instanceof DataBufferInt) {
DataBufferInt dbi = (DataBufferInt)db;
intData = dbi.getData();
isSupportedType = true;
}
} else if (sm instanceof SinglePixelPackedSampleModel) {
SinglePixelPackedSampleModel sppsm =
(SinglePixelPackedSampleModel)sm;
dstOffset =
sppsm.getOffset(-ras.getSampleModelTranslateX(),
-ras.getSampleModelTranslateY());
scanlineStride = sppsm.getScanlineStride();
if(db instanceof DataBufferByte) {
DataBufferByte dbb = (DataBufferByte)db;
byteData = dbb.getData();
pixelBitStride = 8;
isSupportedType = true;
} else if(db instanceof DataBufferUShort) {
DataBufferUShort dbus = (DataBufferUShort)db;
shortData = dbus.getData();
pixelBitStride = 16;
isSupportedType = true;
} else if(db instanceof DataBufferInt) {
DataBufferInt dbi = (DataBufferInt)db;
intData = dbi.getData();
pixelBitStride = 32;
isSupportedType = true;
}
}
if(!isSupportedType) {
throw new IIOException
("Unsupported raw image type: SampleModel = "+sm+
"; DataBuffer = "+db);
}
}
if(isBilevel) {
// Bilevel data are always in a contiguous byte buffer.
decodeRaw(byteData, dstOffset, pixelBitStride, scanlineStride);
} else {
SampleModel sm = ras.getSampleModel();
// Branch based on whether data are bit-contiguous, i.e.,
// data are packaed as tightly as possible leaving no unused
// bits except at the end of a row.
if(isDataBufferBitContiguous(sm, bitsPerSample)) {
// Use byte or float data directly.
if (byteData != null) {
decodeRaw(byteData, dstOffset,
pixelBitStride, scanlineStride);
} else if (floatData != null) {
decodeRaw(floatData, dstOffset,
pixelBitStride, scanlineStride);
} else if (doubleData != null) {
decodeRaw(doubleData, dstOffset,
pixelBitStride, scanlineStride);
} else {
if (shortData != null) {
if(areSampleSizesEqual(sm) &&
sm.getSampleSize(0) == 16) {
// Decode directly into short data.
decodeRaw(shortData, dstOffset,
pixelBitStride, scanlineStride);
} else {
// Decode into bytes and reformat into shorts.
int bpp = getBitsPerPixel(sm);
int bytesPerRow = (bpp*srcWidth + 7)/8;
byte[] buf = new byte[bytesPerRow*srcHeight];
decodeRaw(buf, 0, bpp, bytesPerRow);
reformatData(buf, bytesPerRow, srcHeight,
shortData, null,
dstOffset, scanlineStride);
}
} else if (intData != null) {
if(areSampleSizesEqual(sm) &&
sm.getSampleSize(0) == 32) {
// Decode directly into int data.
decodeRaw(intData, dstOffset,
pixelBitStride, scanlineStride);
} else {
// Decode into bytes and reformat into ints.
int bpp = getBitsPerPixel(sm);
int bytesPerRow = (bpp*srcWidth + 7)/8;
byte[] buf = new byte[bytesPerRow*srcHeight];
decodeRaw(buf, 0, bpp, bytesPerRow);
reformatData(buf, bytesPerRow, srcHeight,
null, intData,
dstOffset, scanlineStride);
}
}
}
} else {
// Read discontiguous data into bytes and set the samples
// into the Raster.
int bpp;
if (planar) {
bpp = bitsPerSample[planarBand];
} else {
bpp = 0;
for (int bps : bitsPerSample) {
bpp += bps;
}
}
int bytesPerRow = (bpp*srcWidth + 7)/8;
byte[] buf = new byte[bytesPerRow*srcHeight];
decodeRaw(buf, 0, bpp, bytesPerRow);
reformatDiscontiguousData(buf, bitsPerSample, bytesPerRow,
srcWidth, srcHeight,
ras);
}
}
if (colorConverter != null) {
float[] rgb = new float[3];
if(byteData != null) {
for (int j = 0; j < dstHeight; j++) {
int idx = dstOffset;
for (int i = 0; i < dstWidth; i++) {
float x0 = (float)(byteData[idx] & 0xff);
float x1 = (float)(byteData[idx + 1] & 0xff);
float x2 = (float)(byteData[idx + 2] & 0xff);
colorConverter.toRGB(x0, x1, x2, rgb);
byteData[idx] = (byte)(rgb[0]);
byteData[idx + 1] = (byte)(rgb[1]);
byteData[idx + 2] = (byte)(rgb[2]);
idx += 3;
}
dstOffset += scanlineStride;
}
} else if(shortData != null) {
if(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) {
for (int j = 0; j < dstHeight; j++) {
int idx = dstOffset;
for (int i = 0; i < dstWidth; i++) {
float x0 = (float)shortData[idx];
float x1 = (float)shortData[idx + 1];
float x2 = (float)shortData[idx + 2];
colorConverter.toRGB(x0, x1, x2, rgb);
shortData[idx] = (short)(rgb[0]);
shortData[idx + 1] = (short)(rgb[1]);
shortData[idx + 2] = (short)(rgb[2]);
idx += 3;
}
dstOffset += scanlineStride;
}
} else {
for (int j = 0; j < dstHeight; j++) {
int idx = dstOffset;
for (int i = 0; i < dstWidth; i++) {
float x0 = (float)(shortData[idx] & 0xffff);
float x1 = (float)(shortData[idx + 1] & 0xffff);
float x2 = (float)(shortData[idx + 2] & 0xffff);
colorConverter.toRGB(x0, x1, x2, rgb);
shortData[idx] = (short)(rgb[0]);
shortData[idx + 1] = (short)(rgb[1]);
shortData[idx + 2] = (short)(rgb[2]);
idx += 3;
}
dstOffset += scanlineStride;
}
}
} else if(intData != null) {
for (int j = 0; j < dstHeight; j++) {
int idx = dstOffset;
for (int i = 0; i < dstWidth; i++) {
float x0 = (float)intData[idx];
float x1 = (float)intData[idx + 1];
float x2 = (float)intData[idx + 2];
colorConverter.toRGB(x0, x1, x2, rgb);
intData[idx] = (int)(rgb[0]);
intData[idx + 1] = (int)(rgb[1]);
intData[idx + 2] = (int)(rgb[2]);
idx += 3;
}
dstOffset += scanlineStride;
}
} else if(floatData != null) {
for (int j = 0; j < dstHeight; j++) {
int idx = dstOffset;
for (int i = 0; i < dstWidth; i++) {
float x0 = floatData[idx];
float x1 = floatData[idx + 1];
float x2 = floatData[idx + 2];
colorConverter.toRGB(x0, x1, x2, rgb);
floatData[idx] = rgb[0];
floatData[idx + 1] = rgb[1];
floatData[idx + 2] = rgb[2];
idx += 3;
}
dstOffset += scanlineStride;
}
} else if(doubleData != null) {
for (int j = 0; j < dstHeight; j++) {
int idx = dstOffset;
for (int i = 0; i < dstWidth; i++) {
// Note: Possible loss of precision.
float x0 = (float)doubleData[idx];
float x1 = (float)doubleData[idx + 1];
float x2 = (float)doubleData[idx + 2];
colorConverter.toRGB(x0, x1, x2, rgb);
doubleData[idx] = rgb[0];
doubleData[idx + 1] = rgb[1];
doubleData[idx + 2] = rgb[2];
idx += 3;
}
dstOffset += scanlineStride;
}
}
}
if (photometricInterpretation ==
BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) {
if(byteData != null) {
int bytesPerRow = (srcWidth*pixelBitStride + 7)/8;
for (int y = 0; y < srcHeight; y++) {
int offset = dstOffset + y*scanlineStride;
for (int i = 0; i < bytesPerRow; i++) {
byteData[offset + i] ^= 0xff;
}
}
} else if(shortData != null) {
int shortsPerRow = (srcWidth*pixelBitStride + 15)/16;
if(sampleFormat[0] ==
BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) {
for (int y = 0; y < srcHeight; y++) {
int offset = dstOffset + y*scanlineStride;
for (int i = 0; i < shortsPerRow; i++) {
int shortOffset = offset + i;
shortData[shortOffset] =
(short)(Short.MAX_VALUE -
shortData[shortOffset]);
}
}
} else {
for (int y = 0; y < srcHeight; y++) {
int offset = dstOffset + y*scanlineStride;
for (int i = 0; i < shortsPerRow; i++) {
shortData[offset + i] ^= 0xffff;
}
}
}
} else if(intData != null) {
int intsPerRow = (srcWidth*pixelBitStride + 31)/32;
for (int y = 0; y < srcHeight; y++) {
int offset = dstOffset + y*scanlineStride;
for (int i = 0; i < intsPerRow; i++) {
int intOffset = offset + i;
intData[intOffset] =
Integer.MAX_VALUE - intData[intOffset];
}
}
} else if(floatData != null) {
int floatsPerRow = (srcWidth*pixelBitStride + 31)/32;
for (int y = 0; y < srcHeight; y++) {
int offset = dstOffset + y*scanlineStride;
for (int i = 0; i < floatsPerRow; i++) {
int floatOffset = offset + i;
floatData[floatOffset] =
1.0F - floatData[floatOffset];
}
}
} else if(doubleData != null) {
int doublesPerRow = (srcWidth*pixelBitStride + 63)/64;
for (int y = 0; y < srcHeight; y++) {
int offset = dstOffset + y*scanlineStride;
for (int i = 0; i < doublesPerRow; i++) {
int doubleOffset = offset + i;
doubleData[doubleOffset] =
1.0F - doubleData[doubleOffset];
}
}
}
}
if(isBilevel) {
Rectangle rect = isImageSimple ?
new Rectangle(dstMinX, dstMinY, dstWidth, dstHeight) :
ras.getBounds();
ImageUtil.setPackedBinaryData(byteData, ras, rect);
}
if (isDirectCopy) { // rawImage == image) {
return;
}
// Copy the raw image data into the true destination image
Raster src = rawImage.getRaster();
// Create band child of source
Raster srcChild = src.createChild(0, 0,
srcWidth, srcHeight,
srcMinX, srcMinY,
planar ? null : sourceBands);
WritableRaster dst = image.getRaster();
// Create dst child covering area and bands to be written
WritableRaster dstChild = dst.createWritableChild(dstMinX, dstMinY,
dstWidth, dstHeight,
dstMinX, dstMinY,
destinationBands);
if (subsampleX == 1 && subsampleY == 1 && !adjustBitDepths) {
srcChild = srcChild.createChild(activeSrcMinX,
activeSrcMinY,
activeSrcWidth, activeSrcHeight,
dstMinX, dstMinY,
null);
dstChild.setRect(srcChild);
} else if (subsampleX == 1 && !adjustBitDepths) {
int sy = activeSrcMinY;
int dy = dstMinY;
while (sy < srcMinY + srcHeight) {
Raster srcRow = srcChild.createChild(activeSrcMinX, sy,
activeSrcWidth, 1,
dstMinX, dy,
null);
dstChild.setRect(srcRow);
sy += subsampleY;
++dy;
}
} else {
int[] p = srcChild.getPixel(srcMinX, srcMinY, (int[])null);
int numBands = p.length;
int sy = activeSrcMinY;
int dy = dstMinY;
while (sy < activeSrcMinY + activeSrcHeight) {
int sx = activeSrcMinX;
int dx = dstMinX;
while (sx < activeSrcMinX + activeSrcWidth) {
srcChild.getPixel(sx, sy, p);
if (adjustBitDepths) {
for (int band = 0; band < numBands; band++) {
p[band] = bitDepthScale[band][p[band]];
}
}
dstChild.setPixel(dx, dy, p);
sx += subsampleX;
++dx;
}
sy += subsampleY;
++dy;
}
}
}
}