sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Add an Area Of Interest (AOI) argument in ImageProcessor.prefetch(…).
Date Sat, 16 May 2020 16:44:24 GMT
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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new a095d21  Add an Area Of Interest (AOI) argument in ImageProcessor.prefetch(…).
a095d21 is described below

commit a095d214c4e75984c084180974b02583499b840e
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat May 16 18:43:03 2020 +0200

    Add an Area Of Interest (AOI) argument in ImageProcessor.prefetch(…).
---
 .../apache/sis/gui/coverage/CoverageCanvas.java    |  20 ++-
 .../org/apache/sis/gui/coverage/RenderingData.java |  26 +++
 .../org/apache/sis/coverage/grid/GridCoverage.java |   5 +-
 .../java/org/apache/sis/image/AnnotatedImage.java  |   1 +
 .../java/org/apache/sis/image/ImageProcessor.java  |  63 +------
 .../java/org/apache/sis/image/PlanarImage.java     |   7 +-
 .../java/org/apache/sis/image/PrefetchedImage.java | 187 +++++++++++++++++++++
 .../sis/internal/coverage/j2d/ImageUtilities.java  |  22 +++
 .../sis/internal/coverage/j2d/TileOpExecutor.java  |   9 +-
 .../sis/internal/coverage/j2d/TiledImage.java      |   3 +-
 .../sis/internal/coverage/j2d/Transferer.java      |   4 +-
 .../sis/referencing/operation/matrix/Matrices.java |   2 +-
 12 files changed, 280 insertions(+), 69 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index b774cf0..aeb414d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -42,6 +42,7 @@ import org.apache.sis.coverage.grid.ImageRenderer;
 import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.image.PlanarImage;
 import org.apache.sis.gui.map.MapCanvas;
 import org.apache.sis.gui.map.MapCanvasAWT;
@@ -293,18 +294,30 @@ public class CoverageCanvas extends MapCanvasAWT {
         private RenderedImage stretchedImage;
 
         /**
-         * Conversion from {@link #resampledImage} (also {@link #stretchedImage}) pixel coordinates
-         * to display coordinates.
+         * The stretched image with tiles computed in advance. The set of prefetched
+         * tiles may differ at each rendering event. This image should not be cached.
+         */
+        private RenderedImage prefetchedImage;
+
+        /**
+         * Conversion from {@link #resampledImage} (also {@link #prefetchedImage})
+         * pixel coordinates to display coordinates.
          */
         private AffineTransform resampledToDisplay;
 
         /**
+         * Size and location of the display device, in pixel units.
+         */
+        private final Envelope2D displayBounds;
+
+        /**
          * Creates a new renderer.
          */
         Worker(final CoverageCanvas canvas) {
             data               = canvas.data.clone();
             objectiveCRS       = canvas.getObjectiveCRS();
             objectiveToDisplay = canvas.getObjectiveToDisplay();
+            displayBounds      = canvas.getDisplayBounds();
             if (data.validateCRS(objectiveCRS)) {
                 resampledImage = canvas.resampledImages.get(Stretching.NONE);
                 stretchedImage = canvas.resampledImages.get(data.selectedStretching);
@@ -334,6 +347,7 @@ public class CoverageCanvas extends MapCanvasAWT {
             if (stretchedImage == null) {
                 stretchedImage = data.stretch(resampledImage);
             }
+            prefetchedImage = data.prefetch(stretchedImage, resampledToDisplay, displayBounds);
         }
 
         /**
@@ -341,7 +355,7 @@ public class CoverageCanvas extends MapCanvasAWT {
          */
         @Override
         protected void paint(final Graphics2D gr) {
-            gr.drawRenderedImage(stretchedImage, resampledToDisplay);
+            gr.drawRenderedImage(prefetchedImage, resampledToDisplay);
         }
 
         /**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
index 0948e36..8de11f5 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
@@ -23,6 +23,7 @@ import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.CoordinateOperation;
@@ -32,6 +33,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.Shapes2D;
 import org.apache.sis.image.Interpolation;
 import org.apache.sis.image.ImageProcessor;
@@ -253,6 +255,9 @@ final class RenderingData implements Cloneable {
 
     /**
      * Creates the stretched image from the given resampled image.
+     *
+     * @param  resampledImage  the image computed by {@link #resample(CoordinateReferenceSystem,
LinearTransform)}.
+     * @return image with color ramp stretched. May be the same instance than given image.
      */
     final RenderedImage stretch(final RenderedImage resampledImage) {
         if (selectedStretching != Stretching.NONE) {
@@ -270,6 +275,27 @@ final class RenderingData implements Cloneable {
     }
 
     /**
+     * Computes immediately, possibly using many threads, the tiles that are going to be
displayed.
+     * The returned instance should be used only for current rendering event; it should not
be cached.
+     *
+     * @param  stretchedImage      the image computed by {@link #stretch(RenderedImage)}.
+     * @param  resampledToDisplay  the transform computed by {@link #getTransform(LinearTransform)}.
+     * @param  displayBounds       size and location of the display device, in pixel units.
+     * @return a temporary image with tiles intersecting the display region already computed.
+     */
+    final RenderedImage prefetch(final RenderedImage stretchedImage, final AffineTransform
resampledToDisplay,
+                                 final Envelope2D displayBounds)
+    {
+        try {
+            return processor.prefetch(stretchedImage, (Rectangle) AffineTransforms2D.transform(
+                        resampledToDisplay.createInverse(), displayBounds, new Rectangle()));
+        } catch (NoninvertibleTransformException e) {
+            recoverableException(e);
+            return stretchedImage;
+        }
+    }
+
+    /**
      * Gets the transform to use for painting the stretched image. If the image to draw is
an instance of
      * {@link BufferedImage}, then it is okay to have any transform. However for other kinds
of image,
      * it is important that the transform has scale factors of 1 and integer translations
because Java2D
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 06a859d..e4830a7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -34,6 +34,7 @@ import org.apache.sis.measure.NumberRange;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.util.collection.DefaultTreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTable;
@@ -314,8 +315,8 @@ public abstract class GridCoverage {
      * @throws IndexOutOfBoundsException if a coordinate is out of bounds.
      */
     static double[] evaluate(final RenderedImage data, final int x, final int y, final double[]
buffer) {
-        final int tx = Math.toIntExact(Math.floorDiv(x - (long) data.getTileGridXOffset(),
data.getTileWidth()));
-        final int ty = Math.toIntExact(Math.floorDiv(y - (long) data.getTileGridYOffset(),
data.getTileHeight()));
+        final int tx = ImageUtilities.pixelToTileX(data, x);
+        final int ty = ImageUtilities.pixelToTileY(data, y);
         return data.getTile(tx, ty).getPixel(x, y, buffer);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
index 761995c..57b707c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
@@ -313,6 +313,7 @@ abstract class AnnotatedImage extends ImageAdapter {
      * method and invoke {@code super.computeProperty(…)} with a sub-region to compute.
      *
      * @param  areaOfInterest  pixel coordinates of the region of interest, or {@code null}
for the whole image.
+     *         It is caller responsibility to ensure that this rectangle is fully included
inside image bounds.
      * @return the computed property value. Note that {@code null} is a valid result.
      * @throws Exception if an error occurred while computing the property.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index ba1e200..e2fc193 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -25,19 +25,15 @@ import java.util.logging.LogRecord;
 import java.awt.Rectangle;
 import java.awt.image.ColorModel;
 import java.awt.image.SampleModel;
-import java.awt.image.Raster;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.awt.image.ImagingOpException;
-import java.awt.image.RasterFormatException;
 import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.math.Statistics;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
 import org.apache.sis.internal.coverage.j2d.TiledImage;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 
@@ -521,7 +517,7 @@ public class ImageProcessor implements Cloneable {
     }
 
     /**
-     * Computes all tiles immediately, then return an image will all tiles ready.
+     * Computes immediately all tiles in the given region of interest, then return an image
will those tiles ready.
      * Computations will use many threads if {@linkplain #getExecutionMode() execution mode}
is parallel.
      *
      * <div class="note"><b>Note:</b>
@@ -529,62 +525,21 @@ public class ImageProcessor implements Cloneable {
      * have a mechanism for specifying which tile to produce in replacement of tiles that
can not be computed.
      * This behavior may be changed in a future version.</div>
      *
-     * @param  source  the image to compute immediately (may be {@code null}).
-     * @return image with all tiles computed, or {@code null} if the given image was null.
+     * @param  source          the image to compute immediately (may be {@code null}).
+     * @param  areaOfInterest  pixel coordinates of the region to prefetch, or {@code null}
for the whole image.
+     * @return image with all tiles intersecting the AOI computed, or {@code null} if the
given image was null.
      * @throws ImagingOpException if an exception occurred during {@link RenderedImage#getTile(int,
int)} call.
      *         This exception wraps the original exception as its {@linkplain ImagingOpException#getCause()
cause}.
      */
-    public RenderedImage prefetch(final RenderedImage source) {
+    public RenderedImage prefetch(RenderedImage source, final Rectangle areaOfInterest) {
         if (source == null || source instanceof BufferedImage || source instanceof TiledImage)
{
             return source;
         }
-        final Prefetch worker = new Prefetch(source);
-        if (parallel(source)) {
-            worker.parallelReadFrom(source);
-        } else {
-            worker.readFrom(source);
-        }
-        return new TiledImage(source.getColorModel(), source.getWidth(), source.getHeight(),
-                              source.getMinTileX(), source.getMinTileY(), worker.tiles);
-    }
-
-    /**
-     * A worker for prefetching tiles in an image.
-     */
-    private static final class Prefetch extends TileOpExecutor {
-        /** Number of tiles in a row. */
-        private final int numXTiles;
-
-        /** Image properties for converting pixel coordinates to tile indices. */
-        private final long tileWidth, tileHeight, tileGridXOffset, tileGridYOffset;
-
-        /** The tiles in a row-major fashion. */
-        final Raster[] tiles;
-
-        /** Prepares an instance for prefetching tiles from the given image. */
-        Prefetch(final RenderedImage source) {
-            super(source, null);
-            numXTiles       = source.getNumXTiles();
-            tileWidth       = source.getTileWidth();
-            tileHeight      = source.getTileHeight();
-            tileGridXOffset = source.getTileGridXOffset();
-            tileGridYOffset = source.getTileGridYOffset();
-            tiles           = new Raster[Math.multiplyExact(source.getNumYTiles(), numXTiles)];
-        }
-
-        /** Invoked in a when a tile have been computed, possibly in a background thread.
*/
-        @Override protected void readFrom(final Raster source) {
-            final long tx = Math.floorDiv(source.getMinX() - tileGridXOffset, tileWidth);
-            final long ty = Math.floorDiv(source.getMinY() - tileGridYOffset, tileHeight);
-            final int index = Math.toIntExact(tx + ty*numXTiles);
-            synchronized (tiles) {
-                if (tiles[index] != null) {
-                    throw new RasterFormatException(Errors.format(
-                            Errors.Keys.DuplicatedElement_1, "Tile[" + tx + ", " + ty + ']'));
-                }
-                tiles[index] = source;
-            }
+        while (source instanceof PrefetchedImage) {
+            source = ((PrefetchedImage) source).source;
         }
+        final PrefetchedImage image = new PrefetchedImage(source, areaOfInterest, parallel(source));
+        return image.isEmpty() ? source : image;
     }
 
     /**
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 bc29971..618dc9a 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
@@ -371,7 +371,7 @@ public abstract class PlanarImage implements RenderedImage {
      */
     @Override
     public Raster getData() {
-        final Rectangle aoi = ImageUtilities.getBounds(this);
+        final Rectangle aoi = getBounds();
         final WritableRaster raster = createWritableRaster(aoi);
         copyData(aoi, raster);
         return raster;
@@ -388,7 +388,7 @@ public abstract class PlanarImage implements RenderedImage {
     @Override
     public Raster getData(final Rectangle aoi) {
         ArgumentChecks.ensureNonNull("aoi", aoi);
-        if (!ImageUtilities.getBounds(this).contains(aoi)) {
+        if (!getBounds().contains(aoi)) {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.OutsideDomainOfValidity));
         }
         final WritableRaster raster = createWritableRaster(aoi);
@@ -410,8 +410,9 @@ public abstract class PlanarImage implements RenderedImage {
         final Rectangle aoi;
         if (raster != null) {
             aoi = raster.getBounds();
+            ImageUtilities.clipBounds(this, aoi);
         } else {
-            aoi = ImageUtilities.getBounds(this);
+            aoi = getBounds();
             raster = createWritableRaster(aoi);
         }
         copyData(aoi, raster);
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
new file mode 100644
index 0000000..9d7e47e
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image;
+
+import java.util.Vector;
+import java.awt.Rectangle;
+import java.awt.image.ColorModel;
+import java.awt.image.SampleModel;
+import java.awt.image.RenderedImage;
+import java.awt.image.Raster;
+import java.awt.image.RasterFormatException;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * An image which delegate all tile requests to another image except for some tiles that
are fetched in advance.
+ * This image has the same coordinate systems than the source image.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @see ImageProcessor#prefetch(RenderedImage, Rectangle)
+ *
+ * @since 1.1
+ * @module
+ */
+final class PrefetchedImage extends PlanarImage {
+    /**
+     * The source image from which to prefetch tiles.
+     */
+    final RenderedImage source;
+
+    /**
+     * Index of the first prefetched tile. This index is determined from the area of interest
(AOI)
+     * specified at construction time; it is not necessarily the index of the first tile
in the image.
+     */
+    private final int minTileX, minTileY;
+
+    /**
+     * Number of prefetched tiles. This number is determined from the area of interest (AOI)
+     * specified at construction time; it is not necessarily include all tiles in the image.
+     */
+    private final int numXTiles, numYTiles;
+
+    /**
+     * The prefetched tiles. This array contains only the tiles in the area of interest (AOI)
+     * specified at construction time; it does not necessarily contains all tiles in the
image.
+     */
+    private final Raster[] tiles;
+
+    /**
+     * Creates a new prefetched image.
+     *
+     * @param source          the image to compute immediately (may be {@code null}).
+     * @param areaOfInterest  pixel coordinates of the region to prefetch, or {@code null}
for the whole image.
+     * @param parallel        whether to execute computation in parallel.
+     */
+    PrefetchedImage(final RenderedImage source, Rectangle areaOfInterest, final boolean parallel)
{
+        this.source = source;
+        if (areaOfInterest != null) {
+            areaOfInterest = new Rectangle(areaOfInterest);
+            ImageUtilities.clipBounds(source, areaOfInterest);
+        }
+        final Worker worker = new Worker(source, areaOfInterest);
+        final Rectangle ti = worker.getTileIndices();
+        minTileX  = ti.x;
+        minTileY  = ti.y;
+        numXTiles = ti.width;
+        numYTiles = ti.height;
+        tiles     = new Raster[Math.multiplyExact(numYTiles, numXTiles)];
+        if (parallel) {
+            worker.parallelReadFrom(source);
+        } else {
+            worker.readFrom(source);
+        }
+    }
+
+    /**
+     * A worker for prefetching tiles in an image.
+     */
+    private final class Worker extends TileOpExecutor {
+        /** Image properties for converting pixel coordinates to tile indices. */
+        private final long tileWidth, tileHeight, tileGridXOffset, tileGridYOffset;
+
+        /** Prepares an instance for prefetching tiles from the given image. */
+        Worker(final RenderedImage source, final Rectangle aoi) {
+            super(source, aoi);
+            tileWidth       = source.getTileWidth();
+            tileHeight      = source.getTileHeight();
+            tileGridXOffset = source.getTileGridXOffset();
+            tileGridYOffset = source.getTileGridYOffset();
+        }
+
+        /** Invoked in a when a tile have been computed, possibly in a background thread.
*/
+        @Override protected void readFrom(final Raster source) {
+            final long tx = Math.floorDiv(source.getMinX() - tileGridXOffset, tileWidth)
 - minTileX;
+            final long ty = Math.floorDiv(source.getMinY() - tileGridYOffset, tileHeight)
- minTileY;
+            assert tx >= 0 && tx < numXTiles : tx;
+            assert ty >= 0 && ty < numYTiles : ty;
+            final int index = Math.toIntExact(tx + ty*numXTiles);
+            synchronized (tiles) {
+                if (tiles[index] != null) {
+                    throw new RasterFormatException(Errors.format(
+                            Errors.Keys.DuplicatedElement_1, "Tile[" + tx + ", " + ty + ']'));
+                }
+                tiles[index] = source;
+            }
+        }
+    }
+
+    /**
+     * Returns {@code true} if this image does not prefetch any tiles.
+     */
+    final boolean isEmpty() {
+        return (numXTiles | numYTiles) == 0;
+    }
+
+    /**
+     * Returns the immediate source of image data for this image.
+     */
+    @Override
+    @SuppressWarnings("UseOfObsoleteCollectionType")
+    public Vector<RenderedImage> getSources() {
+        final Vector<RenderedImage> sources = new Vector<>(1);
+        sources.add(source);
+        return sources;
+    }
+
+    /**
+     * Gets a property from the source image.
+     * All properties are inherited from the source with no change.
+     */
+    @Override
+    public Object getProperty(final String name) {
+        return source.getProperty(name);
+    }
+
+    /** Delegate to the source image. */
+    @Override public String[]    getPropertyNames()   {return source.getPropertyNames();}
+    @Override public ColorModel  getColorModel()      {return source.getColorModel();}
+    @Override public SampleModel getSampleModel()     {return source.getSampleModel();}
+    @Override public int         getWidth()           {return source.getWidth();}
+    @Override public int         getHeight()          {return source.getHeight();}
+    @Override public int         getMinX()            {return source.getMinX();}
+    @Override public int         getMinY()            {return source.getMinY();}
+    @Override public int         getNumXTiles()       {return source.getNumXTiles();}
+    @Override public int         getNumYTiles()       {return source.getNumYTiles();}
+    @Override public int         getMinTileX()        {return source.getMinTileX();}
+    @Override public int         getMinTileY()        {return source.getMinTileY();}
+    @Override public int         getTileWidth()       {return source.getTileWidth();}
+    @Override public int         getTileHeight()      {return source.getTileHeight();}
+    @Override public int         getTileGridXOffset() {return source.getTileGridXOffset();}
+    @Override public int         getTileGridYOffset() {return source.getTileGridYOffset();}
+
+    /**
+     * Returns the tile at the given location in tile coordinates. If the requested
+     * tile is in the region of prefetched tiles, that tile is returned directly.
+     * Otherwise this method delegates to the source image.
+     */
+    @Override
+    public Raster getTile(final int tileX, final int tileY) {
+        final int tx = Math.subtractExact(tileX, minTileX);
+        if (tx >= 0 && tileX < numXTiles) {
+            final int ty = Math.subtractExact(tileY, minTileY);
+            if (ty >= 0 && tileY < numYTiles) {
+                return tiles[tx + ty * numXTiles];
+            }
+        }
+        return source.getTile(tileX, tileY);
+    }
+}
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 2fee3e2..b6cb98f 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
@@ -386,6 +386,28 @@ public final class ImageUtilities extends Static {
     }
 
     /**
+     * Converts a <var>x</var> pixel coordinates to a tile index.
+     *
+     * @param  image  the image containing tiles.
+     * @param  x      the pixel coordinate for which to get tile index.
+     * @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()));
+    }
+
+    /**
+     * Converts a <var>y</var> pixel coordinates to a tile index.
+     *
+     * @param  image  the image containing tiles.
+     * @param  y      the pixel coordinate for which to get tile index.
+     * @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()));
+    }
+
+    /**
      * Suggests the height of a transfer region for a tile of the given size. The given region
should be
      * contained inside {@link Raster#getBounds()}. This method modifies {@link Rectangle#height}
in-place.
      * The {@link Rectangle#width} value is never modified, so caller can iterate on all
raster rows without
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
index 5eaa015..e451af7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
@@ -77,12 +77,15 @@ import org.apache.sis.internal.util.Strings;
 public class TileOpExecutor {
     /**
      * Minimum/maximum index of tiles to process, inclusive.
+     *
+     * @see #getTileIndices()
      */
     private final int minTileX, minTileY, maxTileX, maxTileY;
 
     /**
      * Creates a new operation for tiles in the specified region of the specified image.
-     * It is caller responsibility to ensure that {@code aoi} is contained inside {@code
image} bounds.
+     * It is caller responsibility to ensure that {@code aoi} is contained inside {@code
image} bounds
+     * (caller can invoke {@link ImageUtilities#clipBounds(RenderedImage, Rectangle)} if
needed).
      *
      * @param  image  the image from which tiles will be fetched.
      * @param  aoi    region of interest, or {@code null} for the whole image.
@@ -126,7 +129,9 @@ public class TileOpExecutor {
      * @return range of tile indices to be processed.
      */
     public final Rectangle getTileIndices() {
-        return new Rectangle(minTileX, minTileY, maxTileX - minTileX + 1, maxTileY - minTileY
+ 1);
+        return new Rectangle(minTileX, minTileY,
+                Math.incrementExact(Math.subtractExact(maxTileX, minTileX)),
+                Math.incrementExact(Math.subtractExact(maxTileY, minTileY)));
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
index 0747f6b..2060815 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TiledImage.java
@@ -49,8 +49,7 @@ public final class TiledImage extends PlanarImage {
     private final int width, height;
 
     /**
-     * Index of the first tile in the image. Should be a non-trivial value
-     * for increasing the chances to detect error in index calculation.
+     * Index of the first tile in the image.
      */
     private final int minTileX, minTileY;
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Transferer.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Transferer.java
index 12fc7d5..2f802ef 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Transferer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Transferer.java
@@ -506,8 +506,8 @@ abstract class Transferer {
      * @return object to use for applying the operation.
      */
     static Transferer create(final RenderedImage source, final WritableRaster target) {
-        int tileX = Math.floorDiv((target.getMinX() - source.getTileGridXOffset()), source.getTileWidth());
-        int tileY = Math.floorDiv((target.getMinY() - source.getTileGridYOffset()), source.getTileHeight());
+        int tileX = ImageUtilities.pixelToTileX(source, target.getMinX());
+        int tileY = ImageUtilities.pixelToTileY(source, target.getMinY());
         return create(source.getTile(tileX, tileY), target, target.getBounds());
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
index 57ff192..80d194a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
@@ -889,7 +889,7 @@ public final class Matrices extends Static {
                 if (anchor != null) {
                     final double p = anchor[j];
                     double e = matrix.getElement(j, srcDim);
-                    changed |= (e != (e = (e-p)*rescale + p));
+                    changed |= (e != (e = (e-p)*rescale + p));      // TODO: use Math.fma
in JDK9.
                     matrix.setElement(j, srcDim, e);
                 }
             }


Mime
View raw message