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 5b82429ee8 When computing the sub-region to read from a large untiled
raster, pixel coordinates need to be converted to sample value coordinates.
This conversion was done in TIFF reader but missing in HEIF reader. Since the
conversion is not obvious, add a helper `Builder` class.
5b82429ee8 is described below
commit 5b82429ee8d6caa5f464655153931f15a40b8704
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Jul 6 11:51:54 2026 +0200
When computing the sub-region to read from a large untiled raster,
pixel coordinates need to be converted to sample value coordinates.
This conversion was done in TIFF reader but missing in HEIF reader.
Since the conversion is not obvious, add a helper `Builder` class.
---
.../main/org/apache/sis/image/ComputedImage.java | 1 +
.../image/internal/shared/BatchComputedImage.java | 5 +-
.../image/internal/shared/SampleModelBuilder.java | 5 +-
.../apache/sis/io/stream/HyperRectangleWriter.java | 14 +-
.../main/org/apache/sis/io/stream/Region.java | 171 ++++++++++++++++++++-
.../org/apache/sis/storage/internal/Resources.java | 5 +
.../sis/storage/internal/Resources.properties | 1 +
.../sis/storage/internal/Resources_fr.properties | 1 +
.../sis/storage/tiling/TiledGridCoverage.java | 42 ++++-
.../main/org/apache/sis/util/ArraysExt.java | 20 ++-
.../main/org/apache/sis/storage/geoheif/Image.java | 2 +-
.../apache/sis/storage/geoheif/ImageResource.java | 29 ++++
.../sis/storage/geoheif/UncompressedImage.java | 53 +++----
13 files changed, 296 insertions(+), 53 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ComputedImage.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ComputedImage.java
index 934b8d0c32..e1c90d0203 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ComputedImage.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ComputedImage.java
@@ -600,6 +600,7 @@ public abstract class ComputedImage extends PlanarImage
implements Disposable {
* @param tiles indices of the tiles which will be prefetched.
* @return handler on which to invoke {@code dispose()} after the prefetch
operation
* completed (successfully or not), or {@code null} if none.
+ * @throws ImagingOpException if an error occurred while preparing tile
computation.
*
* @since 1.2
*/
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
index 9aa46cc72d..5495346bce 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
@@ -172,14 +172,17 @@ public abstract class BatchComputedImage extends
ComputedImage {
*
* @param region indices of the tiles which will be prefetched.
* @return handler on which to invoke {@code dispose()} after the prefetch
operation.
+ * @throws ImagingOpException if an error occurred while preparing tile
computation.
*/
@Override
protected Disposable prefetch(final Rectangle region) {
final Raster[] tiles;
try {
tiles = computeTiles(region);
+ } catch (ImagingOpException e) {
+ throw e;
} catch (Exception e) {
- throw (ImagingOpException) new
ImagingOpException(null).initCause(e);
+ throw (ImagingOpException) new
ImagingOpException(e.getMessage()).initCause(e);
}
final Rasters r = new Rasters(region, tiles);
synchronized (this) {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/SampleModelBuilder.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/SampleModelBuilder.java
index d5b753a486..4f0e421c5b 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/SampleModelBuilder.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/SampleModelBuilder.java
@@ -181,10 +181,9 @@ public final class SampleModelBuilder {
bandOffsets = cm.getBandOffsets();
scanlineStride = cm.getScanlineStride();
pixelStride = cm.getPixelStride();
- for (int i=0; i<bankIndices.length; i++) {
- if (bankIndices[i] != 0) return;
+ if (ArraysExt.allEquals(bankIndices, 0)) {
+ bankIndices = null; // PixelInterleavedSampleModel
}
- bankIndices = null; // PixelInterleavedSampleModel
} else if (model instanceof SinglePixelPackedSampleModel) {
final var cm = (SinglePixelPackedSampleModel) model;
bitMasks = cm.getBitMasks();
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java
index 9b949a64de..65f7b6512c 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/HyperRectangleWriter.java
@@ -74,7 +74,6 @@ public class HyperRectangleWriter {
* Creates a new writer for data of a shape specified by the given region.
* The region also specifies the subset to write.
*
- * @param output where to write data.
* @param region size of the source hyper-rectangle and region to write.
* @throws ArithmeticException if the region is too large.
*/
@@ -351,7 +350,7 @@ public class HyperRectangleWriter {
if (isSupported(this, sm)) {
return create(sm, null);
} else {
- throw new RasterFormatException(sm.toString());
+ throw Region.Builder.unsupportedSampleModel(sm);
}
}
@@ -428,7 +427,7 @@ public class HyperRectangleWriter {
if (isSupported(this, sm)) {
return create(sm, null);
} else {
- throw new RasterFormatException(sm.toString());
+ throw Region.Builder.unsupportedSampleModel(sm);
}
}
@@ -471,7 +470,7 @@ public class HyperRectangleWriter {
if (sm instanceof ComponentSampleModel) return
create((ComponentSampleModel) sm);
if (sm instanceof SinglePixelPackedSampleModel) return
create((SinglePixelPackedSampleModel) sm);
if (sm instanceof MultiPixelPackedSampleModel) return
create((MultiPixelPackedSampleModel) sm);
- throw new RasterFormatException(sm.toString());
+ throw Region.Builder.unsupportedSampleModel(sm);
}
/**
@@ -568,8 +567,11 @@ public class HyperRectangleWriter {
/**
* Returns the byte order to use for writing data, or {@code null} if
no change is needed.
* A specific byte order is sometime needed when {@code short} or
{@code int} values are
- * used for packing many bytes. This order can be ignored if, for
example, ARGB values
- * are really stored as integers in native byte order rather than 4
bytes.
+ * used for packing many bytes. This order can be ignored if, for
example, <abbr>ARGB</abbr>
+ * values are really stored as integers in native byte order rather
than 4 bytes.
+ *
+ * @param current the byte order used by the output file.
+ * @return the byte order to use for writing the array of numbers.
*/
public ByteOrder byteOrder(ByteOrder current) {
return requiresBigEndian && (current != ByteOrder.BIG_ENDIAN) ?
ByteOrder.BIG_ENDIAN : null;
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java
index 848b83ea5f..690b21a813 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/Region.java
@@ -17,13 +17,21 @@
package org.apache.sis.io.stream;
import java.util.Arrays;
+import java.awt.image.SampleModel;
+import java.awt.image.BandedSampleModel;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.RasterFormatException;
import static java.lang.Math.addExact;
import static java.lang.Math.subtractExact;
import static java.lang.Math.multiplyExact;
import static java.lang.Math.incrementExact;
import static java.lang.Math.toIntExact;
-import org.apache.sis.io.TableAppender;
import static org.apache.sis.pending.jdk.JDK18.ceilDiv;
+import org.apache.sis.storage.internal.Resources;
+import org.apache.sis.io.TableAppender;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Classes;
/**
@@ -314,4 +322,165 @@ public final class Region {
}
return table.toString();
}
+
+ /**
+ * Helper class for building a region.
+ * All arrays are initialized to zero.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+ public static final class Builder {
+ /**
+ * The number of elements along each dimension.
+ */
+ public final long[] sourceSize;
+
+ /**
+ * Indices of the first value to read or write along each dimension.
+ * May be {@code null} if all lower coordinates are zero.
+ */
+ public final long[] regionLower;
+
+ /**
+ * Indices after the last value to read or write along each dimension.
+ * May be {@code null} if the upper coordinates are {@link
#sourceSize}.
+ */
+ public final long[] regionUpper;
+
+ /**
+ * Subsampling along each dimension. Shall be greater than zero.
+ * May be {@code null} if all subsampling factor are 1 (no
subsampling).
+ */
+ public final long[] subsampling;
+
+ /**
+ * Creates a builder for a region of the given number of dimensions.
+ * If {@code subRegion} is {@code false}, then only {@link
#sourceSize} is non-null.
+ * All non-null arrays are initialized to 0.
+ *
+ * @param dimension number of dimensions.
+ * @param subRegion whether the caller may need to specify a
sub-region.
+ */
+ public Builder(final int dimension, final boolean subRegion) {
+ sourceSize = new long[dimension];
+ if (subRegion) {
+ regionLower = new long[dimension];
+ regionUpper = new long[dimension];
+ subsampling = new long[dimension];
+ } else {
+ regionLower = null;
+ regionUpper = null;
+ subsampling = null;
+ }
+ }
+
+ /**
+ * Index of the bank in the arrays updated by {@link
#pixelsToSampleValues(SampleModel)}.
+ */
+ private static final int BANK_INDEX = 2;
+
+ /**
+ * Computes the subregion to read for filling a raster which uses the
given sample model.
+ * Before to invoke this method, the caller shall set the {@link
#regionLower}, {@link #regionUpper}
+ * and {@link #subsampling} arrays (if non-null) to the pixel
coordinates of the subregion to read.
+ * Subclasses of {@link
org.apache.sis.storage.tiling.TiledGridCoverageResource} can do as below:
+ *
+ * {@snippet lang="java" :
+ * final TileIterator t = ...;
+ * final var b = new Region.Builder(3);
+ * t.getRegionInsideTile(b.regionLower, b.regionUpper,
b.subsampling, true);
+ * b.pixelsToSampleValues(sampleModel);
+ * }
+ *
+ * This method converts the pixel coordinates to sample values
coordinates,
+ * assuming that the sample values will be stored in the raster in the
same order as they are read.
+ * This method also overwrites the {@link #sourceSize} with the
following values, in order:
+ *
+ * <blockquote>[(width × number of samples per pixel), (height),
(number of banks)]</blockquote>
+ *
+ * The arrays can have any length. If too long, extraneous dimensions
will be ignored.
+ * If too short, there is no attempt to update the missing values.
+ *
+ * @param sm the sample model for which to get the size and update
the subregion.
+ * @throws RasterFormatException if the sample model is not recognized.
+ */
+ @SuppressWarnings("fallthrough")
+ public void pixelsToSampleValues(final SampleModel sm) {
+ switch (sourceSize.length) {
+ default: sourceSize[BANK_INDEX] = 1; // Fallthrough in
all cases.
+ case 2: sourceSize[1] = sm.getHeight();
+ case 1: sourceSize[0] = sm.getWidth();
+ case 0: break;
+ }
+ final long n = sm.getNumDataElements();
+ if (n != 1) {
+ final int dimension;
+ if (sm instanceof PixelInterleavedSampleModel) {
+ dimension = 0; // Tile width
+ if (subsampling != null && subsampling[dimension] != 1) {
+ throw new RasterFormatException("Subsampling of pixel
interleave not yet supported.");
+ }
+ } else if (sm instanceof BandedSampleModel) {
+ dimension = BANK_INDEX;
+ } else {
+ throw unsupportedSampleModel(sm);
+ }
+ if (dimension < sourceSize.length) {
+ sourceSize[dimension] *= n;
+ if (regionLower != null) regionLower[dimension] *= n;
+ if (regionUpper != null) regionUpper[dimension] *= n;
+ }
+ }
+ }
+
+ /**
+ * Returns the exception to throw for an unsupported sample model.
+ *
+ * @param sm the unsupported sample model.
+ * @return the exception to throw.
+ */
+ static RasterFormatException unsupportedSampleModel(final SampleModel
sm) {
+ return new RasterFormatException(Resources.format(
+ Resources.Keys.UnsupportedSampleModel_1,
+
CharSequences.camelCaseToSentence(Classes.getShortClassName(sm))));
+ }
+
+ /**
+ * Sets the index of the <em>bank</em> (not band) to read.
+ * This is useful when each bank is read separately and stored in its
own array.
+ *
+ * @param bank index of the bank to read.
+ */
+ public void setBankToRead(final int bank) {
+ regionLower[BANK_INDEX] = bank;
+ regionUpper[BANK_INDEX] = bank + 1;
+ subsampling[BANK_INDEX] = 1;
+ }
+
+ /**
+ * Returns {@code true} if the region would contain all data of an
hyper-cube of the given size.
+ * This is true if {@link #regionLower} is equivalent to (0, 0, …, 0),
if {@link #regionUpper}
+ * and {@link #sourceSize} are both equivalent to the given {@code
size} argument,
+ * and there is no subsampling.
+ *
+ * @param size the expected size of data.
+ * @return whether the region would cover all data of the given size.
+ */
+ public boolean containsAll(final long[] size) {
+ return Arrays.equals(sourceSize, size) &&
+ (regionUpper == null || Arrays.equals(regionUpper,
sourceSize)) &&
+ (regionLower == null || ArraysExt.allEquals(regionLower,
0)) &&
+ (subsampling == null || ArraysExt.allEquals(subsampling,
1));
+ }
+
+ /**
+ * Creates a new region with the fields of this builder as arguments.
+ *
+ * @return the region created from the values of the builder.
+ * @throws ArithmeticException if a size is too large.
+ */
+ public Region build() {
+ return new Region(sourceSize, regionLower, regionUpper,
subsampling);
+ }
+ }
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
index 7f2a153b78..d1c74eaa07 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.java
@@ -502,6 +502,11 @@ public class Resources extends IndexedResourceBundle {
*/
public static final short UnknownFormatFor_1 = 14;
+ /**
+ * Interleave “{0}” is not supported.
+ */
+ public static final short UnsupportedSampleModel_1 = 89;
+
/**
* Using {0} JDBC driver version {1}.{2}.
*/
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
index 19613a17b4..0f69cd797a 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources.properties
@@ -105,5 +105,6 @@ UnexpectedNumberOfCoordinates_4 = The \u201c{0}\u201d
feature at {1} has a {3}
UnexpectedSliceResolution_3 = Expected a resolution of {0} but got {1}
for the slice or tile \u201c{2}\u201d.
UnfilteredData = Unfiltered data.
UnknownFormatFor_1 = Format of \u201c{0}\u201d is not
recognized.
+UnsupportedSampleModel_1 = Interleave \u201c{0}\u201d is not
supported.
UsedOnlyIfNotEncoded = Used only if this information is not
encoded with the data.
UseJdbcDriverVersion_3 = Using {0} JDBC driver version {1}.{2}.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
index ad8399b738..dd0e6dd1ab 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/internal/Resources_fr.properties
@@ -110,5 +110,6 @@ UnexpectedNumberOfCoordinates_4 = L\u2019entit\u00e9
nomm\u00e9e \u00ab\u202f{
UnexpectedSliceResolution_3 = Une r\u00e9solution de {0} \u00e9tait
attendue mais une r\u00e9solution de {1} a \u00e9t\u00e9 trouv\u00e9e dans la
tranche ou tuile \u00ab\u202f{2}\u202f\u00bb.
UnfilteredData = Donn\u00e9es non-filtr\u00e9es.
UnknownFormatFor_1 = Le format de \u00ab\u202f{0}\u202f\u00bb
n\u2019est pas reconnu.
+UnsupportedSampleModel_1 = L\u2019entrelacement \u201c{0}\u201d
n\u2019est pas support\u00e9.
UsedOnlyIfNotEncoded = Utilis\u00e9 seulement si cette
information n\u2019est pas encod\u00e9e avec les donn\u00e9es.
UseJdbcDriverVersion_3 = Utilise le pilote JDBC
\u00ab\u202f{0}\u202f\u00bb version {1}.{2}.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
index 3421c2a561..3f0347ec64 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
@@ -37,6 +37,7 @@ import static java.lang.Math.decrementExact;
import static java.lang.Math.toIntExact;
import static java.lang.Math.floorDiv;
import org.opengis.util.GenericName;
+import org.apache.sis.coverage.SubspaceNotSpecifiedException;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.DisjointExtentException;
@@ -557,6 +558,10 @@ public abstract class TiledGridCoverage extends
GridCoverage {
*
* @param sliceExtent a subspace of this grid coverage, or {@code null}
for the whole image.
* @return the grid slice as a rendered image. Image location is relative
to {@code sliceExtent}.
+ * @throws MismatchedDimensionException if the given extent does not have
the same number of dimensions as this coverage.
+ * @throws SubspaceNotSpecifiedException if the given argument is not
sufficient for reducing the grid to a two-dimensional slice.
+ * @throws DisjointExtentException if the given extent does not intersect
this grid coverage.
+ * @throws CannotEvaluateException if this method cannot produce the
rendered image for another reason.
*/
@Override
public RenderedImage render(GridExtent sliceExtent) {
@@ -871,11 +876,11 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* This policy is based on the fact that this method is typically
invoked before a tile is read.</p>
*
* @return a newly created, initially empty raster.
+ *
+ * @see #getTileLocation()
*/
public WritableRaster createRaster() {
- final int x = getTileOrigin(xDimension);
- final int y = getTileOrigin(yDimension);
- final WritableRaster tile =
Raster.createWritableRaster(getCoverage().model, new Point(x, y));
+ final WritableRaster tile =
Raster.createWritableRaster(getCoverage().model, getTileLocation());
if (fireTileReadEvent) {
fireTileReadEvent(tile.getBounds());
}
@@ -907,6 +912,19 @@ public abstract class TiledGridCoverage extends
GridCoverage {
return tile;
}
+ /**
+ * Creates the coordinates of the upper-left corner of the tile at the
current <abbr>AOI</abbr> position.
+ * This is the ({@code minX}, {@code minY}) values of the tile created
by {@link #createRaster()}.
+ *
+ * @return coordinates of the upper-left pixel of the tile at the
current iterator position.
+ *
+ * @see #createRaster()
+ * @see #getTileCoordinatesInResource()
+ */
+ public Point getTileLocation() {
+ return new Point(getTileOrigin(xDimension),
getTileOrigin(yDimension));
+ }
+
/**
* Returns the location (relative to the cropped tile) of the
upper-left corner of the uncropped tile.
* This value is present under the following conditions:
@@ -966,17 +984,21 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* Rectangle sourceArea = iterator.imageToResource(validArea,
false);
*
* // Conversion from uncropped coordinates to cropped
coordinates.
- * getUncroppedTileLocation().ifPresent((p) ->
sourceArea.translate(p.x, p.y));
+ * iterator.getUncroppedTileLocation().ifPresent((p) ->
sourceArea.translate(p.x, p.y));
* }
* }
*
* @param subsampled whether to return coordinates with subsampling
applied.
* @return pixel to read inside the tile, or {@code null} if the
region is empty.
* @throws ArithmeticException if the tile coordinates overflow
32-bits integer capacity.
+ *
+ * @see #getUncroppedTileLocation()
+ * @see TileIterator#imageToResource(Rectangle, boolean)
*/
public Rectangle getRegionInsideTile(final boolean subsampled) {
- final long[] lower = new long[BIDIMENSIONAL];
- final long[] upper = new long[BIDIMENSIONAL];
+ final int dimension = Math.max(xDimension, yDimension) + 1;
+ final long[] lower = new long[dimension];
+ final long[] upper = new long[dimension];
if (getRegionInsideTile(lower, upper, null, subsampled)) {
return new Rectangle(
toIntExact(lower[xDimension]),
@@ -1000,21 +1022,25 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* The same values can be obtained by {@link #getSubsampling(int)}.</p>
*
* <p>This method is a generalization of {@link
#getRegionInsideTile(boolean)} to any number of dimensions.
- * See that method for a discussion about the {@code subsampled}
argument and a code snippet.</p>
+ * See that method for a discussion about the {@code subsampled}
argument and a code snippet.
+ * The given arrays can be of any length, but that length must be the
same for all arguments.
+ * If the given arrays are longer than necessary, the result is
truncated to the arrays length.
+ * If the given arrays are longer than necessary, the extraneous array
elements are unchanged.</p>
*
* @param lower a pre-allocated array where to store relative
coordinates of the first pixel.
* @param upper a pre-allocated array where to store relative
coordinates after the last pixel.
* @param subsampling a pre-allocated array where to store
subsampling, or {@code null} if not needed.
* @param subsampled whether to return coordinates with subsampling
applied.
* @return {@code true} on success, or {@code false} if the tile is
empty.
+ * @throws ArrayIndexOutOfBoundsException if the given arrays are not
of the same length.
*/
public boolean getRegionInsideTile(final long[] lower,
final long[] upper,
final long[] subsampling,
final boolean subsampled)
{
- int dimension = Math.min(lower.length, upper.length);
final TiledGridCoverage coverage = getCoverage();
+ int dimension = Math.min(coverage.readExtent.getDimension(),
Math.max(lower.length, upper.length));
if (subsampling != null) {
System.arraycopy(coverage.subsampling, 0, subsampling, 0,
dimension);
}
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java
index a68f265556..beb326bd17 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArraysExt.java
@@ -67,7 +67,7 @@ import java.lang.reflect.Array;
* objects.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.5
+ * @version 1.7
*
* @see Arrays
*
@@ -2230,6 +2230,24 @@ public final class ArraysExt {
return true;
}
+ /**
+ * Returns {@code true} if all values in the specified array are equal to
the specified value.
+ *
+ * @param array the array to check.
+ * @param value the expected value.
+ * @return {@code true} if all elements in the given array are equal to
the given value.
+ *
+ * @since 1.7
+ */
+ public static boolean allEquals(final long[] array, final long value) {
+ for (int i=0; i<array.length; i++) {
+ if (array[i] != value) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Returns {@code true} if all values in the specified array are equal to
the specified value.
*
diff --git
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Image.java
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Image.java
index cea5719bb0..bf91a4b2d5 100644
---
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Image.java
+++
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Image.java
@@ -127,7 +127,7 @@ abstract class Image {
*
* @param input a view of the byte sequences as if they were stored
in one single large extent.
* @return tile filled with the pixel values read by this method.
- * @throws Exception any I/O error, arithmetic error or other kinds of
error.
+ * @throws Exception any I/O error, arithmetic error, raster format
error or other kinds of error.
*/
Raster readTile(ChannelDataInput input) throws Exception;
}
diff --git
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
index 76f9879804..9d7d56d382 100644
---
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
+++
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
@@ -30,6 +30,7 @@ import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
+import java.awt.image.RasterFormatException;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import org.opengis.metadata.Metadata;
@@ -39,6 +40,7 @@ import
org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.image.internal.shared.TilePlaceholder;
import org.apache.sis.metadata.iso.DefaultMetadata;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreException;
@@ -46,6 +48,7 @@ import org.apache.sis.storage.base.StoreResource;
import org.apache.sis.storage.tiling.TiledGridCoverage;
import org.apache.sis.storage.tiling.TiledGridCoverageResource;
import org.apache.sis.storage.isobmff.ByteRanges;
+import org.apache.sis.io.stream.Region;
import org.apache.sis.io.stream.ChannelDataInput;
import org.apache.sis.io.stream.inflater.ComputedByteChannel;
import org.apache.sis.util.internal.shared.Numerics;
@@ -317,6 +320,7 @@ final class ImageResource extends TiledGridCoverageResource
implements StoreReso
*
* @param iterator an iterator over the tiles that intersect the
Area Of Interest specified by user.
* @return tiles decoded from the enclosing resource.
+ * @throws Exception any I/O error, arithmetic error, raster format
error or other kinds of error.
*/
@Override
protected Raster[] readTiles(final TileIterator iterator) throws
Exception {
@@ -363,6 +367,9 @@ final class ImageResource extends TiledGridCoverageResource
implements StoreReso
* the image reader to use in the case of read operations delegated to
Image I/O.
* An instance of this class exist for each tile and is discarded
after the tile
* has been read.
+ *
+ * <p><b>Note:</b> this is defined as an inner class of {@link
TiledGridCoverage} subclass
+ * because, at the time of writing this class, {@link AOI} is a
protected class.</p>
*/
static final class ReadContext extends ByteRanges {
/**
@@ -399,6 +406,8 @@ final class ImageResource extends TiledGridCoverageResource
implements StoreReso
* @param inflater an initially empty reference to an inflater
to recycle.
* @param owner the resource for which to read a tile.
* @throws DataStoreException if an error occurred while computing
the range of bytes.
+ * @throws ArithmeticException if an overflow occurred during
integer arithmetic.
+ * @throws RasterFormatException if the sample model is not
recognized.
*/
@SuppressWarnings("LeakingThisInConstructor")
private ReadContext(final AOI iterator,
@@ -424,6 +433,7 @@ final class ImageResource extends TiledGridCoverageResource
implements StoreReso
*
* @param input the input from which to read bytes.
* @param result where to store the result.
+ * @throws Exception any I/O error, arithmetic error, raster
format error or other kinds of error.
*/
final void readTile(ChannelDataInput input, final Raster[] result)
throws Exception {
input = viewAsConsecutiveBytes(input);
@@ -432,6 +442,25 @@ final class ImageResource extends
TiledGridCoverageResource implements StoreReso
result[iterator.getTileIndexInResultArray()] = raster;
}
+ /**
+ * Gets offsets of the <em>sample values</em> to read, relative to
the beginning of the tiles.
+ * If that region is empty, returns an empty raster that the
caller should return immediately.
+ *
+ * @param builder the builder to update.
+ * @return an empty raster if the region to read is empty, or
{@code null} otherwise.
+ * @throws RasterFormatException if the given sample model is
unsupported.
+ * @throws ArithmeticException if an overflow occurred during
integer arithmetic.
+ */
+ final Raster getRegionInsideTile(final Region.Builder builder,
+ final SampleModel sampleModel)
+ {
+ if (iterator.getRegionInsideTile(builder.regionLower,
builder.regionUpper, builder.subsampling, true)) {
+ builder.pixelsToSampleValues(sampleModel);
+ return null;
+ }
+ return
TilePlaceholder.empty(sampleModel).create(iterator.getTileLocation());
+ }
+
/**
* Returns the destination raster. This method shall be invoked at
most once per tile.
* It may be invoked not at all if the read operation is delegated
to Image I/O,
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 070f6161a5..d1d77f30c5 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,13 +16,13 @@
*/
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;
import java.awt.image.SampleModel;
+import java.awt.image.Raster;
import java.awt.image.WritableRaster;
-import java.awt.image.BandedSampleModel;
+import java.awt.image.RasterFormatException;
import org.apache.sis.image.DataType;
import org.apache.sis.image.internal.shared.ImageUtilities;
import org.apache.sis.image.internal.shared.RasterFactory;
@@ -118,26 +118,6 @@ class UncompressedImage extends Image {
}
}
- /**
- * Returns [(width × number of samples per pixel), (height), (number of
banks)], in that order.
- * The banks are assumed to be consecutive.
- *
- * <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, raster height and number of banks, in that
order.
- */
- private static long[] size(final SampleModel sampleModel) {
- long width = sampleModel.getWidth();
- long numBanks = sampleModel.getNumDataElements();
- if (!(sampleModel instanceof BandedSampleModel)) {
- width *= numBanks;
- numBanks = 1;
- }
- return new long[] {width, sampleModel.getHeight(), numBanks};
- }
-
/**
* Computes the range of bytes that will be needed for reading the tile at
the specified index.
* The given region is converted to offsets relatives to the beginning of
the <abbr>HEIF</abbr>
@@ -179,15 +159,27 @@ class UncompressedImage extends 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.
- * @throws ArithmeticException if an offset or index overflows the
capacity of 32-bits integers.
+ * @throws ArithmeticException if an overflow occurred during integer
arithmetic.
+ * @throws RasterFormatException if the sample model is unsupported.
*/
@Override
protected final Reader computeByteRanges(final
ImageResource.Coverage.ReadContext context) throws DataStoreException {
- final long[] sourceSize = size(sampleModel);
- final var region = new Region(sourceSize, null, null, null);
- final long tileIndex = addExact(multiplyExact(context.subTileY,
numXTiles), context.subTileX);
+ final long[] sourceSize;
+ final Region region;
+ { // For local scope.
+ final var builder = new Region.Builder(3, false);
+ builder.pixelsToSampleValues(sampleModel);
+ sourceSize = builder.sourceSize;
+ region = builder.build();
+ }
+ final long tileIndex = addExact(multiplyExact(context.subTileY,
numXTiles), context.subTileX);
computeByteRanges(tileIndex, region, context);
return (ChannelDataInput input) -> {
+ final var builder = new Region.Builder(3, true);
+ final Raster empty = context.getRegionInsideTile(builder,
sampleModel);
+ if (empty != null) {
+ return empty;
+ }
long origin = context.offset();
ComputedByteChannel inflater = context.reuseInflater();
if (inflater == null || inflater.compressedInput() != input) {
@@ -210,15 +202,12 @@ class UncompressedImage extends Image {
hr.setOrigin(origin);
Region bank = region;
final WritableRaster raster = context.createRaster();
- final long[] upper = size(raster.getSampleModel());
final DataBuffer target = raster.getDataBuffer();
final int numBanks = target.getNumBanks();
for (int b=0; b<numBanks; b++) {
- upper[2] = b + 1;
- if (b != 0 || !Arrays.equals(sourceSize, upper)) {
- final long[] lower = new long[upper.length];
- lower[2] = b;
- bank = new Region(sourceSize, lower, upper, null);
+ builder.setBankToRead(b);
+ if (b != 0 || !builder.containsAll(sourceSize)) {
+ bank = builder.build();
}
hr.setDestination(RasterFactory.wrapAsBuffer(target, b));
hr.readAsBuffer(bank, 0);