This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 496130940f `TiledGridResource.getFillValue()` should return an array with one value per band instead of a single value for all bands. 496130940f is described below commit 496130940f6dedfa1797af6169655e4ff140834f Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri Sep 13 15:53:57 2024 +0200 `TiledGridResource.getFillValue()` should return an array with one value per band instead of a single value for all bands. --- .../apache/sis/coverage/privy/ImageUtilities.java | 17 ++++- .../apache/sis/coverage/privy/TilePlaceholder.java | 16 ++--- .../main/org/apache/sis/image/DataType.java | 39 ++++++++--- .../sis/storage/geotiff/CompressedSubset.java | 4 +- .../org/apache/sis/storage/geotiff/DataCube.java | 5 +- .../org/apache/sis/storage/geotiff/DataSubset.java | 38 +++++++--- .../sis/storage/geotiff/ImageFileDirectory.java | 14 +++- .../apache/sis/storage/base/TiledGridCoverage.java | 21 ++++-- .../apache/sis/storage/base/TiledGridResource.java | 61 +++++++++++++---- .../main/org/apache/sis/math/Vector.java | 2 +- .../org/apache/sis/storage/gdal/TiledResource.java | 80 ++++++++++++++-------- .../storage/gimi/internal/MatrixGridRessource.java | 5 -- 12 files changed, 213 insertions(+), 89 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageUtilities.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageUtilities.java index aa27ff7a4a..527cf5148e 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageUtilities.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageUtilities.java @@ -149,7 +149,7 @@ public final class ImageUtilities extends Static { /** * If the given image is showing only one band, returns the index of that band. - * Otherwise returns -1. Image showing only one band are SIS-specific + * Otherwise, returns -1. Images showing only one band are SIS-specific * (usually an image shows all its bands). * * @param image the image for which to get the visible band, or {@code null}. @@ -175,6 +175,21 @@ public final class ImageUtilities extends Static { return -1; } + /** + * Returns the index of the band shown by the given color model. + * This is zero for standard color models, but <abbr>SIS</abbr> + * sometime allows color models to show another band. + * + * @param cm the color model for which to get the visible band. + * @return index of the visible band. + */ + public static int getVisibleBand(final IndexColorModel cm) { + if (cm instanceof MultiBandsIndexColorModel) { + return ((MultiBandsIndexColorModel) cm).visibleBand; + } + return 0; + } + /** * Returns the data type of bands in rasters that use the given sample model. * If each band is stored in its own {@link DataBuffer} element, then this method returns the same value diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/TilePlaceholder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/TilePlaceholder.java index 4a12016b7d..634f590ab9 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/TilePlaceholder.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/TilePlaceholder.java @@ -133,20 +133,18 @@ public class TilePlaceholder { } /** - * Returns a provider of empty tiles filled with the given value in all bands. - * A value of {@code null} is interpreted as 0 for integer types or NaN for floating point types. + * Returns a provider of empty tiles filled with the given values in all bands. + * A {@code null} array is interpreted as 0 for integer types or NaN for floating point types. * - * @param model sample model of the empty tiles. - * @param fillValue the value to use for filling empty spaces in rasters, or {@code null} for zero. + * @param model sample model of the empty tiles. + * @param fillValues the values to use for filling empty spaces in rasters, or {@code null} for the default. * @return provider of filled tiles. */ - public static TilePlaceholder filled(final SampleModel model, final Number fillValue) { - if (fillValue == null) { + public static TilePlaceholder filled(final SampleModel model, final Number[] fillValues) { + if (fillValues == null) { return empty(model); } - final Number[] values = new Number[model.getNumBands()]; - Arrays.fill(values, fillValue); - return filled(model, new FillValues(model, values, true)); + return filled(model, new FillValues(model, fillValues, true)); } /** diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/DataType.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/DataType.java index be2a205ce8..cbbc0acbc1 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/DataType.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/DataType.java @@ -32,7 +32,7 @@ import static org.apache.sis.util.privy.Numerics.MAX_INTEGER_CONVERTIBLE_TO_FLOA * This is a type-safe version of the {@code TYPE_*} constants defined in {@link DataBuffer}. * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.5 * @since 1.1 */ public enum DataType { @@ -45,33 +45,41 @@ public enum DataType { /** * Unsigned 8-bits data. */ - BYTE, + BYTE((byte) 0), /** * Unsigned 16-bits data. */ - USHORT, + USHORT((short) 0), /** * Signed 16-bits data. */ - SHORT, + SHORT((short) 0), /** * Signed 32-bits data. Also used for storing unsigned data; the Java2D API such as * {@link java.awt.image.Raster#getSample(int, int, int)} cannot distinguish the two cases. */ - INT, + INT(0), /** * Single precision (32-bits) floating point data. */ - FLOAT, + FLOAT(Float.NaN), /** * Double precision (64-bits) floating point data. */ - DOUBLE; + DOUBLE(Double.NaN); + + /** + * The default fill value, which is 0 for integer types and NaN for floating point types. + * The class of this number is the wrapper type corresponding to the type of sample values. + * + * @see #fillValue() + */ + private final Number fillValue; /** * All enumeration values, cached for avoiding to recreate this array @@ -82,7 +90,8 @@ public enum DataType { /** * Creates a new enumeration. */ - private DataType() { + private DataType(final Number fillValue) { + this.fillValue = fillValue; } /** @@ -315,4 +324,18 @@ public enum DataType { public final int toDataBufferType() { return ordinal(); } + + /** + * Returns the default fill value, which is 0 for integer types and NaN for floating point types. + * The class of this number is the wrapper class corresponding to the type of sample values, + * ignoring whether the type is signed or unsigned. For example, for {@link #USHORT}, + * the returned fill value is an instance of the {@link Short} class. + * + * @return 0 of NaN in an instance of the wrapper class of the sample values. + * + * @since 1.5 + */ + public final Number fillValue() { + return fillValue; + } } diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java index 6da055be23..5ae22e39b6 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java @@ -105,6 +105,7 @@ final class CompressedSubset extends DataSubset { * @param rasters potentially shared cache of rasters read by this {@code DataSubset}. * @throws ArithmeticException if the number of tiles overflows 32 bits integer arithmetic. */ + @SuppressWarnings("LocalVariableHidesMemberVariable") CompressedSubset(final DataCube source, final TiledGridResource.Subset subset) throws DataStoreException { super(source, subset); scanlineStride = multiplyFull(getTileSize(X_DIMENSION), sourcePixelStride); @@ -216,6 +217,7 @@ final class CompressedSubset extends DataSubset { sourcePixelStride, getTileSize(X_DIMENSION), chunksPerRow, samplesPerChunk, skipAfterChunks, pixelsPerElement, dataType); } + @SuppressWarnings("LocalVariableHidesMemberVariable") final Inflater inflater = this.inflater; final int capacity = getBankCapacity(pixelsPerElement); final Buffer[] banks = new Buffer[numBanks]; @@ -243,7 +245,7 @@ final class CompressedSubset extends DataSubset { } inflater.skip(head); // Last iteration without the trailing `skip(…)` calls. inflater.uncompressRow(); - fillRemainingRows(bank.flip()); + fillRemainingRows(bank.flip(), b); banks[b] = bank; } return createWritableRaster(RasterFactory.wrap(dataType, banks), location); diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java index 2891c0400f..a0f32761ab 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java @@ -170,11 +170,12 @@ abstract class DataCube extends TiledGridResource implements ResourceOnFileSyste * Our netCDF reader does the same thing, and we want a consistent behavior of coverage readers. * </div> * - * If this method returns a non-null value, then {@link #getFillValue()} should return NaN. + * If this method returns a non-null value, then {@link #getFillValues(int[])} should return + * an array of NaN. * * @return value to be replaced by NaN at reading time, or {@code null} if none. * - * @see #getFillValue() + * @see #getFillValues(int[]) */ abstract Number getReplaceableFillValue(); diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java index 3bd7dfb457..5210fcb925 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java @@ -396,7 +396,12 @@ class DataSubset extends TiledGridCoverage implements Localized { final Raster r; if (isEmpty) { if (emptyTiles == null) { - emptyTiles = TilePlaceholder.filled(model, (fillValue != null) ? fillValue : 0); + Number[] values = fillValues; + if (values == null) { + values = new Number[model.getNumBands()]; + Arrays.fill(values, 0); + } + emptyTiles = TilePlaceholder.filled(model, values); } r = emptyTiles.create(origin); } else { @@ -508,9 +513,9 @@ class DataSubset extends TiledGridCoverage implements Localized { * If that assumption was not true, we would have to adjust `capacity`, `lower[0]` and `upper[0]` * (we may do that as an optimization in a future version). */ - final HyperRectangleReader hr = new HyperRectangleReader(ImageUtilities.toNumberEnum(type.toDataBufferType()), input()); - final Region region = new Region(size, lower, upper, subsampling); - final Buffer[] banks = new Buffer[numBanks]; + final var hr = new HyperRectangleReader(ImageUtilities.toNumberEnum(type.toDataBufferType()), input()); + final var region = new Region(size, lower, upper, subsampling); + final var banks = new Buffer[numBanks]; for (int b=0; b<numBanks; b++) { if (b < byteCounts.length && length > byteCounts[b]) { throw new DataStoreContentException(source.reader.resources().getString( @@ -519,7 +524,7 @@ class DataSubset extends TiledGridCoverage implements Localized { hr.setOrigin(offsets[b]); assert model.getSampleSize(b) == sampleSize; // See above comment. final Buffer bank = hr.readAsBuffer(region, getBankCapacity(1)); - fillRemainingRows(bank); + fillRemainingRows(bank, b); banks[b] = bank; } final DataBuffer buffer = RasterFactory.wrap(type, banks); @@ -532,15 +537,28 @@ class DataSubset extends TiledGridCoverage implements Localized { * capacity if the current tile is smaller than the expected tile size (e.g. last tile is truncated). * * @param bank the buffer where to fill remaining rows. + * @param band index of the band to fill. Same as bank index in the particular case of {@code DataSubset} class. */ - final void fillRemainingRows(final Buffer bank) { - if (fillValue != null) { + final void fillRemainingRows(final Buffer bank, final int band) { + if (fillValues != null) { final int limit = bank.limit(); final int capacity = bank.capacity(); // Equals `this.capacity` except for packed sample model. if (limit != capacity) { - Vector.create(bank.limit(capacity), ImageUtilities.isUnsignedType(model)) - .fill(limit, capacity, fillValue); - bank.limit(capacity); + final Vector v = Vector.create(bank.limit(capacity), ImageUtilities.isUnsignedType(model)); + final Number f = fillValues[band]; + /* + * If all values are the same, we can delegate (indirectly) to an `Arrays.fill(…)` method. + * Also, if the raster stores each band in a separated bank (banded sample model), + * we have only one value to set in the given bank. + */ + if (ArraysExt.allEquals(fillValues, f) || model instanceof BandedSampleModel) { + v.fill(limit, capacity, f); + } else { + // Slow fallback for interleaved sample models. + for (int i=limit; i<capacity; i++) { + v.set(i, fillValues[i % fillValues.length]); + } + } } } } diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java index 582b560f27..abf4f071ab 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java @@ -1778,13 +1778,21 @@ final class ImageFileDirectory extends DataCube { } /** - * Returns the value to use for filling empty spaces in the raster, or {@code null} if none, + * Returns the values to use for filling empty spaces in the raster, or {@code null} if none, * not different than zero or not valid for the target data type. * The zero value is excluded because tiles are already initialized to zero by default. */ @Override - protected Number getFillValue() { - return (sampleFormat != FLOAT) ? getFillValue(false) : Double.NaN; + protected Number[] getFillValues(final int[] bands) { + final Number fill; + if (sampleFormat == FLOAT) { + fill = Double.NaN; + } else if ((fill = getFillValue(false)) == null) { + return null; + } + final var values = new Number[(bands != null) ? bands.length : getNumBands()]; + Arrays.fill(values, fill); + return values; } /** diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java index ffe450ff15..91731a7f96 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java @@ -189,18 +189,25 @@ public abstract class TiledGridCoverage extends GridCoverage { * The sample model for all rasters. The width and height of this sample model are the two first elements * of {@link #tileSize} divided by subsampling and clipped to the domain. If user requested to read only * a subset of the bands, then this sample model is already the subset. + * + * @see TiledGridResource#getSampleModel(int[]) */ protected final SampleModel model; /** * The Java2D color model for images rendered from this coverage. + * + * @see TiledGridResource#getColorModel(int[]) */ protected final ColorModel colors; /** - * The value to use for filling empty spaces in rasters, or {@code null} if zero. + * The values to use for filling empty spaces in rasters, or {@code null} if zero in all bands. + * If non-null, the array length is equal to the number of bands. + * + * @see TiledGridResource#getFillValues(int[]) */ - protected final Number fillValue; + protected final Number[] fillValues; /** * Whether the reading of tiles is deferred to {@link RenderedImage#getTile(int, int)} time. @@ -251,10 +258,10 @@ public abstract class TiledGridCoverage extends GridCoverage { if (model.getWidth() != subSize[X_DIMENSION] || model.getHeight() != subSize[Y_DIMENSION]) { model = model.createCompatibleSampleModel(subSize[X_DIMENSION], subSize[Y_DIMENSION]); } - this.model = model; - this.colors = subset.colorsForBandSubset; - this.fillValue = subset.fillValue; - forceTileSize = subSize[X_DIMENSION] * subsampling[X_DIMENSION] == tileSize[X_DIMENSION]; + this.model = model; + this.colors = subset.colorsForBandSubset; + this.fillValues = subset.fillValues; + forceTileSize = subSize[X_DIMENSION] * subsampling[X_DIMENSION] == tileSize[X_DIMENSION]; } /** @@ -725,7 +732,7 @@ public abstract class TiledGridCoverage extends GridCoverage { * coordinates are the values returned by {@link #getTileOrigin(int)} for dimensions * of two-dimensional slices. * - * <p>The raster is <em>not</em> filled with {@link #fillValue}. + * <p>The raster is <em>not</em> filled with {@link #fillValues}. * Filling, if needed, should be done by the caller.</p> * * @return a newly created, initially empty raster. diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java index 47cf1469e5..f4322a3daf 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java @@ -19,11 +19,13 @@ package org.apache.sis.storage.base; import java.util.List; import java.util.Arrays; import java.util.Objects; +import java.lang.reflect.Array; import java.awt.image.DataBuffer; import java.awt.image.ColorModel; import java.awt.image.SampleModel; import java.awt.image.BandedSampleModel; import java.awt.image.ComponentSampleModel; +import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.RasterFormatException; @@ -35,7 +37,9 @@ import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.coverage.grid.GridRoundingMode; import org.apache.sis.coverage.privy.ColorModelFactory; +import org.apache.sis.coverage.privy.ImageUtilities; import org.apache.sis.coverage.privy.RangeArgument; +import org.apache.sis.image.DataType; import org.apache.sis.storage.Resource; import org.apache.sis.storage.AbstractGridCoverageResource; import org.apache.sis.storage.DataStoreException; @@ -222,7 +226,7 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { * * @see RangeArgument#select(SampleModel, boolean) */ - protected boolean getDissociableBands() throws DataStoreException { + protected boolean canSeparateBands() throws DataStoreException { return getSampleModel(null) instanceof ComponentSampleModel; } @@ -305,15 +309,44 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { } /** - * Returns the value to use for filling empty spaces in rasters, - * or {@code null} if none, not different than zero or not valid for the target data type. - * This value is used if a tile contains less pixels than expected. - * The zero value is excluded because tiles are already initialized to zero by default. + * Returns the values to use for filling empty spaces in rasters, with one value per band. + * The returned array can be {@code null} if there is no fill value, or if the fill values + * are not different than zero, or are not valid for the image data type. * - * @return the value to use for filling empty spaces in rasters. + * <p>Fill values are used when a tile contains less pixels than expected. + * A null array is a shortcut for skipping the filling of new tiles, + * because new tiles are already initialized with zero values by default.</p> + * + * <p>The default implementation returns an array of {@link DataType#fillValue()} except + * for the {@linkplain IndexColorModel#getTransparentPixel() transparent pixel} if any. + * If the array would contain only zero values, the default implementation returns null.</p> + * + * @param bands indices (not necessarily in increasing order) of desired bands, or {@code null} for all bands. + * @return the value to use for filling empty spaces in each band, or {@code null} for defaulting to zero. * @throws DataStoreException if an error occurred while fetching filling information. */ - protected abstract Number getFillValue() throws DataStoreException; + protected Number[] getFillValues(final int[] bands) throws DataStoreException { + final SampleModel model = getSampleModel(bands); + final var dataType = DataType.forDataBufferType(model.getDataType()); + IndexColorModel icm = null; +check: if (dataType.isInteger()) { + final ColorModel colors = getColorModel(bands); + if (colors instanceof IndexColorModel) { + icm = (IndexColorModel) colors; + if (icm.getTransparentPixel() > 0) { + break check; + } + } + return null; + } + final Number fill = dataType.fillValue(); + final var fillValues = (Number[]) Array.newInstance(fill.getClass(), model.getNumBands()); + Arrays.fill(fillValues, fill); + if (icm != null) { + fillValues[ImageUtilities.getVisibleBand(icm)] = icm.getTransparentPixel(); + } + return fillValues; + } /** * Parameters that describe the resource subset to be accepted by the {@link TiledGridCoverage} constructor. @@ -395,10 +428,10 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { final ColorModel colorsForBandSubset; /** - * Value to use for filling empty spaces in rasters, or {@code null} if none, + * Values to use for filling empty spaces in rasters, or {@code null} if none, * not different than zero or not valid for the target data type. */ - final Number fillValue; + final Number[] fillValues; /** * Cache to use for tiles loaded by the {@link TiledGridCoverage}. @@ -477,15 +510,16 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { /* * Get the bands selected by user in strictly increasing order of source band index. * If user has specified bands in a different order, that change of band order will - * be handled by the `SampleModel`, not in `includedBands` array. + * be handled by the `SampleModel`, not by the `includedBands` array. */ + int[] requestedBands = null; // Same as `includedBands` but in user-specified order. @SuppressWarnings("LocalVariableHidesMemberVariable") int[] includedBands = null; @SuppressWarnings("LocalVariableHidesMemberVariable") SampleModel modelForBandSubset = null; @SuppressWarnings("LocalVariableHidesMemberVariable") ColorModel colorsForBandSubset = null; boolean loadAllBands = rangeIndices.isIdentity(); if (!loadAllBands) { bands = Arrays.asList(rangeIndices.select(bands)); - loadAllBands = !getDissociableBands(); + loadAllBands = !canSeparateBands(); if (!loadAllBands) { sharedCache = false; if (!rangeIndices.hasAllBands) { @@ -495,8 +529,7 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { } assert ArraysExt.isSorted(includedBands, true); } - // Same as `includedBands`, but in the order requested by the user. - int[] requestedBands = rangeIndices.getSelectedBands(); + requestedBands = rangeIndices.getSelectedBands(); modelForBandSubset = getSampleModel(requestedBands); colorsForBandSubset = getColorModel (requestedBands); } @@ -512,7 +545,7 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { this.includedBands = includedBands; this.modelForBandSubset = Objects.requireNonNull(modelForBandSubset); this.colorsForBandSubset = colorsForBandSubset; - this.fillValue = getFillValue(); + this.fillValues = getFillValues(requestedBands); /* * All `TiledGridCoverage` instances can share the same cache if they read all tiles fully. * If they read only sub-regions or apply subsampling, then they will need their own cache. diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java index ffc29ad7b6..4bff0ab27f 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java @@ -105,7 +105,7 @@ public abstract class Vector extends AbstractList<Number> implements RandomAcces * <li>A {@code Number[]} array.</li> * <li>A {@code String[]} array (not recommended, but happen with some file formats).</li> * <li>A {@code Vector}, in which case it is returned unchanged.</li> - * <li>A {@link Buffer} backed by an array.</li> + * <li>A {@link Buffer} backed by a Java array. Wrap the part between buffer's position and limit.</li> * <li>The {@code null} value, in which case {@code null} is returned.</li> * </ul> * diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java index 3025ab2255..d8768ed759 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java @@ -34,6 +34,7 @@ import java.lang.foreign.MemorySegment; import org.opengis.util.GenericName; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.referencing.privy.AffineTransform2D; +import org.apache.sis.referencing.privy.ExtendedPrecisionMatrix; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; @@ -128,11 +129,12 @@ final class TiledResource extends TiledGridResource { private SampleModel sampleModel; /** - * The fill value, fetched when first requested. + * The fill values, fetched when first requested. An array of length 0 is used as a sentinel + * value meaning that the array has been computed and the result is {@code null}. * - * @see #getFillValue() + * @see #getFillValues(int[]) */ - private Number fillValue; + private Number[] fillValues; /** * Creates a new instance as a child of the given data set. @@ -341,8 +343,27 @@ final class TiledResource extends TiledGridResource { } } + /** + * Always return {@code true} because <abbr>GDAL</abbr> provides a band-oriented <abbr>API</abbr>, + * where each band can always be accessed separately from other bands. + */ + @Override + protected final boolean canSeparateBands() { + return true; + } + + /** + * Always returns 1, because the complexity of reading only a sub-region is handled by <abbr>GDAL</abbr>. + */ + @Override + protected final int getAtomSize(int dim) { + return 1; + } + /** * Creates the color model and sample model. + * This method uses cached values if the {@code bandIndices} argument is + * equal to the values given the last time that this method has been invoked. * * @param bandIndices indices of the selected bands. */ @@ -383,34 +404,19 @@ final class TiledResource extends TiledGridResource { * for all number of bands from 2 to 15. */ } - int visibleBand = -1; if ((red | green | blue) >= 0) { - visibleBand = Math.min(Math.min(red, green), blue); colorModel = ColorModelFactory.createRGB(dataType.numBits, false, alpha >= 0); // TODO: needs custom color model if too many bands, or if order is not (A)RGB. } else if (palette != null) { - visibleBand = paletteIndex; - colorModel = ColorModelFactory.createIndexColorModel(selectedBands.length, visibleBand, palette, true, -1); + colorModel = ColorModelFactory.createIndexColorModel(selectedBands.length, paletteIndex, palette, true, -1); } else { - visibleBand = Math.max(gray, 0); - final Band band = selectedBands[visibleBand]; + gray = Math.max(gray, 0); + final Band band = selectedBands[gray]; final double min = band.getValue(gdal.getRasterMinimum, MemorySegment.NULL); final double max = band.getValue(gdal.getRasterMaximum, MemorySegment.NULL); - colorModel = ColorModelFactory.createGrayScale(dataType.imageType, selectedBands.length, visibleBand, min, max); + colorModel = ColorModelFactory.createGrayScale(dataType.imageType, selectedBands.length, gray, min, max); } sampleModel = new BandedSampleModel(dataType.imageType, width, height, selectedBands.length); - /* - * Also compute the fill value here because the current method needs the visible band. - * TODO: we should compute the fill value for all bands instead, and move this code to - * the `getFillValue()` method. - */ - try (final Arena arena = Arena.ofConfined()) { - final MemorySegment flag = arena.allocate(ValueLayout.JAVA_INT); - final double value = selectedBands[visibleBand].getValue(gdal.getRasterNoDataValue, flag); - if (value != 0 && Band.isTrue(flag)) { - fillValue = value; - } - } selectedBandIndices = bandIndices; } @@ -442,17 +448,35 @@ final class TiledResource extends TiledGridResource { } /** - * Returns the value to use for filling empty spaces in rasters, - * or {@code null} if none, not different than zero or not valid for the target data type. + * Returns the values to use for filling empty spaces in rasters, with one value per band. + * The returned array can be {@code null} if the fill values are not different than zero. * The zero value is excluded because tiles are already initialized to zero by default. */ @Override - protected Number getFillValue() throws DataStoreException { + @SuppressWarnings("ReturnOfCollectionOrArrayField") + protected Number[] getFillValues(final int[] bandIndices) throws DataStoreException { synchronized (getSynchronizationLock()) { - if (fillValue == null) { - createColorAndSampleModel(null); + if (fillValues == null) { + @SuppressWarnings("LocalVariableHidesMemberVariable") + final var fillValues = new Number[] {(bandIndices != null) ? bandIndices.length : bands.length}; + final GDAL gdal = parent.getProvider().GDAL(); + boolean hasNonZero = false; + try (final Arena arena = Arena.ofConfined()) { + final MemorySegment flag = arena.allocate(ValueLayout.JAVA_INT); + for (int i=0; i<fillValues.length; i++) { + final int b = (bandIndices != null) ? bandIndices[i] : i; + final double value = bands[b].getValue(gdal.getRasterNoDataValue, flag); + hasNonZero |= (value != 0); + if (!Band.isTrue(flag)) { + hasNonZero = false; + break; + } + } + } + // Use `ExtendedPrecisionMatrix.CREATE_ZERO` as an arbitrary zero-length array. + this.fillValues = hasNonZero ? fillValues : ExtendedPrecisionMatrix.CREATE_ZERO; } - return fillValue; + return (fillValues.length != 0) ? fillValues : null; } } diff --git a/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java b/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java index be0abc9bca..61f5328da3 100644 --- a/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java +++ b/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java @@ -84,11 +84,6 @@ public abstract class MatrixGridRessource extends TiledGridResource { return colorModel; } - @Override - protected Number getFillValue() throws DataStoreException { - return Double.NaN; - } - @Override public GridGeometry getGridGeometry() throws DataStoreException { return getTileMatrix().getTilingScheme().upsample(getTileSize());