    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
    018 /*
    019 * Implementation notes:
    020 *    See ImageDataReader and DataReaderStrips for notes on development
    021 * with particular emphasis on run-time performance.
    022 */
    023package org.apache.commons.imaging.formats.tiff.datareaders;
    024
    025import java.awt.Rectangle;
    026import java.io.ByteArrayInputStream;
    027import java.io.IOException;
    028import java.nio.ByteOrder;
    029
    030import org.apache.commons.imaging.ImageReadException;
    031import org.apache.commons.imaging.common.ImageBuilder;
    032import org.apache.commons.imaging.formats.tiff.TiffRasterData;
    033import org.apache.commons.imaging.formats.tiff.TiffDirectory;
    034import org.apache.commons.imaging.formats.tiff.TiffImageData;
    035import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat;
    036import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt;
    037import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
    038import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
    039import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
    040import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
    041
    042/**
    043 * Provides a data reader for TIFF file images organized by tiles.
    044 */
    045public final class DataReaderTiled extends ImageDataReader {
    046
    047    private final int tileWidth;
    048    private final int tileLength;
    049
    050    private final int bitsPerPixel;
    051
    052    private final int compression;
    053    private final ByteOrder byteOrder;
    054
    055    private final TiffImageData.Tiles imageData;
    056
    057    public DataReaderTiled(final TiffDirectory directory,
    058        final PhotometricInterpreter photometricInterpreter, final int tileWidth,
    059        final int tileLength, final int bitsPerPixel, final int[] bitsPerSample,
    060        final int predictor, final int samplesPerPixel, final int sampleFormat,
    061        final int width, final int height,
    062        final int compression,
    063        final TiffPlanarConfiguration planarConfiguration,
    064        final ByteOrder byteOrder, final TiffImageData.Tiles imageData) {
    065        super(directory, photometricInterpreter, bitsPerSample, predictor,
    066            samplesPerPixel, sampleFormat, width, height, planarConfiguration);
    067
    068        this.tileWidth = tileWidth;
    069        this.tileLength = tileLength;
    070
    071        this.bitsPerPixel = bitsPerPixel;
    072        this.compression = compression;
    073
    074        this.imageData = imageData;
    075        this.byteOrder = byteOrder;
    076    }
    077
    078    private void interpretTile(final ImageBuilder imageBuilder, final byte[] bytes,
    079        final int startX, final int startY, final int xLimit, final int yLimit) throws ImageReadException, IOException {
    080
    081        // March 2020 change to handle floating-point with compression
    082        // for the compressed floating-point, there is a standard that allows
    083        // 16 bit floats (which is an IEEE 754 standard) and 24 bits (which is
    084        // a non-standard format implemented for TIFF).  At this time, this
    085        // code only supports the 32-bit and 64-bit formats.
    086        if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
    087            // tileLength: number of rows in tile
    088            // tileWidth:  number of columns in tile
    089            final int i0 = startY;
    090            int i1 = startY + tileLength;
    091            if (i1 > yLimit) {
    092                // the tile is padded past bottom of image
    093                i1 = yLimit;
    094            }
    095            final int j0 = startX;
    096            int j1 = startX + tileWidth;
    097            if (j1 > xLimit) {
    098                // the tile is padded to beyond the tile width
    099                j1 = xLimit;
    100            }
    101            final int[] samples = new int[4];
    102            final int[] b = unpackFloatingPointSamples(
    103                j1 - j0, i1 - i0, tileWidth, bytes,
    104                bitsPerPixel, byteOrder);
    105            for (int i = i0; i < i1; i++) {
    106                final int row = i - startY;
    107                final int rowOffset = row * tileWidth;
    108                for (int j = j0; j < j1; j++) {
    109                    final int column = j - startX;
    110                    int k = (rowOffset + column) * samplesPerPixel;
    111                    samples[0] = b[k];
    112                    photometricInterpreter.interpretPixel(
    113                        imageBuilder, samples, j, i);
    114                }
    115            }
    116            return;
    117        }
    118
    119        // End of March 2020 changes to support floating-point format
    120        // changes introduced May 2012
    121        // The following block of code implements changes that
    122        // reduce image loading time by using special-case processing
    123        // instead of the general-purpose logic from the original
    124        // implementation. For a detailed discussion, see the comments for
    125        // a similar treatment in the DataReaderStrip class
    126        //
    127        // verify that all samples are one byte in size
    128        final boolean allSamplesAreOneByte = isHomogenous(8);
    129
    130        if ((bitsPerPixel == 24 || bitsPerPixel == 32) && allSamplesAreOneByte
    131            && photometricInterpreter instanceof PhotometricInterpreterRgb) {
    132            int i1 = startY + tileLength;
    133            if (i1 > yLimit) {
    134                // the tile is padded past bottom of image
    135                i1 = yLimit;
    136            }
    137            int j1 = startX + tileWidth;
    138            if (j1 > xLimit) {
    139                // the tile is padded to beyond the tile width
    140                j1 = xLimit;
    141            }
    142
    143            if (predictor == TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) {
    144                applyPredictorToBlock(tileWidth, i1 - startY, samplesPerPixel, bytes);
    145            }
    146
    147            if (bitsPerPixel == 24) {
    148                // 24 bit case, we don't mask the red byte because any
    149                // sign-extended bits get covered by opacity mask
    150                for (int i = startY; i < i1; i++) {
    151                    int k = (i - startY) * tileWidth * 3;
    152                    for (int j = startX; j < j1; j++, k += 3) {
    153                        final int rgb = 0xff000000
    154                            | (bytes[k] << 16)
    155                            | ((bytes[k + 1] & 0xff) << 8)
    156                            | (bytes[k + 2] & 0xff);
    157                        imageBuilder.setRGB(j, i, rgb);
    158                    }
    159                }
    160            } else if (bitsPerPixel == 32) {
    161                // 32 bit case, we don't mask the high byte because any
    162                // sign-extended bits get shifted up and out of result.
    163                for (int i = startY; i < i1; i++) {
    164                    int k = (i - startY) * tileWidth * 4;
    165                    for (int j = startX; j < j1; j++, k += 4) {
    166                        final int rgb
    167                            = ((bytes[k] & 0xff) << 16)
    168                            | ((bytes[k + 1] & 0xff) << 8)
    169                            | (bytes[k + 2] & 0xff)
    170                            | (bytes[k + 3] << 24);
    171                        imageBuilder.setRGB(j, i, rgb);
    172                    }
    173                }
    174            }
    175
    176            return;
    177        }
    178
    179        // End of May 2012 changes
    180        try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) {
    181
    182            final int pixelsPerTile = tileWidth * tileLength;
    183
    184            int tileX = 0;
    185            int tileY = 0;
    186
    187            int[] samples = new int[bitsPerSampleLength];
    188            resetPredictor();
    189            for (int i = 0; i < pixelsPerTile; i++) {
    190
    191                final int x = tileX + startX;
    192                final int y = tileY + startY;
    193
    194                getSamplesAsBytes(bis, samples);
    195
    196                if (x < xLimit && y < yLimit) {
    197                    samples = applyPredictor(samples);
    198                    photometricInterpreter.interpretPixel(imageBuilder, samples, x, y);
    199                }
    200
    201                tileX++;
    202
    203                if (tileX >= tileWidth) {
    204                    tileX = 0;
    205                    resetPredictor();
    206                    tileY++;
    207                    bis.flushCache();
    208                    if (tileY >= tileLength) {
    209                        break;
    210                    }
    211                }
    212
    213            }
    214        }
    215    }
    216
    217    @Override
    218    public ImageBuilder readImageData(final Rectangle subImageSpecification,
    219        final boolean hasAlpha,
    220        final boolean isAlphaPreMultiplied)
    221            throws ImageReadException, IOException {
    222
    223        final Rectangle subImage;
    224        if (subImageSpecification == null) {
    225            // configure subImage to read entire image
    226            subImage = new Rectangle(0, 0, width, height);
    227        } else {
    228            subImage = subImageSpecification;
    229        }
    230
    231        final int bitsPerRow = tileWidth * bitsPerPixel;
    232        final int bytesPerRow = (bitsPerRow + 7) / 8;
    233        final int bytesPerTile = bytesPerRow * tileLength;
    234
    235        // tileWidth is the width of the tile
    236        // tileLength is the height of the tile
    237        final int col0 = subImage.x / tileWidth;
    238        final int col1 = (subImage.x + subImage.width - 1) / tileWidth;
    239        final int row0 = subImage.y / tileLength;
    240        final int row1 = (subImage.y + subImage.height - 1) / tileLength;
    241
    242        final int nCol = col1 - col0 + 1;
    243        final int nRow = row1 - row0 + 1;
    244        final int workingWidth = nCol * tileWidth;
    245        final int workingHeight = nRow * tileLength;
    246
    247        final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth;
    248
    249        final int x0 = col0 * tileWidth;
    250        final int y0 = row0 * tileLength;
    251
    252        final ImageBuilder workingBuilder
    253            = new ImageBuilder(workingWidth, workingHeight,
    254                hasAlpha, isAlphaPreMultiplied);
    255
    256        for (int iRow = row0; iRow <= row1; iRow++) {
    257            for (int iCol = col0; iCol <= col1; iCol++) {
    258                final int tile = iRow * nColumnsOfTiles + iCol;
    259                final byte[] compressed = imageData.tiles[tile].getData();
    260                final byte[] decompressed = decompress(compressed, compression,
    261                        bytesPerTile, tileWidth, tileLength);
    262                final int x = iCol * tileWidth - x0;
    263                final int y = iRow * tileLength - y0;
    264                interpretTile(workingBuilder, decompressed, x, y, width, height);
    265            }
    266        }
    267
    268        if (subImage.x == x0
    269                && subImage.y == y0
    270                && subImage.width == workingWidth
    271                && subImage.height == workingHeight) {
    272            return workingBuilder;
    273        }
    274
    275        return workingBuilder.getSubset(
    276            subImage.x - x0,
    277            subImage.y - y0,
    278            subImage.width,
    279            subImage.height);
    280    }
    281
    282    @Override
    283    public TiffRasterData readRasterData(final Rectangle subImage)
    284            throws ImageReadException, IOException {
    285        switch (sampleFormat) {
    286            case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT:
    287                return readRasterDataFloat(subImage);
    288            case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER:
    289                return readRasterDataInt(subImage);
    290            default:
    291                throw new ImageReadException("Unsupported sample format, value="
    292                        + sampleFormat);
    293        }
    294    }
    295
    296    private TiffRasterData readRasterDataFloat(final Rectangle subImage)
    297            throws ImageReadException, IOException {
    298        final int bitsPerRow = tileWidth * bitsPerPixel;
    299        final int bytesPerRow = (bitsPerRow + 7) / 8;
    300        final int bytesPerTile = bytesPerRow * tileLength;
    301        int xRaster;
    302        int yRaster;
    303        int rasterWidth;
    304        int rasterHeight;
    305        if (subImage != null) {
    306            xRaster = subImage.x;
    307            yRaster = subImage.y;
    308            rasterWidth = subImage.width;
    309            rasterHeight = subImage.height;
    310        } else {
    311            xRaster = 0;
    312            yRaster = 0;
    313            rasterWidth = width;
    314            rasterHeight = height;
    315        }
    316        float[] rasterDataFloat = new float[rasterWidth * rasterHeight * samplesPerPixel];
    317
    318        // tileWidth is the width of the tile
    319        // tileLength is the height of the tile
    320        final int col0 = xRaster / tileWidth;
    321        final int col1 = (xRaster + rasterWidth - 1) / tileWidth;
    322        final int row0 = yRaster / tileLength;
    323        final int row1 = (yRaster + rasterHeight - 1) / tileLength;
    324
    325        final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth;
    326
    327        for (int iRow = row0; iRow <= row1; iRow++) {
    328            for (int iCol = col0; iCol <= col1; iCol++) {
    329                final int tile = iRow * nColumnsOfTiles + iCol;
    330                final byte[] compressed = imageData.tiles[tile].getData();
    331                final byte[] decompressed = decompress(compressed, compression,
    332                        bytesPerTile, tileWidth, tileLength);
    333                final int x = iCol * tileWidth;
    334                final int y = iRow * tileLength;
    335
    336                final int[] blockData = unpackFloatingPointSamples(
    337                        tileWidth, tileLength, tileWidth,
    338                        decompressed,
    339                        bitsPerPixel, byteOrder);
    340                transferBlockToRaster(x, y, tileWidth, tileLength, blockData,
    341                        xRaster, yRaster, rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat);
    342            }
    343        }
    344
    345        return new TiffRasterDataFloat(rasterWidth, rasterHeight, samplesPerPixel,rasterDataFloat);
    346    }
    347
    348    private TiffRasterData readRasterDataInt(final Rectangle subImage)
    349            throws ImageReadException, IOException {
    350        final int bitsPerRow = tileWidth * bitsPerPixel;
    351        final int bytesPerRow = (bitsPerRow + 7) / 8;
    352        final int bytesPerTile = bytesPerRow * tileLength;
    353        int xRaster;
    354        int yRaster;
    355        int rasterWidth;
    356        int rasterHeight;
    357        if (subImage != null) {
    358            xRaster = subImage.x;
    359            yRaster = subImage.y;
    360            rasterWidth = subImage.width;
    361            rasterHeight = subImage.height;
    362        } else {
    363            xRaster = 0;
    364            yRaster = 0;
    365            rasterWidth = width;
    366            rasterHeight = height;
    367        }
    368        int[] rasterDataInt = new int[rasterWidth * rasterHeight];
    369
    370        // tileWidth is the width of the tile
    371        // tileLength is the height of the tile
    372        final int col0 = xRaster / tileWidth;
    373        final int col1 = (xRaster + rasterWidth - 1) / tileWidth;
    374        final int row0 = yRaster / tileLength;
    375        final int row1 = (yRaster + rasterHeight - 1) / tileLength;
    376
    377        final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth;
    378
    379        for (int iRow = row0; iRow <= row1; iRow++) {
    380            for (int iCol = col0; iCol <= col1; iCol++) {
    381                final int tile = iRow * nColumnsOfTiles + iCol;
    382                final byte[] compressed = imageData.tiles[tile].getData();
    383                final byte[] decompressed = decompress(compressed, compression,
    384                        bytesPerTile, tileWidth, tileLength);
    385                final int x = iCol * tileWidth;
    386                final int y = iRow * tileLength;
    387                final int[] blockData = unpackIntSamples(
    388                        tileWidth, tileLength, tileWidth,
    389                        decompressed,
    390                        predictor, bitsPerPixel, byteOrder);
    391                transferBlockToRaster(x, y, tileWidth, tileLength, blockData,
    392                        xRaster, yRaster, rasterWidth, rasterHeight, rasterDataInt);
    393            }
    394        }
    395        return new TiffRasterDataInt(rasterWidth, rasterHeight, rasterDataInt);
    396    }
    397}