View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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.assertThrows;
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  
29  import org.apache.commons.imaging.FormatCompliance;
30  import org.apache.commons.imaging.ImagingException;
31  import org.apache.commons.imaging.bytesource.ByteSource;
32  import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
33  import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
34  import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
35  import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
36  import org.junit.jupiter.api.Test;
37  import org.junit.jupiter.api.io.TempDir;
38  
39  /**
40   * Performs a test in which a TIFF file with the special-purpose 32-bit integer sample type is used to store data to a file. The file is then read to see if it
41   * matches the original values. The primary purpose of this test is to verify that the TIFF data reader classes behave correctly when reading raster data in
42   * various formats.
43   */
44  public class TiffRoundTripInt32Test extends TiffBaseTest {
45  
46      @TempDir
47      Path tempDir;
48  
49      int width = 48;
50      int height = 23;
51  
52      int[] sample = new int[width * height];
53  
54      public TiffRoundTripInt32Test() {
55          // populate the image data
56          for (int iCol = 0; iCol < width; iCol++) {
57              for (int iRow = 0; iRow < height; iRow++) {
58                  final int index = iRow * width + iCol;
59                  sample[index] = index - 10; // -10 so at least some are negative
60              }
61          }
62      }
63  
64      /**
65       * Gets the bytes for output for a 16 bit floating point format. Note that this method operates over "blocks" of data which may represent either TIFF Strips
66       * or Tiles. When processing strips, there is always one column of blocks and each strip is exactly the full width of the image. When processing tiles,
67       * there may be one or more columns of blocks and the block coverage may extend beyond both the last row and last column.
68       *
69       * @param s            an array of the grid of output values in row major order
70       * @param width        the width of the overall image
71       * @param height       the height of the overall image
72       * @param nRowsInBlock the number of rows in the Strip or Tile
73       * @param nColsInBlock the number of columns in the Strip or Tile
74       * @param byteOrder    little-endian or big-endian
75       * @return a two-dimensional array of bytes dimensioned by the number of blocks and samples
76       */
77      private byte[][] getBytesForOutput32(final int[] s, final int width, final int height, final int nRowsInBlock, final int nColsInBlock,
78              final ByteOrder byteOrder) {
79          final int nColsOfBlocks = (width + nColsInBlock - 1) / nColsInBlock;
80          final int nRowsOfBlocks = (height + nRowsInBlock + 1) / nRowsInBlock;
81          final int bytesPerPixel = 4;
82          final int nBlocks = nRowsOfBlocks * nColsOfBlocks;
83          final int nBytesInBlock = bytesPerPixel * nRowsInBlock * nColsInBlock;
84          final byte[][] blocks = new byte[nBlocks][nBytesInBlock];
85          for (int i = 0; i < height; i++) {
86              final int blockRow = i / nRowsInBlock;
87              final int rowInBlock = i - blockRow * nRowsInBlock;
88              final int blockOffset = rowInBlock * nColsInBlock;
89              for (int j = 0; j < width; j++) {
90                  final int value = s[i * width + j];
91                  final int blockCol = j / nColsInBlock;
92                  final int colInBlock = j - blockCol * nColsInBlock;
93                  final int index = blockOffset + colInBlock;
94                  final int offset = index * bytesPerPixel;
95                  final byte[] b = blocks[blockRow * nColsOfBlocks + blockCol];
96                  if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
97                      b[offset] = (byte) (value & 0xff);
98                      b[offset + 1] = (byte) (value >> 8 & 0xff);
99                      b[offset + 2] = (byte) (value >> 16 & 0xff);
100                     b[offset + 3] = (byte) (value >> 24 & 0xff);
101                 } else {
102                     b[offset] = (byte) (value >> 24 & 0xff);
103                     b[offset + 1] = (byte) (value >> 16 & 0xff);
104                     b[offset + 2] = (byte) (value >> 8 & 0xff);
105                     b[offset + 3] = (byte) (value & 0xff);
106                 }
107             }
108         }
109 
110         return blocks;
111     }
112 
113     @Test
114     public void test() throws Exception {
115         final File[] testFile = new File[4];
116         testFile[0] = writeFile(32, ByteOrder.LITTLE_ENDIAN, false);
117         testFile[1] = writeFile(32, ByteOrder.BIG_ENDIAN, false);
118         testFile[2] = writeFile(32, ByteOrder.LITTLE_ENDIAN, true);
119         testFile[3] = writeFile(32, ByteOrder.BIG_ENDIAN, true);
120         for (int i = 0; i < testFile.length; i++) {
121             final String name = testFile[i].getName();
122             final ByteSource byteSource = ByteSource.file(testFile[i]);
123             final TiffReader tiffReader = new TiffReader(true);
124             final TiffContents contents = tiffReader.readDirectories(byteSource, true, // indicates that application should read image data, if present
125                     FormatCompliance.getDefault());
126             final TiffDirectory directory = contents.directories.get(0);
127             final TiffRasterData rdInt = directory.getRasterData(null);
128             final int[] test = rdInt.getIntData();
129             for (int j = 0; j < sample.length; j++) {
130                 assertEquals(sample[j], test[j], "Extracted data does not match original, test " + name + ": " + i + ", index " + j);
131             }
132             final TiffImagingParameters params = new TiffImagingParameters();
133             params.setSubImage(2, 2, width - 4, height - 4);
134             final TiffRasterData rdSub = directory.getRasterData(params);
135             assertEquals(width - 4, rdSub.getWidth(), "Invalid sub-image width");
136             assertEquals(height - 4, rdSub.getHeight(), "Invalid sub-image height");
137             for (int x = 2; x < width - 2; x++) {
138                 for (int y = 2; y < height - 2; y++) {
139                     final int a = rdInt.getIntValue(x, y);
140                     final int b = rdSub.getIntValue(x - 2, y - 2);
141                     assertEquals(a, b, "Sub Image test failed at (" + x + "," + y + ")");
142                 }
143             }
144             final TiffImagingParameters xparams = new TiffImagingParameters();
145             xparams.setSubImage(2, 2, width, height);
146             assertThrows(ImagingException.class, () -> directory.getRasterData(xparams), "Failed to catch bad subimage for test " + name);
147         }
148     }
149 
150     private File writeFile(final int bitsPerSample, final ByteOrder byteOrder, final boolean useTiles) throws IOException, ImagingException {
151         final String name = String.format("Int32RoundTrip_%2d_%s_%s.tiff", bitsPerSample, byteOrder == ByteOrder.LITTLE_ENDIAN ? "LE" : "BE",
152                 useTiles ? "Tiles" : "Strips");
153         final File outputFile = new File(tempDir.toFile(), name);
154 
155         final int bytesPerSample = bitsPerSample / 8;
156         int nRowsInBlock;
157         int nColsInBlock;
158         int nBytesInBlock;
159         if (useTiles) {
160             // Define the tiles so that they will not evenly subdivide
161             // the image. This will allow the test to evaluate how the
162             // data reader processes tiles that are only partially used.
163             nRowsInBlock = 12;
164             nColsInBlock = 20;
165         } else {
166             // Define the strips so that they will not evenly subdivide
167             // the image. This will allow the test to evaluate how the
168             // data reader processes strips that are only partially used.
169             nRowsInBlock = 2;
170             nColsInBlock = width;
171         }
172         nBytesInBlock = nRowsInBlock * nColsInBlock * bytesPerSample;
173 
174         byte[][] blocks;
175         blocks = this.getBytesForOutput32(sample, width, height, nRowsInBlock, nColsInBlock, byteOrder);
176 
177         // NOTE: At this time, Tile format is not supported.
178         // When it is, modify the tags below to populate
179         // TIFF_TAG_TILE_* appropriately.
180         final TiffOutputSet outputSet = new TiffOutputSet(byteOrder);
181         final TiffOutputDirectory outDir = outputSet.addRootDirectory();
182         outDir.add(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, width);
183         outDir.add(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, height);
184         outDir.add(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, (short) TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER);
185         outDir.add(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL, (short) 1);
186         outDir.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, (short) bitsPerSample);
187         outDir.add(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION, (short) TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_BLACK_IS_ZERO);
188         outDir.add(TiffTagConstants.TIFF_TAG_COMPRESSION, (short) TiffTagConstants.COMPRESSION_VALUE_UNCOMPRESSED);
189 
190         outDir.add(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION, (short) TiffTagConstants.PLANAR_CONFIGURATION_VALUE_CHUNKY);
191 
192         if (useTiles) {
193             outDir.add(TiffTagConstants.TIFF_TAG_TILE_WIDTH, nColsInBlock);
194             outDir.add(TiffTagConstants.TIFF_TAG_TILE_LENGTH, nRowsInBlock);
195             outDir.add(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS, nBytesInBlock);
196         } else {
197             outDir.add(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP, nRowsInBlock);
198             outDir.add(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS, nBytesInBlock);
199         }
200 
201         final AbstractTiffElement.DataElement[] imageData = new AbstractTiffElement.DataElement[blocks.length];
202         for (int i = 0; i < blocks.length; i++) {
203             imageData[i] = new AbstractTiffImageData.Data(0, blocks[i].length, blocks[i]);
204         }
205 
206         AbstractTiffImageData abstractTiffImageData;
207         if (useTiles) {
208             abstractTiffImageData = new AbstractTiffImageData.Tiles(imageData, nColsInBlock, nRowsInBlock);
209         } else {
210             abstractTiffImageData = new AbstractTiffImageData.Strips(imageData, nRowsInBlock);
211         }
212         outDir.setTiffImageData(abstractTiffImageData);
213 
214         try (FileOutputStream fos = new FileOutputStream(outputFile);
215                 BufferedOutputStream bos = new BufferedOutputStream(fos)) {
216             final TiffImageWriterLossy writer = new TiffImageWriterLossy(byteOrder);
217             writer.write(bos, outputSet);
218             bos.flush();
219         }
220         return outputFile;
221     }
222 
223 }