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}