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);