| /* |
| * 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; |
| } |
| } |
| } |
| } |