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 67cfdae67e Bug fixes in the application of artificial tiling on large
untiled images.
67cfdae67e is described below
commit 67cfdae67e47f09a949c88b09eb9cf8476411db6
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Aug 9 20:42:06 2026 +0200
Bug fixes in the application of artificial tiling on large untiled images.
---
.../main/org/apache/sis/image/PlanarImage.java | 68 ++++++++-----
.../org/apache/sis/image/internal/Summarizer.java | 8 +-
.../sis/image/internal/shared/ImageUtilities.java | 4 +-
.../sis/image/internal/shared/RasterFactory.java | 17 ++++
.../image/internal/shared/ScaledColorSpace.java | 2 +-
.../sis/image/internal/shared/TileOpExecutor.java | 7 +-
.../internal/shared/WritableUntiledImage.java | 109 ++++++++++++++++++---
.../sis/storage/tiling/ArtificiallyTiledImage.java | 57 ++++++++---
.../storage/tiling/TiledGridCoverageResource.java | 3 +-
.../org/apache/sis/util/resources/Messages.java | 5 +
.../apache/sis/util/resources/Messages.properties | 1 +
.../sis/util/resources/Messages_fr.properties | 3 +-
12 files changed, 223 insertions(+), 61 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java
index d57e48e447..4cadfc431d 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/PlanarImage.java
@@ -35,6 +35,7 @@ import org.apache.sis.util.resources.Errors;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridGeometry; // For javadoc
import org.apache.sis.image.internal.Summarizer;
+import org.apache.sis.image.internal.shared.RasterFactory;
import org.apache.sis.image.internal.shared.ImageUtilities;
import org.apache.sis.image.internal.shared.TileOpExecutor;
import org.apache.sis.feature.internal.Resources;
@@ -509,68 +510,89 @@ public abstract class PlanarImage implements
RenderedImage {
* This method does not verify argument validity.
*/
private WritableRaster createWritableRaster(final Rectangle aoi) {
- SampleModel sm = getSampleModel();
- if (sm.getWidth() != aoi.width || sm.getHeight() != aoi.height) {
- sm = sm.createCompatibleSampleModel(aoi.width, aoi.height);
- }
- return Raster.createWritableRaster(sm, aoi.getLocation());
+ return RasterFactory.createWritableRaster(getSampleModel(), aoi);
}
/**
* Returns a copy of this image as one large tile.
* The returned raster will not be updated if this image is changed.
+ * Invoking this method is equivalent to invoking {@code copyData(null)}.
+ *
+ * <h4>Warning about memory usage</h4>
+ * Invoking this method may cause an {@link OutOfMemoryError}.
+ * A {@code PlanarImage} may represent an image theoretically larger than
the memory capacity,
+ * but in which data loading or calculation are deferred until first
needed on a tile-by-tile basis.
+ * Invoking this method causes the immediate calculation of all tiles,
which may exceed memory capacity.
+ * This method should be invoked only when the caller has verified that
the image is reasonably small.
*
* @return a copy of this image as one large tile.
+ *
+ * @see #copyData(WritableRaster)
*/
@Override
public Raster getData() {
- final Rectangle aoi = getBounds();
- final WritableRaster raster = createWritableRaster(aoi);
- copyData(aoi, this, raster);
- return raster;
+ return copyData(null);
}
/**
* Returns a copy of an arbitrary region of this image.
+ * The given Area Of Interest (<abbr>AOI</abbr>) shall be contained inside
the image bounds,
* The returned raster will not be updated if this image is changed.
*
* @param aoi the region of this image to copy.
* @return a copy of this image in the given area of interest.
- * @throws IllegalArgumentException if the given rectangle is not
contained in this image bounds.
+ * @throws IllegalArgumentException if the given rectangle is empty or is
not contained inside this image bounds.
*/
@Override
- public Raster getData(final Rectangle aoi) {
+ public Raster getData(Rectangle aoi) {
+ aoi = new Rectangle(aoi);
+ if (aoi.isEmpty()) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "aoi"));
+ }
if (!getBounds().contains(aoi)) {
throw new
IllegalArgumentException(Errors.format(Errors.Keys.OutsideDomainOfValidity));
}
- final WritableRaster raster = createWritableRaster(aoi);
- copyData(aoi, this, raster);
- return raster;
+ final WritableRaster target = createWritableRaster(aoi);
+ copyData(aoi, this, target);
+ return target;
}
/**
* Copies an arbitrary rectangular region of this image to the supplied
writable raster.
- * The region to be copied is determined from the bounds of the supplied
raster.
+ * The region to be copied is determined from the bounds of the supplied
target raster.
* The supplied raster must have a {@link SampleModel} that is compatible
with this image.
* If the given raster is {@code null}, a new raster is created by this
method.
*
- * @param raster the raster to hold a copy of this image, or {@code
null}.
- * @return the given raster if it was not-null, or a new raster otherwise.
+ * <h4>Handling of regions outside the image bounds</h4>
+ * The bounds of the {@code target} raster should intersect the bounds of
this image,
+ * but this method nevertheless accepts target raster located anywhere.
+ * Only the pixels inside the intersection are copied and the other pixels
are unchanged.
+ * This tolerance is useful when using tile sizes that are not divisor of
the image size.
+ * Note that different {@link RenderedImage} implementations may have
different policies.
+ *
+ * <h4>Warning about memory usage</h4>
+ * Invoking this method with a {@code null} argument may cause an {@link
OutOfMemoryError}.
+ * A {@code PlanarImage} may represent an image theoretically larger than
the memory capacity,
+ * but in which data loading or calculation are deferred until first
needed on a tile-by-tile basis.
+ * A null argument causes the immediate calculation of all tiles, which
may exceed memory capacity.
+ *
+ * @param target the raster to hold a copy of this image, or {@code
null}.
+ * @return the given raster if it was not null, or a new raster otherwise.
*/
@Override
- public WritableRaster copyData(WritableRaster raster) {
+ public WritableRaster copyData(WritableRaster target) {
final Rectangle aoi;
- if (raster != null) {
- aoi = raster.getBounds();
+ if (target != null) {
+ aoi = target.getBounds();
ImageUtilities.clipBounds(this, aoi);
} else {
aoi = getBounds();
- raster = createWritableRaster(aoi);
+ target = createWritableRaster(aoi);
}
if (!aoi.isEmpty()) {
- copyData(aoi, this, raster);
+ copyData(aoi, this, target);
}
- return raster;
+ return target;
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/Summarizer.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/Summarizer.java
index 0d4036a4d9..72d607cebb 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/Summarizer.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/Summarizer.java
@@ -218,8 +218,12 @@ public final class Summarizer {
if (!(data instanceof PlanarImage)) continue;
String warning = ((PlanarImage) data).verify();
if (warning == null) continue;
- row = new Summarizer((short) 0,
-
Messages.forLocale(locale).getString(Messages.Keys.PossibleInconsistency_1,
warning));
+ final Messages r = Messages.forLocale(locale);
+ row = new Summarizer((short) 0,
r.getString(Messages.Keys.PossibleInconsistency_1, warning));
+ String tip = warning.substring(warning.lastIndexOf('.') +
1);
+ if (tip.equals("width") || tip.equals("height")) {
+ row.note =
r.getString(Messages.Keys.PartiallyFilledTiles);
+ }
break;
}
/*
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java
index 137751b326..dd572cd365 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ImageUtilities.java
@@ -473,7 +473,7 @@ public final class ImageUtilities {
* @throws ArithmeticException if the result overflows 32 bits integer.
*/
public static Rectangle pixelsToTiles(final RenderedImage image, final
Rectangle pixels) {
- final Rectangle r = new Rectangle();
+ final var r = new Rectangle();
if (!pixels.isEmpty()) {
int size;
long offset, shifted;
@@ -507,7 +507,7 @@ public final class ImageUtilities {
* @throws ArithmeticException if the result overflows 32 bits integer.
*/
public static Rectangle tilesToPixels(final RenderedImage image, final
Rectangle tiles) {
- final Rectangle r = new Rectangle();
+ final var r = new Rectangle();
if (!tiles.isEmpty()) {
int size, offset;
size = image.getTileWidth();
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java
index 230e8ad398..adf3e41cf2 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/RasterFactory.java
@@ -17,6 +17,7 @@
package org.apache.sis.image.internal.shared;
import java.awt.Point;
+import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.awt.image.BandedSampleModel;
@@ -32,6 +33,7 @@ import java.awt.image.DataBufferUShort;
import java.awt.image.RasterFormatException;
import java.awt.image.WritableRaster;
import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
@@ -100,6 +102,21 @@ public final class RasterFactory {
return new WritableUntiledImage(cm,
cm.createCompatibleWritableRaster(width, height), false, null);
}
+ /**
+ * Creates a raster with the given sample model or a compatible one, and
with the given size and location.
+ * This method does not verify argument validity.
+ *
+ * @param model the sample model. Will be resized if needed.
+ * @param bounds the raster bounds.
+ * @return a raster with the given bounds.
+ */
+ public static WritableRaster createWritableRaster(SampleModel model, final
Rectangle bounds) {
+ if (model.getWidth() != bounds.width || model.getHeight() !=
bounds.height) {
+ model = unique(model.createCompatibleSampleModel(bounds.width,
bounds.height));
+ }
+ return Raster.createWritableRaster(model, bounds.getLocation());
+ }
+
/**
* Wraps the given data buffer in a raster.
* The sample model type is selected according the number of bands and the
pixel stride.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java
index b5cb1e923a..51050dca2f 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ScaledColorSpace.java
@@ -190,7 +190,7 @@ final class ScaledColorSpace extends ColorSpace {
final StringBuilder formatRange(final StringBuilder buffer) {
return buffer.append('[').append(offset)
.append(" … ").append(maximum)
- .append(" in band ").append(visibleBand).append(']');
+ .append("] in band ").append(visibleBand);
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TileOpExecutor.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TileOpExecutor.java
index a7863e9bc8..6b2731adbe 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TileOpExecutor.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TileOpExecutor.java
@@ -331,7 +331,7 @@ public class TileOpExecutor {
*/
public final void parallelReadFrom(final RenderedImage source) {
if (isMultiTiled()) {
- executeOnReadable(source, executor((ignore,tile) -> {
+ executeOnReadable(source, executor((ignore, tile) -> {
try {
readFrom(tile);
} catch (Exception ex) {
@@ -370,7 +370,7 @@ public class TileOpExecutor {
*/
public final void parallelWriteTo(final WritableRenderedImage target) {
if (isMultiTiled()) {
- executeOnWritable(target, executor((ignore,tile) -> {
+ executeOnWritable(target, executor((ignore, tile) -> {
try {
writeTo(tile);
} catch (Exception ex) {
@@ -987,9 +987,10 @@ public class TileOpExecutor {
* @throws RuntimeException if any error occurred during the process.
*/
@Override
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
protected void executeOnCurrentTile() {
final WritableRenderedImage image = cursor.image;
- final int tx = super.tx; // Protect
from changes (paranoiac safety).
+ final int tx = super.tx; // Protect from changes (paranoiac
safety).
final int ty = super.ty;
final WritableRaster tile = image.getWritableTile(tx, ty);
try {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableUntiledImage.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableUntiledImage.java
index 77a738ec84..5ced14968e 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableUntiledImage.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableUntiledImage.java
@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Hashtable;
import java.util.function.Function;
import java.awt.Point;
+import java.awt.Rectangle;
import java.awt.image.TileObserver;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
@@ -29,9 +30,10 @@ import java.awt.image.WritableRaster;
import java.awt.image.WritableRenderedImage;
import java.awt.image.ImagingOpException;
import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.resources.Errors;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.feature.internal.Resources;
-import static org.apache.sis.image.PlanarImage.GRID_GEOMETRY_KEY;
+import org.apache.sis.image.PlanarImage;
/**
@@ -41,17 +43,20 @@ import static
org.apache.sis.image.PlanarImage.GRID_GEOMETRY_KEY;
* This class is also preferred to instances of the exact {@link BufferedImage}
* class for the following reasons:
*
- * <p>First, this class can notify tile observers when tiles are acquired for
write operations.
- * We cannot prevent {@link BufferedImage} to implement {@link
WritableRenderedImage}, but we can
- * increase the chances that Apache <abbr>SIS</abbr> is notified about pixel
data modifications.
- * For example, images given to {@link
org.apache.sis.coverage.grid.GridCoverage2D} constructor
- * are often used as sources of {@link org.apache.sis.image.ImageProcessor}
operations,
- * which listen to tile changes in order to flush the cache of invalidated
tiles.</p>
- *
- * <p>Second, this class can compute the {@value
org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY}
- * property when first needed. We use this class even when the property value
is known in advance
- * because it has the desired side-effect of not letting {@link
#getSubimage(int, int, int, int)}
- * inherit that property.</p>
+ * <ul class="verbose">
+ * <li>This class can notify tile observers when tiles are acquired for
write operations.
+ * We cannot prevent {@link BufferedImage} to implement {@link
WritableRenderedImage}, but we can
+ * increase the chances that Apache <abbr>SIS</abbr> is notified about
pixel data modifications.
+ * For example, images given to {@link
org.apache.sis.coverage.grid.GridCoverage2D} constructor
+ * are often used as sources of {@link
org.apache.sis.image.ImageProcessor} operations,
+ * which listen to tile changes in order to flush the cache of invalidated
tiles.</li>
+ * <li>This class can compute the {@value
org.apache.sis.image.PlanarImage#GRID_GEOMETRY_KEY}
+ * property when first needed. We use this class even when the property
value is known in advance
+ * because it has the desired side-effect of not letting {@link
#getSubimage(int, int, int, int)}
+ * inherit that property.</li>
+ * <li>This class implements {@link #getData(Rectangle)} by delegating to
more efficient Java2D methods
+ * and with a tolerance required by Apache <abbr>SIS</abbr> regarding
intersections.</li>
+ * </ul>
*
* <p>This class provides also static helper methods for {@link
WritableRenderedImage} implementations.</p>
*
@@ -136,9 +141,9 @@ public final class WritableUntiledImage extends
BufferedImage {
String[] names = super.getPropertyNames(); // May be null.
if (gridGeometry != null) {
if (names == null) {
- names = new String[] {GRID_GEOMETRY_KEY};
+ names = new String[] {PlanarImage.GRID_GEOMETRY_KEY};
} else {
- names = ArraysExt.append(names, GRID_GEOMETRY_KEY);
+ names = ArraysExt.append(names, PlanarImage.GRID_GEOMETRY_KEY);
}
}
return names;
@@ -156,7 +161,7 @@ public final class WritableUntiledImage extends
BufferedImage {
@Override
@SuppressWarnings("unchecked")
public Object getProperty(final String name) {
- if (GRID_GEOMETRY_KEY.equals(name)) {
+ if (PlanarImage.GRID_GEOMETRY_KEY.equals(name)) {
synchronized (this) {
if (gridGeometry != null) {
if (gridGeometry instanceof GridGeometry) {
@@ -347,6 +352,80 @@ public final class WritableUntiledImage extends
BufferedImage {
return writeCount != 0;
}
+ /**
+ * Returns a copy of this image as one large tile.
+ * The returned raster will not be updated if this image is changed.
+ *
+ * <p>Note: the implementation in Java 25 allocates a whole new tile if
the raster is a subtile.
+ * By contrast, the implementation in this class allocates only the space
required by the subtile.</p>
+ *
+ * @return a copy of this image as one large tile.
+ *
+ * @see PlanarImage#getData()
+ */
+ @Override
+ public Raster getData() {
+ return copyData(null);
+ }
+
+ /**
+ * Returns a copy of an arbitrary region of this image.
+ * The returned raster will not be updated if this image is changed.
+ *
+ * <h4>Handling of regions outside the image bounds</h4>
+ * The given Area Of Interest (<abbr>AOI</abbr>) shall intersect the image
bounds,
+ * but does not need to be fully contained inside those bounds.
+ * If {@code aoi} is partially outside the image bounds,
+ * only the pixels inside the intersection are copied and the other pixels
are set to 0.
+ * This is useful when re-tiling with a tile size which is not divisor of
the image size.
+ * Note that different {@link RenderedImage} implementations may have
different policies.
+ *
+ * @param aoi the region of this image to copy.
+ * @return a copy of this image in the given area of interest.
+ *
+ * @see PlanarImage#getData(Rectangle)
+ * @throws IllegalArgumentException if the given rectangle is empty or
does not intersect this image bounds.
+ */
+ @Override
+ public Raster getData(final Rectangle aoi) {
+ if (aoi.isEmpty()) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "aoi"));
+ }
+ return copyData(RasterFactory.createWritableRaster(getSampleModel(),
aoi));
+ }
+
+ /**
+ * Copies an arbitrary rectangular region of this image to the supplied
writable raster.
+ * The region to be copied is determined from the bounds of the supplied
target raster.
+ *
+ * <h4>Handling of regions outside the image bounds</h4>
+ * The bounds of the {@code target} raster shall intersect the bounds of
this image.
+ * Only the pixels inside the intersection are copied and the other pixels
are unchanged.
+ * This tolerance is useful when using tile sizes that are not divisor of
the image size.
+ * Note that different {@link RenderedImage} implementations may have
different policies.
+ *
+ * @param target the raster to hold a copy of this image, or {@code
null}.
+ * @return the given raster if it was not null, or a new raster otherwise.
+ *
+ * @see PlanarImage#copyData(WritableRaster)
+ */
+ @Override
+ public WritableRaster copyData(WritableRaster target) {
+ Raster source = getRaster();
+ Rectangle aoi = source.getBounds();
+ if (target == null) {
+ target =
RasterFactory.createWritableRaster(source.getSampleModel(), aoi);
+ } else if (!aoi.equals(aoi = aoi.intersection(target.getBounds()))) {
+ if (aoi.isEmpty()) {
+ // Note: this is stricter than `PlanarImage.copy(target)`, but
useful for debugging.
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.OutsideDomainOfValidity));
+ }
+ source = source.createChild(aoi.x, aoi.y, aoi.width, aoi.height,
aoi.x, aoi.y, null);
+ }
+ target.setRect(source);
+ return target;
+ }
+
/**
* Sets a region of the image to the contents of the given raster.
* The raster is assumed to be in the same coordinate space as this image.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ArtificiallyTiledImage.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ArtificiallyTiledImage.java
index 873a7fa6e2..ba659a2afb 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ArtificiallyTiledImage.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ArtificiallyTiledImage.java
@@ -21,6 +21,8 @@ import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.ColorModel;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.coverage.grid.GridExtent;
@@ -153,8 +155,8 @@ final class ArtificiallyTiledImage extends
BatchComputedImage {
final int numXTiles = tiles.width;
final var rasters = new Raster[Math.multiplyExact(numXTiles,
tiles.height)];
for (int i = 0; i < rasters.length; i++) {
- final int x = i % numXTiles;
- final int y = i / numXTiles;
+ final int x = tiles.x + i % numXTiles;
+ final int y = tiles.y + i / numXTiles;
if ((rasters[i] = cache.get(new Point(x, y))) == null) {
if (x < minTileX) minTileX = x;
if (x > maxTileX) maxTileX = x;
@@ -198,20 +200,51 @@ final class ArtificiallyTiledImage extends
BatchComputedImage {
final GridCoverage coverage = source.readAtGetTileTime(request,
requestedBands);
extent = coverage.getGridGeometry().extentOf(request,
PixelInCell.CELL_CORNER, GridRoundingMode.NEAREST);
final RenderedImage image = coverage.render(extent);
- final int tileWidth = getTileWidth();
- final int tileHeight = getTileHeight();
- final var tileBounds = new Rectangle();
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ final SampleModel sampleModel = getSampleModel();
+ final long offsetX = Math.multiplyFull(minTileX, getTileWidth());
+ final long offsetY = Math.multiplyFull(minTileY, getTileHeight());
for (int y = minTileY; y <= maxTileY; y++) {
for (int x = minTileX; x <= maxTileX; x++) {
// No integer arithmetic can overflow in this loop.
- final int i = y * numXTiles + x;
+ final int i = (y - tiles.y) * numXTiles + (x - tiles.x);
if (rasters[i] == null) {
- // By contract, image pixel coordinates (0,0)
correspond to (minTileX, minTileY) in the request.
- tileBounds.x = Math.multiplyExact(x - minTileX,
tileWidth);
- tileBounds.y = Math.multiplyExact(y - minTileY,
tileHeight);
- tileBounds.width = tileWidth;
- tileBounds.height = tileHeight;
- rasters[i] = cache.computeIfAbsent(new Point(x, y),
(key) -> image.getData(tileBounds));
+ rasters[i] = cache.computeIfAbsent(new Point(x, y),
(key) -> {
+ final int tileWidth = sampleModel.getWidth();
+ final int tileHeight = sampleModel.getHeight();
+ final int tileMinX = Math.multiplyExact(key.x,
tileWidth);
+ final int tileMinY = Math.multiplyExact(key.y,
tileHeight);
+ WritableRaster tile =
Raster.createWritableRaster(sampleModel, new Point(tileMinX, tileMinY));
+ /*
+ * By contract, image pixel coordinates (0,0)
correspond to (minTileX, minTileY) in the request.
+ * We need to temporarily translate the raster
where pixel values will be copied.
+ * The original raster is the parent of the
translated raster.
+ */
+ if ((offsetX | offsetY) != 0) {
+ tile = tile.createWritableTranslatedChild(
+ Math.toIntExact(tileMinX - offsetX),
+ Math.toIntExact(tileMinY - offsetY));
+ }
+ Raster copy = image.copyData(tile);
+ /*
+ * Get the untranslated raster. It should be
`tile` directory if we did not applied
+ * any translation, or the direct parent of `tile`
other. We nevertheless search in
+ * all parents in case and fallback on a new
raster if no parent is found.
+ */
+ while (copy.getMinX() != tileMinX ||
+ copy.getMinY() != tileMinY ||
+ copy.getWidth() != tileWidth ||
+ copy.getHeight() != tileHeight)
+ {
+ Raster parent = copy.getParent();
+ if (parent == null) {
+ // Should never happen, but defined for
safety.
+ return
tile.createTranslatedChild(tileMinX, tileMinY);
+ }
+ copy = parent;
+ }
+ return copy;
+ });
}
}
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
index 006ceff53b..7d9655bd5a 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
@@ -872,8 +872,7 @@ check: if (dataType.isInteger()) {
if (virtualSize / stride > ImageLayout.MAX_TILE_SIZE) {
// Tile is too large, even after subsampling.
if (i == xDimension || i == yDimension) {
- // TODO: need more tests
- // applyArtificialTiling |= !loadAtReadTime;
+ applyArtificialTiling |= !loadAtReadTime;
}
}
virtualTileSize[i] = virtualSize;
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.java
index fec0b6a6d7..e06dbad146 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.java
@@ -221,6 +221,11 @@ public class Messages extends IndexedResourceBundle {
*/
public static final short OptionalModuleNotFound_1 = 27;
+ /**
+ * Some tiles may be only partially filled.
+ */
+ public static final short PartiallyFilledTiles = 38;
+
/**
* Possible inconsistency in “{0}”.
*/
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.properties
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.properties
index d9fe0e2450..56aea1e4a6 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.properties
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages.properties
@@ -45,6 +45,7 @@ JNDINotSpecified_1 = No object associated to
the \u201c{0}\u201d J
LocalesDiscarded = Text were discarded for some locales.
NoDataSourceFound_1 = No source of {0} data has been found. Those
data may require an optional module or manual installation.
OptionalModuleNotFound_1 = Optional module \u201c{0}\u201d requested
but not found.
+PartiallyFilledTiles = Some tiles may be only partially filled.
PossibleInconsistency_1 = Possible inconsistency in \u201c{0}\u201d.
PropertyHiddenBy_2 = Property \u201c{0}\u201d is hidden by
\u201c{1}\u201d.
NonConformFormatting_1 = This \u201c{0}\u201d formatting is a
departure from standard format.
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages_fr.properties
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages_fr.properties
index ac6e9a2d99..30374b5d78 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages_fr.properties
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Messages_fr.properties
@@ -52,7 +52,8 @@ JNDINotSpecified_1 = Aucun objet n\u2019est
associ\u00e9 au nom JN
LocalesDiscarded = Des textes ont \u00e9t\u00e9 ignor\u00e9s
pour certaines langues.
NoDataSourceFound_1 = Aucune source de donn\u00e9es {0} n\u2019a
\u00e9t\u00e9 trouv\u00e9e. Ces donn\u00e9es peuvent n\u00e9cessiter un module
optionnel ou une installation manuelle.
OptionalModuleNotFound_1 = Le module optionnel
\u00ab\u202f{0}\u202f\u00bb a \u00e9t\u00e9 demand\u00e9 mais n\u2019a pas
\u00e9t\u00e9 trouv\u00e9.
-PossibleInconsistency_1 = Il y a possiblement une incoh\u00e9rence
dans \u00ab\u202f{0}\u202f\u00bb.
+PartiallyFilledTiles = Certaines tuiles pourraient n'\u00eatre que
partiellement remplies.
+PossibleInconsistency_1 = Il y a possiblement une incoh\u00e9rence
dans \u00ab\u202f{0}\u202f\u00bb.
PropertyHiddenBy_2 = La propri\u00e9t\u00e9
\u00ab\u202f{0}\u202f\u00bb est masqu\u00e9e par \u00ab\u202f{1}\u202f\u00bb.
NonConformFormatting_1 = Cette \u00e9criture de
\u00ab\u202f{0}\u202f\u00bb d\u00e9vie du format standard.
UnknownCodeList_1 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas
le nom d\u2019une liste de codes connue.