是否可以向已经具有 ImagePattern 填充的 SVGPath 添加 LinearGradient 填充?

Is it possible to add a LinearGradient fill to an SVGPath that already has an ImagePattern Fill?

在我的主应用程序中,我将一些 SVGPath 添加到 XYChart。有时他们有一个 ImagePattern 填充,现在需要一个 LinearGradient 填充。 ImagePattern 填充是一个交叉影线,它需要用 LinearGradient 着色,就好像它是一个应用了 LinearGradient 的实心矩形一样。 SVGPath 也有一个虚线轮廓,LinearGradient 应该填充虚线轮廓和 ImagePattern 填充,因为它们是同一形状的一部分。

我已经编写了一些示例代码来展示我的位置。这会在创建交叉影线时为其着色并且看起来不错,但不是我上面描述的效果,因为 ImagePattern 中的每个交叉都单独应用了 LinearGradient。理想情况下,一旦应用了 ImagePattern 填充,LinearGradient 就会应用到最终的 SVGPath。 我也尝试过使用 Blend 和 ColorInput 的一些效果,但没有设法接近解决方案。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.ImagePattern;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Paint;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Line;
import javafx.scene.shape.SVGPath;
import javafx.stage.Stage;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            List<Color> colors = Arrays.asList(Color.RED, Color.YELLOW, Color.GREEN);
            ArrayList<Stop> stops = new ArrayList<>(colors.size() * 2);

            for (int i = 0; i < colors.size(); i++) {
                stops.add(new Stop(getOffset(i, colors.size()), colors.get(i)));
                stops.add(new Stop(getOffset(i + 1, colors.size()), colors.get(i)));
            }

            LinearGradient lg = new LinearGradient(0, 0, 20, 20, false, CycleMethod.REPEAT, stops);

            SVGPath svgPath = new SVGPath();
            svgPath.setContent("M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z");

            Image hatch = createCrossHatch(lg);
            ImagePattern pattern = new ImagePattern(hatch, 0, 0, 10, 10, false);

            svgPath.setFill(pattern);
            svgPath.setStroke(lg);

            BorderPane root = new BorderPane();
            root.setCenter(svgPath);
            Scene scene = new Scene(root, 400, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected static Image createCrossHatch(Paint paint) {
        Pane pane = new Pane();
        pane.setPrefSize(20, 20);
        pane.setStyle("-fx-background-color: transparent;");
        Line fw = new Line(-5, -5, 25, 25);
        Line bw = new Line(-5, 25, 25, -5);
        fw.setStroke(paint);
        bw.setStroke(paint);
        fw.setStrokeWidth(3);
        bw.setStrokeWidth(3);
        pane.getChildren().addAll(fw, bw);
        new Scene(pane);
        SnapshotParameters sp = new SnapshotParameters();
        sp.setFill(Color.TRANSPARENT);

        return pane.snapshot(sp, null);
    }

    private double getOffset(double i, int count) {
        return (((double) 1) / (double) count * (double) i);
    }

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

如果您 运行 提供的代码,您会看到它画了一根狗骨头。虚线轮廓的 lineargradient 颜色应继续通过交叉影线 ImagePattern 填充。我知道为什么孵化的 ImagePattern 是彩色的,但这是我目前最好的折衷方案。如前所述,我希望能够在应用 ImagePattern 填充后将 LinearGradient 填充应用于整个形状,这样 LinearGradient 对整个形状的影响相同。

谢谢

没有直接的方法在一个节点上应用和合并两种绘画。我们可以通过 css 使用背景颜色来叠加许多不同的 paints(例如纯色、线性渐变甚至图像图案),但这不会合并。

所以为了组合两种不同的颜料,一方面是线性渐变,另一方面是图案填充,我们需要将它们应用到两个节点,并在两种颜料之间使用混合效果。

根据发布的代码,这是具有线性渐变的 SVGPath:

@Override
public void start(Stage primaryStage) {
    Node base = getNodeWithGradient();

    BorderPane root = new BorderPane();
    Group group = new Group(base);
    root.setCenter(group);

    Scene scene = new Scene(root, 400, 200);
    primaryStage.setScene(scene);
    primaryStage.show();
}

private SVGPath getNodeWithGradient() {
    List<Color> colors = Arrays.asList(Color.RED, Color.YELLOW, Color.GREEN);
    ArrayList<Stop> stops = new ArrayList<>(colors.size() * 2);

    for (int i = 0; i < colors.size(); i++) {
        stops.add(new Stop(getOffset(i, colors.size()), colors.get(i)));
        stops.add(new Stop(getOffset(i + 1, colors.size()), colors.get(i)));
    }

    LinearGradient lg = new LinearGradient(0, 0, 20, 20, false, CycleMethod.REPEAT, stops);

    SVGPath svgPath = getSVGPath();
    svgPath.setFill(lg);
    svgPath.setStroke(lg);

    return svgPath;
}

private SVGPath getSVGPath() {
    SVGPath svgPath = new SVGPath();
    svgPath.setContent("M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z");
    return svgPath;
}

private double getOffset(double i, int count) {
    return (((double) 1) / (double) count * (double) i);
}

虽然这是带有图像图案填充的 SVGPath:

@Override
public void start(Stage primaryStage) {
    Node overlay = getNodeWithPattern();

    BorderPane root = new BorderPane();
    Group group = new Group(overlay);

    root.setCenter(group);
    Scene scene = new Scene(root, 400, 200);
    primaryStage.setScene(scene);
    primaryStage.show();
}

private SVGPath getNodeWithPattern() {
    Image hatch = createCrossHatch();
    ImagePattern pattern = new ImagePattern(hatch, 0, 0, 10, 10, false);

    SVGPath svgPath = getSVGPath();
    svgPath.setFill(pattern);

    return svgPath;
}

private SVGPath getSVGPath() {
    SVGPath svgPath = new SVGPath();
    svgPath.setContent("M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z");
    return svgPath;
}

private static Image createCrossHatch() {
    Pane pane = new Pane();
    pane.setPrefSize(20, 20);
    Line fw = new Line(-5, -5, 25, 25);
    Line bw = new Line(-5, 25, 25, -5);
    fw.setStroke(Color.BLACK);
    bw.setStroke(Color.BLACK);
    fw.setStrokeWidth(3);
    bw.setStrokeWidth(3);
    pane.getChildren().addAll(fw, bw);
    new Scene(pane);
    SnapshotParameters sp = new SnapshotParameters();
    return pane.snapshot(sp, null);
}

现在的诀窍是合并两个 SVGPath 节点,为顶部节点添加混合模式。

根据 JavaDoc BlendMode.ADD:

The color and alpha components from the top input are added to those from the bottom input.

@Override
public void start(Stage primaryStage) {
    Node base = getNodeWithGradient();
    Node overlay = getNodeWithPattern();
    overlay.setBlendMode(BlendMode.ADD);

    BorderPane root = new BorderPane();
    Group group = new Group(base, overlay);

    root.setCenter(group);
    Scene scene = new Scene(root, 400, 200);
    primaryStage.setScene(scene);
    primaryStage.show();
}

private SVGPath getNodeWithGradient() {
    List<Color> colors = Arrays.asList(Color.RED, Color.YELLOW, Color.GREEN);
    ArrayList<Stop> stops = new ArrayList<>(colors.size() * 2);

    for (int i = 0; i < colors.size(); i++) {
        stops.add(new Stop(getOffset(i, colors.size()), colors.get(i)));
        stops.add(new Stop(getOffset(i + 1, colors.size()), colors.get(i)));
    }

    LinearGradient lg = new LinearGradient(0, 0, 20, 20, false, CycleMethod.REPEAT, stops);

    SVGPath svgPath = getSVGPath();
    svgPath.setFill(lg);
    svgPath.setStroke(lg);
    return svgPath;
}

private SVGPath getNodeWithPattern() {
    Image hatch = createCrossHatch();
    ImagePattern pattern = new ImagePattern(hatch, 0, 0, 10, 10, false);

    SVGPath svgPath = getSVGPath();
    svgPath.setFill(pattern);
    return svgPath;
}

private SVGPath getSVGPath() {
    SVGPath svgPath = new SVGPath();
    svgPath.setContent("M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z");
    return svgPath;
}

private static Image createCrossHatch() {
    Pane pane = new Pane();
    pane.setPrefSize(20, 20);
    Line fw = new Line(-5, -5, 25, 25);
    Line bw = new Line(-5, 25, 25, -5);
    fw.setStroke(Color.BLACK);
    bw.setStroke(Color.BLACK);
    fw.setStrokeWidth(3);
    bw.setStrokeWidth(3);
    pane.getChildren().addAll(fw, bw);
    new Scene(pane);
    SnapshotParameters sp = new SnapshotParameters();
    return pane.snapshot(sp, null);
}

private double getOffset(double i, int count) {
    return (((double) 1) / (double) count * (double) i);
}

我们得到了想要的结果: