001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.formats.tiff.photometricinterpreters.floatingpoint; 018 019import java.awt.Color; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Comparator; 023import java.util.List; 024 025import org.apache.commons.imaging.ImageReadException; 026import org.apache.commons.imaging.common.ImageBuilder; 027import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; 028 029/** 030 * Implements a custom photometric interpreter that can be supplied by 031 * applications in order to render Java images from real-valued TIFF data 032 * products. Most TIFF files include a specification for a "photometric 033 * interpreter" that implements logic for transforming the raw data in a TIFF 034 * file to a rendered image. But the TIFF standard does not include a 035 * specification for a photometric interpreter that can be used for rendering 036 * floating-point data. TIFF files are sometimes used to specify non-image data 037 * as a floating-point raster. This approach is particularly common in GeoTIFF 038 * files (TIFF files that contain tags for supporting geospatial reference 039 * metadata for Geographic Information Systems). Because of the limits of the 040 * stock photometric interpreters, most floating-point TIFF files to not produce 041 * useful images. 042 * <p> 043 * This class allows an Apache Commons implementation to construct and specify a 044 * custom photometric interpreter when reading from a TIFF file. Applications 045 * may supply their own palette that maps real-valued data to specified colors. 046 * <p> 047 * This class provides two constructors: 048 * <ol> 049 * <li>A simple constructor to support gray scales</li> 050 * <li>A constructor to support a color palette (with potential 051 * interpolation)</li> 052 * </ol> 053 * <p> 054 * To use this class, an application must access the TIFF file using the 055 * low-level, TIFF-specific API provided by the Apache Commons Imaging library. 056 * 057 */ 058public class PhotometricInterpreterFloat extends PhotometricInterpreter { 059 060 ArrayList<PaletteEntry> rangePaletteEntries = new ArrayList<>(); 061 ArrayList<PaletteEntry> singleValuePaletteEntries = new ArrayList<>(); 062 063 float minFound = Float.POSITIVE_INFINITY; 064 float maxFound = Float.NEGATIVE_INFINITY; 065 int xMin; 066 int yMin; 067 int xMax; 068 int yMax; 069 070 double sumFound; 071 int nFound; 072 073 /** 074 * Constructs a photometric interpreter that will produce a gray scale 075 * linearly distributed across the RGB color space for values in the range 076 * valueBlack to valueWhite. Note that the two values may be given in either 077 * ascending order or descending order, but they must not be equal. Infinite 078 * values will not result in proper numerical computations. 079 * 080 * @param valueBlack the value associated with the dark side of the gray 081 * scale 082 * @param valueWhite the value associated with the light side of the gray 083 * scale 084 */ 085 public PhotometricInterpreterFloat( 086 final float valueBlack, final float valueWhite) { 087 // The abstract base class requires that the following fields 088 // be set in the constructor: 089 // samplesPerPixel (int) 090 // bits per sample (array of type int[samplesPerPixel]) 091 // predictor (int, not used by this class) 092 // width (int) 093 // height (int) 094 super( 095 1, 096 new int[]{32}, // bits per sample 097 0, // not used by this class 098 32, // pro forma width value 099 32 // pro format height value 100 ); 101 102 103 if (valueWhite > valueBlack) { 104 final PaletteEntryForRange entry 105 = new PaletteEntryForRange(valueBlack, valueWhite, Color.black, Color.white); 106 rangePaletteEntries.add(entry); 107 } else { 108 final PaletteEntryForRange entry 109 = new PaletteEntryForRange(valueWhite, valueBlack, Color.white, Color.black); 110 rangePaletteEntries.add(entry); 111 } 112 } 113 114 /** 115 * Constructs a photometric interpreter that will use the specified palette 116 * to assign colors to floating-point values. 117 * <p> 118 * Although there is no prohibition against using palette entries with overlapping ranges, 119 * the behavior of such specifications is undefined and subject to change in the future. 120 * Therefore, it is not recommended. The exception in in the use of single-value 121 * palette entries which may be used to override the specifications for ranges. 122 * 123 * @param paletteEntries a valid, non-empty list of palette entries 124 */ 125 public PhotometricInterpreterFloat(final List<PaletteEntry> paletteEntries) { 126 // The abstract base class requires that the following fields 127 // be set in the constructor: 128 // samplesPerPixel (int) 129 // bits per sample (array of type int[samplesPerPixel]) 130 // predictor (int, not used by this class) 131 // width (int) 132 // height (int) 133 super( 134 1, 135 new int[]{32}, // bits per sample 136 0, // not used by this class 137 32, // pro forma width value 138 32 // pro format height value 139 ); 140 141 if (paletteEntries == null || paletteEntries.isEmpty()) { 142 throw new IllegalArgumentException( 143 "Palette entries list must be non-null and non-empty"); 144 } 145 146 for (final PaletteEntry entry : paletteEntries) { 147 if (entry.coversSingleEntry()) { 148 singleValuePaletteEntries.add(entry); 149 } else { 150 rangePaletteEntries.add(entry); 151 } 152 } 153 154 final Comparator<PaletteEntry> comparator = (o1, o2) -> { 155 if (o1.getLowerBound() == o2.getLowerBound()) { 156 return Double.compare(o1.getUpperBound(), o2.getUpperBound()); 157 } 158 return Double.compare(o1.getLowerBound(), o2.getLowerBound()); 159 }; 160 161 rangePaletteEntries.sort(comparator); 162 singleValuePaletteEntries.sort(comparator); 163 } 164 165 @Override 166 public void interpretPixel( 167 final ImageBuilder imageBuilder, 168 final int[] samples, final int x, final int y) 169 throws ImageReadException, IOException { 170 171 final float f = Float.intBitsToFloat(samples[0]); 172 // in the event of NaN, do not store entry in the image builder. 173 174 // only the single bound palette entries support NaN 175 for (final PaletteEntry entry : singleValuePaletteEntries) { 176 if (entry.isCovered(f)) { 177 final int p = entry.getARGB(f); 178 imageBuilder.setRGB(x, y, p); 179 return; 180 } 181 } 182 183 if (Float.isNaN(f)) { 184 // if logic reaches here, there is no definition 185 // for a NaN. 186 return; 187 } 188 if (f < minFound) { 189 minFound = f; 190 xMin = x; 191 yMin = y; 192 } 193 if (f > maxFound) { 194 maxFound = f; 195 xMax = x; 196 yMax = y; 197 } 198 nFound++; 199 sumFound += f; 200 201 for (final PaletteEntry entry : singleValuePaletteEntries) { 202 if (entry.isCovered(f)) { 203 final int p = entry.getARGB(f); 204 imageBuilder.setRGB(x, y, p); 205 return; 206 } 207 } 208 209 for (final PaletteEntry entry : rangePaletteEntries) { 210 if (entry.isCovered(f)) { 211 final int p = entry.getARGB(f); 212 imageBuilder.setRGB(x, y, p); 213 break; 214 } 215 } 216 } 217 218 /** 219 * Gets the minimum value found while rendering the image 220 * 221 * @return if data was processed, a valid value; otherwise, Positive 222 * Infinity 223 */ 224 public float getMinFound() { 225 return minFound; 226 } 227 228 /** 229 * Gets the coordinates (x,y) at which the maximum value was identified 230 * during processing 231 * 232 * @return a valid array of length 2. 233 */ 234 public int[] getMaxXY() { 235 return new int[]{xMax, yMax}; 236 } 237 238 /** 239 * Gets the maximum value found while rendering the image 240 * 241 * @return if data was processed, a valid value; otherwise, Negative 242 * Infinity 243 */ 244 public float getMaxFound() { 245 return maxFound; 246 } 247 248 /** 249 * Gets the coordinates (x,y) at which the minimum value was identified 250 * during processing 251 * 252 * @return a valid array of length 2. 253 */ 254 public int[] getMinXY() { 255 return new int[]{xMin, yMin}; 256 } 257 258 /** 259 * Get the mean of the values found while processing 260 * 261 * @return if data was processed, a valid mean value; otherwise, a zero. 262 */ 263 public float getMeanFound() { 264 if (nFound == 0) { 265 return 0; 266 } 267 return (float) (sumFound / nFound); 268 } 269 270 /** 271 * Provides a method for mapping a pixel value to an integer (ARGB) value. 272 * This method is not defined for the standard photometric interpreters and 273 * is provided as a convenience to applications that are processing data 274 * outside the standard TIFF image-reading modules. 275 * 276 * @param f the floating point value to be mapped to an ARGB value 277 * @return a valid ARGB value, or zero if no palette specification covers 278 * the input value. 279 */ 280 public int mapValueToARGB(final float f) { 281 282 // The single-value palette entries can accept a Float.NaN as 283 // a target while the range-of-values entries cannot. So 284 // check the single-values before testing for Float.isNaN() 285 // because NaN may have special treatment. 286 for (final PaletteEntry entry : singleValuePaletteEntries) { 287 if (entry.isCovered(f)) { 288 return entry.getARGB(f); 289 } 290 } 291 292 if (Float.isNaN(f)) { 293 // if logic reaches here, there is no definition 294 // for a NaN. 295 return 0; 296 } 297 298 for (final PaletteEntry entry : rangePaletteEntries) { 299 if (entry.isCovered(f)) { 300 return entry.getARGB(f); 301 } 302 } 303 return 0; 304 } 305 306}