java - 如何使用单独的 alpha 栅格创建 BufferedImage

标签 java swt awt bufferedimage raster

动机: 我的目标是以最有效的方式将 AWT BufferedImage 转换为 SWT ImageData。这个问题的典型答案是整个图片的逐像素转换,即 O(n^2) 复杂度。如果他们可以按原样交换整个像素矩阵,效率会更高。 BufferedImage 在确定颜色和 alpha 的详细编码方式方面似乎非常灵活。

为了向您提供更广泛的背景,我使用 Apache Batik 编写了一个 SVG 图标按需光栅化器,但它适用于 SWT (Eclipse) 应用程序。 Batik 仅呈现到 java.awt.image.BufferedImage,但 SWT 组件需要 org.eclipse.swt.graphics.Image

它们的支持光栅对象:java.awt.image.Rasterorg.eclipse.swt.graphics.ImageData代表完全相同的东西,它们只是一个包装器表示像素的字节值的二维数组。如果我可以使用其中之一来使用颜色编码,瞧,我可以按原样重用支持数组。

我已经走了很远了,这有效:

// defined blank "canvas" for Batik Transcoder for SVG to be rasterized there
public BufferedImage createCanvasForBatik(int w, int h) {
    new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
}

// convert AWT's BufferedImage  to SWT's ImageData to be made into SWT Image later
public ImageData convertToSWT(BufferedImage bufferedImage) {
    DataBuffer db = bufferedImage.getData().getDataBuffer();
    byte[] matrix = ((DataBufferByte) db).getData();

    PaletteData palette =
            new PaletteData(0x0000FF, 0x00FF00, 0xFF0000); // BRG model

    // the last argument contains the byte[] with the image data
    int w = bufferedImage.getWidth(); 
    int h = bufferedImage.getHeight();

    ImageData swtimgdata = new ImageData(w, h, 32, palette);
    swtimgdata.data = matrix; // ImageData has all field public!!

    // ImageData swtimgdata = new ImageData(w, h, 32, palette, 4, matrix);  ..also works
    return swtimgdata;
}

除了透明度之外,一切都有效:(

看起来ImageData需要(总是?)alpha是一个单独的栅格,请参阅颜色栅格中的ImageData.alphaData,请参阅ImageData.data;两者都是 byte[] 类型。

有没有办法让ImageData接受ARGB模型?那是alpha与其他颜色混合吗?我怀疑,所以我走了另一条路。使 BufferedImage 为颜色和 Alpha 使用单独的数组(也称为栅格或“带”)。 ComponentColorModelBandedRaster 似乎正是为了这些事情而设计的。

到目前为止我到达这里:

public BufferedImage createCanvasForBatik(int w, int h) {
    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
    int[] nBits = {8, 8, 8, 8}; // ??
    ComponentColorModel colorModel = new ComponentColorModel(cs, nBits, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
    WritableRaster raster = Raster.createBandedRaster(
        DataBuffer.TYPE_BYTE, w, h, 4, new Point(0,0));
    isPremultiplied = false;
    properties = null;
    return new BufferedImage(colorModel, raster, isPremultiplied, properties);
}

这会为 Alpha 创建一个单独的栅格(波段),同时也会为每种颜色单独创建一个栅格(波段),因此我最终会得到 4 个波段(4 个栅格),这对于 SWT 图像来说再次无法使用。 是否可以创建具有 2 个波段的带状栅格:一个用于 RGB 或 BRG 颜色,一个仅用于 Alpha?

最佳答案

我不太了解 SWT,但根据我对 API 文档的理解,以下内容应该可行:

诀窍是使用自定义的 DataBuffer 实现,该实现伪装成“带状”缓冲区,但在内部使用交错 RGB 和单独的 alpha 数组的组合进行存储。这与标准 BandedSampleModel 配合得很好。您将失去任何通常应用于使用此模型的 BufferedImage 的特殊(硬件)优化的机会,但这并不重要,因为您无论如何都使用 SWT 进行显示。

我建议您首先创建 SWT 图像,然后将 SWT 图像中的颜色和 alpha 数组“包装”到自定义数据缓冲区中。如果你这样做,Batik应该直接渲染到你的SWT图像,然后你可以扔掉BufferedImage(如果这不切实际,你当然可以反过来做,如下所示)好吧,但您可能需要公开下面的自定义数据缓冲区类的内部数组,以创建 SWT 图像)。

代码(重要部分是 SWTDataBuffer 类和 createImage 方法):

public class SplitDataBufferTest {
    /** Custom DataBuffer implementation using separate arrays for RGB and alpha.*/
    public static class SWTDataBuffer extends DataBuffer {
        private final byte[] rgb; // RGB or BGR interleaved
        private final byte[] alpha;

        public SWTDataBuffer(byte[] rgb, byte[] alpha) {
            super(DataBuffer.TYPE_BYTE, alpha.length, 4); // Masquerade as banded data buffer
            if (alpha.length * 3 != rgb.length) {
                throw new IllegalArgumentException("Bad RGB/alpha array lengths");
            }
            this.rgb = rgb;
            this.alpha = alpha;
        }

        @Override
        public int getElem(int bank, int i) {
            switch (bank) {
                case 0:
                case 1:
                case 2:
                    return rgb[i * 3 + bank];
                case 3:
                    return alpha[i];
            }
            throw new IndexOutOfBoundsException(String.format("bank %d >= number of banks, %d", bank, getNumBanks()));
        }

        @Override
        public void setElem(int bank, int i, int val) {
            switch (bank) {
                case 0:
                case 1:
                case 2:
                    rgb[i * 3 + bank] = (byte) val;
                    return;
                case 3:
                    alpha[i] = (byte) val;
                    return;
            }

            throw new IndexOutOfBoundsException(String.format("bank %d >= number of banks, %d", bank, getNumBanks()));
        }
    }

    public static void main(String[] args) {
        // These are given from your SWT image
        int w = 300;
        int h = 200;
        byte[] rgb = new byte[w * h * 3];
        byte[] alpha = new byte[w * h];

        // Create an empty BufferedImage around the SWT image arrays
        BufferedImage image = createImage(w, h, rgb, alpha);

        // Just to demonstrate that it works
        System.out.println("image: " + image);
        paintSomething(image);
        showIt(image);
    }

    private static BufferedImage createImage(int w, int h, byte[] rgb, byte[] alpha) {
        DataBuffer buffer = new SWTDataBuffer(rgb, alpha);
        // SampleModel sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, w, h, 4); // If SWT data is RGB, you can use simpler constructor
        SampleModel sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, w, h, w,
                new int[] {2, 1, 0, 3}, // Band indices for BGRA
                new int[] {0, 0, 0, 0});

        WritableRaster raster = Raster.createWritableRaster(sampleModel, buffer, null);
        ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
        return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
    }

    private static void showIt(final BufferedImage image) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

                JLabel label = new JLabel(new ImageIcon(image));
                label.setOpaque(true);
                label.setBackground(Color.GRAY);
                frame.add(label);

                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    private static void paintSomething(BufferedImage image) {
        int w = image.getWidth();
        int h = image.getHeight();
        int qw = w / 4;
        int qh = h / 4;

        Graphics2D g = image.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.ORANGE);
        g.fillOval(0, 0, w, h);

        g.setColor(Color.RED);
        g.fillRect(5, 5, qw, qh);
        g.setColor(Color.WHITE);
        g.drawString("R", 5, 30);

        g.setColor(Color.GREEN);
        g.fillRect(5 + 5 + qw, 5, qw, qh);
        g.setColor(Color.BLACK);
        g.drawString("G", 5 + 5 + qw, 30);

        g.setColor(Color.BLUE);
        g.fillRect(5 + (5 + qw) * 2, 5, qw, qh);
        g.setColor(Color.WHITE);
        g.drawString("B", 5 + (5 + qw) * 2, 30);

        g.dispose();
    }
}

关于java - 如何使用单独的 alpha 栅格创建 BufferedImage,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45351474/

相关文章:

javacv - 快速截图 - 如何获取图像

java - getScaledInstance java Image 后的高度和宽度 -1

java - 找不到StringTokenizer迭代器错误

java - 构造函数和指令重新排序

java - 在java中使用正则表达式删除边距,填充属性

java - 如何打开与 View 中的错误相关的文件

java - 显示 WizardPage 后执行的操作

java - 如何在 JFace LabelProviders 中处理 Windows shell 图标?

java - 您能否找到谁从 AWTEvent 对象对 EDT 进行了调用?

java - 我应该使用什么布局管理器