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