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 cc2e967908545a0057626b32eba0218417e84bd9 Author: Martin Desruisseaux <[email protected]> AuthorDate: Mon Dec 23 12:52:31 2024 +0100 Retrofit user-provided arguments as `ImageLayout` properties: - The `allowPartialTiles` argument given to `ImageLayout` methods. - The `ImageProcessor.autoTileSize` field. --- .../org/apache/sis/coverage/privy/ImageLayout.java | 121 ++++++++++++++------- .../org/apache/sis/image/BandAggregateLayout.java | 39 ++----- .../main/org/apache/sis/image/ImageOverlay.java | 2 +- .../main/org/apache/sis/image/ImageProcessor.java | 28 +---- .../apache/sis/coverage/privy/ImageLayoutTest.java | 4 +- .../storage/geotiff/writer/ReformattedImage.java | 2 +- .../org/apache/sis/storage/gdal/TiledResource.java | 2 +- 7 files changed, 98 insertions(+), 100 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageLayout.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageLayout.java index aa77bb712c..52f515d029 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageLayout.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/privy/ImageLayout.java @@ -66,8 +66,10 @@ public class ImageLayout { /** * The default instance with a preferred tile width and height of {@value #DEFAULT_TILE_SIZE} pixels. + * Image sizes are preserved but the tile sizes are flexible. The last row and last column of tiles + * in an image are allowed to be only partially filled. */ - public static final ImageLayout DEFAULT = new ImageLayout(null, false); + public static final ImageLayout DEFAULT = new ImageLayout(null, true, false, true); /** * Preferred size (in pixels) for tiles. @@ -78,24 +80,52 @@ public class ImageLayout { */ protected final int preferredTileWidth, preferredTileHeight; + /** + * Whether to allow changes of tile size when needed. If this flag is {@code false}, + * then {@link #suggestTileSize(int, int)} unconditionally returns the preferred tile size. + * + * <p>The {@linkplain #DEFAULT default} value is {@code true}.</p> + * + * @see #allowTileSizeAdjustments(boolean) + * @see #suggestTileSize(int, int) + */ + public final boolean isTileSizeAdjustmentAllowed; + /** * Whether to allow changes of image size when needed. An image may be resized when the * {@link #suggestTileSize(int, int)} method cannot find a size close enough to the preferred tile size. * For example, if the image width is a prime number, there is no way to divide the image horizontally with * an integer number of tiles. The only way to get an integer number of tiles is to change the image size. + * This is done by changing the fields of the {@code bounds} argument given to the + * {@link #suggestTileSize(RenderedImage, Rectangle)} method. + * + * <p>The {@linkplain #DEFAULT default} value is {@code false}.</p> * - * <p>If this flag is {@code true}, then the {@code bounds} argument given to the - * {@link #suggestTileSize(RenderedImage, Rectangle, boolean)} will be modified in-place.</p> + * @see #suggestTileSize(RenderedImage, Rectangle) */ public final boolean isImageBoundsAdjustmentAllowed; + /** + * Whether to allow tiles that are only partially filled in the last row and last column of the tile matrix. + * This flag may be ignored (handled as {@code false}) when the image for which to compute a tile size is opaque. + * + * <p>The {@linkplain #DEFAULT default} value is {@code true}.</p> + */ + public final boolean isPartialTilesAllowed; + /** * Creates a new image layout. * * @param preferredTileSize the preferred tile size, or {@code null} for the default size. + * @param isTileSizeAdjustmentAllowed whether tile size can be modified if needed. * @param isImageBoundsAdjustmentAllowed whether image size can be modified if needed. + * @param isPartialTilesAllowed whether to allow tiles that are only partially filled. */ - public ImageLayout(final Dimension preferredTileSize, final boolean isImageBoundsAdjustmentAllowed) { + public ImageLayout(final Dimension preferredTileSize, + final boolean isTileSizeAdjustmentAllowed, + final boolean isImageBoundsAdjustmentAllowed, + final boolean isPartialTilesAllowed) + { if (preferredTileSize != null) { preferredTileWidth = Math.max(1, preferredTileSize.width); preferredTileHeight = Math.max(1, preferredTileSize.height); @@ -103,7 +133,9 @@ public class ImageLayout { preferredTileWidth = DEFAULT_TILE_SIZE; preferredTileHeight = DEFAULT_TILE_SIZE; } + this.isTileSizeAdjustmentAllowed = isTileSizeAdjustmentAllowed; this.isImageBoundsAdjustmentAllowed = isImageBoundsAdjustmentAllowed; + this.isPartialTilesAllowed = isPartialTilesAllowed; } /** @@ -137,21 +169,11 @@ public class ImageLayout { /** Creates a new layout with exactly the tile size of given image. */ FixedSize(final RenderedImage source, final int minTileX, final int minTileY) { - super(new Dimension(source.getTileWidth(), source.getTileHeight()), false); + super(new Dimension(source.getTileWidth(), source.getTileHeight()), false, false, true); this.minTileX = minTileX; this.minTileY = minTileY; } - /** Returns the fixed tile size. All parameters are ignored. */ - @Override public Dimension suggestTileSize(int imageWidth, int imageHeight, boolean allowPartialTiles) { - return getPreferredTileSize(); - } - - /** Returns the fixed tile size. All parameters are ignored. */ - @Override public Dimension suggestTileSize(RenderedImage image, Rectangle bounds, boolean allowPartialTiles) { - return getPreferredTileSize(); - } - /** Returns indices of the first tile. */ @Override public Point getMinTile() { return new Point(minTileX, minTileY); @@ -182,6 +204,19 @@ public class ImageLayout { } } + /** + * Returns a new layout with the same flags but a different preferred tile size. + * + * @param size the new tile size. + * @return the layout for the given size. + */ + public ImageLayout withPreferredTileSize(final Dimension size) { + if (size.width == preferredTileWidth && size.height == preferredTileHeight) { + return this; + } + return new ImageLayout(size, isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed); + } + /** * Returns the preferred tile size. This is the dimension values specified at construction time. * @@ -261,23 +296,27 @@ public class ImageLayout { /** * Suggests a tile size for the specified image size. This method suggests a tile size which is a divisor * of the given image size if possible, or a size that left as few empty pixels as possible otherwise. - * The {@code allowPartialTile} argument specifies whether to allow tiles that are only partially filled. + * The {@link #isPartialTilesAllowed} flag specifies whether to allow tiles that are only partially filled. * A value of {@code true} implies that tiles in the last row or in the last column may contain empty pixels. * A value of {@code false} implies that this class will be unable to subdivide large images in smaller tiles * if the image size is a prime number. * - * <p>The {@code allowPartialTile} argument should be {@code false} if the tiled image is opaque, + * <p>The {@link #isPartialTilesAllowed} flag should be {@code false} when the tiled image is opaque, * or if the sample value for transparent pixels is different than zero. This restriction is for * avoiding black or colored borders on the image left size and bottom size.</p> * - * @param imageWidth the image width in pixels. - * @param imageHeight the image height in pixels. - * @param allowPartialTiles whether to allow tiles that are only partially filled. + * @param imageWidth the image width in pixels. + * @param imageHeight the image height in pixels. * @return suggested tile size for the given image size. */ - public Dimension suggestTileSize(final int imageWidth, final int imageHeight, final boolean allowPartialTiles) { - return new Dimension(toTileSize(imageWidth, preferredTileWidth, allowPartialTiles), - toTileSize(imageHeight, preferredTileHeight, allowPartialTiles)); + public Dimension suggestTileSize(final int imageWidth, final int imageHeight) { + int tileWidth = preferredTileWidth; + int tileHeight = preferredTileHeight; + if (isTileSizeAdjustmentAllowed) { + tileWidth = toTileSize(imageWidth, tileWidth, isPartialTilesAllowed); + tileHeight = toTileSize(imageHeight, tileHeight, isPartialTilesAllowed); + } + return new Dimension(tileWidth, tileHeight); } /** @@ -285,21 +324,22 @@ public class ImageLayout { * If the given image is null, then this method returns the preferred tile size. * Otherwise, if the given image is already tiled, then this method preserves the * current tile size unless the tiles are too large, in which case they may be subdivided. - * Otherwise (untiled image) this method proposes a tile size. + * Otherwise (untiled image), this method proposes a tile size. * * <p>This method also checks whether the color model supports transparency. If not, then this - * method will not return a size that may result in the creation of partially empty tiles.</p> + * method will not return a size that may result in the creation of partially empty tiles. + * In other words, the {@link #isPartialTilesAllowed} is ignored (handled as {@code false}) + * for opaque images.</p> * * @param image the image for which to derive a tile size, or {@code null}. * @param bounds the bounds of the image to create, or {@code null} if same as {@code image}. - * @param allowPartialTiles whether to allow tiles that are only partially filled. - * This argument is ignored (reset to {@code false}) if the given image is opaque. * @return suggested tile size for the given image. */ - public Dimension suggestTileSize(final RenderedImage image, final Rectangle bounds, boolean allowPartialTiles) { + public Dimension suggestTileSize(final RenderedImage image, final Rectangle bounds) { if (bounds != null && bounds.isEmpty()) { throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "bounds")); } + boolean allowPartialTiles = isPartialTilesAllowed; if (allowPartialTiles && image != null && !isImageBoundsAdjustmentAllowed) { final ColorModel cm = image.getColorModel(); allowPartialTiles = (cm != null); @@ -332,20 +372,23 @@ public class ImageLayout { } else { return getPreferredTileSize(); } - final Dimension tileSize = new Dimension( - toTileSize(width, preferredTileWidth, allowPartialTiles & singleXTile), - toTileSize(height, preferredTileHeight, allowPartialTiles & singleYTile)); + int tileWidth = preferredTileWidth; + int tileHeight = preferredTileHeight; + if (isTileSizeAdjustmentAllowed) { + tileWidth = toTileSize(width, tileWidth, allowPartialTiles & singleXTile); + tileHeight = toTileSize(height, tileHeight, allowPartialTiles & singleYTile); + } /* * Optionally adjust the image bounds for making it divisible by the tile size. */ if (isImageBoundsAdjustmentAllowed && bounds != null && !bounds.isEmpty()) { - final int sx = sizeToAdd(bounds.width, tileSize.width); - final int sy = sizeToAdd(bounds.height, tileSize.height); - if ((bounds.width += sx) < 0) bounds.width -= tileSize.width; // if (overflow) reduce to valid range. - if ((bounds.height += sy) < 0) bounds.height -= tileSize.height; + final int sx = sizeToAdd(bounds.width, tileWidth); + final int sy = sizeToAdd(bounds.height, tileHeight); + if ((bounds.width += sx) < 0) bounds.width -= tileWidth; // if (overflow) reduce to valid range. + if ((bounds.height += sy) < 0) bounds.height -= tileHeight; bounds.translate(-sx/2, -sy/2); } - return tileSize; + return new Dimension(tileWidth, tileHeight); } /** @@ -362,7 +405,7 @@ public class ImageLayout { /** * Creates a banded sample model of the given type with an automatic tile size. * At least one of {@code image} and {@code bounds} arguments must be non null. - * This method uses the {@linkplain #suggestTileSize(RenderedImage, Rectangle, boolean) + * This method uses the {@linkplain #suggestTileSize(RenderedImage, Rectangle) * suggested tile size} for the given image and bounds. * * <p>This method constructs the simplest possible banded sample model: @@ -380,7 +423,7 @@ public class ImageLayout { public BandedSampleModel createBandedSampleModel(final int dataType, final int numBands, final RenderedImage image, final Rectangle bounds, int scanlineStride) { - final Dimension tileSize = suggestTileSize(image, bounds, isImageBoundsAdjustmentAllowed); + final Dimension tileSize = suggestTileSize(image, bounds); if (scanlineStride <= 0) { scanlineStride = tileSize.width; } @@ -403,7 +446,7 @@ public class ImageLayout { */ public SampleModel createCompatibleSampleModel(final RenderedImage image, final Rectangle bounds) { ArgumentChecks.ensureNonNull("image", image); - final Dimension tile = suggestTileSize(image, bounds, isImageBoundsAdjustmentAllowed); + final Dimension tile = suggestTileSize(image, bounds); SampleModel sm = image.getSampleModel(); if (sm.getWidth() != tile.width || sm.getHeight() != tile.height) { sm = sm.createCompatibleSampleModel(tile.width, tile.height); diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java index 963b538255..4aea225fa4 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandAggregateLayout.java @@ -103,14 +103,6 @@ final class BandAggregateLayout extends ImageLayout { */ private final int minTileX, minTileY; - /** - * Whether to use the preferred tile size exactly as specified, without trying to compute a better size. - * This field may be {@code true} if the tiles of the destination image are at exact same location as - * the tiles of a source image having the preferred tile size. In such case, keeping the same size will - * reduce the number of tiles requested in that source image. - */ - private final boolean exactTileSize; - /** * Computes the layout of an image combining the bands of all the specified source images. * The optional {@code bandsPerSource} argument specifies the bands to select in each source images. @@ -220,6 +212,12 @@ final class BandAggregateLayout extends ImageLayout { /** * Creates a new image layout from the values computed by {@code create(…)}. * + * <h4>Tile size</h4> + * The {@code exactTileSize} argument tells whether to use the preferred tile size exactly as specified, + * without trying to compute a better size. This flag may be {@code true} if the tiles of the destination + * image are at exact same location as the tiles of a source image having the preferred tile size. + * In such case, keeping the same size will reduce the number of tiles requested in that source image. + * * @param sources images to combine, in order. * @param bandsPerSource bands to use for each source image, in order. May contain {@code null} elements. * @param bandSelect final band select operation to apply on the aggregated result. @@ -234,8 +232,7 @@ final class BandAggregateLayout extends ImageLayout { final int minTileX, final int minTileY, final int commonDataType, final int numBands, final int scanlineStride) { - super(preferredTileSize, false); - this.exactTileSize = exactTileSize; + super(preferredTileSize, !exactTileSize, false, false); this.bandsPerSource = bandsPerSource; this.bandSelect = bandSelect; this.sources = sources; @@ -327,28 +324,6 @@ final class BandAggregateLayout extends ImageLayout { return new Point(minTileX, minTileY); } - /** - * Suggests a tile size for the specified image size. - * It may be exactly the size of the tiles of a source image, but not necessarily. - * This method may compute a tile size different than the tile size of all sources. - */ - @Override - public Dimension suggestTileSize(int imageWidth, int imageHeight, boolean allowPartialTiles) { - if (exactTileSize) return getPreferredTileSize(); - return super.suggestTileSize(imageWidth, imageHeight, allowPartialTiles); - } - - /** - * Suggests a tile size for operations derived from the given image. - * It may be exactly the size of the tiles of a source image, but not necessarily. - * This method may compute a tile size different than the tile size of all sources. - */ - @Override - public Dimension suggestTileSize(RenderedImage image, Rectangle bounds, boolean allowPartialTiles) { - if (exactTileSize) return getPreferredTileSize(); - return super.suggestTileSize(image, bounds, allowPartialTiles); - } - /** * Returns {@code true} if all filtered sources are writable. * diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageOverlay.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageOverlay.java index 4bb06de124..8d73b7441f 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageOverlay.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageOverlay.java @@ -170,7 +170,7 @@ final class ImageOverlay extends MultiSourceImage { if (autoTileSize) { var tileSize = new Dimension(sampleModel.getWidth(), sampleModel.getHeight()); if ((bounds.width % tileSize.width) != 0 || (bounds.height % tileSize.height) != 0) { - tileSize = new ImageLayout(tileSize, false).suggestTileSize(bounds.width, bounds.height, true); + tileSize = ImageLayout.DEFAULT.withPreferredTileSize(tileSize).suggestTileSize(bounds.width, bounds.height); sampleModel = sampleModel.createCompatibleSampleModel(tileSize.width, tileSize.height); } } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java index da807be9c7..8534939fb4 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java @@ -169,15 +169,6 @@ public class ImageProcessor implements Cloneable { */ private ImageLayout layout; - /** - * Whether the processor is allowed to change the tile size. This configuration is relevant - * only for operations taking a {@link SampleModel} in argument, which implies a tile size. - * - * @see Resizing#CHANGE_TILING - * @see #setImageResizingPolicy(Resizing) - */ - private boolean autoTileSize; - /** * Whether {@code ImageProcessor} can produce an image of different size compared to requested size. * An image may be resized if the requested size cannot be subdivided into tiles of reasonable size. @@ -198,20 +189,11 @@ public class ImageProcessor implements Cloneable { */ NONE(ImageLayout.DEFAULT), - /** - * The tile size can be modified, but not the image size. This resizing policy can - * be used with operations where a {@link SampleModel} argument implies a tile size. - * For other operations, this resizing policy is equivalent to {@link #NONE}. - * - * @since 1.5 - */ - CHANGE_TILING(ImageLayout.DEFAULT), - /** * Image size can be increased. {@code ImageProcessor} will try to increase * by the smallest number of pixels allowing the image to be subdivided in tiles. */ - EXPAND(new ImageLayout(null, true)); + EXPAND(new ImageLayout(null, true, true, true)); /** * The layout corresponding to the enumeration value. @@ -428,8 +410,7 @@ public class ImageProcessor implements Cloneable { * @return the image resizing policy. */ public synchronized Resizing getImageResizingPolicy() { - return layout.isImageBoundsAdjustmentAllowed ? Resizing.EXPAND : - autoTileSize ? Resizing.CHANGE_TILING : Resizing.NONE; + return layout.isImageBoundsAdjustmentAllowed ? Resizing.EXPAND : Resizing.NONE; } /** @@ -439,7 +420,6 @@ public class ImageProcessor implements Cloneable { */ public synchronized void setImageResizingPolicy(final Resizing policy) { layout = policy.layout; - autoTileSize = (policy == Resizing.CHANGE_TILING); } /** @@ -1016,7 +996,7 @@ public class ImageProcessor implements Cloneable { final boolean parallel; final boolean autoTileSize; synchronized (this) { - autoTileSize = this.autoTileSize; + autoTileSize = layout.isTileSizeAdjustmentAllowed; parallel = executionMode != Mode.SEQUENTIAL; } return ImageOverlay.create(sources, bounds, sampleModel, colorModel, autoTileSize | (bounds != null), parallel); @@ -1050,7 +1030,7 @@ public class ImageProcessor implements Cloneable { final boolean parallel; final boolean autoTileSize; synchronized (this) { - autoTileSize = this.autoTileSize; + autoTileSize = layout.isTileSizeAdjustmentAllowed; parallel = executionMode != Mode.SEQUENTIAL; } return ImageOverlay.create(new RenderedImage[] {source}, null, sampleModel, null, autoTileSize, parallel); diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/privy/ImageLayoutTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/privy/ImageLayoutTest.java index fadd11dd61..f0678dad1e 100644 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/privy/ImageLayoutTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/privy/ImageLayoutTest.java @@ -37,11 +37,11 @@ public final class ImageLayoutTest extends TestCase { } /** - * Tests {@link ImageLayout#suggestTileSize(int, int, boolean)}. + * Tests {@link ImageLayout#suggestTileSize(int, int)}. */ @Test public void testSuggestTileSize() { - final Dimension size = ImageLayout.DEFAULT.suggestTileSize(367877, 5776326, true); + final Dimension size = ImageLayout.DEFAULT.suggestTileSize(367877, 5776326); assertEquals(511, size.width); assertEquals(246, size.height); } diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ReformattedImage.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ReformattedImage.java index 2e48d5d57e..ddf9f5623c 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ReformattedImage.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/ReformattedImage.java @@ -161,7 +161,7 @@ public final class ReformattedImage { final int height = JDK18.ceilDiv(image.getHeight(), TILE_DIVISOR); tileSize.width = JDK18.ceilDiv(tileSize.width, TILE_DIVISOR); tileSize.height = JDK18.ceilDiv(tileSize.height, TILE_DIVISOR); - tileSize = new ImageLayout(tileSize, false).suggestTileSize(width, height, true); + tileSize = ImageLayout.DEFAULT.withPreferredTileSize(tileSize).suggestTileSize(width, height); tileSize.width *= TILE_DIVISOR; tileSize.height *= TILE_DIVISOR; sm = sm.createCompatibleSampleModel(tileSize.width, tileSize.height); diff --git a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java index 7acd0e350d..545d5e3ec7 100644 --- a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java +++ b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java @@ -205,7 +205,7 @@ final class TiledResource extends TiledGridResource { } else if (Math.multiplyFull(w, h) <= LARGE_TILE_SIZE) { return new Dimension(w, h); } - return ImageLayout.DEFAULT.suggestTileSize(w, h, true); + return ImageLayout.DEFAULT.suggestTileSize(w, h); } }
