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 da7281a6b0 Bug fix: `ColorModelBuilder` sometime created a "compact" 
color model when it was not desired.
da7281a6b0 is described below

commit da7281a6b058c6ca1b2146584f884b885fa09f1e
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Apr 4 12:55:01 2023 +0200

    Bug fix: `ColorModelBuilder` sometime created a "compact" color model when 
it was not desired.
---
 .../org/apache/sis/coverage/SampleDimension.java   | 109 ++++++++++----------
 .../sis/coverage/grid/GridCoverageBuilder.java     |   2 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    |   2 +-
 .../java/org/apache/sis/image/BandSelectImage.java |   1 +
 .../apache/sis/image/BandedSampleConverter.java    |   3 +-
 .../main/java/org/apache/sis/image/Colorizer.java  |   2 +-
 .../java/org/apache/sis/image/Visualization.java   |  17 ++--
 .../internal/coverage/j2d/ColorModelBuilder.java   | 111 +++++++++++++++------
 .../coverage/j2d/ColorModelBuilderTest.java        |  10 +-
 9 files changed, 151 insertions(+), 106 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
index 8f105ffe50..f0a24e8c9e 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -51,8 +51,8 @@ import org.apache.sis.util.Debug;
  * is a forest” and some other values for <cite>quantitative</cite> 
information like a temperature
  * measurements.
  *
- * <div class="note"><b>Example:</b>
- * an image of sea surface temperature (SST) could define the following 
categories:
+ * <h2>Example</h2>
+ * An image of sea surface temperature (SST) could define the following 
categories:
  * <table class="sis">
  *   <caption>Example of categories in a sample dimension</caption>
  *   <tr><th>Values range</th> <th>Meaning</th></tr>
@@ -62,7 +62,6 @@ import org.apache.sis.util.Debug;
  *   <tr><td>[10…210]</td>     <td>Temperature to be converted into Celsius 
degrees through a linear equation</td></tr>
  * </table>
  * In this example, sample values in range [10…210] define a quantitative 
category, while all others categories are qualitative.
- * </div>
  *
  * <h2>Relationship with metadata</h2>
  * This class provides the same information than ISO 19115 {@link 
org.opengis.metadata.content.SampleDimension},
@@ -650,8 +649,8 @@ public class SampleDimension implements Serializable {
          * Sets an identification of the sample dimension as a character 
sequence.
          * This is a convenience method for creating a {@link GenericName} 
from the given characters.
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#setName(GenericName)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link #setName(GenericName)}.
          *
          * @param  name  identification of the sample dimension.
          * @return {@code this}, for method call chaining.
@@ -664,8 +663,8 @@ public class SampleDimension implements Serializable {
          * Sets an identification of the sample dimension as a band number.
          * This method should be used only when no more descriptive name is 
available.
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#setName(GenericName)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link #setName(GenericName)}.
          *
          * @param  band  sequence identifier of the sample dimension to create.
          * @return {@code this}, for method call chaining.
@@ -780,13 +779,13 @@ public class SampleDimension implements Serializable {
          * Adds a qualitative category for samples of the given boolean value.
          * The {@code true} value is represented by 1 and the {@code false} 
value is represented by 0.
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.
          *
          * @param  name    the category name as a {@link String} or {@link 
InternationalString} object,
          *                 or {@code null} for a default "no data" name.
@@ -802,13 +801,13 @@ public class SampleDimension implements Serializable {
          * Adds a qualitative category for samples of the given tiny (8 bits) 
integer value.
          * The argument is treated as a signed integer ({@value 
Byte#MIN_VALUE} to {@value Byte#MAX_VALUE}).
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.
          *
          * @param  name    the category name as a {@link String} or {@link 
InternationalString} object,
          *                 or {@code null} for a default "no data" name.
@@ -823,13 +822,13 @@ public class SampleDimension implements Serializable {
          * Adds a qualitative category for samples of the given short (16 
bits) integer value.
          * The argument is treated as a signed integer ({@value 
Short#MIN_VALUE} to {@value Short#MAX_VALUE}).
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.
          *
          * @param  name    the category name as a {@link String} or {@link 
InternationalString} object,
          *                 or {@code null} for a default "no data" name.
@@ -844,13 +843,13 @@ public class SampleDimension implements Serializable {
          * Adds a qualitative category for samples of the given integer value.
          * The argument is treated as a signed integer ({@value 
Integer#MIN_VALUE} to {@value Integer#MAX_VALUE}).
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.
          *
          * @param  name    the category name as a {@link String} or {@link 
InternationalString} object,
          *                 or {@code null} for a default "no data" name.
@@ -864,13 +863,13 @@ public class SampleDimension implements Serializable {
         /**
          * Adds a qualitative category for samples of the given floating-point 
value.
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.
          *
          * @param  name    the category name as a {@link String} or {@link 
InternationalString} object,
          *                 or {@code null} for a default "no data" name.
@@ -891,13 +890,13 @@ public class SampleDimension implements Serializable {
         /**
          * Adds a qualitative category for samples of the given double 
precision floating-point value.
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.
          *
          * @param  name    the category name as a {@link String} or {@link 
InternationalString} object,
          *                 or {@code null} for a default "no data" name.
@@ -918,13 +917,13 @@ public class SampleDimension implements Serializable {
         /**
          * Adds a qualitative category for samples in the given range of 
values.
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQualitative(CharSequence, NumberRange)}.
          *
          * @param  name     the category name as a {@link String} or {@link 
InternationalString} object,
          *                  or {@code null} for a default "no data" name.
@@ -944,10 +943,10 @@ public class SampleDimension implements Serializable {
          * This is the most generic method for adding a qualitative category.
          * All other {@code addQualitative(name, …)} methods are convenience 
methods delegating their work to this method.
          *
-         * <div class="note"><b>Usage note:</b>
-         * the {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
+         * <h4>Usage note</h4>
+         * The {@link #setBackground(CharSequence, Number)} method should be 
used instead of this method
          * when the aim is to define a default "no data" category to use when 
the missing value cannot
-         * be categorized more precisely (cloud, instrument error, 
<i>etc</i>).</div>
+         * be categorized more precisely (cloud, instrument error, <i>etc</i>).
          *
          * @param  name     the category name as a {@link String} or {@link 
InternationalString} object,
          *                  or {@code null} for a default "no data" name.
@@ -969,8 +968,8 @@ public class SampleDimension implements Serializable {
          * (not necessarily the {@link Float#NaN} canonical value)
          * and that value shall not have been used by another category.
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#mapQualitative(CharSequence, NumberRange, float)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#mapQualitative(CharSequence, NumberRange, float)}.
          *
          * @param  name       the category name as a {@link String} or {@link 
InternationalString} object,
          *                    or {@code null} for a default "no data" name.
@@ -1037,8 +1036,8 @@ public class SampleDimension implements Serializable {
          * <p><b>Warning:</b> this method is provided for convenience when the 
scale and offset factors are not explicitly specified.
          * If those factor are available, then the other {@code 
addQuantitative(name, samples, …)} methods are more reliable.</p>
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.
          *
          * @param  name        the category name as a {@link String} or {@link 
InternationalString} object,
          *                     or {@code null} for a default "data" name.
@@ -1076,8 +1075,8 @@ public class SampleDimension implements Serializable {
          * Adds a quantitative category for values ranging from {@code 
minimum} to {@code maximum} inclusive
          * in the given units of measurement. The transfer function is set to 
identity.
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.
          *
          * @param  name     the category name as a {@link String} or {@link 
InternationalString} object,
          *                  or {@code null} for a default "data" name.
@@ -1095,8 +1094,8 @@ public class SampleDimension implements Serializable {
          * Adds a quantitative category for values ranging from {@code 
minimum} to {@code maximum} inclusive
          * in the given units of measurement. The transfer function is set to 
identity.
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.
          *
          * @param  name     the category name as a {@link String} or {@link 
InternationalString} object,
          *                  or {@code null} for a default "data" name.
@@ -1118,8 +1117,8 @@ public class SampleDimension implements Serializable {
          *
          * Results of above conversion are measurements in the units specified 
by the {@code units} argument.
          *
-         * <div class="note"><b>Implementation note:</b>
-         * this convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+         * <h4>Default implementation</h4>
+         * This convenience method delegates to {@link 
#addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.
          *
          * @param  name    the category name as a {@link String} or {@link 
InternationalString} object.
          *                 or {@code null} for a default "data" name.
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 5e46bc3419..c6b048c31c 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
@@ -470,7 +470,7 @@ public class GridCoverageBuilder {
                  */
                 bands = GridCoverage2D.defaultIfAbsent(bands, null, 
raster.getNumBands());
                 final SampleModel sm = raster.getSampleModel();
-                final ColorModelBuilder colorizer = new 
ColorModelBuilder(ColorModelBuilder.GRAYSCALE, null);
+                final var colorizer = new 
ColorModelBuilder(ColorModelBuilder.GRAYSCALE, null, false);
                 final ColorModel colors;
                 if (colorizer.initialize(sm, bands.get(visibleBand)) || 
colorizer.initialize(sm, visibleBand)) {
                     colors = 
colorizer.createColorModel(ImageUtilities.getBandType(sm), bands.size(), 
visibleBand);
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 1d5bd9e17e..d9b0fef705 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
@@ -753,7 +753,7 @@ public class ImageRenderer {
     @SuppressWarnings("UseOfObsoleteCollectionType")
     public RenderedImage createImage() {
         final Raster raster = createRaster();
-        final ColorModelBuilder colorizer = new ColorModelBuilder(colors, 
null);
+        final var colorizer = new ColorModelBuilder(colors, null, false);
         final ColorModel colors;
         final SampleModel sm = raster.getSampleModel();
         if (colorizer.initialize(sm, bands[visibleBand]) || 
colorizer.initialize(sm, visibleBand)) {
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
index 9f12e4c2df..07b4f7cd12 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandSelectImage.java
@@ -78,6 +78,7 @@ final class BandSelectImage extends SourceAlignedImage {
     private BandSelectImage(final RenderedImage source, final ColorModel cm, 
final int[] bands) {
         super(source, cm, 
source.getSampleModel().createSubsetSampleModel(bands));
         this.bands = bands;
+        ensureCompatible(cm);
     }
 
     /**
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 22a6a95c71..2076fe5329 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
@@ -131,6 +131,7 @@ class BandedSampleConverter extends ComputedImage {
         this.colorModel = colorModel;
         this.converters = converters;
         this.sampleDimensions = sampleDimensions;
+        ensureCompatible(colorModel);
         /*
          * Get an estimation of the resolution, arbitrarily looking in the 
middle of the range of values.
          * If the converters are linear (which is the most common case), the 
middle value does not matter
@@ -238,7 +239,7 @@ class BandedSampleConverter extends ComputedImage {
             if (sampleDimensions != null && visibleBand >= 0 && visibleBand < 
sampleDimensions.length) {
                 sd = sampleDimensions[visibleBand];
             }
-            final var builder = new 
ColorModelBuilder(ColorModelBuilder.GRAYSCALE, null);
+            final var builder = new 
ColorModelBuilder(ColorModelBuilder.GRAYSCALE, null, false);
             if (builder.initialize(source.getSampleModel(), sd) ||
                 builder.initialize(source.getColorModel()))
             {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
index 6d32896d96..e1f1d7a937 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
@@ -291,7 +291,7 @@ public interface Colorizer extends 
Function<Colorizer.Target, Optional<ColorMode
                     final List<SampleDimension> ranges = 
target.getRanges().orElse(null);
                     if (visibleBand < ranges.size()) {
                         final SampleModel model = target.getSampleModel();
-                        final var c = new ColorModelBuilder(colors, null);
+                        final var c = new ColorModelBuilder(colors, null, 
false);
                         if (c.initialize(model, ranges.get(visibleBand))) {
                             return 
Optional.ofNullable(c.createColorModel(model.getDataType(), 
model.getNumBands(), visibleBand));
                         }
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 ee080d49dd..c7c44fa529 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
@@ -287,7 +287,8 @@ final class Visualization extends ResampledImage {
             if (colorizer != null) {
                 colorModel = colorizer.apply(target).orElse(null);
             }
-            final ColorModel sourceCM = coloredSource.getColorModel();
+            final SampleModel sourceSM = coloredSource.getSampleModel();
+            final ColorModel  sourceCM = coloredSource.getColorModel();
             /*
              * Get a `ColorModelBuilder` which will compute the `ColorModel` 
of destination image.
              * There is different ways to setup the builder, depending on 
which `Colorizer` is used.
@@ -307,8 +308,8 @@ final class Visualization extends ResampledImage {
                  * Ranges of sample values were not specified explicitly. 
Instead, we will try to infer them
                  * in various ways: sample dimensions, scaled color model, or 
image statistics in last resort.
                  */
-                builder = new ColorModelBuilder(target.categoryColors, 
sourceCM);
-                initialized = 
builder.initialize(coloredSource.getSampleModel(), visibleSD);
+                builder = new ColorModelBuilder(target.categoryColors, 
sourceCM, true);
+                initialized = builder.initialize(sourceSM, visibleSD);
                 if (initialized) {
                     /*
                      * If we have been able to configure ColorModelBuilder 
using SampleDimension, apply an adjustment
@@ -328,10 +329,10 @@ final class Visualization extends ResampledImage {
                     if (!initialized) {
                         if (coloredSource instanceof RecoloredImage) {
                             final RecoloredImage colored = (RecoloredImage) 
coloredSource;
-                            builder.initialize(colored.minimum, 
colored.maximum);
+                            builder.initialize(colored.minimum, 
colored.maximum, sourceSM.getDataType());
                             initialized = true;
                         } else {
-                            initialized = 
builder.initialize(coloredSource.getSampleModel(), visibleBand);
+                            initialized = builder.initialize(sourceSM, 
visibleBand);
                         }
                     }
                 }
@@ -343,13 +344,13 @@ final class Visualization extends ResampledImage {
                  */
                 final DoubleUnaryOperator[] sampleFilters = 
SampleDimensions.toSampleFilters(visibleSD);
                 final Statistics statistics = 
processor.valueOfStatistics(source, null, sampleFilters)[VISIBLE_BAND];
-                builder.initialize(statistics.minimum(), statistics.maximum());
+                builder.initialize(statistics.minimum(), statistics.maximum(), 
sourceSM.getDataType());
             }
             if (colorModel == null) {
-                colorModel = builder.compactColorModel(NUM_BANDS, 
VISIBLE_BAND);
+                colorModel = 
builder.createColorModel(ColorModelBuilder.TYPE_COMPACT, NUM_BANDS, 
VISIBLE_BAND);
             }
             converters = new MathTransform1D[] {
-                builder.getSampleToIndexValues()            // Must be after 
`compactColorModel(…)`.
+                builder.getSampleToIndexValues()            // Must be after 
`createColorModel(…)`.
             };
             if (shortcut) {
                 if (converters[0].isIdentity() && colorModel.equals(sourceCM)) 
{
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilder.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilder.java
index a4c9a2d541..4a7214fca0 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilder.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilder.java
@@ -51,7 +51,7 @@ import org.apache.sis.util.resources.Vocabulary;
  * <ol>
  *   <li>Create a new {@link ColorModelBuilder} instance.</li>
  *   <li>Invoke one of {@code initialize(…)} methods.</li>
- *   <li>Invoke {@link #createColorModel(int, int, int)} or {@link 
#compactColorModel(int, int)}.</li>
+ *   <li>Invoke {@link #createColorModel(int, int, int)}.</li>
  *   <li>Invoke {@link #getSampleToIndexValues()} if this auxiliary 
information is useful.</li>
  *   <li>Discards {@code ColorModelBuilder}. Each instance shall be used only 
once.</li>
  * </ol>
@@ -99,11 +99,16 @@ public final class ColorModelBuilder {
     private static final int MAX_VALUE = 0xFF;
 
     /**
-     * The type resulting from sample values conversion applied by {@link 
#compactColorModel(int, int)}.
-     * Current value is {@link DataBuffer#TYPE_BYTE}.
+     * The type resulting from sample values conversion in compact mode.
+     * The value is {@link DataBuffer#TYPE_BYTE}.
      */
     public static final int TYPE_COMPACT = DataBuffer.TYPE_BYTE;
 
+    /**
+     * Whether to rescale the range of sample values to the {@link 
#TYPE_COMPACT} range.
+     */
+    private final boolean compact;
+
     /**
      * Applies a gray scale to quantitative category and transparent colors to 
qualitative categories.
      * This is a possible argument for the {@link 
#ColorModelBuilder(Function)} constructor.
@@ -177,6 +182,9 @@ public final class ColorModelBuilder {
      * and to grayscale colors otherwise, unless {@code inherited} is non-null.
      * Empty arrays of colors are interpreted as explicitly transparent.</p>
      *
+     * <p>This constructor creates a builder in compact mode, unless all 
specified ranges
+     * already fit in {@link #TYPE_COMPACT} range.</p>
+     *
      * @param  colors     the colors to use for each range of values in source 
image.
      * @param  inherited  the colors to use as fallback if some ranges have 
undefined colors, or {@code null}.
      *                    Should be non-null only for styling an exiting image 
before visualization.
@@ -185,6 +193,14 @@ public final class ColorModelBuilder {
         entries = ColorsForRange.list(colors, inherited);
         inheritedColors = inherited;
         this.colors = GRAYSCALE;
+        for (final Map.Entry<NumberRange<?>,Color[]> entry : colors) {
+            final NumberRange<?> range = entry.getKey();
+            if (range.getMinDouble() < 0 || range.getMaxDouble() >= MAX_VALUE 
+ 1) {
+                compact = true;
+                return;
+            }
+        }
+        compact = false;
     }
 
     /**
@@ -200,10 +216,12 @@ public final class ColorModelBuilder {
      *                    The function may return {@code null} for 
unrecognized categories.
      * @param  inherited  the colors to use as fallback for unrecognized 
categories, or {@code null}.
      *                    Should be non-null only for styling an exiting image 
before visualization.
+     * @param  compact    Whether to rescale the range of sample values to the 
{@link #TYPE_COMPACT} range.
      */
-    public ColorModelBuilder(final Function<Category,Color[]> colors, final 
ColorModel inherited) {
+    public ColorModelBuilder(final Function<Category,Color[]> colors, final 
ColorModel inherited, final boolean compact) {
         this.colors = (colors != null) ? colors : GRAYSCALE;
         inheritedColors = inherited;
+        this.compact = compact;
     }
 
     /**
@@ -220,6 +238,9 @@ public final class ColorModelBuilder {
 
     /**
      * Returns {@code true} if the given range is already the [0 … 255] range.
+     *
+     * @see #TYPE_COMPACT
+     * @see #compact()
      */
     private static boolean isAlreadyScaled(final NumberRange<?> range) {
         return range.getMinDouble(true) == 0 && range.getMaxDouble(true) == 
MAX_VALUE;
@@ -310,7 +331,7 @@ public final class ColorModelBuilder {
             final ColorSpace cs = source.getColorSpace();
             if (cs instanceof ScaledColorSpace) {
                 final ScaledColorSpace scs = (ScaledColorSpace) cs;
-                initialize(scs.offset, scs.maximum);
+                initialize(scs.offset, scs.maximum, source.getTransferType());
                 return true;
             }
             /*
@@ -343,29 +364,60 @@ public final class ColorModelBuilder {
      */
 
     /**
-     * Applies colors on the given range of values. The 0 index will be 
reserved for NaN value,
+     * Applies colors on the given range of values.
+     * In compact mode, the 0 index will be reserved for NaN value
      * and indices in the [1 … 255] will be mapped to the given range.
      *
      * <p>This method is typically used as a last resort fallback when all 
other {@code initialize(…)}
      * methods failed or cannot be applied. This method assumes that no {@link 
Category} information
      * is available.</p>
      *
-     * @param  minimum  minimum value, inclusive.
-     * @param  maximum  maximum value, inclusive.
+     * @param  minimum   minimum value, inclusive.
+     * @param  maximum   maximum value, inclusive.
+     * @param  dataType  type of sample values.
      * @throws IllegalStateException if a sample dimension is already defined 
on this colorizer.
      */
-    public void initialize(final double minimum, final double maximum) {
+    public void initialize(final double minimum, final double maximum, final 
int dataType) {
         checkInitializationStatus(false);
         ArgumentChecks.ensureFinite("minimum", minimum);
         ArgumentChecks.ensureFinite("maximum", maximum);
+        if (ImageUtilities.isIntegerType(dataType)) {
+            defaultRange = NumberRange.create(Math.round(minimum), true, 
Math.round(maximum), true);
+        } else {
+            defaultRange = NumberRange.create(minimum, true, maximum, true);
+        }
+        applyDefaultRange();
+    }
+
+    /**
+     * Applies colors on the given range of values.
+     * This method does the same work than {@link #initialize(double, double, 
int)},
+     * but is preferred to the latter when the sample values are known to be 
integer values.
+     *
+     * @param  minimum  minimum value, inclusive.
+     * @param  maximum  maximum value, inclusive.
+     * @throws IllegalStateException if a sample dimension is already defined 
on this colorizer.
+     */
+    public void initialize(final long minimum, final long maximum) {
+        checkInitializationStatus(false);
         defaultRange = NumberRange.create(minimum, true, maximum, true);
-        target = new SampleDimension.Builder()
-                .setBackground(TRANSPARENT, 0)
-                .addQuantitative(COLOR_INDEX, NumberRange.create(1, true, 
MAX_VALUE, true), defaultRange)
-                .setName(VISUAL).build();
+        applyDefaultRange();
+    }
+
+    /**
+     * Initializes this builder with the {@link #defaultRange} value.
+     */
+    private void applyDefaultRange() {
+        final var builder = new SampleDimension.Builder().setName(VISUAL);
+        if (compact) {
+            var samples = NumberRange.create(1, true, MAX_VALUE, true);
+            builder.setBackground(TRANSPARENT, 0).addQuantitative(COLOR_INDEX, 
samples, defaultRange);
+        } else {
+            builder.addQuantitative(COLOR_INDEX, defaultRange, identity(), 
null);
+        }
+        target = builder.build();
         /*
          * We created a synthetic `SampleDimension` with the specified range 
of values.
-         * Recompute `source` and `entries` fields for consistency with the 
new ranges.
          * The `source` is recreated as a matter of principle, but will not be 
used by
          * `compact()` because `target` will take precedence.
          */
@@ -603,6 +655,13 @@ reuse:  if (source != null) {
      * This method builds up the color model from each set of colors 
associated to ranges in the given array.
      * Returned instances of {@link ColorModel} are shared among all callers 
in the running virtual machine.
      *
+     * <h4>Compact mode</h4>
+     * If the {@code compact} argument given to the constructor was {@code 
true},
+     * then the color model has colors interpolated in the [0 … 255] range of 
values.
+     * Conversions from range specified at construction time to the [0 … 255] 
range is
+     * given by {@link #getSampleToIndexValues()}. Images using this color 
model shall
+     * use a {@link DataBuffer} of type {@link #TYPE_COMPACT}.
+     *
      * @param  dataType     the color model type. One of {@link 
DataBuffer#TYPE_BYTE}, {@link DataBuffer#TYPE_USHORT},
      *                      {@link DataBuffer#TYPE_SHORT}, {@link 
DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT}
      *                      or {@link DataBuffer#TYPE_DOUBLE}.
@@ -612,31 +671,17 @@ reuse:  if (source != null) {
      * @param  visibleBand  the band to be made visible (usually 0). All other 
bands, if any, will be ignored.
      * @return a color model suitable for {@link java.awt.image.RenderedImage} 
objects with values in the given ranges.
      */
-    public ColorModel createColorModel(final int dataType, final int numBands, 
final int visibleBand) {
+    public ColorModel createColorModel(int dataType, final int numBands, final 
int visibleBand) {
         checkInitializationStatus(true);
+        if (compact) {
+            compact();
+            dataType = TYPE_COMPACT;
+        }
         ArgumentChecks.ensureStrictlyPositive("numBands", numBands);
         ArgumentChecks.ensureBetween("visibleBand", 0, numBands - 1, 
visibleBand);
         return ColorModelFactory.createPiecewise(dataType, numBands, 
visibleBand, entries);
     }
 
-    /**
-     * Returns a color model with colors interpolated in the [0 … 255] range 
of values.
-     * Conversions from range specified at construction time to the [0 … 255] 
range is
-     * given by {@link #getSampleToIndexValues()}. Images using this color 
model shall
-     * use a {@link DataBuffer} of type {@link #TYPE_COMPACT}.
-     *
-     * @param  numBands     the number of bands for the color model (usually 
1). The returned color model will render only
-     *                      the {@code visibleBand} and ignore the others, but 
the existence of all {@code numBands} will
-     *                      be at least tolerated. Supplemental bands, even 
invisible, are useful for processing.
-     * @param  visibleBand  the band to be made visible (usually 0). All other 
bands, if any, will be ignored.
-     * @return a color model suitable for {@link java.awt.image.RenderedImage} 
objects with values in the given ranges.
-     */
-    public ColorModel compactColorModel(final int numBands, final int 
visibleBand) {
-        checkInitializationStatus(true);
-        compact();
-        return createColorModel(TYPE_COMPACT, numBands, visibleBand);
-    }
-
     /**
      * Returns the conversion from sample values in the source image to sample 
values in the recolored image.
      *
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilderTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilderTest.java
index 012e1dcae6..78c59df847 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilderTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/ColorModelBuilderTest.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.coverage.j2d;
 
 import java.util.List;
-import java.util.Collection;
 import java.util.AbstractMap.SimpleEntry;
 import java.awt.Color;
 import java.awt.image.DataBuffer;
@@ -43,7 +42,7 @@ import static org.junit.Assert.*;
  */
 public final class ColorModelBuilderTest extends TestCase {
     /**
-     * Tests the creation of an index color model using {@link 
ColorModelBuilder#ColorModelBuilder(Collection)}.
+     * Tests the creation of an index color model using explicit range of 
sample values.
      *
      * @throws TransformException if a sample value cannot be converted.
      */
@@ -85,8 +84,7 @@ public final class ColorModelBuilderTest extends TestCase {
     }
 
     /**
-     * Tests the creation of an index color model using {@link 
ColorModelBuilder#ColorModelBuilder(Function)}
-     * and an initialization with a {@link SampleDimension}.
+     * Tests the creation of an index color model using sample dimensions.
      *
      * @throws TransformException if a sample value cannot be converted.
      */
@@ -99,9 +97,9 @@ public final class ColorModelBuilderTest extends TestCase {
                 .addQualitative ("Error", MathFunctions.toNanFloat(3))
                 .setName("Temperature").build();
 
-        final ColorModelBuilder colorizer = new 
ColorModelBuilder(ColorModelBuilder.GRAYSCALE, null);
+        final ColorModelBuilder colorizer = new 
ColorModelBuilder(ColorModelBuilder.GRAYSCALE, null, true);
         assertTrue("initialize", colorizer.initialize(null, sd));
-        final IndexColorModel cm = (IndexColorModel) 
colorizer.compactColorModel(1, 0);     // Must be first.
+        final var cm = (IndexColorModel) 
colorizer.createColorModel(ColorModelBuilder.TYPE_COMPACT, 1, 0);      // Must 
be first.
         /*
          * Test conversion of a few sample values to packed values.
          */


Reply via email to