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());


Reply via email to