You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/04/04 10:55:49 UTC
[sis] branch geoapi-4.0 updated: Bug fix: `ColorModelBuilder` sometime created a "compact" color model when it was not desired.
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 <ma...@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.
*/