Мало того, что изображения полезны для хранения картинок, но вы можете также использовать их для рисования поверхностей вне экрана. Это позволяет вам передавать любое изображение, включая текст и графику, во внеэкранный буфер, который вы можете отображать в более позднее время. Преимущество данного механизма состоит в том, что изображение становится видимым только тогда, когда оно уже окончательно построено. Рисование сложного изображения может занимать несколько миллисекунд или больше, что может наблюдаться пользователем как мигание или мерцание. Это мигание отвлекает пользователя и заставляет его чувствовать вашу визуализацию как более медленную, чем она есть в действительности. Использование внеэкранного изображения для ослабления мерцания называется двойной буферизацией, из-за того что экран рассматривается как буфер для пикселов, а внеэкранное изображение — это второй буфер, где можно готовить пикселы для показа на экране.
Ранее было показано, как можно создать пустой image-объект. Теперь покажем, как можно рисовать на данном изображении, а не на экране. Для этого необходим объект типа Graphics, чтобы использовать любой из его визуализующих методов. Удобно также, что Graphics-объект, который можно использовать для рисования на изображении, доступен через метод getGraphics(). Ниже представлен фрагмент кода, который создает новое изображение, получает его графический контекст, и заполняет полное изображение красными пикселами:
Canvas с = new Canvas ();
Image test = с.createImage(200/ 100);
Graphics gc = test.getGraphics();
gc.setColor(Color.red);
gc.fillRect(0, 0, 200, 100);
Как только создано и заполнено внеэкранное изображение, оно еще не будет видимым. Для его окончательного отображения вызывается drawImage(). Чтобы продемонстрировать влияние двойной буферизации на время рисования, ниже приведен пример, рисующий изображение со значительным временем прорисовки:
g.drawString("Press mouse button to double buffer", 10, h/2); g.setColor(Color.yellow);
g.fillOval(mx - gap, my - gap, gap * 2 + 1, gap * 2 + 1);
if (!flicker) {
screengc.drawImage(buffer, 0, 0, null);
}
}
public void update(Graphics g) {
paint (g);
}
}
Предложенный простой апплет имеет усложненный метод paint(). Он заполняет фон синим и затем рисует сверху красный муаровый образец. В верхней его части выводится некоторый черный текст, и затем рисуется желтый круг с центром в (mx, my)-координатах. Методы mouseMoved() и mouseDragged() переопределены так, чтобы отслеживать позицию мыши.
Эти методы идентичны, за исключением установок булевой переменной flicker.mouseMoved() устанавливает flicker в true, a mouseDragged() — в false. Это приводит к вызову repaint() с установкой flicker в true, когда мышь передвигается (но никакая кнопка не нажата), и установкой ее в false, когда мышь перетаскивается с любой нажатой кнопкой.
Когда вызывается paint() с переменной flicker, установленной в true, мы видим на экране выполнение каждой операции рисования. В случае, когда кнопка мыши нажата, и paint() вызывается с flicker, установленной в false, мы видим совершенно иную картину. Метод paint() обменивает Graphics-ссылку g с графическим контекстом, который отсылает к внеэкранному полотну buffer, который мы создали в init(). Тогда все операции рисования становятся невидимыми. В конце paint(), мы просто вызываем drawImage(), чтобы показать результаты этих методов рисования все сразу.
Обратите внимание, что удобно передавать в drawImage() в качестве четвертого параметра null. Этот параметр используется для передачи объекта типа ImageObserver, который принимает уведомление об image-событиях. Так как это изображение не производится из сетевого потока, нам не нужно уведомление.
Левый снимок на рис. 3 показывает, как выглядит апплет с ненажатыми кнопками мыши. Снимок был сделан, когда изображение было где-то в середине перерисовки. Правый снимок показывает, что, когда кнопка мыши нажимается, изображение всегда имеет законченный, четкий вид (благодаря двойной буферизации).
Рис. 3. Рисунок без буферизации (слева) и с двойной буферизацией (справа)