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

发布于 2024-11-14 19:28:34 字数 1805 浏览 5 评论 0 原文

在 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 毫秒。所以现在我基本上已经将绘图问题的性能影响换成了转换问题。

300 毫秒大约与从磁盘加载 RGB 数据所需的时间相同。我想我可以做得更快。

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

抱歉,我对 ColorModel、Raster 的东西有点迷失了。

谢谢!

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.

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.

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.

300ms is about the same time as it takes to load the RGB data from disk. I would think I could do something faster.

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?

Sorry, bit I'm a little lost on this ColorModel, Raster stuff.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淡紫姑娘! 2024-11-21 19:28:34

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

即使进行了诸如创建 BufferedImage 和 ComponentSampleModel 一次并重用它们之类的优化,调用 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 毫秒。

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

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):

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.

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:

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.

This may not be applicable for all cases, but I thought I'd share in case someone else finds it useful.

顾忌 2024-11-21 19:28:34

经过尝试后,如果当前图形配置使用 ARGB 整数打包光栅,我有一个适用于 Windows 的不错的答案。

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

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

这是班级。希望对您有帮助。

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

}

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.

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.

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.

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
        }
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文