如何使用单独的 alpha 栅格创建 BufferedImage
How to create BufferedImage with a separate alpha 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.Raster
和 org.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 使用单独的数组(也称为光栅或 "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 Image 来说也是不可用的。 是否可以创建具有 2 个波段的带状光栅:一个用于 RGB 或 BRG 中的颜色,一个仅用于 alpha?
我不太了解 SWT,但根据我对 API 文档的理解,以下内容应该有效:
诀窍是使用伪装成 "banded" 缓冲区的自定义 DataBuffer
实现,但在内部使用交错 RGB 和单独的 alpha 数组的组合进行存储。这与标准 BandedSampleModel
配合得很好。使用此模型,您将失去任何通常应用于 BufferedImage
s 的特殊(硬件)优化的机会,但这无关紧要,因为无论如何您都在使用 SWT 进行显示。
我建议您先创建 SWT 图像,然后 "wrap" 来自自定义数据缓冲区中 SWT 图像的颜色和 alpha 数组。如果你这样做,Batik 应该直接渲染到你的 SWT 图像,然后你可以扔掉 BufferedImage
(如果这不切实际,你当然也可以反过来做,但是您可能需要在下面公开自定义数据缓冲区 class 的内部数组,以创建 SWT 图像)。
代码(重要部分是 SWTDataBuffer
class 和 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();
}
}
动机:
我的目标是以最有效的方式将 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
表示完全相同的东西,它们只是围绕表示像素的字节值的二维数组进行包装。如果我可以使一个或另一个使用颜色编码,瞧,我可以按原样重用支持数组。
我已经走得很远了,这很有效:
// 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 Image 来说也是不可用的。 是否可以创建具有 2 个波段的带状光栅:一个用于 RGB 或 BRG 中的颜色,一个仅用于 alpha?
我不太了解 SWT,但根据我对 API 文档的理解,以下内容应该有效:
诀窍是使用伪装成 "banded" 缓冲区的自定义 DataBuffer
实现,但在内部使用交错 RGB 和单独的 alpha 数组的组合进行存储。这与标准 BandedSampleModel
配合得很好。使用此模型,您将失去任何通常应用于 BufferedImage
s 的特殊(硬件)优化的机会,但这无关紧要,因为无论如何您都在使用 SWT 进行显示。
我建议您先创建 SWT 图像,然后 "wrap" 来自自定义数据缓冲区中 SWT 图像的颜色和 alpha 数组。如果你这样做,Batik 应该直接渲染到你的 SWT 图像,然后你可以扔掉 BufferedImage
(如果这不切实际,你当然也可以反过来做,但是您可能需要在下面公开自定义数据缓冲区 class 的内部数组,以创建 SWT 图像)。
代码(重要部分是 SWTDataBuffer
class 和 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();
}
}