Про java swing - часть 1

January 5, 2008

Введение в разработку приложений с графическим интерфейсом для java



Для разработки приложений с графическим интерфейсом под java существует несколько библиотек, лишь две из них: awt и swing были официально признаны и по сути разработаны авторами java – компанией sun. Еще известна библиотека swt – разработка IBM, также ведутся разработки множеством крупных компаний, а также существуют общественные движения, наиболее известен проект java desktop.

Данная часть методического пособия будет посвящен использованию библиотеки swing, ее философии (а она есть и довольно отлична от знакомой вам по MFC), также будет уделено достаточное внимание созданию собственных элементов управления.

JFC – это сокращение от Java Foundation Classes, впервые JFC появился в 1997 и некоторое время поставлялся отдельным пакетом от java core language api. В версиях jdk начиная от 1.2 jfc является уже интегрированным.

Врезка: история развития графической подсистемы java: В ранних - 1.0.x - версиях Java Development Kit активно использовались "тяжелые" компоненты AWT, тесно связанные конкретной аппаратной платформой на которой выполнялось приложение. Дальнейшее развертывание концепции "write once, run everywhere" (написать однажды, запускать везде) привело к тому, что в версии 1.1.x наметился переход к универсальным компонентам. Тогда появились "легкие" интерфейсные классы, в которых любой компонент на экране создается средствами Java с помощью графических классов и методов. Такого рода классы компонентов были объединены в библиотеку под названием Swing и доступны разработчикам в составе как JDK, так и отдельного продукта JFC (Java Foundation Classes). Причем для совместимости со старыми версиями JDK старые компоненты из AWT остались нетронутыми, хотя компания JavaSoft -, отвечающая за выпуск JDK, - рекомендует не смешивать в одной и той же программе старые и новые компоненты.

Основные возможности и концепция JFC


Возможность Описание
Swing GUI Components Для построения интерфейса разработано достаточно большое количество элементов управления: кнопки, списки, наборы закладок …
Pluggable Look-and-Feel Support Приложения, которые используют Swing component-ы предлагают возможные стратегии того как будет выглять кнопка или текстовое поле а также то как она будет себя вести во взаимодействии с пользователем. В стандартной поставке jdk1.3 были стили для отображения интерфейса под windows/motif/metal и вы сами можете разработать стиль отображения: например в 10 версии borland jbuilder разработчики создали собственный стиль.
MVC – Model View Controller Многие компоненты Swing реализованы по технологии MVC. Это означает, что компонент условно разбит на три части: модель (model), вид (view) и контроллер (controller). Каждая из этих частей строго и только свою задачу. Модель хранит важные данные компонента и обеспечивает программный интерфейс к ним. Вид занимается внешним видом изображения компонента и тесно связан с системой настройки интерфейса L&F. Контроллер же управляет компонентом в целом, получая сигналы от вида и уведомляя об изменениях модель компонента. Такое разграничение важно при смене L&F. Реализуя свой компонент для Swing, естественно будет сделать его пригодным для разных интерфейсов L&F. Технология MVC - это все, что вам нужно. Достаточно написать различные виды для Metal L&F, Motif L&F, Windows L&F и т. п. При этом модель и контроллер компонента меняются.
Java 2D API Средства для рисования 2d графики с возможностью отправки изображения на печать, сохранения в файле.
Drag-and-Drop Support Поддержка D&D как внутри java приложения так и между несколькими приложениями: java и java, java и созданные с помощью иных языков программы.
Internationalization Возможность создавать приложения с поддержкой нескольких языков.
Все классы и интерфейсы swing расположены в пакете javax.swing или вложенных в состав его. Предыдущая версия библиотеки awt а также ряд классов носящих вспомогательный характер или не изменившихся находятся в пакете java.awt. так, что в начале вашего проекта всегда подключайте пакеты:
  1. import javax.swing.*;
  2. import java.awt.*;
Примечание: способы поставки и распространения программ с использованием swing. Т.к. библиотека swing входит в стандартную поставку jre (java runtime environment), то особых проблем здесь нет. Другое дело, если ваше приложение использует посторонние библиотеки и их достаточно много, особенно явно эта проблема возникает при создании веб-приложений. Sun разработала технологию java web start (необходимые для ее работы компоненты устанавливаются вместе с jre). Идея заключается в том, что пользователь в интернете скачивает и запускает на выполнение маленький текстовой файлик с расширением jnlp, тут же подсистема java web start выкачивает с сайта sun или иных поставщиков необходимые библиотеки. Создает ярлык для запуска - приложение готово для работы.

java 2d api



Начнем с простого: создадим приложение использующего средства 2d графики. Прежде всего необходимо создать основу – “холст” для рисования для этого следует создать диалоговое окно. Вам следует использовать либо класс Jframe (для немодальных окон) или Jdialog (соответствнно для модальных).
  1. public static void main(String[] args) {
  2.    JFrame jf = new JFrame("Пример диалогового окна");
  3.    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  4.    jf.setSize(300 , 300); или так     jf.setSize(new Dimension (300 , 300));
  5.    jf.setVisible(true);
  6. }
Здесь создается объект "диалоговое окна". В качестве параметра конструктора указывается тест заголовока окна. Затем определяется что должно произойти при закрытии этого окна (когда нажали на маленький крестик в верхнем правом углу окошка). Возможны следующие варианты (все эти константы определены в классе WindowConstants):

DO_NOTHING_ON_CLOSE - ничего не делать. Окно не закроется и ничего не произойдет.

HIDE_ON_CLOSE – окно будет спрятано.

DISPOSE_ON_CLOSE – окно как и в предыдущем случае будет спрятано, но при этом и уничтожено (не насовсем, просто часть ресурсов операционной системы, которые использовались для отображения окна будут "освобождены"). Повторно окно можно показать с помощью вызова setVisible (хотя это может занять чуть больше времени чем "просто спрятать и не освобождать никаких ресурсов" - HIDE_ON_CLOSE).

EXIT_ON_CLOSE - по закрытию формы приложение завершается используя вызов System.exit().

Затем я установил размеры, используя при вызове setSize либо явно заданные размеры – ширина или высота или обертку в виде объекта Dimension (запомните этот класс, многие методы графической подсистемы указывают размеры чего-либо с помощью объектов этого класса).

Начнем с простого: на форме должна рисоваться некая геометрическая «композиция» при создании которой будет использовано максимальное количество методов.

Важно: модель обработки событий в стиле MFC с картами событий в swing не используется – вместо этого вы должны либо перекрывать методы классов-предков, либо использовать концепцию “слушателей” (начнем с первого способа).
  1. JFrame jf = new JFrame("Пример диалогового окна"){
  2.        public void paint(Graphics g) {
  3.           super.paint(g);
  4.           g.setColor(Color.BLUE); // устанавливаем текущий цвет
  5.           g.drawLine(0,0 ,getSize().width , getSize().height);// рисуем линию через весь размер экрана
  6.           g.setColor(new Color (128,128,128));
  7.           g.drawOval(getSize().width/2 , getSize().height/2, 100, 50);
  8.           g.setColor(new Color (0xFFFF00A0, true));
  9.           g.drawPolygon(
  10.                 new Polygon(new int []{0,100,200,300}, new int []{0 , 200, 300, 100}, 4)
  11.           );
  12.  
  13.           g.drawRoundRect(60 , 60 , 210 , 210 , 50 , 10);
  14.           g.drawString("Hello From Java 2D", 50 , 50);
  15.           g.fillRect(20 , 20 , 60 , 60);
  16.        }
  17. };
• При событии перерисовки вызывается виртуальный метод paint, в качестве единственного параметра ему передается значение объекта Graphics, который подобен контексту устройства CDC в стиле MFC.

• В примере используется метод getSize, возвращающий объект Dimension хранящий размеры окна.

• При задании цвета с помощью вызова
  1. g.setColor(new Color (0xFFFF00A0, true));
цвет задается цвет в формате RGBA. Где последняя компонента – прозрачность. А второй параметр конструктора Color указывает на то, есть ли эта прозрачность или нет. Alpha компонент занимает биты с 24-31, красный компонент 16-23, зеленый - 8-15, и синий, соответственно, - 0-7. В общем случае, в составе класса Color есть варианты конструктора, в котором все вышеозначенные компоненты задаются явно и по отдельности:
  1. Color(int r, int g, int b, int a).
При задании цвета учитывайте, то переменная какого типа используется. Если переменная целая, то значение должно быть в диапазоне от 0 до 255, если вещественная, то 0-1.

• При вызове метода fillRect выполняется закраска площади прямоугольника текущим цветом – фактически и рамка объекта и фон одинаковы.

Далее приводится справочное пособие из методов класса Graphics:
  1. abstract  void	clearRect(int x, int y, int width, int height)
Выполняется очистка указанной области в качестве цвета заполнения используется тот что считается цветом по-умолчанию для устройства на котором идет рисование. Проще говоря, это белый.
  1. abstract  void	clipRect(int x, int y, int width, int height)
Изменяется текущая область отсечения.
  1. abstract  void	copyArea(int x, int y, int width, int height, int dx, int dy)
Копирует область с указанными координатами (первый 4-е параметра) в указанную точку.
  1. abstract  Graphics	create()
Создает новый контекст, являющейся копией существующего.
  1. abstract  void	dispose()
Уничтожает и освобождает ресурсы контекста.
  1. void	draw3DRect(int x, int y, int width, int height, boolean raised)
Рисуется прямоугольник с трехмерной рамкой. Raised – указывает на то, будет ли данный прямоугольник визуально "приподнят" или "утоплен".
  1. abstract  void	drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
Рисуем дугу элипса указанного размера. Дуга должна иметь начальный угол (startAngle) и само значение угла (arcAngle).
  1. void	drawChars(char[] data, int offset, int length, int x, int y)
Рисуем текст.
  1. abstract  boolean	drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) 
  2.  abstract  boolean	drawImage(Image img, int x, int y, ImageObserver observer) 
  3.  abstract  boolean	drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) 
  4.  abstract  boolean	drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) 
  5.  abstract  boolean	drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, 
  6. int sx2, int sy2, Color bgcolor, ImageObserver observer)
Рисует изображение в указанной точке и указанного размера. Переменные начинающиеся на “d” ("destination") - приемник, а "s" (“source”), соотвественно, – источник изображения. Также задается фоновый цвет и объект извещатель. Незнакомая вам по MFC концепция объектов, которые заинтересованы в получении извещения о том, что какая-то операция была успешно выполнена. При необходимости выполняется масштабирование изображения.
  1. abstract  void	drawLine(int x1, int y1, int x2, int y2)
Рисуем линию
  1. abstract  void	drawOval(int x, int y, int width, int height)
Рисуем эллипс
  1. abstract  void	drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
Рисуем полигон. В качестве параметра метода drawPolygon указывается массив точек по координатам "x" и "y".
  1. void	drawPolygon(Polygon p)
Еще один полигон
  1. abstract  void	drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
Полилиния – похоже на полигон. Но помните, что полигон всегда замкнут - полилиния же не обязательно.
  1. void	drawRect(int x, int y, int width, int height)
Прямоугольник
  1. abstract  void	drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
Прямоугольник с закругленными краями:


  1. abstract  void	drawString(AttributedCharacterIterator iterator, int x, int y)
Рисуем текст
  1. abstract  void	drawString(String str, int x, int y)
Рисуем текст
  1. void	fill3DRect(int x, int y, int width, int height, boolean raised)
Трехмерный прямоугольник с закругленными краями.

Далее должен был бы идти длинный перечень методов, которые делают тоже, что и вышеперичисленные рисующие примитивы, но с закраской. Я их пропущу, надеясь что вы сами догадаетесь какие у них имена либо наконец-то откроете мануал.
  1. abstract  Shape	getClip()
Получаем информацию о области отсечения. В общем случае область отсечения не прямоугольная, а имеет произвольную форму – полигон.

Дело в том, что когда необходима перерисовка некоторого элемента управления (и форма Jframe –это всего лишь частный случай), то для оптимизации, при вызове метода update (данный метод служит для стирования фона области) и paint (непосредственно рисование), в передаваемом объекте Graphics g уже задана область отсечения и все попытки рисования вне это области не будут отображаться.
  1. abstract  Rectangle	getClipBounds()
 Получаем область отсечения, но при этом берем ее не истинный размер, а “описывающий” прямоугольник.
  1. abstract  Color	getColor()
Получаем значение текущего цвета.
  1. abstract  Font	getFont()
Текущий шрифт.
  1. FontMetrics	getFontMetrics()
Метрики шрифта. Объект FontMetrixs используется для получения характеристик шрифта.
  1. g.setColor (Color.BLUE);
  2. GraphicsEnvironment gEnv =
  3. GraphicsEnvironment.getLocalGraphicsEnvironment();
  4. String envfonts[] = gEnv.getAvailableFontFamilyNames();
  5. setTitle("Общее количество шрифтов: " + envfonts.length);
  6. int w = getSize().width, h = getSize().height;
  7. int start_h = 0;
  8. for (int i = 0; i < envfonts.length; i++){
  9. Font f =  new Font (envfonts[i] , Font.ITALIC, 12);
  10. g.setFont(f);
  11.  
  12. FontMetrics metrics = g.getFontMetrics();
  13. int width = metrics.stringWidth( envfonts[i] );
  14. int height = metrics.getLeading() + metrics.getHeight();
  15. g.drawString( envfonts[i], w/2-width/2, start_h + height );
  16. start_h += height;
  17. }
  1. abstract  FontMetrics	getFontMetrics(Font f)
Получить метрики не для текущего (как в предыдущем методе), а произвольного шрифта, задаваемого как входной параметр.
  1. abstract  void	setClip(int x, int y, int width, int height)
Устанавливаем текущую область отсечения.
  1. abstract  void	setClip(Shape clip)
Тоже, что и предыдущий метод. Но область отсечения задается с помощью Shape.
  1. abstract  void	setColor(Color c)
Установить значение текущего цвета.
  1. abstract  void	setFont(Font font)
Установить значение текущего шрифта.
  1. abstract  void	setPaintMode()
Устанавливаем режим рисования – то какая операция будет производиться при совмещении пикселя старого и нового цвета. Здесь режим затирания старого цвета на новый.
  1. abstract  void	setXORMode(Color c1)
Метод подобен предыдущему, но здесь используется режим XOR, и в качестве цвета XOR режима используется явно задаваемый с1.
  1. abstract  void	translate(int x, int y)
Перенести систему координат (точнее ее начало)T в точку x,y в текущей системе координат разумеется.
  1. g.drawRect(10 , 10 , 100 , 100); // рисуем квадрат со стороной 100 px.
  2.  g.translate(10 , 10); // перемещаем систему координат в точку 10, 10
  3.  g.drawRect(0,0,100,100);// рисуем квадрат совпадающий с ранее нарисованным
В практике объект Graphics уже давно не используется, и новых версиях jdk всегда, в качестве параметра для метода paint, передается объект типа Graphics2D являющийся наследником от Graphics, но обладающим рядом полезных возможностей. В общем случае, вы в коде paint должны преобразовать Graphics к нужному типу.
  1. public void Paint (Graphics g) {
  2.   Graphics2D g2 = (Graphics2D) g;
  3.    // что-то рисуем
  4. }
Для того чтобы правильно применять новые возможности Graphics2D, вам необходимо учесть, что на рисование влияют все факторы перечисленные ниже.
Параметр стиль карандаша – все о том, как будет рисоваться граница линии объекта. Вы можете управлять толщиной линии, и ее стилем. Например, будет ли она сплошной или точечной.
Параметр fill управляет тем, как будет выполняться заливка фона фигуры. Вы можете выбрать будет ли заливка сплошной, или же использован шаблон.
Стиль композиции – то, что будет происходить при наложении изображения поверх существующего.
Параметр трансформации transform – управляет тем? какие преобразования будут выполняться при отображении объекта. Вы можете задать здесь смещение системы координат, ее вращение, масштабирование.
Clip – отсечение, какая часть Graphics2D будет доступна для рисования.
Шрифт
Параметры рендеринга. При формирования итогового изображения, например, есть возможность управлять сглаживанием (antialiasing).
Для того чтобы изменять все эти перечисленные атрибуты следует использовать методы Graphics2D:
 •	setStroke 
 •	setPaint 
 •	setComposite 
 •	setTransform 
 •	setClip 
 •	setFont 
 •	setRenderingHints 
Примеры: в которых рисуются различные параметры для атрибутов рисования:
  1. g.setColor(Color.BLUE);
  2. BasicStroke dashed = new BasicStroke(5.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
  3. 10.0f, new float []{10.0f, 20.f, 50.0f}, 0.0f);
  4. Graphics2D g2 = (Graphics2D) g;
  5. g2.setStroke(dashed);
  6. g2.draw(new Ellipse2D.Double(100 , 100 , 200 , 200));
Здесь используется полная версия конструктора BasicStroke: первый параметр – толщина линии. Второй параметр - стиль линии на окончании.



Третий параметр – тип линии используемой при соединении двух фрагментов.



Пятый параметр - здесь вы должны указать стиль чередования штриховки – элементы массива задают, то сплошную линию, то пропуск.

Следующий параметр - начальное состояние линии штриховки.

Теперь попробуем нарисовать заливку прямоугольника. В первом случае используется сплошная красная заливка, во втором же градиент, переходящий от красного (в левом верхнем углу) до белого (в правом нижнем):
  1. g2.setPaint(Color.red);
  2.  g2.fill(new Rectangle2D.Double(20, 20,100, 100));
  3.  // и второй – с градиентом
  4.  GradientPaint redtowhite = new GradientPaint(20,20,Color.RED,20+100, 20+100,Color.WHITE);
  5.  g2.setPaint(redtowhite);
  6.  g2.fill(new Rectangle2D.Double(20, 20,100, 100));
Следующий пример более сложен, чем предыдущие и демонстрирует методику того, как можно нарисовать на форме картинку взятую из внешнего файла.
  1. BufferedImage bum = new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
  2.  Image img = getToolkit().getImage("C:\\CA_LIC\\boat.gif");
  3.  MediaTracker tracker = new MediaTracker(this);
  4.  tracker.addImage(img, 0);
  5.  try {
  6.   tracker.waitForID(0 , 2000);
  7.  } 
  8.  catch (InterruptedException e) {
  9.     e.printStackTrace();
  10.  }
  11.  Image img2 = img.getScaledInstance(100 , -100 , Image.SCALE_SMOOTH) ;
  12.  tracker.addImage(img2 , 1);
  13.  try {
  14.    tracker.waitForID(1 , 2000);
  15.  }
  16.  catch (InterruptedException e) {
  17.    e.printStackTrace();
  18.  }
  19.  
  20.  bum.getGraphics().drawImage(img2, 0 , 0 , this );
  21.  TexturePaint tpaint = new TexturePaint(bum , new Rectangle2D.Double(10, 10, 20 , 20));
  22.  Graphics2D g2 = (Graphics2D )g;
  23.  g2.setPaint(tpaint);
  24.  g2.fill(new RoundRectangle2D.Double(50, 50, 200, 200, 10, 10));
В этом примере используется множество дополнительных классов и используется правильная методика загрузки изображений для последующей обработки.

Так как исторически java разрабатывалась для того, чтобы работать в среде intenet, то предполагалось, что загрузка изображения может занимать значительное время и даже быть неуспешной. Поэтому, когда мы работаем с изображениями возникает вопрос о том, загружено оно или нет.

Базовым классом для всех изображений служит класс Image. Для изображений которые уже загружены используется класс BufferedImage. Однако перед тем, как создать данное изображение, необходимо дождаться его загрузки. Для этого используется объект типа MediaTracker. При создании которого в качестве параметра конструтора следует указать любой визуальный элемент управления. Затем необходимо добавить изображение в список ожидающих загрузки, давая при этому ему порядковый учетный номер, и затем ожидаем загрузки. Следующий шаг (более для демонстрации, чем для необходимости) – масштабирование изображения. Для этого используем вызов:
  1. img.getScaledInstance(100 , -100 , Image.SCALE_SMOOTH);
В качестве параметров задается новый размер. Если какой-то параметров задан как отрицательный, то этот параметр не учитывается для того, чтобы масштабирование было бы равномерным. Последним параметром задается способ преобразования. В составе класса Image предопределен список констант начинающихся со SCALE_XXX. Затем создается объект заливки фона на базе буферизованного изображения и активной зоны – прямоугольника. Последний шаг - рисуем прямоугольник с загругленной фаской.

Следующим шагом будет управление параметрами рисования – рендеринга. Все использованные параметры (константы) задаются в классе RenderingHints.
  1. Graphics2D g2 = (Graphics2D) g;
  2. g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  3. g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
  4. g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC);
Особый интерес представляет возможность создания заливок по форме текста. Визуально это похоже на "бюджетный" WordArt из msoffice.
  1. TextLayout textTl = new TextLayout("Hello From J",
  2.     new Font("Helvetica", Font.ITALIC, 48),
  3.     g2.getFontRenderContext());
  4. textTl.draw(g2 , 150 , 150);
Для рисования некоторого набора предопределенных фигур или для использования их в качестве зоны отсечения, в составе пакета java.awt.geom были определены стандартные геометрические примитивы:
Arc2D Ellipse2D QuadCurve2D
Area GeneralPath Rectangle2D
CubicCurve2D Line2D RectangularShape
Dimension2D Point2D RoundRectangle2D
Все эти фигуры, кроме Point2D и Dimension2D, поддерживают интерфейс Shape. В этом интерфейсе находятся методы описывающие фигуры. И, соответственно, при желании вы можете создать собственный класс, который будет представлять нестандартную геометрическую фигуру.

Для представления фигур похожих на прямоугольник служат классы: Rectangle2D, RoundRectangle2D, Arc2D, и Ellipse2D.



Класс QuadCurve2D позволяет создать кривую, описываемую (тавтология ...) кривой второго порядка (определяется, такая кривая двумя точками на ее краях и контрольной точкой). Соответственно, CubicCurve2D задает кривую третьего порядка, для ее задания вам нужны две конечные точки и уже две управляющие точки.



Особый интерес представляет класс GeneralPath. Который позволяет задать фигуру по опорным точкам.


  1. g.setColor(Color.BLUE); 
  2. Shape s = new QuadCurve2D.Double (50,50, 25, 78, 120 , 80);
  3. ((Graphics2D) g).draw(s);
  4.  
  5. GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD,12);
  6. path.reset();
  7. double alp30 = Math.PI / 6;
  8. path.moveTo(0,0);
  9. int cx = getSize().width / 2;
  10. int cy = getSize().height / 2;
  11. path.moveTo((float)(cx + 100), cy);
  12.  
  13. for (int i = 1; i <= 12; i++){
  14.   float x  = (float)(cx + 100 * Math.cos (i*alp30));
  15.   float y  = (float)(cy + 100 * Math.sin (i*alp30));
  16.   path.lineTo(x,y);
  17. }
  18.  
  19. ((Graphics2D) g).draw(path);
Особый интерес представляет собой комбинирование фигур – для этого используется класс Area:
  1. Area ar = new Area(path);
  2. ar.exclusiveOr(new Area (new Ellipse2D.Double(100 , 100 , 50 , 50)));
  3. ((Graphics2D) g).draw(ar);
В составе класса Area есть методы для всех булевых операций над объектами:
  1. Area ar = new Area(path);
  2. Area rhs = new Area (new Ellipse2D.Double(100 , 100 , 50 , 50));
  3. ar.exclusiveOr(rhs); // операция XOR исключающего ИЛИ
  4. ar.add(rhs); // операция объединения фигур
  5. ar.intersect(rhs); // пересечение фигур
  6. ar.subtract(rhs); // разница фигур

Трансформация фигур, текста и изображений



Возвращаясь к параметрам управляющими Graphics2D, рассмотрим как можно трансформировать объекты. Вы можете выполнять над графическими объектами операции: перемещение, вращение, масштабирование, усечение объектов на стадии рендеринга. Все эти преобразования задаются с помощью объектов производных от AffineTransform.

Важно: все афинные преобразования не приводят к переходу к новой системе координат, в которой, например, линии, которые были не параллельны стали бы таковыми или наоборот.

Класс Graphics2D предоставляет несколько методов для трансформации. Вам нужно создать объект AffineTransform и затем вызвать метод transform в составе Graphics2D.

Для создания объекта AffineTransform необходимо воспользоваться фабричными методами в составе AffineTransform:

Матрицы преобразования для всех этих операций:


  1. // создаем матрицу вращения
  2. AffineTransform rot =  AffineTransform.getRotateInstance (Math.PI / 12 , 100,100);
  3. // масштабирования
  4. AffineTransform scal = AffineTransform.getScaleInstance (0.5 , 0.33);
  5. // урезания
  6. AffineTransform shear = AffineTransform.getShearInstance (0.5 , 0.33);
  7. // перемещения
  8. AffineTransform transl = AffineTransform.getTranslateInstance(50 , -70);
  9. // применяем матрицу к графическому контексту
  10. g2.setTransform(rot);
  11. // и чего-то рисуем
  12. textTl.draw(g2 , 150 , 150);
или так:
  1. AffineTransform trans = AffineTransform.getRotateInstance(0);
  2. trans.rotate(Math.PI / 6, 100 , 100);
  3. trans.scale(1.5, 0.33);
  4. trans.translate(15 , 40);
  5. g2.setTransform(trans);
  6. textTl.draw(g2 , 150 , 150);

Обработка изображений



Примечание: часто объекты, которые необходимо отрисовать на окне имеют уникальные размеры и форму. Такую, что ее трудно воспроизвести с помощью Shape и Clip-отсечений для Graphics2D. Тогда мы создаем изображение в виде GIF с прозрачным цветом (в java поддержка прозрачности GIF есть по-умолчанию, просто загрузите GIF и используйте его при вызове Graphics.drawImage).

Наблюдатели: как я говорил ранее загрузка изображения может занимать некоторое время в общем случае для ожидания используется класс MediaTracker мы его уже использовали ранее, но он является слишком тяжелым инструментом не позволяющим отслеживать процесс загрузки изображения. Для этого используются классы наблюдателей: Observer.
  1. ImageObserver obs = new ImageObserver() {
  2.     public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
  3.          String mes ="";
  4.          switch (infoflags){
  5.               case ImageObserver.ABORT: mes = " Создание изображения завершилось неудачно";break;
  6.               case ImageObserver.ALLBITS: mes = " Изображение полностью получено";break;
  7.               case ImageObserver.ERROR: mes = " Ошибка";break;
  8.               case ImageObserver.FRAMEBITS: mes = " Получен еще один кадр изображения";break;
  9.               case ImageObserver.HEIGHT: mes = " Получена высота изображения: height = " + height;break;
  10.               case ImageObserver.PROPERTIES: mes = " Получены свойства изображения ";break;
  11.               case ImageObserver.SOMEBITS: mes = " Получен следующий фрагмент изображения";break;
  12.               case ImageObserver.WIDTH: mes = " Получена ширина изображения: width = " + width;break;
  13.          }
  14.          System.out.println ("Img: " + img + " = " + mes);
  15.          return true;
  16.     }
  17. };
  18. Graphics2D g2 = (Graphics2D )g;
  19. g2.drawImage(getToolkit().getImage("C:\\CA_LIC\\boat.gif") , AffineTransform.getRotateInstance(Math.PI / 4), obs);
Для получения любой информации о изображении используются методы класса Image. Например: обратите внимание на то, что в качестве параметров методам передается ссылка на объект “наблюдатель”.
  1. Image i;
  2.  i.getWidth(new ImageProducer (){… код метода ….});
  3.  i.getHeight(new ImageProducer (){… код метода ….});
Критически важно: при вызове метода:
  1. getToolkit().getImage("C:\\CA_LIC\\boat.gif");
Я могу загрудать только файлы в формате JPEG, GIF, PNG. Также я довольно вальяжно вызывал данный метод в событии перерисовки (каждое событие – вызов getImage) все дело в том, что класс Toolkit выполняет кэширование изображений и некоторое время сохраняет его в памяти.



Для представления производителя изображения используется интерфейс ImageProducer. Учитывайте, что в качестве источника изображения может быть не только файл, а все что угодно (лишь бы оно поддерживало данный интерфейс). В примере далее используется класс MemoryImageSource, который позволяет создать изображение по содержимому массива чисел. Если у вас есть объект Image, то для получения ссылки на его производителя воспользуйтесь методом getSource.
  1. MemoryImageSource msource = new MemoryImageSource(
  2.  4,7,
  3.  new int []{
  4.   0xFFFF0000,0xFFFF0000,0xFFFF0000,0xFFFF0000,
  5.   0xFFFF0000,0xFF00FF00,0xFF00FF00,0xFFFF0000,
  6.   0xFFFF0000,0xFF00FF00,0xFF00FF00,0xFFFF0000,
  7.   0xFFFF0000,0xFF0000FF,0xFF0000FF,0xFFFF0000,
  8.   0xFFFF0000,0xFF00FF00,0xFF00FF00,0xFFFF0000,
  9.   0xFFFF0000,0xFF0000FF,0xFF0000FF,0xFFFF0000,
  10.   0xFFFF0000,0xFFFF0000,0xFFFF0000,0xFFFF0000
  11.  }
  12.  , 0, 4
  13. );
  14. Image img = getToolkit().createImage(msource);
Следующим шагом будет управление изображениями. Вы должны понимать что между тем как файл изображения был прочитан из файла или получен по сети и тем как он был отрисован на некотором контексте устройства, проходит несколько стадий, в которые мы можем вмешаться и изменить результат изображения.

И где-то в коде метода paint (Graphics g) вызываем метод:
  1. Graphics2D g2 = (Graphics2D )g;
  2.  g2.drawImage(img, 20, 20 , 50*4, 50*7, this);
Для представления потребителя используйте интерфейс ImageConsumer. Для связывания или, точнее, обертки источника изображений с помощью фильтра используйте класс ImageFilter. Для демонстрации методов работы с ними будет использован пример, когда изображение становится более ярким или более тусклым. Однако технически нам потребуется перейти от представления цвета в стиле RGBA к цветовой модели HSB. Данная схема представляет цветовое пространство в виде опрокинутой пирамиды, вершина которой расположена в центре координат. От вершины перпендикулярно плоскости цветов уходит ось яркости, которую мы и будем изменять.



Врезка: общее представление о цветовых моделях. Модель HSB (Hue Saturation Brightness = Тон Насыщенность Яркость) построена на основе субъективного восприятия цвета человеком. Предложена в 1978 году. Эта модель тоже основана на цветах модели RGB, но любой цвет в ней определяется своим цветом (тоном), насыщенностью (то есть добавлением к нему белой краски) и яркостью ( то есть добавлением к нему черной краски). Фактически любой цвет получается из спектрального добавлением серой краски. Эта модель аппаратно-зависимая и не соответствует восприятию человеческого глаза, так как глаз воспринимает спектральные цвета как цвета с разной яркостью (синий кажется более темным, чем красный), а в модели HSB им всем приписывается яркость 100%. Модель является аппаратно-зависимой. H определяет частоту света и принимает значение от 0 до 360 градусов. V или B: V - значение (принимает значения от 0 до 1) или B - яркость, определяющая уровень белого света (принимает значения от 0 до 100%). Являются высотой конуса. S - определяет насыщенность цвета. Значение ее является радиусом конуса.

В составе класса Color есть методы для преобразования между цветовой моделью CMYK и RGB: HSBtoRGB и RGBtoHSB:
  1. Graphics2D g2 = (Graphics2D) g;
  2. Image img_orig = getToolkit().getImage("C:\\CA_LIC\\boat.gif");
  3. final float value_correct = (float) Math.random();
  4. ImageProducer prod = new FilteredImageSource(img_orig.getSource(),
  5.   new RGBImageFilter() {
  6.   public int filterRGB(int x, int y, int rgba) {
  7.   int alpha = rgba & 0xFF000000;
  8.   Color c = new Color(rgba);
  9.   float[] hsb = new float[3];
  10.   Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), hsb);
  11.   hsb[2] *= value_correct;
  12.   hsb[2] = Math.max(0.0f, Math.min(1.0f, hsb[2]));
  13.   return alpha | Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
  14.   //return c.getAlpha() | Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
  15.   }
  16. });
  17. g2.drawImage(getToolkit().createImage(prod), AffineTransform.getRotateInstance(Math.PI / 4), obs);

Categories: Java