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 */
017package org.apache.commons.imaging.formats.tiff.datareaders;
018
019import java.awt.Rectangle;
020import java.awt.image.BufferedImage;
021import java.io.ByteArrayInputStream;
022import java.io.IOException;
023import java.nio.ByteOrder;
024
025import org.apache.commons.imaging.ImageReadException;
026import org.apache.commons.imaging.common.ImageBuilder;
027import org.apache.commons.imaging.formats.tiff.TiffDirectory;
028import org.apache.commons.imaging.formats.tiff.TiffElement.DataElement;
029import org.apache.commons.imaging.formats.tiff.TiffImageData;
030import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
031import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
032
033public final class DataReaderTiled extends ImageDataReader {
034
035    private final int tileWidth;
036    private final int tileLength;
037
038    private final int bitsPerPixel;
039
040    private final int compression;
041    private final ByteOrder byteOrder;
042
043    private final TiffImageData.Tiles imageData;
044
045    public DataReaderTiled(final TiffDirectory directory,
046            final PhotometricInterpreter photometricInterpreter, final int tileWidth,
047            final int tileLength, final int bitsPerPixel, final int[] bitsPerSample,
048            final int predictor, final int samplesPerPixel, final int width, final int height,
049            final int compression, final ByteOrder byteOrder, final TiffImageData.Tiles imageData) {
050        super(directory, photometricInterpreter, bitsPerSample, predictor,
051                samplesPerPixel, width, height);
052
053        this.tileWidth = tileWidth;
054        this.tileLength = tileLength;
055
056        this.bitsPerPixel = bitsPerPixel;
057        this.compression = compression;
058
059        this.imageData = imageData;
060        this.byteOrder = byteOrder;
061    }
062
063    private void interpretTile(final ImageBuilder imageBuilder, final byte[] bytes,
064            final int startX, final int startY, final int xLimit, final int yLimit) throws ImageReadException, IOException {
065        // changes introduced May 2012
066        // The following block of code implements changes that
067        // reduce image loading time by using special-case processing
068        // instead of the general-purpose logic from the original
069        // implementation. For a detailed discussion, see the comments for
070        // a similar treatment in the DataReaderStrip class
071        //
072
073        // verify that all samples are one byte in size
074        final boolean allSamplesAreOneByte = isHomogenous(8);
075
076        if (predictor != 2 && bitsPerPixel == 24 && allSamplesAreOneByte) {
077            int k = 0;
078            final int i0 = startY;
079            int i1 = startY + tileLength;
080            if (i1 > yLimit) {
081                // the tile is padded past bottom of image
082                i1 = yLimit;
083            }
084            final int j0 = startX;
085            int j1 = startX + tileWidth;
086            if (j1 > xLimit) {
087                // the tile is padded to beyond the tile width
088                j1 = xLimit;
089            }
090            if (photometricInterpreter instanceof PhotometricInterpreterRgb) {
091                for (int i = i0; i < i1; i++) {
092                    k = (i - i0) * tileWidth * 3;
093                    for (int j = j0; j < j1; j++, k += 3) {
094                        final int rgb = 0xff000000
095                                | (((bytes[k] << 8) | (bytes[k + 1] & 0xff)) << 8)
096                                | (bytes[k + 2] & 0xff);
097                        imageBuilder.setRGB(j, i, rgb);
098                    }
099                }
100            } else {
101                final int[] samples = new int[3];
102                for (int i = i0; i < i1; i++) {
103                    k = (i - i0) * tileWidth * 3;
104                    for (int j = j0; j < j1; j++) {
105                        samples[0] = bytes[k++] & 0xff;
106                        samples[1] = bytes[k++] & 0xff;
107                        samples[2] = bytes[k++] & 0xff;
108                        photometricInterpreter.interpretPixel(imageBuilder,
109                                samples, j, i);
110                    }
111                }
112            }
113            return;
114        }
115
116        // End of May 2012 changes
117
118        try (final BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) {
119
120            final int pixelsPerTile = tileWidth * tileLength;
121
122            int tileX = 0;
123            int tileY = 0;
124
125            int[] samples = new int[bitsPerSampleLength];
126            resetPredictor();
127            for (int i = 0; i < pixelsPerTile; i++) {
128
129                final int x = tileX + startX;
130                final int y = tileY + startY;
131
132                getSamplesAsBytes(bis, samples);
133
134                if ((x < xLimit) && (y < yLimit)) {
135                    samples = applyPredictor(samples);
136                    photometricInterpreter.interpretPixel(imageBuilder, samples, x, y);
137                }
138
139                tileX++;
140
141                if (tileX >= tileWidth) {
142                    tileX = 0;
143                    resetPredictor();
144                    tileY++;
145                    bis.flushCache();
146                    if (tileY >= tileLength) {
147                        break;
148                    }
149                }
150
151            }
152        }
153    }
154
155    @Override
156    public void readImageData(final ImageBuilder imageBuilder)
157            throws ImageReadException, IOException {
158        final int bitsPerRow = tileWidth * bitsPerPixel;
159        final int bytesPerRow = (bitsPerRow + 7) / 8;
160        final int bytesPerTile = bytesPerRow * tileLength;
161        int x = 0;
162        int y = 0;
163
164        for (final DataElement tile2 : imageData.tiles) {
165            final byte[] compressed = tile2.getData();
166
167            final byte[] decompressed = decompress(compressed, compression,
168                    bytesPerTile, tileWidth, tileLength);
169
170            interpretTile(imageBuilder, decompressed, x, y, width, height);
171
172            x += tileWidth;
173            if (x >= width) {
174                x = 0;
175                y += tileLength;
176                if (y >= height) {
177                    break;
178                }
179            }
180
181        }
182    }
183
184    @Override
185    public BufferedImage readImageData(final Rectangle subImage)
186            throws ImageReadException, IOException {
187        final int bitsPerRow = tileWidth * bitsPerPixel;
188        final int bytesPerRow = (bitsPerRow + 7) / 8;
189        final int bytesPerTile = bytesPerRow * tileLength;
190        int x = 0;
191        int y = 0;
192
193        // tileWidth is the width of the tile
194        // tileLength is the height of the tile
195        final int col0 = subImage.x / tileWidth;
196        final int col1 = (subImage.x + subImage.width - 1) / tileWidth;
197        final int row0 = subImage.y / tileLength;
198        final int row1 = (subImage.y + subImage.height - 1) / tileLength;
199
200        final int nCol = col1 - col0 + 1;
201        final int nRow = row1 - row0 + 1;
202        final int workingWidth = nCol * tileWidth;
203        final int workingHeight = nRow * tileLength;
204
205        final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth;
206
207        final int x0 = col0 * tileWidth;
208        final int y0 = row0 * tileLength;
209
210        final ImageBuilder workingBuilder =
211                new ImageBuilder(workingWidth, workingHeight, false);
212
213        for (int iRow = row0; iRow <= row1; iRow++) {
214            for (int iCol = col0; iCol <= col1; iCol++) {
215                final int tile = iRow * nColumnsOfTiles + iCol;
216                final byte[] compressed = imageData.tiles[tile].getData();
217                final byte[] decompressed = decompress(compressed, compression,
218                        bytesPerTile, tileWidth, tileLength);
219                x = iCol * tileWidth - x0;
220                y = iRow * tileLength - y0;
221                interpretTile(workingBuilder, decompressed, x, y, workingWidth, workingHeight);
222            }
223        }
224
225        if (subImage.x == x0
226                && subImage.y == y0
227                && subImage.width == workingWidth
228                && subImage.height == workingHeight) {
229            return workingBuilder.getBufferedImage();
230        }
231        return workingBuilder.getSubimage(
232            subImage.x - x0,
233            subImage.y - y0,
234            subImage.width,
235            subImage.height);
236    }
237
238}