在JavaFX中创建图像叠加遮罩

4
我正试图做一件简单的事情。我有一个二进制图像,我想要的是将二进制图像覆盖在彩色图像上,但是二进制图像中的白色像素应该是红色的,黑色透明的。 我非常熟悉JavaFx,但我卡在这里了。我知道可以通过使用PixelReader迭代所有像素来实现它,但我相信有更简单的方法。我尝试使用某种混合效果,但到目前为止没有成功。 我认为它应该类似于这个网站上的内容: 如何在javaFX中混合两个图像 我想要实现的示例 我想到了这个: Image image = new Image("/circle.jpg", false); ImageView iv = new ImageView(image);
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);

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

按像素读取器的解决方案如下:

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);
    }
  }
}   

干杯!

1个回答

5

你做错了什么

差异运算符不是基于像素是否设置的二进制差异,而是混合图像的RGB分量之间的差异,因此,你将得到一个多彩的覆盖层,因为混合图像的RGB分量在像素之间的差异不同。

背景

你正在尝试使用混合模式执行类似于掩码位块传输操作的操作(基本上是基于白色和黑色掩码的像素数据的OR和AND)。虽然在JavaFX 8中内置的混合中有一些技巧,但这是可能的。

你可以创建一个功能请求,以获取更多支持位块传输样式基础知识的混合API,以及公开完整的波特杜夫合成实现,就像Swing has那样,这样底层混合引擎就会更强大,可能更容易使用。

替代方案

最好的做法是在像Photoshop这样的图像编辑器中预处理您的掩码,将黑色部分转换为Alpha通道-然后您可以将掩码直接叠加在原始图像上,缺省合成模式会自动处理。
要使启用Alpha的掩码变为红色,您可以使用mask.setBlendMode(BlendMode.RED)(或者您可以在程序中使用它之前在图像编辑器中预先着色掩码)。
另一种选择是您在问题中提到的PixelReader解决方案(如果您无法预先将掩码转换为使用Alpha,则我认为这很好)。
混合操作可以在适当的硬件上进行硬件加速。因此,如果您经常这样做,使用混合可能会更快(但是您必须在大型图像上非常快地运行多个混合才能真正注意到任何性能差异)。 使用混合操作的示例解决方案 示例输出

blended

输入图像

original.jpg

original

stencil.jpg

stencil

代码

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();
    }
}

我知道你会是那个提供答案的人。 :-) - blaster
我尝试了一下你的答案。我想要合并几个模板(相同的尺寸和二进制)。我创建了一个应该作为模板的组(即多个模板图像)。类似于ImageView iv1 = new ImageView(stencil1); iv1.setBlendMode(BlendMode.ADD)等等...然后我把它们放到了一个组里。到目前为止都很好。为了创建反转的模板,我使用了上面的代码,并用之前创建的组替换了新的ImageView(stencil)。然而结果只是黑色的。有什么建议吗? - blaster
我不太明白你的评论爆破器。如果你想做一些与你原来的问题不同的事情,并且遇到了困难,那就提一个新的问题吧。 - jewelsea
这是新的问题:http://stackoverflow.com/questions/29695808/combine-images-for-overlay - blaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接