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.io.BufferedOutputStream;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.nio.ByteOrder;
27 import java.nio.file.Path;
28 import java.util.ArrayList;
29 import java.util.List;
30
31 import org.apache.commons.imaging.FormatCompliance;
32 import org.apache.commons.imaging.ImagingException;
33 import org.apache.commons.imaging.bytesource.ByteSource;
34 import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
35 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
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 TiffFloatingPointMultivariableTest extends TiffBaseTest {
50
51 @TempDir
52 Path tempDir;
53
54 int width = 48;
55 int height = 23;
56 int samplesPerPixel = 2;
57 float f0 = 0.0F;
58 float f1 = 1.0F;
59 float[] fSample = new float[width * height * samplesPerPixel];
60
61 public TiffFloatingPointMultivariableTest() {
62 for (int iPlane = 0; iPlane < 2; iPlane++) {
63 final int pOffset = iPlane * width * height;
64 for (int iRow = 0; iRow < height; iRow++) {
65 for (int iCol = 0; iCol < width; iCol++) {
66 final int index = pOffset + iRow * width + iCol;
67 fSample[index] = index;
68 }
69 }
70 }
71 }
72
73 private void applyTilePredictor(final int nRowsInBlock, final int nColsInBlock, final byte[] bytes) {
74
75
76
77
78
79
80
81
82 final byte[] b = new byte[bytes.length];
83 final int bytesInRow = nColsInBlock * 4;
84 for (int iPlane = 0; iPlane < samplesPerPixel; iPlane++) {
85
86 final int planarByteOffset = iPlane * nRowsInBlock * nColsInBlock * 4;
87 for (int i = 0; i < nRowsInBlock; i++) {
88 final int aOffset = planarByteOffset + i * bytesInRow;
89 final int bOffset = aOffset + nColsInBlock;
90 final int cOffset = bOffset + nColsInBlock;
91 final int dOffset = cOffset + nColsInBlock;
92 for (int j = 0; j < nColsInBlock; j++) {
93 b[aOffset + j] = bytes[aOffset + j * 4];
94 b[bOffset + j] = bytes[aOffset + j * 4 + 1];
95 b[cOffset + j] = bytes[aOffset + j * 4 + 2];
96 b[dOffset + j] = bytes[aOffset + j * 4 + 3];
97 }
98
99 for (int j = bytesInRow - 1; j > 0; j--) {
100 b[aOffset + j] -= b[aOffset + j - 1];
101 }
102 }
103 }
104
105 System.arraycopy(b, 0, bytes, 0, bytes.length);
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 private byte[][] getBytesForOutput32(final int nRowsInBlock, final int nColsInBlock, final ByteOrder byteOrder, final boolean useTiles,
122 final TiffPlanarConfiguration planarConfiguration) {
123 final int nColsOfBlocks = (width + nColsInBlock - 1) / nColsInBlock;
124 final int nRowsOfBlocks = (height + nRowsInBlock + 1) / nRowsInBlock;
125 final int bytesPerPixel = 4 * samplesPerPixel;
126 final int nBlocks = nRowsOfBlocks * nColsOfBlocks;
127 final int nBytesInBlock = bytesPerPixel * nRowsInBlock * nColsInBlock;
128 final byte[][] blocks = new byte[nBlocks][nBytesInBlock];
129 if (planarConfiguration == TiffPlanarConfiguration.CHUNKY) {
130 for (int i = 0; i < height; i++) {
131 final int blockRow = i / nRowsInBlock;
132 final int rowInBlock = i - blockRow * nRowsInBlock;
133 for (int j = 0; j < width; j++) {
134 final int blockCol = j / nColsInBlock;
135 final int colInBlock = j - blockCol * nColsInBlock;
136 final byte[] b = blocks[blockRow * nColsOfBlocks + blockCol];
137 for (int k = 0; k < 2; k++) {
138 final float sValue = fSample[k * width * height + i * width + j];
139 final int sample = Float.floatToRawIntBits(sValue);
140 final int offset = (rowInBlock * nColsInBlock + colInBlock) * 8 + k * 4;
141 if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
142 b[offset] = (byte) (sample & 0xff);
143 b[offset + 1] = (byte) (sample >> 8 & 0xff);
144 b[offset + 2] = (byte) (sample >> 16 & 0xff);
145 b[offset + 3] = (byte) (sample >> 24 & 0xff);
146 } else {
147 b[offset] = (byte) (sample >> 24 & 0xff);
148 b[offset + 1] = (byte) (sample >> 16 & 0xff);
149 b[offset + 2] = (byte) (sample >> 8 & 0xff);
150 b[offset + 3] = (byte) (sample & 0xff);
151 }
152 }
153 }
154 }
155 } else {
156 for (int i = 0; i < height; i++) {
157 final int blockRow = i / nRowsInBlock;
158 final int rowInBlock = i - blockRow * nRowsInBlock;
159 int blockPlanarOffset = nRowsInBlock * nColsInBlock;
160 if (!useTiles && (blockRow + 1) * nRowsInBlock > height) {
161
162
163
164
165
166 final int nRowsAdjusted = height - blockRow * nRowsInBlock;
167 blockPlanarOffset = nRowsAdjusted * nColsInBlock;
168 }
169 for (int j = 0; j < width; j++) {
170 final int blockCol = j / nColsInBlock;
171 final int colInBlock = j - blockCol * nColsInBlock;
172 final byte[] b = blocks[blockRow * nColsOfBlocks + blockCol];
173 for (int k = 0; k < 2; k++) {
174 final float sValue = fSample[k * width * height + i * width + j];
175 final int sample = Float.floatToRawIntBits(sValue);
176 final int offset = (k * blockPlanarOffset + rowInBlock * nColsInBlock + colInBlock) * 4;
177 if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
178 b[offset] = (byte) (sample & 0xff);
179 b[offset + 1] = (byte) (sample >> 8 & 0xff);
180 b[offset + 2] = (byte) (sample >> 16 & 0xff);
181 b[offset + 3] = (byte) (sample >> 24 & 0xff);
182 } else {
183 b[offset] = (byte) (sample >> 24 & 0xff);
184 b[offset + 1] = (byte) (sample >> 16 & 0xff);
185 b[offset + 2] = (byte) (sample >> 8 & 0xff);
186 b[offset + 3] = (byte) (sample & 0xff);
187 }
188 }
189 }
190 }
191 }
192
193 return blocks;
194 }
195
196 @Test
197 public void test() throws Exception {
198
199
200
201
202
203 final List<File> testFiles = new ArrayList<>();
204 testFiles.add(writeFile(ByteOrder.LITTLE_ENDIAN, false, false, TiffPlanarConfiguration.CHUNKY));
205 testFiles.add(writeFile(ByteOrder.BIG_ENDIAN, false, false, TiffPlanarConfiguration.CHUNKY));
206 testFiles.add(writeFile(ByteOrder.LITTLE_ENDIAN, true, false, TiffPlanarConfiguration.CHUNKY));
207 testFiles.add(writeFile(ByteOrder.BIG_ENDIAN, true, false, TiffPlanarConfiguration.CHUNKY));
208 testFiles.add(writeFile(ByteOrder.LITTLE_ENDIAN, false, false, TiffPlanarConfiguration.PLANAR));
209 testFiles.add(writeFile(ByteOrder.BIG_ENDIAN, false, false, TiffPlanarConfiguration.PLANAR));
210 testFiles.add(writeFile(ByteOrder.LITTLE_ENDIAN, true, false, TiffPlanarConfiguration.PLANAR));
211 testFiles.add(writeFile(ByteOrder.BIG_ENDIAN, true, false, TiffPlanarConfiguration.PLANAR));
212
213
214
215
216
217
218 testFiles.add(writeFile(ByteOrder.BIG_ENDIAN, true, true, TiffPlanarConfiguration.PLANAR));
219
220 for (final File testFile : testFiles) {
221 final String name = testFile.getName();
222 final ByteSource byteSource = ByteSource.file(testFile);
223 final TiffReader tiffReader = new TiffReader(true);
224 final TiffContents contents = tiffReader.readDirectories(byteSource, true,
225 FormatCompliance.getDefault());
226 final TiffDirectory directory = contents.directories.get(0);
227 final TiffRasterData raster = directory.getRasterData(new TiffImagingParameters());
228 assertNotNull(raster, "Failed to get raster from " + name);
229 assertEquals(2, raster.getSamplesPerPixel(), "Invalid samples per pixel in " + name);
230 for (int iPlane = 0; iPlane < 2; iPlane++) {
231 final int pOffset = iPlane * width * height;
232 for (int iRow = 0; iRow < height; iRow++) {
233 for (int iCol = 0; iCol < width; iCol++) {
234 final int index = pOffset + iRow * width + iCol;
235 final float tValue = fSample[index];
236 final float rValue = raster.getValue(iCol, iRow, iPlane);
237 assertEquals(tValue, rValue, "Failed at index x=" + iCol + ", y=" + iRow + ", iPlane=" + iPlane);
238 }
239 }
240 }
241 }
242 }
243
244 private File writeFile(final ByteOrder byteOrder, final boolean useTiles, final boolean usePredictorForTiles,
245 final TiffPlanarConfiguration planarConfiguration) throws IOException, ImagingException {
246
247 final String name = String.format("FpMultiVarRoundTrip_%s_%s%s.tiff", planarConfiguration == TiffPlanarConfiguration.CHUNKY ? "Chunky" : "Planar",
248 useTiles ? "Tiles" : "Strips", usePredictorForTiles ? "_Predictor" : "");
249 final File outputFile = new File(tempDir.toFile(), name);
250
251 final int bytesPerSample = 4 * samplesPerPixel;
252 final int bitsPerSample = 8 * bytesPerSample;
253
254 int nRowsInBlock;
255 int nColsInBlock;
256 int nBytesInBlock;
257 if (useTiles) {
258
259
260
261 nRowsInBlock = 12;
262 nColsInBlock = 20;
263 } else {
264
265
266
267 nRowsInBlock = 2;
268 nColsInBlock = width;
269 }
270 nBytesInBlock = nRowsInBlock * nColsInBlock * bytesPerSample;
271
272 byte[][] blocks;
273 blocks = this.getBytesForOutput32(nRowsInBlock, nColsInBlock, byteOrder, useTiles, planarConfiguration);
274
275 final TiffOutputSet outputSet = new TiffOutputSet(byteOrder);
276 final TiffOutputDirectory outDir = outputSet.addRootDirectory();
277 outDir.add(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, width);
278 outDir.add(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, height);
279 outDir.add(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, (short) TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT);
280 outDir.add(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL, (short) samplesPerPixel);
281 outDir.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, (short) bitsPerSample);
282 outDir.add(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION, (short) TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_BLACK_IS_ZERO);
283 outDir.add(TiffTagConstants.TIFF_TAG_COMPRESSION, (short) TiffTagConstants.COMPRESSION_VALUE_UNCOMPRESSED);
284
285 if (useTiles && usePredictorForTiles) {
286 outDir.add(TiffTagConstants.TIFF_TAG_PREDICTOR, (short) TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING);
287 for (final byte[] block : blocks) {
288 applyTilePredictor(nRowsInBlock, nColsInBlock, block);
289 }
290 }
291
292 if (planarConfiguration == TiffPlanarConfiguration.CHUNKY) {
293 outDir.add(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION, (short) TiffTagConstants.PLANAR_CONFIGURATION_VALUE_CHUNKY);
294 } else {
295 outDir.add(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION, (short) TiffTagConstants.PLANAR_CONFIGURATION_VALUE_PLANAR);
296 }
297
298 if (useTiles) {
299 outDir.add(TiffTagConstants.TIFF_TAG_TILE_WIDTH, nColsInBlock);
300 outDir.add(TiffTagConstants.TIFF_TAG_TILE_LENGTH, nRowsInBlock);
301 outDir.add(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS, nBytesInBlock);
302 } else {
303 outDir.add(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP, nRowsInBlock);
304 outDir.add(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS, nBytesInBlock);
305 }
306
307 final AbstractTiffElement.DataElement[] imageData = new AbstractTiffElement.DataElement[blocks.length];
308 for (int i = 0; i < blocks.length; i++) {
309 imageData[i] = new AbstractTiffImageData.Data(0, blocks[i].length, blocks[i]);
310 }
311
312 AbstractTiffImageData abstractTiffImageData;
313 if (useTiles) {
314 abstractTiffImageData = new AbstractTiffImageData.Tiles(imageData, nColsInBlock, nRowsInBlock);
315 } else {
316 abstractTiffImageData = new AbstractTiffImageData.Strips(imageData, nRowsInBlock);
317 }
318 outDir.setTiffImageData(abstractTiffImageData);
319
320 try (FileOutputStream fos = new FileOutputStream(outputFile);
321 BufferedOutputStream bos = new BufferedOutputStream(fos)) {
322 final TiffImageWriterLossy writer = new TiffImageWriterLossy(byteOrder);
323 writer.write(bos, outputSet);
324 bos.flush();
325 }
326 return outputFile;
327 }
328
329 }