1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.tiff;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertNotNull;
21
22 import java.awt.image.BufferedImage;
23 import java.io.BufferedOutputStream;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.nio.ByteOrder;
28 import java.nio.file.Path;
29
30 import org.apache.commons.imaging.FormatCompliance;
31 import org.apache.commons.imaging.ImagingException;
32 import org.apache.commons.imaging.bytesource.ByteSource;
33 import org.apache.commons.imaging.common.ImageBuilder;
34 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
35 import org.apache.commons.imaging.formats.tiff.photometricinterpreters.floatingpoint.PhotometricInterpreterFloat;
36 import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
37 import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
38 import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
39 import org.junit.jupiter.api.Test;
40 import org.junit.jupiter.api.io.TempDir;
41
42
43
44
45
46
47
48
49 public class TiffFloatingPointRoundTripTest extends TiffBaseTest {
50
51 @TempDir
52 Path tempDir;
53
54 int width = 48;
55 int height = 23;
56 float f0 = 0.0F;
57 float f1 = 1.0F;
58 float[] f = new float[width * height];
59 int[] argb = new int[width * height];
60
61 public TiffFloatingPointRoundTripTest() throws ImagingException, IOException {
62
63 for (int iCol = 0; iCol < width; iCol++) {
64 final float s = iCol / (float) (width - 1);
65 for (int iRow = 0; iRow < height; iRow++) {
66 final int index = iRow * width + iCol;
67 f[index] = s;
68 }
69 }
70
71
72
73
74 final PhotometricInterpreterFloat pi = getPhotometricInterpreter();
75 final ImageBuilder builder = new ImageBuilder(width, height, false);
76 final int[] samples = new int[1];
77 for (int iCol = 0; iCol < width; iCol++) {
78 for (int iRow = 0; iRow < height; iRow++) {
79 final int index = iRow * width + iCol;
80 samples[0] = Float.floatToRawIntBits(f[index]);
81 pi.interpretPixel(builder, samples, iCol, iRow);
82 argb[index] = builder.getRgb(iCol, iRow);
83 }
84 }
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 private byte[][] getBytesForOutput32(final float[] f, final int width, final int height, final int nRowsInBlock, final int nColsInBlock,
101 final ByteOrder byteOrder) {
102 final int nColsOfBlocks = (width + nColsInBlock - 1) / nColsInBlock;
103 final int nRowsOfBlocks = (height + nRowsInBlock + 1) / nRowsInBlock;
104 final int bytesPerPixel = 4;
105 final int nBlocks = nRowsOfBlocks * nColsOfBlocks;
106 final int nBytesInBlock = bytesPerPixel * nRowsInBlock * nColsInBlock;
107 final byte[][] blocks = new byte[nBlocks][nBytesInBlock];
108 for (int i = 0; i < height; i++) {
109 final int blockRow = i / nRowsInBlock;
110 final int rowInBlock = i - blockRow * nRowsInBlock;
111 final int blockOffset = rowInBlock * nColsInBlock;
112 for (int j = 0; j < width; j++) {
113 final int sample = Float.floatToRawIntBits(f[i * width + j]);
114 final int blockCol = j / nColsInBlock;
115 final int colInBlock = j - blockCol * nColsInBlock;
116 final int index = blockOffset + colInBlock;
117 final int offset = index * bytesPerPixel;
118 final byte[] b = blocks[blockRow * nColsOfBlocks + blockCol];
119 if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
120 b[offset] = (byte) (sample & 0xff);
121 b[offset + 1] = (byte) (sample >> 8 & 0xff);
122 b[offset + 2] = (byte) (sample >> 16 & 0xff);
123 b[offset + 3] = (byte) (sample >> 24 & 0xff);
124 } else {
125 b[offset] = (byte) (sample >> 24 & 0xff);
126 b[offset + 1] = (byte) (sample >> 16 & 0xff);
127 b[offset + 2] = (byte) (sample >> 8 & 0xff);
128 b[offset + 3] = (byte) (sample & 0xff);
129 }
130 }
131 }
132
133 return blocks;
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 private byte[][] getBytesForOutput64(final float[] f, final int width, final int height, final int nRowsInBlock, final int nColsInBlock,
150 final ByteOrder byteOrder) {
151 final int nColsOfBlocks = (width + nColsInBlock - 1) / nColsInBlock;
152 final int nRowsOfBlocks = (height + nRowsInBlock + 1) / nRowsInBlock;
153 final int bytesPerPixel = 8;
154 final int nBlocks = nRowsOfBlocks * nColsOfBlocks;
155 final int nBytesInBlock = bytesPerPixel * nRowsInBlock * nColsInBlock;
156 final byte[][] blocks = new byte[nBlocks][nBytesInBlock];
157 for (int i = 0; i < height; i++) {
158 final int blockRow = i / nRowsInBlock;
159 final int rowInBlock = i - blockRow * nRowsInBlock;
160 final int blockOffset = rowInBlock * nColsInBlock;
161 for (int j = 0; j < width; j++) {
162 final long sample = Double.doubleToRawLongBits(f[i * width + j]);
163 final int blockCol = j / nColsInBlock;
164 final int colInBlock = j - blockCol * nColsInBlock;
165 final int index = blockOffset + colInBlock;
166 final int offset = index * bytesPerPixel;
167 final byte[] b = blocks[blockRow * nColsOfBlocks + blockCol];
168 if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
169 b[offset] = (byte) (sample & 0xff);
170 b[offset + 1] = (byte) (sample >> 8 & 0xff);
171 b[offset + 2] = (byte) (sample >> 16 & 0xff);
172 b[offset + 3] = (byte) (sample >> 24 & 0xff);
173 b[offset + 4] = (byte) (sample >> 32 & 0xff);
174 b[offset + 5] = (byte) (sample >> 40 & 0xff);
175 b[offset + 6] = (byte) (sample >> 48 & 0xff);
176 b[offset + 7] = (byte) (sample >> 56 & 0xff);
177 } else {
178 b[offset] = (byte) (sample >> 56 & 0xff);
179 b[offset + 1] = (byte) (sample >> 48 & 0xff);
180 b[offset + 2] = (byte) (sample >> 40 & 0xff);
181 b[offset + 3] = (byte) (sample >> 32 & 0xff);
182 b[offset + 4] = (byte) (sample >> 24 & 0xff);
183 b[offset + 5] = (byte) (sample >> 16 & 0xff);
184 b[offset + 6] = (byte) (sample >> 8 & 0xff);
185 b[offset + 7] = (byte) (sample & 0xff);
186 }
187 }
188 }
189
190 return blocks;
191 }
192
193
194
195
196
197
198
199 private PhotometricInterpreterFloat getPhotometricInterpreter() {
200 return new PhotometricInterpreterFloat(f0, f1 + 1.0e-5f);
201 }
202
203 @Test
204 public void test() throws Exception {
205
206
207
208
209
210 final File[] testFile = new File[8];
211 testFile[0] = writeFile(32, ByteOrder.LITTLE_ENDIAN, false);
212 testFile[1] = writeFile(64, ByteOrder.LITTLE_ENDIAN, false);
213 testFile[2] = writeFile(32, ByteOrder.BIG_ENDIAN, false);
214 testFile[3] = writeFile(64, ByteOrder.BIG_ENDIAN, false);
215 testFile[4] = writeFile(32, ByteOrder.LITTLE_ENDIAN, true);
216 testFile[5] = writeFile(64, ByteOrder.LITTLE_ENDIAN, true);
217 testFile[6] = writeFile(32, ByteOrder.BIG_ENDIAN, true);
218 testFile[7] = writeFile(64, ByteOrder.BIG_ENDIAN, true);
219 for (int i = 0; i < testFile.length; i++) {
220 final String name = testFile[i].getName();
221 final ByteSource byteSource = ByteSource.file(testFile[i]);
222 final TiffReader tiffReader = new TiffReader(true);
223 final TiffContents contents = tiffReader.readDirectories(byteSource, true,
224 FormatCompliance.getDefault());
225 final TiffDirectory directory = contents.directories.get(0);
226 final PhotometricInterpreterFloat pi = getPhotometricInterpreter();
227 final TiffImagingParameters params = new TiffImagingParameters();
228 params.setCustomPhotometricInterpreter(pi);
229 final ByteOrder byteOrder = tiffReader.getByteOrder();
230 final BufferedImage bImage = directory.getTiffImage(byteOrder, params);
231 assertNotNull(bImage, "Failed to get image from " + name);
232 final int[] pixel = new int[width * height];
233 bImage.getRGB(0, 0, width, height, pixel, 0, width);
234 for (int k = 0; k < pixel.length; k++) {
235 assertEquals(argb[k], pixel[k], "Extracted data does not match original, test " + i + ", index " + k);
236 }
237 final float meanValue = pi.getMeanFound();
238 assertEquals(0.5, meanValue, 1.0e-5, "Invalid numeric values in " + name);
239
240
241
242
243
244 }
245 }
246
247 private File writeFile(final int bitsPerSample, final ByteOrder byteOrder, final boolean useTiles) throws IOException, ImagingException {
248 final String name = String.format("FpRoundTrip_%2d_%s_%s.tiff", bitsPerSample, byteOrder == ByteOrder.LITTLE_ENDIAN ? "LE" : "BE",
249 useTiles ? "Tiles" : "Strips");
250 final File outputFile = new File(tempDir.toFile(), name);
251
252 final int bytesPerSample = bitsPerSample / 8;
253 int nRowsInBlock;
254 int nColsInBlock;
255 int nBytesInBlock;
256 if (useTiles) {
257
258
259
260 nRowsInBlock = 12;
261 nColsInBlock = 20;
262 } else {
263
264
265
266 nRowsInBlock = 2;
267 nColsInBlock = width;
268 }
269 nBytesInBlock = nRowsInBlock * nColsInBlock * bytesPerSample;
270
271 byte[][] blocks;
272 if (bitsPerSample == 32) {
273 blocks = getBytesForOutput32(f, width, height, nRowsInBlock, nColsInBlock, byteOrder);
274 } else {
275 blocks = getBytesForOutput64(f, width, height, nRowsInBlock, nColsInBlock, byteOrder);
276 }
277
278
279
280
281 final TiffOutputSet outputSet = new TiffOutputSet(byteOrder);
282 final TiffOutputDirectory outDir = outputSet.addRootDirectory();
283 outDir.add(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, width);
284 outDir.add(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, height);
285 outDir.add(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, (short) TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT);
286 outDir.add(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL, (short) 1);
287 outDir.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, (short) bitsPerSample);
288 outDir.add(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION, (short) TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_BLACK_IS_ZERO);
289 outDir.add(TiffTagConstants.TIFF_TAG_COMPRESSION, (short) TiffTagConstants.COMPRESSION_VALUE_UNCOMPRESSED);
290
291 outDir.add(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION, (short) TiffTagConstants.PLANAR_CONFIGURATION_VALUE_CHUNKY);
292
293 if (useTiles) {
294 outDir.add(TiffTagConstants.TIFF_TAG_TILE_WIDTH, nColsInBlock);
295 outDir.add(TiffTagConstants.TIFF_TAG_TILE_LENGTH, nRowsInBlock);
296 outDir.add(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS, nBytesInBlock);
297 } else {
298 outDir.add(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP, 2);
299 outDir.add(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS, nBytesInBlock);
300 }
301
302 final AbstractTiffElement.DataElement[] imageData = new AbstractTiffElement.DataElement[blocks.length];
303 for (int i = 0; i < blocks.length; i++) {
304 imageData[i] = new AbstractTiffImageData.Data(0, blocks[i].length, blocks[i]);
305 }
306
307 AbstractTiffImageData abstractTiffImageData;
308 if (useTiles) {
309 abstractTiffImageData = new AbstractTiffImageData.Tiles(imageData, nColsInBlock, nRowsInBlock);
310 } else {
311 abstractTiffImageData = new AbstractTiffImageData.Strips(imageData, nRowsInBlock);
312 }
313 outDir.setTiffImageData(abstractTiffImageData);
314
315 try (FileOutputStream fos = new FileOutputStream(outputFile);
316 BufferedOutputStream bos = new BufferedOutputStream(fos)) {
317 final TiffImageWriterLossy writer = new TiffImageWriterLossy(byteOrder);
318 writer.write(bos, outputSet);
319 bos.flush();
320 }
321 return outputFile;
322 }
323 }