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.