javafx - 如何使用 ColorAdjust 设置目标颜色?

标签 javafx

情况

我有一个带有圆形的图像,该图像填充了从白色到黑色/透明度的径向渐变。它用作粒子,需要在其生命周期内着色。

问题

我正在使用 ColorAdjust 将不同的颜色应用于图像。问题是颜色不是我想要的样子。如果我使用绿色作为目标颜色,我会得到一个粉红色的球。

问题

如何计算 ColorAdjust 的色调值以匹配给定的目标颜色?或者有没有更好的方法来为图像着色?我不能使用形状本身,因为使用 ImageView 比使用形状快得多。

代码

这是代码。问题很可能是色调只是与当前颜色的差异:

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;


public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {

            HBox root = new HBox();

            // create alpha masked image
            Image image = createImage(createAlphaMaskedBall(200));

            // create original imageview
            ImageView original = new ImageView( image);

            // create imageview with color adjustment
            ImageView modified = new ImageView( image);

            // colorAdjust effect
            ColorAdjust colorAdjust = new ColorAdjust();

            // set saturation to 1, otherwise hue won't have an effect
            colorAdjust.setSaturation(1); 

            // define target color
            Color targetColor = Color.GREEN; 

            // calculate hue: map from [0,360] to [-1,1]; TODO: here's the problem
            double hue = map( targetColor.getHue(), 0, 360, -1, 1);

            colorAdjust.setHue(hue); 

            // apply color adjustment
            modified.setEffect(colorAdjust);

            root.getChildren().addAll( original, modified);

            Scene scene = new Scene(root,1024,500, Color.BLACK);
            primaryStage.setScene(scene);
            primaryStage.show();

    }

    /**
     * Snapshot an image out of a node, consider transparency.
     * @param node
     * @return
     */
    public static Image createImage( Node node) {

        WritableImage wi;

        SnapshotParameters parameters = new SnapshotParameters();
        parameters.setFill(Color.TRANSPARENT); 

        int imageWidth = (int) node.getBoundsInLocal().getWidth();
        int imageHeight = (int) node.getBoundsInLocal().getHeight();

        wi = new WritableImage( imageWidth, imageHeight);
        node.snapshot(parameters, wi);

        return wi;

    }   

    /**
     * Create an alpha masked ball with gradient colors from White to Black/Transparent. Used e. g. for particles.
     *
     * @param radius
     * @return
     */
    public static Node createAlphaMaskedBall( double radius) {

        Circle ball = new Circle(radius);

        RadialGradient gradient1 = new RadialGradient(0,
            .1,
            0,
            0,
            radius,
            false,
            CycleMethod.NO_CYCLE,
            new Stop(0, Color.WHITE.deriveColor(1,1,1,1)),
            new Stop(1, Color.BLACK.deriveColor(1,1,1,0)));

        ball.setFill(gradient1);

        return ball;
    }

   public static double map(double value, double start, double stop, double targetStart, double targetStop) {
        return targetStart + (targetStop - targetStart) * ((value - start) / (stop - start));
   }

    public static void main(String[] args) {
        launch(args);
    }
}

和截图:

enter image description here

非常感谢你的帮助!

编辑:

解决方案

使用 RGB slider 和 José 解决方案的工作扩展版本,适用于那些想要玩弄它的人:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Slider;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Callback;

/**
 * Modify ColorAdjust so that a given target color is matched.
 */
public class Main extends Application {

    Color[] presetColors = new Color[] {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.MAGENTA, Color.CYAN, Color.DODGERBLUE, Color.LIGHTGREY, Color.DARKGRAY, Color.BLACK, Color.WHITE, Color.BROWN};

    Color targetColor;

    Rectangle referenceColorRectangle; // display target color as reference (rgb) 
    ColorAdjust colorAdjust;
    Slider redSlider;
    Slider greenSlider;
    Slider blueSlider;
    ComboBox<Color> colorComboBox;

    @Override
    public void start(Stage primaryStage) {

            BorderPane root = new BorderPane();

            // content
            // -------------------------

            HBox content = new HBox();
            content.setStyle("-fx-background-color:black");

            // create alpha masked image
            Image image = createImage(createAlphaMaskedBall(100));

            // create original imageview
            ImageView original = new ImageView( image);

            // create imageview with color adjustment
            ImageView modified = new ImageView( image);

            // colorAdjust effect
            colorAdjust = new ColorAdjust();
            modified.setEffect(colorAdjust);

            content.getChildren().addAll( original, modified);

            // toolbar
            // -------------------------
            GridPane toolbar = new GridPane();

            // presets: show colors as rectangles in the combobox list, as hex color in the combobox selection
            colorComboBox = new ComboBox<>();
            colorComboBox.getItems().addAll( presetColors);

            colorComboBox.setOnAction(new EventHandler<ActionEvent>() {

                @Override
                public void handle(ActionEvent event) {

                    updateSliders();

                }

            });
            colorComboBox.setCellFactory(new Callback<ListView<Color>, ListCell<Color>>() {
                @Override
                public ListCell<Color> call(ListView<Color> p) {
                  return new ListCell<Color>() {
                    private final Rectangle rectangle;
                    {
                      setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                      rectangle = new Rectangle(100, 10);
                    }

                    @Override
                    protected void updateItem(Color item, boolean empty) {
                      super.updateItem(item, empty);

                      if (item == null || empty) {
                        setGraphic(null);
                      } else {
                        rectangle.setFill(item);
                        setGraphic(rectangle);
                      }
                    }
                  };
                }
              });

            // sliders, value is initialized later
            redSlider = createSlider( 0,255, 0);
            greenSlider = createSlider( 0,255, 0);
            blueSlider = createSlider( 0,255, 0);

            // reference rectangle in rgb
            referenceColorRectangle = new Rectangle( 0, 0, 100, 100);
            referenceColorRectangle.setStroke(Color.BLACK);

            // listener: get new target color from sliders and apply it 
            ChangeListener<Number> listener = new ChangeListener<Number>() {

                @Override
                public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {

                    updateColor();

                }

            }; 

            redSlider.valueProperty().addListener(listener);
            greenSlider.valueProperty().addListener(listener);
            blueSlider.valueProperty().addListener(listener);

            // add nodes to gridpane
            toolbar.addRow(0, new Label( "Preset"), colorComboBox);
            toolbar.addRow(1, new Label( "Red"), redSlider);
            toolbar.addRow(2, new Label( "Green"), greenSlider);
            toolbar.addRow(3, new Label( "Blue"), blueSlider);

            toolbar.add(referenceColorRectangle, 2, 0, 1, 4);

            // margin for all gridpane nodes
            for( Node node: toolbar.getChildren()) {
                GridPane.setMargin(node, new Insets(5,5,5,5));
            }

            // layout
            root.setTop(toolbar);
            root.setCenter(content);

            // create scene
            Scene scene = new Scene(root,800,400, Color.BLACK);

            primaryStage.setScene(scene);
            primaryStage.show();

            // set height of combobox list
            colorComboBox.lookup(".list-view").setStyle("-fx-pref-height: 200");

            // select 1st color and implicitly initialize the sliders and colors
            colorComboBox.getSelectionModel().selectFirst();

    }

    private void updateColor() {

        // create target color
        targetColor = Color.rgb( (int) redSlider.getValue(), (int) greenSlider.getValue(), (int) blueSlider.getValue());

        // update reference
        referenceColorRectangle.setFill(targetColor);

        // update colorAdjust
        // see http://stackoverflow.com/questions/31587092/how-to-use-coloradjust-to-set-a-target-color
        double hue = map( (targetColor.getHue() + 180) % 360, 0, 360, -1, 1);
        colorAdjust.setHue(hue);

        // use saturation as it is
        double saturation = targetColor.getSaturation();
        colorAdjust.setSaturation(saturation);

        // we use WHITE in the masked ball creation => inverse brightness
        double brightness = map( targetColor.getBrightness(), 0, 1, -1, 0);
        colorAdjust.setBrightness(brightness);

        // System.out.println("Target color: " + targetColor + ", hue 0..360: " + targetColor.getHue() + ", hue 0..1: " + hue);

    }

    private void updateSliders() {

        Color referenceColor = colorComboBox.getValue();

        redSlider.setValue( map( referenceColor.getRed(), 0, 1, 0, 255));
        greenSlider.setValue( map( referenceColor.getGreen(), 0, 1, 0, 255));
        blueSlider.setValue( map( referenceColor.getBlue(), 0, 1, 0, 255));

    }

    private Slider createSlider( double min, double max, double value) {

        Slider slider = new Slider( min, max, value);
        slider.setPrefWidth(600);
        slider.setShowTickLabels(true);
        slider.setShowTickMarks(true);

        return slider;
    }

    /**
     * Snapshot an image out of a node, consider transparency.
     * @param node
     * @return
     */
    public static Image createImage( Node node) {

        WritableImage wi;

        SnapshotParameters parameters = new SnapshotParameters();
        parameters.setFill(Color.TRANSPARENT); 

        int imageWidth = (int) node.getBoundsInLocal().getWidth();
        int imageHeight = (int) node.getBoundsInLocal().getHeight();

        wi = new WritableImage( imageWidth, imageHeight);
        node.snapshot(parameters, wi);

        return wi;

    }   

    /**
     * Create an alpha masked ball with gradient colors from White to Black/Transparent. Used e. g. for particles.
     *
     * @param radius
     * @return
     */
    public static Node createAlphaMaskedBall( double radius) {

        Circle ball = new Circle(radius);

        RadialGradient gradient1 = new RadialGradient(0,
            0,
            0,
            0,
            radius,
            false,
            CycleMethod.NO_CYCLE,
            new Stop(0, Color.WHITE.deriveColor(1,1,1,1)),
            new Stop(1, Color.WHITE.deriveColor(1,1,1,0)));

        ball.setFill(gradient1);

        return ball;
    }

   public static double map(double value, double start, double stop, double targetStart, double targetStop) {
        return targetStart + (targetStop - targetStart) * ((value - start) / (stop - start));
   }

    public static void main(String[] args) {
        launch(args);
    }
}

enter image description here

或解决方案的简短版本:
double hue = map( (targetColor.getHue() + 180) % 360, 0, 360, -1, 1);
colorAdjust.setHue(hue);

// use saturation as it is
double saturation = targetColor.getSaturation();
colorAdjust.setSaturation(saturation);

// we use WHITE in the masked ball creation => inverse brightness
double brightness = map( targetColor.getBrightness(), 0, 1, -1, 0);
colorAdjust.setBrightness(brightness);

最佳答案

如果您查看圆圈中心的颜色(纯白色,100% 不透明度),作为应用 ColorAdust 的结果效果,你有洋红色 #ff00ffff .

您正在应用的颜色的色调值为 120 ( Color.GREEN.getHue() ),如果您创建此颜色:

Color color = Color.hsb(120,1,1);
System.out.println(color);

它将打印 #00ff00ff ,它是绿色(100% 不透明度),在 R、G、B 方面正好是相反的颜色。

基于此,你可以反过来做:如果你想要绿色作为结果,你需要将初始颜色设置为洋红色,色调值为 300:
targetColor = Color.web("#ff00ffff");

如果您查看色轮:

color hue

你会注意到它们是截然相反的颜色,分开 180º。

因此,作为一般规则,您可以将 180º 添加到要显示的色调颜色。
double hue = map( targetColor.getHue()+180, 0, 360, -1, 1);

在我的测试中,我添加了一个 slider 来检查它:

enter image description here

关于javafx - 如何使用 ColorAdjust 设置目标颜色?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31587092/

相关文章:

combobox - JavaFX 8.0 TableView 是否存在排序错误?

java - 如何使我自己的文本字段永久成为按键监听器? java

java - 如何在 Apache tomcat 服务器启动时定期运行 javascript 函数而不是使用 ScheduledExecutorService 的 java 类?

java - webview 无法像网络浏览器一样运行

JavaFX 子级编辑无需外部 css 文件

java - 如何在JavaFx中延迟加载图像?

java - javafx 中的对话框国际化

javafx - 相当于 JavaFX 中 SplitPane 上的 setResizeWeight()?

java - 无法使用提取器实例化 ObservableList

java - 使用笛卡尔坐标将形状添加到 JavaFX Pane