在BufferedImage中快速加载和绘制RGB数据

在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); } 

问题是将此渲染到屏幕的性能非常慢。 大约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; } 

该方法实质上在Windows上将其从RGB转换为ARGB,并且它确实加快了显示速度,但对于1600 x 1200 RGB数据块,此方法需要约300 ms。 所以现在我基本上将绘图问题的性能影响转换为转换问题。

300ms与从磁盘加载RGB数据的时间差不多。 我想我可以做得更快。

有没有更好的方法可以进行转换? 或者,如果我事先修改了RGB数据并添加了Alpha通道,它会有帮助吗? 如果是这样,我的Raster和ColorModel会是什么样子。 此外,由于我的RGB数据不包含透明度,我可以通过使用预乘的alpha或其他东西来获得任何性能改进吗?

对不起,我有点迷失在这个ColorModel,Raster的东西上。

谢谢!

我意识到这是一个非常古老的问题,我只是为其他任何可能偶然发现这个问题寻找更多选择的人发布这个问题。 我最近遇到了一个问题,我试图获取一个大的(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); } 

即使像创建BufferedImageComponentSampleModel并重新使用它们进行优化,在BufferedImage上调用setData的最后一步仍然是大约50-60毫秒,这是不可接受的。

我最终意识到的是,至少对于我的场景,你实际上可以直接写入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); } 

通过这样做,我的性能提高了大约20倍。我现在在3-5毫秒而不是50-60毫秒处理相同的帧。

这可能不适用于所有情况,但我想我会分享以防其他人认为它有用。

在玩完这个后,如果当前的图形配置使用ARGB整数打包栅格,我有一个适合Windows的答案。

我所做的是首先创建兼容的BufferedImage,然后我手动将我的RGB字节数组转换为ARGB int数组。 然后我从兼容的BufferedImage中获取Raster,并将我的ARGB内容写入其中。 这要快得多。

我还有一个类来检查兼容的BufferedImage是否采用我期望的格式,如果不是,则默认为较旧的较慢方法。

这是class级。 希望它能帮到你。

 /** * 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