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 static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG; 026 027import java.awt.Rectangle; 028import java.io.ByteArrayInputStream; 029import java.io.IOException; 030import java.nio.ByteOrder; 031 032import org.apache.commons.imaging.ImagingException; 033import org.apache.commons.imaging.common.Allocator; 034import org.apache.commons.imaging.common.ImageBuilder; 035import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData; 036import org.apache.commons.imaging.formats.tiff.TiffDirectory; 037import org.apache.commons.imaging.formats.tiff.TiffRasterData; 038import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat; 039import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt; 040import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration; 041import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 042import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; 043import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb; 044 045/** 046 * Provides a data reader for TIFF file images organized by tiles. 047 */ 048public final class DataReaderTiled extends ImageDataReader { 049 050 private final int tileWidth; 051 private final int tileLength; 052 053 private final int bitsPerPixel; 054 055 private final int compression; 056 private final ByteOrder byteOrder; 057 058 private final AbstractTiffImageData.Tiles imageData; 059 060 public DataReaderTiled(final TiffDirectory directory, final PhotometricInterpreter photometricInterpreter, final int tileWidth, final int tileLength, 061 final int bitsPerPixel, final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat, final int width, 062 final int height, final int compression, final TiffPlanarConfiguration planarConfiguration, final ByteOrder byteOrder, 063 final AbstractTiffImageData.Tiles imageData) { 064 super(directory, photometricInterpreter, bitsPerSample, predictor, samplesPerPixel, sampleFormat, width, height, planarConfiguration); 065 066 this.tileWidth = tileWidth; 067 this.tileLength = tileLength; 068 069 this.bitsPerPixel = bitsPerPixel; 070 this.compression = compression; 071 072 this.imageData = imageData; 073 this.byteOrder = byteOrder; 074 } 075 076 private void interpretTile(final ImageBuilder imageBuilder, final byte[] bytes, final int startX, final int startY, final int xLimit, final int yLimit) 077 throws ImagingException, IOException { 078 079 // March 2020 change to handle floating-point with compression 080 // for the compressed floating-point, there is a standard that allows 081 // 16 bit floats (which is an IEEE 754 standard) and 24 bits (which is 082 // a non-standard format implemented for TIFF). At this time, this 083 // code only supports the 32-bit and 64-bit formats. 084 if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) { 085 // tileLength: number of rows in tile 086 // tileWidth: number of columns in tile 087 final int i0 = startY; 088 int i1 = startY + tileLength; 089 if (i1 > yLimit) { 090 // the tile is padded past bottom of image 091 i1 = yLimit; 092 } 093 final int j0 = startX; 094 int j1 = startX + tileWidth; 095 if (j1 > xLimit) { 096 // the tile is padded to beyond the tile width 097 j1 = xLimit; 098 } 099 final int[] samples = new int[4]; 100 final int[] b = unpackFloatingPointSamples(j1 - j0, i1 - i0, tileWidth, bytes, bitsPerPixel, byteOrder); 101 for (int i = i0; i < i1; i++) { 102 final int row = i - startY; 103 final int rowOffset = row * tileWidth; 104 for (int j = j0; j < j1; j++) { 105 final int column = j - startX; 106 final int k = (rowOffset + column) * samplesPerPixel; 107 samples[0] = b[k]; 108 photometricInterpreter.interpretPixel(imageBuilder, samples, j, i); 109 } 110 } 111 return; 112 } 113 114 // End of March 2020 changes to support floating-point format 115 // changes introduced May 2012 116 // The following block of code implements changes that 117 // reduce image loading time by using special-case processing 118 // instead of the general-purpose logic from the original 119 // implementation. For a detailed discussion, see the comments for 120 // a similar treatment in the DataReaderStrip class 121 // 122 // verify that all samples are one byte in size 123 final boolean allSamplesAreOneByte = isHomogenous(8); 124 125 if ((bitsPerPixel == 24 || bitsPerPixel == 32) && allSamplesAreOneByte && photometricInterpreter instanceof PhotometricInterpreterRgb) { 126 int i1 = startY + tileLength; 127 if (i1 > yLimit) { 128 // the tile is padded past bottom of image 129 i1 = yLimit; 130 } 131 int j1 = startX + tileWidth; 132 if (j1 > xLimit) { 133 // the tile is padded to beyond the tile width 134 j1 = xLimit; 135 } 136 137 if (predictor == TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) { 138 applyPredictorToBlock(tileWidth, i1 - startY, samplesPerPixel, bytes); 139 } 140 141 if (bitsPerPixel == 24) { 142 // 24 bit case, we don't mask the red byte because any 143 // sign-extended bits get covered by opacity mask 144 for (int i = startY; i < i1; i++) { 145 int k = (i - startY) * tileWidth * 3; 146 for (int j = startX; j < j1; j++, k += 3) { 147 final int rgb = 0xff000000 | bytes[k] << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff; 148 imageBuilder.setRgb(j, i, rgb); 149 } 150 } 151 } else if (bitsPerPixel == 32) { 152 // 32 bit case, we don't mask the high byte because any 153 // sign-extended bits get shifted up and out of result. 154 for (int i = startY; i < i1; i++) { 155 int k = (i - startY) * tileWidth * 4; 156 for (int j = startX; j < j1; j++, k += 4) { 157 final int rgb = (bytes[k] & 0xff) << 16 | (bytes[k + 1] & 0xff) << 8 | bytes[k + 2] & 0xff | bytes[k + 3] << 24; 158 imageBuilder.setRgb(j, i, rgb); 159 } 160 } 161 } 162 163 return; 164 } 165 166 // End of May 2012 changes 167 try (BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bytes), byteOrder)) { 168 169 final int pixelsPerTile = tileWidth * tileLength; 170 171 int tileX = 0; 172 int tileY = 0; 173 174 int[] samples = Allocator.intArray(bitsPerSampleLength); 175 resetPredictor(); 176 for (int i = 0; i < pixelsPerTile; i++) { 177 178 final int x = tileX + startX; 179 final int y = tileY + startY; 180 181 getSamplesAsBytes(bis, samples); 182 183 if (x < xLimit && y < yLimit) { 184 samples = applyPredictor(samples); 185 photometricInterpreter.interpretPixel(imageBuilder, samples, x, y); 186 } 187 188 tileX++; 189 190 if (tileX >= tileWidth) { 191 tileX = 0; 192 resetPredictor(); 193 tileY++; 194 bis.flushCache(); 195 if (tileY >= tileLength) { 196 break; 197 } 198 } 199 200 } 201 } 202 } 203 204 @Override 205 public ImageBuilder readImageData(final Rectangle subImageSpecification, final boolean hasAlpha, final boolean isAlphaPreMultiplied) 206 throws IOException, ImagingException { 207 208 final Rectangle subImage; 209 if (subImageSpecification == null) { 210 // configure subImage to read entire image 211 subImage = new Rectangle(0, 0, width, height); 212 } else { 213 subImage = subImageSpecification; 214 } 215 216 final int bitsPerRow = tileWidth * bitsPerPixel; 217 final int bytesPerRow = (bitsPerRow + 7) / 8; 218 final int bytesPerTile = bytesPerRow * tileLength; 219 220 // tileWidth is the width of the tile 221 // tileLength is the height of the tile 222 final int col0 = subImage.x / tileWidth; 223 final int col1 = (subImage.x + subImage.width - 1) / tileWidth; 224 final int row0 = subImage.y / tileLength; 225 final int row1 = (subImage.y + subImage.height - 1) / tileLength; 226 227 final int nCol = col1 - col0 + 1; 228 final int nRow = row1 - row0 + 1; 229 final int workingWidth = nCol * tileWidth; 230 final int workingHeight = nRow * tileLength; 231 232 final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth; 233 234 final int x0 = col0 * tileWidth; 235 final int y0 = row0 * tileLength; 236 237 // When processing a subimage, the workingBuilder width and height 238 // are set to be integral multiples of the tile width and height. 239 // So the working image may be larger than the specified size of the subimage. 240 // If necessary, the subimage is extracted from the workingBuilder 241 // at the end of this method. This approach avoids the need for the 242 // interpretTile method to implement bounds checking for a subimage. 243 final ImageBuilder workingBuilder = new ImageBuilder(workingWidth, workingHeight, hasAlpha, isAlphaPreMultiplied); 244 245 for (int iRow = row0; iRow <= row1; iRow++) { 246 for (int iCol = col0; iCol <= col1; iCol++) { 247 final int tile = iRow * nColumnsOfTiles + iCol; 248 final byte[] compressed = imageData.tiles[tile].getData(); 249 final int x = iCol * tileWidth - x0; 250 final int y = iRow * tileLength - y0; 251 // Handle JPEG based compression 252 if (compression == TIFF_COMPRESSION_JPEG) { 253 if (planarConfiguration == TiffPlanarConfiguration.PLANAR) { 254 throw new ImagingException("TIFF file in non-supported configuration: JPEG compression used in planar configuration."); 255 } 256 DataInterpreterJpeg.intepretBlock(directory, workingBuilder, x, y, tileWidth, tileLength, compressed); 257 continue; 258 } 259 260 final byte[] decompressed = decompress(compressed, compression, bytesPerTile, tileWidth, tileLength); 261 262 interpretTile(workingBuilder, decompressed, x, y, width, height); 263 } 264 } 265 266 if (subImage.x == x0 && subImage.y == y0 && subImage.width == workingWidth && subImage.height == workingHeight) { 267 return workingBuilder; 268 } 269 270 return workingBuilder.getSubset(subImage.x - x0, subImage.y - y0, subImage.width, subImage.height); 271 } 272 273 @Override 274 public TiffRasterData readRasterData(final Rectangle subImage) throws ImagingException, IOException { 275 switch (sampleFormat) { 276 case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT: 277 return readRasterDataFloat(subImage); 278 case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER: 279 return readRasterDataInt(subImage); 280 default: 281 throw new ImagingException("Unsupported sample format, value=" + sampleFormat); 282 } 283 } 284 285 private TiffRasterData readRasterDataFloat(final Rectangle subImage) throws ImagingException, IOException { 286 final int bitsPerRow = tileWidth * bitsPerPixel; 287 final int bytesPerRow = (bitsPerRow + 7) / 8; 288 final int bytesPerTile = bytesPerRow * tileLength; 289 int xRaster; 290 int yRaster; 291 int rasterWidth; 292 int rasterHeight; 293 if (subImage != null) { 294 xRaster = subImage.x; 295 yRaster = subImage.y; 296 rasterWidth = subImage.width; 297 rasterHeight = subImage.height; 298 } else { 299 xRaster = 0; 300 yRaster = 0; 301 rasterWidth = width; 302 rasterHeight = height; 303 } 304 final float[] rasterDataFloat = Allocator.floatArray(rasterWidth * rasterHeight * samplesPerPixel); 305 306 // tileWidth is the width of the tile 307 // tileLength is the height of the tile 308 final int col0 = xRaster / tileWidth; 309 final int col1 = (xRaster + rasterWidth - 1) / tileWidth; 310 final int row0 = yRaster / tileLength; 311 final int row1 = (yRaster + rasterHeight - 1) / tileLength; 312 313 final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth; 314 315 for (int iRow = row0; iRow <= row1; iRow++) { 316 for (int iCol = col0; iCol <= col1; iCol++) { 317 final int tile = iRow * nColumnsOfTiles + iCol; 318 final byte[] compressed = imageData.tiles[tile].getData(); 319 final byte[] decompressed = decompress(compressed, compression, bytesPerTile, tileWidth, tileLength); 320 final int x = iCol * tileWidth; 321 final int y = iRow * tileLength; 322 323 final int[] blockData = unpackFloatingPointSamples(tileWidth, tileLength, tileWidth, decompressed, bitsPerPixel, byteOrder); 324 transferBlockToRaster(x, y, tileWidth, tileLength, blockData, xRaster, yRaster, rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat); 325 } 326 } 327 328 return new TiffRasterDataFloat(rasterWidth, rasterHeight, samplesPerPixel, rasterDataFloat); 329 } 330 331 private TiffRasterData readRasterDataInt(final Rectangle subImage) throws ImagingException, IOException { 332 final int bitsPerRow = tileWidth * bitsPerPixel; 333 final int bytesPerRow = (bitsPerRow + 7) / 8; 334 final int bytesPerTile = bytesPerRow * tileLength; 335 int xRaster; 336 int yRaster; 337 int rasterWidth; 338 int rasterHeight; 339 if (subImage != null) { 340 xRaster = subImage.x; 341 yRaster = subImage.y; 342 rasterWidth = subImage.width; 343 rasterHeight = subImage.height; 344 } else { 345 xRaster = 0; 346 yRaster = 0; 347 rasterWidth = width; 348 rasterHeight = height; 349 } 350 final int[] rasterDataInt = Allocator.intArray(rasterWidth * rasterHeight); 351 352 // tileWidth is the width of the tile 353 // tileLength is the height of the tile 354 final int col0 = xRaster / tileWidth; 355 final int col1 = (xRaster + rasterWidth - 1) / tileWidth; 356 final int row0 = yRaster / tileLength; 357 final int row1 = (yRaster + rasterHeight - 1) / tileLength; 358 359 final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth; 360 361 for (int iRow = row0; iRow <= row1; iRow++) { 362 for (int iCol = col0; iCol <= col1; iCol++) { 363 final int tile = iRow * nColumnsOfTiles + iCol; 364 final byte[] compressed = imageData.tiles[tile].getData(); 365 final byte[] decompressed = decompress(compressed, compression, bytesPerTile, tileWidth, tileLength); 366 final int x = iCol * tileWidth; 367 final int y = iRow * tileLength; 368 final int[] blockData = unpackIntSamples(tileWidth, tileLength, tileWidth, decompressed, predictor, bitsPerPixel, byteOrder); 369 transferBlockToRaster(x, y, tileWidth, tileLength, blockData, xRaster, yRaster, rasterWidth, rasterHeight, rasterDataInt); 370 } 371 } 372 return new TiffRasterDataInt(rasterWidth, rasterHeight, rasterDataInt); 373 } 374}