This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits 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 68e24554e7 Fix an index out of bounds exception when the image size is 
smaller than the tile size. Contains also opportunistic javadoc fixes.
68e24554e7 is described below

commit 68e24554e7878e723290e160ed39b1e4ce6f4bc5
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat May 30 21:08:18 2026 +0200

    Fix an index out of bounds exception when the image size is smaller than 
the tile size.
    Contains also opportunistic javadoc fixes.
---
 .../main/org/apache/sis/referencing/Builder.java   |  2 +-
 .../sis/storage/geoheif/CoverageBuilder.java       | 21 ++++++----
 .../sis/storage/geoheif/UncompressedImage.java     | 46 +++++++++++++++++-----
 .../org/apache/sis/storage/isobmff/TreeNode.java   |  4 +-
 4 files changed, 52 insertions(+), 21 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
index efb262a864..eee065f5a7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java
@@ -274,7 +274,7 @@ public abstract class Builder<B extends Builder<B>> {
         if (object != null) {
             properties.putAll(IdentifiedObjects.getProperties(object));
             final var valueAlias = (GenericName[]) 
properties.remove(IdentifiedObject.ALIAS_KEY);
-            final var  valueIds  = (Identifier[])  
properties.remove(IdentifiedObject.IDENTIFIERS_KEY);
+            final var valueIds   = (Identifier[])  
properties.remove(IdentifiedObject.IDENTIFIERS_KEY);
             if (valueAlias != null) Collections.addAll(aliases, valueAlias);
             if (valueIds   != null) Collections.addAll(identifiers, valueIds);
         }
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
index 4c0adef74b..f72a91de76 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
@@ -221,7 +221,7 @@ final class CoverageBuilder implements Emptiable {
             final Box property = properties.get(i);
             switch (property.type()) {
                 /*
-                 * We should have only one instance of `ImageSpatialExtents`. 
If nevertheless there is many,
+                 * We should have only one instance of `ImageSpatialExtents`. 
If nevertheless there are many,
                  * the use of the maximum values increases the chances that we 
detect inconsistency because
                  * it may result in a length larger than the size of the box 
which contains the pixels.
                  */
@@ -234,7 +234,7 @@ final class CoverageBuilder implements Emptiable {
                 }
                 /*
                  * We should have one instance of `ComponentDefinition` and 
`PixelInformation`. If nevertheless
-                 * there is many, maybe a broken writer puts one element per 
box. If this assumption is wrong,
+                 * there are many, maybe a broken writer puts one element per 
box. If this assumption is wrong,
                  * the total length should be greater than the pixels box 
size, thus allowing error detection.
                  */
                 case ComponentDefinition.BOXTYPE: {
@@ -340,12 +340,17 @@ final class CoverageBuilder implements Emptiable {
      * the metadata information collected by the tile builder to this builder, 
otherwise they will be lost.
      * This method needs to be invoked before {@link #gridGeometry()} and 
{@link #sampleDimensions()}.</p>
      *
-     * @param  tile  the builder used for the tiles.
+     * <p>If this method is invoked many times with a non-null {@code 
firstBuilder} argument, only the first
+     * occurrence is retained. It does not necessarily cause a lost of data 
because the caller already saved
+     * the image in the {@link ResourceBuilder#resources} list, and the 
builder is used for fetching
+     * information that should be the same for every tiles.</p>
+     *
+     * @param  firstBuilder  the builder used for building the first tile, or 
{@code null} if none.
      */
-    final void setTileBuilder(final CoverageBuilder tile) {
-        if (tile != null && tileBuilder == null) {
-            tileBuilder = tile;
-            metadata = tile.metadata();
+    final void setTileBuilder(final CoverageBuilder firstBuilder) {
+        if (firstBuilder != null && tileBuilder == null) {
+            tileBuilder = firstBuilder;
+            metadata = firstBuilder.metadata();
         }
     }
 
@@ -520,7 +525,7 @@ final class CoverageBuilder implements Emptiable {
             }
             return;
         }
-        final int[] bitsPerSample = new int[numBands];
+        final var bitsPerSample = new int[numBands];
         final var sd = full ? new SampleDimension[numBands] : null;
         final var sb = full ? new SampleDimension.Builder() : null;
         int numBits=0, redMask=0, greenMask=0, blueMask=0, alphaMask=0;
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
index dae972670f..8d65edfa79 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.storage.geoheif;
 
+import java.util.Arrays;
 import static java.lang.Math.addExact;
 import static java.lang.Math.multiplyExact;
 import java.awt.image.DataBuffer;
@@ -73,25 +74,48 @@ final class UncompressedImage extends Image {
         dataType    = builder.dataType();     // Shall be after 
`sampleModel()`.
     }
 
+    /**
+     * Returns (width × number of samples per pixel) and the height, in that 
order.
+     *
+     * <p><b>Limitation:</b> current implementation ignores {@link 
java.awt.image.MultiPixelPackedSampleModel},
+     * but that model does not seem to be used by <abbr>HEIF</abbr>.</p>
+     *
+     * @param  sampleModel  the sample model for which to get the size.
+     * @return the scanline stride and raster height, in that order.
+     */
+    private static long[] size(final SampleModel sampleModel) {
+        return new long[] {
+            sampleModel.getWidth() * (long) sampleModel.getNumDataElements(),
+            sampleModel.getHeight()
+        };
+    }
+
+    /**
+     * Creates a two-dimensional region without subsampling.
+     *
+     * @param  sourceSize   the number of elements along each dimension.
+     * @param  regionUpper  indices after the last value to read along each 
dimension.
+     */
+    private static Region region(final long[] sourceSize, final long[] 
regionUpper) {
+        return new Region(sourceSize, new long[2], regionUpper, new long[] {1, 
1});
+    }
+
     /**
      * Computes the range of bytes that will be needed for reading a single 
tile of this image.
      *
      * @param  context  where to store the ranges of bytes.
+     * @return the function to invoke later for reading the tile.
      * @throws DataStoreException if an error occurred while computing the 
range of bytes.
      */
     @Override
     protected Reader computeByteRanges(final 
ImageResource.Coverage.ReadContext context) throws DataStoreException {
-        final long[] sourceSize = {
-            // Note: the following ignores `MultiPixelPackedSampleModel`, but 
that model does not seem used by HEIF.
-            sampleModel.getWidth() * (long) sampleModel.getNumDataElements(),
-            sampleModel.getHeight()
-        };
+        final long[] sourceSize = size(sampleModel);
         /*
          * In the current implementation, we read the whole tile. If a future 
implementation allows
          * to read a sub-region of the tile, we would need to make 
`HyperRectangleReader` accepts a
          * `Buffer` with an arbitrary position in argument.
          */
-        final var  region    = new Region(sourceSize, new long[2], sourceSize, 
new long[] {1, 1});
+        final var  region    = region(sourceSize, sourceSize);
         final int  dataSize  = dataType.bytes();
         final long tileSize  = multiplyExact(region.length, dataSize);
         final long skipBytes = region.getStartByteOffset(dataSize);
@@ -107,15 +131,17 @@ final class UncompressedImage extends Image {
             input.buffer.order(byteOrder);
             final var hr = new 
HyperRectangleReader(ImageUtilities.toNumberEnum(dataType), input);
             hr.setOrigin(context.offset() - skipBytes);
-            final WritableRaster raster = context.createRaster();
-            final DataBuffer data = raster.getDataBuffer();
-            final int numBanks = data.getNumBanks();
+            final WritableRaster raster       = context.createRaster();
+            final long[]         rasterSize   = size(raster.getSampleModel());
+            final Region         rasterRegion = Arrays.equals(sourceSize, 
rasterSize) ? region : region(sourceSize, rasterSize);
+            final DataBuffer     data         = raster.getDataBuffer();
+            final int            numBanks     = data.getNumBanks();
             for (int b=0; b<numBanks; b++) {
                 if (b != 0) {
                     hr.setOrigin(addExact(hr.getOrigin(), tileSize));
                 }
                 hr.setDestination(RasterFactory.wrapAsBuffer(data, b));
-                hr.readAsBuffer(region, 0);
+                hr.readAsBuffer(rasterRegion, 0);
             }
             return raster;
         };
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/TreeNode.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/TreeNode.java
index 0bd7b6134e..186393e01b 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/TreeNode.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/TreeNode.java
@@ -219,14 +219,14 @@ public abstract class TreeNode {
      */
     @Override
     public final String toString() {
-        return toTree(null, getClass().getSimpleName(), false).toString();
+        return toTree(Locale.getDefault(), getClass().getSimpleName(), 
false).toString();
     }
 
     /**
      * Returns <abbr>HEIF</abbr> boxes and their fields as a tree.
      * Used for showing native metadata or for debugging purposes.
      *
-     * @param  locale       the locale to use, or {@code null} for the default.
+     * @param  locale       the locale to use.
      * @param  rootName     name of the root node.
      * @param  withSummary  whether to put a summary text in container nodes.
      * @return fields contained in this node, together with child boxes.

Reply via email to