| /* |
| * 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.sis.storage.geotiff; |
| |
| import java.io.IOException; |
| import java.text.ParseException; |
| import java.util.List; |
| import java.util.Arrays; |
| import java.util.Locale; |
| import java.util.Optional; |
| import java.util.logging.Level; |
| import java.util.logging.LogRecord; |
| import java.nio.charset.Charset; |
| import javax.measure.Unit; |
| import javax.measure.quantity.Length; |
| import org.opengis.metadata.citation.DateType; |
| import org.opengis.util.FactoryException; |
| import org.opengis.util.GenericName; |
| import org.opengis.util.InternationalString; |
| import org.apache.sis.internal.geotiff.Resources; |
| import org.apache.sis.internal.storage.MetadataBuilder; |
| import org.apache.sis.internal.storage.AbstractGridResource; |
| import org.apache.sis.internal.storage.io.ChannelDataInput; |
| import org.apache.sis.internal.util.UnmodifiableArrayList; |
| import org.apache.sis.storage.DataStoreException; |
| import org.apache.sis.storage.DataStoreContentException; |
| import org.apache.sis.coverage.grid.GridCoverage; |
| import org.apache.sis.coverage.grid.GridGeometry; |
| import org.apache.sis.coverage.grid.GridExtent; |
| import org.apache.sis.coverage.SampleDimension; |
| import org.apache.sis.util.resources.Vocabulary; |
| import org.apache.sis.math.Vector; |
| import org.apache.sis.measure.Units; |
| |
| |
| /** |
| * An Image File Directory (FID) in a TIFF image. |
| * |
| * @author Rémi Maréchal (Geomatys) |
| * @author Alexis Manin (Geomatys) |
| * @author Johann Sorel (Geomatys) |
| * @author Thi Phuong Hao Nguyen (VNSC) |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * |
| * @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags.html">TIFF Tag Reference</a> |
| * |
| * @since 0.8 |
| * @module |
| */ |
| final class ImageFileDirectory extends AbstractGridResource { |
| /** |
| * Possible value for the {@link #tileTagFamily} field. That field tells whether image tiling |
| * was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*} family. |
| */ |
| private static final byte TILE = 1, STRIP = 2; |
| |
| /** |
| * Possible value for {@link #sampleFormat} specifying how to interpret each data sample in a pixel. |
| * Those values are not necessarily the same than the ones documented in {@link Tags#SampleFormat}. |
| * Default value is {@link #UNSIGNED}. |
| */ |
| private static final byte SIGNED = 1, UNSIGNED = 0, FLOAT = 3; |
| |
| /** |
| * The GeoTIFF reader which contain this {@code ImageFileDirectory}. |
| * Used for fetching information like the input channel and where to report warnings. |
| */ |
| private final Reader reader; |
| |
| /** |
| * The identifier as a sequence number in the namespace of the {@link GeoTiffStore}. |
| * The first image has the sequence number "1". |
| * |
| * @see #getIdentifier() |
| */ |
| private final GenericName identifier; |
| |
| /** |
| * {@code true} if this {@code ImageFileDirectory} has not yet read all deferred entries. |
| * When this flag is {@code true}, the {@code ImageFileDirectory} is not yet ready for use. |
| */ |
| boolean hasDeferredEntries; |
| |
| /** |
| * The size of the image described by this FID, or -1 if the information has not been found. |
| * The image may be much bigger than the memory capacity, in which case the image shall be tiled. |
| * |
| * <p><b>Note:</b> |
| * the {@link #imageHeight} attribute is named {@code ImageLength} in TIFF specification.</p> |
| */ |
| private long imageWidth = -1, imageHeight = -1; |
| |
| /** |
| * The size of each tile, or -1 if the information has not be found. |
| * Tiles shall be small enough for fitting in memory, typically in a {@link java.awt.image.Raster} object. |
| * The TIFF specification requires that tile width and height must be a multiple of 16, but the SIS reader |
| * implementation works for any size. Tiles need not be square. |
| * |
| * <p>Assuming integer arithmetic, the number of tiles in an image can be computed as below |
| * (these computed values are not TIFF fields):</p> |
| * |
| * {@preformat math |
| * tilesAcross = (imageWidth + tileWidth - 1) / tileWidth |
| * tilesDown = (imageHeight + tileHeight - 1) / tileHeight |
| * tilesPerImage = tilesAcross * tilesDown |
| * } |
| * |
| * Note that {@link #imageWidth} can be less than {@code tileWidth} and/or {@link #imageHeight} can be less |
| * than {@code tileHeight}. Such case means that the tiles are too large or that the tiled image is too small, |
| * neither of which is recommended. |
| * |
| * <p><b>Note:</b> |
| * the {@link #tileHeight} attribute is named {@code TileLength} in TIFF specification.</p> |
| * |
| * <div class="section">Strips considered as tiles</div> |
| * The TIFF specification also defines a {@code RowsPerStrip} tag, which is equivalent to the |
| * height of tiles having the same width than the image. While the TIFF specification handles |
| * "tiles" and "strips" separately, Apache SIS handles strips as a special kind of tiles where |
| * only {@code tileHeight} is specified and {@code tileWidth} defaults to {@link #imageWidth}. |
| */ |
| private int tileWidth = -1, tileHeight = -1; |
| |
| /** |
| * For each tile, the byte offset of that tile, as compressed and stored on disk. |
| * The offset is specified with respect to the beginning of the TIFF file. |
| * Each tile has a location independent of the locations of other tiles |
| * |
| * <p>Offsets are ordered left-to-right and top-to-bottom. if {@link #isPlanar} is {@code true} |
| * (i.e. components are stored in separate “component planes”), then the offsets for the first |
| * component plane are stored first, followed by all the offsets for the second component plane, |
| * and so on.</p> |
| * |
| * <div class="section">Strips considered as tiles</div> |
| * The TIFF specification also defines a {@code StripOffsets} tag, which contains the byte offset |
| * of each strip. In Apache SIS implementation, strips are considered as a special kind of tiles |
| * having a width equals to {@link #imageWidth}. |
| */ |
| private Vector tileOffsets; |
| |
| /** |
| * For each tile, the number of (compressed) bytes in that tile. |
| * See {@link #tileOffsets} for a description of how the byte counts are ordered. |
| * |
| * <div class="section">Strips considered as tiles</div> |
| * The TIFF specification also defines a {@code RowsPerStrip} tag, which is the number |
| * of bytes in the strip after compression. In Apache SIS implementation, strips are |
| * considered as a special kind of tiles having a width equals to {@link #imageWidth}. |
| */ |
| private Vector tileByteCounts; |
| |
| /** |
| * Whether the tiling was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*} |
| * family of tags. Value can be {@link #TILE}, {@link #STRIP} or 0 if unspecified. This field is used |
| * for error detection since Each TIFF file shall use exactly one family of tags. |
| */ |
| private byte tileTagFamily; |
| |
| /** |
| * If {@code true}, the components are stored in separate “component planes”. |
| * The default is {@code false}, which stands for the "chunky" format |
| * (for example RGB data stored as RGBRGBRGB). |
| */ |
| private boolean isPlanar; |
| |
| /** |
| * How to interpret each data sample in a pixel. |
| * Possible values are {@link #SIGNED}, {@link #UNSIGNED} or {@link #FLOAT}. |
| */ |
| private byte sampleFormat; |
| |
| /** |
| * Whether the bit order should be reversed. This boolean value is determined from the {@code FillOrder} TIFF tag. |
| * |
| * <ul> |
| * <li>Value 1 (mapped to {@code false}) means that pixels with lower column values are stored in the |
| * higher-order bits of the byte. This is the default value.</li> |
| * <li>Value 2 (mapped to {@code true}) means that pixels with lower column values are stored in the |
| * lower-order bits of the byte. In practice, this order is very uncommon and is not recommended.</li> |
| * </ul> |
| * |
| * Value 1 is mapped to {@code false} and 2 is mapped to {@code true}. |
| */ |
| private boolean reverseBitsOrder; |
| |
| /** |
| * Number of bits per component. |
| * The TIFF specification allows a different number of bits per component for each component corresponding to a pixel. |
| * For example, RGB color data could use a different number of bits per component for each of the three color planes. |
| * However, current Apache SIS implementation requires that all components have the same {@code BitsPerSample} value. |
| */ |
| private short bitsPerSample; |
| |
| /** |
| * The number of components per pixel. |
| * The {@code samplesPerPixel} value is usually 1 for bilevel, grayscale and palette-color images, |
| * and 3 for RGB images. If this value is higher, then the {@code ExtraSamples} TIFF tag should |
| * give an indication of the meaning of the additional channels. |
| */ |
| private short samplesPerPixel; |
| |
| /** |
| * Specifies that each pixel has {@code extraSamples.size()} extra components whose interpretation is defined |
| * by one of the values listed below. When this field is used, the {@link #samplesPerPixel} field has a value |
| * greater than what the {@link #photometricInterpretation} field suggests. For example, full-color RGB data |
| * normally has {@link #samplesPerPixel} = 3. If {@code samplesPerPixel} is greater than 3, then this |
| * {@code extraSamples} field describes the meaning of the extra samples. If {@code samplesPerPixel} is, |
| * say, 5 then this {@code extraSamples} field will contain 2 values, one for each extra sample. |
| * |
| * <p>Extra components that are present must be stored as the last components in each pixel. |
| * For example, if {@code samplesPerPixel} is 4 and there is 1 extra component, then it is |
| * located in the last component location in each pixel.</p> |
| * |
| * <p>ExtraSamples is typically used to include non-color information, such as opacity, in an image. |
| * The possible values for each item are:</p> |
| * |
| * <ul> |
| * <li>0 = Unspecified data.</li> |
| * <li>1 = Associated alpha data (with pre-multiplied color).</li> |
| * <li>2 = Unassociated alpha data.</li> |
| * </ul> |
| * |
| * Associated alpha is generally interpreted as true transparency information. Indeed, the original color |
| * values are lost in the case of complete transparency, and rounded in the case of partial transparency. |
| * Also, associated alpha is only logically possible as the single extra channel. |
| * Unassociated alpha channels, on the other hand, can be used to encode a number of independent masks. |
| * The original color data is preserved without rounding. Any number of unassociated alpha channels can |
| * accompany an image. |
| * |
| * <p>If an extra sample is used to encode information that has little or nothing to do with alpha, |
| * then {@code extraSample} = 0 ({@code EXTRASAMPLE_UNSPECIFIED}) is recommended.</p> |
| */ |
| private Vector extraSamples; |
| |
| /** |
| * The color space of the image data, or -1 if unspecified. |
| * |
| * <table> |
| * <caption>Color space codes</caption> |
| * <tr><th>Value</th> <th>Label</th> <th>Description</th></tr> |
| * <tr><td>0</td> <td>WhiteIsZero</td> <td>For bilevel and grayscale images. 0 is imaged as white.</td></tr> |
| * <tr><td>1</td> <td>BlackIsZero</td> <td>For bilevel and grayscale images. 0 is imaged as black.</td></tr> |
| * <tr><td>2</td> <td>RGB</td> <td>RGB value of (0,0,0) represents black, and (255,255,255) represents white.</td></tr> |
| * <tr><td>3</td> <td>PaletteColor</td> <td>The value of the component is used as an index into the RGB values of the {@link #colorMap}.</td></tr> |
| * <tr><td>4</td> <td>TransparencyMask</td> <td>Defines an irregularly shaped region of another image in the same TIFF file.</td></tr> |
| * </table> |
| */ |
| private byte photometricInterpretation = -1; |
| |
| /** |
| * A color map for palette color images ({@link #photometricInterpretation} = 3). |
| * This vector defines a Red-Green-Blue color map (often called a lookup table) for palette-color images. |
| * In a palette-color image, a pixel value is used to index into an RGB lookup table. For example, a |
| * palette-color pixel having a value of 0 would be displayed according to the 0th Red, Green, Blue triplet. |
| * |
| * <p>In a TIFF ColorMap, all the Red values come first, followed by all Green values, then all Blue values. |
| * The number of values for each color is 1 {@literal <<} {@link #bitsPerSample}. Therefore, the {@code ColorMap} |
| * vector for an 8-bit palette-color image would have 3 * 256 values. 0 represents the minimum intensity and 65535 |
| * represents the maximum intensity. Black is represented by 0,0,0 and white by 65535, 65535, 65535.</p> |
| * |
| * <p>{@code ColorMap} must be included in all palette-color images. |
| * In Specification Supplement 1, support was added for color maps containing other then RGB values. |
| * This scheme includes the {@code Indexed} tag, with value 1, and a {@link #photometricInterpretation} |
| * different from {@code PaletteColor}.</p> |
| */ |
| private Vector colorMap; |
| |
| /** |
| * The size of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. |
| * This field should be present only if {@code Threshholding} tag is 2 (an ordered dither or halftone |
| * technique has been applied to the image data). Special values: |
| * |
| * <ul> |
| * <li>-1 means that {@code Threshholding} is 1 or unspecified.</li> |
| * <li>-2 means that {@code Threshholding} is 2 but the matrix size has not yet been specified.</li> |
| * <li>-3 means that {@code Threshholding} is 3 (randomized process such as error diffusion).</li> |
| * </ul> |
| */ |
| private short cellWidth = -1, cellHeight = -1; |
| |
| /** |
| * The minimum or maximum sample value found in the image, with one value per band. |
| * May be a vector of length 1 if the same single value applies to all bands. |
| */ |
| private Vector minValues, maxValues; |
| |
| /** |
| * {@code true} if {@link #minValues} and {@link #maxValues} have been explicitly specified |
| * in the TIFF file, or {@code false} if they have been inferred from {@link #bitsPerSample}. |
| */ |
| private boolean isMinSpecified, isMaxSpecified; |
| |
| /** |
| * The number of pixels per {@link #resolutionUnit} in the {@link #imageWidth} and the {@link #imageHeight} |
| * directions, or {@link Double#NaN} is unspecified. Since ISO 19115 does not have separated resolution fields |
| * for image width and height, Apache SIS stores only the maximal value. |
| */ |
| private double resolution = Double.NaN; |
| |
| /** |
| * The unit of measurement for the {@linkplain #resolution} value, or {@code null} if none. |
| * A null value is used for images that may have a non-square aspect ratio, but no meaningful |
| * absolute dimensions. Default value for TIFF files is inch. |
| */ |
| private Unit<Length> resolutionUnit = Units.INCH; |
| |
| /** |
| * The compression method, or {@code null} if unknown. If the compression method is unknown |
| * or unsupported we can not read the image, but we still can read the metadata. |
| */ |
| private Compression compression; |
| |
| /** |
| * A helper class for building Coordinate Reference System and complete related metadata. |
| * Contains the following information: |
| * |
| * <ul> |
| * <li>{@link GridGeometryBuilder#keyDirectory}</li> |
| * <li>{@link GridGeometryBuilder#numericParameters}</li> |
| * <li>{@link GridGeometryBuilder#asciiParameters}</li> |
| * <li>{@link GridGeometryBuilder#modelTiePoints}</li> |
| * </ul> |
| * |
| * @see #getGridGeometry() |
| */ |
| private GridGeometryBuilder referencing; |
| |
| /** |
| * The sample dimensions, or {@code null} if not yet created. |
| * |
| * @see #getSampleDimensions() |
| */ |
| private List<SampleDimension> sampleDimensions; |
| |
| /** |
| * Returns {@link #referencing}, created when first needed. We delay its creation since |
| * this object is not needed for ordinary TIFF files (i.e. without the GeoTIFF extension). |
| */ |
| private GridGeometryBuilder referencing() { |
| if (referencing == null) { |
| referencing = new GridGeometryBuilder(reader); |
| } |
| return referencing; |
| } |
| |
| /** |
| * Creates a new image file directory. |
| * |
| * @param reader information about the input stream to read, the metadata and the character encoding. |
| * @param index the image index as a sequence number starting with 0 for the first image. |
| */ |
| ImageFileDirectory(final Reader reader, final int index) { |
| super(reader.owner.listeners()); |
| this.reader = reader; |
| identifier = reader.nameFactory.createLocalName(reader.owner.identifier, String.valueOf(index + 1)); |
| } |
| |
| /** |
| * Shortcut for a frequently requested information. |
| */ |
| private ChannelDataInput input() { |
| return reader.input; |
| } |
| |
| /** |
| * Shortcut for a frequently requested information. |
| */ |
| private String filename() { |
| return input().filename; |
| } |
| |
| /** |
| * Shortcut for a frequently requested information. |
| */ |
| private Charset encoding() { |
| return reader.owner.encoding; |
| } |
| |
| /** |
| * Returns the identifier as a sequence number in the namespace of the {@link GeoTiffStore}. |
| * The first image has the sequence number "1". |
| * |
| * @see #getMetadata() |
| */ |
| @Override |
| public Optional<GenericName> getIdentifier() { |
| return Optional.of(identifier); |
| } |
| |
| /** |
| * Adds the value read from the current position in the given stream for the entry identified |
| * by the given GeoTIFF tag. This method may store the value either in a field of this class, |
| * or directly in the {@link MetadataBuilder}. However in the later case, this method should |
| * not write anything under the {@code "metadata/contentInfo"} node. |
| * |
| * @param tag the GeoTIFF tag to decode. |
| * @param type the GeoTIFF type of the value to read. |
| * @param count the number of values to read. |
| * @return {@code null} on success, or the unrecognized value otherwise. |
| * @throws IOException if an error occurred while reading the stream. |
| * @throws ParseException if the value need to be parsed as date and the parsing failed. |
| * @throws NumberFormatException if the value need to be parsed as number and the parsing failed. |
| * @throws ArithmeticException if the value can not be represented in the expected Java type. |
| * @throws IllegalArgumentException if a value which was expected to be a singleton is not. |
| * @throws UnsupportedOperationException if the given type is {@link Type#UNDEFINED}. |
| * @throws DataStoreException if a logical error is found or an unsupported TIFF feature is used. |
| */ |
| Object addEntry(final short tag, final Type type, final long count) |
| throws IOException, ParseException, DataStoreException |
| { |
| switch (tag) { |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Essential information for being able to read the image at least as grayscale. //// |
| //// In Java2D, following information are needed for building the SampleModel. //// |
| //// //// |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * How the components of each pixel are stored. |
| * 1 = Chunky format. The component values for each pixel are stored contiguously (for example RGBRGBRGB). |
| * 2 = Planar format. For example one plane of Red components, one plane of Green and one plane if Blue. |
| */ |
| case Tags.PlanarConfiguration: { |
| final int value = type.readInt(input(), count); |
| switch (value) { |
| case 1: isPlanar = false; break; |
| case 2: isPlanar = true; break; |
| default: return value; // Cause a warning to be reported by the caller. |
| } |
| break; |
| } |
| /* |
| * The number of columns in the image, i.e., the number of pixels per row. |
| */ |
| case Tags.ImageWidth: { |
| imageWidth = type.readUnsignedLong(input(), count); |
| break; |
| } |
| /* |
| * The number of rows of pixels in the image. |
| */ |
| case Tags.ImageLength: { |
| imageHeight = type.readUnsignedLong(input(), count); |
| break; |
| } |
| /* |
| * The tile width in pixels. This is the number of columns in each tile. |
| */ |
| case Tags.TileWidth: { |
| setTileTagFamily(TILE); |
| tileWidth = type.readInt(input(), count); |
| break; |
| } |
| /* |
| * The tile length (height) in pixels. This is the number of rows in each tile. |
| */ |
| case Tags.TileLength: { |
| setTileTagFamily(TILE); |
| tileHeight = type.readInt(input(), count); |
| break; |
| } |
| /* |
| * The number of rows per strip. This is considered by SIS as a special kind of tiles. |
| * From this point of view, TileLength = RowPerStrip and TileWidth = ImageWidth. |
| */ |
| case Tags.RowsPerStrip: { |
| setTileTagFamily(STRIP); |
| tileHeight = type.readInt(input(), count); |
| break; |
| } |
| /* |
| * The tile length (height) in pixels. This is the number of rows in each tile. |
| */ |
| case Tags.TileOffsets: { |
| setTileTagFamily(TILE); |
| tileOffsets = type.readVector(input(), count); |
| break; |
| } |
| /* |
| * For each strip, the byte offset of that strip relative to the beginning of the TIFF file. |
| * In Apache SIS implementation, strips are considered as a special kind of tiles. |
| */ |
| case Tags.StripOffsets: { |
| setTileTagFamily(STRIP); |
| tileOffsets = type.readVector(input(), count); |
| break; |
| } |
| /* |
| * The tile width in pixels. This is the number of columns in each tile. |
| */ |
| case Tags.TileByteCounts: { |
| setTileTagFamily(TILE); |
| tileByteCounts = type.readVector(input(), count); |
| break; |
| } |
| /* |
| * For each strip, the number of bytes in the strip after compression. |
| * In Apache SIS implementation, strips are considered as a special kind of tiles. |
| */ |
| case Tags.StripByteCounts: { |
| setTileTagFamily(STRIP); |
| tileByteCounts = type.readVector(input(), count); |
| break; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Information that defines how the sample values are organized (their layout). //// |
| //// In Java2D, following information are needed for building the SampleModel. //// |
| //// //// |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * Compression scheme used on the image data. |
| */ |
| case Tags.Compression: { |
| final long value = type.readLong(input(), count); |
| compression = Compression.valueOf(value); |
| if (compression == null) { |
| return value; // Cause a warning to be reported by the caller. |
| } |
| break; |
| } |
| /* |
| * The logical order of bits within a byte. If this value is 2, then |
| * bits order shall be reversed in every bytes before decompression. |
| */ |
| case Tags.FillOrder: { |
| final int value = type.readInt(input(), count); |
| switch (value) { |
| case 1: reverseBitsOrder = false; break; |
| case 2: reverseBitsOrder = true; break; |
| default: return value; // Cause a warning to be reported by the caller. |
| } |
| break; |
| } |
| /* |
| * How to interpret each data sample in a pixel. The size of data samples is still |
| * specified by the BitsPerSample field. |
| */ |
| case Tags.SampleFormat: { |
| final int value = type.readInt(input(), count); |
| switch (value) { |
| default: return value; // Warning to be reported by the caller. |
| case 1: sampleFormat = UNSIGNED; break; // Unsigned integer data (default). |
| case 2: sampleFormat = SIGNED; break; // Two’s complement signed integer data. |
| case 3: sampleFormat = FLOAT; break; // IEEE floating point data. |
| case 4: warning(Level.WARNING, Resources.Keys.UndefinedDataFormat_1, filename()); break; |
| } |
| break; |
| } |
| /* |
| * Number of bits per component. The array length should be the number of components in a |
| * pixel (e.g. 3 for RGB values). Typically, all components have the same number of bits. |
| * But the TIFF specification allows different values. |
| */ |
| case Tags.BitsPerSample: { |
| final Vector values = type.readVector(input(), count); |
| /* |
| * The current implementation requires that all 'bitsPerSample' elements have the same value. |
| * This restriction may be revisited in future Apache SIS versions. |
| * Note: 'count' is never zero when this method is invoked, so we do not need to check bounds. |
| */ |
| bitsPerSample = values.shortValue(0); |
| final int length = values.size(); |
| for (int i = 1; i < length; i++) { |
| if (values.shortValue(i) != bitsPerSample) { |
| throw new DataStoreContentException(reader.resources().getString( |
| Resources.Keys.ConstantValueRequired_3, "BitsPerSample", filename(), values)); |
| } |
| } |
| break; |
| } |
| /* |
| * The number of components per pixel. Usually 1 for bilevel, grayscale, and palette-color images, |
| * and 3 for RGB images. Default value is 1. |
| */ |
| case Tags.SamplesPerPixel: { |
| samplesPerPixel = type.readShort(input(), count); |
| break; |
| } |
| /* |
| * Specifies that each pixel has N extra components. When this field is used, the SamplesPerPixel field |
| * has a value greater than the PhotometricInterpretation field suggests. For example, a full-color RGB |
| * image normally has SamplesPerPixel=3. If SamplesPerPixel is greater than 3, then the ExtraSamples field |
| * describes the meaning of the extra samples. It may be an alpha channel, but not necessarily. |
| */ |
| case Tags.ExtraSamples: { |
| extraSamples = type.readVector(input(), count); |
| break; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Information related to the color palette or the meaning of sample values. //// |
| //// In Java2D, following information are needed for building the ColorModel. //// |
| //// //// |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * The color space of the image data. |
| * 0 = WhiteIsZero. For bilevel and grayscale images: 0 is imaged as white. |
| * 1 = BlackIsZero. For bilevel and grayscale images: 0 is imaged as black. |
| * 2 = RGB. RGB value of (0,0,0) represents black, and (65535,65535,65535) represents white. |
| * 3 = Palette color. The value of the component is used as an index into the RGB values of the ColorMap. |
| * 4 = Transparency Mask the defines an irregularly shaped region of another image in the same TIFF file. |
| */ |
| case Tags.PhotometricInterpretation: { |
| final short value = type.readShort(input(), count); |
| if (value < 0 || value > Byte.MAX_VALUE) return value; |
| photometricInterpretation = (byte) value; |
| break; |
| } |
| /* |
| * The lookup table for palette-color images. This is represented by IndexColorModel in Java2D. |
| * Color space is RGB if PhotometricInterpretation is "PaletteColor", or another color space otherwise. |
| * In the RGB case, all the Red values come first, followed by all Green values, then all Blue values. |
| * The number of values for each color is (1 << BitsPerSample) where 0 represents the minimum intensity |
| * (black is 0,0,0) and 65535 represents the maximum intensity. |
| */ |
| case Tags.ColorMap: { |
| colorMap = type.readVector(input(), count); |
| break; |
| } |
| /* |
| * The minimum component value used. MinSampleValue is a single value that apply to all bands |
| * while SMinSampleValue lists separated values for each band. Default is 0. |
| */ |
| case Tags.MinSampleValue: |
| case Tags.SMinSampleValue: { |
| minValues = extremum(minValues, type.readVector(input(), count), false); |
| isMinSpecified = true; |
| break; |
| } |
| /* |
| * The maximum component value used. Default is {@code (1 << BitsPerSample) - 1}. |
| * This field is for statistical purposes and should not to be used to affect the |
| * visual appearance of an image, unless a map styling is applied. |
| */ |
| case Tags.MaxSampleValue: |
| case Tags.SMaxSampleValue: { |
| maxValues = extremum(maxValues, type.readVector(input(), count), true); |
| isMaxSpecified = true; |
| break; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Information useful for defining the image role in a multi-images context. //// |
| //// //// |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * A general indication of the kind of data contained in this subfile, mainly useful when there |
| * are multiple subfiles in a single TIFF file. This field is made up of a set of 32 flag bits. |
| * |
| * Bit 0 is 1 if the image is a reduced-resolution version of another image in this TIFF file. |
| * Bit 1 is 1 if the image is a single page of a multi-page image (see PageNumber). |
| * Bit 2 is 1 if the image defines a transparency mask for another image in this TIFF file (see PhotometricInterpretation). |
| * Bit 4 indicates MRC imaging model as described in ITU-T recommendation T.44 [T.44] (See ImageLayer tag) - RFC 2301. |
| */ |
| case Tags.NewSubfileType: { |
| // TODO |
| break; |
| } |
| /* |
| * Old version (now deprecated) of above NewSubfileType. |
| * 1 = full-resolution image data |
| * 2 = reduced-resolution image data |
| * 3 = a single page of a multi-page image (see PageNumber). |
| */ |
| case Tags.SubfileType: { |
| // TODO |
| break; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Information related to the Coordinate Reference System and the bounding box. //// |
| //// //// |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * References the "GeoKeys" needed for building the Coordinate Reference System. |
| * An array of unsigned SHORT values, which are primarily grouped into blocks of 4. |
| * The first 4 values are special, and contain GeoKey directory header information. |
| */ |
| case Tags.GeoKeyDirectory: { |
| referencing().keyDirectory = type.readVector(input(), count); |
| break; |
| } |
| /* |
| * Stores all of the 'double' valued GeoKeys, referenced by the GeoKeyDirectory. |
| */ |
| case Tags.GeoDoubleParams: { |
| referencing().numericParameters = type.readVector(input(), count); |
| break; |
| } |
| /* |
| * Stores all the characters referenced by the GeoKeyDirectory. Should contains exactly one string |
| * which will be splitted by CRSBuilder, but we allow an arbitrary amount as a paranoiac check. |
| * Note that TIFF files use 0 as the end delimiter in strings (C/C++ convention). |
| */ |
| case Tags.GeoAsciiParams: { |
| final String[] values = type.readString(input(), count, encoding()); |
| switch (values.length) { |
| case 0: break; |
| case 1: referencing().asciiParameters = values[0]; break; |
| default: referencing().asciiParameters = String.join("\u0000", values).concat("\u0000"); break; |
| } |
| break; |
| } |
| /* |
| * The orientation of the image with respect to the rows and columns. |
| * This is an integer numeroted from 1 to 7 inclusive (see TIFF specification for meaning). |
| */ |
| case Tags.Orientation: { |
| // TODO |
| break; |
| } |
| /* |
| * The "grid to CRS" conversion as a 4×4 matrix in row-major fashion. The third matrix row and |
| * the third matrix column may contain only zero values; this block does not reduce the number |
| * of dimensions from 3 to 2. |
| */ |
| case Tags.ModelTransformation: { |
| final Vector m = type.readVector(input(), count); |
| final int n; |
| switch (m.size()) { |
| case 6: // Assume 2D model with implicit [0 0 1] last row. |
| case 9: n = 3; break; // Assume 2D model with full 3×3 matrix. |
| case 12: // Assume 3D model with implicit [0 0 0 1] last row. |
| case 16: n = 4; break; // 3D model with full 4×4 matrix, as required by GeoTIFF spec. |
| default: return m; |
| } |
| referencing().setGridToCRS(m, n); |
| break; |
| } |
| /* |
| * The "grid to CRS" conversion with only the scale factor specified. This block sets the |
| * translation column to NaN, meaning that it will need to be computed from the tie point. |
| */ |
| case Tags.ModelPixelScaleTag: { |
| final Vector m = type.readVector(input(), count); |
| final int size = m.size(); |
| if (size < 2 || size > 3) { // Length should be exactly 3, but we make this reader tolerant. |
| return m; |
| } |
| referencing().setScaleFactors(m); |
| break; |
| } |
| /* |
| * The mapping from pixel coordinates to CRS coordinates as a sequence of (I,J,K, X,Y,Z) records. |
| */ |
| case Tags.ModelTiePoints: { |
| referencing().modelTiePoints = type.readVector(input(), count); |
| break; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Metadata for discovery purposes, conditions of use, etc. //// |
| //// Those metadata are not "critical" information for reading the image. //// |
| //// Should not write anything under 'metadata/contentInfo' node. //// |
| //// //// |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * A string that describes the subject of the image. |
| * For example, a user may wish to attach a comment such as "1988 company picnic" to an image. |
| */ |
| case Tags.ImageDescription: { |
| for (final String value : type.readString(input(), count, encoding())) { |
| reader.metadata.addTitle(value); |
| } |
| break; |
| } |
| /* |
| * Person who created the image. Some older TIFF files used this tag for storing |
| * Copyright information, but Apache SIS does not support this legacy practice. |
| */ |
| case Tags.Artist: { |
| for (final String value : type.readString(input(), count, encoding())) { |
| reader.metadata.addAuthor(value); |
| } |
| break; |
| } |
| /* |
| * Copyright notice of the person or organization that claims the copyright to the image. |
| * Example: “Copyright, John Smith, 1992. All rights reserved.” |
| */ |
| case Tags.Copyright: { |
| for (final String value : type.readString(input(), count, encoding())) { |
| reader.metadata.parseLegalNotice(value); |
| } |
| break; |
| } |
| /* |
| * Date and time of image creation. The format is: "YYYY:MM:DD HH:MM:SS" with 24-hour clock. |
| */ |
| case Tags.DateTime: { |
| for (final String value : type.readString(input(), count, encoding())) { |
| reader.metadata.addCitationDate(reader.getDateFormat().parse(value), |
| DateType.CREATION, MetadataBuilder.Scope.RESOURCE); |
| } |
| break; |
| } |
| /* |
| * The computer and/or operating system in use at the time of image creation. |
| */ |
| case Tags.HostComputer: { |
| for (final String value : type.readString(input(), count, encoding())) { |
| reader.metadata.addHostComputer(value); |
| } |
| break; |
| } |
| /* |
| * Name and version number of the software package(s) used to create the image. |
| */ |
| case Tags.Software: { |
| for (final String value : type.readString(input(), count, encoding())) { |
| reader.metadata.addSoftwareReference(value); |
| } |
| break; |
| } |
| /* |
| * Manufacturer of the scanner, video digitizer, or other type of equipment used to generate the image. |
| * Synthetic images should not include this field. |
| */ |
| case Tags.Make: { |
| // TODO: is Instrument.citation.citedResponsibleParty.party.name an appropriate place? |
| // what would be the citation title? A copy of Tags.Model? |
| break; |
| } |
| /* |
| * The model name or number of the scanner, video digitizer, or other type of equipment used to |
| * generate the image. |
| */ |
| case Tags.Model: { |
| for (final String value : type.readString(input(), count, encoding())) { |
| reader.metadata.addInstrument(null, value); |
| } |
| break; |
| } |
| /* |
| * The number of pixels per ResolutionUnit in the ImageWidth or ImageHeight direction. |
| */ |
| case Tags.XResolution: |
| case Tags.YResolution: { |
| final double r = type.readDouble(input(), count); |
| if (Double.isNaN(resolution) || r > resolution) { |
| resolution = r; |
| } |
| break; |
| } |
| /* |
| * The unit of measurement for XResolution and YResolution. |
| * |
| * 1 = None. Used for images that may have a non-square aspect ratio. |
| * 2 = Inch (default). |
| * 3 = Centimeter. |
| */ |
| case Tags.ResolutionUnit: { |
| final short unit = type.readShort(input(), count); |
| switch (unit) { |
| case 1: resolutionUnit = null; break; |
| case 2: resolutionUnit = Units.INCH; break; |
| case 3: resolutionUnit = Units.CENTIMETRE; break; |
| default: return unit; // Cause a warning to be reported by the caller. |
| } |
| break; |
| } |
| /* |
| * For black and white TIFF files that represent shades of gray, the technique used to convert |
| * from gray to black and white pixels. The default value is 1 (nothing done on the image). |
| * |
| * 1 = No dithering or halftoning has been applied to the image data. |
| * 2 = An ordered dither or halftone technique has been applied to the image data. |
| * 3 = A randomized process such as error diffusion has been applied to the image data. |
| */ |
| case Tags.Threshholding: { |
| final short value = type.readShort(input(), count); |
| switch (value) { |
| case 1: break; |
| case 2: if (cellWidth >= 0 || cellHeight >= 0) return null; else break; |
| case 3: break; |
| default: return value; // Cause a warning to be reported by the caller. |
| } |
| cellWidth = cellHeight = (short) -value; |
| break; |
| } |
| /* |
| * The width and height of the dithering or halftoning matrix used to create |
| * a dithered or halftoned bilevel file. Meaningful only if Threshholding = 2. |
| */ |
| case Tags.CellWidth: { |
| cellWidth = type.readShort(input(), count); |
| break; |
| } |
| case Tags.CellLength: { |
| cellHeight = type.readShort(input(), count); |
| break; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Defined by TIFF specification but currently ignored. //// |
| //// //// |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * For each string of contiguous unused bytes in a TIFF file, the number of bytes and the byte offset |
| * in the string. Those tags are deprecated and do not need to be supported. |
| */ |
| case Tags.FreeByteCounts: |
| case Tags.FreeOffsets: |
| /* |
| * For grayscale data, the optical density of each possible pixel value, plus the precision of that |
| * information. This is ignored by most TIFF readers. |
| */ |
| case Tags.GrayResponseCurve: |
| case Tags.GrayResponseUnit: { |
| warning(Level.FINE, Resources.Keys.IgnoredTag_1, Tags.name(tag)); |
| break; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the {@link #tileTagFamily} field to the given value if it does not conflict with previous value. |
| * |
| * @param family either {@link #TILE} or {@link #STRIP}. |
| * @throws DataStoreContentException if {@link #tileTagFamily} is already set to another value. |
| */ |
| private void setTileTagFamily(final byte family) throws DataStoreContentException { |
| if (tileTagFamily != family && tileTagFamily != 0) { |
| throw new DataStoreContentException(reader.resources().getString( |
| Resources.Keys.InconsistentTileStrip_1, filename())); |
| } |
| tileTagFamily = family; |
| } |
| |
| /** |
| * Computes the minimal or maximal values of the given vector. Those vectors do not need to have the same length. |
| * One of those two vector will be modified in-place. |
| * |
| * @param a the first vector, or {@code null} if none. |
| * @param b the new vector to combine with the existing one. Can not be null. |
| * @param max {@code true} for computing the maximal values, or {@code false} for the minimal value. |
| */ |
| private static Vector extremum(Vector a, Vector b, final boolean max) { |
| if (a != null) { |
| int s = b.size(); |
| int i = a.size(); |
| if (i > s) { // If a vector is longer than b, swap a and b. |
| i = s; |
| final Vector t = a; a = b; b = t; |
| } |
| while (--i >= 0) { // At this point, 'b' shall be the longest vector. |
| final double va = a.doubleValue(i); |
| final double vb = b.doubleValue(i); |
| if (Double.isNaN(vb) || (max ? va > vb : va < vb)) { |
| b.set(i, va); |
| } |
| } |
| } |
| return b; |
| } |
| |
| /** |
| * Multiplies the given value by the number of bytes in one pixel, |
| * or return -1 if the result is not an integer. |
| * |
| * @throws ArithmeticException if the result overflows. |
| */ |
| private long pixelToByteCount(long value) { |
| value = Math.multiplyExact(value, samplesPerPixel * (int) bitsPerSample); |
| return (value % Byte.SIZE == 0) ? value / Byte.SIZE : -1; |
| } |
| |
| /** |
| * Computes the tile width or height from the other size, |
| * or returns a negative number if the size can not be computed. |
| * |
| * @param knownSize the tile width or height. |
| * @return the tile width if the known size was height, or the tile height if the known size was width, |
| * or a negative number if the width or height can not be computed. |
| * @throws ArithmeticException if the result overflows. |
| */ |
| private int computeTileSize(final int knownSize) { |
| final int n = tileByteCounts.size(); |
| if (n != 0) { |
| final long count = tileByteCounts.longValue(0); |
| int i = 0; |
| do if (++i == n) { |
| // At this point, we verified that all vector values are equal. |
| final long length = pixelToByteCount(knownSize); |
| if (count % length != 0) break; |
| return Math.toIntExact(count / length); |
| } while (tileByteCounts.longValue(i) == n); |
| } |
| return -1; |
| } |
| |
| /** |
| * Verifies that the mandatory tags are present and consistent with each others. |
| * If a mandatory tag is absent, then there is a choice: |
| * |
| * <ul> |
| * <li>If the tag can be inferred from other tag values, performs that computation and logs a warning.</li> |
| * <li>Otherwise throws an exception.</li> |
| * </ul> |
| * |
| * This method opportunistically computes default value of optional fields |
| * when those values can be computed from other (usually mandatory) fields. |
| * |
| * @throws DataStoreContentException if a mandatory tag is missing and can not be inferred. |
| */ |
| final void validateMandatoryTags() throws DataStoreContentException { |
| if (imageWidth < 0) throw missingTag(Tags.ImageWidth); |
| if (imageHeight < 0) throw missingTag(Tags.ImageLength); |
| final short offsetsTag, byteCountsTag; |
| switch (tileTagFamily) { |
| case STRIP: { |
| if (tileWidth < 0) tileWidth = Math.toIntExact(imageWidth); |
| if (tileHeight < 0) tileHeight = Math.toIntExact(imageHeight); |
| offsetsTag = Tags.StripOffsets; |
| byteCountsTag = Tags.StripByteCounts; |
| break; |
| } |
| case TILE: { |
| offsetsTag = Tags.TileOffsets; |
| byteCountsTag = Tags.TileByteCounts; |
| break; |
| } |
| default: { |
| throw new DataStoreContentException(reader.resources().getString( |
| Resources.Keys.InconsistentTileStrip_1, filename())); |
| } |
| } |
| if (tileOffsets == null) { |
| throw missingTag(offsetsTag); |
| } |
| if (samplesPerPixel == 0) { |
| samplesPerPixel = 1; |
| missingTag(Tags.SamplesPerPixel, 1, false); |
| } |
| if (bitsPerSample == 0) { |
| bitsPerSample = 1; |
| missingTag(Tags.BitsPerSample, 1, false); |
| } |
| if (colorMap != null) { |
| ensureSameLength(Tags.ColorMap, Tags.BitsPerSample, colorMap.size(), 3 * (1 << bitsPerSample)); |
| } |
| if (sampleFormat != FLOAT) { |
| long minValue, maxValue; |
| if (sampleFormat == UNSIGNED) { |
| minValue = 0L; |
| maxValue = -1L; // All bits set to 1. |
| } else { |
| minValue = Long.MIN_VALUE; |
| maxValue = Long.MAX_VALUE; |
| } |
| final int shift = Long.SIZE - bitsPerSample; |
| if (shift >= 0 && shift < Long.SIZE) { |
| minValue >>>= shift; |
| maxValue >>>= shift; |
| if (minValue < maxValue) { // Exclude the unsigned long case since we can not represent it. |
| minValues = extremum(minValues, Vector.createSequence(minValue, 0, samplesPerPixel), false); |
| maxValues = extremum(maxValues, Vector.createSequence(maxValue, 0, samplesPerPixel), true); |
| } |
| } |
| } |
| /* |
| * All of tile width, height and length information should be provided. But if only one of them is missing, |
| * we can compute it provided that the file does not use any compression method. If there is a compression, |
| * then we set a bit for preventing the 'switch' block to perform a calculation but we let the code performs |
| * the other checks in order to get an exception to be thrown with a good message. |
| */ |
| int missing = !isPlanar && compression.equals(Compression.NONE) ? 0 : 0b1000; |
| if (tileWidth < 0) missing |= 0b0001; |
| if (tileHeight < 0) missing |= 0b0010; |
| if (tileByteCounts == null) missing |= 0b0100; |
| switch (missing) { |
| case 0: |
| case 0b1000: { // Every thing is ok. |
| break; |
| } |
| case 0b0001: { // Compute missing tile width. |
| tileWidth = computeTileSize(tileHeight); |
| missingTag(Tags.TileWidth, tileWidth, true); |
| break; |
| } |
| case 0b0010: { // Compute missing tile height. |
| tileHeight = computeTileSize(tileWidth); |
| missingTag(Tags.TileLength, tileHeight, true); |
| break; |
| } |
| case 0b0100: { // Compute missing tile byte count. |
| final long tileByteCount = pixelToByteCount(Math.multiplyExact(tileWidth, tileHeight)); |
| final long[] tileByteCountArray = new long[tileOffsets.size()]; |
| Arrays.fill(tileByteCountArray, tileByteCount); |
| tileByteCounts = Vector.create(tileByteCountArray, true); |
| missingTag(byteCountsTag, tileByteCount, true); |
| break; |
| } |
| default: { |
| final short tag; |
| switch (Integer.lowestOneBit(missing)) { |
| case 0b0001: tag = Tags.TileWidth; break; |
| case 0b0010: tag = Tags.TileLength; break; |
| default: tag = byteCountsTag; break; |
| } |
| throw missingTag(tag); |
| } |
| } |
| /* |
| * Report an error if the tile offset and tile byte count vectors do not have the same length. |
| * Then ensure that the number of tiles is equal to the expected number. The formula below is the |
| * one documented in the TIFF specification and reproduced in tileWidth & tileHeight fields javadoc. |
| */ |
| ensureSameLength(offsetsTag, byteCountsTag, tileOffsets.size(), tileByteCounts.size()); |
| long expectedCount = Math.multiplyExact( |
| Math.addExact(imageWidth, tileWidth - 1) / tileWidth, |
| Math.addExact(imageHeight, tileHeight - 1) / tileHeight); |
| if (isPlanar) { |
| expectedCount = Math.multiplyExact(expectedCount, samplesPerPixel); |
| } |
| final int actualCount = Math.min(tileOffsets.size(), tileByteCounts.size()); |
| if (actualCount != expectedCount) { |
| throw new DataStoreContentException(reader.resources().getString(Resources.Keys.UnexpectedTileCount_3, |
| filename(), expectedCount, actualCount)); |
| } |
| /* |
| * If a "grid to CRS" conversion has been specified with only the scale factor, we need to compute |
| * the translation terms now. |
| */ |
| if (referencing != null && !referencing.validateMandatoryTags()) { |
| throw missingTag(Tags.ModelTiePoints); |
| } |
| } |
| |
| /** |
| * Completes the metadata with the information stored in the field of this IFD. |
| * This method is invoked only if the user requested the ISO 19115 metadata. |
| * This method creates a new {@code "metadata/contentInfo"} node for this image. |
| * Information not under the {@code "metadata/contentInfo"} node will be merged |
| * with the current content of the given {@code MetadataBuilder}. |
| * |
| * @param metadata where to write metadata information. Caller should have already invoked |
| * {@link MetadataBuilder#setFormat(String)} before {@code completeMetadata(…)} calls. |
| */ |
| final void completeMetadata(final MetadataBuilder metadata, final Locale locale) |
| throws DataStoreContentException, FactoryException |
| { |
| metadata.newCoverage(false); |
| if (compression != null) { |
| metadata.addCompression(compression.name().toLowerCase(locale)); |
| } |
| for (int band = 0; band < samplesPerPixel;) { |
| metadata.newSampleDimension(); |
| metadata.setBitPerSample(bitsPerSample); |
| if (isMinSpecified) metadata.addMinimumSampleValue(minValues.doubleValue(Math.min(band, minValues.size()-1))); |
| if (isMaxSpecified) metadata.addMaximumSampleValue(maxValues.doubleValue(Math.min(band, maxValues.size()-1))); |
| metadata.setBandIdentifier(++band); |
| } |
| /* |
| * Add the resolution into the metadata. Our current ISO 19115 implementation restricts |
| * the resolution unit to metres, but it may be relaxed in a future SIS version. |
| */ |
| if (!Double.isNaN(resolution) && resolutionUnit != null) { |
| metadata.addResolution(resolutionUnit.getConverterTo(Units.METRE).convert(resolution)); |
| } |
| /* |
| * Cell size is relevant only if the Threshholding TIFF tag value is 2. By convention in |
| * this implementation class, other Threshholding values are stored as negative cell sizes: |
| * |
| * -1 means that Threshholding is 1 or unspecified. |
| * -2 means that Threshholding is 2 but the matrix size has not yet been specified. |
| * -3 means that Threshholding is 3 (randomized process such as error diffusion). |
| */ |
| switch (Math.min(cellWidth, cellHeight)) { |
| case -1: { |
| // Nothing to report. |
| break; |
| } |
| case -3: { |
| metadata.addProcessDescription(Resources.formatInternational(Resources.Keys.RandomizedProcessApplied)); |
| break; |
| } |
| default: { |
| metadata.addProcessDescription(Resources.formatInternational( |
| Resources.Keys.DitheringOrHalftoningApplied_2, |
| (cellWidth >= 0) ? cellWidth : '?', |
| (cellHeight >= 0) ? cellHeight : '?')); |
| break; |
| } |
| } |
| /* |
| * Add Coordinate Reference System built from GeoTIFF tags. Note that the CRS may not exist, |
| * in which case the CRS builder returns null. This is safe since all MetadataBuilder methods |
| * ignore null values (a design choice because this pattern come very often). |
| */ |
| if (referencing != null) { |
| getGridGeometry(); // For calculation of gridGeometry if not already done. |
| referencing.completeMetadata(metadata); |
| } |
| } |
| |
| @Override |
| protected void createMetadata(final MetadataBuilder metadata) throws DataStoreException { |
| super.createMetadata(metadata); |
| /* |
| * TODO: |
| * - Modify ImageFileDirectory.completeMetadata(…) with the addition of a boolean telling that |
| * that we invoke this method for a single image instead than the whole image. Use that flag |
| * for skipping MetadataBuilder calls writing in metadata/identificationInfo/resourceFormat. |
| * - Invoke ImageFileDirectory.completeMetadata(…) if not already done and cache in a field. |
| * - Add a metadata utility method taking two Metadata in argument, search for properties that |
| * are equal and replace them by the same instance. |
| * - Invoke that method from here if GeoTiffStore already has a metadata, or conversely from |
| * GeoTiffStore if ImageResource already has a metadata. |
| */ |
| } |
| |
| /** |
| * Returns an object containing the image size, the CRS and the conversion from pixel indices to CRS coordinates. |
| */ |
| @Override |
| public GridGeometry getGridGeometry() throws DataStoreContentException { |
| if (referencing != null) { |
| GridGeometry gridGeometry = referencing.gridGeometry; |
| if (gridGeometry == null) try { |
| gridGeometry = referencing.build(imageWidth, imageHeight); |
| } catch (FactoryException e) { |
| throw new DataStoreContentException(reader.resources().getString(Resources.Keys.CanNotComputeGridGeometry_1, filename()), e); |
| } |
| return gridGeometry; |
| } else { |
| return new GridGeometry(new GridExtent(imageWidth, imageHeight), null); |
| } |
| } |
| |
| /** |
| * Returns the ranges of sample values together with the conversion from samples to real values. |
| */ |
| @Override |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") |
| public List<SampleDimension> getSampleDimensions() throws DataStoreContentException { |
| if (sampleDimensions == null) { |
| final SampleDimension[] dimensions = new SampleDimension[samplesPerPixel]; |
| final SampleDimension.Builder builder = new SampleDimension.Builder(); |
| final InternationalString name = Vocabulary.formatInternational(Vocabulary.Keys.Value); |
| for (int band = 0; band < samplesPerPixel;) { |
| builder.addQualitative(name, minValues.get(Math.min(band, minValues.size()-1)), |
| maxValues.get(Math.min(band, maxValues.size()-1))); |
| dimensions[band] = builder.setName(++band).build(); |
| builder.clear(); |
| } |
| sampleDimensions = UnmodifiableArrayList.wrap(dimensions); |
| } |
| return sampleDimensions; // Safe because unmodifiable. |
| } |
| |
| /** |
| * Loads a subset of the grid coverage represented by this resource. |
| * |
| * @param domain desired grid extent and resolution, or {@code null} for reading the whole domain. |
| * @param range 0-based index of sample dimensions to read, or an empty sequence for reading all ranges. |
| * @return the grid coverage for the specified domain and range. |
| * @throws DataStoreException if an error occurred while reading the grid coverage data. |
| */ |
| @Override |
| public GridCoverage read(final GridGeometry domain, final int... range) throws DataStoreException { |
| throw new DataStoreException("Not yet implemented."); // TODO |
| } |
| |
| /** |
| * Reports a warning with a message created from the given resource keys and parameters. |
| * |
| * @param level the logging level for the message to log. |
| * @param key the {@code Resources} key of the message to format. |
| * @param parameters the parameters to put in the message. |
| */ |
| private void warning(final Level level, final short key, final Object... parameters) { |
| final LogRecord r = reader.resources().getLogRecord(level, key, parameters); |
| reader.owner.warning(r); |
| } |
| |
| /** |
| * Verifies that the given tags have the same length and reports a warning if they do not. |
| */ |
| private void ensureSameLength(final short tag1, final short tag2, final int length1, final int length2) { |
| if (length1 != length2) { |
| warning(Level.WARNING, Resources.Keys.MismatchedLength_4, Tags.name(tag1), Tags.name(tag2), length1, length2); |
| } |
| } |
| |
| /** |
| * Reports a warning for a missing TIFF tag for which a default value can be computed. |
| * |
| * @param missing the numerical value of the missing tag. |
| * @param value the default value or the computed value. |
| * @param computed whether the default value has been computed. |
| */ |
| private void missingTag(final short missing, final long value, final boolean computed) { |
| warning(computed ? Level.WARNING : Level.FINE, |
| computed ? Resources.Keys.ComputedValueForAttribute_2 : Resources.Keys.DefaultValueForAttribute_2, |
| Tags.name(missing), value); |
| } |
| |
| /** |
| * Builds an exception for a missing TIFF tag for which no default value can be computed. |
| * |
| * @param missing the numerical value of the missing tag. |
| */ |
| private DataStoreContentException missingTag(final short missing) { |
| return new DataStoreContentException(reader.resources().getString( |
| Resources.Keys.MissingValue_2, filename(), Tags.name(missing))); |
| } |
| } |