kinow commented on a change in pull request #72: URL: https://github.com/apache/commons-imaging/pull/72#discussion_r422593120
########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/ImageDataReader.java ########## @@ -232,4 +332,267 @@ protected void resetPredictor() { throw new ImageReadException("Tiff: unknown/unsupported compression: " + compression); } } + + /** + * Given a source file that specifies the floating-point data format, unpack + * the raw bytes obtained from the source file and organize them into an + * array of integers containing the bit-equivalent of IEEE-754 32-bit + * floats. Source files containing 64 bit doubles are downcast to floats. + * <p> + * This method supports either the tile format or the strip format of TIFF + * source files. The scan size indicates the number of columns to be + * extracted. For strips, the width and the scan size are always the full + * width of the image. For tiles, the scan size is the full width of the + * tile, but the width may be smaller in the cases where the tiles do not + * evenly divide the width (for example, a 256 pixel wide tile in a 257 + * pixel wide image would result in two columns of tiles, the second column + * having only one column of pixels that were worth extracting. + * + * @param width the width of the data block to be extracted + * @param height the height of the data block to be extracted + * @param scansize the number of pixels in a single row of the block + * @param bytes the raw bytes + * @param predictor the predictor specified by the source, only predictor 3 + * is supported. + * @param bitsPerSample the number of bits per sample, 32 or 64. + * @param byteOrder the byte order for the source data + * @return a valid array of integers in row major order, dimensions + * scan-size wide and height height. + * @throws ImageReadException in the event of an invalid format. + */ + protected int[] unpackFloatingPointSamples( + int width, + int height, + int scansize, + byte[] bytes, + int predictor, + int bitsPerSample, ByteOrder byteOrder) + throws ImageReadException { + int bytesPerSample = bitsPerSample / 8; + int nBytes = bytesPerSample * scansize * height; + int length = bytes.length < nBytes ? nBytes / scansize : height; + + int[] samples = new int[scansize * height]; + // floating-point differencing is indicated by a predictor value of 3. + if (predictor == TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING) { + // at this time, this class supports the 32-bit format. The + // main reason for this is that we have not located sample data + // that can be used for testing and analysis. + if (bitsPerSample != 32) { + throw new ImageReadException( + "Imaging does not yet support floating-point data" + + " with predictor type 3 for " + + bitsPerSample + " bits per sample"); + } + int bytesInRow = scansize * 4; + for (int i = 0; i < length; i++) { + int aOffset = i * bytesInRow; + int bOffset = aOffset + scansize; + int cOffset = bOffset + scansize; + int dOffset = cOffset + scansize; + // in this loop, the source bytes give delta values. + // we adjust them to give true values. This operation is + // done on a row-by-row basis. + for (int j = 1; j < bytesInRow; j++) { + bytes[aOffset + j] += bytes[aOffset + j - 1]; + } + // pack the bytes into the integer bit-equivalent of + // floating point values + int index = i * scansize; + for (int j = 0; j < width; j++) { + int a = bytes[aOffset + j]; + int b = bytes[bOffset + j]; + int c = bytes[cOffset + j]; + int d = bytes[dOffset + j]; + // Pack the 4 byte components into a single integer + // in the byte order used by the TIFF standard + samples[index++] = ((a & 0xff) << 24) + | ((b & 0xff) << 16) + | ((c & 0xff) << 8) + | (d & 0xff); + } + } + return samples; + } // end of predictor==3 case. + + // simple packing case, 64 or 32 bits -------------------------- + if (bitsPerSample == 64) { + int k = 0; + int index = 0; + for (int i = 0; i < length; i++) { + for (int j = 0; j < scansize; j++) { + long b0 = bytes[k++] & 0xffL; + long b1 = bytes[k++] & 0xffL; + long b2 = bytes[k++] & 0xffL; + long b3 = bytes[k++] & 0xffL; + long b4 = bytes[k++] & 0xffL; + long b5 = bytes[k++] & 0xffL; + long b6 = bytes[k++] & 0xffL; + long b7 = bytes[k++] & 0xffL; + long sbits; + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + sbits = (b7 << 56) + | (b6 << 48) + | (b5 << 40) + | (b4 << 32) + | (b3 << 24) + | (b2 << 16) + | (b1 << 8) + | b0; + + } else { + sbits = (b0 << 56) + | (b1 << 48) + | (b2 << 40) + | (b3 << 32) + | (b4 << 24) + | (b5 << 16) + | (b6 << 8) + | b7; + } + // since the photometric interpreter does not + // currently support doubles, we need to replace this + // element with a float. This action is inefficient and + // should be improved. + float f = (float) Double.longBitsToDouble(sbits); + samples[index++] = Float.floatToRawIntBits(f); + } + } + } else if (bitsPerSample == 32) { + int k = 0; + int index = 0; + for (int i = 0; i < length; i++) { + for (int j = 0; j < scansize; j++) { + int b0 = bytes[k++] & 0xff; + int b1 = bytes[k++] & 0xff; + int b2 = bytes[k++] & 0xff; + int b3 = bytes[k++] & 0xff; + int sbits; + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + sbits + = (b3 << 24) + | (b2 << 16) + | (b1 << 8) + | b0; + + } else { + sbits + = (b0 << 24) + | (b1 << 16) + | (b2 << 8) + | b3; + } + // since the photometric interpreter does not + // currently support doubles, we need to replace this + // element with a float. This action is inefficient and + // should be improved. + samples[index++] = sbits; + } + } + } else { + throw new ImageReadException( + "Imaging does not support floating-point samples with " + + bitsPerSample + " bits per sample"); + } + + return samples; + } + + /** + * + * @param xBlock coordinate of block relative to source data + * @param yBlock coordinate of block relative to source data + * @param blockWidth width of block, in pixels + * @param blockHeight height of block in pixels + * @param blockData the data for the block + * @param xRaster coordinate of raster relative to source data + * @param yRaster coordinate of raster relative to source data + * @param rasterWidth width of the raster (salways smaller than source data) Review comment: s/salways/always ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/ImageDataReader.java ########## @@ -232,4 +332,267 @@ protected void resetPredictor() { throw new ImageReadException("Tiff: unknown/unsupported compression: " + compression); } } + + /** + * Given a source file that specifies the floating-point data format, unpack + * the raw bytes obtained from the source file and organize them into an + * array of integers containing the bit-equivalent of IEEE-754 32-bit + * floats. Source files containing 64 bit doubles are downcast to floats. + * <p> + * This method supports either the tile format or the strip format of TIFF + * source files. The scan size indicates the number of columns to be + * extracted. For strips, the width and the scan size are always the full + * width of the image. For tiles, the scan size is the full width of the + * tile, but the width may be smaller in the cases where the tiles do not + * evenly divide the width (for example, a 256 pixel wide tile in a 257 + * pixel wide image would result in two columns of tiles, the second column + * having only one column of pixels that were worth extracting. + * + * @param width the width of the data block to be extracted + * @param height the height of the data block to be extracted + * @param scansize the number of pixels in a single row of the block + * @param bytes the raw bytes + * @param predictor the predictor specified by the source, only predictor 3 + * is supported. + * @param bitsPerSample the number of bits per sample, 32 or 64. + * @param byteOrder the byte order for the source data + * @return a valid array of integers in row major order, dimensions + * scan-size wide and height height. + * @throws ImageReadException in the event of an invalid format. + */ + protected int[] unpackFloatingPointSamples( + int width, + int height, + int scansize, + byte[] bytes, + int predictor, + int bitsPerSample, ByteOrder byteOrder) + throws ImageReadException { + int bytesPerSample = bitsPerSample / 8; + int nBytes = bytesPerSample * scansize * height; + int length = bytes.length < nBytes ? nBytes / scansize : height; + + int[] samples = new int[scansize * height]; + // floating-point differencing is indicated by a predictor value of 3. + if (predictor == TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING) { + // at this time, this class supports the 32-bit format. The + // main reason for this is that we have not located sample data + // that can be used for testing and analysis. + if (bitsPerSample != 32) { + throw new ImageReadException( + "Imaging does not yet support floating-point data" + + " with predictor type 3 for " + + bitsPerSample + " bits per sample"); + } + int bytesInRow = scansize * 4; + for (int i = 0; i < length; i++) { + int aOffset = i * bytesInRow; + int bOffset = aOffset + scansize; + int cOffset = bOffset + scansize; + int dOffset = cOffset + scansize; + // in this loop, the source bytes give delta values. + // we adjust them to give true values. This operation is + // done on a row-by-row basis. + for (int j = 1; j < bytesInRow; j++) { + bytes[aOffset + j] += bytes[aOffset + j - 1]; + } + // pack the bytes into the integer bit-equivalent of + // floating point values + int index = i * scansize; + for (int j = 0; j < width; j++) { + int a = bytes[aOffset + j]; + int b = bytes[bOffset + j]; + int c = bytes[cOffset + j]; + int d = bytes[dOffset + j]; + // Pack the 4 byte components into a single integer + // in the byte order used by the TIFF standard + samples[index++] = ((a & 0xff) << 24) + | ((b & 0xff) << 16) + | ((c & 0xff) << 8) + | (d & 0xff); + } + } + return samples; + } // end of predictor==3 case. + + // simple packing case, 64 or 32 bits -------------------------- + if (bitsPerSample == 64) { + int k = 0; + int index = 0; + for (int i = 0; i < length; i++) { + for (int j = 0; j < scansize; j++) { + long b0 = bytes[k++] & 0xffL; + long b1 = bytes[k++] & 0xffL; + long b2 = bytes[k++] & 0xffL; + long b3 = bytes[k++] & 0xffL; + long b4 = bytes[k++] & 0xffL; + long b5 = bytes[k++] & 0xffL; + long b6 = bytes[k++] & 0xffL; + long b7 = bytes[k++] & 0xffL; + long sbits; + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + sbits = (b7 << 56) + | (b6 << 48) + | (b5 << 40) + | (b4 << 32) + | (b3 << 24) + | (b2 << 16) + | (b1 << 8) + | b0; + + } else { + sbits = (b0 << 56) + | (b1 << 48) + | (b2 << 40) + | (b3 << 32) + | (b4 << 24) + | (b5 << 16) + | (b6 << 8) + | b7; + } + // since the photometric interpreter does not + // currently support doubles, we need to replace this + // element with a float. This action is inefficient and + // should be improved. + float f = (float) Double.longBitsToDouble(sbits); + samples[index++] = Float.floatToRawIntBits(f); + } + } + } else if (bitsPerSample == 32) { + int k = 0; + int index = 0; + for (int i = 0; i < length; i++) { + for (int j = 0; j < scansize; j++) { + int b0 = bytes[k++] & 0xff; + int b1 = bytes[k++] & 0xff; + int b2 = bytes[k++] & 0xff; + int b3 = bytes[k++] & 0xff; + int sbits; + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + sbits + = (b3 << 24) + | (b2 << 16) + | (b1 << 8) + | b0; + + } else { + sbits + = (b0 << 24) + | (b1 << 16) + | (b2 << 8) + | b3; + } + // since the photometric interpreter does not + // currently support doubles, we need to replace this + // element with a float. This action is inefficient and + // should be improved. + samples[index++] = sbits; + } + } + } else { + throw new ImageReadException( + "Imaging does not support floating-point samples with " + + bitsPerSample + " bits per sample"); + } + + return samples; + } + + /** + * + * @param xBlock coordinate of block relative to source data + * @param yBlock coordinate of block relative to source data + * @param blockWidth width of block, in pixels + * @param blockHeight height of block in pixels + * @param blockData the data for the block + * @param xRaster coordinate of raster relative to source data + * @param yRaster coordinate of raster relative to source data + * @param rasterWidth width of the raster (salways smaller than source data) + * @param rasterHeight height of the raster (always smaller than source + * data) + * @param rasterData the raster data. + */ + void transferBlockToRaster(int xBlock, int yBlock, + int blockWidth, int blockHeight, int blockData[], + int xRaster, int yRaster, + int rasterWidth, int rasterHeight, float[] rasterData) { + + // xR0, yR0 are the coordinates within the raster (upper-left corner) + // xR1, yR1 are ONE PAST the coordinates of the lower-right corner + int xR0 = xBlock - xRaster; // xR0, yR0 coordinates relative to + int yR0 = yBlock - yRaster; // the raster + int xR1 = xR0 + blockWidth; + int yR1 = yR0 + blockHeight; + if (xR0 < 0) { + xR0 = 0; + } + if (yR0 < 0) { + yR0 = 0; + } + if (xR1 > rasterWidth) { + xR1 = rasterWidth; + } + if (yR1 > rasterHeight) { + yR1 = rasterHeight; + } + + // Recall that the above logic may have adjusted xR0, xY0 so that + // they are not necessrily point to the source pixel at xRaster, yRaster Review comment: s/necessrily/necessrily ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/ImageDataReader.java ########## @@ -14,6 +14,96 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + + /* + * Implementation Notes: + * + * Additional implementation notes are given in: + * DataReaderStrips.java + * DataReaderTiled.java + * + * The TIFF Floating-Point Formats ---------------------------------- + * In addition to providing images, TIFF files can supply data in the + * form of numerical values. As of March 2020 the Commons Imaging library + * was extended to support some floating-point data formats. + * Unfortunately, the floating-point format allows for a lot of different + * variations and only the most widely used of these are currently supported. + * At the time of implementation, only a small set of data products were + * available. Thus it is likely that developers will wish to extend this capability + * as additional test data become available. When implementing extensions + * to this logic, developers are reminder that image processing requires + * access to literally millions of pixels, so attention to performance + * is essential to a successful implementation (please see the notes in + * DataReaderStrips.java for more information). + * The TIFF floating-point implementation is very poorly documented. + * So these notes are included to provide clarification on at least + * some aspects of the format. + * + * The Predictor==3 Case + * TIFF specifies an extension for a predictor that is intended to + * improve data compression ratios for floating-point values. This + * predictor is specified using the TIFF predictor TAG with a value of 3 + * (see TIFF Technical Note 3, April 8, 2005). Consider a 4-byte floating + * point value given in IEEE-754 format. Let f3 be the high-order byte, + * with f2 the next highest, followed by f1, and f0 for the + * low-order byte. This designation shoulod not be confused with the Review comment: s/shoulod/should ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/photometricinterpreters/fp/PhotometricInterpreterFloat.java ########## @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp; + +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.common.ImageBuilder; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; + +/** + * Implements a custom photometric interpreter that can be supplied by + * applications in order to render Java images from real-valued TIFF data + * products. Most TIFF files include a specification for a "photometric + * interpreter" that implements logic for transforming the raw data in a TIFF + * file to a rendered image. But the TIFF standard does not include a + * specification for a photometric interpreter that can be used for rendering + * floating-point data. TIFF files are sometimes used to specify non-image data + * as a floating-point raster. This approach is particularly common in GeoTIFF + * files (TIFF files that contain tags for supporting geospatial reference + * metadata for Geographic Information Systems). Because of the limits of the + * stock photometric interpreters, most floating-point TIFF files to not produce + * useful images. + * <p> + * This class allows an Apache Commons implementation to construct and specify a + * custom photometric interpreter when reading from a TIFF file. Applications + * may supply their own palette that maps real-valued data to specified colors. + * <p> + * This class provides two constructors: + * <ol> + * <li>A simple constructor to support gray scales</li> + * <li>A constructor to support a color palette (with potential + * interpolation)</li> + * </ol> + * <p> + * To use this class, an application must access the TIFF file using the + * low-level, TIFF-specific API provided by the Apache Commons Imaging library. + * + */ +public class PhotometricInterpreterFloat extends PhotometricInterpreter { + + ArrayList<IPaletteEntry> rangePaletteEntries = new ArrayList<>(); + ArrayList<IPaletteEntry> singleValuePaletteEntries = new ArrayList<>(); + + float minFound = Float.POSITIVE_INFINITY; + float maxFound = Float.NEGATIVE_INFINITY; + int xMin; + int yMin; + int xMax; + int yMax; + + double sumFound; + int nFound; + + /** + * Constructs a photometric interpreter that will produce a gray scale + * linearly distributed across the RGB color space for values in the range + * valueBlack to valueWhite. Note that the two values may be given in either + * ascending order or descending order, but they must not be equal. Infinite + * values will not result in proper numerical computations. + * + * @param valueBlack the value associated with the dark side of the gray + * scale + * @param valueWhite the value associated with the light side of the gray + * scale + */ + public PhotometricInterpreterFloat( + float valueBlack, float valueWhite) { + // The abstract base class requires that the following fields + // be set in the constructor: + // samplesPerPixel (int) + // bits per sample (array of type int[samplesPerPixel]) + // predictor (int, not used by this class) + // width (int) + // height (int) + super( + 1, + new int[]{32}, // bits per sample + 0, // not used by this class + 32, // pro forma width value Review comment: s/forma/format ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/photometricinterpreters/fp/PaletteEntryForRange.java ########## @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp; + +import java.awt.Color; + +/** + * Provides a palette entry for colors associated with a range of values. The + * return value will be interpolated between the minimum and maximum value for + * this entry. + * <p> + * In keeping with the conventions of many Geographic Information Systems (GIS) + * and art applications, this instance "covered" values in the range v0 ≤ f + * < v1. Thus, a value that exactly matches the upper bound of the range is + * not considered "covered". + */ +public class PaletteEntryForRange implements IPaletteEntry { + + private final float v0; + private final float v1; + private final float deltaV; + private final float r0; + private final float r1; + private final float g0; + private final float g1; + private final float b0; + private final float b1; + private final float a0; + private final float a1; + private final float deltaA; + private final float deltaR; + private final float deltaG; + private final float deltaB; + + /** + * Constructs a palette entry for the range of values v0 ≤ f < v1. The + * return color value will be interpolated between the two specified colors. + * + * @param v0 the lower bounds (inclusive) of the covered range of values + * @param v1 the upper bounds (non-inclusive) of the covered range of value + * @param color0 the color assigned to value v0 + * @param color1 the color assigned to value v1 + */ + public PaletteEntryForRange(float v0, float v1, Color color0, Color color1) { + this.v0 = v0; + this.v1 = v1; + deltaV = v1 - v0; + // check for range volation + if (deltaV <= 0 || Float.isNaN(deltaV)) { + throw new IllegalArgumentException("Specified values must be v0<v1"); + } + if (color0 == null || color1 == null) { + throw new IllegalArgumentException("Null colors not allowed"); + } + int argb0 = color0.getRGB(); + a0 = (argb0 >> 24) & 0xff; + r0 = (argb0 >> 16) & 0xff; + g0 = (argb0 >> 8) & 0xff; + b0 = argb0 & 0xff; + + int argb1 = color1.getRGB(); + a1 = (argb1 >> 24) & 0xff; + r1 = (argb1 >> 16) & 0xff; + g1 = (argb1 >> 8) & 0xff; + b1 = argb1 & 0xff; + + deltaA = a1 - a0; + deltaR = r1 - r0; + deltaG = g1 - g0; + deltaB = b1 - b0; + } + + /** + * Constructs a palette entry for the range of values v0 ≤ f < v1. A + * single color will be returned for all values in range + * + * @param v0 the lower bounds (inclusive) of the covered range of values + * @param v1 the upper bounds (non-inclusive) of the covered range of value + * @param color the color assigned to value v0 + */ + public PaletteEntryForRange(float v0, float v1, Color color) { + this.v0 = v0; + this.v1 = v1; + deltaV = v1 - v0; + // check for range volation Review comment: s/volation/violation ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/photometricinterpreters/fp/PhotometricInterpreterFloat.java ########## @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp; + +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.common.ImageBuilder; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; + +/** + * Implements a custom photometric interpreter that can be supplied by + * applications in order to render Java images from real-valued TIFF data + * products. Most TIFF files include a specification for a "photometric + * interpreter" that implements logic for transforming the raw data in a TIFF + * file to a rendered image. But the TIFF standard does not include a + * specification for a photometric interpreter that can be used for rendering + * floating-point data. TIFF files are sometimes used to specify non-image data + * as a floating-point raster. This approach is particularly common in GeoTIFF + * files (TIFF files that contain tags for supporting geospatial reference + * metadata for Geographic Information Systems). Because of the limits of the + * stock photometric interpreters, most floating-point TIFF files to not produce + * useful images. + * <p> + * This class allows an Apache Commons implementation to construct and specify a + * custom photometric interpreter when reading from a TIFF file. Applications + * may supply their own palette that maps real-valued data to specified colors. + * <p> + * This class provides two constructors: + * <ol> + * <li>A simple constructor to support gray scales</li> + * <li>A constructor to support a color palette (with potential + * interpolation)</li> + * </ol> + * <p> + * To use this class, an application must access the TIFF file using the + * low-level, TIFF-specific API provided by the Apache Commons Imaging library. + * + */ +public class PhotometricInterpreterFloat extends PhotometricInterpreter { + + ArrayList<IPaletteEntry> rangePaletteEntries = new ArrayList<>(); + ArrayList<IPaletteEntry> singleValuePaletteEntries = new ArrayList<>(); + + float minFound = Float.POSITIVE_INFINITY; + float maxFound = Float.NEGATIVE_INFINITY; + int xMin; + int yMin; + int xMax; + int yMax; + + double sumFound; + int nFound; + + /** + * Constructs a photometric interpreter that will produce a gray scale + * linearly distributed across the RGB color space for values in the range + * valueBlack to valueWhite. Note that the two values may be given in either + * ascending order or descending order, but they must not be equal. Infinite + * values will not result in proper numerical computations. + * + * @param valueBlack the value associated with the dark side of the gray + * scale + * @param valueWhite the value associated with the light side of the gray + * scale + */ + public PhotometricInterpreterFloat( + float valueBlack, float valueWhite) { + // The abstract base class requires that the following fields + // be set in the constructor: + // samplesPerPixel (int) + // bits per sample (array of type int[samplesPerPixel]) + // predictor (int, not used by this class) + // width (int) + // height (int) + super( + 1, + new int[]{32}, // bits per sample + 0, // not used by this class + 32, // pro forma width value + 32 // pro format height value + ); + + + if (valueWhite > valueBlack) { + PaletteEntryForRange entry + = new PaletteEntryForRange(valueBlack, valueWhite, Color.black, Color.white); + rangePaletteEntries.add(entry); + } else { + PaletteEntryForRange entry + = new PaletteEntryForRange(valueWhite, valueBlack, Color.white, Color.black); + rangePaletteEntries.add(entry); + } + } + + /** + * Constructs a photometric interpreter that will use the specified palette + * to assign colors to floating-point values. + * + * @param paletteEntries a valid, non-empty list of palette entries + */ + public PhotometricInterpreterFloat(List<IPaletteEntry> paletteEntries) { + // The abstract base class requires that the following fields + // be set in the constructor: + // samplesPerPixel (int) + // bits per sample (array of type int[samplesPerPixel]) + // predictor (int, not used by this class) + // width (int) + // height (int) + super( + 1, + new int[]{32}, // bits per sample + 0, // not used by this class + 32, // pro forma width value Review comment: s/forma/format ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/photometricinterpreters/fp/PaletteEntryForRange.java ########## @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp; + +import java.awt.Color; + +/** + * Provides a palette entry for colors associated with a range of values. The + * return value will be interpolated between the minimum and maximum value for + * this entry. + * <p> + * In keeping with the conventions of many Geographic Information Systems (GIS) + * and art applications, this instance "covered" values in the range v0 ≤ f + * < v1. Thus, a value that exactly matches the upper bound of the range is + * not considered "covered". + */ +public class PaletteEntryForRange implements IPaletteEntry { + + private final float v0; + private final float v1; + private final float deltaV; + private final float r0; + private final float r1; + private final float g0; + private final float g1; + private final float b0; + private final float b1; + private final float a0; + private final float a1; + private final float deltaA; + private final float deltaR; + private final float deltaG; + private final float deltaB; + + /** + * Constructs a palette entry for the range of values v0 ≤ f < v1. The + * return color value will be interpolated between the two specified colors. + * + * @param v0 the lower bounds (inclusive) of the covered range of values + * @param v1 the upper bounds (non-inclusive) of the covered range of value + * @param color0 the color assigned to value v0 + * @param color1 the color assigned to value v1 + */ + public PaletteEntryForRange(float v0, float v1, Color color0, Color color1) { + this.v0 = v0; + this.v1 = v1; + deltaV = v1 - v0; + // check for range volation Review comment: s/volation/violation ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/photometricinterpreters/fp/PhotometricInterpreterFloat.java ########## @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp; + +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.common.ImageBuilder; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; + +/** + * Implements a custom photometric interpreter that can be supplied by + * applications in order to render Java images from real-valued TIFF data + * products. Most TIFF files include a specification for a "photometric + * interpreter" that implements logic for transforming the raw data in a TIFF + * file to a rendered image. But the TIFF standard does not include a + * specification for a photometric interpreter that can be used for rendering + * floating-point data. TIFF files are sometimes used to specify non-image data + * as a floating-point raster. This approach is particularly common in GeoTIFF + * files (TIFF files that contain tags for supporting geospatial reference + * metadata for Geographic Information Systems). Because of the limits of the + * stock photometric interpreters, most floating-point TIFF files to not produce + * useful images. + * <p> + * This class allows an Apache Commons implementation to construct and specify a + * custom photometric interpreter when reading from a TIFF file. Applications + * may supply their own palette that maps real-valued data to specified colors. + * <p> + * This class provides two constructors: + * <ol> + * <li>A simple constructor to support gray scales</li> + * <li>A constructor to support a color palette (with potential + * interpolation)</li> + * </ol> + * <p> + * To use this class, an application must access the TIFF file using the + * low-level, TIFF-specific API provided by the Apache Commons Imaging library. + * + */ +public class PhotometricInterpreterFloat extends PhotometricInterpreter { + + ArrayList<IPaletteEntry> rangePaletteEntries = new ArrayList<>(); + ArrayList<IPaletteEntry> singleValuePaletteEntries = new ArrayList<>(); + + float minFound = Float.POSITIVE_INFINITY; + float maxFound = Float.NEGATIVE_INFINITY; + int xMin; + int yMin; + int xMax; + int yMax; + + double sumFound; + int nFound; + + /** + * Constructs a photometric interpreter that will produce a gray scale + * linearly distributed across the RGB color space for values in the range + * valueBlack to valueWhite. Note that the two values may be given in either + * ascending order or descending order, but they must not be equal. Infinite + * values will not result in proper numerical computations. + * + * @param valueBlack the value associated with the dark side of the gray + * scale + * @param valueWhite the value associated with the light side of the gray + * scale + */ + public PhotometricInterpreterFloat( + float valueBlack, float valueWhite) { + // The abstract base class requires that the following fields + // be set in the constructor: + // samplesPerPixel (int) + // bits per sample (array of type int[samplesPerPixel]) + // predictor (int, not used by this class) + // width (int) + // height (int) + super( + 1, + new int[]{32}, // bits per sample + 0, // not used by this class + 32, // pro forma width value + 32 // pro format height value + ); + + + if (valueWhite > valueBlack) { + PaletteEntryForRange entry + = new PaletteEntryForRange(valueBlack, valueWhite, Color.black, Color.white); + rangePaletteEntries.add(entry); + } else { + PaletteEntryForRange entry + = new PaletteEntryForRange(valueWhite, valueBlack, Color.white, Color.black); + rangePaletteEntries.add(entry); + } + } + + /** + * Constructs a photometric interpreter that will use the specified palette + * to assign colors to floating-point values. + * + * @param paletteEntries a valid, non-empty list of palette entries + */ + public PhotometricInterpreterFloat(List<IPaletteEntry> paletteEntries) { + // The abstract base class requires that the following fields + // be set in the constructor: + // samplesPerPixel (int) + // bits per sample (array of type int[samplesPerPixel]) + // predictor (int, not used by this class) + // width (int) + // height (int) + super( + 1, + new int[]{32}, // bits per sample + 0, // not used by this class + 32, // pro forma width value + 32 // pro format height value + ); + + if (paletteEntries == null || paletteEntries.isEmpty()) { + throw new IllegalArgumentException( + "Palette entries list must be non-null and non-empty"); + } + + for (IPaletteEntry entry : paletteEntries) { + if (entry.coversSingleEntry()) { + singleValuePaletteEntries.add(entry); + } else { + rangePaletteEntries.add(entry); + } + } + + Comparator<IPaletteEntry> comparator = new Comparator<IPaletteEntry>() { + @Override + public int compare(IPaletteEntry o1, IPaletteEntry o2) { + if (o1.getLowerBound() == o2.getLowerBound()) { + return Double.compare(o1.getUpperBound(), o2.getUpperBound()); + } + return Double.compare(o1.getLowerBound(), o2.getLowerBound()); + } + }; Review comment: Java 8 lambda ``` Comparator<IPaletteEntry> comparator = (o1, o2) -> { if (o1.getLowerBound() == o2.getLowerBound()) { return Double.compare(o1.getUpperBound(), o2.getUpperBound()); } return Double.compare(o1.getLowerBound(), o2.getLowerBound()); }; ``` ########## File path: src/test/java/org/apache/commons/imaging/examples/tiff/ExampleReadFloatingPointData.java ########## @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.examples.tiff; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteOrder; Review comment: Unused import ########## File path: src/test/java/org/apache/commons/imaging/examples/tiff/ExampleReadFloatingPointData.java ########## @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.examples.tiff; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import javax.imageio.ImageIO; +import org.apache.commons.imaging.FormatCompliance; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.common.ImageBuilder; +import org.apache.commons.imaging.common.bytesource.ByteSourceFile; +import org.apache.commons.imaging.formats.tiff.TiffContents; +import org.apache.commons.imaging.formats.tiff.TiffDirectory; +import org.apache.commons.imaging.formats.tiff.TiffRasterData; +import org.apache.commons.imaging.formats.tiff.TiffRasterStatistics; +import org.apache.commons.imaging.formats.tiff.TiffReader; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp.IPaletteEntry; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp.PaletteEntryForRange; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp.PaletteEntryForValue; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp.PhotometricInterpreterFloat; + +/** + * A simple example application that reads the content of a TIFF file containing + * floating-point data and extracts its content. TIFF files are * sometimes used to store non-image information for scientific and geophysical + * data products, including terrestrial elevation and ocean depth data. + * + */ +public class ExampleReadFloatingPointData { Review comment: Used this file to experiment with the new code. Excellent example!!! :clap: :clap: :clap: Brilliant! Thanks a lot for this. ########## File path: src/main/java/org/apache/commons/imaging/formats/tiff/photometricinterpreters/fp/PhotometricInterpreterFloat.java ########## @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.tiff.photometricinterpreters.fp; + +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.common.ImageBuilder; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; + +/** + * Implements a custom photometric interpreter that can be supplied by + * applications in order to render Java images from real-valued TIFF data + * products. Most TIFF files include a specification for a "photometric + * interpreter" that implements logic for transforming the raw data in a TIFF + * file to a rendered image. But the TIFF standard does not include a + * specification for a photometric interpreter that can be used for rendering + * floating-point data. TIFF files are sometimes used to specify non-image data + * as a floating-point raster. This approach is particularly common in GeoTIFF + * files (TIFF files that contain tags for supporting geospatial reference + * metadata for Geographic Information Systems). Because of the limits of the + * stock photometric interpreters, most floating-point TIFF files to not produce + * useful images. + * <p> + * This class allows an Apache Commons implementation to construct and specify a + * custom photometric interpreter when reading from a TIFF file. Applications + * may supply their own palette that maps real-valued data to specified colors. + * <p> + * This class provides two constructors: + * <ol> + * <li>A simple constructor to support gray scales</li> + * <li>A constructor to support a color palette (with potential + * interpolation)</li> + * </ol> + * <p> + * To use this class, an application must access the TIFF file using the + * low-level, TIFF-specific API provided by the Apache Commons Imaging library. + * + */ +public class PhotometricInterpreterFloat extends PhotometricInterpreter { + + ArrayList<IPaletteEntry> rangePaletteEntries = new ArrayList<>(); + ArrayList<IPaletteEntry> singleValuePaletteEntries = new ArrayList<>(); + + float minFound = Float.POSITIVE_INFINITY; + float maxFound = Float.NEGATIVE_INFINITY; + int xMin; + int yMin; + int xMax; + int yMax; + + double sumFound; + int nFound; + + /** + * Constructs a photometric interpreter that will produce a gray scale + * linearly distributed across the RGB color space for values in the range + * valueBlack to valueWhite. Note that the two values may be given in either + * ascending order or descending order, but they must not be equal. Infinite + * values will not result in proper numerical computations. + * + * @param valueBlack the value associated with the dark side of the gray + * scale + * @param valueWhite the value associated with the light side of the gray + * scale + */ + public PhotometricInterpreterFloat( + float valueBlack, float valueWhite) { + // The abstract base class requires that the following fields + // be set in the constructor: + // samplesPerPixel (int) + // bits per sample (array of type int[samplesPerPixel]) + // predictor (int, not used by this class) + // width (int) + // height (int) + super( + 1, + new int[]{32}, // bits per sample + 0, // not used by this class + 32, // pro forma width value + 32 // pro format height value + ); + + + if (valueWhite > valueBlack) { + PaletteEntryForRange entry + = new PaletteEntryForRange(valueBlack, valueWhite, Color.black, Color.white); + rangePaletteEntries.add(entry); + } else { + PaletteEntryForRange entry + = new PaletteEntryForRange(valueWhite, valueBlack, Color.white, Color.black); + rangePaletteEntries.add(entry); + } + } + + /** + * Constructs a photometric interpreter that will use the specified palette + * to assign colors to floating-point values. + * + * @param paletteEntries a valid, non-empty list of palette entries + */ + public PhotometricInterpreterFloat(List<IPaletteEntry> paletteEntries) { + // The abstract base class requires that the following fields + // be set in the constructor: + // samplesPerPixel (int) + // bits per sample (array of type int[samplesPerPixel]) + // predictor (int, not used by this class) + // width (int) + // height (int) + super( + 1, + new int[]{32}, // bits per sample + 0, // not used by this class + 32, // pro forma width value + 32 // pro format height value + ); + + if (paletteEntries == null || paletteEntries.isEmpty()) { + throw new IllegalArgumentException( + "Palette entries list must be non-null and non-empty"); + } + + for (IPaletteEntry entry : paletteEntries) { + if (entry.coversSingleEntry()) { + singleValuePaletteEntries.add(entry); + } else { + rangePaletteEntries.add(entry); + } + } + + Comparator<IPaletteEntry> comparator = new Comparator<IPaletteEntry>() { + @Override + public int compare(IPaletteEntry o1, IPaletteEntry o2) { + if (o1.getLowerBound() == o2.getLowerBound()) { + return Double.compare(o1.getUpperBound(), o2.getUpperBound()); + } + return Double.compare(o1.getLowerBound(), o2.getLowerBound()); + } + }; + + Collections.sort(rangePaletteEntries, comparator); + Collections.sort(singleValuePaletteEntries, comparator); Review comment: ``` rangePaletteEntries.sort(comparator); singleValuePaletteEntries.sort(comparator); ``` ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org