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 4e347cda9d Tune the `Colorizer` contract for saying that a null `Color[]` array means to use default colors, which are not necessarily transparent. If the range has more than one value, that default is now grayscale. 4e347cda9d is described below commit 4e347cda9d295a552e2acf08d91ae4c2786b25cd Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri Mar 31 20:09:34 2023 +0200 Tune the `Colorizer` contract for saying that a null `Color[]` array means to use default colors, which are not necessarily transparent. If the range has more than one value, that default is now grayscale. JavaFX application: Add an "Original colors" item in the table of category colors. It uses the above-cited new meaning for null `Color[]` array. --- .../apache/sis/gui/coverage/CoverageCanvas.java | 7 -- .../apache/sis/gui/coverage/CoverageControls.java | 3 +- .../apache/sis/gui/coverage/CoverageStyling.java | 116 +++++++++------------ .../apache/sis/internal/gui/control/ColorCell.java | 31 ++++-- .../internal/gui/control/ColorColumnHandler.java | 31 +----- .../apache/sis/internal/gui/control/ColorRamp.java | 51 +++++++-- .../sis/internal/gui/control/ValueColorMapper.java | 12 --- .../sis/internal/gui/control/package-info.java | 2 +- .../sis/gui/coverage/CoverageStylingApp.java | 5 +- .../apache/sis/coverage/grid/ImageRenderer.java | 2 +- .../java/org/apache/sis/image/BandSelectImage.java | 2 +- .../main/java/org/apache/sis/image/Colorizer.java | 51 +++++++-- .../java/org/apache/sis/image/ImageProcessor.java | 7 +- .../java/org/apache/sis/image/Visualization.java | 13 ++- .../internal/coverage/j2d/ColorModelBuilder.java | 80 ++++++++++---- .../internal/coverage/j2d/ColorModelFactory.java | 15 +-- .../sis/internal/coverage/j2d/ColorsForRange.java | 45 +++++--- .../org/apache/sis/util/resources/Vocabulary.java | 5 + .../sis/util/resources/Vocabulary.properties | 1 + .../sis/util/resources/Vocabulary_fr.properties | 1 + .../org/apache/sis/internal/netcdf/Raster.java | 11 +- .../apache/sis/internal/netcdf/RasterResource.java | 4 +- 22 files changed, 287 insertions(+), 208 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java index 07ecc32463..735b28b475 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java @@ -411,13 +411,6 @@ public class CoverageCanvas extends MapCanvasAWT { interpolationProperty.set(interpolation); } - /** - * Returns the colors to use for given categories of sample values, or {@code null} is unspecified. - */ - final Function<Category, java.awt.Color[]> getCategoryColors() { - return data.processor.getCategoryColors(); - } - /** * Sets the colors to use for given categories in image. Invoking this method causes a repaint event, * so it should be invoked only if at least one color is known to have changed. diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java index b277c36422..48e6437384 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java @@ -49,7 +49,7 @@ import org.apache.sis.util.resources.Vocabulary; * The controls are updated when the coverage shown in {@link CoverageCanvas} is changed. * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 1.1 */ final class CoverageControls extends ViewAndControls { @@ -221,7 +221,6 @@ final class CoverageControls extends ViewAndControls { */ final void copyStyling(final CoverageControls c) { styling.copyStyling(c.styling); - view.setCategoryColors(c.view.getCategoryColors() == null ? null : styling); GUIUtilities.copySelection(c.stretching, stretching); GUIUtilities.copySelection(c.interpolation, interpolation); } diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java index 8f2a2d51a7..987f588f7a 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java @@ -17,10 +17,10 @@ package org.apache.sis.gui.coverage; import java.awt.Color; -import java.util.Arrays; import java.util.Map; import java.util.HashMap; import java.util.Locale; +import java.util.Objects; import java.util.function.Function; import javafx.geometry.Pos; import javafx.scene.control.TableCell; @@ -30,14 +30,13 @@ import javafx.scene.control.MenuItem; import javafx.collections.ObservableList; import javafx.beans.value.ObservableValue; import javafx.scene.control.ContextMenu; +import org.opengis.util.InternationalString; import org.apache.sis.coverage.Category; -import org.apache.sis.internal.coverage.j2d.ColorModelBuilder; import org.apache.sis.internal.gui.Resources; import org.apache.sis.internal.gui.ImmutableObjectProperty; import org.apache.sis.internal.gui.control.ColorRamp; import org.apache.sis.internal.gui.control.ColorColumnHandler; import org.apache.sis.util.resources.Vocabulary; -import org.opengis.util.InternationalString; /** @@ -47,7 +46,7 @@ import org.opengis.util.InternationalString; * that may change in any future version.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 1.1 */ final class CoverageStyling extends ColorColumnHandler<Category> implements Function<Category,Color[]> { @@ -56,12 +55,7 @@ final class CoverageStyling extends ColorColumnHandler<Category> implements Func * * @see #key(Category) */ - private final Map<String,int[]> customizedColors; - - /** - * The fallback to use if no color is defined in this {@code CoverageStyling} for a category. - */ - private final Function<Category,Color[]> fallback; + private final Map<String,ColorRamp> customizedColors; /** * The view to notify when a color changed, or {@code null} if none. @@ -74,22 +68,17 @@ final class CoverageStyling extends ColorColumnHandler<Category> implements Func CoverageStyling(final CoverageCanvas canvas) { customizedColors = new HashMap<>(); this.canvas = canvas; - if (canvas != null) { - final Function<Category, Color[]> c = canvas.getCategoryColors(); - if (c != null) { - fallback = c; - return; - } - } - fallback = ColorModelBuilder.GRAYSCALE; } /** - * Copy styling information from the given source. + * Copies styling information from the given source. * This is used when the user clicks on "New window" button. */ final void copyStyling(final CoverageStyling source) { customizedColors.putAll(source.customizedColors); + if (canvas != null) { + canvas.setCategoryColors(customizedColors.isEmpty() ? null : this); + } } /** @@ -111,79 +100,72 @@ final class CoverageStyling extends ColorColumnHandler<Category> implements Func * Returns the key to use in {@link #customizedColors} for the given category. */ private static String key(final Category category) { - return category.getName().toString(Locale.ENGLISH); + return category.getName().toString(Locale.ROOT); } /** - * Associates colors to the given category. + * Returns the colors to apply for the given category as an observable value. * - * @param colors the new color for the given category, or {@code null} for resetting default value. - */ - final void setARGB(final Category category, final int[] colors) { - final String key = key(category); - final int[] old; - if (colors != null && colors.length != 0) { - old = customizedColors.put(key, colors); - } else { - old = customizedColors.remove(key); - } - if (canvas != null && !Arrays.equals(colors, old)) { - canvas.setCategoryColors(this); // Causes a repaint event. - } - } - - /** - * Returns the colors to apply for the given category, or {@code null} for transparent. - * Does the same work as {@link #apply(Category)}, but returns colors as an array of ARGB codes. - * Contrarily to {@link #apply(Category)}, this method may return references to internal arrays; - * <strong>do not modify.</strong> + * @param row the row item for which to get color to show in color cell. Never {@code null}. + * @return the color(s) to use for the given row, or {@code null} if none (transparent). */ @Override - protected int[] getARGB(final Category category) { - int[] ARGB = customizedColors.get(key(category)); - if (ARGB == null) { - final Color[] colors = fallback.apply(category); - if (colors != null) { - ARGB = new int[colors.length]; - for (int i=0; i<colors.length; i++) { - ARGB[i] = colors[i].getRGB(); - } + protected ObservableValue<ColorRamp> getObservableValue(final Category category) { + ColorRamp ramp = customizedColors.get(key(category)); + if (ramp == null) { + if (!category.isQuantitative()) { + return null; } + ramp = ColorRamp.DEFAULT; } - return ARGB; + return new ImmutableObjectProperty<>(ramp); } /** - * Returns the colors to apply for the given category, or {@code null} for transparent. - * This method returns copies of internal arrays; changes to the returned array do not - * affect this {@code CoverageStyling} (assuming {@link #fallback} also does copies). + * Returns the colors to apply for the given category, or {@code null} for default. + * This method returns copies of internal arrays; changes to the returned array do + * not affect this {@code CoverageStyling}. * * @param category the category for which to get the colors. - * @return colors to apply for the given category, or {@code null}. + * @return colors to apply for the given category, or {@code null} if the category is unrecognized. */ @Override public Color[] apply(final Category category) { - final int[] ARGB = customizedColors.get(key(category)); - if (ARGB != null) { - final Color[] colors = new Color[ARGB.length]; - for (int i=0; i<colors.length; i++) { - colors[i] = new Color(ARGB[i], true); + final ColorRamp ramp = customizedColors.get(key(category)); + if (ramp != null) { + final int[] ARGB = ramp.colors; + if (ARGB != null) { + final Color[] colors = new Color[ARGB.length]; + for (int i=0; i<colors.length; i++) { + colors[i] = new Color(ARGB[i], true); + } + return colors; } - return colors; } - return fallback.apply(category); + return null; } /** - * Invoked when users confirmed that (s)he wants to use the selected colors. + * Associates colors to the given category. + * This is invoked when users confirmed that (s)he wants to use the selected colors. * * @param category the category for which to assign new color(s). * @param colors the new color for the given category, or {@code null} for resetting default value. * @return the type of color (solid or gradient) shown for the given value. */ @Override - protected ColorRamp.Type applyColors(final Category category, ColorRamp colors) { - setARGB(category, (colors != null) ? colors.colors : null); + protected ColorRamp.Type applyColors(final Category category, final ColorRamp colors) { + final String key = key(category); + final ColorRamp old; + if (colors != null) { + old = customizedColors.put(key, colors); + } else { + old = customizedColors.remove(key); + } + if (canvas != null && !Objects.equals(colors, old)) { + canvas.setCategoryColors(customizedColors.isEmpty() ? null : this); + // Above method call causes a repaint event even if value is the same. + } return category.isQuantitative() ? ColorRamp.Type.GRADIENT : ColorRamp.Type.SOLID; } @@ -195,7 +177,7 @@ final class CoverageStyling extends ColorColumnHandler<Category> implements Func * (this argument would be removed if this method was public API). */ final TableView<Category> createCategoryTable(final Resources resources, final Vocabulary vocabulary) { - final TableColumn<Category,String> name = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Name)); + final var name = new TableColumn<Category,String>(vocabulary.getString(Vocabulary.Keys.Name)); name.setCellValueFactory(CoverageStyling::getCategoryName); name.setCellFactory(CoverageStyling::createNameCell); name.setEditable(false); @@ -224,7 +206,7 @@ final class CoverageStyling extends ColorColumnHandler<Category> implements Func */ private static TableCell<Category,String> createNameCell(final TableColumn<Category,String> column) { @SuppressWarnings("unchecked") - final TableCell<Category,String> cell = (TableCell<Category,String>) TableColumn.DEFAULT_CELL_FACTORY.call(column); + final var cell = (TableCell<Category,String>) TableColumn.DEFAULT_CELL_FACTORY.call(column); cell.setAlignment(Pos.CENTER_LEFT); return cell; } diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorCell.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorCell.java index ccb8283410..87e3df3fe1 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorCell.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorCell.java @@ -34,7 +34,6 @@ import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.Rectangle; -import org.apache.sis.internal.gui.GUIUtilities; /** @@ -48,7 +47,7 @@ import org.apache.sis.internal.gui.GUIUtilities; * {@link EventHandler} is for reacting to user color selection using the control shown.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * * @param <S> the type of row data as declared in the {@code TableView} generic type. * @@ -73,8 +72,8 @@ final class ColorCell<S> extends TableCell<S,ColorRamp> implements EventHandler< /** * The type of color ramp as determined by {@link ColorColumnHandler#applyColors(Object, ColorRamp)}. - * This is updated by {@link #updateItem(ColorRamp, boolean)} when the value changes and stored for - * keeping that value stable (this class does not support mutable colors type). + * This is updated by {@link #updateItem(ColorRamp, boolean)} when the value changes, and is stored + * for keeping that value stable (this class does not support mutable colors type). * May be {@code null} if there are no values in the row of this cell. */ private ColorRamp.Type type; @@ -102,7 +101,6 @@ final class ColorCell<S> extends TableCell<S,ColorRamp> implements EventHandler< */ ColorCell(final ColorColumnHandler<S> handler) { this.handler = handler; - setContentDisplay(ContentDisplay.GRAPHIC_ONLY); setOnMouseClicked(ColorCell::mouseClicked); } @@ -168,7 +166,7 @@ final class ColorCell<S> extends TableCell<S,ColorRamp> implements EventHandler< colorRampChooser.setEditable(false); colorRampChooser.setMaxWidth(Double.MAX_VALUE); colorRampChooser.setCellFactory((column) -> new RampChoice()); - colorRampChooser.getItems().setAll(ColorRamp.GRAYSCALE, ColorRamp.BELL); + colorRampChooser.getItems().setAll(ColorRamp.DEFAULTS); updateColorRampChooser(getItem()); } control = colorRampChooser; @@ -184,6 +182,7 @@ final class ColorCell<S> extends TableCell<S,ColorRamp> implements EventHandler< control.setOnAction(this); } } + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); setGraphic(control); return control; } @@ -195,16 +194,22 @@ final class ColorCell<S> extends TableCell<S,ColorRamp> implements EventHandler< private final class RampChoice extends ListCell<ColorRamp> { /** Creates a new combo box choice. */ RampChoice() { - setContentDisplay(ContentDisplay.GRAPHIC_ONLY); setMaxWidth(Double.POSITIVE_INFINITY); } /** Sets the colors to show in the combo box item. */ @Override protected void updateItem(final ColorRamp colors, final boolean empty) { super.updateItem(colors, empty); - if (colors == null) { + if (empty || colors == null) { + setText(null); + setGraphic(null); + } else if (colors.isTransparent()) { + setContentDisplay(ContentDisplay.TEXT_ONLY); + setText(colors.toString()); setGraphic(null); } else { + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + setText(null); Rectangle r = (Rectangle) getGraphic(); if (r == null) { r = createRectangle(-40); @@ -328,21 +333,27 @@ final class ColorCell<S> extends TableCell<S,ColorRamp> implements EventHandler< private void setColorItem(final ColorRamp colors) { assert controlNotFocused(); Rectangle view = null; + String label = null; if (colors != null) { final Paint paint = colors.paint(); if (paint != null) { + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); if (colorView == null) { colorView = createRectangle(WIDTH_ADJUST); } view = colorView; view.setFill(paint); + } else { + setContentDisplay(ContentDisplay.TEXT_ONLY); + label = colors.toString(); } } setGraphic(view); + setText(label); } /** - * Removes the control in the cell and paint the color in a rectangle instead. + * Removes the control in the cell and paints the color in a rectangle instead. * This method does nothing if the control is already hidden. * * <p>This method sets the focus to the table before to remove the combo box. @@ -444,7 +455,7 @@ final class ColorCell<S> extends TableCell<S,ColorRamp> implements EventHandler< final Object value = ((ComboBoxBase<?>) event.getSource()).getValue(); final ColorRamp colors; if (value instanceof Color) { - colors = new ColorRamp(GUIUtilities.toARGB((Color) value)); + colors = new ColorRamp((Color) value); } else { // A ClassCastException here would be a bug in ColorCell editors management. colors = (ColorRamp) value; diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java index c1f58cbd5c..27a25dc25a 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java @@ -23,7 +23,6 @@ import javafx.scene.control.TableView; import javafx.scene.control.TableColumn; import javafx.scene.control.TablePosition; import javafx.beans.value.ObservableValue; -import org.apache.sis.internal.gui.ImmutableObjectProperty; /** @@ -36,7 +35,7 @@ import org.apache.sis.internal.gui.ImmutableObjectProperty; * that may change in any future version.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * * @param <S> the type of row data as declared in the {@link TableView} generic type. * @@ -62,39 +61,19 @@ public abstract class ColorColumnHandler<S> implements Callback<TableColumn.Cell protected abstract ColorRamp.Type applyColors(S row, ColorRamp colors); /** - * Gets the ARGB codes of colors to show in the cell for the given row data. - * This method is sufficient when the color(s) can be changed only by calls to - * {@link #applyColors(S, ColorRamp)}. If the color(s) may change externally, - * then {@link #getObservableValue(S)} should be overridden too. - * - * @param row the row item for which to get ARGB codes to show in color cell. - * @return the colors as ARGB codes, or {@code null} if none (transparent). - */ - protected abstract int[] getARGB(S row); - - /** - * Returns the color associated to given row as an observable value. The default implementation creates - * an unmodifiable value derived from {@link #getARGB(S)}. It is okay if the color(s) cannot be changed - * in other way than by calls to {@link #applyColors(Object, ColorRamp)}. If this assumption does not hold, - * then subclasses should override this method and return the observable which is mutated when the value change. + * Returns the color associated to given row as an observable value. * * @param row the row item for which to get color to show in color cell. Never {@code null}. - * @return the color(s) to use for the given row, or {@code null} if none (transparent). + * @return the color(s) to use for the given row, or {@code null} for default. */ - protected ObservableValue<ColorRamp> getObservableValue(S row) { - final int[] ARGB = getARGB(row); - if (ARGB != null) { - return new ImmutableObjectProperty<>(new ColorRamp(ARGB)); - } - return null; - } + protected abstract ObservableValue<ColorRamp> getObservableValue(S row); /** * Invoked by {@link TableColumn} for computing the value of a {@link ColorCell}. * This method is public as an implementation side-effect; do not rely on that. * * @param cell the row value together with references to column and table where the show the color cell. - * @return the color cell value, or {@code null} if none (transparent). + * @return the color cell value, or {@code null} for default (original color, grayscale or transparent). */ @Override public final ObservableValue<ColorRamp> call(final TableColumn.CellDataFeatures<S,ColorRamp> cell) { diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java index d212cc1055..d4572999e7 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java @@ -17,6 +17,7 @@ package org.apache.sis.internal.gui.control; import java.util.Arrays; +import java.util.Objects; import javafx.scene.control.ColorPicker; import javafx.scene.control.ComboBox; import javafx.scene.paint.Color; @@ -33,9 +34,10 @@ import org.apache.sis.util.resources.Vocabulary; * A single color or a gradient of colors shown as a rectangle in a {@link ColorCell}. * Can also produce a string representation to be shown in a list. * Instances should be considered immutable. + * The same instance may be shared by many cells. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * * @see ColorCell#getItem() * @@ -59,14 +61,25 @@ public final class ColorRamp { } /** - * Default color ramp. + * Original colors of the rendered image. */ - static final ColorRamp GRAYSCALE = new ColorRamp(0xFF000000, 0xFFFFFFFF); + public static final ColorRamp DEFAULT = new ColorRamp(Vocabulary.format(Vocabulary.Keys.OriginalColors)); /** - * Blue – Cyan – White – Yellow – Red. + * Grayscale color ramp. */ - static final ColorRamp BELL = new ColorRamp(0xFF0000FF, 0xFF00FFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFF0000); + private static final ColorRamp GRAYSCALE = new ColorRamp(0xFF000000, 0xFFFFFFFF); + + /** + * Default colors to put in a {@link ColorCell} combox box. + * This array shall not be modified. + */ + static final ColorRamp[] DEFAULTS = { + DEFAULT, + GRAYSCALE, + new ColorRamp(0xFF0000FF, 0xFF00FFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFF0000), // Blue – Cyan – White – Yellow – Red. + new ColorRamp(0xFF0000FF, 0xFFFF00FF, 0xFFFF0000) // Blue – Magenta – Red. + }; /** * ARGB codes of this single color or color ramp. @@ -75,6 +88,8 @@ public final class ColorRamp { * <p><strong>This array should be read-only.</strong> We make it public because this class is internal. * If this {@code ColorRamp} class moves to public API, then we would need to replace this public access * by an accessor doing a copy.</p> + * + * @see #isTransparent() */ public final int[] colors; @@ -112,11 +127,21 @@ public final class ColorRamp { /** * Creates a new item for the given colors. + * + * @param colors ARGB codes of this single color or color ramp. */ - ColorRamp(final int... colors) { + public ColorRamp(final int... colors) { this.colors = colors; } + /** + * Creates a new item for the given text. + */ + private ColorRamp(final String text) { + colors = null; + name = text; + } + /** * Returns {@code true} if this ramp has no color with a non-zero transparency. * If this method returns {@code false}, then {@link #colors} is guaranteed non-empty. @@ -158,7 +183,7 @@ public final class ColorRamp { * @return color or gradient paint for table cell, or {@code null} if none. */ final Paint paint() { - if (paint == null) { + if (paint == null && colors != null) { switch (colors.length) { case 0: break; case 1: { @@ -223,11 +248,17 @@ public final class ColorRamp { * Returns whether the given object is equal to this {@code ColorRamp}. * This is used for locating this {@code ColorRamp} in a {@link ComboBox}. * - * @param other the object to compare with {@code this} for equality. + * @param obj the object to compare with {@code this} for equality. * @return whether the given object is equal to this color ramp. */ @Override - public boolean equals(final Object other) { - return (other instanceof ColorRamp) && Arrays.equals(colors, ((ColorRamp) other).colors); + public boolean equals(final Object obj) { + if (obj instanceof ColorRamp) { + final ColorRamp other = (ColorRamp) obj; + if (Arrays.equals(colors, other.colors)) { + return (colors != null) || Objects.equals(name, other.name); + } + } + return false; } } diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ValueColorMapper.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ValueColorMapper.java index 39fd1e53a4..c7f2a0c888 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ValueColorMapper.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ValueColorMapper.java @@ -230,18 +230,6 @@ public final class ValueColorMapper extends TabularWidget { ColumnHandler() { } - /** - * Returns the colors to apply for the given step, or {@code null} for transparent. - * This method is defined for safety but should not be invoked; use {@link #getObservableValue(S)} instead. - * - * @param level the value for which to get the color to show in color cell. - */ - @Override - protected int[] getARGB(final Step level) { - final ColorRamp r = level.color.get(); - return (r != null) ? r.colors : null; - } - /** * Returns the color associated to given row as an observable value. * diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/package-info.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/package-info.java index c14d01de17..f932e882d2 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/package-info.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/package-info.java @@ -24,7 +24,7 @@ * may change in incompatible ways in any future version without notice. * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 1.1 */ package org.apache.sis.internal.gui.control; diff --git a/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageStylingApp.java b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageStylingApp.java index 7c66339826..05ace09b00 100644 --- a/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageStylingApp.java +++ b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageStylingApp.java @@ -27,6 +27,7 @@ import org.apache.sis.coverage.Category; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.internal.gui.Resources; +import org.apache.sis.internal.gui.control.ColorRamp; import org.apache.sis.measure.Units; @@ -34,7 +35,7 @@ import org.apache.sis.measure.Units; * Shows category table built by {@link CoverageStyling} with arbitrary data. * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.4 * @since 1.1 */ public final class CoverageStylingApp extends Application { @@ -77,7 +78,7 @@ public final class CoverageStylingApp extends Application { .build(); final CoverageStyling styling = new CoverageStyling(null); - styling.setARGB(band.getCategories().get(1), new int[] {0xFF607080}); + styling.applyColors(band.getCategories().get(1), new ColorRamp(0xFF607080)); final TableView<Category> table = styling.createCategoryTable( Resources.forLocale(null), Vocabulary.getResources((Locale) null)); table.getItems().setAll(band.getCategories()); 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 b9005bd109..31545550ea 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 @@ -667,7 +667,7 @@ public class ImageRenderer { * transparent for qualitative categories (typically "no data" values). * * <h4>Example</h4> - * the following code specifies a color palette from blue to red with white in the middle. + * The following code specifies a color palette from blue to red with white in the middle. * This is useful for data with a clear 0 (white) in the middle of the range, * with a minimal value equals to the negative of the maximal value. * 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 43ade8f563..9f12e4c2df 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 @@ -87,7 +87,7 @@ final class BandSelectImage extends SourceAlignedImage { * @param bands the bands to select. Should be a clone of user-specified argument * for protection against user changes in the given array. */ - static RenderedImage create(final RenderedImage source, final int[] bands) { + static RenderedImage create(final RenderedImage source, final int... bands) { final int numBands = ImageUtilities.getNumBands(source); if (bands.length == numBands && ArraysExt.isRange(0, bands)) { return source; 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 d21f3d2895..8888913868 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 @@ -181,9 +181,27 @@ public interface Colorizer extends Function<Colorizer.Target, Optional<ColorMode /** * Creates a colorizer which will interpolate colors in multiple ranges of values. - * When the image data type is 8 or 16 bits integer, this colorizer creates {@link IndexColorModel} instances. + * The range of pixel values are specified by {@link NumberRange} elements, + * and the colors to interpolate in each range are specified by {@code Color[]} arrays. + * Empty arrays (i.e. no color) are interpreted as an explicit request for full transparency. + * + * <p>When the image data type is 8 or 16 bits integer, + * this colorizer creates {@link IndexColorModel} instances. * For other kinds of data type such as floating points, - * this colorizer creates a non-standard (and potentially slow) color model. + * this colorizer creates a non-standard (and potentially slow) color model.</p> + * + * <h4>Default colors</h4> + * The {@code colors} map shall not be null or empty but may contain {@code null} values. + * Those null values are translated to default sets of colors in an implementation dependent way. + * In current implementation, the defaults are: + * + * <ul> + * <li>If the range minimum and maximum values are not equal, default to grayscale colors.</li> + * <li>Otherwise default to a fully transparent color.</li> + * </ul> + * + * Those defaults may change in any future Apache SIS version. + * For example a future version may first tries to preserve the existing colors of an image. * * <h4>Limitations</h4> * In current implementation, the non-standard color model ignores the specified colors. @@ -195,7 +213,7 @@ public interface Colorizer extends Function<Colorizer.Target, Optional<ColorMode * @see ImageProcessor#visualize(RenderedImage) */ public static Colorizer forRanges(final Map<NumberRange<?>,Color[]> colors) { - ArgumentChecks.ensureNonEmpty("colors", colors.entrySet()); + // Can not use `Map.copyOf(colors)` because it may contain null values. final var factory = ColorModelFactory.piecewise(colors); return (target) -> { if (target instanceof Visualization.Target) { @@ -220,14 +238,31 @@ public interface Colorizer extends Function<Colorizer.Target, Optional<ColorMode * The given function provides a way to colorize images without knowing in advance the numerical values of pixels. * For example, instead of specifying <cite>"pixel value 0 is blue, 1 is green, 2 is yellow"</cite>, * the given function allows to specify <cite>"Lakes are blue, Forests are green, Sand is yellow"</cite>. - * The function can return {@code null} or empty color arrays for some categories, - * which are interpreted as fully transparent pixels. * - * <p>This colorizer is used when {@link Target#getRanges()} provides a non-empty value. + * <h4>Default colors</h4> + * The given function can return {@code null} or empty color arrays for some categories. + * An empty array (i.e. no color) is interpreted as an explicit request for transparency. + * But null arrays are interpreted as unrecognized category, + * in which case the defaults are implementation dependent. + * In current implementation, the defaults are: + * + * <ul> + * <li>If all categories are unrecognized, then the colorizer returns an empty value.</li> + * <li>Otherwise, {@linkplain Category#isQuantitative() quantitative} categories default to grayscale colors.</li> + * <li>Otherwise qualitative categories default to a fully transparent color.</li> + * </ul> + * + * Those defaults may change in any future Apache SIS version. + * For example a future version may first tries to preserve the existing colors of an image. + * + * <h4>Conditions</h4> + * This colorizer is used when {@link Target#getRanges()} provides a non-empty value. * That value is typically fetched from the {@value PlanarImage#SAMPLE_DIMENSIONS_KEY} image property, * which is itself typically fetched from {@link org.apache.sis.coverage.grid.GridCoverage#getSampleDimensions()}. - * If no sample dimension information is available, then this colorizer do not build a color model. - * A fallback can be specified with {@link #orElse(Colorizer)}.</p> + * If no sample dimension information is available, + * or if the specified function did not returned at non-null value for at least one category, + * then this colorizer does not build a color model. + * A fallback can be specified with {@link #orElse(Colorizer)}. * * @param colors colors to use for arbitrary categories of sample values. * @return a colorizer which will apply colors determined by the {@link Category} of sample values. diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java index dea9f52adf..1e71d8569d 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java +++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java @@ -405,6 +405,7 @@ public class ImageProcessor implements Cloneable { */ public synchronized void setColorizer(final Colorizer colorizer) { this.colorizer = colorizer; + colors = null; } /** @@ -438,8 +439,10 @@ public class ImageProcessor implements Cloneable { */ @Deprecated(since="1.4", forRemoval=true) public synchronized void setCategoryColors(final Function<Category,Color[]> colors) { - setColorizer(colors != null ? Colorizer.forCategories(colors) : null); - this.colors = colors; + if (colors != this.colors) { + setColorizer(colors != null ? Colorizer.forCategories(colors) : null); + this.colors = colors; + } } /** 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 7237500a48..d83417ecba 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 @@ -264,7 +264,7 @@ final class Visualization extends ResampledImage { break; } } - source = BandSelectImage.create(source, new int[] {visibleBand}); + source = BandSelectImage.create(source, visibleBand); final SampleDimension visibleSD = (sampleDimensions != null && visibleBand < sampleDimensions.length) ? sampleDimensions[visibleBand] : null; /* @@ -298,8 +298,9 @@ final class Visualization extends ResampledImage { */ boolean initialized; final ColorModelBuilder builder; - if (target.rangeColors != null) { - builder = new ColorModelBuilder(target.rangeColors.entrySet()); + final var rangeColors = target.rangeColors; + if (rangeColors != null && !rangeColors.isEmpty()) { + builder = new ColorModelBuilder(rangeColors.entrySet()); initialized = true; } else { /* @@ -307,6 +308,7 @@ final class Visualization extends ResampledImage { * in various ways: sample dimensions, scaled color model, or image statistics in last resort. */ builder = new ColorModelBuilder(target.categoryColors); + final ColorModel colorModel = coloredSource.getColorModel(); initialized = builder.initialize(coloredSource.getSampleModel(), visibleSD); if (initialized) { /* @@ -315,14 +317,15 @@ final class Visualization extends ResampledImage { * determined by the SampleModel, then user enhanced contrast by a call to `stretchColorRamp(…)`. * We want to preserve that contrast enhancement. */ - builder.rescaleMainRange(coloredSource.getColorModel()); + builder.rescaleMainRange(colorModel); } else { /* + * At this point there is no more user-supplied colors (through `Colorizer`) that we can use. * If we have not been able to use the SampleDimension, try to use the ColorModel or SampleModel. * There is no call to `rescaleMainRange(…)` because the following code already uses the range * specified by the ColorModel, if available. */ - initialized = builder.initialize(coloredSource.getColorModel()); + initialized = builder.initialize(colorModel); if (!initialized) { if (coloredSource instanceof RecoloredImage) { final RecoloredImage colored = (RecoloredImage) coloredSource; 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 055c491d22..455899fa16 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 @@ -111,8 +111,16 @@ public final class ColorModelBuilder { (category) -> category.isQuantitative() ? new Color[] {Color.BLACK, Color.WHITE} : null; /** - * The colors to use for each category. Never {@code null}. - * The function may return {@code null}, which means transparent. + * The colors to use for each category. Never {@code null} (default value is grayscale). + * The function may return {@code null}, which means that the category is not recognized. + * If no category is recognized, no {@link ColorModel} will be built using that function. + * An empty array is interpreted as a color specified as transparent. + * + * <h4>Default value</h4> + * Default value is {@link #GRAYSCALE}. + * If the function returns {@code null} for an unrecognized category, + * the default colors for that category will be the same as {@link #GRAYSCALE}: + * grayscale for quantitative categories and transparent for qualitative categories. */ private final Function<Category,Color[]> colors; @@ -151,11 +159,15 @@ public final class ColorModelBuilder { * The {@code ColorModelBuilder} is considered initialized after this constructor; * callers shall <strong>not</strong> invoke an {@code initialize(…)} method. * + * <p>The {@code colors} map shall not be null or empty but may contain {@code null} values. + * Null values default to a fully transparent color when the range contains a single value, + * and to grayscale colors otherwise. + * Empty arrays of colors are interpreted as explicitly transparent.</p> + * * @param colors the colors to use for each range of values in source image. - * A {@code null} entry value means transparent. */ public ColorModelBuilder(final Collection<Map.Entry<NumberRange<?>,Color[]>> colors) { - ArgumentChecks.ensureNonNull("colors", colors); + ArgumentChecks.ensureNonEmpty("colors", colors); entries = ColorsForRange.list(colors); this.colors = GRAYSCALE; } @@ -165,7 +177,7 @@ public final class ColorModelBuilder { * Callers need to invoke an {@code initialize(…)} method after this constructor. * * @param colors the colors to use for each category, or {@code null} for default. - * The function may return {@code null}, which means transparent. + * The function may return {@code null} for unrecognized categories. */ public ColorModelBuilder(final Function<Category,Color[]> colors) { this.colors = (colors != null) ? colors : GRAYSCALE; @@ -206,26 +218,30 @@ public final class ColorModelBuilder { this.source = source; final List<Category> categories = source.getCategories(); if (!categories.isEmpty()) { + boolean isUndefined = true; boolean missingNodata = true; ColorsForRange[] entries = new ColorsForRange[categories.size()]; for (int i=0; i<entries.length; i++) { - final Category category = categories.get(i); - entries[i] = new ColorsForRange(category, colors); - missingNodata &= category.isQuantitative(); + final var range = new ColorsForRange(categories.get(i), colors); + isUndefined &= range.isUndefined(); + missingNodata &= range.isData; + entries[i] = range; } - /* - * If the model uses floating point values and there is no "no data" category, add one. - * We force a "no data" category because floating point values may be NaN. - */ - if (missingNodata && (model == null || !ImageUtilities.isIntegerType(model))) { - final int count = entries.length; - entries = Arrays.copyOf(entries, count + 1); - entries[count] = new ColorsForRange(TRANSPARENT, - NumberRange.create(Float.class, Float.NaN), null, false); + if (!isUndefined) { + /* + * If the model uses floating point values and there is no "no data" category, add one. + * We force a "no data" category because floating point values may be NaN. + */ + if (missingNodata && (model == null || !ImageUtilities.isIntegerType(model))) { + final int count = entries.length; + entries = Arrays.copyOf(entries, count + 1); + entries[count] = new ColorsForRange(TRANSPARENT, + NumberRange.create(Float.class, Float.NaN), null, false); + } + // Leave `target` to null. It will be computed by `compact()` if needed. + this.entries = entries; + return true; } - // Leave `target` to null. It will be computed by `compact()` if needed. - this.entries = entries; - return true; } } return false; @@ -274,6 +290,26 @@ public final class ColorModelBuilder { initialize(scs.offset, scs.maximum); return true; } + /* + * If the color model uses integer type, compute the maximal value based on the number of bits. + * The main use case is `IndexColorModel` with values on 16 bits but with a color ramp that does + * not exploit the full range allowed by 16 bits. + */ + if (ImageUtilities.isIntegerType(source.getTransferType())) { + long maximum = Numerics.bitmask(source.getPixelSize()) - 1; + long minimum = 0; + if (source instanceof IndexColorModel) { + final IndexColorModel indexed = (IndexColorModel) source; + int t = indexed.getMapSize(); + if (t <= maximum) maximum = t - 1L; // Inclusive. + t = indexed.getTransparentPixel(); + if (t == 0) minimum = 1; + } + if (minimum < maximum) { + initialize(minimum, maximum); + return true; + } + } } return false; } @@ -315,7 +351,7 @@ public final class ColorModelBuilder { final ColorsForRange[] entries = new ColorsForRange[categories.size()]; for (int i=0; i<entries.length; i++) { final Category category = categories.get(i); - entries[i] = new ColorsForRange(category, category.getName() == TRANSPARENT ? GRAYSCALE : colors); + entries[i] = new ColorsForRange(category, colors); } this.entries = entries; } @@ -494,7 +530,7 @@ reuse: if (source != null) { span += sourceRange.getSpan(); final ColorsForRange[] tmp = Arrays.copyOf(entries, ++count); System.arraycopy(entries, deferred, tmp, ++deferred, count - deferred); - tmp[deferred-1] = new ColorsForRange(null, sourceRange, new Color[] {Color.BLACK, Color.WHITE}, true); + tmp[deferred-1] = new ColorsForRange(null, sourceRange, null, true); entries = tmp; } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java index 1b1aad4e5b..510fa6cd61 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorModelFactory.java @@ -299,14 +299,17 @@ public final class ColorModelFactory { /** * Prepares a factory of color models interpolated for the ranges in the given map entries. * The {@link ColorModel} instances will be shared among all callers in the running virtual machine. + * The {@code colors} map shall not be null or empty but may contain {@code null} values. + * Null values default to fully transparent color when the range contains a single value, + * and to grayscale otherwise. Empty arrays of colors are interpreted as explicitly transparent. * * @param colors the colors to use for each range of sample values. - * The map may contain {@code null} values, which means transparent. * @return a factory of color model suitable for {@link RenderedImage} objects with values in the given ranges. */ public static ColorModelFactory piecewise(final Map<NumberRange<?>, Color[]> colors) { - return PIECEWISES.intern(new ColorModelFactory(DataBuffer.TYPE_BYTE, 0, DEFAULT_VISIBLE_BAND, - ColorsForRange.list(colors.entrySet()))); + final var entries = colors.entrySet(); + ArgumentChecks.ensureNonEmpty("colors", entries); + return PIECEWISES.intern(new ColorModelFactory(DataBuffer.TYPE_BYTE, 0, DEFAULT_VISIBLE_BAND, ColorsForRange.list(entries))); } /** @@ -474,6 +477,7 @@ public final class ColorModelFactory { public static ColorModel createColorScale(final int dataType, final int numBands, final int visibleBand, final double lower, final double upper, final Color... colors) { + ArgumentChecks.ensureNonEmpty("colors", colors); return createPiecewise(dataType, numBands, visibleBand, new ColorsForRange[] { new ColorsForRange(null, new NumberRange<>(Double.class, lower, true, upper, false), colors, true) }); @@ -849,10 +853,7 @@ public final class ColorModelFactory { buffer.append(System.lineSeparator()).append(CharSequences.spaces(9 - start.length())).append(start); if (i < ARGB.length) { buffer.append('…'); - final int[] colors = ARGB[i]; - if (colors != null) { - ColorsForRange.appendColorRange(buffer, colors.length, (j) -> colors[j]); - } + ColorsForRange.appendColorRange(buffer, ARGB[i]); } } return buffer.toString(); diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java index d8012bd6e8..31894df212 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ColorsForRange.java @@ -18,8 +18,8 @@ package org.apache.sis.internal.coverage.j2d; import java.util.Map; import java.util.Collection; +import java.util.Objects; import java.util.function.Function; -import java.util.function.IntUnaryOperator; import java.awt.Color; import java.awt.image.IndexColorModel; import org.apache.sis.coverage.Category; @@ -33,7 +33,7 @@ import org.apache.sis.util.ArraysExt; * used only the time needed for {@link ColorModelFactory#createPiecewise(int, int, int, ColorsForRange[])}. * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * * @see ColorModelFactory#createPiecewise(int, int, int, ColorsForRange[]) * @@ -55,7 +55,12 @@ final class ColorsForRange implements Comparable<ColorsForRange> { /** * The colors to apply on the range of sample values. - * A null or empty array means transparent. + * An empty array means that the category is explicitly specified as transparent. + * A null value means that the category is unrecognized, in which case the default + * is grayscale for quantitative category and transparent for qualitative category. + * + * @see #isUndefined() + * @see #toARGB() */ private final Color[] colors; @@ -84,7 +89,7 @@ final class ColorsForRange implements Comparable<ColorsForRange> { * * @param name a name identifying the range of values, or {@code null} for automatic. * @param sampleRange range of sample values on which the colors will be applied. - * @param colors colors to apply on the range of sample values, or {@code null} for transparent. + * @param colors colors to apply on the range of sample values, or {@code null} for default. * @param isData whether this entry should be taken as main data (not fill values). */ ColorsForRange(final CharSequence name, final NumberRange<?> sampleRange, final Color[] colors, final boolean isData) { @@ -95,6 +100,14 @@ final class ColorsForRange implements Comparable<ColorsForRange> { this.isData = isData; } + /** + * Returns {@code true} if no color has been specified for this range. + * Note that "undefined" is not the same as fully transparent color. + */ + final boolean isUndefined() { + return colors == null; + } + /** * Converts {@linkplain Map#entrySet() map entries} to an array of {@code ColorsForRange} entries. * The {@link #category} of each entry is left to null. @@ -108,7 +121,9 @@ final class ColorsForRange implements Comparable<ColorsForRange> { final ColorsForRange[] entries = new ColorsForRange[colors.size()]; int n = 0; for (final Map.Entry<NumberRange<?>,Color[]> entry : colors) { - entries[n++] = new ColorsForRange(null, entry.getKey(), entry.getValue(), true); + final NumberRange<?> range = entry.getKey(); + boolean singleton = Objects.equals(range.getMinValue(), range.getMaxValue()); + entries[n++] = new ColorsForRange(null, range, entry.getValue(), !singleton); } return ArraysExt.resize(entries, n); // `resize` should not be needed, but we are paranoiac. } @@ -119,9 +134,7 @@ final class ColorsForRange implements Comparable<ColorsForRange> { @Override public String toString() { final StringBuilder buffer = new StringBuilder(name).append(": ").append(sampleRange); - if (colors != null) { - appendColorRange(buffer, colors.length, (i) -> colors[i].getRGB()); - } + appendColorRange(buffer, toARGB()); return buffer.toString(); } @@ -135,14 +148,14 @@ final class ColorsForRange implements Comparable<ColorsForRange> { * @param count number of ARGB codes. * @param colors providers of ARGB codes for given indices. */ - static void appendColorRange(final StringBuilder buffer, final int count, final IntUnaryOperator colors) { - if (count != 0) { + static void appendColorRange(final StringBuilder buffer, final int[] colors) { + if (colors != null && colors.length != 0) { String s = " → ARGB["; int i = 0; do { - buffer.append(s).append(Integer.toHexString(colors.applyAsInt(i)).toUpperCase()); + buffer.append(s).append(Integer.toHexString(colors[i]).toUpperCase()); s = " … "; - } while (i < (i = count-1)); + } while (i < (i = colors.length - 1)); buffer.append(']'); } } @@ -165,8 +178,8 @@ final class ColorsForRange implements Comparable<ColorsForRange> { private int getAlpha() { int max = 0; if (colors != null) { - for (int i=0; i<colors.length; i++) { - final int alpha = colors[i].getAlpha(); + for (final Color color : colors) { + final int alpha = color.getAlpha(); if (alpha > max) { if (alpha >= 0xFF) { return 0xFF; @@ -174,6 +187,8 @@ final class ColorsForRange implements Comparable<ColorsForRange> { max = alpha; } } + } else if (isData) { + return 0xFF; } return max; } @@ -199,6 +214,8 @@ final class ColorsForRange implements Comparable<ColorsForRange> { if ((combined & 0xFF000000) != 0) { return ARGB; } + } else if (isData) { + return new int[] {0xFF000000, 0xFFFFFFFF}; } return ArraysExt.EMPTY_INT; } diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java index 24be11c4a3..25ef1259f5 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java +++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java @@ -952,6 +952,11 @@ public final class Vocabulary extends IndexedResourceBundle { */ public static final short OriginInCellCenter = 155; + /** + * Original colors + */ + public static final short OriginalColors = 272; + /** * Other surface */ diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties index 21efb2b707..e3d1182324 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties +++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties @@ -193,6 +193,7 @@ Operations = Operations Optional = Optional Options = Options Origin = Origin +OriginalColors = Original colors OriginInCellCenter = Origin in a cell center Others = Others OtherSurface = Other surface diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties index 39059bff17..d804f40bca 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties +++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties @@ -200,6 +200,7 @@ Operations = Op\u00e9rations Optional = Optionnel Options = Options Origin = Origine +OriginalColors = Couleurs originales OriginInCellCenter = Origine au centre d\u2019une cellule Others = Autres OtherSurface = Autre surface diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java index bc8c71c109..7b46de2c08 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java @@ -40,7 +40,7 @@ import org.apache.sis.coverage.grid.BufferedGridCoverage; * but it is {@link ImageRenderer} responsibility to perform this substitution as an optimization.</p> * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.3 + * @version 1.4 * @since 1.0 */ final class Raster extends BufferedGridCoverage { @@ -63,11 +63,6 @@ final class Raster extends BufferedGridCoverage { */ private final int visibleBand; - /** - * Name to display in error messages. Not to be used for processing. - */ - private final String label; - /** * The colors to use for each category, or {@code null} for default. * The function may return {@code null}, which means transparent. @@ -80,18 +75,16 @@ final class Raster extends BufferedGridCoverage { * @param domain the grid extent, CRS and conversion from cell indices to CRS. * @param range sample dimensions for each image band. * @param data the sample values, potentially multi-banded. - * @param lebel name to display in error messages. Not to be used for processing. * @param pixelStride increment to apply on index for moving to the next pixel in the same band. * @param bandOffsets offsets to add to sample index in each band, or {@code null} if none. * @param visibleBand the band to use for defining pixel colors when the image is displayed on screen. * @param colors the colors to use for each category, or {@code null} for default. */ Raster(final GridGeometry domain, final List<SampleDimension> range, final DataBuffer data, - final String label, final int pixelStride, final int[] bandOffsets, final int visibleBand, + final int pixelStride, final int[] bandOffsets, final int visibleBand, final Function<Category,Color[]> colors) { super(domain, range, data); - this.label = label; this.colors = colors; this.pixelStride = pixelStride; this.bandOffsets = bandOffsets; diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java index 48c8c07a0d..5f819e8105 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java @@ -67,7 +67,7 @@ import org.apache.sis.internal.storage.StoreResource; * @author Martin Desruisseaux (Geomatys) * @author Johann Sorel (Geomatys) * @author Alexis Manin (Geomatys) - * @version 1.3 + * @version 1.4 * @since 1.0 */ public final class RasterResource extends AbstractGridCoverageResource implements StoreResource, ResourceOnFileSystem { @@ -727,7 +727,7 @@ public final class RasterResource extends AbstractGridCoverageResource implement } final Variable main = data[visibleBand]; final Raster raster = new Raster(targetDomain, UnmodifiableArrayList.wrap(bands), imageBuffer, - String.valueOf(identifier), rangeIndices.getPixelStride(), bandOffsets, visibleBand, + rangeIndices.getPixelStride(), bandOffsets, visibleBand, main.decoder.convention().getColors(main)); logReadOperation(location, targetDomain, startTime); return raster;