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}