在 javafx 中创建图像叠加掩码

Create an image-overlay mask in javafx

我正在尝试做一件简单的事情。我有一个二值图像,我只想将二值图像叠加在彩色图像上,但是二值图像中的白色像素应该是红色的,黑色透明的。 我很习惯 JavaFx,但我坚持使用这个。我知道我可以通过使用 PixelReader 遍历所有像素来实现它,但我确信有更简单的方法。我尝试使用某种混合效果,但到目前为止运气不好。 我认为它应该与此类似: How to Blend two Image in javaFX

我想到了这个: 图片 image = new Image("/circle.jpg", false); ImageView iv = new ImageView(图像);

Image mask = new Image("/mask.jpg", false);
ImageView ivMask = new ImageView(mask);

Rectangle r = new Rectangle(mask.getWidth(), mask.getHeight());
r.setFill(Color.RED);

r.setBlendMode(BlendMode.MULTIPLY); // sets the white area red

Group g = new Group(ivMask, r);   // sets the white area red


// this is not working as expected
iv.setBlendMode(BlendMode.DIFFERENCE);

Group g2 = new Group(iv, g);

感谢您的任何建议! 如果您认为逐像素处理比仅创建叠加层更快,请告诉我。

pixel-reader 的解决方案是:

Pane root = new Pane();

// read the underlaying image
root.getChildren().add(new ImageView(new Image("/src.jpg")));

Image mask = new Image("/mask.jpg");
PixelReader pixelReader = mask.getPixelReader();

Canvas resultCanvas = new Canvas();
root.getChildren().add(resultCanvas);

GraphicsContext resultLayer = resultCanvas.getGraphicsContext2D();
for (int y = 0; y < mask.getHeight(); y++) {
  for (int x = 0; x < mask.getWidth(); x++) {
    if( pixelReader.getColor(x, y).equals(Color.WHITE) ){
      resultLayer.fillRect(x, y, 1.0, 1.0);
    }
  }
}   

干杯!

你做错了什么

差异运算符不是基于是否设置像素的二元差异,而是 RGB 分量的差异,因此您将得到 multi-colored 叠加层,而不是实心红色叠加层,因为混合图像的 RGB 分量的差异因像素而异。

背景

您正在尝试使用混合模式执行类似于 masked bit-blit operation 的操作(基本上,基于黑底白字遮罩的像素数据的 OR 然后 AND)。尽管在 JavaFX 8 中使用 built-in 混合有点棘手,但这是可能的。

您可以创建功能请求以在混合 API 中为 bit-blt 样式基础提供额外支持,并公开完整的 porter duff compositing implementation like Swing has 以便底层混合引擎有一点更强大,可能更容易使用。

备选方案

首选的做法是 pre-process 在像 photoshop 这样的图像编辑器中将你的蒙版转换为 alpha 通道 - 然后你可以将你的蒙版叠加在你的原始和默认合成模式将采用它。

要使启用 alpha 的蒙版变为红色,您可以只使用 mask.setBlendMode(BlendMode.RED)(或者您可以 pre-color 在图像编辑器中使用蒙版,然后再在您的程序中使用它)。

另一种选择是您在问题中使用的 PixelReader 解决方案(如果您无法 pre-convert 您的遮罩使用 alpha,我认为这很好)。

混合操作可以在适当的硬件上进行硬件加速。因此,如果您经常使用混合,可能会更快(但您必须在大图像上非常快速地进行许多混合 运行 才能真正注意到任何类型的性能差异)。

使用混合操作的示例解决方案

示例输出

输入图像

original.jpg

stencil.jpg

代码

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.*;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Blended extends Application {
    @Override
    public void start(Stage stage) {
        Image original = new Image(
            getClass().getResourceAsStream("original.jpg")
        );

        Image stencil = new Image(
            getClass().getResourceAsStream("stencil.jpg")
        );

        // first invert the stencil so that it is black on white rather than white on black.
        Rectangle whiteRect = new Rectangle(stencil.getWidth(), stencil.getHeight());
        whiteRect.setFill(Color.WHITE);
        whiteRect.setBlendMode(BlendMode.DIFFERENCE);

        Group inverted = new Group(
                new ImageView(stencil),
                whiteRect
        );

        // overlay the black portions of the inverted mask onto the image.
        inverted.setBlendMode(BlendMode.MULTIPLY);
        Group overlaidBlack = new Group(
                new ImageView(original),
                inverted
        );

        // create a new mask with a red tint (red on black).
        Rectangle redRect = new Rectangle(stencil.getWidth(), stencil.getHeight());
        redRect.setFill(Color.RED);
        redRect.setBlendMode(BlendMode.MULTIPLY);

        Group redStencil = new Group(
                new ImageView(stencil),
                redRect
        );

        // overlay the red mask on to the image.
        redStencil.setBlendMode(BlendMode.ADD);
        Group overlaidRed = new Group(
                overlaidBlack,
                redStencil
        );

        // display the original, composite image and stencil.
        HBox layout = new HBox(10);
        layout.getChildren().addAll(
                new ImageView(original),
                overlaidRed,
                new ImageView(stencil)
        );
        layout.setPadding(new Insets(10));
        stage.setScene(new Scene(layout));
        stage.show();
    }

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