Зміст
Вступ
Mono забезпечує два механізми малювання
API (анґлійською Application Programming Interface) — прикладни́й програ́мний інтерфе́йс, інтерфейс програмування застосунків, інтерфейс прикладного програмування — набір визначень підпрограм, протоколів взаємодії та засобів для створення програмного забезпечення.
Інакше кажучи, це набір чітко визначених методів для взаємодії різних складових, що надає розробнику засоби для швидкої розробки програмного забезпечення.
Cairo — бібліотека для створення й опрацювання двовимірних малюнків.
Використання Mono.Cairo вимагає попереднього встановлення
MonoDevelop і Cairo.
Модель малювання Cairo описує алгоритм того, як cairo моделює малюнок. Вона містить лише кілька понять, які застосовують багатократно й різними методами. Спочатку буде описано іменники. Після цього — методи, які описують маніпулювання іменниками у ході створення зображення.
Іменники Cairo. Перші три іменники — призначення, джерело, маска зображено трьома шарами наступних малюнків. Четвертий іменник — шлях, який малюють на середньому шарі, коли це доречно. Кінцевий іменник, контекст, не показано.
Призначення — це поверхня, на якій малюють. Її можна прив’язати до масиву пікселів або до файлу SVG чи PDF або до чогось іншого.
ImageSurface surface = new ImageSurface(Format.RGB24, 120, 120); Context cr = new Context(surface);
Контекст cr у цьому прикладі прив’язано до поверхні surface зображення розміром 120×120 пікселів і 32 біт на піксель для зберігання даних колірної моделі RGB і Alpha-каналу. Поверхні можна створювати для більшості серверних модулів cairo, подробиці подано у посібнику (анґлійською).
Cairo малює таким чином: джерело та маску розміщують над пунктом призначення. Потім усі шари стискають разом, і фарбу з джерела переносять до місця призначення — туди, куди це дозволяє маска.
Найкращим місцем для створення й використання Context є ExposeEvent для даного віджета. Зазвичай потрібно використовувати Gtk.DrawingArea. Наприклад, наступним чином.
void OnDrawingAreaExposed (object o, ExposeEventArgs args) { DrawingArea area = (DrawingArea) o; using (Cairo.Context g = Gdk.CairoHelper.Create (area.GdkWindow)) { // Здійснити малювання g.GetTarget().Dispose(); // прибирання сміття "вручну" } }
MoveTo (double x, double y) — переносить перо у точку з вказаними координатами (x, y) без долучення до контуру відповідної ланки. Інакше кажучи, змінює поточну точку.
LineTo (double x, double y) — додає до контура відрізок прямої з поточної точки у точку, координати (x, y) якої вказано як аргументи.
RelLineTo (double dx, double dy) — додає до контура відрізок прямої з поточної точки у точку, координати якої відрізняються від координат поточної на dx і dy — див. приклад частини коду
cr.MoveTo(0.25, 0.25); cr.LineTo(0.5, 0.375); cr.RelLineTo(0.25, -0.125);
для створення такого зображення без кругів у початковій та кінцевій вершинах контура.
Arc (double x, double y, double r, double angle1, double angle2) — додає до контура дугу кола з таким тлумаченням аргументів:
x, y — координати цетра кола;
r — радіус кола;
angle1, angle2 — кутові аргументи початку й кінця дуги при обході у напрямку руху годинникової стрілки. Якщо попередньої поточної (контрольної точки) немає на дузі, до контура буде додано відрізок прямої від неї до початку дуги. За поточну (контрольну) точку буде обрано кінець дуги. Наприклад, якщо до розглянутого вище коду додати наступну вказівку,
cr.Arc(0.5, 0.5, 0.25 * Math.Sqrt(2), -0.25 * Math.PI, 0.25 * Math.PI);
буде отримано таке зображення.
ArcNegative — аналог Arc з протилежним напрямком обходу кола.
CurveTo (double x1, double y1, double x2, double y2, double x3, double y3) — додає до контура кубічний сплайн Безьє з таким тлумаченням аргументів:
x1, y1 — координати точки, що лежить на дотичній до кривої у початковій точці кривої;
x2, y2 — координати точки, що лежить на дотичній до кривої у кінцевій точці кривої;
x3, y3 — координати кінцевої точки кривої.
RelCurveTo — аналог CurveTo, у якому аргументами є не координати відповідних точок, а різниці цих координат і відповідних координат початкової точки кривої. Наприклад, якщо до розглянутих вище двох частин коду додати наступну вказівку,
cr.RelCurveTo(-0.25, -0.125, -0.25, 0.125, -0.5, 0);
буде отримано таке зображення.
ClosePath() — замикає контур, додаючи відрізок прямої, що сполучає поточну (контрольну) точку з точкою, з якої було розпочато побудову контура. Наприклад, якщо до розглянутих вище трьох частин коду додати наступну вказівку,
cr.ClosePath();
буде отримано таке зображення.
Rectangle (double x, double y, double w, double h) — додає до контура прямокутник з таким тлумаченням аргументів:
TextPath (string s) — перетворює рядок тексту s на контур з прив'язуванням контуру до поточної (контрольної) точки. Для вищої продуктивності при роботі з довгим текстом доцільно віддати перевагу використанню метода ShowText замість поєднання TextPath і Fill.
SetFontSize (double scale) — задає розмір шрифту у pt.
Stroke() — переміщує віртуальне перо по шляху, дозволяючи джерелу переходити маску лінією з відповідними значеннями таких властивостей:
— див. частину коду прикладу застосування,
cr.LineWidth = 0.1; cr.SetSourceColor(new Color(0, 0, 0)); cr.Rectangle(0.25, 0.25, 0.5, 0.5); cr.Stroke();
який можна проілюструвати таким чином.
Примітка. У коді вказано не екранні координати у пікселях, а відношення значення екранної координати у пікселях до відповідного розміру зображення у пікселях — 0.25, 0.25, 0.5, 0.5 у поданому вище коді. Таке можна робити після застосування перетворення масгабування (див. далі).
cr.SetSourceColor(new Color(0, 0, 0)); cr.SetSourceRGB(0, 0, 0); cr.Rectangle(0.25, 0.25, 0.5, 0.5); cr.Fill();
який можна проілюструвати таким чином.
cr.SetSourceColor(new Color(0, 0, 0)); cr.SelectFontFace("Georgia", FontSlant.Normal, FontWeight.Bold); cr.SetFontSize(1.2); TextExtents te = cr.TextExtents("a"); cr.MoveTo(0.5 - te.Width / 2 - te.XBearing, 0.5 - te.Height / 2 - te.YBearing); cr.ShowText("a");
який можна проілюструвати таким чином.
cr.SetSourceColor(new Color(0, 0, 0)); cr.PaintWithAlpha(0.5);
який можна проілюструвати таким чином.
Gradient linpat = new LinearGradient(0, 0, 1, 1); linpat.AddColorStop(0, new Color(0, 0.3, 0.8)); linpat.AddColorStop(1, new Color(1, 0.8, 0.3)); Gradient radpat = new RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.6); radpat.AddColorStop(0, new Color(0, 0, 0, 1)); radpat.AddColorStop(1, new Color(0, 0, 0, 0)); cr.Source = linpat; cr.Mask(radpat);
який можна проілюструвати таким чином.
У cairo є три основні типи джерел: кольори, градієнти та зображення.
Колір як джерело найпростіший у використанні, бо використовує однакові відтінки та непрозорість для всього джерела. Його вибирають установленням властивості Color — див. приклад частини коду
cr.SetSourceColor(new Color(0, 0, 0)); cr.MoveTo(0, 0); cr.LineTo(1, 1); cr.MoveTo(1, 0); cr.LineTo(0, 1); cr.LineWidth = 0.2; cr.Stroke(); cr.Rectangle(0, 0, 0.5, 0.5); cr.SetSourceColor(new Color(1, 0, 0, 0.80)); cr.Fill(); cr.Rectangle(0, 0.5, 0.5, 0.5); cr.SetSourceColor(new Color(0, 1, 0, 0.60)); cr.Fill(); cr.Rectangle(0.5, 0, 0.5, 0.5); cr.SetSourceColor(new Color(0, 0, 0, 0.40)); cr.Fill();
для створення такого зображення.
Точка зупинки заданого кольору – концепція, відома з програмного забезпечення для редагування зображень. Градієнти будують за допомогою таких точок, які створюють наступними методами:
AddColorStop (double t, Color c)
AddColorStopRgb (double t, Color c)
– значення параметра t з діапазону [0; 1] визначає розташування точки між межами, с – її колір.
Якщо градієнт створено методом LinearGradient (x0, y0, x1, y1)
Можна додати скільки завгодно таких точок зупинки. Але не менше двох – див. приклад частини коду
Gradient radpat = new RadialGradient(0.25, 0.25, 0.1, 0.5, 0.5, 0.5); radpat.AddColorStop(0, new Color(1.0, 0.8, 0.8)); radpat.AddColorStop(1, new Color(0.9, 0.0, 0.0)); for (int i=1; i<10; i++) for (int j=1; j<10; j++) cr.Rectangle(i/10.0 - 0.04, j/10.0 - 0.04, 0.08, 0.08); cr.Source = radpat; cr.Fill(); Gradient linpat = new LinearGradient(0.25, 0.35, 0.75, 0.65); linpat.AddColorStop(0.00, new Color(1, 1, 1, 0)); linpat.AddColorStop(0.25, new Color(0, 1, 0, 0.5)); linpat.AddColorStop(0.50, new Color(1, 1, 1, 0)); linpat.AddColorStop(0.75, new Color(0, 0, 1, 0.5)); linpat.AddColorStop(1.00, new Color(1, 1, 1, 0)); cr.Rectangle(0.0, 0.0, 1, 1); cr.Source = linpat; cr.Fill();
для створення такого зображення.
PushGroup();
PopGroupToSource() — використовують, доки не виберуть нове джерело;
PopGroup() — використовують, коли потрібно зберегти джерело для багаторазового використання у майбутньому.
Сairo завжди має активний шлях. Інколи порожній. Якщо викликати метод Stroke, він намалює контур. Якщо викликата метод Fill, буде заповнено внутрішню частину шляху. Якщо шлях порожній, обидва виклики не призведуть до жодних змін щодо пункту призначення. Після кожного наведення контуру або заповнення шлях буде спорожнено, щоб була можливість будувати наступний шлях "з нуля".
Cairo підтримує повторне використання шляхів, маючи альтернативні версії методів:
Обидва малюють те саме, але альтернативний не спорожнює шлях. Навіть установка кліпу має консервативний варіант.
Перетворення — це засіб встановлення зв’язку між двома системами координат:
які, як усталено, узгоджені.
Перетворення застосовують для такого:
Rotate (double a) — змінює поворот навколо початку координат на кут, радіанна міра якого дорівнює a;
TransformPoint (ref double x, ref double y) — змінює значення координат x, y у просторі користувач на значення координат на поверхні згідно з поточною матрицею перетворення;
TransformDistance (ref double dx, ref double dy) — змінює вектор з координатами dx i dy за допомогою поточної матриці перетворення;
InverseTransformPoint (ref double x, ref double y) — змінює значення координат x, y на поверхні на значення координат у просторі користувача згідно з матррицею, оберненою до поточної матриці перетворення;
InverseTransformDistance (ref double dx, ref double dy) — змінює вектор з координатами dx i dy згідно з матррицею, оберненою до поточної матриці перетворення;
Scale (double sx, double sy) — змінює поточну матрицю перетворення розтягом вздовж осей координат Ox, Oy з коефіцієнтами розтягу sx, sy відповідно;
Translate (double tx, double ty) — змінює поточну матрицю перетворення стисканням перенесенням початку координат простору користувача на вектор з координатами tx, ty;
Transform (Matrix m) — перетворює поточну матрицю перетворення за допомогою матриці афінного перетворення m.
Наприклад, перетворення робочого середовища 1 × 1 на 100 × 100 пікселів посередині поверхні 120 × 120 пікселів можна хдійснити одним із трьох способів:
Останній спосіб використовують, коли необхідно отримати доступ до додаткового керування.
Примітка. Налаштування ширини лінії завжди встановлюють у координатах користувача. Вона не змінюються шляхом встановлення масштабу. При використанні масштабування ширина лінії множиться на цей масштаб. Щоб указати ширину лінії в пікселях, використовують InverseTransformDistance, щоб перетворити (1, 1) відстань у просторі пристрою (поверхні) на, наприклад, відстань у (0.01, 0.01) просторі користувача.
Поданий вище опис не охоплює всі функції в Cairo. Опис функцій, які рідше, потрібно деінде. Наприклад, в офіційній документації анґлійською мовою. Приклади використання cairo іншими мовами можна знайти на
cairographics.org.
Зауваження щодо ширина лінії. При роботі з перетворенням рівномірного масштабування не можна просто задати кількість пікселів для ширини лінії. Це можна здійснити за допомогою InverseTransformDistance (далі подано приклад для ширина 1піксель.
double ux=1, uy=1; cr.InverseTransformDistance(ref ux, ref uy); cr.LineWidth = Math.Max(ux, uy);
Для отримання однакової ширину ліній у просторі пристрою при різних коефіцієнтах стискання вздовж осей при масштабуванні потрібно повернутися до рівномірного масштабу, перш ніж створювати. На наступному малюнку контур ліворуч створено під деформацією, а контур праворуч — при рівномірному масштабі
внаслідок використання такого коду.
cr.LineWidth = 0.1; cr.Save(); cr.Scale(0.5, 1); cr.Arc(0.5, 0.5, 0.40, 0, 2 * Math.PI); cr.Stroke(); cr.Translate(1, 0); cr.Arc(0.5, 0.5, 0.40, 0, 2 * Math.PI); cr.Restore(); cr.Stroke();
Об'єкт PointD — пара двовимірних координат типу double.
Див. приклад коду,
void OnDrawingAreaExposed (object o, ExposeEventArgs args) { DrawingArea area = (DrawingArea) o; Cairo.Context g = Gdk.CairoHelper.Create (area.GdkWindow); PointD p1,p2,p3,p4; p1 = new PointD ( 10, 10); p2 = new PointD (100, 10); p3 = new PointD (100,100); p4 = new PointD ( 10,100); g.MoveTo (p1); g.LineTo (p2); g.LineTo (p3); g.LineTo (p4); g.LineTo (p1); g.ClosePath(); g.SetSourceColor(new Color (0,0,0)); g.FillPreserve(); // зображення контуру без його руйнації g.SetSourceColor(new Color (1,0,0)); g.Stroke(); g.GetTarget().Dispose(); g.Dispose(); }
який малює чорний квадрат з червоним контуром.
Зберігання й відновлення стану
Більшість параметрів малювання залежать від стану. Серед таких властивостей, які можна встановити, маємо такі:
Color – колір обведення/запопнення. Значення кольорів моделі RGB (червоний, зелений, синій, альфа) задають з діапазону [0; 1] (як double);
LineWidth – ширина лінії обведення;
LineCap – контролює обмеження ліній (круглі, квадратні тощо).
Підхід, заснований на понятті стану, вважають зручнішим, ніж визначення всіх параметрів малювання в одному виклику функції. Однак, при створенні власних функцій малювання інколи важко контролювати всі зміни стану, що охоплюють кілька методів. У більшості випадків розробники не хочуть піклуватися про певні модифікатори стану, припускаючи, що їх не встановлено.
Методи контролю стану
Save() – копіює поточний стан і розташовує копію на вершину стека.
Restore() – повертає один стан зі стеку. Очевидно, що всі виклики, що змінюють стан, розміщені в Saveдужках Restore, є локальними.
Хорошою практикою програмування вважають розміщення всіх змін стану в дужках Save / Restore. Таким чином можна легко контролювати стан Cairo і працювати над «чистим» (незабрудненим) станом у функціях вищого рівня.
Див. приклад функції для малювання трикутника.
void DrawTriangle (Cairo.Context g, double x, double y, bool fill) { g.Save (); g.MoveTo (new PointD (x - 10, y + 10)); g.LineTo (new PointD (x, y - 10)); g.LineTo (new PointD (x + 10, y + 10)); g.LineTo (new PointD (x - 10, y + 10)); g.ClosePath (); g.SetSourceColor(new Color (1,0,0)); if (fill) g.Fill (); else g.Stroke (); g.Restore (); }
У цьому прикладі стан, який є під час входу у функцію, залишається таким самим при виході з функції.
Опрацювання пікселів можна здійснити з допомогою класу Bitmap, що не належить до бібліотеки Cairo. Наприклад, у консольному проєкті з під'єднанням певних пакунків.
cтворення представника з наповненням його даними з файлу растрової графіки:
Bitmap bm = new Bitmap("назва файлу");
визначення кольору пікселя з координатами j, k:
System.Drawing.Color c = bm.GetPixel(j, k);
cтворення нового кольору cNew з використанням складових c.R, c.G і c.B моделі RGB кольору с (на прикладі зменшення значення усіх складових удвічі):
System.Drawing.Color cNew = System.Drawing.Color.FromArgb(c.R/2. c.G/2, c.B/2);
встановлення кольору cNew для пікселя з координатами j, k:
bm.SetPixel(j, k, cNew);
запис у файл:
bm.Save("назва файлу");
Приклади використання Cairo проілюстровано кодами:
при поєднанні з GKT# 2.0 для компіляції вказівкою терміналу:
mcs 0.cs -pkg:mono-cairo -pkg:gtk-sharp-2.0
при поєднанні з GKT# 3.0 для компіляції як консольного проєкту з під'єднанням певних пакунків.
Лінійні ґрадієнти у прямокутниках (2 / 3);
лінійний ґрадієнт, обмежений кривими (2 / 3);
запис у файл та зміна методів заокруглення і згладжування (2 / 3 — один і той самий код для обох випадків) — помітно при розгляді зі значним збільшенням;
заокруглення сторін і кутів прямокутника (2 / 3);
перетворення (2 / 3) — зверніть увагу на товщину ліній цього і попереднього прикладів;
випадковий візерунок (2 / 3) — плитки Труше. Нижче подано результати роботи програми при трьох послідовних запусків програми.
рухомий текст (2 / 3) з використанням таймера і зміною прозорості;
обертання відрізка (2 / 3) з використанням таймера і зміною прозорості;
віддзеркалення (2 / 3) — спрацьовує при наявності у поточній теці (у якій розташовано програму) файлу зображення image.png;
малювання користувачем відрізків прямих і прямокутників (3) з використанням подій, породжених діями з мишею і клавіатурою;
опрацювання пікселів (3) — спрацьовує при наявності у поточній теці (у якій розташовано програму) файлу зображення gryvna.png.
.Примітка. Якщо немає потреби малювати на початковому чи попіксельно опрацьованому зображенні, то немає необхідності використовувати Mono.Cairo. Достатньо обмежитися бібліотекою GKT#. А саме: використати елемент керування Image і буфер Gdk.Pixbuf, який у свою чергу можна заповнити зображенням з файлу. Див., наприклад, код консольного проєкту, що створює таке саме наповнення вікна, як показано на малюнку вище. При цьому достатньо під'єднання таких пакунків.
Не намагайтеся зберегти Cairo.Context через кілька подій експозиції, це не спрацює через подвійну буферизацію.
Використовуйте Cairo лише в основному потоці Gdk.
При потребі малювати чіткі горизонтальниі чи вертакальні лінії товщиною 1 px робіть координати напівцілими. Це пов’язано з тим, як працює згладжування. При цілих координатах кінців лінію товщиною 1 px буде намальовано «між» двома пікселями. Інакше кажучи, точки отримують 1/2 значення кольору відбудеться розмиття.
Не забувайте, що значення складових кольору та альфа-каналу виражають значенням з діапазону [0, 1], а не цілим з діапазону 0..255.
Не забувайте вручну утилізувати Context ціль Surface наприкінці події експозиції: автоматичний збір сміття в Cairo ще не працює на 100%.
Текст упорядкував Олександр Рудик з використанням сторінок 1, 2, 3 сайту mono-project.com і сторінки zetcode.com/gui/gtksharp/drawingII/. Переклад подано зі скороченнями й перестановками.