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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 9aa776c7af Last adjustements on the `Colorizer` work and addition of a 
convenience `GridcoverageProcessor.visualize(GridCoverage, …)` method.
9aa776c7af is described below

commit 9aa776c7afbc577dd398dfa8335618af819e7e33
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Mar 30 15:47:10 2023 +0200

    Last adjustements on the `Colorizer` work and addition of a convenience 
`GridcoverageProcessor.visualize(GridCoverage, …)` method.
---
 .../org/apache/sis/coverage/grid/GridCoverage.java |  4 +-
 .../sis/coverage/grid/GridCoverageBuilder.java     |  2 +
 .../sis/coverage/grid/GridCoverageProcessor.java   | 83 +++++++++++++++++++++-
 .../apache/sis/coverage/grid/ImageRenderer.java    |  6 +-
 .../apache/sis/coverage/grid/SliceGeometry.java    |  2 +
 .../apache/sis/image/BandedSampleConverter.java    |  6 +-
 .../java/org/apache/sis/image/ImageProcessor.java  | 18 +++--
 .../java/org/apache/sis/image/Visualization.java   | 27 ++++---
 .../sis/internal/coverage/SampleDimensions.java    | 31 ++++++--
 .../sis/internal/map/coverage/RenderingData.java   | 24 ++-----
 10 files changed, 155 insertions(+), 48 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index fbea3ecdb7..bb4c8d5e25 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -314,10 +314,10 @@ public abstract class GridCoverage extends BandedCoverage 
{
             final MathTransform1D[] converters, final ImageProcessor processor)
     {
         try {
-            SampleDimensions.CONVERTED_BANDS.set(sampleDimensions);
+            SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.set(sampleDimensions);
             return processor.convert(source, getRanges(), converters, 
bandType);
         } finally {
-            SampleDimensions.CONVERTED_BANDS.remove();
+            SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.remove();
         }
     }
 
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index ca04e9a09e..15a52e41ad 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -483,6 +483,8 @@ public class GridCoverageBuilder {
                  */
                 if (bands != null) {
                     properties.put(PlanarImage.SAMPLE_DIMENSIONS_KEY, 
bands.toArray(SampleDimension[]::new));
+                } else {
+                    properties.remove(PlanarImage.SAMPLE_DIMENSIONS_KEY);
                 }
                 if (raster instanceof WritableRaster) {
                     final WritableRaster wr = (WritableRaster) raster;
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index b915d36752..a57a73b479 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -23,7 +23,6 @@ import java.util.Objects;
 import java.util.function.Function;
 import java.awt.Shape;
 import java.awt.Rectangle;
-import java.awt.image.ColorModel;
 import java.awt.image.RenderedImage;
 import javax.measure.Quantity;
 import org.opengis.util.FactoryException;
@@ -39,6 +38,7 @@ import org.apache.sis.image.DataType;
 import org.apache.sis.image.Colorizer;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.image.Interpolation;
+import org.apache.sis.internal.coverage.SampleDimensions;
 import org.apache.sis.internal.coverage.MultiSourcesArgument;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
@@ -220,6 +220,7 @@ public class GridCoverageProcessor implements Cloneable {
      * @param colorizer colorization algorithm to apply on computed image, or 
{@code null} for default.
      *
      * @see ImageProcessor#setColorizer(Colorizer)
+     * @see #visualize(GridCoverage, GridExtent)
      *
      * @since 1.4
      */
@@ -328,6 +329,12 @@ public class GridCoverageProcessor implements Cloneable {
      * If {@code maskInside} is {@code false}, then the mask is reversed:
      * the pixels set to fill values are the ones outside the ROI.
      *
+     * <h4>Properties used</h4>
+     * This operation uses the following properties in addition to method 
parameters:
+     * <ul>
+     *   <li>{@linkplain #getFillValues() Fill values} values to assign to 
pixels inside/outside the region of interest.</li>
+     * </ul>
+     *
      * @param  source      the coverage on which to apply a mask.
      * @param  mask        region (in arbitrary CRS) of the mask.
      * @param  maskInside  {@code true} for masking pixels inside the shape, 
or {@code false} for masking outside.
@@ -369,13 +376,19 @@ public class GridCoverageProcessor implements Cloneable {
      * If the source coverage is backed by a {@link 
java.awt.image.WritableRenderedImage},
      * then changes in the source coverage are reflected in the returned 
coverage and conversely.
      *
+     * <h4>Properties used</h4>
+     * This operation uses the following properties in addition to method 
parameters:
+     * <ul>
+     *   <li>{@linkplain #getColorizer() Colorizer} for customizing the 
rendered image color model.</li>
+     * </ul>
+     *
      * @param  source      the coverage for which to convert sample values.
      * @param  converters  the transfer functions to apply on each sample 
dimension of the source coverage.
      * @param  sampleDimensionModifier  a callback for modifying the {@link 
SampleDimension.Builder} default
      *         configuration for each sample dimension of the target coverage, 
or {@code null} if none.
      * @return the coverage which computes converted values from the given 
source.
      *
-     * @see ImageProcessor#convert(RenderedImage, NumberRange<?>[], 
MathTransform1D[], DataType, ColorModel)
+     * @see ImageProcessor#convert(RenderedImage, NumberRange<?>[], 
MathTransform1D[], DataType)
      *
      * @since 1.3
      */
@@ -430,6 +443,12 @@ public class GridCoverageProcessor implements Cloneable {
      *       of this {@code translate(…)} method into a single 
translation.</li>
      * </ul>
      *
+     * <h4>Properties used</h4>
+     * This operation uses the following properties in addition to method 
parameters:
+     * <ul>
+     *   <li>(none)</li>
+     * </ul>
+     *
      * @param  source       the grid coverage to translate.
      * @param  translation  translation to apply on each grid axis in order.
      * @return a grid coverage whose grid coordinates (both low and high ones) 
and
@@ -488,6 +507,15 @@ public class GridCoverageProcessor implements Cloneable {
      *       by {@code translate(…)} when possible.</li>
      * </ul>
      *
+     * <h4>Properties used</h4>
+     * This operation uses the following properties in addition to method 
parameters:
+     * <ul>
+     *   <li>{@linkplain #getInterpolation() Interpolation method} (nearest 
neighbor, bilinear, <i>etc</i>).</li>
+     *   <li>{@linkplain #getFillValues() Fill values} for pixels outside 
source image.</li>
+     *   <li>{@linkplain #getPositionalAccuracyHints() Positional accuracy 
hints}
+     *       for enabling faster resampling at the cost of lower 
precision.</li>
+     * </ul>
+     *
      * @param  source  the grid coverage to resample.
      * @param  target  the desired geometry of returned grid coverage. May be 
incomplete.
      * @return a grid coverage with the characteristics specified in the given 
grid geometry.
@@ -742,6 +770,57 @@ public class GridCoverageProcessor implements Cloneable {
         return new BandAggregateGridCoverage(aggregate, snapshot());
     }
 
+    /**
+     * Renders the given grid coverage as an image suitable for displaying 
purpose.
+     * The resulting image is for visualization only and should not be used 
for computational purposes.
+     * There is no guarantee about the number of bands in returned image or 
about which formula is used
+     * for converting floating point values to integer values.
+     *
+     * <h4>How to specify colors</h4>
+     * The image colors can be controlled by the {@link Colorizer} set on this 
coverage processor.
+     * The recommended way is to associate colors to {@linkplain 
Category#getName() category names},
+     * {@linkplain org.apache.sis.measure.MeasurementRange#unit() units of 
measurement}
+     * or other category properties. Example:
+     *
+     * {@snippet lang="java" :
+     *     Map<String,Color[]> colors = Map.of(
+     *         "Temperature", new Color[] {Color.BLUE, Color.MAGENTA, 
Color.RED},
+     *         "Wind speed",  new Color[] {Color.GREEN, Color.CYAN, 
Color.BLUE});
+     *
+     *     processor.setColorizer(Colorizer.forCategories((category) ->
+     *         colors.get(category.getName().toString(Locale.ENGLISH))));
+     *
+     *     RenderedImage visualization = processor.visualize(source, slice);
+     *     }
+     *
+     * <h4>Properties used</h4>
+     * This operation uses the following properties in addition to method 
parameters:
+     * <ul>
+     *   <li>{@linkplain #getColorizer() Colorizer} for customizing the 
rendered image color model.</li>
+     * </ul>
+     *
+     * @param  source  the grid coverage to visualize.
+     * @param  slice   the slice and extent to render, or {@code null} for the 
whole coverage.
+     * @return rendered image for visualization purposes only.
+     * @throws IllegalArgumentException if the given extent does not have the 
same number of dimensions
+     *         than the specified coverage or does not intersect.
+     *
+     * @see ImageProcessor#visualize(RenderedImage)
+     *
+     * @since 1.4
+     */
+    public RenderedImage visualize(final GridCoverage source, final GridExtent 
slice) {
+        ArgumentChecks.ensureNonNull("source", source);
+        final SampleDimension[] bands = 
source.getSampleDimensions().toArray(SampleDimension[]::new);
+        final RenderedImage image = source.render(slice);
+        try {
+            SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.set(bands);
+            return imageProcessor.visualize(image);
+        } finally {
+            SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.remove();
+        }
+    }
+
     /**
      * Invoked when an ignorable exception occurred.
      *
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
index 9fde21c79b..b9005bd109 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -447,7 +447,7 @@ public class ImageRenderer {
     public GridGeometry getImageGeometry(final int dimCRS) {
         GridGeometry ig = imageGeometry;
         if (ig == null || dimCRS != GridCoverage2D.BIDIMENSIONAL) {
-            if (isSameGeometry(dimCRS)) {
+            if (imageUseSameGeometry(dimCRS)) {
                 ig = geometry;
             } else try {
                 ig = new SliceGeometry(geometry, sliceExtent, gridDimensions, 
mtFactory)
@@ -515,7 +515,7 @@ public class ImageRenderer {
      * can return {@link #geometry} directly. This common case avoids the need 
for more costly computation with
      * {@link SliceGeometry}.
      */
-    private boolean isSameGeometry(final int dimCRS) {
+    private boolean imageUseSameGeometry(final int dimCRS) {
         final int tgtDim = geometry.getTargetDimension();
         ArgumentChecks.ensureBetween("dimCRS", GridCoverage2D.BIDIMENSIONAL, 
tgtDim, dimCRS);
         if (tgtDim == dimCRS && geometry.getDimension() == 
gridDimensions.length) {
@@ -762,7 +762,7 @@ public class ImageRenderer {
         }
         SliceGeometry supplier = null;
         if (imageGeometry == null) {
-            if (isSameGeometry(GridCoverage2D.BIDIMENSIONAL)) {
+            if (imageUseSameGeometry(GridCoverage2D.BIDIMENSIONAL)) {
                 imageGeometry = geometry;
             } else {
                 supplier = new SliceGeometry(geometry, sliceExtent, 
gridDimensions, mtFactory);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
index dc57a98663..26efeb3bac 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
@@ -59,6 +59,7 @@ final class SliceGeometry implements Function<RenderedImage, 
GridGeometry> {
 
     /**
      * Extents of the slice to take in the {@linkplain #geometry}.
+     * May be {@code null} if unknown.
      */
     private final GridExtent sliceExtent;
 
@@ -189,6 +190,7 @@ final class SliceGeometry implements 
Function<RenderedImage, GridGeometry> {
         }
         GeneralEnvelope subArea = null;
         if (useSubExtent && cornerToCRS != null) try {
+            // `extent` is non-null if `useSubExtent` is true.
             subArea = extent.toEnvelope(cornerToCRS, gridToCRS, null);
         } catch (TransformException e) {
             // GridGeometry.reduce(…) is the public method invoking indirectly 
this method.
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
 
b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
index baab75fe89..8cc2527aa5 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/image/BandedSampleConverter.java
@@ -99,8 +99,8 @@ class BandedSampleConverter extends ComputedImage {
     /**
      * Description of bands, or {@code null} if unknown.
      * Not used by this class, but provided as a {@value 
#SAMPLE_DIMENSIONS_KEY} property.
-     * The value is fetched from {@link SampleDimensions#CONVERTED_BANDS} for 
avoiding to
-     * expose a {@code SampleDimension[]} argument in public {@link 
ImageProcessor} API.
+     * The value is fetched from {@link 
SampleDimensions#IMAGE_PROCESSOR_ARGUMENT} for avoiding
+     * to expose a {@code SampleDimension[]} argument in public {@link 
ImageProcessor} API.
      *
      * @see #getProperty(String)
      */
@@ -221,7 +221,7 @@ class BandedSampleConverter extends ComputedImage {
         }
         final int numBands = converters.length;
         final BandedSampleModel sampleModel = 
layout.createBandedSampleModel(targetType, numBands, source, null);
-        final SampleDimension[] sampleDimensions = 
SampleDimensions.CONVERTED_BANDS.get();
+        final SampleDimension[] sampleDimensions = 
SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.get();
         final int visibleBand = ImageUtilities.getVisibleBand(source);
         ColorModel colorModel = ColorModelBuilder.NULL_COLOR_MODEL;
         if (colorizer != null) {
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index e0ca82b6cc..dea9f52adf 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -993,6 +993,12 @@ public class ImageProcessor implements Cloneable {
      * If {@code maskInside} is {@code false}, then the mask is reversed:
      * the pixels set to fill values are the ones outside the shape.
      *
+     * <h4>Properties used</h4>
+     * This operation uses the following properties in addition to method 
parameters:
+     * <ul>
+     *   <li>{@linkplain #getFillValues() Fill values} values to assign to 
pixels inside/outside the shape.</li>
+     * </ul>
+     *
      * @param  source      the image on which to apply a mask.
      * @param  mask        geometric area (in pixel coordinates) of the mask.
      * @param  maskInside  {@code true} for masking pixels inside the shape, 
or {@code false} for masking outside.
@@ -1037,7 +1043,7 @@ public class ImageProcessor implements Cloneable {
      * <h4>Properties used</h4>
      * This operation uses the following properties in addition to method 
parameters:
      * <ul>
-     *   <li>{@linkplain #getColorizer() Colorizer}.</li>
+     *   <li>{@linkplain #getColorizer() Colorizer} for customizing the 
rendered image color model.</li>
      * </ul>
      *
      * <h4>Result relationship with source</h4>
@@ -1135,7 +1141,7 @@ public class ImageProcessor implements Cloneable {
      * @param  source    the image to be resampled.
      * @param  bounds    domain of pixel coordinates of resampled image to 
create.
      *                   Updated by this method if {@link Resizing#EXPAND} 
policy is applied.
-     * @param  toSource  conversion of pixel coordinates from resampled image 
to {@code source} image.
+     * @param  toSource  conversion of pixel center coordinates from resampled 
image to {@code source} image.
      * @return resampled image (may be {@code source}).
      *
      * @see GridCoverageProcessor#resample(GridCoverage, GridGeometry)
@@ -1362,7 +1368,7 @@ public class ImageProcessor implements Cloneable {
      * <h4>Properties used</h4>
      * This operation uses the following properties in addition to method 
parameters:
      * <ul>
-     *   <li>{@linkplain #getColorizer() Colorizer}.</li>
+     *   <li>{@linkplain #getColorizer() Colorizer} for customizing the 
rendered image color model.</li>
      * </ul>
      *
      * @param  source  the image to recolor for visualization purposes.
@@ -1406,15 +1412,17 @@ public class ImageProcessor implements Cloneable {
      *       if {@code bounds} size is not divisible by a tile size.</li>
      *   <li>{@linkplain #getPositionalAccuracyHints() Positional accuracy 
hints}
      *       for enabling faster resampling at the cost of lower 
precision.</li>
-     *   <li>{@linkplain #getColorizer() Colorizer}.</li>
+     *   <li>{@linkplain #getColorizer() Colorizer} for customizing the 
rendered image color model.</li>
      * </ul>
      *
      * @param  source    the image to be resampled and recolored.
      * @param  bounds    domain of pixel coordinates of resampled image to 
create.
      *                   Updated by this method if {@link Resizing#EXPAND} 
policy is applied.
-     * @param  toSource  conversion of pixel coordinates from resampled image 
to {@code source} image.
+     * @param  toSource  conversion of pixel center coordinates from resampled 
image to {@code source} image.
      * @return resampled and recolored image for visualization purposes only.
      *
+     * @see #resample(RenderedImage, Rectangle, MathTransform)
+     *
      * @since 1.4
      */
     public RenderedImage visualize(final RenderedImage source, final Rectangle 
bounds, final MathTransform toSource) {
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
index 35229898d1..7237500a48 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Visualization.java
@@ -197,9 +197,12 @@ final class Visualization extends ResampledImage {
             this.bounds   = bounds;
             this.source   = source;
             this.toSource = toSource;
-            Object ranges = source.getProperty(SAMPLE_DIMENSIONS_KEY);
-            if (ranges instanceof SampleDimension[]) {
-                sampleDimensions = (SampleDimension[]) ranges;
+            sampleDimensions = SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.get();
+            if (sampleDimensions == null) {
+                Object ranges = source.getProperty(SAMPLE_DIMENSIONS_KEY);
+                if (ranges instanceof SampleDimension[]) {
+                    sampleDimensions = (SampleDimension[]) ranges;
+                }
             }
         }
 
@@ -247,8 +250,19 @@ final class Visualization extends ResampledImage {
              * Keep only the band to make visible in order to reduce the 
amount of calculation during
              * resampling and for saving memory.
              */
-            while (source instanceof ImageAdapter) {
-                source = ((ImageAdapter) source).source;
+            if (toSource == null) {
+                toSource = MathTransforms.identity(BIDIMENSIONAL);
+            }
+            for (;;) {
+                if (source instanceof ImageAdapter) {
+                    source = ((ImageAdapter) source).source;
+                } else if (source instanceof ResampledImage) {
+                    final ResampledImage r = (ResampledImage) source;
+                    toSource = MathTransforms.concatenate(toSource, 
r.toSource);
+                    source   = r.getSource();
+                } else {
+                    break;
+                }
             }
             source = BandSelectImage.create(source, new int[] {visibleBand});
             final SampleDimension visibleSD = (sampleDimensions != null && 
visibleBand < sampleDimensions.length)
@@ -259,9 +273,6 @@ final class Visualization extends ResampledImage {
              * requires the tile layout of destination image to be the same as 
source image.
              * Otherwise combine interpolation and value conversions in a 
single operation.
              */
-            if (toSource == null) {
-                toSource = MathTransforms.identity(BIDIMENSIONAL);
-            }
             final boolean shortcut = toSource.isIdentity() && (bounds == null 
|| ImageUtilities.getBounds(source).contains(bounds));
             if (shortcut) {
                 layout = ImageLayout.fixedSize(source);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java
index d896643025..a8dcb60a16 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java
@@ -37,14 +37,33 @@ import org.apache.sis.util.Static;
  */
 public final class SampleDimensions extends Static {
     /**
-     * The sample dimensions of a {@link 
org.apache.sis.image.BandedSampleConverter} image.
-     * We use this thread-local variable as an internal workaround for an 
parameter that we
-     * do not expose in the public API of {@link ImageProcessor}.
+     * A hidden argument passed to some {@link ImageProcessor} operations.
+     * Used for a parameter that we do not want to expose in the public API,
+     * because {@link ImageProcessor} is not supposed to know grid coverages.
+     * We may revisit in future Apache SIS version if we find a better way to
+     * pass this information.
      *
-     * <p>The content of the array in this thread-local variable shall not be 
modified,
-     * because it may be a direct reference to an internal array (not a 
clone).</p>
+     * This is used in:
+     * <ul>
+     *   <li>The <em>target</em> sample dimensions of a {@link 
org.apache.sis.image.BandedSampleConverter} image.</li>
+     *   <li>The <em>source</em> sample dimensions of a {@link 
org.apache.sis.image.Visualization} image.</li>
+     * </ul>
+     *
+     * Usage pattern:
+     *
+     * {@snippet lang="java" :
+     *     try {
+     *         SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.set(dataRanges);
+     *         return imageProcessor.doSomeStuff(...);
+     *     } finally {
+     *         SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.remove();
+     *     }
+     *     }
+     *
+     * The content of the array in this thread-local variable shall not be 
modified,
+     * because it may be a direct reference to an internal array (not a clone).
      */
-    public static final ThreadLocal<SampleDimension[]> CONVERTED_BANDS = new 
ThreadLocal<>();
+    public static final ThreadLocal<SampleDimension[]> 
IMAGE_PROCESSOR_ARGUMENT = new ThreadLocal<>();
 
     /**
      * Do not allow instantiation of this class.
diff --git 
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
 
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
index bea3a65a64..6c51dd0946 100644
--- 
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
+++ 
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
@@ -662,30 +662,16 @@ public class RenderingData implements Cloneable {
          */
         if (CREATE_INDEX_COLOR_MODEL) {
             final ColorModelType ct = 
ColorModelType.find(recoloredImage.getColorModel());
-            if (ct.isSlow || (ct.useColorRamp && processor.getColorizer() != 
null)) {
-                return 
processor.visualize(withSampleDimensions(recoloredImage), bounds, 
displayToCenter);
+            if (ct.isSlow || (ct.useColorRamp && processor.getColorizer() != 
null)) try {
+                SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.set(dataRanges);
+                return processor.visualize(recoloredImage, bounds, 
displayToCenter);
+            } finally {
+                SampleDimensions.IMAGE_PROCESSOR_ARGUMENT.remove();
             }
         }
         return processor.resample(recoloredImage, bounds, displayToCenter);
     }
 
-    /**
-     * Returns an image augmented with the sample dimensions if not already 
present.
-     * If the property is present but with a different value, the {@link 
#dataRanges}
-     * will overwrite the image property value.
-     *
-     * @param  image  the image for which to add a property if not already 
present.
-     * @return image augmented with the given property.
-     */
-    private RenderedImage withSampleDimensions(RenderedImage image) {
-        final String key = PlanarImage.SAMPLE_DIMENSIONS_KEY;
-        final SampleDimension[] value = dataRanges;
-        if (!Objects.deepEquals(image.getProperty(key), value)) {
-            image = processor.addUserProperties(image, Map.of(key, value));
-        }
-        return image;
-    }
-
     /**
      * Conversion or transformation from {@linkplain 
PlanarCanvas#getObjectiveCRS() objective CRS} to
      * {@linkplain #data} CRS. This transform will include {@code 
WraparoundTransform} steps if needed.

Reply via email to