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

commit e96b37e1a3b919d40603926ebade38306ec2e92d
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Dec 6 15:54:53 2021 +0100

    `ComputedImage.prefetch(Rectangle)` method for notifying that many tiles 
will be fetched.
    It allows implementations to allocate and release resources once for the 
group of tiles,
    or to compute them in some specific order (e.g. sequential order during I/O 
operations).
---
 .../apache/sis/image/BandedSampleConverter.java    | 20 +++++-
 .../java/org/apache/sis/image/ComputedImage.java   | 22 +++++++
 .../java/org/apache/sis/image/ImageAdapter.java    | 21 ++++++-
 .../java/org/apache/sis/image/PlanarImage.java     | 13 ++++
 .../java/org/apache/sis/image/PrefetchedImage.java | 22 +++++--
 .../java/org/apache/sis/image/ResampledImage.java  | 28 +++++++++
 .../org/apache/sis/image/SourceAlignedImage.java   | 21 +++++++
 .../sis/internal/coverage/j2d/ImageUtilities.java  | 73 +++++++++++++++++++---
 8 files changed, 206 insertions(+), 14 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
 
b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
index 2d7522e..04fa3c8 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
@@ -36,6 +36,7 @@ import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
 import org.apache.sis.internal.coverage.j2d.WriteSupport;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.Numbers;
+import org.apache.sis.util.Disposable;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.measure.NumberRange;
@@ -61,7 +62,7 @@ import org.apache.sis.measure.NumberRange;
  * In such case, writing converted values will cause the corresponding source 
values to be updated too.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -342,6 +343,23 @@ class BandedSampleConverter extends ComputedImage {
     }
 
     /**
+     * Notifies the source image that tiles will be computed soon in the given 
region.
+     * If the source image is an instance of {@link PlanarImage}, then this 
method
+     * forwards the notification to it. Otherwise default implementation does 
nothing.
+     */
+    @Override
+    protected Disposable prefetch(final Rectangle tiles) {
+        final RenderedImage source = getSource();
+        if (source instanceof PlanarImage) {
+            final Rectangle pixels = ImageUtilities.tilesToPixels(this, tiles);
+            ImageUtilities.clipBounds(source, pixels);
+            return ((PlanarImage) 
source).prefetch(ImageUtilities.pixelsToTiles(source, pixels));
+        } else {
+            return super.prefetch(tiles);
+        }
+    }
+
+    /**
      * A {@code BandedSampleConverter} capable to retro-propagate the changes 
to the source coverage.
      * This class contains the inverse of all {@link MathTransform1D} given to 
the parent class.
      */
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
index 12d3b58..2ad2aa3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
@@ -573,6 +573,28 @@ public abstract class ComputedImage extends PlanarImage 
implements Disposable {
     }
 
     /**
+     * Notifies this image that tiles will be computed soon in the given 
region.
+     * This method is invoked by {@link ImageProcessor#prefetch(RenderedImage, 
Rectangle)}
+     * before to request (potentially in multi-threads) all tiles in the area 
of interest.
+     * If the returned {@code Disposable} is non-null, {@code ImageProcessor} 
guarantees
+     * that the {@link Disposable#dispose()} method will be invoked after the 
prefetch
+     * operation completed, successfully or not.
+     *
+     * <p>The default implementation does nothing. Subclasses can override 
this method
+     * if they need to allocate and release resources once for a group of 
tiles.</p>
+     *
+     * @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.
+     *
+     * @since 1.2
+     */
+    @Override
+    protected Disposable prefetch(Rectangle tiles) {
+        return null;
+    }
+
+    /**
      * Returns whether any tile is under computation or is checked out for 
writing.
      * There is two reasons why this method may return {@code true}:
      *
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java
index c75124d..1482872 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageAdapter.java
@@ -25,6 +25,7 @@ import java.awt.image.RenderedImage;
 import java.awt.image.Raster;
 import java.awt.image.WritableRaster;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Disposable;
 
 
 /**
@@ -41,7 +42,7 @@ import org.apache.sis.util.ArgumentChecks;
  * and forward {@link #getTile(int, int)}, {@link #getData()} and other data 
methods to the source image.</div>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -124,6 +125,24 @@ abstract class ImageAdapter extends PlanarImage {
     @Override public final WritableRaster copyData(WritableRaster r) {return 
source.copyData(r);}
 
     /**
+     * Notifies the source image that tiles will be computed soon in the given 
region.
+     * If the source image is an instance of {@link PlanarImage}, then this 
method
+     * forwards the notification to it. Otherwise default implementation does 
nothing.
+     */
+    @Override
+    protected Disposable prefetch(final Rectangle tiles) {
+        if (source instanceof PlanarImage) {
+            /*
+             * Forwarding directly is possible because the contract
+             * of this class said that tile indices must be the same.
+             */
+            return ((PlanarImage) source).prefetch(tiles);
+        } else {
+            return super.prefetch(tiles);
+        }
+    }
+
+    /**
      * Compares the given object with this image for equality. This method 
should be quick and compare
      * how images compute their values from their sources; it should not 
compare the actual pixel values.
      *
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
index ff230d7..a778f23 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
@@ -28,6 +28,7 @@ import java.awt.image.RenderedImage;
 import java.util.Vector;
 import java.util.function.DoubleUnaryOperator;
 import org.apache.sis.util.Classes;
+import org.apache.sis.util.Disposable;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.Numerics;
@@ -510,6 +511,18 @@ public abstract class PlanarImage implements RenderedImage 
{
     }
 
     /**
+     * Notifies this image that tiles will be computed soon in the given 
region.
+     * The method contract is given by {@link 
ComputedImage#prefetch(Rectangle)}.
+     *
+     * @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.
+     */
+    Disposable prefetch(Rectangle tiles) {
+        return null;
+    }
+
+    /**
      * Verifies whether image layout information are consistent. This method 
verifies that the coordinates
      * of image upper-left corner are equal to the coordinates of the 
upper-left corner of the tile in the
      * upper-left corner, and that image size is equal to the sum of the sizes 
of all tiles. Compatibility
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
index fd15bb9..989c58e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
@@ -30,6 +30,7 @@ import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
 import org.apache.sis.internal.coverage.j2d.TilePlaceholder;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Disposable;
 
 
 /**
@@ -118,10 +119,23 @@ final class PrefetchedImage extends PlanarImage 
implements TileErrorHandler.Exec
         numYTiles = ti.height;
         tiles     = new Raster[Math.multiplyExact(numYTiles, numXTiles)];
         worker.setErrorHandler(errorHandler, ImageProcessor.class, "prefetch");
-        if (parallel) {
-            worker.parallelReadFrom(source);
-        } else {
-            worker.readFrom(source);
+        final Disposable ph = (source instanceof PlanarImage) ? ((PlanarImage) 
source).prefetch(ti) : null;
+        try {
+            if (parallel) {
+                worker.parallelReadFrom(source);
+            } else {
+                worker.readFrom(source);
+            }
+        } catch (Throwable ex) {
+            if (ph != null) try {
+                ph.dispose();
+            } catch (Throwable e) {
+                ex.addSuppressed(e);
+            }
+            throw ex;
+        }
+        if (ph != null) {
+            ph.dispose();
         }
         /*
          * If an error occurred during a tile computation, the array element 
corresponding
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
index f348c61..9f8ded6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
@@ -43,6 +43,7 @@ import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Disposable;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
@@ -877,6 +878,33 @@ public class ResampledImage extends ComputedImage {
     }
 
     /**
+     * Notifies the source image that tiles will be computed soon in the given 
region.
+     * If the source image is an instance of {@link ComputedImage}, then this 
method
+     * forwards the notification to it. Otherwise default implementation does 
nothing.
+     *
+     * @since 1.2
+     */
+    @Override
+    protected Disposable prefetch(final Rectangle tiles) {
+        final RenderedImage source = getSource();
+        if (source instanceof PlanarImage) try {
+            final Dimension s = interpolation.getSupportSize();
+            Rectangle pixels = ImageUtilities.tilesToPixels(this, tiles);
+            final Rectangle2D bounds = new Rectangle2D.Double(
+                    pixels.x      -  0.5  *  s.width,
+                    pixels.y      -  0.5  *  s.height,
+                    pixels.width  + (double) s.width,
+                    pixels.height + (double) s.height);
+            pixels = (Rectangle) 
Shapes2D.transform(MathTransforms.bidimensional(toSource), bounds, pixels);
+            ImageUtilities.clipBounds(source, pixels);
+            return ((PlanarImage) 
source).prefetch(ImageUtilities.pixelsToTiles(source, pixels));
+        } catch (TransformException e) {
+            recoverableException("prefetch", e);
+        }
+        return super.prefetch(tiles);
+    }
+
+    /**
      * Compares the given object with this image for equality. This method 
returns {@code true}
      * if the given object is non-null, is an instance of the exact same class 
than this image,
      * has equal sources and do the same resampling operation (same 
interpolation method,
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
index 1c3724e..e0602ef 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/image/SourceAlignedImage.java
@@ -17,10 +17,12 @@
 package org.apache.sis.image;
 
 import java.util.Set;
+import java.awt.Rectangle;
 import java.awt.image.ColorModel;
 import java.awt.image.SampleModel;
 import java.awt.image.RenderedImage;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Disposable;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.internal.jdk9.JDK9;
 
@@ -184,4 +186,23 @@ abstract class SourceAlignedImage extends ComputedImage {
     @Override public final int getTileHeight()      {return 
getSource().getTileHeight();}
     @Override public final int getTileGridXOffset() {return 
getSource().getTileGridXOffset();}
     @Override public final int getTileGridYOffset() {return 
getSource().getTileGridYOffset();}
+
+    /**
+     * Notifies the source image that tiles will be computed soon in the given 
region.
+     * If the source image is an instance of {@link PlanarImage}, then this 
method
+     * forwards the notification to it. Otherwise default implementation does 
nothing.
+     */
+    @Override
+    protected Disposable prefetch(final Rectangle tiles) {
+        final RenderedImage source = getSource();
+        if (source instanceof PlanarImage) {
+            /*
+             * Forwarding directly is possible because the contract
+             * of this class said that tile indices must be the same.
+             */
+            return ((PlanarImage) source).prefetch(tiles);
+        } else {
+            return super.prefetch(tiles);
+        }
+    }
 }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
index 948e5fe..200d66a 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ImageUtilities.java
@@ -40,6 +40,10 @@ import org.apache.sis.util.resources.Vocabulary;
 
 import static java.lang.Math.abs;
 import static java.lang.Math.rint;
+import static java.lang.Math.floorDiv;
+import static java.lang.Math.addExact;
+import static java.lang.Math.toIntExact;
+import static org.apache.sis.internal.jdk9.JDK9.multiplyFull;
 import static org.apache.sis.internal.util.Numerics.COMPARISON_THRESHOLD;
 
 
@@ -49,7 +53,7 @@ import static 
org.apache.sis.internal.util.Numerics.COMPARISON_THRESHOLD;
  * (see {@code *Factory} classes for creating those objects).
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -498,7 +502,7 @@ public final class ImageUtilities extends Static {
      * @return tile index for the given pixel coordinate.
      */
     public static int pixelToTileX(final RenderedImage image, final int x) {
-        return Math.toIntExact(Math.floorDiv((x - (long) 
image.getTileGridXOffset()), image.getTileWidth()));
+        return toIntExact(floorDiv((x - (long) image.getTileGridXOffset()), 
image.getTileWidth()));
     }
 
     /**
@@ -509,7 +513,7 @@ public final class ImageUtilities extends Static {
      * @return tile index for the given pixel coordinate.
      */
     public static int pixelToTileY(final RenderedImage image, final int y) {
-        return Math.toIntExact(Math.floorDiv((y - (long) 
image.getTileGridYOffset()), image.getTileHeight()));
+        return toIntExact(floorDiv((y - (long) image.getTileGridYOffset()), 
image.getTileHeight()));
     }
 
     /**
@@ -517,11 +521,12 @@ public final class ImageUtilities extends Static {
      * The returned value is a coordinate of the pixel in upper-left corner.
      *
      * @param  image  the image containing tiles.
-     * @param  tileX  the tile coordinate for which to get pixel coordinate.
+     * @param  tileX  the tile index for which to get pixel coordinate.
      * @return smallest <var>x</var> pixel coordinate inside the tile.
      */
     public static int tileToPixelX(final RenderedImage image, final int tileX) 
{
-        return Math.addExact(Math.multiplyExact(tileX, image.getTileWidth()), 
image.getTileGridXOffset());
+        // Following `long` arithmetic never overflows even if all values are 
`Integer.MAX_VALUE`.
+        return toIntExact(multiplyFull(tileX, image.getTileWidth()) + 
image.getTileGridXOffset());
     }
 
     /**
@@ -529,11 +534,63 @@ public final class ImageUtilities extends Static {
      * The returned value is a coordinate of the pixel in upper-left corner.
      *
      * @param  image  the image containing tiles.
-     * @param  tileY  the tile coordinate for which to get pixel coordinate.
+     * @param  tileY  the tile index for which to get pixel coordinate.
      * @return smallest <var>y</var> pixel coordinate inside the tile.
      */
     public static int tileToPixelY(final RenderedImage image, final int tileY) 
{
-        return Math.addExact(Math.multiplyExact(tileY, image.getTileHeight()), 
image.getTileGridYOffset());
+        return toIntExact(multiplyFull(tileY, image.getTileHeight()) + 
image.getTileGridYOffset());
+    }
+
+    /**
+     * Converts pixel coordinates to pixel indices.
+     * This method does <strong>not</strong> clip the rectangle to image 
bounds.
+     *
+     * @param  image   the image containing tiles.
+     * @param  pixels  the pixel coordinates for which to get tile indices.
+     * @return tile indices that fully contain the pixel coordinates.
+     */
+    public static Rectangle pixelsToTiles(final RenderedImage image, final 
Rectangle pixels) {
+        final Rectangle r = new Rectangle();
+        if (!pixels.isEmpty()) {
+            int  size;
+            long offset, shifted;
+            size     = image.getTileWidth();
+            offset   = image.getTileGridXOffset();
+            shifted  = pixels.x - offset;
+            r.x      = toIntExact(floorDiv(shifted, size));
+            r.width  = toIntExact(floorDiv(shifted + (pixels.width - 1), size) 
- r.x + 1);
+            size     = image.getTileHeight();
+            offset   = image.getTileGridYOffset();
+            shifted  = pixels.y - offset;
+            r.y      = toIntExact(floorDiv(shifted, size));
+            r.height = toIntExact(floorDiv(shifted + (pixels.height - 1), 
size) - r.y + 1);
+        }
+        return r;
+    }
+
+    /**
+     * Converts tile indices to pixel coordinate inside the tiles.
+     * Tiles will be fully included in the returned range of pixel indices.
+     * This method does <strong>not</strong> clip the rectangle to image 
bounds.
+     *
+     * @param  image  the image containing tiles.
+     * @param  tiles  the tile indices for which to get pixel coordinates.
+     * @return pixel coordinates that fully contain the tiles.
+     */
+    public static Rectangle tilesToPixels(final RenderedImage image, final 
Rectangle tiles) {
+        final Rectangle r = new Rectangle();
+        if (!tiles.isEmpty()) {
+            int size, offset;
+            size     = image.getTileWidth();
+            offset   = image.getTileGridXOffset();
+            r.x      = toIntExact(multiplyFull(tiles.x, size) + offset);
+            r.width  = toIntExact(((((long) tiles.x) + tiles.width) * size) + 
offset - r.x);
+            size     = image.getTileHeight();
+            offset   = image.getTileGridYOffset();
+            r.y      = toIntExact(multiplyFull(tiles.y, size) + offset);
+            r.height = toIntExact(((((long) tiles.y) + tiles.height) * size) + 
offset - r.y);
+        }
+        return r;
     }
 
     /**
@@ -553,7 +610,7 @@ public final class ImageUtilities extends Static {
         if (bounds.isEmpty()) {
             throw new 
RasterFormatException(Resources.format(Resources.Keys.EmptyTileOrImageRegion));
         }
-        final int afterLastRow = Math.addExact(bounds.y, bounds.height);
+        final int afterLastRow = addExact(bounds.y, bounds.height);
         int size;
         try {
             size = DataBuffer.getDataTypeSize(dataType);

Reply via email to