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.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.AbstractTiffRasterData;
37 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
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.AbstractPhotometricInterpreter;
43 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
44
45
46
47
48 public final class DataReaderTiled extends AbstractImageDataReader {
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 AbstractPhotometricInterpreter photometricInterpreter, final int tileWidth,
61 final int tileLength, final int bitsPerPixel, final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int sampleFormat,
62 final int width, 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 this.tileWidth = tileWidth;
66 this.tileLength = tileLength;
67 this.bitsPerPixel = bitsPerPixel;
68 this.compression = compression;
69 this.imageData = imageData;
70 this.byteOrder = byteOrder;
71 }
72
73 private void interpretTile(final ImageBuilder imageBuilder, final byte[] bytes, final int startX, final int startY, final int xLimit, final int yLimit)
74 throws ImagingException, IOException {
75
76
77
78
79
80
81 if (sampleFormat == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
82
83
84 final int i0 = startY;
85 int i1 = startY + tileLength;
86 if (i1 > yLimit) {
87
88 i1 = yLimit;
89 }
90 final int j0 = startX;
91 int j1 = startX + tileWidth;
92 if (j1 > xLimit) {
93
94 j1 = xLimit;
95 }
96 final int[] samples = new int[4];
97 final int[] b = unpackFloatingPointSamples(j1 - j0, i1 - i0, tileWidth, bytes, bitsPerPixel, byteOrder);
98 for (int i = i0; i < i1; i++) {
99 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
112
113
114
115
116
117
118
119
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
126 i1 = yLimit;
127 }
128 int j1 = startX + tileWidth;
129 if (j1 > xLimit) {
130
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
140
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
150
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
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
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
218
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
235
236
237
238
239
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
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
304
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
350
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 }