Анимация

 

Есть несколько способов создать анимацию. Самый простой из них — записать заранее все необходимые кадры в графические файлы, загрузить их в оперативную память В виде Объектов класса Image или Bufferedlmage и выводить по очереди на экран.

Это сделано в листинге 15.9. Заготовлено десять кадров в файлах runl.gif, run2.gif, , runl0.gif. Они загружаются в массив imgt] и выводятся на экран циклически 100 раз, с задержкой в 0,1 сек.

 

Листинг 15.9. Простая анимация 

import java.awt.*; 

import ]ava.awt.event.*;

class SimpleAnim extends Frame{

private Image[] img = new Image[10]; 

private int count; 

SimpleAnim(String s){ super(s);

MediaTracker tr = new MediaTracker(this); 

for (int k = 0; k < 10; k++){

img[k] = getToolkit(}.getlmage(«run»+(k+D+».gif»); 

tr.addlmage(img[k], 0); 

try{

tr.waitForAll(); // Ждем загрузки всех изображений 

}catch(InterruptedException e)(} 

setSize(400, 300); 

setvisible(true);

},

public void paint(Graphics g){

g.drawImage(img[count % 10], 0, 0, this);

}

// public void update(Graphics g){ paint(g); } 

public void go(){ while(count < 100){

repaint(); // Выводим следующий кадр 

try{       // Задержка в 0.1 сек

Thread.sleep(100); 

}catch(InterruptedException e){)

count++; 

public static void main(String[] args){

SimpleAnim f = new SimpleAnim(» Простая анимация»);

f.go();

f.addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent ev){

System.exit(0); }

}); 

}

Обратите внимание на следующее важное обстоятельство. Мы не можем обратиться прямо к методу paint () для перерисовки окна компонента, потому что выполнение этого метода связано с операционной системой — метод paint о выполняется автоматически при каждом изменении содержимого окна, его перемещении и изменении размеров. Для запроса на перерисовку окна в классе component есть метод repaint ().

Метод repaint о ждет, когда представится возможность пересоздать в окне, и потом обращается к методу update (Graphics gj. При этом нескольку обращений к repaint о могут быть произведены исполняющей системой Java за один раз.

Метод update() сначала обращается к методу g.ciearRectO, заполняющему окно цветом фона, а уж затем к методу paint (g). Полный исходный текст таков:

public void update(Graphics g){

if ((this instanceof java.awt.Canvas) ||

(this instanceof java.awt.Panel)      || 

(this instanceof java.awt.Frame)      || 

(this instanceof java.awt.Dialog)     ||

(this instanceof java.awt.Window)){

g.clearRect(0, 0, width, height); 

}

paint(g); 

}

Если кадры анимации полностью перерисовывают окно, то его очистка методом clearRecto не нужна. Более того, она часто вызывает неприятное мерцание из-за появления на мгновение белого фона. В таком случае надо сделать следующее переопределение:

public void update(Graphics g) { 

paint(g);

В листинге 15.9 это переопределение сделано как комментарий.

Для «легких» компонентов дело обстоит сложнее. Метод repaint () последовательно обращается к методам repaint () объемлющих «легких» контейнеров, пока не встретится «тяжелый» контейнер, чаще всего это экземпляр класса Container. В нем вызывается метод update о, очищающий и перерисовывающий контейнер. После этого идет обращение к методам update о всех «легких» компонентов в контейнере.

Отсюда следует, что для устранения мерцания «легких» компонентов необходимо переопределять метод update о первого объемлющего «тяжелого» контейнера, обращаясь в нем к методам super.update (g) или super.paint(g).

Если кадры покрывают только часть окна, причем каждый раз новую, то очистка окна необходима, иначе старые кадры останутся в окне, появится «хвост». Чтобы устранить мерцание, используют прием, получивший название «двойная буферизация » (double buffering).

 

Улучшение изображения двойной буферизацией

 

Суть двойной буферизации в том, что в оперативной памяти создается буфер — объект класса image или Bufferedimage, и вызывается его графический контекст, в котором формируется изображение. Там же происходит очистка буфера, которая тоже не отражается на экране. Только после выполнения всех действий готовое изображение выводится на экран.

Все это происходит в методе updateo, а метод paint о только обращается к update (). Листинги 15.10—15.11 разъясняют данный прием.

 

Листинг 15.10. Двойная буферизация с помощью класса image

public void update(Graphics g){

int w = getSize().width, h = getSize().height;

// Создаем изображение-буфер в оперативной памяти 

Image offlmg = createlmage(w, h);

// Получаем его графический контекст 

Graphics offGr = offImg.getGraphics();

// Меняем текущий цвет буфера на цвет фона 

offGr.setColor(getBackground());

//и заполняем им окно компонента, очищая буфер 

offGr.fillRect(0, 0, w, h);

// Восстанавливаем текущий цвет буфера 

offGr.setColor(getForeground());

// Для листинга 15.9 выводим в контекст изображение 

offGr.drawlmage(img[count % 10], 0, 0, this);

// Рисуем в графическом контексте буфера

// (необязательное действие) 

paint(offGr);

// Выводим изображение-буфер на экран

// (можно перенести в метод paint()) 

g.drawlmage(offlmg, 0, 0, this); }

// Метод paint() необязателен 

public void paint(Graphics g)J update(g); }

 

Листинг 15.11. Двойная буферизация с помощью класса Bufferedimage

public void update(Graphics g){ 

Graphics2D g2 = (Graphics2D},g; 

int w = getSize().width, h = getSize().height;

// Создаем изображение-буфер в оперативной памяти 

Bufferedimage bi = (Bufferedimage)createlmage(w, h);

// Создаем графический контекст буфера 

Graphics2D big = bi.createGraphics();

// Устанавливаем цвет фона 

big.setColor(getBackground());

// Очищаем буфер цветом фона 

big.clearRect(0, 0, w, h);

// Восстанавливаем текущий цвет 

big.setColor(getForeground());

// Выводим что-нибудь в графический контекст big 

// …

// Выводим буфер на экран 

g2.drawImage(bi, 0, 0, this); 

}

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

Данный метод удобен и при перерисовке отдельных частей изображения. В этом случае в изображении-буфере рисуется неизменяемая часть изображения, а в методе paint() — то, что меняется при каждой перерисовке.

В листинге 15.12 показан второй способ анимации — кадры изображения рисуются непосредственно в программе, в методе update (), по заданному закону изменения изображения. В результате красный мячик прыгает на фоне изображения.

 

Листинг 15.12. Анимация рисованием

import Java.awt.*;

import j ava.awt.event.*;

import Java.awt.geom.*;

import java.awt.image.*;

class DrawAniml extends Frame{ 

private Image img; 

private int count;

DrawAniml(String s) { 

super(s);

MediaTracker tr = new MediaTracker(this); 

img = getToolkit().getlmage(«back2.jpg»); 

tr.addlmage(img, 0); 

try{

tr.waitForlD(0) ; 

}catch(InterruptedException e) {}

SetSize(400, 400); 

setvisible(true);

}

public void update(Graphics g){ 

Graphics2D g2 = (Graphics2D)g; 

int w = getSizeO.width, h = getSize().height; 

Bufferedlmage bi = (Bufferedlmage)createlmage(w, h) ; 

Graphics2D big = bi.createGraphics();

// Заполняем фон изображением img 

big.drawlmage(img, 0, 0, this);

// Устанавливаем цвет рисования 

big.setColor(Color.red);

// Рисуем в графическом контексте буфера круг, 

// перемещающийся по синусоиде 

big.fill(new Arc2D.Double(4*count, 50+30*Math.sin(count),

50, 50, 0, 360, Arc2D.OPEN)); 

// Меняем цвет рисования 

big.setColor(getForeground());

// Рисуем горизонтальную прямую 

big.draw(new Line2D.Double(0, 125, w, 125));

// Выводим изображение-буфер на экран 

g2.drawlmage(bi, 0, 0, this); }

public void go(){ 

while(count < 100){ 

repaint(); 

try{

Thread.sleep(10); 

}catch(InterruptedException e){} 

count++; 

public static void main(String[] args){

DrawAniml f = new DrawAniml(» Анимация»);

f.go();

f.addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent ev){

System.exit(0); 

}

}); 

}

Эффект мерцания, переливы цвета, затемнение и прочие эффекты, получающиеся заменой отдельных пикселов изображения, удобно создавать с помощью класса Memoryimagesource. Методы newPixeis() этого класса вызывают немедленную перерисовку изображения даже без обращения к методу repaint(), если перед этим выполнен метод setAnimated(true). Чаще всего применяются два метода:

  • newPixels(int x, int y, int width, int height) — получателю посылается указанный аргументами прямоугольный фрагмент изображения;
  •  nevPixels() — получателю посылается все изображение.

В листинге 15.13 показано применение этого способа. Квадрат, выведенный на экран, переливается разными цветами.

 

Листинг 15.13. Анимация с помощью MemorylmageSource

import Java.awt.*;

import java.awt.event.*;

import java.awt.image.*;

class InMemory extends Frame{

private int w = 100, h = 100, count; 

private int[] pix = new int[w * h]; 

private Image img; 

MemorylmageSource mis; 

InMemory(String s){ super(s); 

int i = 0; 

for(int у = 0; у < h; y++){

int red = 255 * у / (h — 1); 

for(int x = 0; x < w; x++){

int green = 25$ * x / (w — 1);

pix[i++] = (255 « 24}|(red << 16)|(green << 8) | 128; 

}

}

mis = new MemorylmageSource(w, h, pix, 0, w);

// Задаем возможность анимации

mis.setAnimated(true);

img = createImage(mis);

setSize(350, 300);

setVisible(true); 

public void paint(Graphics gr){

gr.drawImage(img, 10, 30, this);

}

public void update(Graphics g) { paint(g); }

public void got){ 

while (count < 100){

int i = 0; 

// Изменяем массив пикселов по некоторому закону 

for(int у — 0; у < h;,y++)

for (int x. = 0; x < w; x++)

pix[i++J = (255 « 24)|(255 + 8 * count « 16)|

(8*count «8)| 255 + 8 * count; 

// Уведомляем потребителя об изменении 

mis.newPixels(); 

try{

Thread.sleep(100); 

}catch(InterruptedException e){} 

count++; 

public static void main(String[] args){

InMemory f= new InMemory(» Изображение в памяти»);

f.go();

f.addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent ev){

System.exit(0); 

}

)); 

}

Вот и все средства для анимации, остальное — умелое их применение. Комбинируя рассмотренные способы, можно добиться удивительных эффектов. В документации SUN J2SDK, в каталогах demo\applets и demo\jfc\Java2D \src, приведено много примеров апплетов и приложений с анимацией.