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 75624a737d Fix the case of GeoTIFF files not read correctly when the 
image is untiled but the tile is unnecessary large (larger than the actual 
image).
75624a737d is described below

commit 75624a737d2af6cfeaa8a960c1094f14a8595979
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jul 18 16:48:54 2025 +0200

    Fix the case of GeoTIFF files not read correctly when the image is untiled
    but the tile is unnecessary large (larger than the actual image).
---
 .../org/apache/sis/coverage/grid/GridCoverage.java |  2 +-
 .../main/org/apache/sis/image/PixelIterator.java   |  6 +--
 .../apache/sis/image/privy/ColorModelFactory.java  |  3 ++
 .../sis/storage/geotiff/CompressedSubset.java      | 23 +++++----
 .../org/apache/sis/storage/geotiff/DataCube.java   | 19 ++++++--
 .../org/apache/sis/storage/geotiff/DataSubset.java | 26 +++++-----
 .../sis/storage/geotiff/ImageFileDirectory.java    | 20 ++++++--
 .../apache/sis/storage/geotiff/inflater/LZW.java   |  3 +-
 .../apache/sis/storage/base/TiledGridCoverage.java | 55 +++++++++++++++-------
 .../apache/sis/storage/base/TiledGridResource.java | 25 +++++++---
 netbeans-project/nbproject/project.xml             |  1 +
 11 files changed, 125 insertions(+), 58 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
index 520def8287..c224df7107 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
@@ -537,7 +537,7 @@ public abstract class GridCoverage extends BandedCoverage {
     public TreeTable toTree(final Locale locale, final int bitmask) {
         final Vocabulary vocabulary = 
Vocabulary.forLocale(Objects.requireNonNull(locale));
         final TableColumn<CharSequence> column = TableColumn.VALUE_AS_TEXT;
-        final TreeTable tree = new DefaultTreeTable(column);
+        final var tree = new DefaultTreeTable(column);
         final TreeTable.Node root = tree.getRoot();
         root.setValue(column, Classes.getShortClassName(this));
         TreeTable.Node branch = root.newChild();
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java
index 4155780ff0..1ced8e4296 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PixelIterator.java
@@ -397,7 +397,7 @@ public class PixelIterator {
          */
         static int getScanlineStride(final SampleModel sm) {
             if (sm instanceof ComponentSampleModel) {
-                final ComponentSampleModel csm = (ComponentSampleModel) sm;
+                final var csm = (ComponentSampleModel) sm;
                 if (csm.getPixelStride() == 1) {
                     for (final int offset : csm.getBandOffsets()) {
                         if (offset != 0) return 0;
@@ -407,13 +407,13 @@ public class PixelIterator {
                     }
                 }
             } else if (sm instanceof SinglePixelPackedSampleModel) {
-                final SinglePixelPackedSampleModel csm = 
(SinglePixelPackedSampleModel) sm;
+                final var csm = (SinglePixelPackedSampleModel) sm;
                 final int[] offsets = csm.getBitOffsets();
                 if (offsets.length == 1 && offsets[0] == 0) {
                     return csm.getScanlineStride();
                 }
             } else if (sm instanceof MultiPixelPackedSampleModel) {
-                final MultiPixelPackedSampleModel csm = 
(MultiPixelPackedSampleModel) sm;
+                final var csm = (MultiPixelPackedSampleModel) sm;
                 if (csm.getDataBitOffset() == 0 && csm.getPixelBitStride() == 
DataBuffer.getDataTypeSize(csm.getDataType())) {
                     return csm.getScanlineStride();
                 }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorModelFactory.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorModelFactory.java
index d5945ed4fc..f75dd78c6f 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorModelFactory.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorModelFactory.java
@@ -481,6 +481,9 @@ public final class ColorModelFactory {
                                               final double lower, final double 
upper, final Color... colors)
     {
         ArgumentChecks.ensureNonEmpty("colors", colors);
+        if (colors.length == 2 && colors[0].getRGB() == 0xFF000000 && 
colors[1].getRGB() == 0xFFFFFFFF) {
+            return createGrayScale(dataType, numBands, visibleBand, lower, 
upper);
+        }
         return createPiecewise(dataType, numBands, visibleBand, new 
ColorsForRange[] {
             new ColorsForRange(null, new NumberRange<>(Double.class, lower, 
true, upper, false), colors, true, null)
         });
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 9c62813d72..c5ce241813 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
@@ -36,10 +36,13 @@ import org.apache.sis.image.privy.RasterFactory;
  */
 final class CompressedSubset extends DataSubset {
     /**
-     * Number of sample values to skip for moving to the next row of a tile in 
the GeoTIFF file.
-     * This is not necessarily the same scanline stride as for the tiles 
created by this class.
+     * Number of sample values to skip for moving to the next row of a tile in 
the <abbr>TIFF</abbr> file.
+     * This is not necessarily the same scanline stride as the one for the 
tiles created by this class.
+     *
+     * @see #sourcePixelStride
+     * @see java.awt.image.ComponentSampleModel#getScanlineStride()
      */
-    private final long scanlineStride;
+    private final long sourceScanlineStride;
 
     /**
      * Number of sample values to skip before to read the first value of the 
first pixel in a row.
@@ -73,7 +76,7 @@ final class CompressedSubset extends DataSubset {
     private final int[] skipAfterChunks;
 
     /**
-     * Number of sample values that compose a chunk (pixel or sample) in the 
GeoTIFF file.
+     * Number of sample values that compose a chunk (pixel or sample) in the 
<abbr>TIFF</abbr> file.
      * The value of this field can be:
      *
      * <ul>
@@ -107,9 +110,9 @@ final class CompressedSubset extends DataSubset {
     @SuppressWarnings("LocalVariableHidesMemberVariable")
     CompressedSubset(final DataCube source, final TiledGridResource.Subset 
subset) throws DataStoreException {
         super(source, subset);
-        scanlineStride     = Math.multiplyExact(sourcePixelStride, 
getTileSize(X_DIMENSION));
-        final int between  = Math.multiplyExact(sourcePixelStride, 
Math.toIntExact(getSubsampling(X_DIMENSION) - 1));
-        long afterLastBand = scanlineStride - sourcePixelStride;
+        sourceScanlineStride = source.getScanlineStride(sourcePixelStride);
+        long afterLastBand   = sourceScanlineStride - sourcePixelStride;
+        final int between    = Math.multiplyExact(sourcePixelStride, 
Math.toIntExact(getSubsampling(X_DIMENSION) - 1));
         if (includedBands != null && sourcePixelStride > 1) {
             final int[] skips = new int[includedBands.length];
             final int m = skips.length - 1;
@@ -184,7 +187,7 @@ final class CompressedSubset extends DataSubset {
      * @param  upper        (<var>x</var>, <var>y</var>) coordinates after the 
last pixel to read relative to the tile.
      * @param  subsampling  (<var>sx</var>, <var>sy</var>) subsampling factors.
      * @param  location     pixel coordinates in the upper-left corner of the 
tile to return.
-     * @return a single tile decoded from the GeoTIFF file.
+     * @return a single tile decoded from the <abbr>TIFF</abbr> file.
      */
     @Override
     Raster readSlice(final long[] offsets, final long[] byteCounts, final 
long[] lower, final long[] upper,
@@ -241,14 +244,14 @@ final class CompressedSubset extends DataSubset {
              * by the mathematical operation identified by `predictor`.
              */
             for (long y = lower[1]; --y >= 0;) {
-                inflater.skip(scanlineStride);          // `skip(…)` may round 
to next element boundary.
+                inflater.skip(sourceScanlineStride);    // `skip(…)` may round 
to next element boundary.
             }
             for (int y = height; --y > 0;) {            // (height - 1) 
iterations.
                 inflater.skip(head);
                 inflater.uncompressRow();
                 inflater.skip(tail);
                 for (int j=betweenRows; --j>=0;) {
-                    inflater.skip(scanlineStride);
+                    inflater.skip(sourceScanlineStride);
                 }
             }
             inflater.skip(head);                        // Last iteration 
without the trailing `skip(…)` calls.
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 b1e2774774..0b8ea082e4 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
@@ -134,12 +134,25 @@ abstract class DataCube extends TiledGridResource 
implements StoreResource {
     abstract long getNumTiles();
 
     /**
-     * Gets the stream position and the length in bytes of compressed tile 
arrays in the GeoTIFF file.
+     * Gets the stream position or the length in bytes of compressed tile 
arrays in the GeoTIFF file.
      * Values in the returned vector are {@code long} primitive type.
      *
-     * @return stream position (relative to file beginning) and length of 
compressed tile arrays, in bytes.
+     * @param  length  {@code false} for requesting tile offsets, or {@code 
true} for tile lengths.
+     * @return stream position (relative to file beginning) or length of 
compressed tile arrays, in bytes.
      */
-    abstract Vector[] getTileArrayInfo();
+    abstract Vector getTileArrayInfo(boolean length);
+
+    /**
+     * Returns the number of sample values for moving to the next row in a 
tile of the <abbr>TIFF</abbr> file.
+     * The {@code pixelStride} argument could be computed by this class, but 
is given in argument because its
+     * value is already known by the caller.
+     *
+     * @param  pixelStride  number of sample values for moving to the next 
pixel.
+     * @return number of sample values for moving to the next row.
+     *
+     * @see java.awt.image.ComponentSampleModel#getScanlineStride()
+     */
+    abstract long getScanlineStride(int pixelStride);
 
     /**
      * Returns {@code true} if {@link Integer#reverseBytes(int)} should be 
invoked on each byte read.
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 06dc570e39..bc1bd2dabe 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
@@ -55,9 +55,9 @@ import static org.apache.sis.pending.jdk.JDK18.ceilDiv;
 
 
 /**
- * Raster data obtained from a GeoTIFF file in the domain requested by user. 
The number of dimensions is 2
- * for standard TIFF files, but this class accepts higher number of dimensions 
if 3- or 4-dimensional data
- * are stored in a GeoTIFF file using some convention. This base class 
transfers uncompressed data.
+ * Raster data obtained from a GeoTIFF file in the domain requested by user. 
The number of dimensions is 2 for
+ * standard <abbr>TIFF</abbr> files, but this class accepts higher number of 
dimensions if 3- or 4-dimensional
+ * data are stored in a GeoTIFF file using some convention. This base class 
transfers uncompressed data.
  * Compressed data are handled by specialized subclasses.
  *
  * <h2>Cell Coordinates</h2>
@@ -114,7 +114,7 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
     protected final int numBanks;
 
     /**
-     * Number of interleaved sample values in a pixel in the GeoTIFF file 
(ignoring band subset).
+     * Number of interleaved sample values in a pixel in the <abbr>TIFF</abbr> 
file (ignoring band subset).
      * For planar images (banded sample model), this is equal to 1. For pixel 
interleaved image,
      * this is equal to the number of bands in the original image.
      *
@@ -122,6 +122,7 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
      * and 8 samples are packed in each byte. Conversely a sample may also be 
1, 2, 4 or 8 bytes.</p>
      *
      * @see java.awt.image.ComponentSampleModel#getPixelStride()
+     * @see CompressedSubset#sourceScanlineStride
      */
     protected final int sourcePixelStride;
 
@@ -149,11 +150,10 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
      */
     DataSubset(final DataCube source, final TiledGridResource.Subset subset) 
throws DataStoreException {
         super(subset);
-        this.source    = source;
-        this.numTiles  = toIntExact(source.getNumTiles());
-        final Vector[] tileArrayInfo = source.getTileArrayInfo();
-        this.tileOffsets    = tileArrayInfo[0];
-        this.tileByteCounts = tileArrayInfo[1];
+        this.source         = source;
+        this.numTiles       = toIntExact(source.getNumTiles());
+        this.tileOffsets    = source.getTileArrayInfo(false);
+        this.tileByteCounts = source.getTileArrayInfo(true);
         /*
          * "Banks" (in `java.awt.image.DataBuffer` sense) are synonymous to 
"bands" for planar image only.
          * Otherwise there is only one bank no matter the number of bands. 
Each bank will be read separately.
@@ -229,8 +229,8 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
      */
     protected final int getBankCapacity(final int pixelsPerElement) {
         // `ceilDiv(…)` must happen before multiplication by image height.
-        final int scanlineStride = ceilDiv(multiplyExact(model.getWidth(), 
targetPixelStride), pixelsPerElement);
-        return multiplyExact(scanlineStride, model.getHeight());
+        final int targetScanlineStride = 
ceilDiv(multiplyExact(model.getWidth(), targetPixelStride), pixelsPerElement);
+        return multiplyExact(targetScanlineStride, model.getHeight());
     }
 
     /**
@@ -489,7 +489,7 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
      * @param  upper        (<var>x</var>, <var>y</var>) coordinates after the 
last pixel to read relative to the tile.
      * @param  subsampling  (<var>sx</var>, <var>sy</var>) subsampling factors.
      * @param  location     pixel coordinates in the upper-left corner of the 
tile to return.
-     * @return a single tile decoded from the GeoTIFF file.
+     * @return a single tile decoded from the <abbr>TIFF</abbr> file.
      * @throws IOException if an I/O error occurred.
      * @throws DataStoreException if a logical error occurred.
      * @throws RuntimeException if the Java2D image cannot be created for 
another reason
@@ -583,7 +583,7 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
     /**
      * Creates the raster with the given data buffer, which contains the 
pixels that have been read.
      *
-     * @param  buffer    the sample values which have been read from the 
GeoTIFF tile.
+     * @param  buffer    the sample values which have been read from the 
<abbr>TIFF</abbr> tile.
      * @param  location  pixel coordinates in the upper-left corner of the 
tile to return.
      * @return raster with the given sample values.
      */
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 23a73e1eb9..618c6709f3 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
@@ -1640,6 +1640,19 @@ final class ImageFileDirectory extends DataCube {
         return sampleModel;
     }
 
+    /**
+     * Returns the number of sample values for moving to the next row in a 
tile of the <abbr>TIFF</abbr> file.
+     * The given {@code pixelStride} argument should be {@code 
sampleModel.getPixelString()} and the returned
+     * value should be {@code sampleModel.getScanlineStride()}.
+     *
+     * @param  pixelStride  number of sample values for moving to the next 
pixel.
+     * @return number of sample values for moving to the next row.
+     */
+    @Override
+    final long getScanlineStride(final int pixelStride) {
+        return Math.multiplyFull(pixelStride, tileWidth);
+    }
+
     /**
      * Returns the number of components per pixel.
      */
@@ -1868,11 +1881,12 @@ final class ImageFileDirectory extends DataCube {
      * Gets the stream position or the length in bytes of compressed tile 
arrays in the GeoTIFF file.
      * Values in the returned vector are {@code long} primitive type.
      *
-     * @return stream position (relative to file beginning) and length of 
compressed tile arrays, in bytes.
+     * @param  length  {@code false} for requesting tile offsets, or {@code 
true} for tile lengths.
+     * @return stream position (relative to file beginning) or length of 
compressed tile arrays, in bytes.
      */
     @Override
-    Vector[] getTileArrayInfo() {
-        return new Vector[] {tileOffsets, tileByteCounts};
+    Vector getTileArrayInfo(final boolean length) {
+        return length ? tileByteCounts : tileOffsets;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/inflater/LZW.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/inflater/LZW.java
index f5317e9489..0db2dc0e3a 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/inflater/LZW.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/inflater/LZW.java
@@ -193,7 +193,7 @@ final class LZW extends CompressionChannel {
     /**
      * Pointers to byte sequences for a code in the {@link #entriesForCodes} 
array.
      * Each element is a value encoded by {@link #offsetAndLength(int, int)} 
method.
-     * Elements are decoded by {@link #offset(int)} {@link #length(int)} 
methods.
+     * Elements are decoded by {@link #offset(int)} and {@link #length(int)} 
methods.
      */
     private final int[] entriesForCodes;
 
@@ -304,6 +304,7 @@ final class LZW extends CompressionChannel {
     @Override
     public int read(final ByteBuffer target) throws IOException {
         final int start = target.position();
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         int previousCode = this.previousCode;
         /*
          * If a previous invocation of this method was unable to write some 
data
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 cada7ef2ac..20631fbadd 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
@@ -105,12 +105,10 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
     protected final GridExtent readExtent;
 
     /**
-     * Whether to force the {@link #readExtent} tile intersection to the 
{@link #virtualTileSize}.
-     * This is relevant only for the last column of tile matrix, because those 
tiles may be truncated
-     * if the image size is not a multiple of tile size. It is usually 
necessary to read those tiles
-     * fully anyway because otherwise, the pixels read from the storage would 
not be aligned with the
-     * pixels stored in the {@link Raster}. However, there is a few exceptions 
where the read extent
-     * should not be forced to the tile size:
+     * Whether to enforce {@link #virtualTileSize} even if the intersection 
with {@link #readExtent} is smaller.
+     * Forcing whole tile is relevant mostly (but not only) for the last 
column of tile matrix, because otherwise
+     * the {@linkplain java.awt.image.ComponentSampleModel#getScanlineStride() 
scanline stride} would be wrong.
+     * However, there is a few exceptions where the read extent should not be 
forced to the tile size:
      *
      * <ul>
      *   <li>If the image is untiled, then the {@link 
org.apache.sis.storage.base.TiledGridResource.Subset}
@@ -120,21 +118,38 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
      * </ul>
      *
      * This a list of Boolean flags packed as a bitmask with the flag for the 
first dimension in the lowest bit.
-     * The default implementation always sets the flag of the last dimension 
to {@code false} (0), then sets the
-     * flags of other dimensions to {@code true} (1) if we are not in the case 
of a big untiled image.
+     * The default implementation sets the bit to 1 in all dimensions where 
the read operation should be done in
+     * a tiled fashion (i.e., not reading a sub-region of an effectively 
untiled image).
+     *
+     * <h4>Example</h4>
+     * The GeoTIFF reader always sets the flag of the last dimension (the 
rows) to {@code false} (0),
+     * then sets the flags of other dimensions to {@code true} (1) if we are 
not in the case of a big
+     * untiled image.
      */
     private final long forceWholeTiles;
 
     /**
-     * Size of all tiles in the domain of this {@code TiledGridCoverage}, 
without clipping and subsampling.
+     * Size of all tiles in the domain of this {@code TiledGridCoverage}, 
without sub-sampling.
      * All coverages created from the same {@link TiledGridResource} shall 
have the same tile size values.
      * The length of this array is the number of dimensions in the source 
{@link GridExtent}.
      * This is often {@value #BIDIMENSIONAL} but can also be more.
      *
-     * <p>The tile size may be virtual if the {@link TiledGridResource} 
subclass decided to coalesce
-     * many real tiles in bigger virtual tiles. This is sometime useful when a 
subsampling is applied,
-     * for avoiding that the subsampled tiles become too small.
-     * This strategy may be convenient when coalescing is easy.</p>
+     * <h4>What is a "virtual" size</h4>
+     * The tile size stored in this field is usually the size of tiles used by 
the binary encoding of the file
+     * which is read by {@link TiledGridResource}. However, this tile size may 
differ in two circumstances.
+     * In such case, this tile size is said "virtual".
+     *
+     * <h5>Tiles coalescence</h5>
+     * The first circumstance is when the {@link TiledGridResource} subclass
+     * decided to coalesce many tiles from the file in bigger tiles in memory.
+     * This is sometime useful when a sub-sampling is applied,
+     * for avoiding that the sub-sampled tiles become too small.
+     * This strategy may be convenient when coalescing tiles is easy for the 
subclass.
+     *
+     * <h5>Untiled image</h5>
+     * The second circumstance is when the coverage is effectively untiled.
+     * It may be because the binary file is untiled, or because the requested 
region is fully inside a single tile.
+     * In such case, the virtual tile size is the size of the requested region.
      *
      * @see #getTileSize(int)
      */
@@ -233,7 +248,9 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
     protected final Number[] fillValues;
 
     /**
-     * Whether the reading of tiles is deferred to {@link 
RenderedImage#getTile(int, int)} time.
+     * Whether the reading of tiles is deferred until {@link 
RenderedImage#getTile(int, int)} is invoked.
+     * This is true if the user explicitly {@linkplain 
TiledGridResource#setLoadingStrategy requested such
+     * deferred loading strategy} and the resource considers that it is worth 
to do so.
      */
     private final boolean deferredTileReading;
 
@@ -248,7 +265,6 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
     protected TiledGridCoverage(final TiledGridResource.Subset subset) {
         super(subset.domain, subset.ranges);
         final GridExtent extent = subset.domain.getExtent();
-        final int dimension = subset.virtualTileSize.length;
         deferredTileReading = subset.deferredTileReading();     // May be 
shorter than other arrays or the grid geometry.
         readExtent          = subset.readExtent;
         subsampling         = subset.subsampling;
@@ -256,6 +272,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         includedBands       = subset.includedBands;
         rasters             = subset.cache;
         virtualTileSize     = subset.virtualTileSize;
+        final int dimension = virtualTileSize.length;
         tmcOfFirstTile      = new long[dimension];
         tileStrides         = new int [dimension];
         final int[] subSize = new int [dimension];
@@ -307,9 +324,11 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
     }
 
     /**
-     * Returns the size of all tiles in the domain of this {@code 
TiledGridCoverage}, without clipping and subsampling.
-     * It may be a virtual tile size if the {@link TiledGridResource} subclass 
decided to coalesce many real tiles into
-     * fewer bigger virtual tiles.
+     * Returns the size of all tiles in the domain of this {@code 
TiledGridCoverage}, without sub-sampling.
+     * This usually the same size as the tiles in the storage which is read by 
{@link TiledGridResource},
+     * but not necessarily. It may be larger if the {@link TiledGridResource} 
subclass decided to coalesce
+     * many real tiles into larger virtual tiles, or it may be smaller when 
reading a sub-region of an
+     * effectively untiled coverage.
      *
      * @param  dimension  dimension for which to get tile size.
      * @return tile size in the given dimension, without clipping and 
subsampling.
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 104f9d1258..0b5c4e414c 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
@@ -407,6 +407,7 @@ check:  if (dataType.isInteger()) {
     public final class Subset {
         /**
          * The full size of the coverage in the enclosing {@link 
TiledGridResource}.
+         * This is taken from {@link #getGridGeometry()} and does not take 
sub-sampling in account.
          */
         final GridExtent sourceExtent;
 
@@ -463,11 +464,14 @@ check:  if (dataType.isInteger()) {
         final long[] subsamplingOffsets;
 
         /**
-         * Size of tiles (or chunks) in the resource, without clipping and 
subsampling.
-         * May be a virtual tile size (i.e., tiles larger than the real tiles) 
if the
-         * resource can easily coalesce many tiles in a single read operation.
+         * Size of tiles (or chunks) in the resource, without sub-sampling.
+         * May be a virtual tile size (i.e., tiles larger than the tiles in 
the file)
+         * if the resource can easily coalesce many tiles in a single read 
operation.
+         * Conversely, it may also be smaller than the real tile size if the 
subset
+         * is effectively untiled (the requested region covers a single tile).
          *
          * @see #getVirtualTileSize(long[])
+         * @see TiledGridCoverage#virtualTileSize
          */
         final long[] virtualTileSize;
 
@@ -584,7 +588,9 @@ check:  if (dataType.isInteger()) {
                 subsamplingOffsets = 
ArraysExt.resize(target.getSubsamplingOffsets(), dimension);
             }
             /*
-             * Virtual tile size is usually the same as the real tile size.
+             * Virtual tile size is usually the same as the tile size encoded 
in the binary file.
+             * The virtual size may be larger if the subclass override 
`getVirtualTileSize(…)`.
+             * The loop below is where the virtual tile size may be made 
smaller.
              */
             virtualTileSize = getVirtualTileSize(subsampling);
             for (int i=0; i < virtualTileSize.length; i++) {
@@ -638,14 +644,19 @@ check:  if (dataType.isInteger()) {
 
         /**
          * Returns flags telling, for each dimension, whether the read region 
should be an integer number of tiles.
+         * By default (when {@link #canReadTruncatedTiles(int, boolean)} is 
not overridden), the flags are set for
+         * all dimensions except the ones where the region to read is smaller 
than the tile size. The latter case
+         * happens when reading an effectively untiled coverage (when the 
requested region is inside a single tile).
          *
          * @param  subSize  tile size after subsampling.
          * @return a bitmask with the flag for the first dimension in the 
lowest bit.
+         *
+         * @see TiledGridCoverage#forceWholeTiles
          */
         final long forceWholeTiles(final int[] subSize) {
             long forceWholeTiles = 0;
             for (int i=0; i<subSize.length; i++) {
-                if (!canReadTruncatedTiles(i, 
Math.multiplyExact(subsampling[i], subSize[i]) != virtualTileSize[i])) {
+                if (!canReadTruncatedTiles(i, 
Math.multiplyExact(subsampling[i], subSize[i]) < virtualTileSize[i])) {
                     forceWholeTiles |= Numerics.bitmask(i);
                 }
             }
@@ -672,7 +683,9 @@ check:  if (dataType.isInteger()) {
         }
 
         /**
-         * Whether the reading of tiles is deferred to {@link 
RenderedImage#getTile(int, int)} time.
+         * Whether the reading of tiles is deferred until {@link 
RenderedImage#getTile(int, int)} is invoked.
+         * This is true if the user explicitly {@linkplain #setLoadingStrategy 
requested such deferred loading
+         * strategy} and this method considers that it is worth to do so.
          */
         final boolean deferredTileReading() {
             if (loadingStrategy != RasterLoadingStrategy.AT_GET_TILE_TIME) {
diff --git a/netbeans-project/nbproject/project.xml 
b/netbeans-project/nbproject/project.xml
index 2b218382a5..8ae7f076ad 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -34,6 +34,7 @@
             <word>Geopackage</word>
             <word>Molodensky</word>
             <word>transformative</word>
+            <word>untiled</word>
         </spellchecker-wordlist>
     </configuration>
 </project>

Reply via email to