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.photometricinterpreters.floatingpoint;
18  
19  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  
23  import java.awt.Color;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.apache.commons.imaging.ImagingException;
29  import org.apache.commons.imaging.common.ImageBuilder;
30  import org.junit.jupiter.api.BeforeAll;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Provides a unit test for the TIFF photometric interpreter used for mapping floating-point values to a color palette.
35   */
36  public class PhotometricInterpreterFloatTest {
37  
38      private static PhotometricInterpreterFloat pInterp;
39      private static PhotometricInterpreterFloat bandedInterp;
40      private static ImageBuilder imageBuilder;
41      private static ImageBuilder bandedImageBuilder;
42  
43      private static final Color orange = new Color(255, 136, 62);
44      private static final Color green = new Color(22, 155, 98);
45  
46      @BeforeAll
47      public static void setUpClass() throws ImagingException, IOException {
48          // the setup is to assign color (grayscale) values to the
49          // pixels along the main diagonal at coordinates
50          // (0, 0), (1, 1), ... (256, 256).
51          // The floating point values at each pixel are just the
52          // index divided by 256.
53  
54          final List<PaletteEntry> paletteList = new ArrayList<>();
55          final List<PaletteEntry> reverseList = new ArrayList<>();
56          for (int i = 0; i < 256; i += 32) {
57              final int i1 = i + 31;
58              final float f0 = i / 256f;
59              final float f1 = (i + 32) / 256f;
60              final int argb0 = 0xff000000 | i << 8 | i;
61              final int argb1 = 0xff000000 | i1 << 8 | i;
62              final Color c0 = new Color(argb0);
63              final Color c1 = new Color(argb1);
64              final PaletteEntryForRange entry = new PaletteEntryForRange(f0, f1, c0, c1);
65              paletteList.add(entry);
66          }
67          // The interpreter is supposed to sort entries. To test that,
68          // we copy them to a list in reverse order.
69          for (int i = paletteList.size() - 1; i >= 0; i--) {
70              final PaletteEntry entry = paletteList.get(i);
71              reverseList.add(entry);
72          }
73  
74          pInterp = new PhotometricInterpreterFloat(reverseList);
75  
76          // pre-populate the state data for the interpreter with
77          // some values so that we can test min/max access methods.
78          imageBuilder = new ImageBuilder(257, 257, false);
79          final int[] samples = new int[1];
80          for (int i = 0; i <= 256; i++) {
81              final float f = i / 256f;
82              samples[0] = Float.floatToRawIntBits(f);
83              pInterp.interpretPixel(imageBuilder, samples, i, i);
84          }
85  
86          // Now set up a palette than maps values in a range to a single color.
87          final List<PaletteEntry> bandedPaletteList = new ArrayList<>();
88          bandedPaletteList.add(new PaletteEntryForRange(0f, 0.33f, green));
89          bandedPaletteList.add(new PaletteEntryForRange(0.33f, 0.66f, Color.white));
90          bandedPaletteList.add(new PaletteEntryForRange(0.66f, 1.0f, orange));
91          bandedPaletteList.add(new PaletteEntryForValue(Float.NaN, Color.gray));
92          bandedPaletteList.add(new PaletteEntryForValue(-1, Color.gray));
93          bandedInterp = new PhotometricInterpreterFloat(bandedPaletteList);
94          bandedImageBuilder = new ImageBuilder(300, 200, false);
95          for (int j = 0; j < 300; j++) {
96              final float f = j / 299.0f;
97              samples[0] = Float.floatToRawIntBits(f);
98              for (int i = 0; i < 200; i++) {
99                  bandedInterp.interpretPixel(bandedImageBuilder, samples, j, i);
100             }
101         }
102         samples[0] = Float.floatToRawIntBits(Float.NaN);
103         for (int i = 0; i < 200; i++) {
104             bandedInterp.interpretPixel(bandedImageBuilder, samples, 0, i);
105             bandedInterp.interpretPixel(bandedImageBuilder, samples, 299, i);
106         }
107         samples[0] = Float.floatToRawIntBits(-1);
108         for (int i = 0; i < 300; i++) {
109             bandedInterp.interpretPixel(bandedImageBuilder, samples, i, 0);
110             bandedInterp.interpretPixel(bandedImageBuilder, samples, i, 199);
111         }
112     }
113 
114     public PhotometricInterpreterFloatTest() {
115     }
116 
117     @Test
118     public void testConstructors() {
119         new PhotometricInterpreterFloat(0, 1);
120         new PhotometricInterpreterFloat(1, 0);
121 
122         assertThrows(IllegalArgumentException.class, () -> new PhotometricInterpreterFloat(null), "Constructor failed to detect null arguments");
123 
124         assertThrows(IllegalArgumentException.class, () -> new PhotometricInterpreterFloat(0.1f, 0.1f),
125                 "Constructor failed to detect bad-range argument values");
126 
127     }
128 
129     /**
130      * Test of getMaxFound method, of class PhotometricInterpreterFloat.
131      */
132     @Test
133     public void testGetMaxFound() {
134         final float expResult = 1.0F;
135         final float result = pInterp.getMinFound();
136         assertEquals(expResult, result, 1.0, "Invalid maximum value");
137     }
138 
139     /**
140      * Test of getMaxXY method, of class PhotometricInterpreterFloat.
141      */
142     @Test
143     public void testGetMaxXY() {
144         final int[] expResult = { 256, 256 };
145         final int[] result = pInterp.getMaxXY();
146         assertArrayEquals(expResult, result);
147     }
148 
149     /**
150      * Test of getMeanFound method, of class PhotometricInterpreterFloat.
151      */
152     @Test
153     public void testGetMeanFound() {
154         final float expResult = 0.5F;
155         final float result = pInterp.getMinFound();
156         assertEquals(expResult, result, 1.0, "Invalid mean value");
157     }
158 
159     /**
160      * Test of getMinFound method, of class PhotometricInterpreterFloat.
161      */
162     @Test
163     public void testGetMinFound() {
164         final float expResult = 0.0F;
165         final float result = pInterp.getMinFound();
166         assertEquals(expResult, result, 0.0, "Invalid minimum value");
167     }
168 
169     /**
170      * Test of getMinXY method, of class PhotometricInterpreterFloat.
171      */
172     @Test
173     public void testGetMinXY() {
174         final int[] expResult = { 0, 0 };
175         final int[] result = pInterp.getMinXY();
176         assertArrayEquals(expResult, result);
177     }
178 
179     /**
180      * Test of interpretPixel method, of class PhotometricInterpreterFloat.
181      */
182     @Test
183     public void testInterpretPixel() {
184         for (int i = 0; i < 256; i++) {
185             final int lowTest = i / 32 * 32;
186             final int argb = imageBuilder.getRgb(i, i);
187             final int b = argb & 0xff;
188             assertEquals(b, lowTest, "Invalid conversion for level " + i);
189         }
190 
191         // nothing should match the i=256 case.
192         // The last entry in the palette has values
193         // in the range 224.0/256.0 <= value < 256.0/256.0. So when it
194         // was rendered, there was not palette entry that matched it,
195         // and the corresponding pixel was set to zero.
196         int argb = imageBuilder.getRgb(256, 256);
197         assertEquals(argb, 0, "Invalid upper-bound test");
198 
199         // Now inspect the banded palette case
200         argb = bandedImageBuilder.getRgb(0, 0);
201         assertEquals(Color.gray.getRGB(), argb, "Invalid mapping of NaN");
202         argb = bandedImageBuilder.getRgb(50, 10);
203         assertEquals(green.getRGB(), argb, "Invalid mapping of green range");
204         argb = bandedImageBuilder.getRgb(150, 10);
205         assertEquals(Color.white.getRGB(), argb, "Invalid mapping of white range");
206         argb = bandedImageBuilder.getRgb(250, 10);
207         assertEquals(orange.getRGB(), argb, "Invalid mapping of orange range");
208     }
209 
210     /**
211      * Test of interpretPixel method, of class PhotometricInterpreterFloat.
212      */
213     @Test
214     public void testMapValueToARGB() {
215 
216         int argb = pInterp.mapValueToArgb(0.5f);
217         int test = imageBuilder.getRgb(128, 128);
218         assertEquals(test, argb, "Conflicting results from value-to-ARGB map");
219 
220         // pInterp does not define a state for NaN, but bandedInterp does.
221         // so test both variations
222         argb = pInterp.mapValueToArgb(Float.NaN);
223         assertEquals(0, argb, "Non-defined NaN did not return ARGB of zero");
224 
225         // to test mappings for special values, use the banded-interpreter
226         argb = bandedInterp.mapValueToArgb(Float.NaN);
227         test = Color.gray.getRGB();
228         assertEquals(test, argb, "Float.NaN mapped to incorrect ARGB");
229         argb = bandedInterp.mapValueToArgb(-1f);
230         test = Color.gray.getRGB();
231         assertEquals(test, argb, "Excluded value mapped to incorrect ARGB");
232     }
233 
234     /**
235      * Test of overlapping entries
236      */
237     @Test
238     public void testOverlappingEntriesEntry() throws ImagingException, IOException {
239         final Color c0 = new Color(0xff0000ff);
240         final Color c1 = new Color(0xff00ff00);
241         final List<PaletteEntry> overlapList = new ArrayList<>();
242         overlapList.add(new PaletteEntryForRange(0.0f, 1.0f, c0));
243         overlapList.add(new PaletteEntryForRange(0.0f, 1.5f, c1));
244 
245         final PhotometricInterpreterFloat interpreter = new PhotometricInterpreterFloat(overlapList);
246 
247         imageBuilder = new ImageBuilder(257, 257, false);
248         final int[] samples = new int[1];
249         samples[0] = Float.floatToRawIntBits(0.5f);
250         interpreter.interpretPixel(imageBuilder, samples, 0, 0);
251         samples[0] = Float.floatToRawIntBits(1.2f);
252         interpreter.interpretPixel(imageBuilder, samples, 1, 1);
253         int argb0 = imageBuilder.getRgb(0, 0) | 0xff000000;
254         int argb1 = imageBuilder.getRgb(1, 1) | 0xff000000;
255         assertEquals(argb0, c0.getRGB(), "Invalid result for overlapping palette entry 0");
256         assertEquals(argb1, c1.getRGB(), "Invalid result for overlapping palette entry 1");
257         argb0 = interpreter.mapValueToArgb(0.5f);
258         argb1 = interpreter.mapValueToArgb(1.2f);
259         assertEquals(argb0, c0.getRGB(), "Invalid mapping for overlapping palette entry 0");
260         assertEquals(argb1, c1.getRGB(), "Invalid mapping for overlapping palette entry 1");
261     }
262 
263 }