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. */