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
commit 1d075eb2fbfbdbbbecca27371e9944f416b82cdd
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Aug 11 20:33:23 2020 +0200
Provide control on the colors used for rendering different range of values (categories) of a GridCoverage.
---
.../apache/sis/gui/coverage/CategoryColors.java | 165 ++++++++++
.../sis/gui/coverage/CategoryColorsCell.java | 364 +++++++++++++++++++++
.../java/org/apache/sis/gui/coverage/Controls.java | 12 +-
.../apache/sis/gui/coverage/CoverageCanvas.java | 23 ++
.../apache/sis/gui/coverage/CoverageControls.java | 110 +++----
.../apache/sis/gui/coverage/CoverageStyling.java | 158 +++++++++
.../org/apache/sis/internal/gui/ColorName.java | 87 +++++
.../org/apache/sis/internal/gui/GUIUtilities.java | 33 ++
.../sis/gui/coverage/CoverageStylingApp.java | 83 +++++
.../apache/sis/internal/gui/GUIUtilitiesTest.java | 27 ++
.../org/apache/sis/util/resources/Vocabulary.java | 15 +
.../sis/util/resources/Vocabulary.properties | 3 +
.../sis/util/resources/Vocabulary_fr.properties | 3 +
13 files changed, 1013 insertions(+), 70 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CategoryColors.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CategoryColors.java
new file mode 100644
index 0000000..e4cf01f
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CategoryColors.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.coverage;
+
+import java.util.Arrays;
+import javafx.collections.ObservableList;
+import javafx.scene.control.ComboBox;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.CycleMethod;
+import javafx.scene.paint.LinearGradient;
+import javafx.scene.paint.Paint;
+import javafx.scene.paint.Stop;
+import org.apache.sis.internal.gui.ColorName;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.internal.gui.GUIUtilities;
+
+
+/**
+ * Represents a single color or a color ramp that can be represented in {@link CategoryColorsCell}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+final class CategoryColors {
+ /**
+ * Default color palette.
+ */
+ static final CategoryColors GRAYSCALE = new CategoryColors(0xFF000000, 0xFFFFFFFF),
+ BELL = new CategoryColors(0xFF0000FF, 0xFF00FFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFF0000);
+
+ /**
+ * ARGB codes of this single color or color ramp.
+ * If null or empty, then default to transparent.
+ */
+ final int[] colors;
+
+ /**
+ * The paint for this palette, created when first needed.
+ */
+ private transient Paint paint;
+
+ /**
+ * A name for this palette, computed when first needed.
+ *
+ * @see #toString()
+ */
+ private transient String name;
+
+ /**
+ * Creates a new palette for the given colors.
+ */
+ CategoryColors(final int... colors) {
+ this.colors = colors;
+ }
+
+ /**
+ * Declares this {@code CategoryColors} as the selected item in the given chooser.
+ * If this instance is not found, then it is added to the chooser list.
+ */
+ final void asSelectedItem(final ComboBox<CategoryColors> colorRampChooser) {
+ final ObservableList<CategoryColors> items = colorRampChooser.getItems();
+ int i = items.indexOf(this);
+ if (i < 0) {
+ i = items.size();
+ items.add(this);
+ }
+ colorRampChooser.getSelectionModel().select(i);
+ }
+
+ /**
+ * Returns the first color, or {@code null} if none.
+ * This is used for qualitative categories, which are expected to contain only one color.
+ */
+ final Color firstColor() {
+ if (colors != null && colors.length != 0) {
+ return GUIUtilities.fromARGB(colors[0]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the paint to use for filling a rectangle using this color palette.
+ * Returns {@code null} if this {@code CategoryColors} contains no color.
+ */
+ final Paint paint() {
+ if (paint == null) {
+ switch (colors.length) {
+ case 0: break;
+ case 1: {
+ paint = GUIUtilities.fromARGB(colors[0]);
+ break;
+ }
+ default: {
+ final Stop[] stops = new Stop[colors.length];
+ final double scale = 1d / (stops.length - 1);
+ for (int i=0; i<stops.length; i++) {
+ stops[i] = new Stop(scale*i, GUIUtilities.fromARGB(colors[i]));
+ }
+ paint = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, stops);
+ }
+ }
+ }
+ return paint;
+ }
+
+ /**
+ * Returns a string representation of this color palette.
+ * This string representation will appear in the combo box when that box is shown.
+ */
+ @Override
+ public String toString() {
+ if (name == null) {
+ final int n;
+ if (colors == null || (n = colors.length) == 0) {
+ name = Vocabulary.format(Vocabulary.Keys.Transparent);
+ } else if (equals(GRAYSCALE)) {
+ name = Vocabulary.format(Vocabulary.Keys.Grayscale);
+ } else {
+ name = ColorName.of(colors[0]);
+ if (n > 1) {
+ final StringBuilder buffer = new StringBuilder(name);
+ if (n > 2) {
+ buffer.append(" … ").append(ColorName.of(colors[n / 2]));
+ }
+ name = buffer.append(" … ").append(ColorName.of(colors[n - 1])).toString();
+ }
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Returns a hash code value for this palette.
+ * Defined mostly for consistency with {@link #equals(Object)}.
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(colors) ^ 81;
+ }
+
+ /**
+ * Returns whether the given object is equal to this {@code CategoryColors}.
+ */
+ @Override
+ public boolean equals(final Object other) {
+ return (other instanceof CategoryColors) && Arrays.equals(colors, ((CategoryColors) other).colors);
+ }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CategoryColorsCell.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CategoryColorsCell.java
new file mode 100644
index 0000000..bbae3e2
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CategoryColorsCell.java
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.coverage;
+
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.ComboBoxBase;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.ColorPicker;
+import javafx.scene.control.ContentDisplay;
+import javafx.geometry.Pos;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.beans.value.ObservableValue;
+import javafx.beans.property.ReadOnlyObjectWrapper;
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.internal.gui.GUIUtilities;
+import org.apache.sis.coverage.Category;
+
+
+/**
+ * Cell representing the color of a qualitative or quantitative category.
+ * The color can be modified by selecting the table row, then clicking on the color.
+ *
+ * <p>The interfaces implemented by this class are implementation convenience
+ * that may change in any future version.</p>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+final class CategoryColorsCell extends TableCell<Category,CategoryColors> implements EventHandler<ActionEvent> {
+ /**
+ * Space (in pixels) to remove on right side of the rectangle representing colors.
+ */
+ private static final double WIDTH_ADJUST = -9;
+
+ /**
+ * Height (in pixels) of the rectangle representing colors.
+ */
+ private static final double HEIGHT = 16;
+
+ /**
+ * The function that determines which colors to apply on a given category.
+ * This same instance is shared by all cells of the same category table.
+ */
+ private final CoverageStyling styling;
+
+ /**
+ * The control for selecting a single color, or {@code null} if not yet created.
+ * This applies to qualitative categories.
+ */
+ private ColorPicker colorPicker;
+
+ /**
+ * The control for selecting a color ramp, or {@code null} if not yet created.
+ * This applies to quantitative categories.
+ *
+ * @see Category#isQuantitative()
+ */
+ private ComboBox<CategoryColors> colorRampChooser;
+
+ /**
+ * The single color shown in the table. Created when first needed.
+ */
+ private Rectangle singleColor;
+
+ /**
+ * Colors to restore if user cancels an edit action.
+ */
+ private CategoryColors restoreOnCancel;
+
+ /**
+ * Creates a cell for the colors column.
+ * This constructor is for {@link #createTable(CoverageStyling, Vocabulary)} usage only.
+ */
+ private CategoryColorsCell(final CoverageStyling styling) {
+ this.styling = styling;
+ setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
+ }
+
+ /**
+ * Invoked when the color in this cell changed. It may be because of user selection in the combo box,
+ * or because this cell is now used for a new {@link Category} instance.
+ *
+ * <div class="note"><b>Implementation note:</b>
+ * this method should not invoke {@link #setGraphic(Node)} if the current graphic is a {@link ComboBoxBase}
+ * because this method may be invoked at any time, including during execution of {@link #startEdit()} or
+ * {@link #commitEdit(Object)}. Adding or removing {@link ColorPicker} or {@link ComboBox} in this method
+ * cause problems with focus system. In particular we must be sure to remove {@link ColorPicker} only after
+ * it has lost focus.</div>
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void updateItem(final CategoryColors colors, final boolean empty) {
+ super.updateItem(colors, empty);
+ final Node control = getGraphic();
+ if (colors != null) {
+ if (control == null) {
+ setColorView(colors);
+ } else if (control instanceof Rectangle) {
+ ((Rectangle) control).setFill(colors.paint());
+ } else if (control instanceof ColorPicker) {
+ ((ColorPicker) control).setValue(colors.firstColor());
+ } else {
+ // A ClassCastException here would be a bug in CategoryColorsCell editors management.
+ colors.asSelectedItem(((ComboBox<CategoryColors>) control));
+ }
+ } else if (control instanceof Rectangle) {
+ setGraphic(null);
+ }
+ }
+
+ /**
+ * Returns {@code true} if neither {@link #colorPicker} or {@link #colorRampChooser} has the focus.
+ * This is used for assertions.
+ */
+ private boolean controlNotFocused() {
+ return (colorPicker == null || !colorPicker.isFocused()) &&
+ (colorRampChooser == null || !colorRampChooser.isFocused());
+ }
+
+ /**
+ * Sets the color representation when no editing is under way. It is caller responsibility to ensure
+ * that the current graphic is not a combo box, or that it is safe to remove that combo box from the
+ * scene (i.e. that combo box does not have focus anymore).
+ */
+ private void setColorView(final CategoryColors colors) {
+ assert controlNotFocused();
+ Rectangle view = null;
+ if (colors != null) {
+ final Paint paint = colors.paint();
+ if (paint != null) {
+ if (singleColor == null) {
+ singleColor = createRectangle(WIDTH_ADJUST);
+ }
+ view = singleColor;
+ view.setFill(paint);
+ }
+ }
+ setGraphic(view);
+ }
+
+ /**
+ * Creates the graphic to draw in a table cell or combo box cell for representing a color or color ramp.
+ *
+ * @param adjust amount of space (in pixels) to add or remove on the right size.
+ * Should be a negative number for removing space.
+ */
+ private Rectangle createRectangle(final double adjust) {
+ final Rectangle gr = new Rectangle();
+ gr.setHeight(HEIGHT);
+ gr.widthProperty().bind(widthProperty().add(adjust));
+ return gr;
+ }
+
+ /**
+ * Cell for a color ramp in a {@link ComboBox}.
+ */
+ private final class Ramp extends ListCell<CategoryColors> {
+ /** Creates a new cell. */
+ Ramp() {
+ setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
+ setMaxWidth(Double.POSITIVE_INFINITY);
+ }
+
+ /** Sets the colors to show in the combo box item. */
+ @Override
+ protected void updateItem(final CategoryColors colors, final boolean empty) {
+ super.updateItem(colors, empty);
+ if (colors == null) {
+ setGraphic(null);
+ } else {
+ Rectangle r = (Rectangle) getGraphic();
+ if (r == null) {
+ r = createRectangle(-40);
+ setGraphic(r);
+ }
+ r.setFill(colors.paint());
+ }
+ }
+ }
+
+ /**
+ * Transitions from non-editing state to editing state. This method is automatically invoked when a
+ * row is selected and the user clicks on the color cell in that row. This method sets the combo box
+ * as the graphic element in that cell and shows it immediately. The immediate {@code control.show()}
+ * is for avoiding to force users to perform a third mouse click.
+ */
+ @Override
+ public void startEdit() {
+ restoreOnCancel = getItem();
+ final CategoryColors colors = (restoreOnCancel != null) ? restoreOnCancel : CategoryColors.GRAYSCALE;
+ final ComboBoxBase<?> control;
+ if (getTableRow().getItem().isQuantitative()) {
+ if (colorRampChooser == null) {
+ colorRampChooser = new ComboBox<>();
+ colorRampChooser.setEditable(false);
+ colorRampChooser.setCellFactory((column) -> new Ramp());
+ colorRampChooser.getItems().setAll(CategoryColors.GRAYSCALE, CategoryColors.BELL);
+ addListeners(colorRampChooser);
+ }
+ colors.asSelectedItem(colorRampChooser);
+ control = colorRampChooser;
+ } else {
+ if (colorPicker == null) {
+ colorPicker = new ColorPicker();
+ addListeners(colorPicker);
+ }
+ colorPicker.setValue(colors.firstColor());
+ control = colorPicker;
+ }
+ /*
+ * Call `startEdit()` only after above call to `setValue(…)` because we want `isEditing()`
+ * to return false during above value change. This is for preventing change listeners to
+ * misinterpret the value change as a user selection.
+ */
+ super.startEdit();
+ setGraphic(control); // Must be before `requestFocus()`, otherwise focus request is ignored.
+ control.requestFocus(); // Must be before `show()`, otherwise there is apparent focus confusion.
+ control.show();
+ }
+
+ /**
+ * Finishes configuration of a newly created combo box.
+ */
+ private void addListeners(final ComboBoxBase<?> control) {
+ control.setOnAction(this);
+ control.setOnHidden((e) -> hidden());
+ }
+
+ /**
+ * Invoked when a combo box has been hidden. This method sets the focus to the table before to remove
+ * the combo box. This is necessary for causing the combo box to lost focus, otherwise focus problems
+ * appear next time that the combo box is shown.
+ *
+ * <p>IF the cell was in editing mode when this method is invoked, it means that the user clicked outside
+ * the combo box area without validating his/her choice. In this case {@link #commitEdit(Object)} has not
+ * been invoked and we need to either commit now or cancel. Current implementation cancels.</p>
+ */
+ private void hidden() {
+ if (isEditing()) {
+ if (isHover()) {
+ return; // Keep editing state.
+ }
+ setItem(restoreOnCancel);
+ super.cancelEdit();
+ }
+ restoreOnCancel = null;
+ getTableView().requestFocus(); // Must be before `setGraphic(…)` for causing ColorPicker to lost focus.
+ setColorView(getItem());
+ }
+
+ /**
+ * Transitions from an editing state into a non-editing state without saving any user input.
+ * This method is automatically invoked when the user click on another table row.
+ */
+ @Override
+ public void cancelEdit() {
+ setItem(restoreOnCancel);
+ restoreOnCancel = null;
+ super.cancelEdit();
+ assert controlNotFocused();
+ setColorView(getItem());
+ }
+
+ /**
+ * Invoked when users confirmed that (s)he wants to use the selected colors.
+ *
+ * @param colors the colors to use.
+ */
+ @Override
+ public void commitEdit(final CategoryColors colors) {
+ super.commitEdit(colors);
+ styling.setARGB(getTableRow().getItem(), colors.colors);
+ }
+
+ /**
+ * Invoked when the user selected a new value in the color picker or color ramp chooser.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handle(final ActionEvent event) {
+ if (isEditing()) {
+ final Object source = event.getSource();
+ final CategoryColors value;
+ if (source instanceof ColorPicker) {
+ final Color color = ((ColorPicker) source).getValue();
+ value = (color != null) ? new CategoryColors(GUIUtilities.toARGB(color)) : null;
+ } else {
+ // A ClassCastException here would be a bug in CategoryColorsCell editors management.
+ value = ((ComboBox<CategoryColors>) source).getValue();
+ }
+ commitEdit(value);
+ }
+ }
+
+ /**
+ * Creates a table of categories.
+ *
+ * @param styling function that determines which colors to apply on a given category.
+ * @param vocabulary resources for the locale in use.
+ */
+ static TableView<Category> createTable(final CoverageStyling styling, final Vocabulary vocabulary) {
+ final TableColumn<Category,String> name = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Name));
+ name.setCellValueFactory(CategoryColorsCell::getCategoryName);
+ name.setCellFactory(CategoryColorsCell::createNameCell);
+ name.setEditable(false);
+ name.setId("name");
+
+ final TableColumn<Category,CategoryColors> colors = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Colors));
+ colors.setCellValueFactory(styling);
+ colors.setCellFactory((column) -> new CategoryColorsCell(styling));
+ colors.setId("colors");
+
+ final TableView<Category> table = new TableView<>();
+ table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ table.getColumns().setAll(name, colors);
+ table.setEditable(true);
+ return table;
+ }
+
+ /**
+ * Invoked for creating a cell for the "name" column.
+ * Returns the JavaFX default cell except for vertical alignment, which is centered.
+ */
+ 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);
+ cell.setAlignment(Pos.CENTER_LEFT);
+ return cell;
+ }
+
+ /**
+ * Invoked when the table needs to render a text in the "Name" column of the category table.
+ */
+ private static ObservableValue<String> getCategoryName(final TableColumn.CellDataFeatures<Category,String> cell) {
+ final InternationalString name = cell.getValue().getName();
+ return (name != null) ? new ReadOnlyObjectWrapper<>(name.toString()) : null;
+ }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
index 0bd74af..d3ff50d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
@@ -51,16 +51,10 @@ abstract class Controls {
private static final Insets NEXT_CAPTION_MARGIN = new Insets(30, 0, 6, 0);
/**
- * Margin for adding an indentation to a node when the node is inside a group
- * created by {@link Styles#createControlGrid(int, Label...)}.
+ * Same indentation as {@link Styles#FORM_INSETS}, but without the space on other sides.
+ * This is used when the node is outside a group created by {@link Styles#createControlGrid(int, Label...)}.
*/
- static final Insets INDENT = new Insets(0, 0, 0, 15);
-
- /**
- * Margin for adding an indentation to a node when the node is outside a group
- * created by {@link Styles#createControlGrid(int, Label...)}.
- */
- static final Insets INDENT_OUTSIDE = new Insets(0, 0, 0, 15 + Styles.FORM_INSETS.getLeft());
+ static final Insets CONTENT_MARGIN = new Insets(0, 0, 0, Styles.FORM_INSETS.getLeft());
/**
* The toolbar button for selecting this view.
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 cfafb5d..802163c 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
@@ -20,6 +20,7 @@ import java.util.Map;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
+import java.util.function.Function;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
@@ -54,6 +55,7 @@ import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.image.PlanarImage;
import org.apache.sis.image.Interpolation;
+import org.apache.sis.coverage.Category;
import org.apache.sis.gui.map.MapCanvas;
import org.apache.sis.gui.map.MapCanvasAWT;
import org.apache.sis.gui.map.RenderingMode;
@@ -306,6 +308,25 @@ public class CoverageCanvas extends MapCanvasAWT {
}
/**
+ * 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.
+ *
+ * @param colors colors to use for arbitrary categories of sample values, or {@code null} for default.
+ */
+ final void setCategoryColors(final Function<Category, java.awt.Color[]> colors) {
+ data.processor.setCategoryColors(colors);
+ resampledImage = null;
+ requestRepaint();
+ }
+
+ /**
* Sets the background, as a color for now but more patterns may be allowed in a future version.
*/
final void setBackground(final Color color) {
@@ -432,6 +453,8 @@ public class CoverageCanvas extends MapCanvasAWT {
/**
* Invoked when a new interpolation has been specified.
+ *
+ * @see #setInterpolation(Interpolation)
*/
private void onInterpolationSpecified(final Interpolation newValue) {
data.processor.setInterpolation(newValue);
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 69bf462..f07e984 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
@@ -16,11 +16,11 @@
*/
package org.apache.sis.gui.coverage;
+import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.lang.ref.Reference;
import javafx.scene.control.Accordion;
-import javafx.scene.control.ColorPicker;
import javafx.scene.control.Control;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.BorderPane;
@@ -30,15 +30,15 @@ import javafx.scene.layout.VBox;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
-import javafx.collections.ObservableList;
-import javafx.scene.Node;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
+import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.paint.Color;
-import javafx.scene.text.Font;
import javafx.util.StringConverter;
import org.apache.sis.storage.Resource;
+import org.apache.sis.coverage.Category;
+import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.gui.referencing.RecentReferenceSystems;
import org.apache.sis.gui.map.MapMenu;
@@ -64,6 +64,11 @@ final class CoverageControls extends Controls {
private final CoverageCanvas view;
/**
+ * The control showing categories and their colors for the current coverage.
+ */
+ private final TableView<Category> categoryTable;
+
+ /**
* The controls for changing {@link #view}.
*/
private final Accordion controls;
@@ -84,9 +89,8 @@ final class CoverageControls extends Controls {
final RecentReferenceSystems referenceSystems)
{
final Resources resources = Resources.forLocale(vocabulary.getLocale());
- final Color background = Color.BLACK;
view = new CoverageCanvas(vocabulary.getLocale());
- view.setBackground(background);
+ view.setBackground(Color.BLACK);
final StatusBar statusBar = new StatusBar(referenceSystems, view);
view.statusBar = statusBar;
imageAndStatus = new BorderPane(view.getView());
@@ -97,64 +101,52 @@ final class CoverageControls extends Controls {
* "Display" section with the following controls:
* - Current CRS
* - Interpolation
- * - Color stretching
- * - Background color
*/
final VBox displayPane;
{ // Block for making variables locale to this scope.
- final Font font = fontOfGroup();
- final Label crsLabel = new Label(vocabulary.getString(Vocabulary.Keys.ReferenceSystem));
- final Label crsShown = new Label();
- crsLabel.setLabelFor(crsShown);
- crsLabel.setFont(font);
- crsLabel.setPadding(Styles.FORM_INSETS);
- crsShown.setPadding(INDENT_OUTSIDE);
- crsShown.setTooltip(new Tooltip(resources.getString(Resources.Keys.SelectCrsByContextMenu)));
- menu.selectedReferenceSystem().ifPresent((text) -> crsShown.textProperty().bind(text));
+ final Label crsControl = new Label();
+ final Label crsHeader = labelOfGroup(vocabulary, Vocabulary.Keys.ReferenceSystem, crsControl, true);
+ crsControl.setPadding(CONTENT_MARGIN);
+ crsControl.setTooltip(new Tooltip(resources.getString(Resources.Keys.SelectCrsByContextMenu)));
+ menu.selectedReferenceSystem().ifPresent((text) -> crsControl.textProperty().bind(text));
/*
- * The pane containing controls will be divided in sections separated by labels:
- * ones for values and one for colors.
+ * Creates a "Values" sub-section with the following controls:
+ * - Interpolation
*/
- final int valuesHeader = 0;
- final int colorsHeader = 2;
- final GridPane gp;
- gp = Styles.createControlGrid(valuesHeader + 1,
- label(vocabulary, Vocabulary.Keys.Interpolation, createInterpolationButton(vocabulary.getLocale())),
- label(vocabulary, Vocabulary.Keys.Stretching, Stretching.createButton((p,o,n) -> view.setStyling(n))),
- label(vocabulary, Vocabulary.Keys.Background, createBackgroundButton(background)));
+ final GridPane valuesControl = Styles.createControlGrid(0,
+ label(vocabulary, Vocabulary.Keys.Interpolation, createInterpolationButton(vocabulary.getLocale())));
+ final Label valuesHeader = labelOfGroup(vocabulary, Vocabulary.Keys.Values, valuesControl, false);
/*
- * Insert space (one row) between "interpolation" and "stretching"
- * so we can insert the "colors" section header.
+ * All sections put together.
*/
- final ObservableList<Node> items = gp.getChildren();
- for (final Node item : items) {
- if (GridPane.getColumnIndex(item) == 0) {
- ((Label) item).setPadding(INDENT);
- }
- final int row = GridPane.getRowIndex(item);
- if (row >= colorsHeader) {
- GridPane.setRowIndex(item, row + 1);
- }
- }
- final Label values = new Label(vocabulary.getString(Vocabulary.Keys.Values));
- final Label colors = new Label(vocabulary.getString(Vocabulary.Keys.Colors));
- values.setFont(font);
- colors.setFont(font);
- GridPane.setConstraints(values, 0, valuesHeader, 2, 1); // Span 2 columns.
- GridPane.setConstraints(colors, 0, colorsHeader, 2, 1);
- items.addAll(values, colors);
- displayPane = new VBox(crsLabel, crsShown, gp);
+ displayPane = new VBox(crsHeader, crsControl, valuesHeader, valuesControl);
+ }
+ /*
+ * "Colors" section with the following controls:
+ * - Colors for each category
+ * - Color stretching
+ */
+ final VBox colorsPane;
+ { // Block for making variables locale to this scope.
+ final CoverageStyling styling = new CoverageStyling(view);
+ categoryTable = CategoryColorsCell.createTable(styling, vocabulary);
+ final GridPane gp = Styles.createControlGrid(0,
+ label(vocabulary, Vocabulary.Keys.Stretching, Stretching.createButton((p,o,n) -> view.setStyling(n))));
+
+ colorsPane = new VBox(
+ labelOfGroup(vocabulary, Vocabulary.Keys.Categories, categoryTable, true), categoryTable, gp);
}
/*
* Put all sections together and have the first one expanded by default.
* The "Properties" section will be built by `PropertyPaneCreator` only if requested.
*/
- final TitledPane p1 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Display), displayPane);
- final TitledPane p2 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Properties), null);
- controls = new Accordion(p1, p2);
+ final TitledPane p1 = new TitledPane(vocabulary.getString(Vocabulary.Keys.SpatialRepresentation), displayPane);
+ final TitledPane p2 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Colors), colorsPane);
+ final TitledPane p3 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Properties), null);
+ controls = new Accordion(p1, p2, p3);
controls.setExpandedPane(p1);
view.coverageProperty.bind(coverage);
- p2.expandedProperty().addListener(new PropertyPaneCreator(view, p2));
+ p3.expandedProperty().addListener(new PropertyPaneCreator(view, p3));
}
/**
@@ -223,17 +215,6 @@ final class CoverageControls extends Controls {
}
/**
- * Creates the button for selecting a background color.
- */
- private ColorPicker createBackgroundButton(final Color background) {
- final ColorPicker b = new ColorPicker(background);
- b.setOnAction((e) -> {
- view.setBackground(((ColorPicker) e.getSource()).getValue());
- });
- return b;
- }
-
- /**
* Invoked the first time that the "Properties" pane is opened for building the JavaFX visual components.
* We deffer the creation of this pane because it is often not requested at all, since this is more for
* developers than users.
@@ -272,6 +253,13 @@ final class CoverageControls extends Controls {
@Override
final void coverageChanged(final GridCoverage data, final Reference<Resource> originator) {
view.setOriginator(originator);
+ if (data != null) {
+ final int visibleBand = 0; // TODO: provide a selector for the band to show.
+ final List<SampleDimension> bands = data.getSampleDimensions();
+ categoryTable.getItems().setAll(bands.get(visibleBand).getCategories());
+ } else {
+ categoryTable.getItems().clear();
+ }
}
/**
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
new file mode 100644
index 0000000..fca46ed
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+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.function.Function;
+import javafx.util.Callback;
+import javafx.scene.control.TableColumn;
+import javafx.beans.value.ObservableValue;
+import javafx.beans.property.SimpleObjectProperty;
+import org.apache.sis.coverage.Category;
+import org.apache.sis.internal.coverage.j2d.Colorizer;
+
+
+/**
+ * Colors to apply on coverages based on their {@link Category} instances.
+ *
+ * <p>The interfaces implemented by this class are implementation convenience
+ * that may change in any future version.</p>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+final class CoverageStyling implements Function<Category,Color[]>,
+ Callback<TableColumn.CellDataFeatures<Category,CategoryColors>, ObservableValue<CategoryColors>>
+{
+ /**
+ * Customized colors selected by user. Keys are English names of categories.
+ *
+ * @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;
+
+ /**
+ * The view to notify when a color changed, or {@code null} if none.
+ */
+ private final CoverageCanvas canvas;
+
+ /**
+ * Creates a new styling instance.
+ */
+ 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 = Colorizer.GRAYSCALE;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Associates colors to the given category.
+ */
+ 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.
+ }
+ }
+
+ /**
+ * Same 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>
+ */
+ private 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();
+ }
+ }
+ }
+ return ARGB;
+ }
+
+ /**
+ * 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).
+ *
+ * @param category the category for which to get the colors.
+ * @return colors to apply for the given category, or {@code null}.
+ */
+ @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);
+ }
+ return colors;
+ }
+ return fallback.apply(category);
+ }
+
+ /**
+ * Invoked by {@link TableColumn} for computing value of a {@link CategoryColorsCell}.
+ * This method is public as an implementation side-effect; do not rely on that.
+ */
+ @Override
+ public ObservableValue<CategoryColors> call(final TableColumn.CellDataFeatures<Category,CategoryColors> cell) {
+ final Category category = cell.getValue();
+ if (category != null) {
+ final int[] ARGB = getARGB(category);
+ if (ARGB != null) {
+ return new SimpleObjectProperty<>(new CategoryColors(ARGB));
+ }
+ }
+ return null;
+ }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ColorName.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ColorName.java
new file mode 100644
index 0000000..4647fd7
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ColorName.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gui;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import javafx.scene.paint.Color;
+
+
+/**
+ * Provides a name for a given {@link Color} instance.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public final class ColorName {
+ /**
+ * Do not allow instantiation of this class.
+ */
+ private ColorName() {
+ }
+
+ /**
+ * The color names.
+ */
+ private static final Map<Color,String> NAMES = new HashMap<>(175);
+ static {
+ final StringBuilder buffer = new StringBuilder();
+ for (final Field field : Color.class.getFields()) {
+ if (Modifier.isStatic(field.getModifiers()) && Color.class.equals(field.getType())) try {
+ final String name = field.getName();
+ buffer.append(name.toLowerCase()); // Default locale is okay here.
+ buffer.setCharAt(0, name.charAt(0)); // Code point not used in Color API.
+ NAMES.put((Color) field.get(null), buffer.toString());
+ buffer.setLength(0);
+ } catch (Exception e) {
+ // Ignore. The map is only informative.
+ }
+ }
+ }
+
+ /**
+ * Returns the name of given color.
+ *
+ * @param color color for which to get a name.
+ * @return name of given color, or hexadecimal code if the given code does not have a known name.
+ */
+ public static String of(final Color color) {
+ String name = NAMES.get(color);
+ if (name == null) {
+ name = Integer.toHexString(GUIUtilities.toARGB(color));
+ }
+ return name;
+ }
+
+ /**
+ * Returns the name of given ARGB code.
+ *
+ * @param color color for which to get a name.
+ * @return name of given color, or hexadecimal code if the given code does not have a known name.
+ */
+ public static String of(final int color) {
+ String name = NAMES.get(GUIUtilities.fromARGB(color));
+ if (name == null) {
+ name = Integer.toHexString(color);
+ }
+ return name;
+ }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
index eb346ef..3d8bfe9 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
@@ -28,6 +28,7 @@ import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.shape.Rectangle;
import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
import javafx.stage.Window;
import javax.measure.Unit;
import javax.measure.Quantity;
@@ -299,4 +300,36 @@ public final class GUIUtilities extends Static {
m = Units.METRE.getConverterTo(unit).convert(Math.max(m, Formulas.LINEAR_TOLERANCE));
return Quantities.create(m, unit);
}
+
+ /**
+ * Returns a color from a ARGB value packed in an integer.
+ *
+ * @param code the ARGB value.
+ * @return color for the given ARGB value.
+ */
+ public static Color fromARGB(final int code) {
+ return Color.rgb(0xFF & (code >>> Byte.SIZE*2), // Red
+ 0xFF & (code >>> Byte.SIZE), // Green
+ 0xFF & (code)); // Blue
+ }
+
+ /**
+ * Returns a ARGB value packed in an integer.
+ *
+ * @param color color for which to get the ARGB value.
+ * @return ARGB value for the given color.
+ */
+ public static int toARGB(final Color color) {
+ return (toByte(color.getOpacity()) << 3*Byte.SIZE)
+ | (toByte(color.getRed()) << 2*Byte.SIZE)
+ | (toByte(color.getGreen()) << Byte.SIZE)
+ | toByte(color.getBlue());
+ }
+
+ /**
+ * Converts a floating point value in the 0 … 1 range to an integer value in the 0 … 255 range.
+ */
+ private static int toByte(final double value) {
+ return (int) Math.round(value * 255);
+ }
}
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
new file mode 100644
index 0000000..a66da1d
--- /dev/null
+++ b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageStylingApp.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.coverage;
+
+import java.util.Locale;
+import javafx.stage.Stage;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.control.TableView;
+import javafx.scene.layout.BorderPane;
+import org.apache.sis.coverage.Category;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.measure.Units;
+
+
+/**
+ * Shows category table built by {@link CoverageStyling} with arbitrary data.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public final strictfp class CoverageStylingApp extends Application {
+ /**
+ * Starts the test application.
+ *
+ * @param args ignored.
+ */
+ public static void main(final String[] args) {
+ launch(args);
+ }
+
+ /**
+ * Creates and starts the test application.
+ *
+ * @param window where to show the application.
+ */
+ @Override
+ public void start(final Stage window) {
+ final BorderPane pane = new BorderPane();
+ pane.setCenter(createCategoryTable());
+ window.setTitle("BandColorsTable Test");
+ window.setScene(new Scene(pane));
+ window.setWidth (400);
+ window.setHeight(300);
+ window.show();
+ }
+
+ /**
+ * Creates a table with arbitrary categories to show.
+ */
+ private static TableView<Category> createCategoryTable() {
+ final SampleDimension band = new SampleDimension.Builder()
+ .addQualitative("Background", 0)
+ .addQualitative("Cloud", 1)
+ .addQualitative("Land", 2)
+ .addQuantitative("Temperature", 5, 255, 0.15, -5, Units.CELSIUS)
+ .setName("Sea Surface Temperature")
+ .build();
+
+ final CoverageStyling styling = new CoverageStyling(null);
+ styling.setARGB(band.getCategories().get(1), new int[] {0xFF607080});
+ final TableView<Category> table = CategoryColorsCell.createTable(styling, Vocabulary.getResources((Locale) null));
+ table.getItems().setAll(band.getCategories());
+ return table;
+ }
+}
diff --git a/application/sis-javafx/src/test/java/org/apache/sis/internal/gui/GUIUtilitiesTest.java b/application/sis-javafx/src/test/java/org/apache/sis/internal/gui/GUIUtilitiesTest.java
index 11c14d7..61b4219 100644
--- a/application/sis-javafx/src/test/java/org/apache/sis/internal/gui/GUIUtilitiesTest.java
+++ b/application/sis-javafx/src/test/java/org/apache/sis/internal/gui/GUIUtilitiesTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.internal.gui;
import java.util.Arrays;
import java.util.List;
+import javafx.scene.paint.Color;
import org.apache.sis.test.TestCase;
import org.junit.Test;
@@ -42,4 +43,30 @@ public final strictfp class GUIUtilitiesTest extends TestCase {
final List<Integer> y = Arrays.asList(1, 2, 3, 7, 8);
assertEquals(Arrays.asList(1, 2, 7), GUIUtilities.longestCommonSubsequence(x, y));
}
+
+ /**
+ * Tests {@link GUIUtilities#fromARGB(int)}.
+ */
+ @Test
+ public void testFromARGB() {
+ final java.awt.Color reference = java.awt.Color.ORANGE;
+ final Color color = GUIUtilities.fromARGB(reference.getRGB());
+ assertEquals(reference.getRed(), StrictMath.round(255 * color.getRed()));
+ assertEquals(reference.getGreen(), StrictMath.round(255 * color.getGreen()));
+ assertEquals(reference.getBlue(), StrictMath.round(255 * color.getBlue()));
+ assertEquals(reference.getAlpha(), StrictMath.round(255 * color.getOpacity()));
+ }
+
+ /**
+ * Tests {@link GUIUtilities#toARGB(Color)}.
+ */
+ @Test
+ public void testToARGB() {
+ final int ARGB = GUIUtilities.toARGB(Color.ORANGE);
+ final java.awt.Color reference = new java.awt.Color(ARGB);
+ assertEquals(0xFF, reference.getRed());
+ assertEquals(0xA5, reference.getGreen());
+ assertEquals(0x00, reference.getBlue());
+ assertEquals(0xFF, reference.getAlpha());
+ }
}
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 1465229..8b43adc 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
@@ -160,6 +160,11 @@ public final class Vocabulary extends IndexedResourceBundle {
public static final short Cardinality = 20;
/**
+ * Categories
+ */
+ public static final short Categories = 248;
+
+ /**
* Caused by {0}
*/
public static final short CausedBy_1 = 21;
@@ -560,6 +565,11 @@ public final class Vocabulary extends IndexedResourceBundle {
public static final short Gray = 95;
/**
+ * Grayscale
+ */
+ public static final short Grayscale = 250;
+
+ /**
* Green
*/
public static final short Green = 96;
@@ -1160,6 +1170,11 @@ public final class Vocabulary extends IndexedResourceBundle {
public static final short Transparency = 201;
/**
+ * Transparent
+ */
+ public static final short Transparent = 249;
+
+ /**
* Truncated Julian
*/
public static final short TruncatedJulian = 202;
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 e3440eb..10cf9a5 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
@@ -34,6 +34,7 @@ Bilinear = Bilinear
Black = Black
Blue = Blue
Cardinality = Cardinality
+Categories = Categories
CausedBy_1 = Caused by {0}
Cells = Cells
CellCount_1 = {0} cells
@@ -115,6 +116,7 @@ Geographic = Geographic
GeographicExtent = Geographic extent
GeographicIdentifier = Geographic identifier
Gray = Gray
+Grayscale = Grayscale
Green = Green
GridExtent = Grid extent
Height = Height
@@ -235,6 +237,7 @@ Trace = Trace
Transformation = Transformation
TransformationAccuracy = Transformation accuracy
Transparency = Transparency
+Transparent = Transparent
TruncatedJulian = Truncated Julian
Type = Type
TypeOfResource = Type of resource
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 c3c7670..409c3d5 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
@@ -41,6 +41,7 @@ Bilinear = Bilin\u00e9aire
Black = Noir
Blue = Bleu
Cardinality = Cardinalit\u00e9
+Categories = Cat\u00e9gories
CausedBy_1 = Caus\u00e9e par {0}
Cells = Cellules
CellCount_1 = {0} cellules
@@ -122,6 +123,7 @@ Geographic = G\u00e9ographique
GeographicExtent = \u00c9tendue g\u00e9ographique
GeographicIdentifier = Identifiant g\u00e9ographique
Gray = Gris
+Grayscale = Niveaux de gris
Green = Vert
GridExtent = \u00c9tendue de la grille
Height = Hauteur
@@ -242,6 +244,7 @@ Trace = Trace
Transformation = Transformation
TransformationAccuracy = Pr\u00e9cision de la transformation
Transparency = Transparence
+Transparent = Transparent
TruncatedJulian = Julien tronqu\u00e9
Type = Type
TypeOfResource = Type de ressource
|