Java 缓冲区策略图形或整数数组

2022-09-04 03:51:43

在 Java 中进行 2D 游戏开发时,大多数教程都会创建要渲染的缓冲区策略。这是非常有道理的。但是,人们似乎偏向的地方是将实际图形绘制到缓冲区的方法。

某些教程会创建一个缓冲图像,然后创建一个整数数组来表示各个像素颜色。

private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();

Graphics g = bs.getDrawGraphics();
g.setColor(new Color(0x556B2F));
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);

但是,其他一些教程不会创建缓冲图像,而是将像素绘制到 int 数组,而是使用 BufferStrategy 的图形组件将其图像直接绘制到缓冲区。

Graphics g = bs.getDrawGraphics();
g.setColor(new Color(0x556B2F));
g.fillRect(0, 0, getWidth(), getHeight());

g.drawImage(testImage.image, x*128, y*128, 128, 128, null);

我只是想知道,为什么要创建整个int数组,然后绘制它。这需要在实现矩形,拉伸,透明度等方面做更多的工作。缓冲区策略的图形组件已经具有可以轻松调用的方法。使用 int 数组是否有一些巨大的性能提升?

我已经查了几个小时,我看到的所有网站都只是解释了他们在做什么,而不是为什么他们选择这样做。


答案 1

让我们明确一件事:两个代码片段做完全相同的事情 - 绘制图像。然而,这些片段相当不完整 - 第二个片段没有显示“testImage.image”实际上是什么或它是如何创建的。但他们最终都调用Graphics.drawImage()和图形或Graphics2D中drawImage()的所有变体,绘制图像,简单明了。在第二种情况下,我们根本不知道它是BufferedImage,VolatileImage还是Kit工具包图像。

因此,这里实际插图的绘图没有区别!

这两个代码段之间只有一个区别 - 第一个代码段获取对最终在内部支持 Image 实例的整数数组的直接引用。这样就可以直接访问像素数据,而不必通过(Buffered)Image API来使用相对较慢的getRGB()和setRGB()方法。为什么不能在上下文中具体化的原因是这个问题,数组是被获取的,但从未在代码片段中使用过。因此,为了给以下解释任何存在的理由,我们必须假设有人想要直接读取或编辑图像的像素,很可能是出于优化原因,因为(缓冲)图像API操作数据的“缓慢”。

这些优化原因可能是过早的优化,可能会适得其反。


首先,此代码之所以有效,是因为图像的类型INT_RGB这将为图像提供IntDataBuffer。如果它是另一种类型的图像(例如3BYTE_BGR),则此代码将使用 ClassCastException 失败,因为后备数据缓冲区将不是 IntDataBuffer。当您仅手动创建映像并强制执行特定类型时,这可能不是什么大问题,但映像往往是从外部工具创建的文件加载的。

其次,直接访问像素缓冲区还有另一个更大的缺点:当您这样做时,Java2D将拒绝加速该图像,因为它无法知道您何时会在其控制范围之外对其进行更改。只是为了清楚起见:加速是在视频内存中保留未更改的图像的过程,而不是每次绘制时从系统内存中复制它。这可能是一个巨大的性能改进(或者如果你破坏了它,就会损失),这取决于你使用了多少图像。

如何使用 Java2D 创建硬件加速映像?

(正如相关问题所示:您应该使用GraphicsConfiguration.createCompatibleImage()来构建BufferedImage实例)。

所以从本质上讲:尝试将Java2D API用于所有内容,不要直接访问缓冲区。这个异地资源提供了一个很好的想法,即API具有哪些功能可以支持您,而不必进入低级别:

http://www.pushing-pixels.org/2008/06/06/effective-java2d.html


答案 2

首先,有很多历史方面。早期的API是非常基本的,所以做任何非平凡的事情的唯一方法是实现所有必需的原语。

原始数据访问有点过时,我们可以尝试做一些“考古学”来找到使用这种方法的原因。我认为主要有两个原因:

1. 滤镜效果

我们不要忘记滤镜效果(各种模糊等)很简单,对任何游戏开发人员都非常重要,并且被广泛使用。

enter image description here

使用Java 1实现这种效果的简单方法是使用定义为矩阵的数组和过滤器。例如,赫伯特·希尔德(Herbert Schildt)曾经有很多这样的演示:int

public class Blur {

    public void convolve() {
        for (int y = 1; y < height - 1; y++) {
            for (int x = 1; x < width - 1; x++) {
                int rs = 0;
                int gs = 0;
                int bs = 0;
                for (int k = -1; k <= 1; k++) {
                    for (int j = -1; j <= 1; j++) {
                        int rgb = imgpixels[(y + k) * width + x + j];
                        int r = (rgb >> 16) & 0xff;
                        int g = (rgb >> 8) & 0xff;
                        int b = rgb & 0xff;
                        rs += r;
                        gs += g;
                        bs += b;
                    }
                }
                rs /= 9;
                gs /= 9;
                bs /= 9;
                newimgpixels[y * width + x] = (0xff000000
                        | rs << 16 | gs << 8 | bs);
            }
        }
    }
} 

当然,您可以使用 来实现这一点,但原始数据访问更有效。后来,提供了更好的抽象层:getRGBGraphics2D

公共接口 BufferedImageOp

此接口描述对 BufferedImage 对象执行的单输入/单输出操作。它由AffinTransformOp,ConvolveOp,ColorConvertOp,RescaleOp和LookUpOp实现。这些对象可以传递到 BufferedImageFilter 中,以对 ImageProducer-ImageFilter-ImageConsumer 范例中的 BufferedImage 进行操作。

2. 双缓冲

另一个问题与闪烁和非常慢的绘图有关。双重缓冲消除了丑陋的闪烁,突然之间,它提供了一种简单的方法来执行过滤效果,因为您已经有了缓冲区。

enter image description here

类似于最终结论:)

我想说的是,你所描述的情况对于任何不断发展的技术来说都是非常普遍的。有两种方法可以实现相同的目标:

  • 使用遗留方法,编写更多代码等
  • 依赖于新的抽象层,提供的技术等

还有一些有用的扩展可以进一步简化您的生活,因此无需使用:)int[]