当前位置: 首页 > 面试题库 >

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

狄峻熙
2023-03-14
问题内容

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

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

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

抱歉,我有点不喜欢这个ColorModel和Raster东西。

谢谢!


问题答案:

解决了这个问题之后,如果当前的图形配置使用的是ARGB整数打包栅格,我将得到一个不错的答案,该答案适用于Windows。

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

我还有一个类,用于检查兼容的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
        }
    }

}


 类似资料:
  • 问题内容: 我试图在bufferedimage上绘制水平和垂直线。它最终应该看起来像一个网格单元。但是,当我运行代码时,我只看到两行:最左边的行和最上面的行(即,从0,0到0,图像的高度和从0,0到图像的宽度,0的一行),这里是代码段: 和覆盖的绘制方法: 所有这些都在扩展了JPanel的名为RobotMaze的类中。任何帮助表示赞赏。 问题答案:

  • 问题内容: 谁能解释如何从BufferedImage获取rgb值数组? 我在BufferedImage中有一个灰度图像,需要提取一个描述图像的0到255个值的数组。 我知道BufferedImage是正确的,因为我可以将其保存为PNG。但是,如果使用,我会得到一堆巨大的负数。 我搜索了一段时间,并看到了一些有关转移某些值的参考资料([post]),但并不太了解他们在说什么。 基本上,我想从Buff

  • 谁能解释一下如何从BufferedImage中获得一个rgb值数组? 我在BufferedImage中有一个灰度图像,需要提取一个描述图像的0到255个值的数组。 我知道BufferedImage是正确的,因为我可以将其保存为PNG。但是,如果我使用我得到一堆巨大的负数。 我搜索了一段时间,看到了一些关于转移一些价值观的参考(帖子),但并不真正理解他们在说什么。 基本上,我想从BufferedIm

  • 我对工具包的结果计数有这样的输出格式: 我实现了一个循环来查询工具包,然后检查结果值中的相关性,如下所示: 不幸的是,这个解决方案非常缓慢。你对如何做到这一点有什么建议吗?多谢了。 编辑:因为项目太多,所以很慢。

  • 当我通过VS代码运行我的网页时,我的所有图片都会显示出来,但由于某种原因,当我通过localhost运行网页时,没有任何图片或CSS被发送。这是我下面的代码,任何帮助都将不胜感激。我曾试图在网上找到解决方案,但迄今为止似乎没有任何效果。 这是我的文件结构

  • 我用鼠标光标制作了一个游戏,我想用绿色版本的图像覆盖光标来表示健康状况,但只有与健康百分比相对应的几何部分。来自以下帖子的解决方案:在java中绘制圆的切片? 简而言之,给定任何角度,如何绘制BuffereImage的扇区?