    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.imaging.formats.tiff.datareaders;
    018
    019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.COMPRESSION_JPEG;
    020
    021import java.awt.Rectangle;
    022import java.io.ByteArrayInputStream;
    023import java.io.IOException;
    024import java.nio.ByteOrder;
    025
    026import org.apache.commons.imaging.ImagingException;
    027import org.apache.commons.imaging.common.Allocator;
    028import org.apache.commons.imaging.common.ImageBuilder;
    029import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData;
    030import org.apache.commons.imaging.formats.tiff.AbstractTiffRasterData;
    031import org.apache.commons.imaging.formats.tiff.TiffDirectory;
    032import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat;
    033import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt;
    034import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
    035import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
    036import org.apache.commons.imaging.formats.tiff.photometricinterpreters.AbstractPhotometricInterpreter;
    037import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
    038
    039/**
    040 * Provides a data reader for TIFF file images organized by tiles.
    041 * <p>
    042 * See {@link AbstractImageDataReader} for notes discussing design and development with particular emphasis on run-time performance.
    043 */
    044public final class DataReaderStrips extends AbstractImageDataReader {
    045
    046    private final int bitsPerPixel;
    047    private final int compression;
    048    private final int rowsPerStrip;
    049    private final TiffPlanarConfiguration planarConfiguration;
    050    private final ByteOrder byteOrder;
    051    private int x;
    052    private int y;
    053    private final AbstractTiffImageData.Strips imageData;
    054
    055    public DataReaderStrips(final TiffDirectory directory, final AbstractPhotometricInterpreter photometricInterpreter, final int bitsPerPixel,
    056            final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat, final int width, final int height,
    057            final int compression, final TiffPlanarConfiguration planarConfiguration, final ByteOrder byteOrder, final int rowsPerStrip,
    058            final AbstractTiffImageData.Strips imageData) {
    059        super(directory, photometricInterpreter, bitsPerSample, predictor, samplesPerPixel, sampleFormat, width, height, planarConfiguration);
    060
    061        this.bitsPerPixel = bitsPerPixel;
    062        this.compression = compression;
    063        this.rowsPerStrip = rowsPerStrip;
    064        this.planarConfiguration = planarConfiguration;
    065        this.imageData = imageData;
    066        this.byteOrder = byteOrder;
    067    }
    068
    069    private void interpretStrip(final ImageBuilder imageBuilder, final byte[] bytes, final int pixelsPerStrip, final int yLimit)
    070            throws ImagingException, IOException {
    071        if (y >= yLimit) {
    072            return;
    073        }
    074
    075        // changes added March 2020
    076        if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
    077            int k = 0;
    078            int nRows = pixelsPerStrip / width;
    079            if (y + nRows > yLimit) {
    080                nRows = yLimit - y;
    081            }
    082            final int i0 = y;
    083            final int i1 = y + nRows;
    084            x = 0;
    085            y += nRows;
    086            final int[] samples = new int[1];
    087            final int[] b = unpackFloatingPointSamples(width, i1 - i0, width, bytes, bitsPerPixel, byteOrder);
    088
    089            for (int i = i0; i < i1; i++) {
    090                for (int j = 0; j < width; j++) {
    091                    samples[0] = b[k];
    092                    k += samplesPerPixel;
    093                    photometricInterpreter.interpretPixel(imageBuilder, samples, j, i);
    094                }
    095            }
    096
    097            return;
    098        }
    099
    100        // changes added May 2012
    101        // In the original implementation, a general-case bit reader called
    102        // getSamplesAsBytes is used to retrieve the samples (raw data values)
    103        // for each pixel in the strip. These samples are then passed into a
    104        // photogrammetric interpreter that converts them to ARGB pixel values
    105        // and stores them in the image. Because the bit-reader must handle
    106        // a large number of formats, it involves several conditional
    107        // branches that must be executed each time a pixel is read.
    108        // Depending on the size of an image, the same evaluations must be
    109        // executed redundantly thousands and perhaps millions of times
    110        // in order to process the complete collection of pixels.
    111        // This code attempts to remove that redundancy by
    112        // evaluating the format up-front and bypassing the general-format
    113        // code for two commonly used data formats: the 8 bits-per-pixel
    114        // and 24 bits-per-pixel cases. For these formats, the
    115        // special case code achieves substantial reductions in image-loading
    116        // time. In other cases, it simply falls through to the original code
    117        // and continues to read the data correctly as it did in previous
    118        // versions of this class.
    119        // In addition to bypassing the getBytesForSample() method,
    120        // the 24-bit case also implements a special block for RGB
    121        // formatted images. To get a sense of the contributions of each
    122        // optimization (removing getSamplesAsBytes and removing the
    123        // photometric interpreter), consider the following results from tests
    124        // conducted with large TIFF images using the 24-bit RGB format
    125        // bypass getSamplesAsBytes: 67.5 % reduction
    126        // bypass both optimizations: 77.2 % reduction
    127        //
    128        //
    129        // Future Changes
    130        // Both of the 8-bit and 24-bit blocks make the assumption that a strip
    131        // always begins on x = 0 and that each strip exactly fills out the rows
    132        // it contains (no half rows). The original code did not make this
    133        // assumption, but the approach is consistent with the TIFF 6.0 spec
    134        // (1992),
    135        // and should probably be considered as an enhancement to the
    136        // original general-case code block that remains from the original
    137        // implementation. Taking this approach saves one conditional
    138        // operation per pixel or about 5 percent of the total run time
    139        // in the 8 bits/pixel case.
    140        // verify that all samples are one byte in size
    141        final boolean allSamplesAreOneByte = isHomogenous(8);
    142
    143        if (predictor != 2 && bitsPerPixel == 8 && allSamplesAreOneByte) {
    144            int k = 0;
    145            int nRows = pixelsPerStrip / width;
    146            if (y + nRows > yLimit) {
    147                nRows = yLimit - y;
    148            }
    149            final int i0 = y;
    150            final int i1 = y + nRows;
    151            x = 0;
    152            y += nRows;
    153            final int[] samples = new int[1];
    154            for (int i = i0; i < i1; i++) {
    155                for (int j = 0; j < width; j++) {
    156                    samples[0] = bytes[k++] & 0xff;
    157                    photometricInterpreter.interpretPixel(imageBuilder, samples, j, i);
    158                }
    159            }
    160            return;
    161        }
    162        if ((bitsPerPixel == 24 || bitsPerPixel == 32) && allSamplesAreOneByte && photometricInterpreter instanceof PhotometricInterpreterRgb) {
    163            int k = 0;
    164            int nRows = pixelsPerStrip / width;
    165            if (y + nRows > yLimit) {
    166                nRows = yLimit - y;
    167            }
    168            final int i0 = y;
    169            final int i1 = y + nRows;
    170            x = 0;
    171            y += nRows;
    172            if (predictor == TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) {
    173                applyPredictorToBlock(width, nRows, samplesPerPixel, bytes);
    174            }
    175
    176            if (bitsPerPixel == 24) {
    177                // 24 bit case, we don't mask the red byte because any
    178                // sign-extended bits get covered by opacity mask
    179                for (int i = i0; i < i1; i++) {
    180                    for (int j = 0; j < width; j++, k += 3) {
    181                        final int rgb = 0xff000000 | bytes[k] << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff;
    182                        imageBuilder.setRgb(j, i, rgb);
    183                    }
    184                }
    185            } else {
    186                // 32 bit case, we don't mask the high byte because any
    187                // sign-extended bits get shifted up and out of result
    188                for (int i = i0; i < i1; i++) {
    189                    for (int j = 0; j < width; j++, k += 4) {
    190                        final int rgb = (bytes[k] & 0xff) << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff | bytes[k + 3] << 24;
    191                        imageBuilder.setRgb(j, i, rgb);
    192                    }
    193                }
    194            }
    195
    196            return;
    197        }
    198
    199        // original code before May 2012 modification
    200        // this logic will handle all cases not conforming to the
    201        // special case handled above
    202        try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) {
    203
    204            int[] samples = Allocator.intArray(bitsPerSampleLength);
    205            resetPredictor();
    206            for (int i = 0; i < pixelsPerStrip; i++) {
    207                getSamplesAsBytes(bis, samples);
    208
    209                if (x < width) {
    210                    samples = applyPredictor(samples);
    211
    212                    photometricInterpreter.interpretPixel(imageBuilder, samples, x, y);
    213                }
    214
    215                x++;
    216                if (x >= width) {
    217                    x = 0;
    218                    resetPredictor();
    219                    y++;
    220                    bis.flushCache();
    221                    if (y >= yLimit) {
    222                        break;
    223                    }
    224                }
    225            }
    226        }
    227    }
    228
    229    @Override
    230    public ImageBuilder readImageData(final Rectangle subImageSpecification, final boolean hasAlpha, final boolean isAlphaPreMultiplied)
    231            throws IOException, ImagingException {
    232
    233        final Rectangle subImage;
    234        if (subImageSpecification == null) {
    235            // configure subImage to read entire image
    236            subImage = new Rectangle(0, 0, width, height);
    237        } else {
    238            subImage = subImageSpecification;
    239        }
    240
    241        // the legacy code is optimized to the reading of whole
    242        // strips (except for the last strip in the image, which can
    243        // be a partial). So create a working image with compatible
    244        // dimensions and read that. Later on, the working image
    245        // will be sub-imaged to the proper size.
    246        // strip0 and strip1 give the indices of the strips containing
    247        // the first and last rows of pixels in the subimage
    248        final int strip0 = subImage.y / rowsPerStrip;
    249        final int strip1 = (subImage.y + subImage.height - 1) / rowsPerStrip;
    250        final int workingHeight = (strip1 - strip0 + 1) * rowsPerStrip;
    251
    252        // the legacy code uses a member element "y" to keep track
    253        // of the row index of the output image that is being processed
    254        // by interpretStrip. y is set to zero before the first
    255        // call to interpretStrip. y0 will be the index of the first row
    256        // in the full image (the source image) that will be processed.
    257        final int y0 = strip0 * rowsPerStrip;
    258        final int yLimit = subImage.y - y0 + subImage.height;
    259
    260        // When processing a subimage, the workingBuilder height is set
    261        // to be an integral multiple of the rowsPerStrip and
    262        // the full width of the strips. So the working image may be larger than
    263        // the specified size of the subimage. If necessary, the subimage
    264        // is extracted from the workingBuilder at the end of this method.
    265        // This approach avoids the need for the interpretStrips method
    266        // to implement bounds checking for a subimage.
    267        final ImageBuilder workingBuilder = new ImageBuilder(width, workingHeight, hasAlpha, isAlphaPreMultiplied);
    268
    269        // the following statement accounts for cases where planar configuration
    270        // is not specified and the default (CHUNKY) is assumed.
    271        final boolean interleaved = planarConfiguration != TiffPlanarConfiguration.PLANAR;
    272        if (interleaved) {
    273            // Pixel definitions are organized in an interleaved format
    274            // For example, red-green-blue values for each pixel
    275            // would appear contiguous in input sequence.
    276            for (int strip = strip0; strip <= strip1; strip++) {
    277                final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip;
    278                final long rowsRemaining = height - strip * rowsPerStripLong;
    279                final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong);
    280                final long bytesPerRow = (bitsPerPixel * width + 7) / 8;
    281                final long bytesPerStrip = rowsInThisStrip * bytesPerRow;
    282                final long pixelsPerStrip = rowsInThisStrip * width;
    283
    284                final byte[] compressed = imageData.getImageData(strip).getData();
    285
    286                if (compression == COMPRESSION_JPEG) {
    287                    final int yBlock = strip * rowsPerStrip;
    288                    final int yWork = yBlock - y0;
    289                    DataInterpreterJpeg.intepretBlock(directory, workingBuilder, 0, yWork, width, (int) rowsInThisStrip, compressed);
    290                    continue;
    291                }
    292
    293                final byte[] decompressed = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip);
    294
    295                interpretStrip(workingBuilder, decompressed, (int) pixelsPerStrip, yLimit);
    296            }
    297        } else {
    298            // pixel definitions are organized in a 3 separate sections of input
    299            // sequence. For example, red-green-blue values would be given as
    300            // red values for all pixels, followed by green values for all pixels,
    301            // etc.
    302            if (compression == COMPRESSION_JPEG) {
    303                throw new ImagingException("TIFF file in non-supported configuration: JPEG compression used in planar configuration.");
    304            }
    305            final int nStripsInPlane = imageData.getImageDataLength() / 3;
    306            for (int strip = strip0; strip <= strip1; strip++) {
    307                final long rowsPerStripLong = 0xFFFFffffL & rowsPerStrip;
    308                final long rowsRemaining = height - strip * rowsPerStripLong;
    309                final long rowsInThisStrip = Math.min(rowsRemaining, rowsPerStripLong);
    310                final long bytesPerRow = (bitsPerPixel * width + 7) / 8;
    311                final long bytesPerStrip = rowsInThisStrip * bytesPerRow;
    312                final long pixelsPerStrip = rowsInThisStrip * width;
    313
    314                final byte[] b = Allocator.byteArray((int) bytesPerStrip);
    315                for (int iPlane = 0; iPlane < 3; iPlane++) {
    316                    final int planeStrip = iPlane * nStripsInPlane + strip;
    317                    final byte[] compressed = imageData.getImageData(planeStrip).getData();
    318                    final byte[] decompressed = decompress(compressed, compression, (int) bytesPerStrip, width, (int) rowsInThisStrip);
    319                    int index = iPlane;
    320                    for (final byte element : decompressed) {
    321                        b[index] = element;
    322                        index += 3;
    323                    }
    324                }
    325                interpretStrip(workingBuilder, b, (int) pixelsPerStrip, height);
    326            }
    327        }
    328
    329        if (subImage.x == 0 && subImage.y == y0 && subImage.width == width && subImage.height == workingHeight) {
    330            // the subimage exactly matches the ImageBuilder bounds
    331            // so we can return that.
    332            return workingBuilder;
    333        }
    334        return workingBuilder.getSubset(subImage.x, subImage.y - y0, subImage.width, subImage.height);
    335    }
    336
    337    @Override
    338    public AbstractTiffRasterData readRasterData(final Rectangle subImage) throws ImagingException, IOException {
    339        switch (sampleFormat) {
    340        case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT:
    341            return readRasterDataFloat(subImage);
    342        case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER:
    343            return readRasterDataInt(subImage);
    344        default:
    345            throw new ImagingException("Unsupported sample format, value=" + sampleFormat);
    346        }
    347    }
    348
    349    private AbstractTiffRasterData readRasterDataFloat(final Rectangle subImage) throws ImagingException, IOException {
    350        final int xRaster;
    351        final int yRaster;
    352        final int rasterWidth;
    353        final int rasterHeight;
    354        if (subImage != null) {
    355            xRaster = subImage.x;
    356            yRaster = subImage.y;
    357            rasterWidth = subImage.width;
    358            rasterHeight = subImage.height;
    359        } else {
    360            xRaster = 0;
    361            yRaster = 0;
    362            rasterWidth = width;
    363            rasterHeight = height;
    364        }
    365
    366        final float[] rasterDataFloat = Allocator.floatArray(rasterWidth * rasterHeight * samplesPerPixel);
    367
    368        // the legacy code is optimized to the reading of whole
    369        // strips (except for the last strip in the image, which can
    370        // be a partial). So create a working image with compatible
    371        // dimensions and read that. Later on, the working image
    372        // will be sub-imaged to the proper size.
    373        // strip0 and strip1 give the indices of the strips containing
    374        // the first and last rows of pixels in the subimage
    375        final int strip0 = yRaster / rowsPerStrip;
    376        final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip;
    377
    378        for (int strip = strip0; strip <= strip1; strip++) {
    379            final int yStrip = strip * rowsPerStrip;
    380            final int rowsRemaining = height - yStrip;
    381            final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip);
    382            final int bytesPerRow = (bitsPerPixel * width + 7) / 8;
    383            final int bytesPerStrip = rowsInThisStrip * bytesPerRow;
    384
    385            final byte[] compressed = imageData.getImageData(strip).getData();
    386            final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip);
    387
    388            final int[] blockData = unpackFloatingPointSamples(width, rowsInThisStrip, width, decompressed, bitsPerPixel, byteOrder);
    389            transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat);
    390        }
    391        return new TiffRasterDataFloat(rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat);
    392    }
    393
    394    private AbstractTiffRasterData readRasterDataInt(final Rectangle subImage) throws ImagingException, IOException {
    395        final int xRaster;
    396        final int yRaster;
    397        final int rasterWidth;
    398        final int rasterHeight;
    399        if (subImage != null) {
    400            xRaster = subImage.x;
    401            yRaster = subImage.y;
    402            rasterWidth = subImage.width;
    403            rasterHeight = subImage.height;
    404        } else {
    405            xRaster = 0;
    406            yRaster = 0;
    407            rasterWidth = width;
    408            rasterHeight = height;
    409        }
    410
    411        final int[] rasterDataInt = Allocator.intArray(rasterWidth * rasterHeight);
    412
    413        // the legacy code is optimized to the reading of whole
    414        // strips (except for the last strip in the image, which can
    415        // be a partial). So create a working image with compatible
    416        // dimensions and read that. Later on, the working image
    417        // will be sub-imaged to the proper size.
    418        // strip0 and strip1 give the indices of the strips containing
    419        // the first and last rows of pixels in the subimage
    420        final int strip0 = yRaster / rowsPerStrip;
    421        final int strip1 = (yRaster + rasterHeight - 1) / rowsPerStrip;
    422
    423        for (int strip = strip0; strip <= strip1; strip++) {
    424            final int yStrip = strip * rowsPerStrip;
    425            final int rowsRemaining = height - yStrip;
    426            final int rowsInThisStrip = Math.min(rowsRemaining, rowsPerStrip);
    427            final int bytesPerRow = (bitsPerPixel * width + 7) / 8;
    428            final int bytesPerStrip = rowsInThisStrip * bytesPerRow;
    429
    430            final byte[] compressed = imageData.getImageData(strip).getData();
    431            final byte[] decompressed = decompress(compressed, compression, bytesPerStrip, width, rowsInThisStrip);
    432            final int[] blockData = unpackIntSamples(width, rowsInThisStrip, width, decompressed, predictor, bitsPerPixel, byteOrder);
    433            transferBlockToRaster(0, yStrip, width, rowsInThisStrip, blockData, xRaster, yRaster, rasterWidth, rasterHeight, rasterDataInt);
    434        }
    435        return new TiffRasterDataInt(rasterWidth, rasterHeight, rasterDataInt);
    436    }
    437}