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}