ГЛАВА 15
Изображения и звук
 

Как: уже упоминалось в предыдущей главе, изображение в Java — это объект класса image. Там же показано, как в апплетах применяются методы getlmageо для создания этих объектов из графических файлов.

Приложения тоже могут применять аналогичные методы get image () класса Toolkit из пакета java.awt с одним аргументом типа string или URL. Обращение к этим методам из компонента выполняется через метод getToolkit () класса Component и выглядит так:

Image img = getToolkit().getlmage(«С:\\images\\lvanov.gif»);

В общем случае обращение можно сделать через статический метод getDef aultToolkit () класса Toolkit:

Image img = Toolkit.getDefaultToolkit().getlmage(» C:\\images\\Ivanov.gif «); 

Но, кроме этих методов, класс Toolkit содержит пять методов createlmage (), возвращающих ссылку на объект типа image:

  • createlmage (String filsName) — создает изображение из содержимого графического файла filename ;
  • createlmage (URL address) — создает изображение из содержимого графического файла по адресу address ;
  • createlmage (byte [] imageData) — создает изображение из массива байтов imageData , данные в котором должны иметь формат GIF или JPEG;
  • createlmage (byte [] imageData, int offset, int length) — создает изображение из части массива imageData , начинающейся с индекса offset длиной length байтов;
  • createlmage (ImageProducer producer) — создает изображение, полученное от поставщика producer .

Последний метод есть и в классе component. Он использует модель «поставщик-потребитель» и требует подробного объяснения.

 

Модель обработки «поставщик-потребитель»

 

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

В библиотеке AWT применяются две модели обработки изображения. Одна модель реализует давно известную в программировании общую модель «поставщик-потребитель» (Producer-Consumer). Согласно этой модели один объект, «поставщик», генерирует сам или преобразует полученную из другого места продукцию, в данном случае, набор пикселов, и передает другим объектам. Эти объекты, «потребители», принимают продукцию и тоже преобразуют ее при необходимости. Только после этого создается объект класса image и изображение выводится на экран. У одного поставщика может быть несколько потребителей, которые должны быть зарегистрированы поставщиком. Поставщик и потребитель активно взаимодействуют, обращаясь к методам друг друга.

В AWT эта модель описана В двух интерфейсах: ImageProducer И ImageConsumer пакета j ava. awt. image.

Интерфейс ImageProducer описывает пять методов:

  • addConsumer(ImageConsumer ic) — регистрирует потребителя ic; removeConsumer (ImageConsumer ic) — отменяет регистрацию;
  • isConsumer( ImageConsumer ic) — логический метод, проверяет, зарегистрирован ли потребитель ic;
  • startProduction (ImageConsumer ic) — регистрирует потребителя ic И НЭ-читает поставку изображения всем зарегистрированным потребителям;
  • requestTopDownLeftRightResend (ImageConsumer ic) — используется потребителем для того, чтобы затребовать изображение еще раз в порядке «сверху-вниз, слева-направо» для методов обработки, применяющих именно такой порядок.

С каждым экземпляром класса image связан объект, реализующий интерфейс ImageProducer. Его можно получить методом getSource () класса Image.

Самая простая реализация интерфейса ImageProducer — класс метогу-imagesource — создает пикселы в оперативной памяти по массиву байтов или целых чисел. Вначале создается массив pix, содержащий цвет каждой точки. Затем одним из шести конструкторов создается объект класса MemoryimageSource. Он может быть обработан потребителем или прямо преобразован в тип Image методом createlmage ().

В листинге 15.1 приведена простая программа, выводящая на экран квадрат размером 100×100 пикселов. Левый верхний угол квадрата синий, левый нижний — красный, правый верхний — зеленый, а к центру квадрата цвета перемешиваются.

 

Листинг 15.1. Изображение, построенное по точкам

import java.awt.*;

import j ava.awt.event.*;

import java.awt.image.*;

class InMemory extends Frame { 

private int w = 100, h = 100; 

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

private Image img; 

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 = 255 * x / (w — 1) ;

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

setSize(250, 200); 

setVisible(true); 

}

public vqid paint(Graphics gr){ 

if (img == null)

img = createlmage(new MemoryImageSource<w, h, pix> 0, w)); 

gr.drawlmage(img, 50, 50, this); 

public static void main(String[] args){

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

f.addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent ev){

System.exit (0); 

}

}); 

}

В листинге 15.1 в конструктор класса-поставщика MemoryimageSource (w, h, pix, о, w) заносится ширина w и высота h изображения, массив pix, смещение в этом массиве о и длина строки w. Потребителем служит изображение img, которое создается методом createlmage () и выводится на экран методом drawlmage(img, 50, 50, this). Левый верхний угол изображения img располагается в точке (50, 50) контейнера, а последний аргумент this показывает, что роль imageObserver играет сам класс InMemory. Это заставляет включить в метод paint о проверку if (img == null), иначе изображение будет постоянно перерисовываться. Другой способ избежать этого — переопределить метод imageupdate (), о чем говорилось в главе 14, просто написав В нем return true.

 

Интерфейс imageConsumer описывает семь методов, самыми важными из которых являются два метода setPixeis (). Первый:

setPixels(int x, int y, int width, int height, ColorModel model, byte[] pix, int offset, int scansize);

Второй метод отличается только тем, что массив pix содержит элементы типа int.

 

К этим методам обращается поставщик для передачи пикселов потребителю. Передается прямоугольник шириной width и высотой height с заданным верхним левым углом (х, у), заполняемый пикселами из массива pix, начиная с индекса offset. Каждая строка занимает scansize элементов массива pix. Цвета пикселов определяются в цветовой модели model (обычно это модель RGB).

 

Классы-фильтры

 

Интерфейс imageConsumer нет нужды реализовывать, обычно используется его готовая реализация — класс imageFilter. Несмотря на название, этот класс не производит никакой фильтрации, он передает изображение без изменений. Для преобразования изображений данный класс следует расширить, переопределив метод setPixeiso. Результат преобразования следует передать потребителю, роль которого играет поле consumer этого класса.

В пакете java. awt. image есть четыре расширения класса ImageFilter:

  • CropImageFilter (int x, int у, int w, int h) — выделяет фрагмент изображения, указанный в приведенном конструкторе;
  • RGBimageFilter — позволяет изменять отдельные пикселы; это абстрактный класс, он требует расширения и переопределения своего метода filterRGBO ;
  • RepдicateScaieFilter (int w, int h) — изменяет размеры изображения на указанные в приведенном конструкторе, дублируя строки и/или столбцы при увеличении размеров или убирая некоторые из них при уменьшении;
  • AreaAveragingScaleFilter (int w, int h) — расширение предыдущего класса; использует более сложный алгоритм изменения размеров изображения, усредняющий значения соседних пикселов.

Применяются эти классы совместно со вторым классом-поставщиком, реализующим интерфейс ImageProducer — классом FilteredlmageSource. Этот класс преобразует уже готовую продукцию, полученную от другого поставщика producer, используя для преобразования объект filter класса-фильтра imageFilter или его подкласса, Оба объекта задаются в конструкторе

FilteredlmageSource(ImageProducer producer, ImageFilter filter)

Все это кажется очень запутанным, но схема применения фильтров всегда одна и та же. Она показана в листингах 15.2—15.4.

 

Как выделить фрагмент изображения

 

В листинге 15.2 выделяется фрагмент изображения и выводится на экран в увеличенном виде. Кроме того, ниже выводятся изображения, увеличенные с помощью классов RepiicateScaieFiiter и AreaAveragingScaleFilter.

 

Листинг 15.2. Примеры масштабирования изображения

import j ava.awt.*;

import j ava.awt.event.*;

import j ava.awt.image.*;

class CropTest extends Frame{

private Image img, cropimg, replimg, averimg; 

CropTest(String s){ super (s) ;

// 1. Создаем изображение — объект класса Image 

img = getToolkit().getlmage(«javalogo52x88.gif»); 

// 2. Создаем объекты-фильтры:

// а) выделяем левый верхний угол размером 30×30 

CropImageFilter crp = new CropImageFilter(0, 0, 30, 30);

// б) увеличиваем изображение в два раза простым методом 

RepiicateScaieFiiter rsf = new RepiicateScaieFiiter(104, 176);

// в) увеличиваем изображение в два раза с усреднением 

AreaAveragingScaleFilter asf = new AreaAveragingScaleFilter(104, 176);

// 3. Создаем измененные изображения

cropimg = createlmage(new FilteredlmageSource(img.getSource(), crp)); 

replimg = createlmage(new FilteredlmageSource(img.getSource(), rsf)}; 

averimg = createlmage(new FilteredlmageSource(img.getSource(), asf)); 

setSize(400, 350); setvisible(true); }

public void paint(Graphics gS { g.drawlmage(img, 10, 40, this); 

g.drawlmage(cropimg, 150, 40, 100, 100, this); 

g.drawlmage(replimg, 10, 150, this); 

g.drawlmage(averimg, 150, 150, this); 

public static void main(String[] args){

Frame f= new CropTest(» Масштабирование»); 

f.addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent ev){

System.exit(0); 

});

}

}

 

Как изменить цвет изображения

 

В листинге 15.3 меняются цвета каждого пиксела изображения. Это достигается просто сдвигом rgb » 1 содержимого пиксела на один бит вправо в методе fiiterRGB (). При этом усиливается красная составляющая цвета. Метод f iiterRGB о переопределен в расширении coiorFilter класса RGBImageFilter.

 

Листинг 15.3. Изменение цвета всех пикселов ;

import j ava.awt.*;

import java.awt.event.*;

import java.awt.image.*;

class RGBTest extends Frame{ 

private Image img, newimg; 

RGBTest(String s){

super(s);

img = getToolkit().getlmage(«javalogo52x88.gif»);

RGBImageFilter rgb = new CoiorFilter();

newimg = createlmage(new FilteredlmageSource(img.getSource(), rgb));

setSize(400, 350);

setVisible(true); } public void paint(Graphics g){

g.drawlmage(img, 10, 40, this);

g.drawlmage(newimg, 150, 40, this); }

public static void main(String[] args){

Frame f= new RGBTest(» Изменение цвета»); 

f.addWindowListener(new WindowAdapter(){

public void wlndowClosing(WindowEvent ev){

System.exit(0); 

}

}); 

}

class CoiorFilter extends RGBImageFilter{ CoiorFilter(){

canFilterlndexColorModel = true; } 

public int fiiterRGB(int x, int y, int rgb){

return rgb » 1; 

}

 

Как переставить пикселы изображения

 

В листинге 15.4 определяется преобразование пикселов изображения. Создается новый фильтр — расширение shiftFiiter класса imageFilter, сдвигающее изображение циклически вправо на указанное в конструкторе число пикселов. Все, что для этого нужно, — это переопределить метод setPixels().

 

Листинг 15.4. Циклический сдвиг изображения

import j ava.awt.*;

import j ava.awt.event.*;

import j ava.awt.image.*;

class Shiftlmage extends Frame{ private Image img, newimg; 

Shiftlmage(String s){ super(s);

// 1. Получаем изображение из файла 

img = getToolkit().getlmage(«javalogo52x88.gif»);

// 2. Создаем экземпляр фильтра 

ImageFilter imf = new ShiftFiiter(26); 

// Сдвиг на 26 пикселов

// 3. Получаем новые пикселы с помощью фильтра 

ImageProducer ip = new FilteredlmageSource(img.getSource(), imf);

// 4. Создаем новое изображение 

newimg = createlmage(ip); 

setSize(300, 200);

setvisible(true) ; }

public void paint(Graphics gr){

gr.drawlmage(img, 20, 40, this); 

gr.drawlmage(newimg, 100, 40, this); } 

public static void main(StringU args){

Frame f= new ShiftImage(» Циклический сдвиг изображения»); 

f.atidWindowListener(new WindowAdapter()(

public void windowClosing(WindowEvent ev){

System.exit(0); )

}); 

}

// Класс-фильтр 

class ShiftFilter extends ImageFilterf

private int sh; 

// Сдвиг на sh пикселов вправо.

public ShiftFilter(int shift)!{ sh = shift; } 

public void setPixels(int x, int y, int w, int h,

ColorModel m, byte[] pix, int off, int size){ 

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

if (k+sh <= w)

consumer.setPixels(k, y, 1, h, m, pix, off+sh+k, size); 

else

consumer.setPixels(k, y, 1, h, m, pix, off+sh+k-w, size); 

}

Как видно из листинга 15.4, переопределение метода setPixels о заключается в том, чтобы изменить аргументы этого метода, переставив, тем самым, пикселы изображения, и передать их потребителю consumer — полю класса imageFiiter методом setPixels о потребителя.

Вторая модель обработки изображения введена в Java 2D. Она названа моделью прямого доступа (immediate mode model).