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 8018b2255165b4ac93a172a77105ad406e9c53eb Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Dec 24 12:11:37 2024 +0100 Remove the `ColorModel` property from `ImageLayout` for avoiding an overlap with `Colorizer`. --- .../org/apache/sis/image/BandAggregateLayout.java | 2 +- .../main/org/apache/sis/image/Colorizer.java | 28 ++++++- .../main/org/apache/sis/image/ImageLayout.java | 98 ++++++---------------- .../main/org/apache/sis/image/ImageOverlay.java | 15 ++-- .../main/org/apache/sis/image/ImageProcessor.java | 5 +- 5 files changed, 68 insertions(+), 80 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 d17e6c2164..7b5ae49354 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 @@ -229,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(null, null, preferredTileSize, !exactTileSize, false, false, minTile); + super(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/Colorizer.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Colorizer.java index 18a8f28754..be69136e2f 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Colorizer.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/Colorizer.java @@ -34,8 +34,10 @@ import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.privy.ColorModelBuilder; import org.apache.sis.coverage.privy.ColorScaleBuilder; import org.apache.sis.coverage.privy.ColorModelFactory; +import org.apache.sis.coverage.privy.ImageUtilities; import org.apache.sis.measure.NumberRange; import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.privy.UnmodifiableArrayList; /** @@ -63,7 +65,7 @@ public interface Colorizer extends Function<Colorizer.Target, Optional<ColorMode * such as the {@link SampleDimension}s of the target coverage. * * @author Martin Desruisseaux (Geomatys) - * @version 1.4 + * @version 1.5 * @since 1.4 */ class Target { @@ -107,6 +109,30 @@ public interface Colorizer extends Function<Colorizer.Target, Optional<ColorMode this.visibleBand = -1; } + + /** + * Creates a new target with the same sample dimensions and visible band as the given image. + * This is a convenience constructor for operations producing the same kind of data than an + * existing image, taken as a template. The list of sample dimensions is fetched from the + * image property associated to the {@value PlanarImage#SAMPLE_DIMENSIONS_KEY} key. + * + * @param model sample model of the computed image to colorize (mandatory). + * @param template the image from which to get the sample dimensions and visible band, or {@code null} if none. + * @since 1.5 + */ + public Target(SampleModel model, final RenderedImage template) { + this.model = Objects.requireNonNull(model); + visibleBand = ImageUtilities.getVisibleBand(template); + if (template != null) { + final Object value = template.getProperty(PlanarImage.SAMPLE_DIMENSIONS_KEY); + if (value instanceof SampleDimension[]) { + ranges = UnmodifiableArrayList.wrap((SampleDimension[]) value); + return; + } + } + ranges = null; + } + /** * Returns the sample model of the computed image to colorize. * The color model created by {@link #apply(Target)} 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 a6a457b425..2aa8e81412 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 @@ -47,6 +47,10 @@ import org.apache.sis.feature.internal.Resources; * may be replaced by the tile size {@linkplain #suggestTileSize(int, int) computed} * by this {@code ImageLayout} class.</p> * + * <p>This class contains no information about colors. + * The {@link ColorModel} to associate with a {@link SampleModel} + * is controlled by a separated interface: {@link Colorizer}.</p> + * * <p>Instances of this class are immutable and thread-safe.</p> * * @author Martin Desruisseaux (Geomatys) @@ -79,17 +83,13 @@ 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, 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; + public static final ImageLayout DEFAULT = new ImageLayout(null, null, true, false, true, null); /** * 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. + * + * @see #createCompatibleSampleModel(RenderedImage, Rectangle) */ protected final SampleModel sampleModel; @@ -152,30 +152,21 @@ public class ImageLayout { /** * 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 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 ColorModel colorModel, - final SampleModel sampleModel, + protected ImageLayout(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); @@ -217,7 +208,7 @@ 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(destination.getColorModel(), destination.getSampleModel(), + super(destination.getSampleModel(), new Dimension(destination.getTileWidth(), destination.getTileHeight()), false, false, true, new Point(minTileX, minTileY)); this.destination = destination; @@ -234,33 +225,6 @@ 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}. @@ -268,23 +232,18 @@ public class ImageLayout { * @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 (cascade && model != null) { + width = model.getWidth(); + height = model.getHeight(); } if (Objects.equals(sampleModel, model) && width == preferredTileWidth && height == preferredTileHeight) { return this; } - return new ImageLayout(colorModel, model, new Dimension(width, height), + return new ImageLayout(model, new Dimension(width, height), isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, getPreferredMinTile()); } @@ -300,7 +259,7 @@ public class ImageLayout { */ public ImageLayout allowTileSizeAdjustments(boolean allowed) { if (isTileSizeAdjustmentAllowed == allowed) return this; - return new ImageLayout(colorModel, sampleModel, + return new ImageLayout(sampleModel, getPreferredTileSize(), allowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, getPreferredMinTile()); } @@ -316,7 +275,7 @@ public class ImageLayout { */ public ImageLayout allowImageBoundsAdjustments(boolean allowed) { if (isImageBoundsAdjustmentAllowed == allowed) return this; - return new ImageLayout(colorModel, sampleModel, + return new ImageLayout(sampleModel, getPreferredTileSize(), isTileSizeAdjustmentAllowed, allowed, isPartialTilesAllowed, getPreferredMinTile()); } @@ -332,15 +291,14 @@ public class ImageLayout { */ public ImageLayout allowPartialTiles(boolean allowed) { if (isPartialTilesAllowed == allowed) return this; - return new ImageLayout(colorModel, sampleModel, + return new ImageLayout(sampleModel, getPreferredTileSize(), isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, allowed, getPreferredMinTile()); } /** * Creates a new layout with the tile size and tile indices of the given image. - * Other properties of this {@code ImageLayout} (color model, sample model and - * all Boolean flags) are inherited unchanged. + * Other properties of this {@code ImageLayout} (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. @@ -359,7 +317,7 @@ public class ImageLayout { { return this; } - return new ImageLayout(colorModel, sampleModel, + return new ImageLayout(sampleModel, preferredTileSize, isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, preferredMinTile); } @@ -377,7 +335,7 @@ public class ImageLayout { if (size.width == preferredTileWidth && size.height == preferredTileHeight) { return this; } - return new ImageLayout(colorModel, sampleModel, + return new ImageLayout(sampleModel, size, isTileSizeAdjustmentAllowed, isImageBoundsAdjustmentAllowed, isPartialTilesAllowed, getPreferredMinTile()); } @@ -491,8 +449,7 @@ 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 {@linkplain #colorModel preferred color model} or, if the latter - * is unspecified, the {@linkplain RenderedImage#getColorModel() image color model} supports transparency. + * <p>This method checks whether 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> @@ -505,17 +462,16 @@ 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) { - cm = image.getColorModel(); + final ColorModel cm = image.getColorModel(); allowPartialTiles = (cm != null); - } - if (allowPartialTiles) { - if (cm instanceof IndexColorModel) { - allowPartialTiles = ((IndexColorModel) cm).getTransparentPixel() == 0; - } else if (cm != null) { - allowPartialTiles = (cm.getTransparency() != ColorModel.OPAQUE); + if (allowPartialTiles) { + if (cm instanceof IndexColorModel) { + allowPartialTiles = ((IndexColorModel) cm).getTransparentPixel() == 0; + } else { + allowPartialTiles = (cm.getTransparency() != ColorModel.OPAQUE); + } } } /* 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 2749c490e4..fb6fd7ca6d 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 @@ -85,19 +85,20 @@ final class ImageOverlay extends MultiSourceImage { * @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 colorizer function for deriving a color model, of {@code null} for automatic. * @param autoTileSize whether this method is allowed to change the tile size. * @param parallel whether parallel computation is allowed. * @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 bounds. */ - static RenderedImage create(RenderedImage[] sources, Rectangle bounds, SampleModel sampleModel, ColorModel colorModel, - final boolean autoTileSize, final boolean parallel) + static RenderedImage create(RenderedImage[] sources, Rectangle bounds, SampleModel sampleModel, + final Colorizer colorizer, final boolean autoTileSize, final boolean parallel) { final Area aoi = (bounds != null) ? new Area(bounds) : null; Area[] contributions = new Area[sources.length]; final Area validArea = new Area(); + ColorModel colorModel = null; /* * Filter the source images for keeping only the ones that intersect the bounds. * Check image compatibility (number of bands) and color model in the same loop. @@ -146,14 +147,16 @@ final class ImageOverlay extends MultiSourceImage { } /* * Except if there is no image, the sample model should be non-null at this point. - * However, the color model may still be null if none was specified in argument and - * no compatible color model was found in the source images. Leave thet color model - * to null (i.e., we don't invent colors when we don't know what they should be). + * The color model is optionally specified in the colorizer, which we check now + * with the current color model used only as a fallback. */ if (count == 0) { throw new IllegalArgumentException(Resources.format(Resources.Keys.SourceImagesDoNotIntersect)); } final RenderedImage main = sources[0]; + if (colorizer != null) { + colorModel = colorizer.apply(new Colorizer.Target(sampleModel, main)).orElse(colorModel); + } if (count == 1 && sampleModel.equals(main.getSampleModel())) { return (colorModel != null) ? RecoloredImage.apply(main, colorModel) : main; } 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 0f54ef3828..3ea729e744 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 @@ -997,6 +997,7 @@ public class ImageProcessor implements Cloneable { * <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> + * <li>{@linkplain #getColorizer() Colorizer} for customizing the rendered image color model.</li> * </ul> * * @param sources the images to overlay. Null array elements are ignored. @@ -1011,12 +1012,14 @@ public class ImageProcessor implements Cloneable { public RenderedImage overlay(final RenderedImage[] sources, final Rectangle bounds) { ArgumentChecks.ensureNonEmpty("sources", sources); final ImageLayout layout; + final Colorizer colorizer; final boolean parallel; synchronized (this) { layout = this.layout; + colorizer = this.colorizer; parallel = executionMode != Mode.SEQUENTIAL; } - return ImageOverlay.create(sources, bounds, layout.sampleModel, layout.colorModel, + return ImageOverlay.create(sources, bounds, layout.sampleModel, colorizer, layout.isTileSizeAdjustmentAllowed | (bounds != null), parallel); }