All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.imaging.formats.tiff.datareaders.DataReaderStrips Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.imaging.formats.tiff.datareaders;

import java.awt.Rectangle;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteOrder;

import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.common.ImageBuilder;
import org.apache.commons.imaging.formats.tiff.TiffRasterData;
import org.apache.commons.imaging.formats.tiff.TiffDirectory;
import org.apache.commons.imaging.formats.tiff.TiffImageData;
import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat;
import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt;
import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;

/**
 * Provides a data reader for TIFF file images organized by tiles.
 * 

* See {@link ImageDataReader} for notes discussing design and development with * particular emphasis on run-time performance. */ public final class DataReaderStrips extends ImageDataReader { private final int bitsPerPixel; private final int compression; private final int rowsPerStrip; private final TiffPlanarConfiguration planarConfiguration; private final ByteOrder byteOrder; private int x; private int y; private final TiffImageData.Strips imageData; public DataReaderStrips(final TiffDirectory directory, final PhotometricInterpreter photometricInterpreter, final int bitsPerPixel, final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat, final int width, final int height, final int compression, final TiffPlanarConfiguration planarConfiguration, final ByteOrder byteOrder, final int rowsPerStrip, final TiffImageData.Strips imageData) { super(directory, photometricInterpreter, bitsPerSample, predictor, samplesPerPixel, sampleFormat, width, height, planarConfiguration); this.bitsPerPixel = bitsPerPixel; this.compression = compression; this.rowsPerStrip = rowsPerStrip; this.planarConfiguration = planarConfiguration; this.imageData = imageData; this.byteOrder = byteOrder; } private void interpretStrip( final ImageBuilder imageBuilder, final byte[] bytes, final int pixelsPerStrip, final int yLimit) throws ImageReadException, IOException { if (y >= yLimit) { return; } // changes added March 2020 if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) { int k = 0; int nRows = pixelsPerStrip / width; if (y + nRows > yLimit) { nRows = yLimit - y; } final int i0 = y; final int i1 = y + nRows; x = 0; y += nRows; final int[] samples = new int[1]; final int[] b = unpackFloatingPointSamples( width, i1 - i0, width, bytes, bitsPerPixel, byteOrder); for (int i = i0; i < i1; i++) { for (int j = 0; j < width; j++) { samples[0] = b[k]; k += samplesPerPixel; photometricInterpreter.interpretPixel(imageBuilder, samples, j, i); } } return; } // changes added May 2012 // In the original implementation, a general-case bit reader called // getSamplesAsBytes is used to retrieve the samples (raw data values) // for each pixel in the strip. These samples are then passed into a // photogrammetric interpreter that converts them to ARGB pixel values // and stores them in the image. Because the bit-reader must handle // a large number of formats, it involves several conditional // branches that must be executed each time a pixel is read. // Depending on the size of an image, the same evaluations must be // executed redundantly thousands and perhaps millions of times // in order to process the complete collection of pixels. // This code attempts to remove that redundancy by // evaluating the format up-front and bypassing the general-format // code for two commonly used data formats: the 8 bits-per-pixel // and 24 bits-per-pixel cases. For these formats, the // special case code achieves substantial reductions in image-loading // time. In other cases, it simply falls through to the original code // and continues to read the data correctly as it did in previous // versions of this class. // In addition to bypassing the getBytesForSample() method, // the 24-bit case also implements a special block for RGB // formatted images. To get a sense of the contributions of each // optimization (removing getSamplesAsBytes and removing the // photometric interpreter), consider the following results from tests // conducted with large TIFF images using the 24-bit RGB format // bypass getSamplesAsBytes: 67.5 % reduction // bypass both optimizations: 77.2 % reduction // // // Future Changes // Both of the 8-bit and 24-bit blocks make the assumption that a strip // always begins on x = 0 and that each strip exactly fills out the rows // it contains (no half rows). The original code did not make this // assumption, but the approach is consistent with the TIFF 6.0 spec // (1992), // and should probably be considered as an enhancement to the // original general-case code block that remains from the original // implementation. Taking this approach saves one conditional // operation per pixel or about 5 percent of the total run time // in the 8 bits/pixel case. // verify that all samples are one byte in size final boolean allSamplesAreOneByte = isHomogenous(8); if (predictor != 2 && bitsPerPixel == 8 && allSamplesAreOneByte) { int k = 0; int nRows = pixelsPerStrip / width; if (y + nRows > yLimit) { nRows = yLimit - y; } final int i0 = y; final int i1 = y + nRows; x = 0; y += nRows; final int[] samples = new int[1]; for (int i = i0; i < i1; i++) { for (int j = 0; j < width; j++) { samples[0] = bytes[k++] & 0xff; photometricInterpreter.interpretPixel(imageBuilder, samples, j, i); } } return; } if ((bitsPerPixel == 24 || bitsPerPixel == 32) && allSamplesAreOneByte && photometricInterpreter instanceof PhotometricInterpreterRgb) { int k = 0; int nRows = pixelsPerStrip / width; if (y + nRows > yLimit) { nRows = yLimit - y; } final int i0 = y; final int i1 = y + nRows; x = 0; y += nRows; if (predictor == TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) { applyPredictorToBlock(width, nRows, samplesPerPixel, bytes); } if (bitsPerPixel == 24) { // 24 bit case, we don't mask the red byte because any // sign-extended bits get covered by opacity mask for (int i = i0; i < i1; i++) { for (int j = 0; j < width; j++, k += 3) { final int rgb = 0xff000000 | (bytes[k] << 16) | ((bytes[k + 1] & 0xff) << 8) | (bytes[k + 2] & 0xff); imageBuilder.setRGB(j, i, rgb); } } } else { // 32 bit case, we don't mask the high byte because any // sign-extended bits get shifted up and out of result for (int i = i0; i < i1; i++) { for (int j = 0; j < width; j++, k += 4) { final int rgb = ((bytes[k] & 0xff) << 16) | ((bytes[k + 1] & 0xff) << 8) | (bytes[k + 2] & 0xff) | (bytes[k + 3] << 24); imageBuilder.setRGB(j, i, rgb); } } } return; } // ------------------------------------------------------------ // original code before May 2012 modification // this logic will handle all cases not conforming to the // special case handled above try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) { int[] samples = new int[bitsPerSampleLength]; resetPredictor(); for (int i = 0; i < pixelsPerStrip; i++) { getSamplesAsBytes(bis, samples); if (x < width) { samples = applyPredictor(samples); photometricInterpreter.interpretPixel(imageBuilder, samples, x, y); } x++; if (x >= width) { x = 0; resetPredictor(); y++; bis.flushCache(); if (y >= yLimit) { break; } } } } } @Override public ImageBuilder readImageData(final Rectangle subImageSpecification, final boolean hasAlpha, final boolean isAlphaPreMultiplied) throws ImageReadException, IOException { final Rectangle subImage; if (subImageSpecification == null) { // configure subImage to read entire image subImage = new Rectangle(0, 0, width, height); } else { subImage = subImageSpecification; } // the legacy code is optimized to the reading of whole // strips (except for the last strip in the image, which can // be a partial). So create a working image with compatible // dimensions and read that. Later on, the working image // will be sub-imaged to the proper size. // strip0 and strip1 give the indices of the strips containing // the first and last rows of pixels in the subimage final int strip0 = subImage.y / rowsPerStrip; final int strip1 = (subImage.y + subImage.height - 1) / rowsPerStrip; final int workingHeight = (strip1 - strip0 + 1) * rowsPerStrip; // the legacy code uses a member element "y" to keep track // of the row index of the output image that is being processed // by interpretStrip. y is set to zero before the first // call to interpretStrip. y0 will be the index of the first row // in the full image (the source image) that will be processed. final int y0 = strip0 * rowsPerStrip; final int yLimit = subImage.y - y0 + subImage.height; final ImageBuilder workingBuilder = new ImageBuilder(width, workingHeight, hasAlpha, isAlphaPreMultiplied); if (planarConfiguration != TiffPlanarConfiguration.PLANAR) { for (int strip = strip0; strip <= strip1; strip++) { final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip; final long rowsRemaining = height - (strip * rowsPerStripLong); final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong); final long bytesPerRow = (bitsPerPixel * width + 7) / 8; final long bytesPerStrip = rowsInThisStrip * bytesPerRow; final long pixelsPerStrip = rowsInThisStrip * width; final byte[] compressed = imageData.getImageData(strip).getData(); final byte[] decompressed = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip); interpretStrip( workingBuilder, decompressed, (int) pixelsPerStrip, yLimit); } } else { final int nStripsInPlane = imageData.getImageDataLength() / 3; for (int strip = strip0; strip <= strip1; strip++) { final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip; final long rowsRemaining = height - (strip * rowsPerStripLong); final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong); final long bytesPerRow = (bitsPerPixel * width + 7) / 8; final long bytesPerStrip = rowsInThisStrip * bytesPerRow; final long pixelsPerStrip = rowsInThisStrip * width; final byte[] b = new byte[(int) bytesPerStrip]; for (int iPlane = 0; iPlane < 3; iPlane++) { final int planeStrip = iPlane * nStripsInPlane + strip; final byte[] compressed = imageData.getImageData(planeStrip).getData(); final byte[] decompressed = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip); int index = iPlane; for (final byte element : decompressed) { b[index] = element; index += 3; } } interpretStrip(workingBuilder, b, (int) pixelsPerStrip, height); } } if (subImage.x == 0 && subImage.y == y0 && subImage.width == width && subImage.height == workingHeight) { // the subimage exactly matches the ImageBuilder bounds // so we can return that. return workingBuilder; } return workingBuilder.getSubset( subImage.x, subImage.y - y0, subImage.width, subImage.height); } @Override public TiffRasterData readRasterData(final Rectangle subImage) throws ImageReadException, IOException { switch (sampleFormat) { case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT: return readRasterDataFloat(subImage); case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER: return readRasterDataInt(subImage); default: throw new ImageReadException("Unsupported sample format, value=" + sampleFormat); } } private TiffRasterData readRasterDataFloat(final Rectangle subImage) throws ImageReadException, IOException { int xRaster; int yRaster; int rasterWidth; int rasterHeight; if (subImage != null) { xRaster = subImage.x; yRaster = subImage.y; rasterWidth = subImage.width; rasterHeight = subImage.height; } else { xRaster = 0; yRaster = 0; rasterWidth = width; rasterHeight = height; } float[] rasterDataFloat = new float[rasterWidth * rasterHeight * samplesPerPixel]; // the legacy code is optimized to the reading of whole // strips (except for the last strip in the image, which can // be a partial). So create a working image with compatible // dimensions and read that. Later on, the working image // will be sub-imaged to the proper size. // strip0 and strip1 give the indices of the strips containing // the first and last rows of pixels in the subimage final int strip0 = yRaster / rowsPerStrip; final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip; for (int strip = strip0; strip <= strip1; strip++) { final int yStrip = strip * rowsPerStrip; final int rowsRemaining = height - yStrip; final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip); final int bytesPerRow = (bitsPerPixel * width + 7) / 8; final int bytesPerStrip = rowsInThisStrip * bytesPerRow; final byte[] compressed = imageData.getImageData(strip).getData(); final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip); final int[] blockData = unpackFloatingPointSamples( width, rowsInThisStrip, width, decompressed, bitsPerPixel, byteOrder); transferBlockToRaster(0, yStrip, width, (int) rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat); } return new TiffRasterDataFloat(rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat); } private TiffRasterData readRasterDataInt(final Rectangle subImage) throws ImageReadException, IOException { int xRaster; int yRaster; int rasterWidth; int rasterHeight; if (subImage != null) { xRaster = subImage.x; yRaster = subImage.y; rasterWidth = subImage.width; rasterHeight = subImage.height; } else { xRaster = 0; yRaster = 0; rasterWidth = width; rasterHeight = height; } int[] rasterDataInt = new int[rasterWidth * rasterHeight]; // the legacy code is optimized to the reading of whole // strips (except for the last strip in the image, which can // be a partial). So create a working image with compatible // dimensions and read that. Later on, the working image // will be sub-imaged to the proper size. // strip0 and strip1 give the indices of the strips containing // the first and last rows of pixels in the subimage final int strip0 = yRaster / rowsPerStrip; final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip; for (int strip = strip0; strip <= strip1; strip++) { final int yStrip = strip * rowsPerStrip; final int rowsRemaining = height - yStrip; final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip); final int bytesPerRow = (bitsPerPixel * width + 7) / 8; final int bytesPerStrip = rowsInThisStrip * bytesPerRow; final byte[] compressed = imageData.getImageData(strip).getData(); final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip); final int[] blockData = unpackIntSamples( width, rowsInThisStrip, width, decompressed, predictor, bitsPerPixel, byteOrder); transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, rasterDataInt); } return new TiffRasterDataInt(rasterWidth, rasterHeight, rasterDataInt); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy