动机:
我的目标是以最有效的方式将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.Raster
和org.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")。 ComponentColorModel
和BandedRaster
似乎完全是针对这些事情。
到目前为止,我到了这里:
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?
答案 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();
}
}