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}