1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.apache.commons.imaging.formats.tiff.datareaders;
24
25 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG;
26
27 import java.awt.Rectangle;
28 import java.io.ByteArrayInputStream;
29 import java.io.IOException;
30 import java.nio.ByteOrder;
31
32 import org.apache.commons.imaging.ImagingException;
33 import org.apache.commons.imaging.common.Allocator;
34 import org.apache.commons.imaging.common.ImageBuilder;
35 import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData;
36 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
37 import org.apache.commons.imaging.formats.tiff.TiffRasterData;
38 import org.apache.commons.imaging.formats.tiff.TiffRasterDataFloat;
39 import org.apache.commons.imaging.formats.tiff.TiffRasterDataInt;
40 import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
41 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
42 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
43 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
44
45
46
47
48 public final class DataReaderTiled extends ImageDataReader {
49
50 private final int tileWidth;
51 private final int tileLength;
52
53 private final int bitsPerPixel;
54
55 private final int compression;
56 private final ByteOrder byteOrder;
57
58 private final AbstractTiffImageData.Tiles imageData;
59
60 public DataReaderTiled(final TiffDirectory directory, final PhotometricInterpreter photometricInterpreter, final int tileWidth, final int tileLength,
61 final int bitsPerPixel, final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat, final int width,
62 final int height, final int compression, final TiffPlanarConfiguration planarConfiguration, final ByteOrder byteOrder,
63 final AbstractTiffImageData.Tiles imageData) {
64 super(directory, photometricInterpreter, bitsPerSample, predictor, samplesPerPixel, sampleFormat, width, height, planarConfiguration);
65
66 this.tileWidth = tileWidth;
67 this.tileLength = tileLength;
68
69 this.bitsPerPixel = bitsPerPixel;
70 this.compression = compression;
71
72 this.imageData = imageData;
73 this.byteOrder = byteOrder;
74 }
75
76 private void interpretTile(final ImageBuilder imageBuilder, final byte[] bytes, final int startX, final int startY, final int xLimit, final int yLimit)
77 throws ImagingException, IOException {
78
79
80
81
82
83
84 if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
85
86
87 final int i0 = startY;
88 int i1 = startY + tileLength;
89 if (i1 > yLimit) {
90
91 i1 = yLimit;
92 }
93 final int j0 = startX;
94 int j1 = startX + tileWidth;
95 if (j1 > xLimit) {
96
97 j1 = xLimit;
98 }
99 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
115
116
117
118
119
120
121
122
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
129 i1 = yLimit;
130 }
131 int j1 = startX + tileWidth;
132 if (j1 > xLimit) {
133
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
143
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
153
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
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
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
221
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
238
239
240
241
242
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
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
307
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
353
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 }