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