Графіка засобами Java

Зміст

На чому малювати?

Можливість малювати на елементах керування (JFrame, JPanel, JButton та інших), успадкованих від класу Component, забезпечено класами Graphics і Graphics2D. Успадковані з класу Component методи

paint          (Graphics g)
paintComponent (Graphics g)

повертають графічний контекст — об'єкт, у якому і здійснюють малювання.

Аргумент Graphics g створює система, а програма бере його в готовому вигляді і використовує для малювання. Нижче приклади методів подано у застосуванні до такого елемента g. Назву методу записують через крапку після назви такого елемента, як це зроблено у поданих нижче прикладах.

Клас Graphics має підклас Graphics2D, який істотно розширює можливості малювання. Наприклад, дає можливість задавати товщину ліній. Щоб отримати доступ до методів Graphics2D, потрібно привести тип графічного контексту до цього класу. Як це зробити? Нижче після опису методів роботи з кольором, графічними примітивами й текстом подано приклад використання Graphics2D з малюванням на представнику JPanel (використання JFrame не дає можливості задати колір тла).

Кольори

Примітка.

Контури

Заповнення
Заповнені кольором фігури малюють з допомогою таких методів класу Graphics: fillRect, fill3DRect, fillOval, fillArc, fillRoundRect, fillPolygon, . Вони мають такі самі параметри, як і відповідні методи, що малюють лише межі фігур. Тобто ті, в яких замість префікса fill використано префікс draw.

Текст

Виведення тексту в область малювання поточним кольором і шрифтом, починаючи з точки (x, y) — лівої нижньої точки першої літери тексту на базовій лінії (baseline) виведення шрифту — здійснюють такими методами класі Graphics:

Робота з шрифтами

Об'єкти класу Font зберігають накреслення символів, що утворюють шрифт. Їх можна створити двома конструкторами:

При виведенні тексту логічним назвам шрифтів і стилів зіставляються фізичні назви шрифтів (font face name) або назви родин шрифтів (font name). Це назви шрифтів, наявних у графічній підсистемі операційної системи. Наприклад, логічній назві "Serif" можна співставити назву родини шрифтів "Times New Roman", а у поєднанні зі стилями — конкретні фізичні назви "Times New Roman Bold", "Times New Roman Italic". Ці шрифти повинні перебувати у складі шрифтів графічної системи того ПК, на якій виконують застосунок. Встановлення JDK гарантує наявність щонайменше родини шрифтів Lucida.

При виведенні рядка тексту у вікно програми дуже часто виникає необхідність розташувати його певним чином щодо інших елементів зображення: центрувати, вивести над або під іншим графічним об'єктом, ліворуч чи праворуч від нього. Для цього треба знати розміри рядка, наприклад, його висоту й ширину. Можуть знадобитися й інші лінійні розміри шрифту — див. малюнок у повноекранному режимі з такими підписами виділених довжин та ліній.

  1. accender line — лінія верхніх виносних елементів;
  2. cap line — лінія великих літер;
  3. lower case line, mean line, x-height line — лінія малих літер;
  4. base line — базовий рівень;
  5. descender line — лінія нижніх виносних елементів;
  6. leading — інтерліньяж;
  7. side-bearing — півапрош;
  8. letterspace — апрош;
  9. body, em-square — кегельний майданчик;
  10. x-height — висота малих літер;
  11. cap height — висота великих літер;
  12. body height — висота тіла;
  13. point size, em — кегль;
  14. shoulders — заплечики;
  15. descent — нижній винос ;
  16. ascent — верхній винос.

FontMetrics — клас для вимірювання розмірів окремих символів і рядка в цілому. Цей клас є абстрактним, тому не можна скористатися його конструктором. Для отримання об'єкта класу FontMetrics, що містить набір метричних характеристик шрифту f, потрібно звернутися до методу getFontMetrics (Font f) класу Graphics або класу Component.

Методи класу FontMetrics:

Приклад 1 проекту graphtest містить два класи:

і створює таке зображення.

Примітка. При зміні розмірів вікна геометричні фігури залишаються нерухомими, а напис: "Проба пера" буде рухомим — центрованим по горизонталі внизу вікна.

Клас Graphics2D
Клас Graphics2D пакунку java.awt має такі нововведення порівняно з Graphics:

Таким чином Java 2D стала повноцінною системою малювання, виведення тексту і зображень. Розглянемо, як втілено ці можливості і як ними можна скористатися.

Правило перетворення координат користувача в координати графічного пристрою задано автоматично при створенні графічного контексту так само, як колір або шрифт. Надалі його можна змінити методом setTransform так само, як змінити колір чи шрифт. Параметром цього методу служить об'єкт класу AffineTransform з пакета java.awt.geom, подібно об'єктам класу Color або Font при завданні кольору або шрифту.

Клас AffineTransform містить такі конструктори афінних перетворень координат:

У багатьох випадках зручніше створити перетворення статичними методами, які повертають об'єкт класу AffineTransform і всі аргументи яких мають тип double, якщо не сказано інше:

Задання поточного перетворення можна зробити одним з таких методів:

setTransform (a, b, c, d, e, f);
setTransform (at);
setToIdentity ();
setToRotation (a);
setToRotation (a, x, y);
setToRotation (u, v);
setToRotation (u, v, x, y);
setToQuadrantRotation (n);
setToQuadrantRotation (n, x, y);
setToScale (r, s);
setToShare (c, b);
setToTranslate (e, f);

Тут і далі типи та тлумачення аргументів — ті самі, що й у поданих вище зі спільними частинами назв і тією самою кількістю аргументів. Наприклад, методу setToTranslate відповідає метод getTranslateInstance.

Виконання перетворень, заданих методами:

concatenate (at);
rotate (a);
rotate (a, x, y);
rotate (u, v);
rotate (u, v, x, y);
quadrantRotate (n);
quadrantRotate (n, x, y);
scale (r, s);
shear (c, b);
translate (e, f);

буде здійснено перед перетворенням, яке оголошено поточним. Перетворення, задане методом preConcatenate(at), навпаки, буде здійснено після поточного перетворення.

Приклад 2 проекту graphtest містить два класи:

Порівняно з Прикладом 1:

Результат виконання кодуЧастина коду з методами класу 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);

  1. Поворот на 90° проти напрямку руху годинникової стрілки
    навколо "точки згину" з координатами (60, 60).
  2. Паралельне перенесення на вектор з координатами (50, 0)
    — "зсув по горизонталі".
   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);

  1. Поворот на 90° проти напрямку руху годинникової стрілки
    навколо "точки згину" з координатами (60, 60).
  2. Паралельне перенесення на вектор з координатами (50, 0)
    — "зсув по горизонталі".
  3. Стиснення удвічі в усіх напрямках.

Приклад 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);

  1. Паралельне перенесення на вектор з координатами (50, 0)
    — "зсув по горизонталі".
  2. Поворот на 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);

  1. Стиснення удвічі в усіх напрямках.
  2. Паралельне перенесення на вектор з координатами (50, 0)
    — "зсув по горизонталі".
  3. Поворот на 90° проти напрямку руху годинникової стрілки
    навколо "точки згину" з координатами (60, 60).

Клас BasicStroke
Основний конструктор класу

BasicStroke (width, cap, join, miter, dash, float dashBegin);

визначає такі властивості пера:

Решта конструкторів задають деякі характеристики як усталено:

Після створення пера одним з перелічених вище конструкторів і встановлення властивостей пера методом 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

  1. Cтворити:
    • або порожній об'єкт класу GeneralPath конструктором GeneralPath();
    • або об'єкт з однією фігурою s класу Shape конструктором GeneralPath(s).
  2. До створеного об'єкта додати необхідні фігури s методом append(s, connect):

    • при справдженні булевої змінної connect (значення true) нову фігуру s з'єднують з уже наявними за допомогою поточного пера;

    • при хибності булевої змінної connect (значенні false) нову фігуру s не з'єднувати з уже наявними.

Клас GeneralPath має інший спосіб побудови шляху з відрізків прямих, квадратних і кубічних кривих. Таким чином можна створити об'єкт GeneralPath об'єкт, задавши одне з правил визначення належності точки внутрішнім точкам фігури (правило намотування) залежно від кількості обертів вектора, спрямованого з даної точки до точки, що рухається межею фігури:

або вказавши початкову координатну ємність. Сталі класу GeneralPath потрібно вказувати як аргумент коструктора цього класу:

… = new GeneralPath(GeneralPath.WIND_NON_ZERO); або
… = new GeneralPath(GeneralPath.WIND_EVEN_ODD);

Алгоритм 2 побудови представника класу GeneralPath

  1. Cтворити порожній об'єкт класу GeneralPath конструктором GeneralPath();

  2. Додати частини фігури, використавши такі методи з дійсними аргументами (тип 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
Надає можливість градієнтного заповнення таким чином:

Таке заповнення створює конструктор з дійсними аргументами 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
можна використати таким чином (назви створюваних об'єктів можуть бути іншими, синтаксис вказівок подано у наступному прикладі).

  1. Створити об'єкт buffer класу BufferedImage пакунку java.awt.image, вказавши як аргументи конструктора розміри області у пікселях і тип, що задають однією з таких сталих класу BufferedImage:

    • TYPE_3BYTE_BGR — 8-розрядні складові кольору RGB, відповідає стилю Windows колірної моделі BGR з кольорами синім, зеленими і червоним, збереженими у 3 байтах;

    • TYPE_4BYTE_ABGR,
      TYPE_4BYTE_ABGR_PRE — 8-розрядні складові кольору RGBA з кольорами синім, зеленими і червоним, збережений у 3 байтах і 1 байті альфа-каналу;

    • TYPE_BYTE_BINARY — непрозоре, упаковане байтами 1-, 2- або 4-бітове зображення;

    • TYPE_BYTE_GRAY — півтонове зображення байтами без знака, неіндексоване;

    • TYPE_BYTE_INDEXED — індексоване зображення байта;

    • TYPE_CUSTOM — не розпізнаний тип зображення (спеціалізоване зображення);

    • TYPE_INT_ARGB,
      TYPE_INT_ARGB_PRE — 8-розрядні складові кольору RGBA, упаковані в цілочисельні пікселі;

    • TYPE_INT_BGR — 8-розрядні складові кольору RGB, відповідає домовленостям Windows або Соляріс, колірна модель BGR з кольорами синім, зеленим і червоним, упаковані у цілочисельні пікселі;

    • TYPE_INT_RGB — 8-розрядні складові кольору RGB, упаковані в цілочисельні пікселі. Саме цей тип буде використано у прикладах;

    • TYPE_USHORT_555_RGB — модель кольору RGB, у якій відведено по 5 бітів на кожний з кольорів: червоний, зелений і синій без альфа-каналу;

    • TYPE_USHORT_565_RGB — модель кольору RGB, у якій відведено по 5 бітів на червоний та синій колір і 6 бітів на зеленийц без альфа-каналу;

    • TYPE_USHORT_GRAY — півтонування короткого цілого без знака, неіндексованого.

  2. Створити bufferg — графічний контекст (представник класу Graphics2D) буфера buffer методом createGraphics класу BufferedImage.

  3. Заповнити графічний контекст буфера зображенням, яке слугуватиме зразком заповнення (так само, як до цього заповнювали звичайні графічні контексти при малюванні).

  4. Створити об'єкт класу TexturePaint конструктором

    TexturePaint (buffer, r);

    попередньо створивши прямокутник r — об'єкт класу Rectangle2D, розміри якого є розмірами зразка заповнення.

Після створення заповнення — об'єкта класу Color, GradientPaint або TexturePaint — його встановлюють у графічному контексті методом setPaint і використовують у подальшому методом fill.

Приклад 6 проекту graphtest містить два класи:

і створює вікно такого вигляду.

Класи LinearGradientPaint і RadialGradientPaint
Надають можливість створити градієнтне заповнення декількома кольорами уздовж прямої або радіально. Загальні властивості цих класів зібрано в абстрактному класі MultipleGradientPaint.

Алгоритм використання класу LinearGradientPaint

  1. Задати координати (тип float) точок A(x1, y1) і B(x2, y2) — кінців відрізка, уздовж якого буде здійснено перехід між кольорами.

  2. Відрізок [A, B] поділити на кілька частин точками, вказавши для кожної точки поділу С (у тому числі для точок A, B) координату — дійсне число (тип float) з відрізка [0, 1], що дорівнює відношенню відстаней AC : AB. З координат усіх точок поділу сформувати деякий масив t (назва може бути й іншою) з урахуванням такого:

    • 0 відповідає центру кола A;
    • 1 відповідає точці B на колі.
  3. Для кожної точці поділу відрізка [A, B] задати свій колір. Усі кольори занести у деякий масив с (назва може бути й іншою).

  4. Створити представника класу 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:

Ці сталі вказують як останній необов'язковий аргумент останніх двох згаданих конструкторів.

Можна також використати конструктори 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:

Ключі (методи поліпшення) та їхні значення задають сталими класу 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

Для останньої властивості (контраст тексту РК) менше значення відповідає тексту з більшою контрастністю при відображенні темного тексту на світлому тлі. Типове корисне значення перебуває у вузькому діапазоні 140-180. Якщо значення не вказано, буде застосовано значення як усталено для системи або реалізації.

Не всі графічні системи забезпечують виконання цих методів, тому надання значень властивостям не означає, що обумовлені ними методи буде застосовано насправді.

Для кращої відповідності унаочнення налаштуванням дисплею визначено властивість "awt.font.desktophints", що зберігає таблицю типу Map з методами поліпшення візуалізації, наявними в налаштуваннях дисплея. Скористатися цією властивістю можна, наприклад, таким чином:

…
Graphics2D g2 = (Graphics2D) g;
Toolkit tk = Toolkit.getDefaultToolkit ();
Map map = (Map) (tk.getDesktopProperty ( "awt.font.desktophints"));
if (null != map) g2.addRenderingHints (map);
…

Завантаження і запис растрової графіки
Методи класу ImageIO надають можливість:

Приклад 10 проекту graphtest містить два класи:

і створює вікно такого вигляду.

У поданому прикладі 10:

Приклад 11 проекту graphtest містить два класи:

і створює вікно такого вигляду.

У поданому прикладі 11 використаний рядок адреси (при ОС Linux Mint / Ubuntu):
"/home/chief/NetBeansProjects/graphtest/netbeans.png" можна замінити на назву файлу "netbeans.png". Інакше кажучи, поточню текою, з якої починають пошук файлів, є тека проекту.

Метод getRGB класу BufferedImage
При застосуванні до представника BufferedImage (bi)

Color c = new Color(bi.getRGB(j,k));

повертає колір (тип Color) пікселя з указаними екранними координатами (j, k). Для отримання інтенсивностей базових кольорів моделі RGB використовують такі методи класу Color:

Метод createImage класу Toolkit
Повертає посилання на об'єкт типу Image для таких аргументів:

Останній спосіб вимагає докладного пояснення.

Модель "постачальник-споживач"

Часто зображення перед виведенням на екран опрацьовують: змінюються кольори окремих пікселів або цілих ділянок зображення, виділяються і перетворюють якісь частини зображення. В бібліотеці AWT застосовують дві моделі опрацювання зображень. Одна модель реалізує модель "постачальник-споживач" (Producer-Consumer):

У одного постачальника може бути кілька споживачів, які повинні бути зареєстровані постачальником. Постачальник і споживач активно взаємодіють, звертаючись до методів один одного. У графічної бібліотеці AWT цю модель описано двома інтерфейсами: ImageProducer і ImageConsumer пакунку java.awt.image.

Інтерфейс ImageProducer описує 5 методів з аргументом ic типу ImageConsumer:

З кожним екземпляром класу Image пов'язаний об'єкт, який реалізує інтерфейс ImageProducer. Його можна отримати методом getSource класу Image. Найпростіша реалізація інтерфейсу ImageProducer — клас MemoryImageSource — створює пікселі в оперативній пам'яті за масивом елементів типу byte або int. Спочатку створюють масив, що містить колір кожної точки. Потім створюють об'єкт класу MemoryImageSource. Його опрацьовує споживач або його перетворюють у тип Image методом createImage. У наступному прикладі подано програму, що виводить на екран квадрат розміром 500×500 пікселів:

а в інших точках інтенсивність кольорів лінійно спадає від суми відстаней до кута.

Приклад 12 проекту graphtest містить два класи:

і створює вікно такого вигляду.

У коді класу Panel в конструктор класу-постачальника

MemoryImageSource (w, h, pix, 0, w)

занесено ширину w й висоту h зображення, масив pix, зміщенням у цьому масиві 0 і довжину рядка w. Споживачем є зображення img, створене методом createImage і виведене на екран методом drawImage. Лівий верхній кут зображення img розташовано у точці (0, 0) контейнера, а останній аргумент this показує, що роль ImageObserver грає сам клас Panel. Використання перевірки if (img == null) забезпечує сталість зображення без перемалювання навіть при зміні розмірів вікна.

Якщо прибрати таку перевірку (наприклад, закоментувати рядок з перевіркою умови), то зображення переходу кольорів буде змінюватися зі зміною розмірів вікна.

Інтерфейс ImageConsumer містить метод setPixels, виклик якого виглядає так:

setPixels (x, y, w, h, m, p, j0, l);

До цього методу звертається постачальник для передачі споживачеві пікселів, які містить прямокутник шириною w і висотою h з координатами верхнього лівого кута x, y — усі 4 змінні типу int — за даними масиву p типу int[], починаючи з індексу j0 типу int. Кожний рядок займає l (тип int) елементів масиву p. Кольори пікселів визначають у колірній моделі m типу ColorModel. Зазвичай це модель RGB. Можливе використання масиву p з елементами типу byte, а не int.

Клас ImageFilter
Як правило немає потреби втілювати інтерфейс ImageConsumer, бо є можливість використати готове його втілення — клас ImageFilter. Незважаючи на назву, цей клас не здійснює жодної фільтрації. Для перетворення зображень даний клас потрібно розширити, перевизначивши метод setPixels. Результат перетворення передають споживачеві, роль якого грає захищена (protected) властивість consumer цього класу.

Пакет java.awt.image містить 4 готових розширення класу ImageFilter:

Застосовують ці класи спільно з класом FilteredImageSource. Цей клас перетворює вже готову продукцію, використовуючи для перетворення об'єкт filter класу-фільтра ImageFilter або його підкласу. Обидва об'єкти задають у конструкторі

FilteredImageSource (producer, filter);

з аргументами типу ImageProducer і ImageFilter.

Поданий вище опис видається занадто складним і заплутаним. Але схема застосування фільтрів завжди одна й та сама — див. наступні три приклади.

Приклад 13 проекту graphtest містить два класи:

Використавши файл coin.png такого зображення,

проект створює вікно з анімацією.

Примітка. У поданому проекті один і той самий кадр показано протягом 10 викликів таймером методу перемалювання repaint. Це зроблено для того, щоб уникнути "випадання" окремих кадрів навіть при коректній роботі таймера, підтвердженій друком системного часу.

Приклад 14 проекту graphtest містить два класи:

Використавши файл gryvna.jpg із зображенняи гривні, проект створює таке вікно.

Приклад 15 проекту graphtest містить три класи:

Використавши файл gryvna.jpg ширини 300 із зображення гривні, проект створює таке вікно.

Для порівняння згори подано початкове зображення, знизу — зі спотвореною лівою половиною (150/300 = 1/2).

Приклад 16 проекту graphtest містить три класи:

Використавши файл gryvna.jpg із зображення гривні, проект створює таке вікно.

Для порівняння згори подано початкове зображення, знизу — спотворене циклічним зсувом на 80 пікселів праворуч.

Приклад 17 проекту graphtest містить три класи:

Використавши файл 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

Отже, Java 2D передбачає систему

DataBuffer — SampleModel — Raster

для керування даними зображення BufferedImage. Можна маніпулювати точками зображення, використовуючи їхні координати у методах класів Raster або звертаючись до складових кольору пікселя методами класів SampleModel. Якщо потрібно працювати з окремими байтами, використовують класи DataBuffer. Застосовувати цю систему доводиться лише при створенні свого способу перетворення зображення. Стандартні перетворення виконують дуже просто.

Перетворення зображення a → b здійснюють методи

filter (a, b);

у таких випадках:

В обох випадках метод повертає посилання на змінений об'єкт 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 містить два класи:

Зміна інтенсивності зображення — це спотворення зображення шляхом одного й того самого лінійного перетворення: x → kx + b інтенсивності кожного кольору з приводенням результату до діапазону значень складової. Числа k і b (тип float) одні й ті самі для всіх пікселів. Їх задають у конструкторі класу разом з правилами візуалізації h (тип RenderingHints):

RescaleOp (k, b, h);

Після цього застосовують метод filter.

Приклад 19 проекту graphtest містить два класи:

Використавши файл gryvna.jpg із зображенням гривні, проект створює таке вікно.

Для порівняння згори подано початкове зображення, знизу — "негатив", створений таким лінійним перетворенням інтенсивностей кольорів:

x → 255 – x.

Інакше кажучи, при k = –1, b = 255.

Зміна складових кольору передбачає здійснення таких кроків:

Приклад 20 проекту graphtest містить два класи:

Використавши файл gryvna.jpg із зображенням гривні, проект створює таке вікно.

Запровадимо такі позначення:

Згортка (convolution) за відомими значеннями вагових коефіцієнтів wjk задає лінійне перетворення насиченості кольору точки залежно від насиченості кольору навколишніх точок таким чином:

JK
wjk · fx+j  y+k
j = – J    k = – K

Змінюючи значення вагових коефіцієнтів wjk , можна отримати різні ефекти, підсилюючи або зменшуючи вплив сусідніх точок:

У Java 2D згортка здійснюють за таким алгоритмом.

  1. Задати значення елементів лінійного масиву ваг w з переліком значень wjk у порядку зростання k + j · (2K + 1) — у порядку зростання j, а при сталому j — у порядку зростання k;

  2. За значенням створеного масиву w створити представника класу Kernel — ядро згортки — такою вказівкою:

    Kernel kern = new Kernel (2*j+1, 2*k+1, w);

  3. Створити об'єкт класу ConvolveOp за створеним ядром:

    ConvolveOp conv = new ConvolveOp (kern);

  4. Застосувати метод filter до створеного об'єкту класу ConvolveOp:

    conv.filter (bi, bimg);

Приклад 21 проекту graphtest містить два класи:

Використавши файл 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 таким чином:

У результаті отримано таку саму для сприйняття користувача анімацію, але створену меншою (у 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, який створює таку анімації поширення хвиль інтенсивностей кольорів:

при нульовій інтенсивності блакитного кольору.


Текст упорядкував Олександр Рудик з використанням таких джерел: