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