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