Модель обработки прямым доступом

 

Подобно тому, как вместо класса Graphics система Java 2D использует его расширение Graphics2D, описанное в главе 9, вместо класса image в Java 2D употребляется его расширение — класс Bufferedimage. В конструкторе этого класса

Bufferedlmage(int width, int height, int imageType)

задаются размеры изображения и способ хранения точек — одна из констант:

TYPE_INT_RGB       TYPE_4BYTE_ABRG        TYPE_USHORT_565_RGB

TYPE_INT_ARGB      TYPE_4BYTE_ABRG_PRE    TYPE_USHORT_555_RGB

TYPE_INT_ARGB_PRE  TYPE_BYTE_GRAY         TYPE_USHORT_GRAY

TYPE_INT_BRG       TYPE_BYTE_BINARY

TYPE_3BYTE_BRG     TYPE_BYTE_INDEXED

Как видите, каждый пиксел может занимать 4 байта — INT, 4BYTE, или 2 байта — USHORT, или 1 байт — BYTE. Может использоваться цветовая модель RGB, или добавлена альфа-составляющая — ARGB, или задан другой порядок расположения цветовых составляющих — BRG, или заданы градации серого цвета — GRAY. Каждая составляющая цвета может занимать один байт, 5 битов или 6 битов.

Экземпляры класса Bufferedimage редко создаются конструкторами. Для их создания чаще обращаются к методам createimage () класса component с простым приведением типа:

Bufferedimage bi = (Bufferedlmage)createimage(width, height)

При этом экземпляр bi получает характеристики компонента: цвет фона и цвет рисования, способ хранения точек.

Расположение точек в изображении регулируется классом Raster или его подклассом WritabieRaster. Эти классы задают систему координат изображения, предоставляют доступ к отдельным пикселам методами getPixeio, позволяют выделять фрагменты изображения методами getPixeiso. Класс WritabieRaster дополнительно разрешает изменять отдельные пикселы методами setPixei () или целые фрагменты изображения методами setPixels () и setRect().

Начало системы координат изображения — левый верхний угол — имеет координаты (minx, minY), не обязательно равные нулю.

При создании экземпляра класса Bufferedimage автоматически формируется связанный с ним экземпляр класса WritabieRaster.

Точки изображения хранятся в скрытом буфере, содержащем одномерный или двумерный массив точек. Вся работа с буфером осуществляется методами одного из классов DataBufferByte, DataBufferlnt, DataBufferShort, DataBufferushort в зависимости от длины данных. Общие свойства этих классов собраны в их абстрактном суперклассе DataBuffer. В нем определены типы данных, хранящихся в буфере: TYPE_BYTE, TYPEJJSHORT, TYPE_INT, TYPEJJNDEFINED.

Методы класса DataBuffer предоставляют прямой доступ к данным буфера, но удобнее и безопаснее обращаться к ним методами классов Raster и WritableRaster.

При создании экземпляра класса Raster или класса WritableRaster создается экземпляр соответствующего подкласса класса DataBuffer.

Чтобы отвлечься от способа хранения точек изображения, Raster может обращаться не к буферу DataBuffer, а к подклассам абстрактного класса SampieModei, рассматривающим не отдельные байты буфера, а составляющие (samples) цвета. В модели RGB — это красная, зеленая и синяя составляющие. В пакете java.awt. image есть пять подклассов класса SampieModei:

  • componentsampieModel — каждая составляющая цвета хранится в отдельном элементе массива DataBuffer;
  • BandedSampleModel — данные хранятся по составляющим, составляющие одного цвета хранятся обычно в одном массиве, a DataBuffer содержит двумерный массив: по массиву для каждой составляющей; данный класс расширяет класс ComponentsampieModel ;
  • PixelInterleavedSampieModel — все составляющие цвета одного пиксела хранятся в соседних элементах единственного массива DataBuffer ; данный Класс расширяет класс ComponentsampieModel ;
  • MultiPixeiPackedSampieModel — цвет каждого пиксела содержит только одну составляющую, которая может быть упакована в один элемент массива DataBuffer ;
  • singiePixelPackedSampleModel — все составляющие цвета каждого пиксела хранятся в одном элементе массива DataBuffer .

Итак, Java 2D создает сложную и разветвленную трехслойную систему DataBuffer — SampieModei — Raster управления данными изображения Bufferedimage . Вы можете манипулировать точками изображения, используя их координаты в методах классов Raster или спуститься на уровень ниже и обращаться к составляющим цвета пиксела методами классов SampieModei . Если же вам надо работать с отдельными байтами, воспользуйтесь классами DataBuffer .

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

 

Преобразование изображения в Java 2D

 

Преобразование изображения source, хранящегося в объекте класса Buf f redlmage, В новое изображение destination выполняется методом filter(Buffredlmage source, Buffredlmage destination) описанным в интерфейсе BuffredimageOp. Указанный метод возвращает ссылку на новый, измененный объект destination класса Buffredlmage, что позволяет задать цепочку последовательных преобразований.

Можно преобразовать только координатную систему изображения методом filter(Raster source, WritableRaster destination) возвращающим ссылку на измененный объект класса WritableRaster. Данный метод описан в интерфейсе RasterOp.

Способ преобразования определяется классом, реализующим эти интерфейсы, а параметры преобразования задаются в конструкторе класса.

В пакете java.awt.image есть шесть классов, реализующих интерфейсы BuffredimageOp и RasterOp:

  • AffineTransformOp — выполняет аффинное преобразование изображения: сдвиг, поворот, отражение, сжатие или растяжение по осям;
  • RescaieOp — изменяет интенсивность изображения;
  • LookupOp — изменяет отдельные составляющие цвета изображения; 
  • BandCombineOp — меняет составляющие цвета в Raster; 
  • СolorConvertdp — изменяет цветовую модель изображения;
  • ConvolveOp — выполняет свертку, позволяющую изменить контраст и/или яркость изображения, создать эффект «размытости» и другие эффекты.

Рассмотрим, как можно применить эти классы для преобразования изображения.

 

Аффинное преобразование изображения

 

Класс AffineTransform и его использование подробно разобраны в главе 9, здесь мы только применим его для преобразования изображения.

В конструкторе класса AffineTransformOp указывается предварительно созданное аффинное преобразование at и способ интерполяции interp и/или правила визуализации hints:

AffineTransformOp(AffineTransform at, int interp); AffineTransformOp(AffineTransform at, RenderingHints hints);

Способ интерполяции — это одна из двух констант: TYPE_NEAREST_NEIGHBOR (по умолчанию во втором конструкторе) или TYPE_BILINEAR .

После создания объекта класса AffineTransformOp применяется метод filter (). При этом изображение преобразуется внутри новой области типа Bufferedimage. Сама область выделена черным цветом (справа).

Другой способ аффинного преобразования изображения — применить метод drawlmage(Bufferedlmage img, BufferedlmageOp op, int x, int y) класса Graphics2D. При этом преобразуется вся область img (посередине).

В листинге 15.5 показано, как задаются преобразования.

Обратите внимание на особенности работы с Bufferedimage. Надо создать графический контекст изображения и вывести в него изображение. Эти действия кажутся лишними, но удобны для двойной буферизации, которая сейчас стала стандартом перерисовки изображений, а в библиотеке Swing выполняется автоматически.

 

Листинг 15.5. Аффинное преобразование изображения

import j ava.awt.*; 

import Java.awt.geom.*;

import Java.awt. image.*;

import java.awt.event.*;

public class AffOp extends Frame{ 

private Bufferedimage bi; 

public AffOp(String s){ super (s) ;

// Загружаем изображение 

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

// В этом блоке организовано ожидание загрузки 

try{

MediaTracker mt = new MediaTracker(this); 

mt.addlmage(img, 0);

mt.waitForlD(O); 

// Ждем окончания загрузки }

catch(Exception e){}

// Размеры создаваемой области bi совпадают 

//с размерами изображения img

bi = new Bufferedlmage(img.getWidth(this), img.getHeight(this), 

Bufferedlmage.TYPE_INT_RGB);

// Создаем графический контекст big изображения bi 

Graphics2D big = bi.createGraphics();

// Выводим изображение img в графический контекст 

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

}

public void paint(Graphics g){ 

Graphics2D g2 = (Graphics2D)g; 

int w = getSize().width; 

int h = getSize().height; 

int bw = bi.getWidth(this); 

int bh = bi.getHeight(this);

// Создаем аффинное преобразование

 at AffineTransform at = new AffineTransform(); 

at.rotate(Math.PI/4);     // Задаем поворот на 45 градусов

//по часовой стрелке вокруг левого верхнего угла.

//Затем сдвигаем изображение вправо на величину bw 

at.preConcatenate(new AffineTransform(l, 0, О, 1, bw, 0));

// Определяем область хранения bimg преобразованного

// изображения. Ее размер вдвое больше исходного 

Bufferedimage bimg =

new Bufferedlmage(2*bw, 2*bw, Bufferedlmage.TYPE_INT_RGB);

// Создаем объект biop,. содержащий преобразование at 

BufferedlmageOp biop = new AffineTransformOp(at,

AffineTransformOp.TYPE_NEAREST_NEIGHBOR);

// Преобразуем изображение, результат заносим в bimg biop.filter(bi, bimg);

// Выводим исходное изображение. g2.drawlmage(bi, null, 10, 30);

// Выводим измененную преобразованием Ыор область bi g2.drawImage(bi, biop, w/4+3, 30);

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

g2.drawlmage(bimg, null, w/2+3, 30); } 

public static void main(String[] args){

Frame f = new AffOpf» Аффинное преобразование»); 

f.addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent e){

System.exit(0); 

}

});

f.setSize(400, 200); 

f.setVisible(true) ; 

}

 

Изменение интенсивности изображения

 

Изменение интенсивности изображения выражается математически в умножении каждой составляющей цвета на число factor и прибавлении к результату умножения числа offset. Результат приводится к диапазону значений составляющей. После этого интенсивность каждой составляющей цвета линейно изменяется в одном и том же масштабе.

Числа factor и offset постоянны для каждого пиксела и задаются в конструкторе класса вместе с правилами визуализации hints:

RescaleOp(float factor, float^offset, RenderingHints hints) После этого остается применить метод filter ().

Интенсивность каждого цвета уменьшена вдвое, в результате белый фон стал серым, а цвета — темнее. Затем интенсивность увеличена на 70 единиц. В листинге 15.6 приведена программа, выполняющая это преобразование.

 

Листинг 15.6. Изменение интенсивности изображения

import Java.awt.*;

import j ava.awt.image.*;

import j ava.awt.event.*;

public class Rescale extends Frame{ 

private Bufferedlmage bi; 

public Rescale(String s){ 

super (s) ;

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

try{

MediaTracker mt = new MediaTracker(this); 

mt.addlmage(img, 0); 

mt.waitForlD(O); }

catch(Exception e){}

bi = new Bufferedlmage(img.getWidth(this), img.getHeight(this),

BufferedImage.TYPE_INT_RGB); 

Graphics2D big = bi.createGraphics(); 

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

}

public void paint(Graphics g){ 

Graphics2D g2 = (Graphics2D)g; 

int w = getSize().width; 

int bw = bi.getWidth(this);

int bh = bi.getHeight(this); 

Bufferedlmage bimg =

new Bufferedlmage(bw, bh, BufferedImage.TYPE_INT_RGB); 

//——————— Начало определения преобразования ———-——— 

RescaleOp гор = new RescaleOp(0.5f, 70.Of, null); 

rop.filter(bi, bimg); 

//——————— Конец определения преобразования ———————

g2.drawlmage(bi, null, 10, 30); 

g2.drawlmage(bimg, null, w/2+3, 30); 

public static void main(String(] args){

Frame f = new Rescale(» Изменение интенсивности»); 

f.addWindowListener(new WindowAdapter(){ 

public void windowClosing(WindowEvent e) {

System.exit(0);

}

));

f.setSize(300, 200);

f.setvisible(true);

}

 

Изменение составляющих цвета

 

Чтобы изменить отдельные составляющие цвета, надо прежде всего посмотреть тип хранения элементов в Bufferedimage, по умолчанию это TYPE_INT_RGB. Здесь три составляющие — красная, зеленая и синяя. Каждая составляющая цвета занимает один байт, все они хранятся в одном числе типа int. Затем надо составить таблицу новых значений составляющих. В листинге 15.7 это двумерный массив samples. Потом заполняем данный массив нужными значениями составляющих каждого цвета. В листинге 15.7 задается ярко-красный цвет рисования и белый цвет фона. По полученной таблице создаем экземпляр класса ByteLookupTabie, который свяжет эту таблицу с буфером данных. Этот экземпляр используем для создания объекта класса LookupOp. Наконец, применяем метод filter () этого класса.

В листинге 15.7 приведен только фрагмент программы. Для получения полной программы его надо вставить в листинг 15.6 вместо выделенного в нем фрагмента. Логотип Java будет нарисован ярко-красным цветом.

 

Листинг 15.7. Изменение составляющих цвета

//————————————— Вставить в листинг 15.6 ————————

byte samples[][] = new byte[3][256]; 

for (int j = 0; j < 255; j++){

samples[0][j] = (byte)(255);     // Красная составляющая 

samples[1][j] = (byte)(0);       // Зеленая составляющая 

samples[2][j] = (byte)(0);       // Синяя составляющая 

}

samples[0][255] = (byte) (255);  // Цвет фона — белый

samples[1][255] = (byte) (255) ;

samples [2] [255] = (bybej (255) ;

ByteLookupTabie blut=new ByteLookupTabie(0, samples); 

LookupOp lop = new LookupOp(blut, null); 

lop.filter(bi, bimg); 

//————————————— Конец вставки ———————————————-

Создание различных эффектов

Операция свертки (convolution) задает значение цвета точки в зависимости от цветов окружающих точек следующим образом.

Пусть точка с координатами (х, у) имеет цвет, выражаемый числом А(х, у). Составляем массив из девяти вещественных чисел w(0), w(i), … w(8). Тогда новое значение цвета точки с координатами (х, у) будет равно:

w(0)*A(x-l, y-l)+w(l)*A(x, y-l)+w(2)*A(x+l, y-l)+

w(3)*A(x-l, y)+w(4)*A(x, y)+w(5)*A(x+l, у)+

w(6)*A(x-l, y+l)+w(7)*A(x, y+l)+w(8)*A(x+l, y+1)

Задавая различные значения весовым коэффициентам w(i), будем получать различные эффекты, усиливая или уменьшая влияние соседних точек.

Если сумма всех девяти чисел w(i) равна 1.0f, то интенсивность цвета останется прежней. Если при этом все веса равны между собой, т. е. равны 0.1111111f, то получим эффект размытости, тумана, дымки. Если вес w(4) значительно больше остальных при общей сумме их l.0f, то возрастет контрастность, возникнет эффект графики, штрихового рисунка.

Можно свернуть не только соседние точки, но и следующие ряды точек, взяв массив весовых коэффициентов из 15 элементов (3×5, 5×3), 25 элементов (5×5) и больше.

В Java 2D свертка делается так. Сначала определяем массив весов, например:

float[] w = (0, -1, О, -1, 5, -1, О, -1, 0};

Затем создаем экземпляр класса Kernel — ядра свертки:

Kernel kern = new Kernel(3, 3, w);

Потом объект класса ConvoiveOp с этим ядром:

ConvolveOp conv = new ConvoiveOp(kern);

Все готово, применяем метод filter (): conv.filter(bi, bimg);

В листинге 15.8 записаны действия, необходимые для создания эффекта «размытости».

 

Листинг 15.8. Создание различных эффектов

//—————————— Вставить в листинг 15.6 ——————————————

float[] wl = { 0.llllllllf, 0.llllllllf, 0.llllllllf, 

               0.llllllllf, 0.llllllllf, 0.llllllllf, 

               0.llllllllf, 0.llllllllf, 0.llllllllf }; 

Kernel kern = new Kernel(3, 3, wl);

ConvolveOp cop = new ConvolveOp(kern, ConvolveOp.EDGE_NO_OP, null); 

copl.fliter(bi, bimg) ; 

//—————————— Конец вставки ————————————————————

В результате команды слева направо представлены исходные изображение и изображения, преобразованные весовыми матрицами wl, w2 и w3, где матрица wl показана в листинге 15.8, а матрицы w2 и w3 выглядят так:

float[] w2 = { 0, -1, 0,-1, 4, -1, 0, -1, 0 } ; 

float[] w3 = { -1, -1, -1,-1, 9, -1, -1, -1, -1 };