You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2024/12/25 15:29:48 UTC

(sis) 01/03: Remove the `ColorModel` property from `ImageLayout` for avoiding an overlap with `Colorizer`.

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 <ma...@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);
     }