如何使用单独的Alpha栅格创建BufferedImage

时间:2017-07-27 12:56:42

标签: 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代表完全相同的东西,它们只是包围表示像素的2D字节值数组。如果我可以让其中一个使用来自颜色编码,瞧,我可以重复使用后备数组。

我走得很远,这很有效:

// 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使用单独的数组(又名栅格或" band")。 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?

1 个答案:

答案 0 :(得分:1)

我不详细了解SWT,但根据我对API文档的理解,以下内容应该有效:

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

我建议你首先创建你的SWT图像,然后" wrap"自定义数据缓冲区中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();
    }
}