Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Урок 9
Графічні примітиви
Потрібні в цьому уроці нові класи ви можете знайти в папці Java9.
При створенні компонента, тобто обєкта класу Component, автоматично формується його графічний контекст (graphics context). В контексті розміщується область рисування і виведення тексту та зображень. Контекст містить поточний і альтернативний колір рисування і колір фону обєкти класу Сolor, поточний шрифт для виведення тексту обєкт класу Font. В контексті визначена система координат, початок якої з координатами (0, 0) розташований у верхньому лівому куті області рисування, вісь Ох направлена вправо, вісь Оу вниз. Точки координат знаходяться між пікселями.
Керує контекстом класс Graphics або новий клас Graphics2D, введений в Java 2. Оскільки графічний контекст сильно залежить від конкретної графічної платформи, ці класи зроблені абстрактними. Тому не можна безпосередньо створити екземпляри класу Graphics або Graphics2D. Одначе кожна віртуальна машина Java реалізує методи цих класів, створює їх екземпляри для компонента і представляє обєкт класу Graphics методом getGraphics() класу Component або як аргумент методів paint() і update(). Подивимось спочатку, які методи роботи з графікою і текстом представляє нам класс Graphics.
9.1. Методи класу Graphics
При створенні контексту в ньому задається поточний колір для рисування, зазвичай чорний, і колір фону області рисування білий або сірий. Змінити поточний колір можна методом setСoІor (Color newCoІor), аргумент newСoІor котрого обєкт класу Color. Узнати поточний колір можна методом getСolor(), повертаючим обєкт класу Сolor.
9.1.1. Як задати колір
Колір, як і все в Java, обєкт певного класу, а саме, класу Сolor. Основу класу складають сім конструкторів кольору. Самий простий конструктор:
Color(int red, int green, int blue)
створює колір, одержаний змішуванням червоного red, зеленого green і синього blue . Ця кольорова модель називається RGB. Кожна складова змінюється від 0 (відсутність складової) до 255 (повна інтенсивність цієї складової). Наприклад:
Color pureRed = new Color(255, 0, 0);
Color pureGreen = new Color(0, 255, 0);
визначають чистий яскраво-червоний pureRed і чистий яскраво-зелений pureGreen кольори.
В другому конструкторі інтенсивність складової можна змінювати більш гладко дійсними числами від 0.0 (відсутність складової) до 1.0 (повна інтенсивність складової):
Color(float red, float green, float blue)
Наприклад:
Color someColor = new Color(0.05f, 0.4f, 0.95f);
Третій конструктор
Color(int rgb)
задає всі три складові в одному цілому числі. В бітах 1623 записується червона складова, в бітах 815 зелена, а в бітах 07 синя складова кольору. Наприклад:
Color с = new Color(0хFF8F48FF);
Тут червона складова задана з інтенсивністю 0x8F, зелена 0x48, синя 0xFF.
Наступні три конструктори
Color(int red, int green, int blue, int alpha)
Color(float red, float green, float blue, float alpha)
Color(int rgb, boolean hasAlpha)
вводять четверту складову кольору, так звану "альфу", що визначає прозорість кольору. Ця складова проявляє себе при накладанні одного кольору на другий. Якщо альфа рівна 255 або 1,0, то колір повністю непрозорий, попередній колір не просвічується крізь нього. Якщо альфа рівна 0 або 0,0, то колір абсолютно прозорий, для кожного пікселя видно тільки попередній колір. Останній із цих конструкторів враховує складову альфа, що знаходиться в бітах 2431, якщо параметр hasAІpha рівний true. Якщо ж hasAІpha рівне false, то складова альфа вважається рівною 255, незалежно від того, що записано в старших бітах параметра rgb. Перші три конструктори створюють непрозорий колір з альфою, рівною 255 або 1,0.
Сьомий конструктор
Color(ColorSpace cspace, float[] components, float alpha)
дозволяє створювати колір не тільки в кольоровій моделі (color model) RGB, але і в інших моделях: CMYK, HSB, CIEXYZ, визначених обєктом класу ColorSpace. Для створення кольору в моделі HSB можна скористатися статичним методом
getHSBColor(float hue, float saturation, float brightness).
Якщо немає необхідності ретельно підбирати кольори, то можна просто скористатися однією із тринадцати статичних констант типу color класу Сolor. Наперекір стандарту "Code Conventions" вони записуються рядковими літерами: black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow.
Методи класу Color дозволяють одержати складові поточного кольору: getRed(), getGreen(), getBlue(), getAlpha(), getRGB(), getColorSpace (), getComponents (). Два методи створюють більш яскравий brighter() і більш темний darker() кольори в порівнянні з поточним кольором. Вони корисні, якщо треба виділити активний компонент або, навпаки, показати неактивний компонент блідніше решти компонентів. Два статичних методи повертають колір, перетворений із кольорової моделі RGB в HSB і навпаки:
float[] RGBtoHSB(int red, int green, int blue, float[] hsb)
int HSBtoRGB(int hue, int saturation, int brightness)
Створивши колір, можна рисувати ним в графічному контексті.
9.1.2. Як нарисувати фігуру
Основний метод рисування
drawLine(int x1, int y1, int х2, int y2)
рисує поточним кольором відрізок прямої між точками з координатами (x1, y1) і (х2, у2). Одного цього методу достатньо, щоб нарисувати будь-яку картину по точках, рисуюючи кожну точку з координатами (х, у) методом drawLine (x, у, х, у) і змінюючи кольори від точки до точки. Але ніхто, зрозуміло, не стане цього робити.
Інші графічні примітиви:
9.2. Клас Polygon
Цей клас призначений для роботи з многокутником, зокрема, з трикутниками і довільними чотирикутниками. Обєкти цього класу можна створити двома конструкторами:
Після створення обєкта в нього можна додавати вершини методом
addPoint(int x, int у)
Логічні методи contains() дозволяють перевірити, чи не лежить в многокутнику задана аргументами метода точка, відрізок прямої або цілий прямокутник зі сторонами, паралельними сторонам екрана. Логічні методи intersects() дозволяють перевірити, чи не перетинаються з даним многокутником відрізок прямої, заданий аргументами метода, або прямокутник зі сторонами, паралельними сторонам экрана. Методи getBounds() і getBounds2D() повертають прямокутник, цілком вміщаючий в себе даний многокутник.
Повернемося до методів класу Graphics. Декілька методів викреслюють фігури, залиті поточним кольором: fillRect(), fill3DRect(), filІArco, fiІІОval(), filІPoІiygon(), filІRoundRect(). У них такі ж аргументи, як і у відповідних методів, викреслюючих незаповнені фігури. Наприклад, якщо ви хочете змінити колір фону області рисування, то установіть новий поточний колір і накресліть ним заповнений прямокутник величиною у всю область:
public void paint(Graphics g){
Color initColor = g.getColor(); // Зберігаємо початковий колір
g.setColor(new Color(0, 0, 255)); // Устанавлюємо колір фону
// Заливаємо область рисування
g.fillRect(0, 0, getSizeO.width - 1, getSize().height - 1);
g.setColor(initColor); // Відновлюємо початковий колір
// Подальші дії
}
Як бачимо, в класі Graphics зібрані тільки самі необхідні засоби рисування. Немає навіть методу, що задає колір фону (хоча можна задати колір фону компонента методом setBackground() класу Сomponent). Засоби рисування, виведення тексту в область рисування і виведення зображень значно доповнені і розширені в підкласі Graphics2D, що входить в систему Java 2D. Наприклад, в ньому єсть метод задання кольору фону setBackground(Color с). Перед тим як звернутися до класу Graphics2D, розглянемо засоби класу Graphics для виведення тексту.
9.3. Як вивести текст
Для виведення тексту в область рисування поточним ольором і шрифтом, починаючи з точки (х, у), в класі Graphics єсть декілька методів:
Четвертий метод виводить текст, занесений в обєкт класу, реалізуючого інтерфейс AttributedCharacterІterator. Це дозволяє задавати свій шрифт для кожного символу:
drawString(AttributedCharacterІterator iter, int x, int y)
Точка (х, у) це ліва нижня точка першої літери тексту на базовій лінії (baseline) виведення шрифта.
9.3.1. Як установити шрифт
Метод setFont(Font newFont) класу Graphics установлює поточний шрифт для виведення тексту. Метод getFont() повертає поточний шрифт. Як і все в мові Java, шрифт це обєкт класу Font. Подивимося, які можливості представляє цей клас.
9.3.2. Як задати шрифт
Обєкти класу Font зберігають накреслення (glyphs) символів, що утворюють шрифт. Їх можна створити двома конструкторами:
Типографський пункт в Россії і деяких європейських країнах рівний 0,376 мм, Точніше, 1/72 частини французького дюйма. В англо-американській системі мір пункт рівний 1/72 частини англійського дюйма, 0,351 мм. Останній пункт і використовується в компютерній графіці. Імя шрифту name може бути рядком із фізичним іменем шрифту, наприклад, "Courier New", або один із рядків "Dialog", "Dialoglnput",' "Monospaced", "Serif", "SansSerif", "Symbol". Це так звані логічні імена шрифтів (logical font names). Якщо name == null, то задається шрифт по замовчувнню. Стиль шрифту style це одна із констант класу Font:
Напівжирний курсив (bolditalic) можна задати операцією побітового додавання, Font. BOLD | Font. ITALIC, як це зроблено в лістинзі 8.3. При виведенні тексту логічним іменам шрифтів і стилям співставляються фізичні імена шрифтів (font face name) або імена сімейств шрифтів (font name). Ці імена реальних шрифтів, наявних у графічній підсистемі операційної системи. Наприклад, логічному іменi "Serif" може бути співставлено імя сімейства (family) шрифтів Times New Roman, а в комбінації зі стилями конкретні фізичні імена Times New Roman Bold, Times New Roman Italic. Ці шрифти повинні знаходитися в складі шрифтів графічної системи тієї машини, на якій виконується додаток. Список імен доступних шрифтів можна прородивитися наступними операторами:
Font[] fnt = Toolkit.getGraphicsEnvironment.getAHFonts();
for (int i = 0; i< fnt.length; i++)
System.out.println(fnt[i].getFontName());
В склад SUN J2SDK входить сімейство шрифтів Lucida. Установивши SDK, ви можете бути впевнені, що ці шрифти єсть у вашій системі. Таблиці співставлення логічених і фізичних імен шрифтів знаходяться у файлах з іменами
і т. д. Ці файли повинні бути розташовані в JDK в каталозі jdkl.3\jre\lib або в якому-небудь іншому підкаталозі lib кореневого каталога JDK тієї машини, на якій виконується додаток. Потрібний файл вибирається віртуальною машиною Java по закінченні імені файла. Це закінчення співпадає з міжнародним кодом мови, установленого в локалі або в системній властивості user.language (див. рис. 6.2). Якщо у вас установлена російська локаль з міжнародним кодом мови "ru", то для співставлення буде вибраний файл font.properties.ru. Якщо такий файл не знайдено, то використовується файл font.properties, що не відповідає ніякій конкретній локалі. Тому можна залишити в системі тільки один файл font.properties, переписавши в нього зміст потрібного файла або створивши файл заново. Для будь-якої локалі буде використовуватися цей файл. В лістинзі 9.1 показано скорочений зміст файла font.properties.ru із JDK 1.3 для платформи MS Windows.
Лістинг 9.1. Примірний файл font.properties.ru :
# %W% %E%
# Це просто коментарі
# AWT Font default Properties for Russian Windows
#
# Три співставлення логічному імені "Dialog":
dialog.0=Arial,RUSSIAN_CHARSET
dialog.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
dialog.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
# По три співставлення стилям ITALIC, BOLD, ITALIC+BOLD:
dialog.italic.0=Arial Italic,RUSSIAN_CHARSET
dialog.italic.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
dialog.italic.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
dialog.bold.0=Arial Bold,RUSSIAN_CHARSET
dialog.bold.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
dialog.bold.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
dialog.bolditalic.0=Arial Bold Italic,RUSSIAN_CHARSET
dialog.bolditalic.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
dialog.bolditalic.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
# По три співставлення імені "Dialoglnput" і стилям:
dialoginput.0=Courier New,RUSSIAN_CHARSET
dialoginput.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
dialoginput.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
dialoginput.italic.0=Courier New Italic,RUSSIAN_CHARSET
# і так далі
#
# По три співставлення імені "Serif" і стилям:
serif.0=Times New Roman,RUSSIAN_CHARSET
serif.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
serif.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
serif.italic.0=Times New Roman Italic,RUSSIAN_CHARSET
# і так далі
# Інші логічні імена
sansserif. CMArial,RUSSIAN_CHARSET
sansserif.l=WingDings,SVMBOL_CHARSET,NEED_CONVERTED
sansserif.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
sansserif.italic. 0=Arial Italic,ROSSIAN_CHARSET
# і так далі
#
monospaced.0=Courier New,RUSSIAN_CHARSET
monospaced.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
monospaced.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
monospaced.italic.0=Courier New Italic,RUSSIAN_CHARSET
# і так далі
# Default font definition
#
default.char=2751
# for backword compatibility
# Старі логічні імена версії JDK 1.0
timesroman.0=Times New Roman,RUSSIAN_CHARSET
helvetica.0=Arial,RUSSIAN_CHARSET
courier.0=Courier New,RUSSIAN_CHARSET
zapfdingbats.0=WingDings,SYMBOL_CHARSET
# font filenames for reduced initialization time
# Файли зі шрифтами
filename.Arial=ARIAL.TTF
filename.Arial_Bold=ARIALBD.TTF
filename.Arial_Italic=ARIALI.TTF
filename.Arial_Bold_Italic=ARIALBI.TTF
filename.Courier_New=COUR.TTF
filename.Courier_New_Bold=COURBD.TTF
filename.Courier_New_Italic=COURI.TTF
filename.Courier_New_Bold_Italic=COURBI.TTF
filename.Times_New_Roman=TIMES.TTF
filename.Times_New_Roman_Bold=TlMESBD.TTF
filename.Times_New_Roman_Italic=TIMES3.TTF
filename.Times_New_Roman_Bold Italic=TIMESBI.TTF
filename.WingDings=WINGDING.TTF
filename.Symbol=SYMBOl.TTF
# name aliases
# Псевдоіими логічних імен закоментовані
# alias.timesroman=serif
# alias.helvetica=sansserif
# alias.courier=monospaced
# Static FontCharset info
#
# Класи перетворення символів у байти
fontcharset.dialog.0=sun.io.CharToByteCP1251
fontcharset.dialog.l=sun.awt.windows.CharToByteWingDings
fontcharset.dialog.2=sun.awt.CharToByteSymbol
fontcharset.dialoginput.0=sun.io.CharToByteCP1251
fontcharset.dialoginput.l=sun.awt.windows.CharToByteWingDings
fontcharset.dialoginput.2=sun.awt.CharToByteSymbol
fontcharset.serif.0=sun.io.CharToByteCP1251
fontcharset.serif.l=sun.awt.windows.CharToByteWingDings
fontcharset.serif.2=sun.awt.CharToByteSymbol
fontcharset.sansserif.0=sun.io.CharToByteCP1251
fontcharset.sansserif.l=sun.awt.windows.CharToByteWingDings
fontcharset.sansserif.2=sun.awt.CharToByteSymbol
fontcharset.monospaced.0=sun.io.CharToByteCP1251
fontcharset.monospaced.l=sun.awt.windows.CharToByteWingDings
fontcharset.monospaced.2=sun.awt.CharToByteSymbol
# Exclusion Range info
#
# He проглядати вцьому шрифті вказані діапазони
exclusion.dialog.0=0100-0400,0460-ffff
exclusion.dialoginput.0=0100-0400, 0460-ffff
exclusion.serif.0=0100-0400,04 60-ffff
exclusion.sansserif.0=0100-0400, 0460-ffff
exclusion.monospaced.0=0100-0400,0460-ffff
# charset for text input
#
# Введені байтові символи кодуються в кириличний діапазон кодуванняи Unicode
inputtextcharset=RUSSIAN_CHARSET
Більша частина цього файла зайнята співставленнями логічних і фізичних імен. Ви бачите, що під номером 0:
Там, де указаний стиль: dialog.italic, dialog.bold і т.д., підставлений відповідний фізичний шрифт. В рядках лістинга 9.1, що починаються зі слова filename, указані файли з відповідними фізичними шрифтами, наприклад:
filename.Arial=ARIAL.TTF
Ці рядки необобовязкові, але вони прискорюють пошук файлів зі шрифтами. Теперь подивимося на останні рядки лістинга 9.1. Рядок
exclusion.dialog.0=0100-0400, 0460-ffff
означає, що в шрифті, співставленому логічному імені "Dialog" під номером 0, а саме, Arial, не стануть відшукуватися накреслення (glyphs) символів з кодами в діапазонах '\u0100' '\u0400' і '\u0460' '\uFFFF'. Вони будуть взяті із шрифту, співставленого цьому імени під номером 1, а саме, WingDings. Те ж саме буде відбуватися, якщо потрібні накреслення не знайдені в шрифті, співставленому логічному імені під номером 0. Не всі файли зі шрифтами Unicode містять накреслення (glyphs) всіх символів. Якщо потрібні накреслення не знайдені і в співставленні 1 (в даному прикладі в шрифті WingDings), вони будуть відшукуватися у співставленні 2 (тобто в шрифті Symbol) і т. д. Подібних співставлень можна написати скільки завгодно. Таким чином, кожному логічому імені шрифта можна співставити різні діапазони різних реальних шрифтів, а також застрахуватися від відсутності накреслень деяких символів в шрифтах Unicode.
Всі співставлення під номерами 0, 1, 2, 3, 4 належить повторити для всіх стилів: bold, italic, bolditalic. Якщо в графічній системі використовуються шрифти Unicode, як, наприклад, в MS Windows NT/2000, то більше ні про що не треба турбуватися. Якщо ж графічна система використовує байтові ASCII-шрифти як, наприклад, MS Windows 95/98/ME, то треба потурбуватися про їх правильне перекодування в Unicode і навпаки. Для цього на платформі MS Windows використовуються константи Win32 API RUSSIAN_CHARSET, SYMBOL_CHARSET, ANSI_CHARSET, OEM_CHARSET і ін., показуючі, яку кодову таблицю використовувати при перекодуванні, так же, як це відмічалося в уроці 5 при створенні рядка із масиву байтів. Якщо логічним іменам співставлені байтові ASCII-шрифти (в прикладі це шрифти WingDings і Symbol), то необхідність перекодування задається константою NEED_CONVERTED. Перекодуванням займаються методи спеціальних класів charToByteCP1251, TiarToByteWingDings, CharToByteSymbol. Вони указуються для кожного співставлення імен в рядках, що починаються зі слова fontcharset. Ці рядки обовязкові для всіх шрифтів, помічених константою NEED_CONVERTED. В останньому рядку файла указана кодова сторінка для перекодування в Unicode символів, що вводяться в поля введення:
inputtextcharset = RUSSIAN_CHARSET
Цей запис задає кодову таблицю СР1251. Отже, збираючись виводити рядок str в графічний контекст методом drawstring(), ми створюємо поточний шрифт конструктором класуа Font, указуючи в ньому логічне імя шрифту, наприклад, "Serif". Виконуюча система Java відшукує у файле font.properties, відповідному локальній мові, співставлений цьому логічному імені фізичний шрифт операційної системи, наприклад, Times New Roman. Якщо це Unicode-шрифт, то із нього добуваються накреслення символів рядка str по їх кодуванню Unicode і відображаються в графічний контекст. Якщо це байтовий ASCII-шрифт, то рядок str попередньо перекодовується в масив байтів методами класу, указаного в одному із рядків fontcharset, наприклад, CharToByteCP1251. Хороші приклади файлів font.properties.ru зібрані на сторінці Сергія Астахова, указаній в уроці 0. Обговорення цих питань і прикладів файлів font.properties для X Window System дані в документації SUN J2SDK в файлі docs/guide/intl /fontprop.html.
Завершуючи обговорення логічних і фізичних імен шрифтів, належить сказати, що в JDK 1.0 використовувались логічні імена "Helvetica", "TimesRoman", "Courier", замінені в JDK 1.1.З "SansSerif", "Serif", "Monospaced", відповідно, із ліцензійних міркувань. Старі імена залишились у файлах font.properties для сумісності.
При виведенні рядка у вікно додатку дуже часто виникає необхідність розташувати її певним способом відносно інших елементів зображення: центрувати, вивести над або під іншим графічним обєктом. Для цього треба знати метрику рядка: ії висоту і ширину. Для вимірювання розмірів окремих символів і рядка в цілому розроблений клас FontMetrics. В Java 2D клас FontMetrics замінений класом TextLayout. Його ми розглянемо в кінці цього уроку , а зараз вияснимо, яку користь можна отримати із методів класу FontMetrics.
9.4. Класс FontMetrics
Клас FontMetrics являється абстрактним, тому не можна скористатися його конструктором. Для одержання обєкта класу FontMetrics, що містить набір метричних характеристик шрифту f, треба звернутися до методу getFontMetrics (f) класу Graphics або класу Component. Клас FontMetrics дозволяє узнати ширину окремого символу ch в пікселях методом charwidth(ch), загальну ширину всіх символів масиву або під-масиву символів або байтів методами getСhars() і getBytes(), ширину цілого рядка str в пікселях методом stringwidth(str).
Декілька методів повертають в пікселях вертикальні розміри шрифту. Інтерліньяж (leading) відстань між нижньою точкою звисаючих елементів такихлітер, как р, у і верхньою точкою виступаючих елементів таких літер, як б і в наступного повертає метод getLeading(). Середня відстань від базової лінії шрифту до верхньої точки прописных літер і виступаючих елементів того ж рядка (ascent) повертаєт метод getAscent(), а максимальне метод getMaxAscent(). Середню відстань звисаючих елементів від базової лінії того ж рядка (descent) повертає метод getDescent(), а максимальну метод getMaxDescent(). Нарешті, висоту шрифту (height) суму ascent + descent + leading повертає метод getHeight(). Висота шрифту рівна відстані між базовими лініями сусідніх рядків. Ці елементи показані на рис. 9.1.
Рис. 9.1. Елементи шрифту
Додаткові характеристики шрифту можно визначити методами класу LineMetrics із пакету java.awt.font. Обєкт цього класу можна одержати кількома методами getLineMetrics() класу FontMetrics. Лістинг 9.2 показує застосування графічних примітивів і шрифтів, а рис. 9.2 результат виконання програми із цього лістингу.
Лістинг 9.2. Використання графічиих примітивів і шрифтів
import java.awt.*;
import java.awt.event.*;
class GraphTest extends Frame{
GraphTest(String s){
super(s);
}
public void paint(Graphics g){
Dimension d = getSize();
int dx = d.width / 20, dy = d.height / 20;
g.drawRect(dx, dy + 20, d.width - 2*dx, d.height - 2*dy - 20);
g.drawRoundRect(2 * dx, 2*dy + 20, d.width - 4*dx, d.height - 4*dy - 20, dx, dy);
g.fillArc(d.width / 2 - dx, d.height - 2*dy + 1, 2*dx, dy - 1, 0, 360);
g.drawArc(d.width / 2 - 3*dx, d.height - 3*dy / 2 - 5, dx, dy / 2, 0, 360);
g.drawArc(d.width / 2 + 2*dx, d.height - 3*dy / 2 - 5, dx, dy / 2, 0, 360);
Font f1 = new Font("Serif", Font.BOLD|Font.ITALIC, 2 * dy);
Font f2 = new Font ("Serif", Font.BOLD, 5 * dy / 2);
FontMetrics fm1 = getFontMetrics(f1);
FontMetrics fm2 = getFontMetrics(f2);
String s1 = "Всяка останя помилка";
String s2 = "являється передостанньою.";
String s3 = "Закон налаштування програми";
int firstLine = d.height / 3;
int nextLine = fm1.getHeight();
int secondLine = firstLine + nextLine / 2;
g.setFont(f2);
g.drawString(s3, (d.width-fm2.stringWidth(s3)) / 2, firstLine);
g.drawLine(d.width / 4, secondLine - 2,3 * d.width / 4, secondLine - 2);
g.drawLine(d.width / 4, secondLine - 1, 3 * d.width / 4, secondLine - 1);
g.drawLine(d.width / 4, secondLine, 3 * d.width / 4, secondLine);
g.setFont(f1);
g.drawString(s1, (d.width - fm1.stringWidth(s1)) /2, firstLine + 2 * nextLine);
g.drawString(s2, (d.width - fm1.stringWidth(s2)) / 2, firstLine + 3 * nextLine);
}
public static void main(String[] args){
GraphTest f = new GraphTest(" Приклад рисування");
f.setBounds(0, 0, 500, 300);
f.setVisible(true);
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
}
В лістинзі 9.2 використаний простий клас Dimension, головне призначення якого зберігати ширину і висоту прямокутного обєкта в своїх полях width і height. Метод getsize() класу Сomponent повертає розміри компонента у вигляді обєкта класу Dimension. В лістинзі 9.2 розміри компонента f типу GrapMest установлені в конструкторі методом setBounds() рівнbми 500x300 пікселів. Ще одна особливість лістингу 9.2 для викреслювання товстої лінії, відділяючої заголовок від тексту, прийшлось провести три паралельні прямі на відстані один піксель одна від одної.
Рис. 9.2. Приклад використання класу Graphics
Як бачимо із огляду класу Graphics і супутніх йому класів, засоби рисування и виведення тексту в цьому класі досить обмежені. Лінії можна проводити тільки суцільні і тільки товщиною в один піксель, текст виводиться тільки горизонтально і зліва направо, не враховуються особливості пристроїв виведення, наприклад, розрішимість екрана. Ці обмеження можна обійти різними хитрощами: креслити декілька паралельних ліній, притиснутих одна до одної, як в лістинзі 9.2, або вузький заповнений прямокутник, виводити текст по одній літері, одердати розрішимість екрана методом getScreenSize() класу Java.awt.Toolkit і використовувати його надалі. Але все це утруднює програмування, лишає його стрункості і природності, порушує принцип KISS. В Java 2 клас Graphics, в рамках системи Java 2D, значно розширений класом Graphics2D.
9.5. Можливості Java 2D
В систему пакетів і класів Java 2D, основа якої клас Graphics2D пакета java.awt, внесено декілька принципово нових положень.
З такими можливостями Java 2D стала повноцінною системою рисування, виведення тексту і зображень. Подивимося, як реалізовані ці можливості, і як ними можна скористатися.
9.6. Перетворення координат
Правило перетворення координат користувача в координати графічного пристрою (transform) задається автоматично при створенні графічного контексту так же, як колір і шрифт. Надалі його можна змінити методом setTransform() так же, як змінюються колір або шрифт. Аргументом цього методу служить обєкт класу AffineTransform із пакету java.awt.geom, подібно обєктам класу Сolor або Font при заданні кольору або шрифту. Ррозглянемо детальніше клас AffineTransform.
Афінне перетворення координат задається двома основними конструкторами класу AffineTransform:
AffineTransform(double a, double b, double с, double d, double e, double f)
AffineTransform (float a, float b, float c, float d, float e, float f)
При цьому точка з координатами (х, у) в просторі користувача перейде в точку з координатами (а*х+с*у+е, b*x+d*y+f) в просторі графічного пристрою. Таке перетворення не викривляє площину прямі лінії переходять в прямі, кути мж лініями зберігаються. Прикладами афінних перетворень служать повороти навколо будь-якої точки на будь-який кут, паралельні зcуви, відображення від осей, стиснення і розтягнення по вісям.
Наступні два конструктори використовують в якості аргумента масив {а, b, с, d, e, f} або {а, b, с, d}, якщо
e = f = 0, складений із таких же коефіцієнтів в тому ж порядку:
AffineTransform(double[] arr)
AffineTransform(float[] arr)
Пятий конструктор створює новий обєкт по іншому, уже наявному, обєкту:
AffineTransform(AffineTransform at)
Шостий конструктор конструктор по замовчуванню створює тотожне перетворення:
AffineTransform ()
Ці конструктори математично точні, але незручні при заданні конкретних перетворень. Попробуйте розрахувати коефіцієнти повороту на 57° навколо точки з координатами (20, 40) або догадатися, як буде перетворено простір користувача після виконання методів:
AffineTransform at = new AffineTransform(-1.5, 4.45, -0.56, 34.7, 2.68, 0.01);
g.setTransform(at);
В багатьох випадках зручніше створювати перетворення статичними методами, повертаючими обєкт класу AffineTransform.
getRotatelnstance (double angle) повертає поворот на кут angle, заданий в радіанах, навколо початку координат. Додатній напрям повороту такий, що точки вісі Ох повертаються в напряму до вісі Оу. Якщо вісі координат користувача не змінювалися перетворенням відображення, то додатнє значення angle задає поворот по годинниковій стрілці.
Метод createІnverse() повертає перетворення, обернене поточному перетворенню. Після створення перетворення його можна змінювати методами:
зробивши поточним перетворення, задане одним із цих методів. Перетворення, задані методами:
виконується перед поточним перетворенням, створюючи композицію перетворень.
Перетворення, задане методом preconcatenate{AffineTransform at), навпаки, здійснюються післе поточного перетворення. Інші методи класу AffineTransform виконують перетворення різних фігур в просторі користувача. Пора привести приклад. Додамо в початок методу paint() в лістинзі 9.2 чотири оператори, як записано в лістинзі 9.3.
Лiстинг 9.3. Перетворення простору користувача
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
class GraphTest extends Frame{
GraphTest(String s){
super(s);
}
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
AffineTransform at = AffineTransform.getRotateInstance(-Math.PI/4.0, 250.0,150.0);
at.concatenate(new AffineTransform(0.5, 0.0, 0.0, 0.5, 100.0, 60.0));
g.setTransform(at);
Dimension d = getSize();
int dx = d.width / 20, dy = d.height / 20;
g.drawRect(dx, dy + 20, d.width - 2*dx, d.height - 2*dy - 20);
g.drawRoundRect(2 * dx, 2*dy + 20, d.width - 4*dx, d.height - 4*dy - 20, dx, dy);
g.fillArc(d.width / 2 - dx, d.height - 2*dy + 1, 2*dx, dy - 1, 0, 360);
g.drawArc(d.width / 2 - 3*dx, d.height - 3*dy / 2 - 5, dx, dy / 2, 0, 360);
g.drawArc(d.width / 2 + 2*dx, d.height - 3*dy / 2 - 5, dx, dy / 2, 0, 360);
Font f1 = new Font("Serif", Font.BOLD|Font.ITALIC, 2 * dy);
Font f2 = new Font ("Serif", Font.BOLD, 5 * dy / 2);
FontMetrics fm1 = getFontMetrics(f1);
FontMetrics fm2 = getFontMetrics(f2);
String s1 = "Всяка останя помилка";
String s2 = "являється передостанньою.";
String s3 = "Закон налаштування програми";
int firstLine = d.height / 3;
int nextLine = fm1.getHeight();
int secondLine = firstLine + nextLine / 2;
g.setFont(f2);
g.drawString(s3, (d.width-fm2.stringWidth(s3)) / 2, firstLine);
g.drawLine(d.width / 4, secondLine - 2,3 * d.width / 4, secondLine - 2);
g.drawLine(d.width / 4, secondLine - 1, 3 * d.width / 4, secondLine - 1);
g.drawLine(d.width / 4, secondLine, 3 * d.width / 4, secondLine);
g.setFont(f1);
g.drawString(s1, (d.width - fm1.stringWidth(s1)) /2, firstLine + 2 * nextLine);
g.drawString(s2, (d.width - fm1.stringWidth(s2)) / 2, firstLine + 3 * nextLine);
}
public static void main(String[] args){
GraphTest f = new GraphTest(" Приклад рисування");
f.setBounds(0, 0, 500, 300);
f.setVisible(true);
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
}
Метод paint () починається з отримання екземпляру g класу Graphics2D простим приведенням аргументу gr до типу Graphics2D. Потім, методом getRotatelnstance() визначаються поворот на 45° проти годинникової стрілки навколо точки (250.0, 150.0). Це перетворення экземпляр at класу AffineTransform. Метод concatenate(), виконуваний обєктом at, додає до цього перетворення стиснення в два рази по обом вісям координат і перенос початку координат в точку (100.0, 60.0). Нарешті, композиція цих перетворень установлюється як поточне перетворення обєкта g методом setTransform(). Перетворення виконується в наступному порядку. Спочатку простір користувача стискується в два рази вподовж обох вісей, потім початок координат користувача лівий верхній кут переноситься в точку (100.0, 60.0) простору графічного пристрою. Потім картинка повертається на кут 45° проти часової стрілки навколо точки (250.0, 150.0). Результат цих перетворень показаний на рис. 9.3.
Рис. 9.3. Перетворення координат
9.7. Рисування фігур засобами Java2D
Характеристики пера для рисування фігур описані в інтерфейсі stroke. В Java 2D єсть поки що тільки один клас, реалізуючий цей інтерфейс клас BasicStroke.
9.7.1. Класс BasicStroke
Конструктори класу BasicStroke визначають характеристики пера. Основний конструктор
BasicStroke(float width, int cap, int join, float miter, float[] dash, float dashBegin)
задає:
Решта конструкторів задають деякі характеристики по замовчуванню:
Краще один раз побачити, ніж сто раз прочитати. В лістинзі 9.4 визначено пять пер з різними характеристиками, рис. 9.4 показує, як вони рисують.
Лістинг 9.4. Визначення пер
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
class StrokeTest extends Frame{
StrokeTest(String s) {
super (s) ;
setSize(500, 400);
setVisible(true);
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
g.setFont(new Font("Serif", Font.PLAIN, 15));
BasicStroke pen1 = new BasicStroke(20, BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER,30);
BasicStroke pen2 = new BasicStroke(20, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
BasicStroke рen3 = new BasicStroke(20, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
float[] dash1 = {5, 20};
BasicStroke pen4 = new BasicStroke(10, BasicStroke.CAP_ROUND,BasicStroke.JOIN_BEVEL, 10, dash1, 0);
float[] dash2 = {10, 5, 5, 5};
BasicStroke pen5 = new BasicStroke(10, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, dash2, 0);
g.setStroke(pen1);
g.draw(new Rectangle2D.Double(50, 50, 50, 50));
g.draw(new Line2D.Double(50, 180, 150, 180));
g.setStroke(pen2);
g.draw(new Rectangle2D.Double(200, 50, 50, 50));
g.draw(new Line2D.Double(50, 230, 150, 230));
g.setStroke(рen3);
g.draw(new Rectangle2D.Double(350, 50, 50, 50));
g.draw(new Line2D.Double(50, 280, 150, 280));
g.drawString("JOIN_MITER", 40, 130);
g.drawString("JOIN_ROUND", 180, 130);
g.drawString("JOINJBEVEL", 330, 130);
g.drawString("CAP_BUTT", 170, 190);
g.drawString("CAP_ROUND", 170, 240);
g.drawString("CAP_SQUARE", 170, 290);
g.setStroke(pen5);
g.draw(new Line2D.Double(50, 330, 250, 330));
g.setStroke(pen4);
g.draw(new Line2D.Double(50, 360, 250, 360));
g.drawString("{10, 5, 5, 5,...}", 260, 335);
g.drawString("(5, 10,...}", 260, 365);
}
public static void main(String[] args){
new StrokeTest("Мої пера");
}
}
Після створення пера одним із конструкторов і установки пера методом setStroke() можна рисувати різні фігури методами draw() і fill(). Загальні вастивості фігур, які можна нарисувати методом draw() класу Graphics2D, описані в інтерфейсі shape. Цей інтерфейс реалізований для сстворення звичайного набору фігур прямокутников, прямих, эліпсів, дуг, точок класами Rectangle2D, RoundRectangle2D, Line2D, Ellipse2D, Arc2D, Point2D пакету java.awt.geom. В цьому пакеті єсть ще класи CubicСurve2D і QuadCurve2D для створення кривих третього і другого порядку. Всі ці класи абстрактні, але існують їх реалізації вкладені класи Double і Float для задання координат числами відповідного типа. В Лістинзі 9.4 використані класи Rectangle2D.Double і Line2d.Double для викреслювання прямокутників і відрізків.
9.7.2. Клас GeneralPath
В пакеті java.awt.geom єсть ще один цікавий клас GeneralPath. Обєкти цього класу можуть містити складні конструкції, складені із відрізків прямых або кривих ліній і інших фігур, або не зєднаних між собою. Більше того, оскільки цей клас реалізує інтерфейс shape, його екземпляри самі являються фігурами і можуть бути елементами інших обєктів класу GeneralPath.
Спочатку створюється пустий обєкт класуа GeneralPath конструктором по замовчуванню GeneralPath() або обєкт, що містить одну фігуру, конструктором GeneralPath(Shape sh). Потім до цього объекта додаються фігуры методом append(Shape sh, boolean connect). Якщо параметр connect рівний true, то нова фігура зєднується з попередніми фігурами за допомогою поточного пера. В обєкті єсть поточна точка. Спочатку її координати (0, 0), потім її можна перемістити в точку (х, у) методом moveTo(float x,float у).
Від поточної точки до точки (х, у) можна провести:
Поточною точкою після цього становитсья точка (х, у). Початкову і кінцеву точки можна зєднати методом cІosePath(). Ось як можна створити трикутник із заданими вершинами:
GeneralPath p = new GeneralPath();
p.moveTo(xl, yl); // Переносимо поточну точку в першу вершину,
p.lineTo(x2, y2); // проводимо сторону трикутника до другої вершини,
p.lineTo(x3, уЗ); // проводимо другу сторону,
p.closePathf); // проводимо третю сторону до першої вершини
Способи заповнення фігур визначені в інтерфейсі Paint. Зараз Java 2D містить три реалізації цього інтерфейса класи Сolor, GradientPaint і TexturePaint. Клас Color нам відомий, подивимось, які способи заливки пропонують класи GradientPaint і TexturePaint.
Рис. 9.4. Пера з різними характеристиками
9.7.3. Класи GradientPaint і TexturePaint
Клас GradientPaint пропонує зробити заливку наступним чином. В двох точках М і N устанавлюються різні кольори. В точці M(x1, y1) задається колір c1, в точці N(х2, у2) колір с2. Колір заливки гладко змінюється від с1 до с2 вподовж прямої, що зєднує точки М і N, залишаючись постійним вподовж кожної прямої, перпендикулярної прямій МN. Таку заливку створює конструктор
GradientPaint(float x1, float y1, Color c1, float x2, float y2, Color c2)
При цьому зовні відрізка МN колір залишається постійним: за точкою М - колір c1, за точкою N - колір с2.
Другий конструктор
GradientPaint(float xl, float yl, Color cl, float x2, float y2, Color c2, boolean cyclic)
при заданні параметра cyclic == true повторює заливку смуги МN у всій фігурі. Ще два конструктори задають точки як обєкти класу Point2D.
Класс TexturePaint поступє складніше. Спочатку створюється буфер обєкт класу Bufferedlmage із пакету java.awt.image. Це великий складний клас. Ми з ним ще зустрінемося в уроці 15, а поки що нам знадобиться тільки його графічний контекст, що управляється екземпляром класу Graphics2D. Цей екземпляр можна отримати методом createGraphics() класу Bufferedlmage. Графічний контекст буферу заповнюється фигурою, яка буде служить зразком заповнення. Потім по буферу створюється обєкт класу TexturePaint. При цьому ще задається прямокутник, розміри якого будуть розмірами зразка заповнення. Конструктор виглядає так:
TexturePaint(Bufferedlmage buffer, Rectangle2D anchor)
Після створення заливки обєкта класу Сolor, GradientPaint або TexturePaint вона установлюється в графічному контексті методом setPaint (Paint p) і використовується надалі методом fill (Shape sh). Все це демонструє лістинг 9.5 і рис. 9.5.
Лістинг 9.5. Способи заливки
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.event.*;
class PaintTest extends Frame{ PaintTest(String s){ super(s) ;
setSize(300, 300);
setVisible(true);
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){ System.exit(0); } });
}
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
BufferedImage bi = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);
Graphics2D big = bi.createGraphics();
big.draw(new Line2D.Double(0.0, 0.0, 10.0, 10.0));
big.draw(new Line2D.Double(0.0, 10.0, 10.0, 0.0));
TexturePaint tp = new TexturePaint(bi,
new Rectangle2D.Double(0.0, 0.0, 10.0, 10.0));
g.setPaint(tp);
g.fill(new Rectangle2D. Double (50, 50, 200, 200));
GradientPaint gp = new GradientPaint(100, 100, Color.white,
150, 150, Color.black, true); g.setPaint(gp);
g.fill(new Ellipse2D.Double (100, 100, 200, 200));
}
public static void main(String[] args){
new PaintTest(" Способи заливки");
}
}
Рис. 9.5. Способи заливки
9.8. Виведення тексту засобами Java 2D
Шрифт обєкт класу Font крім імені, стилю і розміру має ще півтора десятка атрибутів: підкреслювання, закреслювання, нахили, колір шрифту і колір фону, ширину і товщину символів, аффінне перетворення, розташування зліва направо або справа наліво. Атрибути шрифту задаються як статичні константи класу TextAttribute. Найбільш використовувані атрибути перечислені в табл. 9.1.
Таблиця 9.1. Атрибути шрифту
Атрибут |
Значення |
BACKGROUND |
Колір фону. Обєкт, реалізуючий інтерфейс Paint |
FOREGROUND |
Колір тексту. Обєкт, реалізуючий інтерфейс Paint |
BIDI EMBEDDED |
Рівень вкладеності перегляду тексту. Ціле від 1 до 1 5 |
CHAR_REPLACEMENT |
Фігура, що заміняє символ. Обєкт GraphicAttribute |
FAMILY |
Сімейство шрифта. Рядок типу string |
FONT |
Шрифт. Обєкт класу Font |
JUSTIFICATION |
Допуск при вирівнюванні абзаца. Обєкт класу Float із значеннями від 0,0 до 1,0. Єсть дві константи: JUSTIFICATION FULL і JUSTIFICATION NONE |
POSTURE |
Нахил шрифту. Обєкт класу Float. Єсть дві константи: POSTURE_OBLIQUE і POSTURE_REGULAR |
RUNJHRECTION |
Перегляд тексту: RUN DIRECTION LTR зліва направо, RUN DIRECTION RTL справа наліво |
SIZE |
Розмір шрифта в пунктах. Обєкт класу Float |
STRIKETHROUGH |
Перекреслювання шрифта. Задається константою STRIKETHROUGH ON, по замовчуванню перекреслювання немає |
SUPERSCRIPT |
Нижні або верхні індекси. Константи: SUPERSCRIPT_NONE, SUPERSCRIPT_SUB, SUPERSCRIPT_SUPER |
SWAP COLORS |
Переміна місцями кольору тексті і кольору фону. Константа SWAP COLORS ON, по замовчуванню заміни немає |
TRANSFORM |
Перетворення шрифту. Обєкт класу AffineTransform |
UNDERLINE |
Підкреслювання шрифту. Константи: UNDERLINE_ON, UNDERLINE_LOW_DASHED, UNDERLINE_LOW_DOTTED, UNDERLINE LOW GRAY, UNDERLINE LOW ONE PIXEL, UNDERLINE_LOW_TWO_PIXEL |
WEIGHT |
Товщина шрифту. Константи: WEIGHT ULTRA LIGHT, WEIGHT _ EXTRA_LIGHT, WEIGHT _ LIGHT, WEIGHT _ DEMILIGHT, WEIGHT _ REGULAR, WEIGHT _ SEMIBOLD, WEIGHT MEDIUM, WEIGHT DEMIBOLD, WEIGHT _ BOLD, WEIGHT HEAVY, WEIGHT _ EXTRABOLD, WEIGHT _ ULTRABOLD |
WIDTH |
Ширина шрифту. Константи: WIDTH CONDENSED,WIDTH SEMI CONDENSED, WIDTH REGULAR, WIDTH_SEMI_EXTENDED, WIDTH_EXTENDED |
На жаль, не всі шрифти дозволяют задавати всі атрибути. Побачити список допустимих атрибутів для даного шрифта можна методом getAvailableAttributes() класу Font. В класі Font єсть конструктор Font(Map attributes), яким можна зразу задати потрібні атрибути створюваному шрифту. Це вимагає попереднього запису атрибутів у спеціально створений для цього обєкт класу, реалізуючий інтерфейс Map: класу HashMap, WeakHashMap або Hashtable (див. урок 7). Наприклад:
HashMap hm = new HashMap ();
hm.put(TextAttribute.SIZE, new Float(60.Of));
hm.put(TextAttribute.POSTURE, TextAttribute.POSTUREJDBLIQUE);
Font f = new Font(hm);
Можна створити шрифт і другим конструктором, яким ми користувалися в лістинзі 9.2, а потім додавати і змінювати атрибути методами deriveFont() класу Font. Текст в Java 2D має свій власний контекст обєкт класу FontRenderContext, що зберігає всю інформацію, необхідну для виведення тексту. Отримати його можна методом getFontRendercontext() класу Graphics2D. Вся інформація про текст, в тому числі і про його контекст, збирається в обєкті класу TextLayout. Цей класс в Java 2D заміняє клас FontMetrics. В конструкторі класу TextLayout задається текст, шрифт і контекст. Початок методу paint() з усіма цими визначеннями може виглядати так:
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
FontRenderContext frc = g.getFontRenderContext();
Font f = new Font("Serif", Font.BOLD, 15);
String s = "Якийсь текст";
TextLayout tl = new TextLayout(s, f, frc) ; // Продовження методу }
В класі TextLayout єсть не тільки більше двадцати методів getxxxo, що дозволяють узнати різні дані про текст, його шрифте і контекст, але і метод
draw(Graphics2D g, float x, float у)
що викреслює зміст обєкта класу TextLayout в графічній області g, починаючи з точки (х, у). Ще один цікавий метод
getOutline(AffineTransform at)
повертаєт контур шрифту у вигляді обєкта shape. Цей контур можна потім заповнити по якому-небудь зразку або вивести тільки контур, як показано в лістинзі 9.6.
Лістинг 9.6. Виведення тексту засобами Java 2D
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.event.*;
class StillText extends Frame{
StillText(String s) {
super(s);
setSize(400, 200);
setVisible(true);
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0) ;
}
});
}
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
int w = getSize().width, h = getSize().height;
FontRenderContext frc = g.getFontRenderContext();
String s = "Тінь";
Font f = new Font("Serif", Font.BOLD, h/3);
TextLayout tl = new TextLayout(s, f, frc);
AffineTransform at = new AffineTransform();
at.setToTranslation(w/2-tl.getBounds().getWidth()/2, h/2);
Shape sh = tl.getOutline(at);
g.draw(sh);
AffineTransform atsh = new AffineTransform(1, 0.0, 1.5, -1, 0.0, 0.0);
g.transform(at);
g.transform(atsh);
Font df = f.deriveFont(atsh);
TextLayout dtl = new TextLayout(s, df, frc);
Shape sh2 = dtl.getOutline(atsh);
g.fill(sh2); }
public static void main(String[] args) {
new StillText(" Ефект тіні");
}
}
На рис. 9.6 показано виведення цієї програми.
Рис. 9.6. Виведення тексту засобами Java 2D
Ще одна можливість створити текст з атрибутами визначити обєкт класуа AttributedString із пакета java. text. Конструктор цього класу
AttributedString(String text, Map attributes)
задає зразу і текст, і його атрибути. Потім можна додати або змінити характеристики тексту одним із трьох методів addAttibute().
Якщо текст займає декілька рядків, то постає питання його форматування. Для цього замість класу TextLayout використовується клас LineBreakMeasurer, методи якого дозволяють відформатувати абзац. Для кожного сегмента тексту можна одержати екземпляр класу TextLayout і вивести текст, використовуючи його атрибути. Для редагування тексту необхідно відслідкувати курсором (caret) поточну позицію в тексті. Це здійснюється методами класу TextHitinfo, а методи класу TextLayout дозволяють одержати позицію курсора, виділити блок тексту і підсвітити його. Нарешті, можна задати окремі правила для виведення кожного символу текста. Для цього треба одержати екземпляр класу Glyphvector методом createGІyphvector() класу Font, змінити позицію символу методом setСІiyphPosition(), задати перетворення символу, якщо це допустимо для даного шрифта, методом setСІyphTransform(), і вивести змінений текст методом drawGІyphVector() класу Graphics2D. Все це показано в лістинзі 9.7 і на рис. 9.7 виведення програми лістинга 9.7.
Лістинг 9.7. Виведення окремих символів
import j ava.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.event.*;
class GlyphTest extends Frame{ GlyphTest(String s){ super(s) ;
setSize(400, 150);
setVisible(true);
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
public void paint(Graphics gr){
int h = 5;
Graphics2D g = (Graphics2D)gr;
FontRenderContext frc = g.getFontRenderContext();
Font f = new Font("Serif", Font.BOLD, 30);
GlyphVector gv = f.createGiyphvector(frc, "Танцюючий текст");
int len = gv.getNumGlyphs();
for (int i = 0; i < len; i++){
Point2D.Double p = new Point2D.Double(25 * i, h = -h);
gv.setGlyphPosition(i, p) ;
}
g.drawGiyphVector(gv, 10, 100); }
public static void main(String[] args)(
new GlyphTest(" Виведення окремих символів");
}
}
Рис. 9.7. Виведення окремих символів
9.9. Методи покращення візуалізації
Візуалізацію (rendering) створеної графіки можна удосконалити, установивши один із методів (hint) покращення одним із методів класу Graphics2D:
setRenderingHints(RenderingHints.Key key, Object value)
setRenderingHints(Map hints)
Ключі методи покращення і їх значення задаються константами класу RenderingHints, перечисленными в табл. 9.2.
Таблиця 9.2. Методи візуалізації і їх значення
Метод |
Значення |
KEY_ANTIALIASING |
Розмивання крайніх пікселів ліній для гладкості зображення; три значення, що задаються константами VALUE ANTIALIAS DEFAULT, VALUE ANTIALIAS ON, VALUE~ANTIALIAS OFF |
KEY_TEXT_ANTTALIAS ING |
Tе ж для тексту. Константи: VALUE TEXT ANTIALIASING_DEFAULT, VALUE TEXT ANTIALIASING ON, VALUE_TEXT_ANTIALIASING_OFF |
KEY RENDERING |
Три типи візуалізації. Константи: VALUE RENDER SPEED, VALUE RENDER QUALITY, VALUE RENDER DEFAULT |
KEY COLOR RENDERING |
Tе ж для кольору. Константи: VALUE COLOR RENDER_SPEED, VALUE COLOR RENDER QUALITY, VALUE COLOR RENDER DEFAULT |
KEY ALPHA INTERPOLATION |
Плавне спряження ліній. Константи: VALUE ALPHA INTERPOLATION_SPEED, VALUE ALPHA INTERPOLATION QUALITY, VALUE_ALPHA_INTERPOLATION_DEFAULT |
KEY_INTERPOLATION |
Способи спряження. Константи: VALUE_INTERPOLATION_BILINEAR, VALUE INTERPOLATION BICUBIC, VALUE INTERPOLATION_NEAREST_NEIGHBOR |
KEY_DITHERING |
Заміна близьких кольорів. Константи: VALUE DITHER ENABLE, VALUE DITHER DISABLE, VALUE DITHER DEFAULT |
He всі графічні системи забезпечують виконання цих методів, тому задання указаних атрибутив не означає, що визначені ними методи будуть застосовуватися на самім ділі. Ось як може виглядати початок метода paint() з указанням методів покращення візуалізації:
public void paint(Graphics gr){
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
// Продовження методу
}
9.10. Заключення
В цьому уроці ми, розуміється, не змогли детально разібрати всі можливості Java 2D. Ми не торкнулись моделів задання кольору і змішування кольорів, друку графіки і тексту, динамічного завантаження шрифтів, зміни області рисування. В уроці 15 ми розглянемо засоби Java 2D для роботи із зображеннями, в уроці 18 засоби друку. В документації SUN J2SDK, в каталозі docs\guide\2d\spec, єсть керівництво Java 2 API Guide з оглядом всіх можливостей Java 2D. Там єсть посилання на посібники по Java 2D. В каталозі demo\jfc\Java2D\src приведени вихідні тексти програм, що використовують Java 2D.
Лабораторна робота 8. Робота з класом Graphics і Graphics2D