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