Зміст
На чому малювати?
Можливість малювати на елементах керування (JFrame, JPanel, JButton та інших), успадкованих від класу Component, забезпечено класами Graphics і Graphics2D. Успадковані з класу Component методи
paint (Graphics g) paintComponent (Graphics g)
повертають графічний контекст — об'єкт, у якому і здійснюють малювання.
Аргумент Graphics g створює система, а програма бере його в готовому вигляді і використовує для малювання. Нижче приклади методів подано у застосуванні до такого елемента g. Назву методу записують через крапку після назви такого елемента, як це зроблено у поданих нижче прикладах.
Клас Graphics має підклас Graphics2D, який істотно розширює можливості малювання. Наприклад, дає можливість задавати товщину ліній. Щоб отримати доступ до методів Graphics2D, потрібно привести тип графічного контексту до цього класу. Як це зробити? Нижче після опису методів роботи з кольором, графічними примітивами й текстом подано приклад використання Graphics2D з малюванням на представнику JPanel (використання JFrame не дає можливості задати колір тла).
Кольори
Color с = new Color(r, g, b);
— створення кольору с з одночасно цілими (int від 0 до 255) або дробовими (від 0. до 1.) інтенсивностями кольорів r, g, b моделі RGB;
Color с = new Color(r, g, b, a);
— створення кольору с з одночасно цілими (int від 0 до 255) або дробовими (від 0. до 1.) інтенсивностями кольорів r, g, b, a моделі RGBA, де α-канал визначає прозорість (0 повна прозорість);
Color с = new Color(0xFFrrggbb);
— створення кольору с з цілими інтенсивностями кольорів rr, gg, bb моделі RGBA, записаними у 16-ій системі числення;
Color с = new Color(0xaarrggbb, hasA);
— створення кольору с з цілими інтенсивностями кольорів aa, rr, gg, bb моделі ARGB, записаними у 16-ій системі числення. При hasA = true значення інтенсивності α-каналу буде враховано, інакше (при значенні false) колір буде непрозорим;
Color с = new Color(0xaarrggbb, hasA);
— створення кольору с з цілими інтенсивностями кольорів aa, rr, gg, bb моделі ARGB, записаними у 16-ій системі числення. При hasA = true значення інтенсивності α-каналу буде враховано, інакше (при значенні false) колір буде непрозорим;
Color с = new Color.*;
— створення кольору с, де замість зірочки потрібно записати назву одного з 13 стандартних: black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow (замість малих літер можна використати й великі);
getHSBColor(hue, saturation, brightness);
— повертає колір з дробовими (від 0. до 1.) значеннями параметрів hue, saturation, brightness моделі HSB;
Примітка.
У класі SystemColor, що розширює клас Color, зібрано сталі, які виражають системні кольори екрану, вікна, меню, тексту, різних складових системи. З їх допомогою можна оформити застосунок стандартними кольорами графічної оболонки операційної системи.
Методи класу Color дозволяють отримати складові поточного кольору: getRed, getGreen, getBlue, getAlpha, getRGB, getColorSpace, getComponents.
Два методи створюють яскравіший brighter і темніший darker кольори у порівнянні з поточним кольором. Вони корисні, якщо потрібно виділити зображення активної складової яскравим кольором або, навпаки, показати неактивну складову блідніше інших складових.
Два статичних методи повертають колір, перетворений з колірної моделі RGB в модель HSB і назад:
int HSBtoRGB (int hue, int saturation, int brightness); float [] RGBtoHSB (int red, int green, int blue, float [] hsb);
g.drawLine(x1, y1, x2, y2);
— малювання відрізку прямої з кінцями у точках (x1, y1), (x2, y2);
g.drawRect(x, y, w, h);
— малювання прямокутника, де x, y — координати верхнього лівого кута, w — ширина по горизонталі, h — висота по вертикалі;
g.draw3DRect (x, y, w, h, raised);
— малювання прямокутника, що ніби виділяється з площини малювання, якщо параметр raised дорівнює true, або ніби втиснутий у площину, якщо параметр raised дорівнює false. Як і раніше, x, y — координати верхнього лівого кута, w — ширина по горизонталі, h — висота по вертикалі;
g.drawOval(x, y, w, h);
— малювання овалу, вписаного у прямокутник, в якому x, y — координати верхнього лівого кута, w — ширина по горизонталі, h — висота по вертикалі;
g.drawArc(x, y, w, h, angle, arc);
— малювання дуги овалу, вписаного у прямокутник, заданий першими чотирма параметрами. Дуга має величину arc градусів і відраховується від кута angle. Кут відраховують у градусах від додатного напрямку осі Ox. Додатний напрям відладання кутів — проти напрямку руху годинникової стрілки. Всі параметри мають цілий тип;
g.drawRoundRect(x, y, w, h, wa, hа);
— малювання прямокутника із закругленими краями. Заокруглення — четвертинки овалів, вписаних у прямокутники шириною wa і висотою ha, побудовані в кутах основного прямокутника;
drawPolyline(x, y, n);
— малювання незамкненої ламаної з n вершинами, цілі координати яких збережено у масивах x, y;
drawPolygon(x, y, n);
— малювання замкненої ламаної ламану з n вершинами, цілі координати яких збережено у масивах x, y;
g.drawPolygon (poly);
— малювання многокутника poly, який попередньо потрібно створити. Наприклад, таким чином.
int [] aX = { 20, 100, 100, 250, 250, 20, 20, 50};// абсциси вершин 8-кутника int [] aY = {180, 180, 200, 200, 220, 200, 200, 190};// ординати вершин 8-кутника Polygon poly = new Polygon (aX, aY, 8); // 8-кутник - об'єкт класу Polygon
poly.addPoint(x, y); — долучення точки з вершиною (x, y) до многокутника poly.
Логічні (boolean) методи contains дозволяють перевірити, чи не лежить у багатокутнику точка чи прямокутник зі сторонами, паралельними сторонам екрана:
contains (int x, int y); contains (double x, double y); contains (Point p); contains (Point2D p); contains (double x, double y, double width, double height); contains (Rectangle2D rectangle);
Логічні (boolean) методи intersects дозволяють перевірити, чи не перетинається з даними багатокутником відрізок прямої, заданий параметрами методу, або прямокутник зі сторонами, паралельними сторонам екрана:
intersects (double x, double y, double width, double height); intersects (Rectangle2D rectangle);
Методи getBounds і getBounds2D повертають прямокутники класу Rectangle і класу Rectangle2D відповідно, що цілком містять в собі даний багатокутник (описані навколого нього).
Заповнення
Заповнені кольором фігури малюють з допомогою таких методів класу Graphics: fillRect, fill3DRect, fillOval, fillArc, fillRoundRect, fillPolygon, . Вони мають такі самі параметри, як і відповідні методи, що малюють лише межі фігур. Тобто ті, в яких замість префікса fill використано префікс draw.
g.fillRect(x, y, w, h);
— малювання зафарбованого прямокутника, де x, y — координати верхнього лівого кута, w — ширина по горизонталі, h — висота по вертикалі;
g.fill3DRect (x, y, w, h, raised)
— малювання зафарбованого прямокутника, що ніби виділяється з площини малювання, якщо параметр raised дорівнює true, або ніби втиснутий у площину, якщо параметр raised дорівнює false. Як і раніше, x, y — координати верхнього лівого кута, w — ширина по горизонталі, h — висота по вертикалі;
g.fillOval(x, y, w, h);
— малювання зафарбованого овалу, вписаного у прямокутник, в якому x, y — координати верхнього лівого кута, w — ширина по горизонталі, h — висота по вертикалі;
g.fillArc(x, y, w, h, angle, arc);
— малювання зафарбованого сектора овалу, вписаного у прямокутник, заданий першими чотирма параметрами. Дуга має величину arc градусів і відраховується від кута angle. Кут відраховують у градусах від додатного напрямку осі Ox. Додатний напрям відладання кутів — проти напрямку руху годинникової стрілки. Всі параметри мають цілий тип;
g.fillRoundRect(x, y, w, h, wa, hа);
— малювання зафарбованого прямокутника із закругленими краями. Заокруглення — четвертинки овалів, вписаних у прямокутники шириною wa і висотою ha, побудовані в кутах основного прямокутника;
fillPolygon(x, y, n);
— малювання зафарбованого n-кутника, цілі координати яких збережено у масивах x, y;
g.fillPolygon (poly);
— малювання зафарбованого многокутника poly, який попередньо потрібно створити. Наприклад, таким чином.
int [] aX = { 20, 100, 100, 250, 250, 20, 20, 50};// абсциси вершин 8-кутника int [] aY = {180, 180, 200, 200, 220, 200, 200, 190};// ординати вершин 8-кутника Polygon poly = new Polygon (aX, aY, 8); // 8-кутник - об'єкт класу Polygon
Текст
Виведення тексту в область малювання поточним кольором і шрифтом, починаючи з точки (x, y) — лівої нижньої точки першої літери тексту на базовій лінії (baseline) виведення шрифту — здійснюють такими методами класі Graphics:
drawBytes (b, j, l, x, y)
— виводить l елементів масиву байтів b, починаючи з індексу j;
drawChars (c, j, l, x, y)
— виводить l елементів масиву символів c, починаючи з індексу j;
drawString (i, x, y);
— виводить текст, занесений в об'єкт і класу AttributedCharacterIterator. Це дозволяє задавати свій шрифт для кожного символу, що виводять.
Робота з шрифтами
Об'єкти класу Font зберігають накреслення символів, що утворюють шрифт. Їх можна створити двома конструкторами:
Font (Map a)
— задає шрифт із зазначеним аргументом a класу Map. Ключі атрибутів і деякі значення задають сталими класу TextAttribute з пакета java.awt.font;
Font (name, style, size) — задає шрифт з такими параметрами:
name — рядок назви. Назвою шрифту name може бути рядок з фізичною назвою шрифту. Наприклад, "Courier New". Або "Dialog", "Dialoginput", "Monospaced", "SansSerif", "Serif", "Symbol". Це так звані логічні назви шрифтів (logical font names). Якщо name == null, то задають шрифт як усталено;
style — стиль (ціле) — одна зі сталих класу Font:
Напівжирний курсив можна задати побітовим складанням:
Font.bold | Font.italic
size — розмір — ціле число друкарських пунктів. В англо-американській системі мір пункт дорівнює 1/72 частини англійського дюйма (0,351 мм). Цей пункт застосовують у комп'ютерній графіці.
При виведенні тексту логічним назвам шрифтів і стилів зіставляються фізичні назви шрифтів (font face name) або назви родин шрифтів (font name). Це назви шрифтів, наявних у графічній підсистемі операційної системи. Наприклад, логічній назві "Serif" можна співставити назву родини шрифтів "Times New Roman", а у поєднанні зі стилями — конкретні фізичні назви "Times New Roman Bold", "Times New Roman Italic". Ці шрифти повинні перебувати у складі шрифтів графічної системи того ПК, на якій виконують застосунок. Встановлення JDK гарантує наявність щонайменше родини шрифтів Lucida.
При виведенні рядка тексту у вікно програми дуже часто виникає необхідність розташувати його певним чином щодо інших елементів зображення: центрувати, вивести над або під іншим графічним об'єктом, ліворуч чи праворуч від нього. Для цього треба знати розміри рядка, наприклад, його висоту й ширину. Можуть знадобитися й інші лінійні розміри шрифту — див. малюнок у повноекранному режимі з такими підписами виділених довжин та ліній.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Результат виконання коду | Частина коду з методами класу AffineTransform з переліком перетворень, які застосовано послідовно. |
---|---|
// var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); // at.preConcatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); // at.preConcatenate(AffineTransform.getScaleInstance (0.5, 0.5)); // g2.setTransform (at); Тотожнє перетворення. | |
var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); // at.preConcatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); // at.preConcatenate(AffineTransform.getScaleInstance (0.5, 0.5)); g2.setTransform (at); Поворот на 90° проти напрямку руху годинникової стрілки навколо "точки згину" з координатами (60, 60). | |
var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); at.preConcatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); // at.preConcatenate(AffineTransform.getScaleInstance (0.5, 0.5)); g2.setTransform (at);
| |
var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); at.preConcatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); at.preConcatenate(AffineTransform.getScaleInstance (0.5, 0.5)); g2.setTransform (at);
|
Приклад 3 проекту graphtest містить два класи:
Порівняно з Прикладом 2 у класі Panel метод preConcatenate замінено на сoncatenate. Інакше кажучи, замінено порядок виконання перетворень.
Результат виконання коду | Частина коду з методами класу AffineTransform з переліком перетворень, які застосовано послідовно. |
---|---|
// var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); // at.concatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); // at.concatenate(AffineTransform.getScaleInstance (0.5, 0.5)); // g2.setTransform (at); Тотожнє перетворення. | |
var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); // at.concatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); // at.concatenate(AffineTransform.getScaleInstance (0.5, 0.5)); g2.setTransform (at); Поворот на 90° проти напрямку руху годинникової стрілки навколо "точки згину" з координатами (60, 60). | |
var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); at.concatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); // at.concatenate(AffineTransform.getScaleInstance (0.5, 0.5)); g2.setTransform (at);
| |
var at = AffineTransform.getRotateInstance(-Math.PI/2., 60.0, 60.0); at.concatenate(AffineTransform.getTranslateInstance (50.0, 0.0)); at.concatenate(AffineTransform.getScaleInstance (0.5, 0.5)); g2.setTransform (at);
|
Клас BasicStroke
Основний конструктор класу
BasicStroke (width, cap, join, miter, dash, float dashBegin);
визначає такі властивості пера:
miter — відстань, починаючи з якої застосовують сполучення join, тип float;
dashBegin — індекс, починаючи з якого перебирають елементи масиву dash, тип float.
Решта конструкторів задають деякі характеристики як усталено:
BasicStroke (width, cap, join, miter) — суцільна лінія;
BasicStroke (width, cap, join)
— суцільна лінія, у якій при сполученні join_miter задають значення miter = 10.0;
BasicStroke (width)
— прямий обріз cap_square і сполучення join_miter зі значенням miter = 10.0;
BasicStroke () — ширина 1.0.
Після створення пера одним з перелічених вище конструкторів і встановлення властивостей пера методом setStroke можна малювати різні фігури:
Загальні властивості фігур, які можна намалювати методом draw класу Graphics2D, описано в інтерфейсі Shape. Даний інтерфейс реалізований для створення звичайного набору фігур — прямокутників, прямих, еліпсів, дуг, точок відповідними класами Rectangle2D, RoundRectangle2D, Line2D, Ellipse2D, Arc2D, Point2D пакунку java.awt.geom. У цьому пакунку є ще класи CubicCurve2D і QuadCurve2D для створення кривих третього і другого порядку. Всі ці класи абстрактні, але існують їх реалізації — вкладені класи Double і Float для завдання координат числами відповідного типу. У наступному прикладі використано класи Rectangle2D.Double, Line2d.Double і Ellipse2D.Double для креслення прямокутників, відрізків, еліпсів.
Приклад 4 проекту graphtest містить два класи:
і створює вікно такого вигляду.
Клас GeneralPath
Входить до пакунку java.awt.geom. Об'єкти цього класу можуть містити складні конструкції, складені з відрізків прямих або кривих ліній та інших фігур, сполучених або не сполучених між собою. Цей клас реалізує інтерфейс Shape, представники якого самі є фігурами і можуть бути елементами інших об'єктів класу GeneralPath.
Алгоритм 1 побудови представника класу GeneralPath
До створеного об'єкта додати необхідні фігури s методом append(s, connect):
при справдженні булевої змінної connect (значення true) нову фігуру s з'єднують з уже наявними за допомогою поточного пера;
при хибності булевої змінної connect (значенні false) нову фігуру s не з'єднувати з уже наявними.
Клас GeneralPath має інший спосіб побудови шляху з відрізків прямих, квадратних і кубічних кривих. Таким чином можна створити об'єкт GeneralPath об'єкт, задавши одне з правил визначення належності точки внутрішнім точкам фігури (правило намотування) залежно від кількості обертів вектора, спрямованого з даної точки до точки, що рухається межею фігури:
Cтворити порожній об'єкт класу GeneralPath конструктором GeneralPath();
Додати частини фігури, використавши такі методи з дійсними аргументами (тип float):
moveTo (x, y) — переміщення поточної точки у точку (x, y);
lineTo (x, y) — долучення відрізку прямої з поточної точки до точки (x, y);
quadTo (u, v, x, y) — долучення квадратної кривої з поточної точки до точки (x, y) і контрольною точкою (u, v) — точкою поперетину дотичних до кривої, проведених з початкової та кінцевої точок кривої Безьє;
curveTo(u, v, w, z, x, y) — долучення кубічної кривої з поточної точки до точки (x, y) і контрольними точками (u, v), (w, z) — точками на дотичних до кривої, проведених відповідно з початкової та кінцевої точок кривої Безьє;
closePath () — закриття поточного шляху.
Приклад 5 проекту graphtest містить два класи:
і створює вікно такого вигляду.
Способи заповнення фігур визначено в інтерфейсі Paint. Наразі Java 2D містить кілька втілень цього інтерфейса — класи Color, GradientPaint, TexturePaint, абстрактний клас MultipleGradientPaint і його розширення LinearGradientPaint і RadialGradientPaint. Клас Color нам відомий. Розглянемо способи заповнень класів GradientPaint і TexturePaint.
Клас GradientPaint
Надає можливість градієнтного заповнення таким чином:
у точці, A(x1, y1) встановлюють колір с1;
у точці, B(x2, y2) встановлюють колір с2;
колір заповнення майже лінійно за інтенсивністю кожної складової змінюється від с1 до с2 вздовж відрізка [AB], залишаючись сталим уздовж кожної прямої, перпендикулярної до прямої (AB);
у півплощині, яка не містить точки B і обмежена перпендикуляром до прямої (AB), що проходить через точку A, встановлюють колір с1;
у півплощині, яка не містить точки A і обмежена перпендикуляром до прямої (AB), що проходить через точку B, встановлюють колір с2.
Таке заповнення створює конструктор з дійсними аргументами x1, y1, x2, y2 типу float і аргументами с1, с2 типу Color
GradientPaint (x1, y1, c1, x2, у2, c2);
GradientPaint (x1, y1, c1, x2, у2, c2, cyclic);
— конструктор при такому самому тлумаченні перших шести аргументів, як і для попередньо розглянутого конструктора, і при значенні булевого аргумента cyclic == true циклічно повторює заповнення вздовж прямої (AB) без дотримання двох останніх властивостей щодо сталості кольору у півплощинах.
Обидва конструктори мають конструктори-аналоги, у яких пару координат замінено на відповідні об'єкти класу Point2D.
Клас TexturePaint
можна використати таким чином (назви створюваних об'єктів можуть бути іншими, синтаксис вказівок подано у наступному прикладі).
Створити об'єкт buffer класу BufferedImage пакунку java.awt.image, вказавши як аргументи конструктора розміри області у пікселях і тип, що задають однією з таких сталих класу BufferedImage:
Заповнити графічний контекст буфера зображенням, яке слугуватиме зразком заповнення (так само, як до цього заповнювали звичайні графічні контексти при малюванні).
Створити об'єкт класу TexturePaint конструктором
TexturePaint (buffer, r);
попередньо створивши прямокутник r — об'єкт класу Rectangle2D, розміри якого є розмірами зразка заповнення.
Після створення заповнення — об'єкта класу Color, GradientPaint або TexturePaint — його встановлюють у графічному контексті методом setPaint і використовують у подальшому методом fill.
Приклад 6 проекту graphtest містить два класи:
і створює вікно такого вигляду.
Класи LinearGradientPaint і RadialGradientPaint
Надають можливість створити градієнтне заповнення декількома кольорами уздовж прямої або радіально. Загальні властивості цих класів зібрано в абстрактному класі MultipleGradientPaint.
Алгоритм використання класу LinearGradientPaint
Задати координати (тип float) точок A(x1, y1) і B(x2, y2) — кінців відрізка, уздовж якого буде здійснено перехід між кольорами.
Відрізок [A, B] поділити на кілька частин точками, вказавши для кожної точки поділу С (у тому числі для точок A, B) координату — дійсне число (тип float) з відрізка [0, 1], що дорівнює відношенню відстаней AC : AB. З координат усіх точок поділу сформувати деякий масив t (назва може бути й іншою) з урахуванням такого:
Для кожної точці поділу відрізка [A, B] задати свій колір. Усі кольори занести у деякий масив с (назва може бути й іншою).
Створити представника класу LinearGradientPaint таким конструктором:
LinearGradientPaint (x1, y1, x2, у2, t, c);
Для радіального заповнення створюють представника класу RadialGradientPaint таким конструктором:
RadialGradientPaint (x1, у1, r, t, c);
Тут дійсні координати x1, y1 задають центр кола A, дійсне число r — довжина його радіуса AB. Тлумачення масивів t, c — таке саме, як і для LinearGradientPaint.
Кольори поза відрізком [AB] задають сталими вкладеного класу MultipleGradientPaint.cycleMethod:
NO_CYCLE — використати перший і останній кольори (як усталено);
REFLECT — зі зміною порядку перебору кольорів на протилежний при переході через точки, утворені відкладанням відрізків, рівних [AB], на прямій (AB) по різні боки від кінців відрізка [AB];
REPEAT — циклічно перебираються кольору від першого до останнього, без зміни порядку перебору кольорів на протилежний при переході через точки, утворені відкладанням відрізків, рівних [AB], на прямій (AB) по різні боки від кінців відрізка [AB].
Ці сталі вказують як останній необов'язковий аргумент останніх двох згаданих конструкторів.
Можна також використати конструктори LinearGradientPaint i RadialGradientPaint, у яких замість пари координат використовують об'єкт класу Point2D.
Після створення заповнення — об'єкта класу LinearGradientPaint або RadialGradientPaint — його встановлюють у графічному контексті методом setPaint і використовують у подальшому при використанні методу fill.
Приклад 7 проекту graphtest містить два класи:
і створює вікно такого вигляду.
Виведення тексту засобами Java 2D
Шрифт — об'єкт класу Font — крім назви, стилю і розміру має ще півтора десятка властивостей: підкреслення, перекреслення, нахил, колір шрифту й колір тла, ширину й товщину символів, афінні перетворення, розташування зліва направо чи справа наліво.
Властивості шрифту задають за допомогою статичних сталих класу TextAttribute. Перелічимо Найчастіше використовувані властивості.
Не всі шрифти дозволяють задати всі атрибути. Переглянути список допустимих атрибутів для даного шрифту можна, імпортувавши відповідний клас:
import java.text.AttributedCharacterIterator;
і використавши його, наприклад, таким чином:
Font f = new Font ( "Times New Roman", Font.BOLD, 12); AttributedCharacterIterator.Attribute [] a = f.getAvailableAttributes (); for (int i = 0; i < a.length; i ++) System.out.println (a[i]);
У результаті буде виведено перелік властивостей.
Клас Font має конструктор Font (Map attributes), яким можна відразу надати потрібні значення властивостям створюваного шрифту. Це вимагає попереднього запису атрибутів у спеціально створений об'єкт класу, що реалізовує інтерфейс Map: класу HashMap, WeakHashMap або Hashtable. Наприклад, таким чином:
import java.util.HashMap; import java.awt.font.TextAttribute; … HashMap hm = new HashMap (); hm.put (TextAttribute.SIZE, (60.0f)); hm.put (TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); Font f = new Font (hm); …
Можна створити шрифт конструктором, використаним наприкінці вже розглянутого коду, а потім додавати і змінювати властивості методом deriveFont класу Font.
Текст в Java 2D володіє власним контекстом — об'єктом класу FontRenderContext, що зберігає всі дані, необхідні для виведення тексту. Отримати його можна методом getFontRenderContext класу Graphics2D.
Вся інформація про текст, в тому числі і про його контекст, зібрано в об'єкті класу TextLayout. Цей клас в Java 2D замінює клас FontMetrics. У конструкторі класу TextLayout задають текст, шрифт і контекст. Наприклад, таким чином:
import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; … Graphics2D g2 = (Graphics2D) g; FontRenderContext frc = g2.getFontRenderContext (); Font f = new Font ( "Serif", Font.BOLD, 20); String s = "Проба пера"; TextLayout t = new TextLayout (s, f, frc); …
Клас TextLayout містить більше 20 методів get* для отримання даних про текст, його шрифт і контекст, а також метод draw з аргументами g2 (тип Graphics2D) і x, y (тип float), що виводить вміст об'єкта класу TextLayout у графічну область g2, починаючи з точки (x, y).
Метод getOutline з аргументом типу AffineTransform повертає контур шрифту у вигляді об'єкта Shape. Цей контур можна потім заповнити за зразком або вивести лише контур, як показано у наступному прикладі.
Приклад 8 проекту graphtest містить два класи:
і створює вікно такого вигляду.
Існує ще одна можливість створити текст з атрибутами — визначити об'єкт класу Attributedstring з пакунку java.text. Конструктор цього класу
AttributedString (s, a);
для рядка s (тип String) і властивостей a (тип Map) задає текст з властивостями. Потім можна додати або змінити властивості тексту методом addAttibute.
Для форматування тексту замість класу TextLayout застосовують клас LineBreakMeasurer, методи якого дозволяють відформатувати абзац. Для кожної частини тексту можна отримати екземпляр класу TextLayout і вивести текст, використовуючи його атрибути.
Для редагування тексту необхідно відстежувати поточну позицію в тексті. Це здійснюють методами класу TextHitinfo, а методи класу TextLayout дозволяють отримати позицію вказівника, виділити блок тексту й підсвітити його.
Клас GlyphVector
Можна задати окремі правила для виведення кожного окремого символу тексту. Для цього треба отримати екземпляр класу GlyphVector методом createGlyphVector класу Font, змінити позицію символу методом setGlyphPosition, задати перетворення символу, якщо це допустимо для даного шрифту, методом setGlyphTransform, і вивести змінений текст методом drawGlyphVector класу Graphics2D. Все це показано в наступному прикладі.
Приклад 9 проекту graphtest містить два класи:
і створює вікно такого вигляду.
Методи поліпшення унаочнення
Унаочнення створеної графіки можна вдосконалити, встановивши метод поліпшення одним з методів класу Graphics2D:
setRenderingHints (key, value); — аргумент key має тип RenderingHints.Key, value — тип Object. Наприклад,
setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
setRenderingHints (Map hints); — аргумент hints має тип Map.
Ключі (методи поліпшення) та їхні значення задають сталими класу RenderingHints. Деякі з них подано наступною таблицею.
Тлумачення | Ключ | Значення | |
---|---|---|---|
згладжування | KEY_ANTIALIASING |
VALUE_ANTIALIAS_ON VALUE_ANTIALIAS_OFF VALUE_ANTIALIAS_DEFAULT | |
наближення альфа-каналу | KEY_ALPHA_INTERPOLATION |
VALUE_ALPHA_INTERPOLATION_QUALITY VALUE_ALPHA_INTERPOLATION_SPEED VALUE_ALPHA_INTERPOLATION_DEFAULT | |
унаочнення кольорів | KEY_COLOR_RENDERING |
VALUE_COLOR_RENDER_QUALITY VALUE_COLOR_RENDER_SPEED VALUE_COLOR_RENDER_DEFAULT | |
заміна близьких кольорів | KEY_DITHERING |
VALUE_DITHER_DISABLE VALUE_DITHER_ENABLE VALUE_DITHER_DEFAULT | |
дробові метрики тексту | KEY_FRACTIONALMETRICS |
VALUE_FRACTIONALMETRICS_ON VALUE_FRACTIONALMETRICS_OFF VALUE_FRACTIONALMETRICS_DEFAULT | |
наближення зображення | KEY_INTERPOLATION |
VALUE_INTERPOLATION_BICUBIC VALUE_INTERPOLATION_BILINEAR VALUE_INTERPOLATION_NEAREST_NEIGHBOR | |
тип унаочнення | KEY_RENDERING |
VALUE_RENDER_QUALITY VALUE_RENDER_SPEED VALUE_RENDER_DEFAULT | |
контроль нормалізації обведення | KEY_STROKE_CONTROL |
VALUE_STROKE_NORMALIZE VALUE_STROKE_DEFAULT VALUE_STROKE_PURE | |
згладжування тексту | KEY_TEXT_ANTIALIASING |
VALUE_TEXT_ANTIALIAS_ON VALUE_TEXT_ANTIALIAS_OFF VALUE_TEXT_ANTIALIAS_DEFAULT VALUE_TEXT_ANTIALIAS_GASP VALUE_TEXT_ANTIALIAS_LCD_HRGB VALUE_TEXT_ANTIALIAS_LCD_HBGR VALUE_TEXT_ANTIALIAS_LCD_VRGB VALUE_TEXT_ANTIALIAS_LCD_VBGR | |
контраст тексту РК | KEY_TEXT_LCD_CONTRAST | натуральне число з проміжку від 100 до 250 |
Примітка. У поданому проекті один і той самий кадр показано протягом 10 викликів таймером методу перемалювання repaint. Це зроблено для того, щоб уникнути "випадання" окремих кадрів навіть при коректній роботі таймера, підтвердженій друком системного часу.
Приклад 14 проекту graphtest містить два класи:
Використавши файл gryvna.jpg із зображенняи гривні, проект створює таке вікно.
Приклад 15 проекту graphtest містить три класи:
GraphTest — головний клас з входом у програму;
Panel — побудова зображень: початкового та спотвореного;
ColorFilter — містить метод filterRGB, що для кожного пікселя за його координатами на растровому зображенні отримує інтенсивності кольорів моделі RGB, змінює b — інтенсивність блакитного — за такою формулою:
if (x<150) b = b*x/150;
Використавши файл gryvna.jpg ширини 300 із зображення гривні, проект створює таке вікно.
Для порівняння згори подано початкове зображення, знизу — зі спотвореною лівою половиною (150/300 = 1/2).
Приклад 16 проекту graphtest містить три класи:
GraphTest — головний клас з входом у програму;
Panel — побудова зображень: початкового та спотвореного;
ShiftFilter — містить метод setPixels, що здійснює циклічний зсув на 80 пікселів праворуч.
Використавши файл gryvna.jpg із зображення гривні, проект створює таке вікно.
Для порівняння згори подано початкове зображення, знизу — спотворене циклічним зсувом на 80 пікселів праворуч.
Приклад 17 проекту graphtest містить три класи:
GraphTest — головний клас з входом у програму;
Panel — побудова зображень: початкового та спотвореного;
MirrorFilter — містить метод setPixels, що здійснює дзеркальне відображення відносно горизонталі.
Використавши файл gryvna.jpg із зображення гривні, проект створює таке вікно.
Для порівняння згори подано початкове зображення, знизу — спотворене дзеркальним відображення відносно горизонталі.
Модель опрацювання прямим доступом (immediate mode model) — друга модель опрацювання зображення, запроваджена в Java 2D. Подібно до того, як замість класу Graphics система Java 2D використовує його розширення Graphics2D, замість класу Image в Java 2D використовують його розширення — клас BufferedImage. У конструкторі цього класу:
BufferedImage (w, h, imageType);
аргументи типу int задають розміри зображення w, h і спосіб зберігання точок imageType одією з таких сталих:
TYPE_INT_RGB TYPE_INT_ARGB TYPE_INT_ARGB_PRE TYPE_INT_BRG TYPE_3BYTE_BRG TYPE_4BYTE_ABRG TYPE_4BYTE_ABRG_PRE TYPE_BYTE_GRAY TYPE_BYTE_BINARY TYPE_BYTE_INDEXED TYPE_USHORT_565_RGB TYPE_USHORT_555_RGB TYPE_USHORT_GRAY
Назви сталих вказують на кількість відведеної пам'яті й використану колірну модель, у тому числі лише з відтінками сірого кольору (GRAY). Кожна складова кольору може займати один байт чи 5 або 6 бітів.
Представників класу BufferedImage рідко створюють конструкторами. Для цього частіше використовують метод createImage класу Component:
BufferedImage bi = (BufferedImage) createImage (w, h);
При цьому представник bi отримує властивості складової: колір тла й колір малювання, спосіб зберігання точок.
Розташування точок у зображенні встановлює клас Raster або його підклас WritableRaster. Ці класи задають систему координат зображення, надають доступ до окремих пікселів методом getPixel, дозволяють виділяти частину зображення методом getPixels. Клас WritableRaster додатково дозволяє змінювати окремі пікселі методом setPixel або цілі фрагменти зображення методами setPixels і setRect.
При створенні представника класу BufferedImage автоматично буде створено пов'язаний з ним представник класу WritableRaster.
Точки зображення зберігають у прихованому буфері, що містить одновимірний або двовимірний масив точок. Всю роботу з буфером здійснюють методами одного з таких класів: DataBufferByte, DataBufferInt, DataBufferShort, DataBufferUShort залежно від довжини даних. Загальні властивості цих класів зібрано в їхньому абстрактному суперкласі DataBuffer. У ньому визначено типи даних, що зберігають у буфері: TYPE_BYTE, TYPE_USHORT, TYPE_INT, TYPE_UNDEFINED.
Методи класу DataBuffer надають прямий доступ до даних буфера, але зручніше і безпечніше звертатися до них методами класів Raster і WritableRaster.
При створенні представника класу Raster або класу WritableRaster буде створено представник відповідного підкласу класу DataBuffer.
Raster може звертатися не лише до буфера DataBuffer, а й до підкласів абстрактного класу SampleModel, що розглядає не окремі байти буфера, а складові (samples) кольору. У моделі RGB — це червона, зелена і синя складові.
Підкласи класу SampleModel пакету java.awt.image
ComponentSampleModel — кожну складову кольору зберігають в окремому елементі масиву DataBuffer;
BandedSampleModel — розширення класу ComponentSampleModel — дані зберігають у масивах, кожний з яких містить дані лише щодо однієї складової кольору;
PixelInterleavedSampleModel — розширення класу ComponentSampleModel — усі складові кольору одного пікселя зберігають у сусідніх елементах одного масиву DataBuffer;
MultiPixelPackedSampleModel — колір кожного пікселя містить лише одну складову, яку можна упаковати в один елемент масиву DataBuffer;
SinglePixelPackedSampleModel — усі складові кольору кожного пікселя зберігають в одному елементі масиву DataBuffer.
Отже, Java 2D передбачає систему
для керування даними зображення BufferedImage. Можна маніпулювати точками зображення, використовуючи їхні координати у методах класів Raster або звертаючись до складових кольору пікселя методами класів SampleModel. Якщо потрібно працювати з окремими байтами, використовують класи DataBuffer. Застосовувати цю систему доводиться лише при створенні свого способу перетворення зображення. Стандартні перетворення виконують дуже просто.
Перетворення зображення a → b здійснюють методи
filter (a, b);
у таких випадках:
для a, b з класу BufferedImage метод описано в інтерфейсі BufferedImageOp;
для a з класу Raster, b з класу WritableRaster метод описано в інтерфейсі RasterOp лише для перетворення координатної системи зображення.
В обох випадках метод повертає посилання на змінений об'єкт b.
Спосіб перетворення визначено класом, які реалізують ці інтерфейси. Параметри перетворення задано в конструкторі класу.
Класи, втілені інтерфейсами BufferedImageOp і RasterOp
Розглянемо, як можна застосувати ці класи для перетворення зображення.
Клас AffineTransform розглянуто вище. Далі ми лише застосуємо його аналог для перетворення зображення. У конструкторі класу AffineTransformOp вказують у такому порядку: попередньо створене афінне перетворення at типу AffineTransform, спосіб інтерполяції interp типу int і/або правила унаочнення hints типу RenderingHints.
AffineTransformOp (at, interp); AffineTransformOp (at, interp, hints); AffineTransformOp (at, hints);
Спосіб інтерполяції — це одна з двох сталих:
Після створення об'єкта класу AffineTransformOp застосовують метод filter. При цьому зображення буде перетворено всередині нової області типу BufferedImage, яку на наступному малюнку виділено чорним кольором.
Інший спосіб афінного перетворення зображення — це застосувати метод
drawImage (bi, op, x, y);
класу Graphics2D для bi з класу BufferedImage, op з класу BufferedImageOp, x,y типу int. При цьому буде перетворено всю область img, як показано на наступному малюнку посередині.
У поданому нижче прикладі 18 зверніть увагу на особливості роботи з BufferedImage: потрібно створити графічний контекст зображення і вивести в нього зображення. Ці дії здаються зайвими, але вони зручні для подвійний буферизації, яка зараз стала стандартом перемальовування зображень. У бібліотеці Swing її виконують автоматично.
Приклад 18 проекту graphtest містить два класи:
GraphTest — головний клас з входом у програму;
Panel — побудова зображень з використанням файлу cd.png для отримання такого вікна
Зміна інтенсивності зображення — це спотворення зображення шляхом одного й того самого лінійного перетворення: x → kx + b інтенсивності кожного кольору з приводенням результату до діапазону значень складової. Числа k і b (тип float) одні й ті самі для всіх пікселів. Їх задають у конструкторі класу разом з правилами візуалізації h (тип RenderingHints):
RescaleOp (k, b, h);
Після цього застосовують метод filter.
Приклад 19 проекту graphtest містить два класи:
GraphTest — головний клас з входом у програму;
Panel — побудова зображень: початкового та спотвореного.
Використавши файл gryvna.jpg із зображенням гривні, проект створює таке вікно.
Для порівняння згори подано початкове зображення, знизу — "негатив", створений таким лінійним перетворенням інтенсивностей кольорів:
Інакше кажучи, при k = –1, b = 255.
Зміна складових кольору передбачає здійснення таких кроків:
створити 2-вимірний масив s, який для кожної складової (i = 0, 1, 2) і кожного допустимого значення інтенсивності кольору (j = 0, 1, 2, …, 255) встановлює нове значення інтенсивності кольору s[i,j], на яке буде змінено початкове значення j;
створити об'єкти типів ByteLookupTable і LookupOp за побудованим масивом s;
застосувати метод filter для побудови нового зображення.
Приклад 20 проекту graphtest містить два класи:
GraphTest — головний клас з входом у програму;
Panel — побудова зображень: початкового та спотвореного наданням червоній складовій кольору найбільшого з можливих значень 255.
Використавши файл gryvna.jpg із зображенням гривні, проект створює таке вікно.
fx y — інтенсивність кольору точки з координатами x, y;
wjk при j = – J, – J + 1, …, J – 1, J; k = – K, – K + 1, …, K – 1, K — елементи деякої прямокутної таблиці чисел.
Згортка (convolution) за відомими значеннями вагових коефіцієнтів wjk задає лінійне перетворення насиченості кольору точки залежно від насиченості кольору навколишніх точок таким чином:
J | K | ||
∑ | ∑ | wjk · fx+j y+k | |
j = – J | k = – K |
Змінюючи значення вагових коефіцієнтів wjk , можна отримати різні ефекти, підсилюючи або зменшуючи вплив сусідніх точок:
якщо сума всіх (2J + 1)(2K + 1) чисел wjk дорівнює 1, то загальна інтенсивність кольору внутрішні майже не зміниться (див. формулу для точок на межі зображення);
якщо при цьому значення wjk однакові, проявиться ефект розмитості, імли;
якщо вага w00 істотно перевищує інші значення wjk при загальній сумі 1, то зростає контрастність.
У Java 2D згортка здійснюють за таким алгоритмом.
Задати значення елементів лінійного масиву ваг w з переліком значень wjk у порядку зростання k + j · (2K + 1) — у порядку зростання j, а при сталому j — у порядку зростання k;
За значенням створеного масиву w створити представника класу Kernel — ядро згортки — такою вказівкою:
Kernel kern = new Kernel (2*j+1, 2*k+1, w);
Створити об'єкт класу ConvolveOp за створеним ядром:
ConvolveOp conv = new ConvolveOp (kern);
Застосувати метод filter до створеного об'єкту класу ConvolveOp:
conv.filter (bi, bimg);
Приклад 21 проекту graphtest містить два класи:
GraphTest — головний клас з входом у програму;
Panel — побудова зображень: початкового та створених за допомогою згорток.
Використавши файл mashroom.jpg із зображенням гриба, проект створює таке вікно.
Є кілька способів створити анімацію. Найпростіший з них — записати заздалегідь всі необхідні кадри у графічні файли, завантажити їх в оперативну пам'ять у вигляді об'єктів класу Image або BufferedImage і виводити їх по черзі на екран.
Приклад 22 проекту graphtest містить один клас GraphTest, який, використавши файли
f1,
f2,
f3,
f4,
f5,
f6,
створює вікно з анімацією.
Не можна звертатися безпосередньо до методу paint для перемальовування вікна, бо цей метод буде виконано автоматично при кожній зміні вмісту вікна, його переміщенні чи зміні розмірів. Для запиту на перемальовування вікна у класі Component є метод repaint. Метод repaint чекає, коли трапиться нагода перемалювати вікно, і потім звертається до методу paint. Для "важкої" складової він викликає метод update (Graphics g). Кілька звернень до методу repaint можуть бути виконані за один раз.
Метод update у класі Component звертається до методу paint, який переозначують у підкласах класу Component. Для "легких" складових срава йде складніше. Метод repaint послідовно звертається до методів repaint, що охоплюють "легкі" контейнери, поки не зустрінеться "важкий" контейнер. Найчастіше це екземпляр класу Container. У ньому викликають перевизначення метод update, що очищає і перемальовує контейнер. Потім йде звернення у зворотному порядку до методів update всіх "легких" складових у контейнері.
Звідси випливає, що для усунення мерехтіння "легких" складових необхідно перевизначати метод update першого осяжний "важкого" контейнера, звертаючись в ньому до методу super.update(g) або super.paint(g). Якщо кадри покривають лише частину вікна, причому кожен раз нову, то очищення вікна методом clearRect необхідна, інакше старі кадри залишаться у вікні, з'явиться "хвіст". Щоб усунути мерехтіння, використовують прийом, який отримав назву подвійна буферизація (double buffering).
Поліпшення зображення подвійний буферизацією полягає у тому, що в оперативній пам'яті створюють буфер — об'єкт класу Image або BufferedImage — і викликають його графічний контекст, у якому формують зображення. Там же відбувається очищення буфера, що не відображають на екрані. Тільки після виконання всіх дій готове зображення виводять на екран. Все це відбувається в методі update, а метод paint лише звертається до update. Наступні приклади 23 і 24 роз'яснюють даний прийом.
Приклад 23 проекту graphtest містить один клас GraphTest, отриманий з коду прикладу 22 таким чином:
долученням (наприкінці коду) переозначення методу update, що використовує подвійну буферизацію об'єктом класу Image;
збільшенням у 10 разів (до 1 секунди) проміжку часу між виведенням зображень;
вилученням 10-кратного повтору виведення одного й того самого зображення, використаного для правильної анімації без використання подвійної буферизації.
У результаті отримано таку саму для сприйняття користувача анімацію, але створену меншою (у 10 разів) кількістю дій.
Приклад 24 проекту graphtest містить один клас GraphTest, який відрізняється від коду прикладу 23 тим, що у переозначеному методі update використано подвійну буферизацію об'єктом класу BufferedImage. Але результат виконання коду той самий.
Приклад 25 проекту graphtest містить один клас Graphtest, який використовує файли зображень background.jpg i plane.png для створення такої анімації за допомогою затримки у часі Thread.sleep.
Того самого результату можна досягнути з використанням класів Timer і TimerTask — див. відповідний код.
Ефект переливання кольору, мерехтіння, затемнення та інші ефекти, отримані заміною окремих пікселів зображення, зручно створювати за допомогою класу MemoryImageSource. Методи newPixels цього класу викликають негайне перемалювання зображення навіть без звернення до методу repaint, якщо перед цим застосовано метод setAnimated(true). Найчастіше застосовуються два методи:
Приклад 26 проекту graphtest містить один клас Graphtest, який створює таку анімації поширення хвиль інтенсивностей кольорів:
при нульовій інтенсивності блакитного кольору.
Текст упорядкував Олександр Рудик з використанням таких джерел: