Java 将灰度图像绘制到另一个BuffereImage';s阿尔法波段仅使用API

Java 将灰度图像绘制到另一个BuffereImage';s阿尔法波段仅使用API,java,graphics,bufferedimage,alpha-transparency,compositing,Java,Graphics,Bufferedimage,Alpha Transparency,Compositing,我有两个buffereImage:一个是TYPE_INT_ARGB,另一个是TYPE_BYTE_GRAY。如何仅使用API将整个彩色图像的alpha波段替换为灰度图像,而不干扰RGB值 final int width = 200; final int height = 200; final BufferedImage colorImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); final BufferedI

我有两个buffereImage:一个是TYPE_INT_ARGB,另一个是TYPE_BYTE_GRAY。如何仅使用API将整个彩色图像的alpha波段替换为灰度图像,而不干扰RGB值

final int width = 200;
final int height = 200;

final BufferedImage colorImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final BufferedImage grayscaleImg = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g = colorImg.createGraphics();
// flip some mystical switches
// g.drawImage( grayscaleImg into colorImg's alpha band )
g.dispose();
我可以通过如下方式屏蔽和复制字节手动执行此操作:

WritableRaster clrRaster = colorImg.getRaster();
DataBufferInt clrBuffer = (DataBufferInt) clrRaster.getDataBuffer();
int[] clrData = clrBuffer.getData();

WritableRaster grayRaster = grayscaleImg.getRaster();
DataBufferByte grayBuffer = (DataBufferByte) grayRaster.getDataBuffer();
byte[] grayData = grayBuffer.getData();

int pixel, alphaBits;
for(int i = 0; i < clrData.length; i++) {
    pixel = clrData[i] & 0x00ffffff;
    alphaBits = (int)grayData[i] << 24;
    clrData[i] = pixel | alphaBits;
}
writeableRaster clrRaster=colorImg.getRaster();
DataBufferInt clrBuffer=(DataBufferInt)clrRaster.getDataBuffer();
int[]clrData=clrBuffer.getData();
WritableRaster grayRaster=grayscaleMg.getRaster();
DataBufferByte grayBuffer=(DataBufferByte)grayRaster.getDataBuffer();
字节[]grayData=grayBuffer.getData();
整数像素,字母位;
for(int i=0;ialphaBits=(int)grayData[i]啊,你想用遮罩把图像的一部分剪下来,我的错

简单的解决方案——首先使用基于alpha的掩码。但我想你没有这个选择。我确实尝试过找到一个可能做到这一点的解决方案,但相反,我无意中发现了

能够产生你想要的结果

(蓝色是面板的背景色)

核心功能用于

public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask) {
    int width = image.getWidth();
    int height = image.getHeight();

    int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
    int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);

    for (int i = 0; i < imagePixels.length; i++) {
        int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha
        int alpha = maskPixels[i] << 24; // Shift blue to alpha
        imagePixels[i] = color | alpha;
    }

    image.setRGB(0, 0, width, height, imagePixels, 0, width);
}

啊,你想用遮罩把图像的一部分剪下来,我的错

简单的解决方案——首先使用基于alpha的掩码。但我想你没有这个选择。我确实尝试过找到一个可能做到这一点的解决方案,但相反,我无意中发现了

能够产生你想要的结果

(蓝色是面板的背景色)

核心功能用于

public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask) {
    int width = image.getWidth();
    int height = image.getHeight();

    int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
    int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);

    for (int i = 0; i < imagePixels.length; i++) {
        int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha
        int alpha = maskPixels[i] << 24; // Shift blue to alpha
        imagePixels[i] = color | alpha;
    }

    image.setRGB(0, 0, width, height, imagePixels, 0, width);
}

感谢并感谢满是鳗鱼的@Hovercraft提出的
BufferedImageOp
,这导致了
BandCombineOp
。当使用合适的矩阵喂养时,该课程可以操纵任何或所有波段。在不访问
DataBuffer
或操纵位的情况下,变亮、变暗、移动和复制波段

如何将图像放入alpha波段
  • 加载或绘制图像;这将成为alpha波段。图像 可以是彩色或灰度
  • 将图像转换为
    buffereImage。键入\u BYTE\u GRAY
  • 将灰度转换为
    缓冲图像。键入\u INT\u ARGB
    。颜色 标注栏将包含几乎相同的灰度副本
  • 使用
    BandCombineOp
    将色带复制到alpha色带。有关矩阵,请参见
    toAlpha()
  • 这是在编码位操作上移动/复制频带的更好方法,因为它在数学上很优雅,并且API可以利用视频加速硬件(如果存在并且由Java支持的话)

    示例Java应用程序:嵌入alpha 包括一个应用程序来演示此过程。选择一个颜色源和alpha源,应用程序会将它们合成为一个图像。显示两个源图像、中间图像和合成图像以确保其完整性。右键单击任何要保存的图像

    颜色来源:猫

    阿尔法来源:伯德

    Alpha源:绿色波段复制到Alpha波段

    复合材料

    要运行该应用程序:(1)创建一个名为
    embedalpha
    的JavaFX项目,(2)删除自动生成的.java文件的内容,(3)粘贴我的代码,以及(4)运行

    要跟踪该过程,请在方法
    handleBuildComposite()
    中放置一个断点。忽略
    appendGallery()
    toAlpha()
    是否复制带区

    /*
    Your use of any portion of the code constitutes your acceptance of full
    responsibility for all wonderful and aweful outcomes.
     */
    package embedalpha;
    
    import java.awt.AlphaComposite;
    import java.awt.Dimension;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.Toolkit;
    import java.awt.color.ColorSpace;
    import java.awt.color.ICC_ColorSpace;
    import java.awt.color.ICC_Profile;
    import java.awt.image.BandCombineOp;
    import java.awt.image.BufferedImage;
    import java.awt.image.ColorConvertOp;
    import java.io.File;
    import javafx.application.Application;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.collections.ListChangeListener;
    import javafx.collections.ObservableList;
    import javafx.embed.swing.SwingFXUtils;
    import javafx.event.ActionEvent;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.control.Alert;
    import javafx.scene.control.Button;
    import javafx.scene.control.ButtonType;
    import javafx.scene.control.ScrollPane;
    import javafx.scene.control.Tooltip;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseButton;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.FlowPane;
    import javafx.scene.layout.HBox;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.ImagePattern;
    import javafx.scene.paint.Paint;
    import javafx.stage.FileChooser;
    import javafx.stage.Stage;
    import javax.imageio.ImageIO;
    
    /**
     * Demonstrate copying/moving a color band to the alpha band within the same
     * image using only API. The specific method is toAlpha().
     * 
     * @author deskwarrior
     * @see https://stackoverflow.com/questions/47446922/draw-grayscale-image-to-another-bufferedimages-alpha-band-using-api-only
     */
    public class TheApp extends Application {
        private final int NODE_SPACING = 5;
        private final int TILE_SIZE = 40;
        private final int APP_WIDTH;
        private final int APP_HEIGHT;
    
        private final ObjectProperty<BufferedImage> colorSource = new SimpleObjectProperty<>(this, "colorSource", null);
        private final ObjectProperty<BufferedImage> alphaSource = new SimpleObjectProperty<>(this, "alphaSource", null);
        private final Paint backgroundColor;
        private final HBox gallery = new HBox(NODE_SPACING);
        private final IntegerProperty gallerySize = new SimpleIntegerProperty(this, "gallerySize", 0);
        private final Tooltip canvasTip = new Tooltip("Right-click to save image as PNG.");
        private Canvas colorSourceCanvas = null;
        private Canvas alphaSourceCanvas = null;
    
        private final FileChooser openFC = new FileChooser();
        private final FileChooser saveFC = new FileChooser();
        private File lastDirectoryVisited = null;
    
        private final RenderingHints colorHints = new RenderingHints(
                RenderingHints.KEY_COLOR_RENDERING, 
                RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        private final ICC_ColorSpace grayColorSpace;
        private final ICC_ColorSpace rgbColorSpace;
    
        private final BorderPane root = new BorderPane();
        private Stage stage = null;
    
    
        public TheApp() {
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
            APP_WIDTH = screenSize.width;
            APP_HEIGHT = screenSize.height * 2 / 3;
    
            openFC.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.gif", "*.bmp"),
                new FileChooser.ExtensionFilter("All Files", "*.*"));
    
            saveFC.setTitle("Save image as PNG");
            saveFC.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Portable network graphics", "*.png"));
    
            colorHints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            ICC_Profile grayProfile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
            grayColorSpace = new ICC_ColorSpace(grayProfile);
            ICC_Profile rgbProfile = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
            rgbColorSpace = new ICC_ColorSpace(rgbProfile);
    
            Image tile = createCheckeredTile();
            backgroundColor = new ImagePattern(tile, 0, 0, TILE_SIZE, TILE_SIZE, false);
        }
    
        /**
         * Convert a BufferedImage to another color space.
         * 
         * @param src cannot be TYPE_CUSTOM or null
         * @param dstImageType valid BufferedImage types: TYPE_BYTE_GRAY, 
         * TYPE_INT_RGB, TYPE_INT_ARGB
         * @return BufferedImage in the new color space. never null.
         * @exception NullPointerException when src is null
         * @exception IllegalArgumentException when dstImageType is not one of the
         * three accepted types
         */
        private BufferedImage toImageType(BufferedImage src, int dstImageType) {
            BufferedImage dst;
    
            if(src.getType() == dstImageType)
                dst = src;
    
            else {
                ColorSpace dstColorSpace;
    
                switch(dstImageType) {
                    case BufferedImage.TYPE_BYTE_GRAY:
                        dstColorSpace = grayColorSpace;
                        break;
    
                    case BufferedImage.TYPE_INT_RGB:
                    case BufferedImage.TYPE_INT_ARGB:
                        dstColorSpace = rgbColorSpace;
                        break;
    
                    default:
                        String msg = String.format("Conversion to BufferedImage type %d not supported.", dstImageType);
                        throw new IllegalArgumentException(msg);
                }
    
                /*
                Using ColorConvertOp because the documentation promises images
                with pre-multiplied alphas will be divided out.
    
                Another possibility is dst.createGraphics().drawImage(src). Whether 
                drawImage() divides out premultiplied alphas is unknown to me, and  
                I don't feel like tracing the method to find out.
                 */
                ColorSpace srcColorSpace = src.getColorModel().getColorSpace();
                ColorConvertOp op = new ColorConvertOp(srcColorSpace, dstColorSpace, colorHints);
                dst = new BufferedImage(src.getWidth(), src.getHeight(), dstImageType);
                op.filter(src, dst);
            }
    
            return dst;
        }
    
        /**
         * Starting point for merging color source and alpha source into one image.
         * 
         * @param e 
         */
        private void handleBuildComposite(ActionEvent e) {
            try {
                e.consume();
    
                /*
                Color source's RGB bands will become the composite's RGB bands.
                If color source is ARGB then discard the alpha band to remove
                any premultiplied alpha values.
                If color source is grayscale then convert to RGB in preparation
                for merge.
                */
                final BufferedImage colorSourceRGB = toImageType(getColorSource(), BufferedImage.TYPE_INT_RGB);
                appendGallery(colorSourceRGB, "Color source: RGB");
    
                /*
                One of alpha source's RGB bands will become the composite's alpha 
                band. 
                If alpha source is a color image, then convert to grayscale to get 
                R == G == B; at the expense of some information. If a color band 
                were copied to the alpha band without conversion, the resulting 
                alpha band wouldn't accurately represent the original color image 
                because R != G != B.
                If alpha source is a grayscale image then no change.
                 */
                final BufferedImage alphaSourceGray = toImageType(getAlphaSource(), BufferedImage.TYPE_BYTE_GRAY);
                appendGallery(alphaSourceGray, "Alpha source: grayscale");
    
                /*
                The objective of this app is to copy/move a color band into the 
                alpha band. Problem is grayscales don't have an alpha band. Convert 
                to ARGB to give it one.
                */
                final BufferedImage alphaRGBA = toImageType(alphaSourceGray, BufferedImage.TYPE_INT_ARGB);
                appendGallery(alphaRGBA, "Alpha source: grayscale to RGBA");
    
                /*
                Method toAlpha() is where the magic happens. Copy/move one of the 
                color bands into the alpha band.
                 */
                final BufferedImage trueAlpha = toAlpha(alphaRGBA);
                appendGallery(trueAlpha, "Alpha source: Green to Alpha, RGB to white");
    
                /*
                Composite the color values with the alpha values into one image.
                Copy colorSourceRGB's RGB into the composite's RGB. 
                Copy trueAlpha's alpha into the composite's alpha.
                */
                BufferedImage colorPlusAlpha = toComposite(colorSourceRGB, trueAlpha);
                appendGallery(colorPlusAlpha, "Color + alpha composite");
            }
    
            catch(Exception ex) {
                final Alert dlg = new Alert(Alert.AlertType.ERROR, ex.getMessage(), ButtonType.CLOSE);
                dlg.showAndWait();
            }
        }
    
        /**
         * Copy the green band into the alpha band.
         * 
         * @param src presumed to be some type with an alpha band. You're screwed
         * if it isn't.
         * @return a BufferedImage with the green band in the alpha band, and
         * the RGB bands set to white. Never null.
         */
        private BufferedImage toAlpha(BufferedImage src) {
            /*
            This matrix specifies which band(s) to manipulate and how. The 3-ones
            in the right-most column sets dst's RGB bands to white. The solitary
            one in the bottom row will copy the green band into dst's alpha band.
    
            Footnote: when the grayscale was converted to ARGB I expected the RGB
            bands to be identical. After some testing the bands were found to be
            *near* identical; it seems a color conversion from grayscale to 
            RGB is not as simple as a bulk memory copy. The differences are low 
            enough that no one would've noticed a difference had the either the 
            red or blue band been chosen over the green band.
            */
            final float[][] matrix = new float[][] {
                {0, 0, 0, 1},
                {0, 0, 0, 1},
                {0, 0, 0, 1},
                {0, 1, 0, 0}};
    
            BandCombineOp op = new BandCombineOp(matrix, null);
            BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
            op.filter(src.getRaster(), dst.getRaster());
            return dst;
        }
    
        /**
         * Composite color's RGB bands with alpha's alpha band into one image.
         * 
         * @param color anything except BufferedImage.TYPE_CUSTOM and null.
         * @param alpha anything with an alpha band otherwise very bad
         * @return a BufferedImage of the two inputs. never null.
         */
        private BufferedImage toComposite(BufferedImage color, BufferedImage alpha) {
            final int width = Math.max(color.getWidth(), alpha.getWidth());
            final int height = Math.max(color.getHeight(), alpha.getHeight());
            final BufferedImage dst = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = dst.createGraphics();
            g.drawImage(color, null, 0, 0);
    
            /*
            AlphaComposite.DST_IN ignores alpha's RGB bands and copies just the
            alpha band.
            */
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN));
            g.drawImage(alpha, null, 0, 0);
            g.dispose();
            return dst;
        }
    
        private Image createCheckeredTile() {
            final Canvas can = new Canvas(TILE_SIZE, TILE_SIZE);
            final GraphicsContext gc = can.getGraphicsContext2D();
            final int haf = TILE_SIZE / 2;
    
            gc.setFill(Color.DARKGRAY);
            gc.fillRect(0, 0, haf, haf); // top-left
            gc.fillRect(haf, haf, haf, haf); // bottom-right
    
    
            gc.setFill(Color.DIMGREY);
            gc.fillRect(haf, 0, haf, haf); // top-right
            gc.fillRect(0, haf, haf, haf); // bottom-left
    
            WritableImage snapshot = can.snapshot(null, null);
            return snapshot;
        }
    
        private void loadImage(String title, ObjectProperty<BufferedImage> imageObj) {
            try {
                openFC.setTitle(title);
                openFC.setInitialDirectory(lastDirectoryVisited);
                File filePath = openFC.showOpenDialog(stage);
                if(filePath != null) {
                    lastDirectoryVisited = filePath.getParentFile();
                    BufferedImage image = ImageIO.read(filePath);
                    imageObj.set(image);
                }
            }
    
            catch(Exception ex) {
                final Alert dlg = new Alert(Alert.AlertType.ERROR, ex.getMessage(), ButtonType.CLOSE);
                dlg.showAndWait();
            }
        }
    
        private void saveImage(MouseEvent e) {
            try {
                if(e.getButton() == MouseButton.SECONDARY) {
                    e.consume();
                    saveFC.setInitialDirectory(lastDirectoryVisited);
                    File filePath = saveFC.showSaveDialog(stage);
                    if(filePath != null) {
                        lastDirectoryVisited = filePath.getParentFile();
                        Canvas canvas = (Canvas) e.getSource();
                        BufferedImage img = (BufferedImage) canvas.getUserData();
                        ImageIO.write(img, "png", filePath);
                    }
                }
            }
    
            catch(Exception ex) {
                final Alert dlg = new Alert(Alert.AlertType.ERROR, ex.getMessage(), ButtonType.CLOSE);
                dlg.showAndWait();
            }
        }
    
        private Canvas appendGallery(BufferedImage src, String desc) {
            final int width = src.getWidth();
            final int height = src.getHeight();
            final Canvas canvas = new Canvas(width, height);
    
            canvas.setUserData(src);
            Tooltip.install(canvas, canvasTip);
            canvas.setOnMouseReleased(this::saveImage);
    
            final GraphicsContext gc = canvas.getGraphicsContext2D();
            final Image image = SwingFXUtils.toFXImage(src, null);
            gc.setFill(backgroundColor);
            gc.fillRect(0, 0, width, height);
            gc.drawImage(image, 0, 0);
    
            String desc2 = desc != null && !desc.isEmpty() ? desc : "?";
            if(src.isAlphaPremultiplied())
                desc2 += ", premultiplied alpha";
            gc.setStroke(Color.LIME);
            gc.strokeText(desc2, 5, 20);
    
            gallery.getChildren().add(canvas);
            return canvas;
        }
    
        private boolean neitherColorNorAlpha(Node n) {
            boolean colorOrAlpha = 
                ((colorSourceCanvas != null) && (n == colorSourceCanvas)) ||
                ((alphaSourceCanvas != null) && (n == alphaSourceCanvas));
            return !colorOrAlpha;
        }
    
        private void setupMenu() {
            Button loadColorSource = new Button("Load color source ...");
            loadColorSource.setTooltip(new Tooltip("The image's color values will become the composite's color values."));
            loadColorSource.setOnAction((e) -> {
                e.consume();
                loadImage(loadColorSource.getText(), colorSource);
                colorSourceCanvas = appendGallery(getColorSource(), "Color source");
            });
    
            Button loadAlphaSource = new Button("Load alpha source ...");
            loadAlphaSource.setTooltip(new Tooltip("The image's color values will become the composite's alpha values."));
            loadAlphaSource.setOnAction((e) -> {
                e.consume();
                loadImage(loadAlphaSource.getText(), alphaSource);
                alphaSourceCanvas = appendGallery(getAlphaSource(), "Alpha source");
            });
    
            Button buildComposite = new Button("Build composite");
            buildComposite.setTooltip(new Tooltip("Merge color and alpha into one image."));
            buildComposite.setOnAction(this::handleBuildComposite);
            buildComposite.disableProperty().bind(colorSource.isNull().or(alphaSource.isNull()));
    
            Button clearGallery = new Button("Clear gallery");
            clearGallery.setTooltip(new Tooltip("Keep the current color source and alpha source; discard other images."));
            clearGallery.disableProperty().bind(gallerySize.lessThanOrEqualTo(2));
            clearGallery.setOnAction((e) -> {
                e.consume();
                gallery.getChildren().removeIf(this::neitherColorNorAlpha);
            });
    
            FlowPane parent = new FlowPane(NODE_SPACING, NODE_SPACING);
            parent.setAlignment(Pos.CENTER);
            parent.getChildren().addAll(loadColorSource, loadAlphaSource, buildComposite, clearGallery);
            parent.setPadding(new Insets(NODE_SPACING));
            root.setTop(parent);
        }
    
        private void setupGallery() {
            gallery.setPadding(new Insets(NODE_SPACING));
            ObservableList<Node> children = gallery.getChildren();
            children.addListener(new ListChangeListener<Node>() {
                @Override
                public void onChanged(ListChangeListener.Change c) {
                    setGallerySize(children.size());
                }
            });
            ScrollPane scroll = new ScrollPane(gallery);
            scroll.setPannable(true);
            scroll.setStyle("-fx-background-color: transparent;");
            root.setCenter(scroll);
        }
    
        @Override
        public void start(Stage primaryStage) {
            stage = primaryStage;
            setupMenu();
            setupGallery();
    
            Scene scene = new Scene(root, APP_WIDTH, APP_HEIGHT);
            stage.setTitle("Embed alpha");
            stage.setScene(scene);
            stage.setResizable(true);
            stage.show();
        }
    
        public int getGallerySize() {
            return gallerySize.get();
        }
    
        public void setGallerySize(int value) {
            gallerySize.set(value);
        }
    
        public IntegerProperty gallerySizeProperty() {
            return gallerySize;
        }
    
        public BufferedImage getAlphaSource() {
            return alphaSource.get();
        }
    
        public void setAlphaSource(BufferedImage value) {
            alphaSource.set(value);
        }
    
        public ObjectProperty alphaSourceProperty() {
            return alphaSource;
        }
    
        public BufferedImage getColorSource() {
            return colorSource.get();
        }
    
        public void setColorSource(BufferedImage value) {
            colorSource.set(value);
        }
    
        public ObjectProperty colorSourceProperty() {
            return colorSource;
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    /*
    您对本准则任何部分的使用均构成您对本准则的完全接受
    对所有精彩和令人敬畏的结果负责。
    */
    包埋α;
    导入java.awt.AlphaComposite;
    导入java.awt.Dimension;
    导入java.awt.Graphics2D;
    导入java.awt.RenderingHints;
    导入java.awt.Toolkit;
    导入java.awt.color.ColorSpace;
    导入java.awt.color.ICC_颜色空间;
    导入java.awt.color.ICC_配置文件;
    导入java.awt.image.BandCombineOp;
    导入java.awt.image.buffereImage;
    导入java.awt.image.ColorConvertOp;
    导入java.io.File;
    导入javafx.application.application;
    导入javafx.beans.property.IntegerProperty;
    导入javafx.beans.property.ObjectProperty;
    导入javafx.beans.property.SimpleIntegerProperty;
    导入javafx.beans.property.SimpleObject属性;
    导入javafx.collections.ListChangeListener;
    导入javafx.collections.ObservableList;
    导入javafx.embed.swing.SwingFXUtils;
    导入javafx.event.ActionEvent;
    导入javafx.geometry.Insets;
    导入javafx.geometry.Pos;
    导入javafx.scene.Node;
    导入javafx.scene.scene;
    导入javafx.scene.canvas.canvas;
    导入javafx.scene.canvas.GraphicsContext;
    导入javafx.scene.control.Alert;
    导入javafx.scene.control.Button;
    导入javafx.scene.control.ButtonType;
    导入javafx.scene.control.ScrollPane;
    导入javafx.scene.control.Tooltip;
    导入javafx.scene.image.image;
    导入javafx.scene.image.WritableImage;
    导入javafx.scene.input.MouseButton;
    导入javafx.scene.input.MouseEvent;
    导入javafx.scene.layout.BorderPane;
    导入javafx.scene.layout.FlowPane;
    导入javafx.scene.layout.HBox;
    导入javafx.scene.paint.Color;
    导入javafx.scene.paint.ImagePattern;
    导入javafx.scene.paint.paint;
    导入javafx.stage.FileChooser;
    导入javafx.stage.stage;
    导入javax.imageio.imageio;
    /**
    *演示将色带复制/移动到同一区域内的alpha色带
    *图像仅使用API。具体方法是toAlpha()。
    * 
    *@author deskwarrior
    *@见https://stackoverflow.com/questions/47446922/draw-grayscale-image-to-another-bufferedimages-alpha-band-using-api-only
    */
    公共类TheApp扩展了应用程序{
    专用最终int节点_间距=5;
    私人最终内部瓷砖尺寸=40;
    专用最终int APP_宽度;
    私人最终内部应用程序高度;
    private final ObjectProperty colorSource=新的SimpleObject属性(此“colorSource”,null);
    private final ObjectProperty alphaSource=新的SimpleObject属性(此“alphaSource”,null);
    P
    
    public static BufferedImage grayScaleToTransparency(BufferedImage master) {
        ImageFilter filter = new RGBImageFilter() {
            public final int filterRGB(int x, int y, int rgb) {
                return (rgb << 16) & 0xFF000000;
            }
        };
    
        ImageProducer ip = new FilteredImageSource(master.getSource(), filter);
        Image img = Toolkit.getDefaultToolkit().createImage(ip);
        
        BufferedImage buffer = createCompatibleImage(img.getWidth(null), img.getHeight(null), Transparency.TRANSLUCENT);
        Graphics2D g2d = buffer.createGraphics();
        g2d.drawImage(img, 0, 0, null);
        g2d.dispose();
        
        return buffer;
    }
    
    import java.awt.AlphaComposite;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GraphicsConfiguration;
    import java.awt.GraphicsEnvironment;
    import java.awt.Image;
    import java.awt.RenderingHints;
    import java.awt.Toolkit;
    import java.awt.Transparency;
    import java.awt.image.BufferedImage;
    import java.awt.image.FilteredImageSource;
    import java.awt.image.ImageFilter;
    import java.awt.image.ImageProducer;
    import java.awt.image.RGBImageFilter;
    import java.io.File;
    import java.io.IOException;
    import javax.imageio.ImageIO;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (Exception ex) {
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public static GraphicsConfiguration getGraphicsConfiguration() {
            return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        }
    
        public static BufferedImage createCompatibleImage(int width, int height, int transparency) {
            BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
            image.coerceData(true);
            return image;
        }
    
        public static void applyQualityRenderingHints(Graphics2D g2d) {
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        }
    
        public static BufferedImage applyMask(BufferedImage master, BufferedImage mask) {
            int imgWidth = master.getWidth();
            int imgHeight = master.getHeight();
    
            BufferedImage imgMask = createCompatibleImage(imgWidth, imgHeight, Transparency.TRANSLUCENT);
            Graphics2D g2 = imgMask.createGraphics();
            applyQualityRenderingHints(g2);
    
            g2.drawImage(mask, 0, 0, null);
            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 1f));
            g2.drawImage(master, 0, 0, null);
            g2.dispose();
    
            return imgMask;
        }
    
        public static BufferedImage grayScaleToTransparency(BufferedImage master) {
            ImageFilter filter = new RGBImageFilter() {
                public final int filterRGB(int x, int y, int rgb) {
                    return (rgb << 16) & 0xFF000000;
                }
            };
    
            ImageProducer ip = new FilteredImageSource(master.getSource(), filter);
            Image img = Toolkit.getDefaultToolkit().createImage(ip);
    
            BufferedImage buffer = createCompatibleImage(img.getWidth(null), img.getHeight(null), Transparency.TRANSLUCENT);
            Graphics2D g2d = buffer.createGraphics();
            g2d.drawImage(img, 0, 0, null);
            g2d.dispose();
    
            return buffer;
        }
    
        public class TestPane extends JPanel {
    
            private BufferedImage master;
            private BufferedImage originalMask;
            private BufferedImage alphaMask;
            private BufferedImage masked;
    
            public TestPane() {
                setBackground(Color.BLUE);
                try {
                    master = ImageIO.read(new File("/Users/swhitehead/Downloads/lIceL.png"));
                    originalMask = ImageIO.read(new File("/Users/swhitehead/Downloads/MXmFp.png"));
                    alphaMask = grayScaleToTransparency(originalMask);
                    masked = applyMask(master, alphaMask);
    //                tinted = tint(master, mask);
                } catch (IOException exp) {
                    exp.printStackTrace();
                }
            }
    
            protected int desiredWidth() {
                return master.getWidth() + originalMask.getWidth() + alphaMask.getWidth() + masked.getWidth();
            }
    
            protected int desiredHeight() {
                return Math.max(Math.max(Math.max(master.getHeight(), originalMask.getHeight()), alphaMask.getHeight()), masked.getHeight());
            }
    
            @Override
            public Dimension getPreferredSize() {
                Dimension size = super.getPreferredSize();
                if (master != null && originalMask != null) {
                    size = new Dimension(desiredWidth(),
                            desiredHeight());
                }
                return size;
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                int x = (getWidth() - desiredWidth()) / 2;
                int y = (getHeight() - master.getHeight()) / 2;
                g.drawImage(master, x, y, this);
    
                x += originalMask.getWidth();
                y = (getHeight() - originalMask.getHeight()) / 2;
                g.drawImage(originalMask, x, y, this);
    
                x += alphaMask.getWidth();
                y = (getHeight() - alphaMask.getHeight()) / 2;
                g.drawImage(alphaMask, x, y, this);
    
                x += masked.getWidth();
                y = (getHeight() - masked.getHeight()) / 2;
                g.drawImage(masked, x, y, this);
            }
    
        }
    
    }
    
    /*
    Your use of any portion of the code constitutes your acceptance of full
    responsibility for all wonderful and aweful outcomes.
     */
    package embedalpha;
    
    import java.awt.AlphaComposite;
    import java.awt.Dimension;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.Toolkit;
    import java.awt.color.ColorSpace;
    import java.awt.color.ICC_ColorSpace;
    import java.awt.color.ICC_Profile;
    import java.awt.image.BandCombineOp;
    import java.awt.image.BufferedImage;
    import java.awt.image.ColorConvertOp;
    import java.io.File;
    import javafx.application.Application;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.collections.ListChangeListener;
    import javafx.collections.ObservableList;
    import javafx.embed.swing.SwingFXUtils;
    import javafx.event.ActionEvent;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.control.Alert;
    import javafx.scene.control.Button;
    import javafx.scene.control.ButtonType;
    import javafx.scene.control.ScrollPane;
    import javafx.scene.control.Tooltip;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseButton;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.FlowPane;
    import javafx.scene.layout.HBox;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.ImagePattern;
    import javafx.scene.paint.Paint;
    import javafx.stage.FileChooser;
    import javafx.stage.Stage;
    import javax.imageio.ImageIO;
    
    /**
     * Demonstrate copying/moving a color band to the alpha band within the same
     * image using only API. The specific method is toAlpha().
     * 
     * @author deskwarrior
     * @see https://stackoverflow.com/questions/47446922/draw-grayscale-image-to-another-bufferedimages-alpha-band-using-api-only
     */
    public class TheApp extends Application {
        private final int NODE_SPACING = 5;
        private final int TILE_SIZE = 40;
        private final int APP_WIDTH;
        private final int APP_HEIGHT;
    
        private final ObjectProperty<BufferedImage> colorSource = new SimpleObjectProperty<>(this, "colorSource", null);
        private final ObjectProperty<BufferedImage> alphaSource = new SimpleObjectProperty<>(this, "alphaSource", null);
        private final Paint backgroundColor;
        private final HBox gallery = new HBox(NODE_SPACING);
        private final IntegerProperty gallerySize = new SimpleIntegerProperty(this, "gallerySize", 0);
        private final Tooltip canvasTip = new Tooltip("Right-click to save image as PNG.");
        private Canvas colorSourceCanvas = null;
        private Canvas alphaSourceCanvas = null;
    
        private final FileChooser openFC = new FileChooser();
        private final FileChooser saveFC = new FileChooser();
        private File lastDirectoryVisited = null;
    
        private final RenderingHints colorHints = new RenderingHints(
                RenderingHints.KEY_COLOR_RENDERING, 
                RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        private final ICC_ColorSpace grayColorSpace;
        private final ICC_ColorSpace rgbColorSpace;
    
        private final BorderPane root = new BorderPane();
        private Stage stage = null;
    
    
        public TheApp() {
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
            APP_WIDTH = screenSize.width;
            APP_HEIGHT = screenSize.height * 2 / 3;
    
            openFC.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.gif", "*.bmp"),
                new FileChooser.ExtensionFilter("All Files", "*.*"));
    
            saveFC.setTitle("Save image as PNG");
            saveFC.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Portable network graphics", "*.png"));
    
            colorHints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            ICC_Profile grayProfile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
            grayColorSpace = new ICC_ColorSpace(grayProfile);
            ICC_Profile rgbProfile = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
            rgbColorSpace = new ICC_ColorSpace(rgbProfile);
    
            Image tile = createCheckeredTile();
            backgroundColor = new ImagePattern(tile, 0, 0, TILE_SIZE, TILE_SIZE, false);
        }
    
        /**
         * Convert a BufferedImage to another color space.
         * 
         * @param src cannot be TYPE_CUSTOM or null
         * @param dstImageType valid BufferedImage types: TYPE_BYTE_GRAY, 
         * TYPE_INT_RGB, TYPE_INT_ARGB
         * @return BufferedImage in the new color space. never null.
         * @exception NullPointerException when src is null
         * @exception IllegalArgumentException when dstImageType is not one of the
         * three accepted types
         */
        private BufferedImage toImageType(BufferedImage src, int dstImageType) {
            BufferedImage dst;
    
            if(src.getType() == dstImageType)
                dst = src;
    
            else {
                ColorSpace dstColorSpace;
    
                switch(dstImageType) {
                    case BufferedImage.TYPE_BYTE_GRAY:
                        dstColorSpace = grayColorSpace;
                        break;
    
                    case BufferedImage.TYPE_INT_RGB:
                    case BufferedImage.TYPE_INT_ARGB:
                        dstColorSpace = rgbColorSpace;
                        break;
    
                    default:
                        String msg = String.format("Conversion to BufferedImage type %d not supported.", dstImageType);
                        throw new IllegalArgumentException(msg);
                }
    
                /*
                Using ColorConvertOp because the documentation promises images
                with pre-multiplied alphas will be divided out.
    
                Another possibility is dst.createGraphics().drawImage(src). Whether 
                drawImage() divides out premultiplied alphas is unknown to me, and  
                I don't feel like tracing the method to find out.
                 */
                ColorSpace srcColorSpace = src.getColorModel().getColorSpace();
                ColorConvertOp op = new ColorConvertOp(srcColorSpace, dstColorSpace, colorHints);
                dst = new BufferedImage(src.getWidth(), src.getHeight(), dstImageType);
                op.filter(src, dst);
            }
    
            return dst;
        }
    
        /**
         * Starting point for merging color source and alpha source into one image.
         * 
         * @param e 
         */
        private void handleBuildComposite(ActionEvent e) {
            try {
                e.consume();
    
                /*
                Color source's RGB bands will become the composite's RGB bands.
                If color source is ARGB then discard the alpha band to remove
                any premultiplied alpha values.
                If color source is grayscale then convert to RGB in preparation
                for merge.
                */
                final BufferedImage colorSourceRGB = toImageType(getColorSource(), BufferedImage.TYPE_INT_RGB);
                appendGallery(colorSourceRGB, "Color source: RGB");
    
                /*
                One of alpha source's RGB bands will become the composite's alpha 
                band. 
                If alpha source is a color image, then convert to grayscale to get 
                R == G == B; at the expense of some information. If a color band 
                were copied to the alpha band without conversion, the resulting 
                alpha band wouldn't accurately represent the original color image 
                because R != G != B.
                If alpha source is a grayscale image then no change.
                 */
                final BufferedImage alphaSourceGray = toImageType(getAlphaSource(), BufferedImage.TYPE_BYTE_GRAY);
                appendGallery(alphaSourceGray, "Alpha source: grayscale");
    
                /*
                The objective of this app is to copy/move a color band into the 
                alpha band. Problem is grayscales don't have an alpha band. Convert 
                to ARGB to give it one.
                */
                final BufferedImage alphaRGBA = toImageType(alphaSourceGray, BufferedImage.TYPE_INT_ARGB);
                appendGallery(alphaRGBA, "Alpha source: grayscale to RGBA");
    
                /*
                Method toAlpha() is where the magic happens. Copy/move one of the 
                color bands into the alpha band.
                 */
                final BufferedImage trueAlpha = toAlpha(alphaRGBA);
                appendGallery(trueAlpha, "Alpha source: Green to Alpha, RGB to white");
    
                /*
                Composite the color values with the alpha values into one image.
                Copy colorSourceRGB's RGB into the composite's RGB. 
                Copy trueAlpha's alpha into the composite's alpha.
                */
                BufferedImage colorPlusAlpha = toComposite(colorSourceRGB, trueAlpha);
                appendGallery(colorPlusAlpha, "Color + alpha composite");
            }
    
            catch(Exception ex) {
                final Alert dlg = new Alert(Alert.AlertType.ERROR, ex.getMessage(), ButtonType.CLOSE);
                dlg.showAndWait();
            }
        }
    
        /**
         * Copy the green band into the alpha band.
         * 
         * @param src presumed to be some type with an alpha band. You're screwed
         * if it isn't.
         * @return a BufferedImage with the green band in the alpha band, and
         * the RGB bands set to white. Never null.
         */
        private BufferedImage toAlpha(BufferedImage src) {
            /*
            This matrix specifies which band(s) to manipulate and how. The 3-ones
            in the right-most column sets dst's RGB bands to white. The solitary
            one in the bottom row will copy the green band into dst's alpha band.
    
            Footnote: when the grayscale was converted to ARGB I expected the RGB
            bands to be identical. After some testing the bands were found to be
            *near* identical; it seems a color conversion from grayscale to 
            RGB is not as simple as a bulk memory copy. The differences are low 
            enough that no one would've noticed a difference had the either the 
            red or blue band been chosen over the green band.
            */
            final float[][] matrix = new float[][] {
                {0, 0, 0, 1},
                {0, 0, 0, 1},
                {0, 0, 0, 1},
                {0, 1, 0, 0}};
    
            BandCombineOp op = new BandCombineOp(matrix, null);
            BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
            op.filter(src.getRaster(), dst.getRaster());
            return dst;
        }
    
        /**
         * Composite color's RGB bands with alpha's alpha band into one image.
         * 
         * @param color anything except BufferedImage.TYPE_CUSTOM and null.
         * @param alpha anything with an alpha band otherwise very bad
         * @return a BufferedImage of the two inputs. never null.
         */
        private BufferedImage toComposite(BufferedImage color, BufferedImage alpha) {
            final int width = Math.max(color.getWidth(), alpha.getWidth());
            final int height = Math.max(color.getHeight(), alpha.getHeight());
            final BufferedImage dst = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = dst.createGraphics();
            g.drawImage(color, null, 0, 0);
    
            /*
            AlphaComposite.DST_IN ignores alpha's RGB bands and copies just the
            alpha band.
            */
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN));
            g.drawImage(alpha, null, 0, 0);
            g.dispose();
            return dst;
        }
    
        private Image createCheckeredTile() {
            final Canvas can = new Canvas(TILE_SIZE, TILE_SIZE);
            final GraphicsContext gc = can.getGraphicsContext2D();
            final int haf = TILE_SIZE / 2;
    
            gc.setFill(Color.DARKGRAY);
            gc.fillRect(0, 0, haf, haf); // top-left
            gc.fillRect(haf, haf, haf, haf); // bottom-right
    
    
            gc.setFill(Color.DIMGREY);
            gc.fillRect(haf, 0, haf, haf); // top-right
            gc.fillRect(0, haf, haf, haf); // bottom-left
    
            WritableImage snapshot = can.snapshot(null, null);
            return snapshot;
        }
    
        private void loadImage(String title, ObjectProperty<BufferedImage> imageObj) {
            try {
                openFC.setTitle(title);
                openFC.setInitialDirectory(lastDirectoryVisited);
                File filePath = openFC.showOpenDialog(stage);
                if(filePath != null) {
                    lastDirectoryVisited = filePath.getParentFile();
                    BufferedImage image = ImageIO.read(filePath);
                    imageObj.set(image);
                }
            }
    
            catch(Exception ex) {
                final Alert dlg = new Alert(Alert.AlertType.ERROR, ex.getMessage(), ButtonType.CLOSE);
                dlg.showAndWait();
            }
        }
    
        private void saveImage(MouseEvent e) {
            try {
                if(e.getButton() == MouseButton.SECONDARY) {
                    e.consume();
                    saveFC.setInitialDirectory(lastDirectoryVisited);
                    File filePath = saveFC.showSaveDialog(stage);
                    if(filePath != null) {
                        lastDirectoryVisited = filePath.getParentFile();
                        Canvas canvas = (Canvas) e.getSource();
                        BufferedImage img = (BufferedImage) canvas.getUserData();
                        ImageIO.write(img, "png", filePath);
                    }
                }
            }
    
            catch(Exception ex) {
                final Alert dlg = new Alert(Alert.AlertType.ERROR, ex.getMessage(), ButtonType.CLOSE);
                dlg.showAndWait();
            }
        }
    
        private Canvas appendGallery(BufferedImage src, String desc) {
            final int width = src.getWidth();
            final int height = src.getHeight();
            final Canvas canvas = new Canvas(width, height);
    
            canvas.setUserData(src);
            Tooltip.install(canvas, canvasTip);
            canvas.setOnMouseReleased(this::saveImage);
    
            final GraphicsContext gc = canvas.getGraphicsContext2D();
            final Image image = SwingFXUtils.toFXImage(src, null);
            gc.setFill(backgroundColor);
            gc.fillRect(0, 0, width, height);
            gc.drawImage(image, 0, 0);
    
            String desc2 = desc != null && !desc.isEmpty() ? desc : "?";
            if(src.isAlphaPremultiplied())
                desc2 += ", premultiplied alpha";
            gc.setStroke(Color.LIME);
            gc.strokeText(desc2, 5, 20);
    
            gallery.getChildren().add(canvas);
            return canvas;
        }
    
        private boolean neitherColorNorAlpha(Node n) {
            boolean colorOrAlpha = 
                ((colorSourceCanvas != null) && (n == colorSourceCanvas)) ||
                ((alphaSourceCanvas != null) && (n == alphaSourceCanvas));
            return !colorOrAlpha;
        }
    
        private void setupMenu() {
            Button loadColorSource = new Button("Load color source ...");
            loadColorSource.setTooltip(new Tooltip("The image's color values will become the composite's color values."));
            loadColorSource.setOnAction((e) -> {
                e.consume();
                loadImage(loadColorSource.getText(), colorSource);
                colorSourceCanvas = appendGallery(getColorSource(), "Color source");
            });
    
            Button loadAlphaSource = new Button("Load alpha source ...");
            loadAlphaSource.setTooltip(new Tooltip("The image's color values will become the composite's alpha values."));
            loadAlphaSource.setOnAction((e) -> {
                e.consume();
                loadImage(loadAlphaSource.getText(), alphaSource);
                alphaSourceCanvas = appendGallery(getAlphaSource(), "Alpha source");
            });
    
            Button buildComposite = new Button("Build composite");
            buildComposite.setTooltip(new Tooltip("Merge color and alpha into one image."));
            buildComposite.setOnAction(this::handleBuildComposite);
            buildComposite.disableProperty().bind(colorSource.isNull().or(alphaSource.isNull()));
    
            Button clearGallery = new Button("Clear gallery");
            clearGallery.setTooltip(new Tooltip("Keep the current color source and alpha source; discard other images."));
            clearGallery.disableProperty().bind(gallerySize.lessThanOrEqualTo(2));
            clearGallery.setOnAction((e) -> {
                e.consume();
                gallery.getChildren().removeIf(this::neitherColorNorAlpha);
            });
    
            FlowPane parent = new FlowPane(NODE_SPACING, NODE_SPACING);
            parent.setAlignment(Pos.CENTER);
            parent.getChildren().addAll(loadColorSource, loadAlphaSource, buildComposite, clearGallery);
            parent.setPadding(new Insets(NODE_SPACING));
            root.setTop(parent);
        }
    
        private void setupGallery() {
            gallery.setPadding(new Insets(NODE_SPACING));
            ObservableList<Node> children = gallery.getChildren();
            children.addListener(new ListChangeListener<Node>() {
                @Override
                public void onChanged(ListChangeListener.Change c) {
                    setGallerySize(children.size());
                }
            });
            ScrollPane scroll = new ScrollPane(gallery);
            scroll.setPannable(true);
            scroll.setStyle("-fx-background-color: transparent;");
            root.setCenter(scroll);
        }
    
        @Override
        public void start(Stage primaryStage) {
            stage = primaryStage;
            setupMenu();
            setupGallery();
    
            Scene scene = new Scene(root, APP_WIDTH, APP_HEIGHT);
            stage.setTitle("Embed alpha");
            stage.setScene(scene);
            stage.setResizable(true);
            stage.show();
        }
    
        public int getGallerySize() {
            return gallerySize.get();
        }
    
        public void setGallerySize(int value) {
            gallerySize.set(value);
        }
    
        public IntegerProperty gallerySizeProperty() {
            return gallerySize;
        }
    
        public BufferedImage getAlphaSource() {
            return alphaSource.get();
        }
    
        public void setAlphaSource(BufferedImage value) {
            alphaSource.set(value);
        }
    
        public ObjectProperty alphaSourceProperty() {
            return alphaSource;
        }
    
        public BufferedImage getColorSource() {
            return colorSource.get();
        }
    
        public void setColorSource(BufferedImage value) {
            colorSource.set(value);
        }
    
        public ObjectProperty colorSourceProperty() {
            return colorSource;
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            launch(args);
        }
    }