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 b9f98ce7e1f8868ccdb49d00b2b703343516b6b1 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Dec 23 18:15:23 2024 +0100 Add `SampleModel` and `ColorModel` properties into `ImageLayout` in replacement for optional arguments given to image processor. --- .../org/apache/sis/image/BandAggregateLayout.java | 8 +- .../main/org/apache/sis/image/ImageLayout.java | 180 ++++++++++++++++----- .../main/org/apache/sis/image/ImageProcessor.java | 33 ++-- 3 files changed, 167 insertions(+), 54 deletions(-) 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 9ddb2f6ecd..d17e6c2164 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 @@ -44,6 +44,12 @@ import org.apache.sis.coverage.privy.CommonDomainFinder; * * <p>Instances of this class are temporary and used only during image construction.</p> * + * <h2>Restrictions</h2> + * The inherited {@link #sampleModel} must be a {@link BandedSampleModel}. + * All {@linkplain BandedSampleModel#getBandOffsets() band offsets} are zeros and + * all {@linkplain BandedSampleModel#getBankIndices() bank indices} are identity mapping. + * This simplicity is needed by current implementation of {@link BandAggregateImage}. + * * @author Alexis Manin (Geomatys) * @author Martin Desruisseaux (Geomatys) * @@ -223,7 +229,7 @@ final class BandAggregateLayout extends ImageLayout { final Rectangle domain, final Dimension preferredTileSize, final boolean exactTileSize, final Point minTile, final int commonDataType, final int numBands, final int scanlineStride) { - super(preferredTileSize, !exactTileSize, false, false, minTile); + super(null, null, preferredTileSize, !exactTileSize, false, false, minTile); this.bandsPerSource = bandsPerSource; this.bandSelect = bandSelect; this.sources = sources; diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageLayout.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageLayout.java index 47500f5e33..a6a457b425 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageLayout.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageLayout.java @@ -17,6 +17,7 @@ package org.apache.sis.image; import java.util.Arrays; +import java.util.Objects; import java.awt.Point; import java.awt.Dimension; import java.awt.Rectangle; @@ -28,11 +29,11 @@ import java.awt.image.BandedSampleModel; import java.awt.image.WritableRenderedImage; import org.apache.sis.math.MathFunctions; import org.apache.sis.util.ArraysExt; -import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.privy.Strings; import org.apache.sis.system.Configuration; import org.apache.sis.coverage.privy.RasterFactory; +import org.apache.sis.feature.internal.Resources; /** @@ -41,6 +42,11 @@ import org.apache.sis.coverage.privy.RasterFactory; * for deriving an actual tile size for a given image size. * The rules for deriving a tile size are configurable by flags. * + * <p>An image layout can be specified in more details with a {@link SampleModel}. + * The size of a sample model usually determines the size of tiles, but the former + * may be replaced by the tile size {@linkplain #suggestTileSize(int, int) computed} + * by this {@code ImageLayout} class.</p> + * * <p>Instances of this class are immutable and thread-safe.</p> * * @author Martin Desruisseaux (Geomatys) @@ -73,7 +79,19 @@ public class ImageLayout { * 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, true, false, true, null); + public static final ImageLayout DEFAULT = new ImageLayout(null, null, null, true, false, true, null); + + /** + * Preferred color model, or {@code null} if none. If no {@link #sampleModel} is specified, it may + * be {@linkplain ColorModel#createCompatibleSampleModel(int, int) derived} from this color model. + */ + protected final ColorModel colorModel; + + /** + * Preferred sample model, or {@code null} if none. The sample model width and height may be replaced + * by the tile size {@linkplain #suggestTileSize(int, int) computed} by this {@code ImageLayout} class. + */ + protected final SampleModel sampleModel; /** * Preferred size (in pixels) for tiles. @@ -133,20 +151,32 @@ public class ImageLayout { protected final int preferredMinTileX, preferredMinTileY; /** - * Creates a new image layout. + * Creates a new image layout with the given properties. + * If both the given color model and sample model are non-null, + * then then shall be {@linkplain ColorModel#isCompatibleSampleModel(SampleModel) compatible}. * - * @param preferredTileSize the preferred tile size, or {@code null} for the default size. + * @param colorModel preferred color model, or {@code null} if none. + * @param sampleModel preferred sample model, or {@code null} if none. + * @param preferredTileSize 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. * @param preferredMinTile preferred tile index where image start their tile matrix, or {@code null} for (0,0). + * @throws IllegalArgumentException if the given color model and sample model are incompatible. */ - protected ImageLayout(final Dimension preferredTileSize, - final boolean isTileSizeAdjustmentAllowed, - final boolean isImageBoundsAdjustmentAllowed, - final boolean isPartialTilesAllowed, - final Point preferredMinTile) + protected ImageLayout(final ColorModel colorModel, + final SampleModel sampleModel, + final Dimension preferredTileSize, + final boolean isTileSizeAdjustmentAllowed, + final boolean isImageBoundsAdjustmentAllowed, + final boolean isPartialTilesAllowed, + final Point preferredMinTile) { + if (colorModel != null && sampleModel != null && !colorModel.isCompatibleSampleModel(sampleModel)) { + throw new IllegalArgumentException(Resources.format(Resources.Keys.IncompatibleColorModel)); + } + this.colorModel = colorModel; + this.sampleModel = sampleModel; if (preferredTileSize != null) { preferredTileWidth = Math.max(1, preferredTileSize.width); preferredTileHeight = Math.max(1, preferredTileSize.height); @@ -187,8 +217,9 @@ public class ImageLayout { /** Creates a new layout with exactly the tile size of given image. */ FixedDestination(final WritableRenderedImage destination, final int minTileX, final int minTileY) { - super(new Dimension(destination.getTileWidth(), destination.getTileHeight()), - false, false, true, new Point(minTileX, minTileY)); + super(destination.getColorModel(), destination.getSampleModel(), + new Dimension(destination.getTileWidth(), destination.getTileHeight()), + false, false, true, new Point(minTileX, minTileY)); this.destination = destination; } @@ -203,6 +234,61 @@ public class ImageLayout { } } + /** + * Returns a new layout with the same properties than this layout except for the color model. + * If the given argument value results in no change, returns {@code this}. + * + * @param model the new color model, or {@code null} if none. + * @param cascade whether to derive a new sample model from the given color model. + * @return the layout for the given color model. + * @throws IllegalArgumentException if {@code cascade} is {@code false} + * and the the given color model is incompatible with the current sample model. + */ + public ImageLayout withColorModel(final ColorModel model, final boolean cascade) { + SampleModel sm = sampleModel; + if (model != null) { + if (cascade) { + sm = model.createCompatibleSampleModel(preferredTileWidth, preferredTileHeight); + } else if (sm != null && !model.isCompatibleSampleModel(sm)) { + throw new IllegalArgumentException(Resources.format(Resources.Keys.IncompatibleColorModel)); + } + } + if (Objects.equals(colorModel, model) && Objects.equals(sampleModel, sm)) { + return this; + } + return new ImageLayout(model, sm, + getPreferredTileSize(), isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, + getPreferredMinTile()); + } + + /** + * Returns a new layout with the same properties than this layout except for the sample model. + * If the given argument value results in no change, returns {@code this}. + * + * @param model the new sample model, or {@code null} if none. + * @param cascade whether to set the preferred tile size to the size of the given sample model. + * @return the layout for the given sample model. + * @throws IllegalArgumentException if sample model is incompatible with the current color model. + */ + public ImageLayout withSampleModel(final SampleModel model, final boolean cascade) { + int width = preferredTileWidth; + int height = preferredTileHeight; + if (model != null) { + if (cascade) { + width = model.getWidth(); + height = model.getHeight(); + } else if (colorModel != null && !colorModel.isCompatibleSampleModel(model)) { + throw new IllegalArgumentException(Resources.format(Resources.Keys.IncompatibleColorModel)); + } + } + if (Objects.equals(sampleModel, model) && width == preferredTileWidth && height == preferredTileHeight) { + return this; + } + return new ImageLayout(colorModel, model, new Dimension(width, height), + isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, + getPreferredMinTile()); + } + /** * Returns a new layout with the same properties than this layout except whether it allows changes of tile size. * If the given argument value results in no change, returns {@code this}. @@ -214,8 +300,9 @@ public class ImageLayout { */ public ImageLayout allowTileSizeAdjustments(boolean allowed) { if (isTileSizeAdjustmentAllowed == allowed) return this; - return new ImageLayout(getPreferredTileSize(), allowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, - getPreferredMinTile()); + return new ImageLayout(colorModel, sampleModel, + getPreferredTileSize(), allowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, + getPreferredMinTile()); } /** @@ -229,8 +316,9 @@ public class ImageLayout { */ public ImageLayout allowImageBoundsAdjustments(boolean allowed) { if (isImageBoundsAdjustmentAllowed == allowed) return this; - return new ImageLayout(getPreferredTileSize(), isTileSizeAdjustmentAllowed, allowed, isPartialTilesAllowed, - getPreferredMinTile()); + return new ImageLayout(colorModel, sampleModel, + getPreferredTileSize(), isTileSizeAdjustmentAllowed, allowed, isPartialTilesAllowed, + getPreferredMinTile()); } /** @@ -244,13 +332,15 @@ public class ImageLayout { */ public ImageLayout allowPartialTiles(boolean allowed) { if (isPartialTilesAllowed == allowed) return this; - return new ImageLayout(getPreferredTileSize(), isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, allowed, - getPreferredMinTile()); + return new ImageLayout(colorModel, sampleModel, + getPreferredTileSize(), isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, allowed, + getPreferredMinTile()); } /** * Creates a new layout with the tile size and tile indices of the given image. - * Other properties (all Boolean flags) are copied unchanged. + * Other properties of this {@code ImageLayout} (color model, sample model and + * all Boolean flags) are inherited unchanged. * If the given argument value results in no change, returns {@code this}. * * @param source image from which to take tile size and tile indices. @@ -269,8 +359,9 @@ public class ImageLayout { { return this; } - return new ImageLayout(preferredTileSize, isTileSizeAdjustmentAllowed, - isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, preferredMinTile); + return new ImageLayout(colorModel, sampleModel, + preferredTileSize, isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, + preferredMinTile); } /** @@ -286,7 +377,8 @@ public class ImageLayout { if (size.width == preferredTileWidth && size.height == preferredTileHeight) { return this; } - return new ImageLayout(size, isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, + return new ImageLayout(colorModel, sampleModel, + size, isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, getPreferredMinTile()); } @@ -399,10 +491,11 @@ public class ImageLayout { * 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. * - * <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. - * In other words, the {@link #isPartialTilesAllowed} is ignored (handled as {@code false}) - * for opaque images.</p> + * <p>This method also checks whether the {@linkplain #colorModel preferred color model} or, if the latter + * is unspecified, the {@linkplain RenderedImage#getColorModel() image color model} supports transparency. + * If not, then this method will not return a size that may result in the creation of partially empty tiles. + * In other words, the {@link #isPartialTilesAllowed} flag 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}. @@ -412,16 +505,17 @@ public class ImageLayout { if (bounds != null && bounds.isEmpty()) { throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "bounds")); } + ColorModel cm = colorModel; boolean allowPartialTiles = isPartialTilesAllowed; if (allowPartialTiles && image != null && !isImageBoundsAdjustmentAllowed) { - final ColorModel cm = image.getColorModel(); + cm = image.getColorModel(); allowPartialTiles = (cm != null); - if (allowPartialTiles) { - if (cm instanceof IndexColorModel) { - allowPartialTiles = ((IndexColorModel) cm).getTransparentPixel() == 0; - } else { - allowPartialTiles = (cm.getTransparency() != ColorModel.OPAQUE); - } + } + if (allowPartialTiles) { + if (cm instanceof IndexColorModel) { + allowPartialTiles = ((IndexColorModel) cm).getTransparentPixel() == 0; + } else if (cm != null) { + allowPartialTiles = (cm.getTransparency() != ColorModel.OPAQUE); } } /* @@ -506,19 +600,29 @@ public class ImageLayout { } /** - * Creates a sample model compatible with the sample model of the given image - * but with a size matching the preferred tile size. + * Creates a sample model with a size computed from the given image size. + * If a {@link #sampleModel} has been specified, the new sample model is derived from that field value. + * Otherwise, the new sample model is derived from the image sample model. * - * @param image the image form which to get a sample model. + * @param image the image from which to derive a sample model. * @param bounds the bounds of the image to create, or {@code null} if same as {@code image}. - * @return image sample model with preferred tile size. + * @return image sample model with a tile size derived from the given image size. + * @throws IllegalStateException if {@link #sampleModel} is non-null + * and the given image does not have the same number of bands. * * @see ComputedImage#ComputedImage(SampleModel, RenderedImage...) */ public SampleModel createCompatibleSampleModel(final RenderedImage image, final Rectangle bounds) { - ArgumentChecks.ensureNonNull("image", image); - final Dimension tile = suggestTileSize(image, bounds); SampleModel sm = image.getSampleModel(); + if (sampleModel != null) { + int expected = sampleModel.getNumBands(); + int actual = sm.getNumBands(); + if (expected != actual) { + throw new IllegalStateException(Resources.format(Resources.Keys.UnexpectedNumberOfBands_2, expected, actual)); + } + sm = sampleModel; + } + final Dimension tile = suggestTileSize(image, bounds); if (sm.getWidth() != tile.width || sm.getHeight() != tile.height) { sm = sm.createCompatibleSampleModel(tile.width, tile.height); sm = RasterFactory.unique(sm); 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 1dcb702295..0f54ef3828 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 @@ -994,14 +994,13 @@ public class ImageProcessor implements Cloneable { * <h4>Properties used</h4> * This operation uses the following properties in addition to method parameters: * <ul> + * <li>{@linkplain #getImageLayout() Image layout} for the desired sample model and color model.</li> * <li>{@linkplain ImageLayout#isImageBoundsAdjustmentAllowed Image bounds adjustment flag} for deciding * whether to use a modified image size if {@code bounds} is not divisible by a tile size.</li> * </ul> * - * @param sources the images to overlay. Null array elements are ignored. - * @param bounds range of pixel coordinates, or {@code null} for the union of all source images. - * @param sampleModel the sample model, of {@code null} for automatic. - * @param colorModel the color model, of {@code null} for automatic. + * @param sources the images to overlay. Null array elements are ignored. + * @param bounds range of pixel coordinates, or {@code null} for the union of all source images. * @return the image overlay, or one of the given sources if only one is suitable. * @throws IllegalArgumentException if there is an incompatibility between some source images * or if no image intersect the given bounds. @@ -1009,17 +1008,16 @@ public class ImageProcessor implements Cloneable { * @since 1.5 */ @SuppressWarnings("LocalVariableHidesMemberVariable") - public RenderedImage overlay(final RenderedImage[] sources, final Rectangle bounds, - final SampleModel sampleModel, final ColorModel colorModel) - { + public RenderedImage overlay(final RenderedImage[] sources, final Rectangle bounds) { ArgumentChecks.ensureNonEmpty("sources", sources); + final ImageLayout layout; final boolean parallel; - final boolean autoTileSize; synchronized (this) { - autoTileSize = layout.isTileSizeAdjustmentAllowed; + layout = this.layout; parallel = executionMode != Mode.SEQUENTIAL; } - return ImageOverlay.create(sources, bounds, sampleModel, colorModel, autoTileSize | (bounds != null), parallel); + return ImageOverlay.create(sources, bounds, layout.sampleModel, layout.colorModel, + layout.isTileSizeAdjustmentAllowed | (bounds != null), parallel); } /** @@ -1033,27 +1031,32 @@ public class ImageProcessor implements Cloneable { * <h4>Properties used</h4> * This operation uses the following properties in addition to method parameters: * <ul> + * <li>{@linkplain #getImageLayout() Image layout} for the default sample model.</li> * <li>{@linkplain ImageLayout#isImageBoundsAdjustmentAllowed Image bounds adjustment flag} for deciding * whether to use a modified image size if the source image size is not divisible by a tile size.</li> * </ul> * * @param source the images to reformat. * @param sampleModel the desired sample model. + * Can be null only if a default sample model is specified by {@link ImageLayout#sampleModel}. * @return the reformatted image. * * @since 1.5 */ @SuppressWarnings("LocalVariableHidesMemberVariable") - public RenderedImage reformat(final RenderedImage source, final SampleModel sampleModel) { + public RenderedImage reformat(final RenderedImage source, SampleModel sampleModel) { ArgumentChecks.ensureNonNull("source", source); - ArgumentChecks.ensureNonNull("sampleModel", sampleModel); + final ImageLayout layout; final boolean parallel; - final boolean autoTileSize; synchronized (this) { - autoTileSize = layout.isTileSizeAdjustmentAllowed; + layout = this.layout; parallel = executionMode != Mode.SEQUENTIAL; } - return ImageOverlay.create(new RenderedImage[] {source}, null, sampleModel, null, autoTileSize, parallel); + if (sampleModel == null) { + sampleModel = layout.sampleModel; + ArgumentChecks.ensureNonNull("sampleModel", sampleModel); + } + return ImageOverlay.create(new RenderedImage[] {source}, null, sampleModel, null, layout.isTileSizeAdjustmentAllowed, parallel); } /**