java BufferedImage中RGB数据的快速加载和绘制
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6319465/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Fast loading and drawing of RGB data in BufferedImage
提问by awinbra
In some Java code running on Windows, I'm reading some large blocks of RGB data from disk and want to display this to screen as quickly as possible. The RGB data is 8 bits per channel without any alpha. Currently I have code like the following to create the BufferedImage.
在 Windows 上运行的一些 Java 代码中,我正在从磁盘读取一些大块的 RGB 数据,并希望尽快将其显示到屏幕上。RGB 数据是每通道 8 位,没有任何 alpha。目前我有如下代码来创建 BufferedImage。
BufferedImage getBufferedImage(File file, int width, int height) {
byte[] rgbData = readRGBFromFile(file);
WritableRaster raster = Raster.createInterleavedRaster(
rgbData, width, height,
width * 3, // scanlineStride
3, // pixelStride
new int[]{0, 1, 2}, // bandOffsets
null);
ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[]{8, 8, 8}, // bits
false, // hasAlpha
false, // isPreMultiplied
ComponentColorModel.OPAQUE,
DataBuffer.TYPE_BYTE);
return new BufferedImage(colorModel, raster, false, null);
}
The problem is that the performance of rendering this to the screen is pretty slow. Around 250 - 300 ms. I've read that for the best performance you need to display in a BufferedImage that's compatible with the screen. To do that, I pass the buffered image returned from the above method to a method like this.
问题是将其渲染到屏幕的性能非常慢。大约 250 - 300 毫秒。我已经读过,为了获得最佳性能,您需要在与屏幕兼容的 BufferedImage 中显示。为此,我将从上述方法返回的缓冲图像传递给这样的方法。
BufferedImage createCompatibleImage(BufferedImage image)
{
GraphicsConfiguration gc = GraphicsEnvironment.
getLocalGraphicsEnvironment().
getDefaultScreenDevice().
getDefaultConfiguration();
BufferedImage newImage = gc.createCompatibleImage(
image.getWidth(),
image.getHeight(),
Transparency.TRANSLUCENT);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
That method essentially converts it from RGB to ARGB on Windows and it really speeds up the displaying, but this method takes ~300 ms for a 1600 x 1200 RGB data block. So now I've basically traded the performance hit of the drawing problem to a converting problem.
该方法本质上是在 Windows 上将其从 RGB 转换为 ARGB,它确实加快了显示速度,但是对于 1600 x 1200 RGB 数据块,这种方法需要大约 300 毫秒。所以现在我基本上已经将绘图问题的性能影响换成了转换问题。
300ms is about the same time as it takes to load the RGB data from disk. I would think I could do something faster.
300 毫秒与从磁盘加载 RGB 数据所需的时间大致相同。我想我可以做得更快。
Is there a better way I can do the conversion? Or would it help if I modified the RGB data and added an alpha channel myself beforehand? If so what would my Raster and ColorModel look like. Also, since my RGB data doesn't contain transparency can I get any performance improvements by using pre multiplied alpha or something?
有没有更好的方法可以进行转换?或者,如果我事先修改了 RGB 数据并自己添加了一个 alpha 通道会有所帮助吗?如果是这样,我的 Raster 和 ColorModel 会是什么样子。另外,由于我的 RGB 数据不包含透明度,我可以通过使用预乘 alpha 或其他方法获得任何性能改进吗?
Sorry, bit I'm a little lost on this ColorModel, Raster stuff.
抱歉,我有点迷失在这个 ColorModel、Raster 的东西上。
Thanks!
谢谢!
回答by Brent Writes Code
I realize this is a really old question, I'm just posting this for anybody else who might stumble upon this question looking for more options. I had an issue recently where I was attempting to take a large (720p) RGB byte[] and render it to a BufferedImage
. The original implementation I was using looked something like this (simplified here):
我意识到这是一个非常古老的问题,我只是将其发布给其他可能偶然发现此问题以寻找更多选择的人。我最近遇到了一个问题,我试图获取一个大(720p)RGB 字节[] 并将其渲染为BufferedImage
. 我使用的原始实现看起来像这样(这里简化了):
public void processFrame(byte[] frame, int width, int height)
{
DataBuffer videoBuffer = new DataBufferByte(frame,frame.length);
BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
ComponentSampleModel sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE,width,height,3,width*3,new int[] {2,1,0});
Raster raster = Raster.createRaster(sampleModel,videoBuffer,null);
currentImage.setData(raster);
}
Even with optimizations like creating the BufferedImage
and ComponentSampleModel
once and reusing them, the final step of calling setData
on the BufferedImage
was still taking on the order of 50-60 milliseconds, which is unacceptable.
即使像创建优化BufferedImage
和ComponentSampleModel
一次并重复使用它们,调用的最后步骤中setData
对BufferedImage
依然在采取的50-60毫秒量级,这是不可接受的。
What I ended up realizing is that, at least for my scenario, you can actually write to the backing byte array of the BufferedImage
directly and bypass most of the intermediate processing (assuming the backing metadata for the image is already correct). So I changed my code to look like this:
我最终意识到,至少在我的场景中,您实际上可以BufferedImage
直接写入支持字节数组并绕过大部分中间处理(假设图像的支持元数据已经正确)。所以我把我的代码改成这样:
public void processFrame(byte[] frame, int width, int height)
{
BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
byte[] imgData = ((DataBufferByte)currentImage.getRaster().getDataBuffer()).getData();
System.arraycopy(frame,0,imgData,0,frame.length);
}
Just by doing this, my performance improved by about a factor of 20. I now process the same frames in 3-5 milliseconds instead of 50-60 milliseconds.
仅仅通过这样做,我的性能就提高了大约 20 倍。我现在在 3-5 毫秒而不是 50-60 毫秒内处理相同的帧。
This may not be applicable for all cases, but I thought I'd share in case someone else finds it useful.
这可能不适用于所有情况,但我想我会分享以防其他人发现它有用。
回答by awinbra
After playing around with this I have a decent answer that works for Windows if the current graphics configuration is using ARGB integer packed rasters.
如果当前的图形配置使用 ARGB 整数打包光栅,那么在玩弄这个之后,我有一个适用于 Windows 的不错的答案。
What I do is create the compatible BufferedImage first, then I manually convert my RGB bytes array to an ARGB int array. Then I get the Raster from the compatible BufferedImage and write my ARGB ints into it. This is much faster.
我所做的是首先创建兼容的 BufferedImage,然后我手动将我的 RGB 字节数组转换为 ARGB int 数组。然后我从兼容的 BufferedImage 获取 Raster 并将我的 ARGB int 写入其中。这要快得多。
I also have a class that checks if the compatible BufferedImage is in the format I expect, if it isn't it defaults to the older slower approach.
我还有一个类,用于检查兼容的 BufferedImage 是否符合我期望的格式,如果不是,则默认为较旧的较慢方法。
Here is the class. Hope it helps you.
这是课堂。希望对你有帮助。
/**
* This class can read chunks of RGB image data out of a file and return a BufferedImage.
* It may use an optimized technique for loading images that relies on assumptions about the
* default image format on Windows.
*/
public class RGBImageLoader
{
private byte[] tempBuffer_;
private boolean fastLoading_;
public RGBImageLoader()
{
fastLoading_ = canUseFastLoadingTechnique();
}
private boolean canUseFastLoadingTechnique()
{
// Create an image that's compatible with the screen
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage image = gc.createCompatibleImage(100, 100, Transparency.TRANSLUCENT);
// On windows this should be an ARGB integer packed raster. If it is then we can
// use our optimization technique
if(image.getType() != BufferedImage.TYPE_INT_ARGB)
return false;
WritableRaster raster = image.getRaster();
if(!(raster instanceof IntegerInterleavedRaster))
return false;
if(!(raster.getDataBuffer() instanceof DataBufferInt))
return false;
if(!(image.getColorModel() instanceof DirectColorModel))
return false;
DirectColorModel colorModel = (DirectColorModel) image.getColorModel();
if(!(colorModel.getColorSpace() instanceof ICC_ColorSpace) ||
colorModel.getNumComponents() != 4 ||
colorModel.getAlphaMask() != 0xff000000 ||
colorModel.getRedMask() != 0xff0000 ||
colorModel.getGreenMask() != 0xff00 ||
colorModel.getBlueMask() != 0xff)
return false;
if(raster.getNumBands() != 4 ||
raster.getNumDataElements() != 1 ||
!(raster.getSampleModel() instanceof SinglePixelPackedSampleModel))
return false;
return true;
}
public BufferedImage loadImage(File file, int width, int height, long imageOffset) throws IOException
{
if(fastLoading_)
return loadImageUsingFastTechnique(file, width, height, imageOffset);
else
return loadImageUsingCompatibleTechnique(file, width, height, imageOffset);
}
private BufferedImage loadImageUsingFastTechnique(File file, int width, int height, long imageOffset) throws IOException
{
int sizeBytes = width * height * 3;
// Make sure buffer is big enough
if(tempBuffer_ == null || tempBuffer_.length < sizeBytes)
tempBuffer_ = new byte[sizeBytes];
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "r");
raf.seek(imageOffset);
int bytesRead = raf.read(tempBuffer_, 0, sizeBytes);
if (bytesRead != sizeBytes)
throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage image = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
WritableRaster raster = image.getRaster();
DataBufferInt dataBuffer = (DataBufferInt) raster.getDataBuffer();
addAlphaChannel(tempBuffer_, sizeBytes, dataBuffer.getData());
return image;
}
finally
{
try
{
if(raf != null)
raf.close();
}
catch(Exception ex)
{
}
}
}
private BufferedImage loadImageUsingCompatibleTechnique(File file, int width, int height, long imageOffset) throws IOException
{
int sizeBytes = width * height * 3;
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "r");
// Lets navigate to the offset
raf.seek(imageOffset);
DataBufferByte dataBuffer = new DataBufferByte(sizeBytes);
byte[] bytes = dataBuffer.getData();
int bytesRead = raf.read(bytes, 0, sizeBytes);
if (bytesRead != sizeBytes)
throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);
WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, // dataBuffer
width, // width
height, // height
width * 3, // scanlineStride
3, // pixelStride
new int[]{0, 1, 2}, // bandOffsets
null); // location
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), // ColorSpace
new int[]{8, 8, 8}, // bits
false, // hasAlpha
false, // isPreMultiplied
ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
BufferedImage loadImage = new BufferedImage(colorModel, raster, false, null);
// Convert it into a buffered image that's compatible with the current screen.
// Not ideal creating this image twice....
BufferedImage image = createCompatibleImage(loadImage);
return image;
}
finally
{
try
{
if(raf != null)
raf.close();
}
catch(Exception ex)
{
}
}
}
private BufferedImage createCompatibleImage(BufferedImage image)
{
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage newImage = gc.createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
private void addAlphaChannel(byte[] rgbBytes, int bytesLen, int[] argbInts)
{
for(int i=0, j=0; i<bytesLen; i+=3, j++)
{
argbInts[j] = ((byte) 0xff) << 24 | // Alpha
(rgbBytes[i] << 16) & (0xff0000) | // Red
(rgbBytes[i+1] << 8) & (0xff00) | // Green
(rgbBytes[i+2]) & (0xff); // Blue
}
}
}