Java буферная графика или целочисленный массив

При разработке 2D-игр на Java большинство учебных пособий создают буферную стратегию рендеринга. Это имеет смысл. Тем не менее, там, где люди, кажется, искажаются, это метод рисования реальной графики в буфер.

Некоторые учебные пособия создают буферизованное изображение, а затем создают целочисленный массив для представления цветов отдельных пикселей.

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, а вместо этого используют компонент Graphics 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?

Я искал это часами, и все сайты, которые я видел, просто объясняют, что они делают, а не почему они решили сделать это таким образом.

2 ответа

Прежде всего, есть много исторических аспектов. Ранний API был очень простым, поэтому единственный способ сделать что-нибудь нетривиальное - реализовать все необходимые примитивы.

Доступ к необработанным данным немного старомоден, и мы можем попытаться сделать некоторую "археологию", чтобы найти причину, по которой был использован такой подход. Я думаю, что есть две основные причины:

1. Фильтр эффектов

Давайте не будем забывать, что эффекты фильтров (различные виды размытия и т. Д.) Просты, очень важны для любого разработчика игр и широко используются.

Самый простой способ реализовать такой эффект в Java 1 - использовать 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);
            }
        }
    }
} 

Естественно, вы можете реализовать это с помощью getRGB, но доступ к необработанным данным намного эффективнее. Потом, Graphics2D обеспечен лучший уровень абстракции:

открытый интерфейс BufferedImageOp

Этот интерфейс описывает операции с одним входом / одним выходом, выполняемые с объектами BufferedImage. Это реализуется с помощью AffineTransformOp, ConvolveOp, ColorConvertOp, RescaleOp и LookupOp. Эти объекты могут быть переданы в BufferedImageFilter для работы с BufferedImage в парадигме ImageProducer-ImageFilter-ImageConsumer.

2. Двойная буферизация

Другая проблема была связана с мерцанием и очень медленным рисованием. Двойная буферизация устраняет мерзкое мерцание и внезапно предоставляет простой способ сделать эффекты фильтрации, потому что у вас уже есть буфер.

Что-то вроде окончательного заключения:)

Я бы сказал, что описанная вами ситуация довольно распространена для любой развивающейся технологии. Есть два способа достижения одинаковых целей:

  • использовать устаревший подход, код больше и т. д.
  • полагаться на новые уровни абстракции, предоставляемые методы и т. д.

Есть также несколько полезных расширений, которые еще больше упростят вашу жизнь, поэтому не нужно использовать int[]:)

Давайте проясним одно: оба фрагмента кода делают одно и то же - рисуют изображение. Тем не менее, фрагменты довольно неполные - второй фрагмент не показывает, что такое testImage.image на самом деле или как он создается. Но оба они в конечном счете вызывают Graphics.drawImage() и все варианты drawImage () в Graphics или Graphics2D рисуют изображение, простое и понятное. Во втором случае мы просто не знаем, является ли это BufferedImage, VolatileImage или даже изображение набора инструментов.

Таким образом, нет никакой разницы в рисовании на самом деле здесь!

Существует только одно различие между двумя фрагментами - первый также получает прямую ссылку на массив целых чисел, который в конечном итоге внутренне поддерживает экземпляр Image. Это дает прямой доступ к данным пикселей, вместо того чтобы проходить через (Buffered) API изображения, используя, например, относительно медленные методы getRGB() и setRGB(). Причина, по которой это не может быть конкретизировано в данном контексте, заключается в том, что этот массив получается, но никогда не используется во фрагменте. Таким образом, чтобы дать следующее объяснение любой причине существования, мы должны сделать предположение, что кто-то хочет непосредственно прочитать или отредактировать пиксели изображения, вполне возможно, по причинам оптимизации, учитывая "медлительность" (Buffered)Image API для манипулировать данными.

И эти причины оптимизации могут быть преждевременной оптимизацией, которая может иметь неприятные последствия для вас.


Прежде всего, этот код работает только потому, что тип изображения - INT_RGB, который придаст изображению IntDataBuffer. Если это был другой тип изображения, например 3BYTE_BGR, этот код завершится с ошибкой ClassCastException, так как буфер резервных данных не будет IntDataBuffer. Это не может быть большой проблемой, когда вы только вручную создаете изображения и применяете определенный тип, но изображения, как правило, загружаются из файлов, созданных внешними инструментами.

Во-вторых, есть еще один большой недостаток прямого доступа к пиксельному буферу: когда вы это сделаете, Java2D откажется от ускорения этого изображения, так как оно не может знать, когда вы будете вносить изменения в него вне его контроля. Просто для наглядности: ускорение - это процесс сохранения неизмененного изображения в видеопамяти, а не копирование его из системной памяти при каждом его рисовании. Это потенциально может значительно повысить производительность (или потерю, если вы ее сломаете), в зависимости от того, сколько изображений вы используете.

Как я могу создать аппаратно-ускоренный образ с Java2D?

(Как показывает этот связанный вопрос: вы должны использовать GraphicsConfiguration.createCompatibleImage() для создания экземпляров BufferedImage).

Итак, по сути: попробуйте использовать API Java2D для всего, не обращайтесь к буферам напрямую. Этот сторонний ресурс дает хорошее представление о том, какие функции API может вам в этом помочь, не переходя на низкий уровень:

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

Другие вопросы по тегам