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);
     }
 
     /**

Reply via email to