    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 */
    023
    024package org.apache.commons.imaging.formats.tiff.datareaders;
    025
    026import java.awt.Rectangle;
    027import java.awt.image.BufferedImage;
    028import java.io.ByteArrayInputStream;
    029import java.io.IOException;
    030import java.nio.ByteOrder;
    031
    032import org.apache.commons.imaging.ImageReadException;
    033import org.apache.commons.imaging.common.ImageBuilder;
    034import org.apache.commons.imaging.formats.tiff.TiffRasterData;
    035import org.apache.commons.imaging.formats.tiff.TiffDirectory;
    036import org.apache.commons.imaging.formats.tiff.TiffElement.DataElement;
    037import org.apache.commons.imaging.formats.tiff.TiffImageData;
    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,            final int compression, final ByteOrder byteOrder, final TiffImageData.Tiles imageData) {
    062        super(directory, photometricInterpreter, bitsPerSample, predictor,
    063            samplesPerPixel, sampleFormat, width, height);
    064
    065        this.tileWidth = tileWidth;
    066        this.tileLength = tileLength;
    067
    068        this.bitsPerPixel = bitsPerPixel;
    069        this.compression = compression;
    070
    071        this.imageData = imageData;
    072        this.byteOrder = byteOrder;
    073    }
    074
    075    private void interpretTile(final ImageBuilder imageBuilder, final byte[] bytes,
    076        final int startX, final int startY, final int xLimit, final int yLimit) throws ImageReadException, IOException {
    077
    078        // March 2020 change to handle floating-point with compression
    079        // for the compressed floating-point, there is a standard that allows
    080        // 16 bit floats (which is an IEEE 754 standard) and 24 bits (which is
    081        // a non-standard format implemented for TIFF).  At this time, this
    082        // code only supports the 32-bit format.
    083        if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
    084            // tileLength: number of rows in tile
    085            // tileWidth:  number of columns in tile
    086            final int i0 = startY;
    087            int i1 = startY + tileLength;
    088            if (i1 > yLimit) {
    089                // the tile is padded past bottom of image
    090                i1 = yLimit;
    091            }
    092            final int j0 = startX;
    093            int j1 = startX + tileWidth;
    094            if (j1 > xLimit) {
    095                // the tile is padded to beyond the tile width
    096                j1 = xLimit;
    097            }
    098            final int[] samples = new int[4];
    099            int[] b = unpackFloatingPointSamples(
    100                j1 - j0, i1 - i0, tileWidth, bytes,
    101                predictor, bitsPerPixel, byteOrder);
    102            for (int i = i0; i < i1; i++) {
    103                int row = i - startY;
    104                int rowOffset = row * tileWidth;
    105                for (int j = j0; j < j1; j++) {
    106                    int column = j - startX;
    107                    samples[0] = b[rowOffset + column];
    108                    photometricInterpreter.interpretPixel(
    109                        imageBuilder, samples, j, i);
    110                }
    111            }
    112            return;
    113        }
    114
    115        // End of March 2020 changes to support floating-point format
    116
    117        // changes introduced May 2012
    118        // The following block of code implements changes that
    119        // reduce image loading time by using special-case processing
    120        // instead of the general-purpose logic from the original
    121        // implementation. For a detailed discussion, see the comments for
    122        // a similar treatment in the DataReaderStrip class
    123        //
    124
    125        // verify that all samples are one byte in size
    126        final boolean allSamplesAreOneByte = isHomogenous(8);
    127
    128          if (bitsPerPixel == 24 && allSamplesAreOneByte
    129            && photometricInterpreter instanceof PhotometricInterpreterRgb) {
    130            final int i0 = startY;
    131            int i1 = startY + tileLength;
    132            if (i1 > yLimit) {
    133                // the tile is padded past bottom of image
    134                i1 = yLimit;
    135            }
    136            final int j0 = startX;
    137            int j1 = startX + tileWidth;
    138            if (j1 > xLimit) {
    139                // the tile is padded to beyond the tile width
    140                j1 = xLimit;
    141            }
    142            if (predictor == 2) {
    143                // pre-apply the predictor logic before feeding
    144                // the bytes to the photometric interpretor.
    145                for (int i = i0; i < i1; i++) {
    146                    int k = (i - i0) * tileWidth * 3;
    147                    int p0 = bytes[k++] & 0xff;
    148                    int p1 = bytes[k++] & 0xff;
    149                    int p2 = bytes[k++] & 0xff;
    150                    for (int j = 1; j < tileWidth; j++) {
    151                        p0 = (bytes[k] + p0) & 0xff;
    152                        bytes[k++] = (byte) p0;
    153                        p1 = (bytes[k] + p1) & 0xff;
    154                        bytes[k++] = (byte) p1;
    155                        p2 = (bytes[k] + p2) & 0xff;
    156                        bytes[k++] = (byte) p2;
    157                    }
    158                }
    159            }
    160
    161            for (int i = i0; i < i1; i++) {
    162                int k = (i - i0) * tileWidth * 3;
    163                for (int j = j0; j < j1; j++, k += 3) {
    164                    final int rgb = 0xff000000
    165                        | (((bytes[k] << 8) | (bytes[k + 1] & 0xff)) << 8)
    166                        | (bytes[k + 2] & 0xff);
    167                    imageBuilder.setRGB(j, i, rgb);
    168                }
    169            }
    170
    171            return;
    172        }
    173
    174        // End of May 2012 changes
    175
    176        try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) {
    177
    178            final int pixelsPerTile = tileWidth * tileLength;
    179
    180            int tileX = 0;
    181            int tileY = 0;
    182
    183            int[] samples = new int[bitsPerSampleLength];
    184            resetPredictor();
    185            for (int i = 0; i < pixelsPerTile; i++) {
    186
    187                final int x = tileX + startX;
    188                final int y = tileY + startY;
    189
    190                getSamplesAsBytes(bis, samples);
    191
    192                if (x < xLimit && y < yLimit) {
    193                    samples = applyPredictor(samples);
    194                    photometricInterpreter.interpretPixel(imageBuilder, samples, x, y);
    195                }
    196
    197                tileX++;
    198
    199                if (tileX >= tileWidth) {
    200                    tileX = 0;
    201                    resetPredictor();
    202                    tileY++;
    203                    bis.flushCache();
    204                    if (tileY >= tileLength) {
    205                        break;
    206                    }
    207                }
    208
    209            }
    210        }
    211    }
    212
    213    @Override
    214    public void readImageData(final ImageBuilder imageBuilder)
    215            throws ImageReadException, IOException {
    216        final int bitsPerRow = tileWidth * bitsPerPixel;
    217        final int bytesPerRow = (bitsPerRow + 7) / 8;
    218        final int bytesPerTile = bytesPerRow * tileLength;
    219        int x = 0;
    220        int y = 0;
    221
    222        for (final DataElement tile2 : imageData.tiles) {
    223            final byte[] compressed = tile2.getData();
    224
    225            final byte[] decompressed = decompress(compressed, compression,
    226                    bytesPerTile, tileWidth, tileLength);
    227
    228            interpretTile(imageBuilder, decompressed, x, y, width, height);
    229
    230            x += tileWidth;
    231            if (x >= width) {
    232                x = 0;
    233                y += tileLength;
    234                if (y >= height) {
    235                    break;
    236                }
    237            }
    238
    239        }
    240    }
    241
    242    @Override
    243    public BufferedImage readImageData(final Rectangle subImage)
    244            throws ImageReadException, IOException {
    245        final int bitsPerRow = tileWidth * bitsPerPixel;
    246        final int bytesPerRow = (bitsPerRow + 7) / 8;
    247        final int bytesPerTile = bytesPerRow * tileLength;
    248
    249
    250        // tileWidth is the width of the tile
    251        // tileLength is the height of the tile
    252        final int col0 = subImage.x / tileWidth;
    253        final int col1 = (subImage.x + subImage.width - 1) / tileWidth;
    254        final int row0 = subImage.y / tileLength;
    255        final int row1 = (subImage.y + subImage.height - 1) / tileLength;
    256
    257        final int nCol = col1 - col0 + 1;
    258        final int nRow = row1 - row0 + 1;
    259        final int workingWidth = nCol * tileWidth;
    260        final int workingHeight = nRow * tileLength;
    261
    262        final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth;
    263
    264        final int x0 = col0 * tileWidth;
    265        final int y0 = row0 * tileLength;
    266
    267        final ImageBuilder workingBuilder =
    268                new ImageBuilder(workingWidth, workingHeight, false);
    269
    270        for (int iRow = row0; iRow <= row1; iRow++) {
    271            for (int iCol = col0; iCol <= col1; iCol++) {
    272                final int tile = iRow * nColumnsOfTiles + iCol;
    273                final byte[] compressed = imageData.tiles[tile].getData();
    274                final byte[] decompressed = decompress(compressed, compression,
    275                        bytesPerTile, tileWidth, tileLength);
    276                int x = iCol * tileWidth - x0;
    277                int y = iRow * tileLength - y0;
    278                interpretTile(workingBuilder, decompressed, x, y, workingWidth, workingHeight);
    279            }
    280        }
    281
    282        if (subImage.x == x0
    283                && subImage.y == y0
    284                && subImage.width == workingWidth
    285                && subImage.height == workingHeight) {
    286            return workingBuilder.getBufferedImage();
    287        }
    288        return workingBuilder.getSubimage(
    289            subImage.x - x0,
    290            subImage.y - y0,
    291            subImage.width,
    292            subImage.height);
    293    }
    294
    295    @Override
    296    public TiffRasterData readRasterData(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[] rasterData = new float[rasterWidth * rasterHeight];
    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        final int x0 = col0 * tileWidth;
    328        final int y0 = row0 * tileLength;
    329
    330        for (int iRow = row0; iRow <= row1; iRow++) {
    331            for (int iCol = col0; iCol <= col1; iCol++) {
    332                final int tile = iRow * nColumnsOfTiles + iCol;
    333                final byte[] compressed = imageData.tiles[tile].getData();
    334                final byte[] decompressed = decompress(compressed, compression,
    335                    bytesPerTile, tileWidth, tileLength);
    336                int x = iCol * tileWidth - x0;
    337                int y = iRow * tileLength - y0;
    338                int[] blockData = unpackFloatingPointSamples(
    339                    tileWidth, tileLength, tileWidth,
    340                    decompressed,
    341                    predictor, bitsPerPixel, byteOrder);
    342                transferBlockToRaster(x, y, tileWidth, tileLength, blockData,
    343                    xRaster, yRaster, rasterWidth, rasterHeight, rasterData);
    344            }
    345        }
    346
    347        return new TiffRasterData(rasterWidth, rasterHeight, rasterData);
    348    }
    349
    350}