Малювання з Mono.Cairo

Зміст

Вступ
Mono забезпечує два механізми малювання

API (анґлійською Application Programming Interface) — прикладни́й програ́мний інтерфе́йс, інтерфейс програмування застосунків, інтерфейс прикладного програмування — набір визначень підпрограм, протоколів взаємодії та засобів для створення програмного забезпечення.

Інакше кажучи, це набір чітко визначених методів для взаємодії різних складових, що надає розробнику засоби для швидкої розробки програмного забезпечення.

Cairoбібліотека для створення й опрацювання двовимірних малюнків.

Використання Mono.Cairo вимагає попереднього встановлення MonoDevelop і Cairo.

Модель малювання Cairo описує алгоритм того, як cairo моделює малюнок. Вона містить лише кілька понять, які застосовують багатократно й різними методами. Спочатку буде описано іменники. Після цього — методи, які описують маніпулювання іменниками у ході створення зображення.

Іменники Cairo. Перші три іменники — призначення, джерело, маска зображено трьома шарами наступних малюнків. Четвертий іменник — шлях, який малюють на середньому шарі, коли це доречно. Кінцевий іменник, контекст, не показано.

Призначенняце поверхня, на якій малюють. Її можна прив’язати до масиву пікселів або до файлу SVG чи PDF або до чогось іншого.

Джерелоце «фарба», з якою працюють — відображено чорним кольором на наступному малюнку. Лапки вказують на можливу прозорість (наявність даних альфа-каналу), використання візерунку або навіть раніше створеної поверхні як джерела.

Масказасіб контролю, де буде застосовано джерело до місця призначення. Її зручно уявити як (жовтий) шар з отворами, які пропускають джерело. При застосуванні дієслова малювання скрізь, де дозволяє маска, джерело буле скопійовано. Скрізь, де забороняє маска, не відбудеться жодних змін.

Шляхзасіб подання лінії на шарі маски. Ним маніпулюють вказівками шляху, а потім використовують вказівки малювання.

Контекстзасіб відстеження всього, на що впливають вказівки . Він відстежує одне джерело, одне призначення та одну маску. Він також відстежує кілька допоміжних змінних, як-от ширину та стиль лінії, накреслення та розмір шрифту тощо. Найголовніше, що він відстежує контур, який перетворюється на маску за допомогою вказівок малювання.

Перш ніж почати малювати щось, потрібно створити контекст і прив’язати його до певної поверхні, що може бути вікном на екрані, закадровим буфером або файлом на диску. Наприклад, до ImageSurface при створенні файлу PNG таким чином.

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(); // прибирання сміття "вручну"
  }
}

Методи створення контуру

Методи роботи з текстом Методи виведення

Щоб створити зображення, потрібно постійно готувати контекст для кожного методу малювання:

У 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();

для створення такого зображення.

Примітка При заміні першої вказівки на таку:

cr.Color = new Color(0, 0, 0);

результат виконання програми буде тим самим, але при компіляції буде отримано повідомлення про використання застарілого способу зміни кольору.

Градієнт описує послідовність кольорів, встановлюючи початкове та кінцеве опорне розташування та серію «зупинок» на цьому шляху.

Клас Gradient має два підкласи з такими конструкторами:

Точка зупинки заданого кольору – концепція, відома з програмного забезпечення для редагування зображень. Градієнти будують за допомогою таких точок, які створюють наступними методами:

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();

для створення такого зображення.

Надалі будемо дотримуватися такого тлумачення.

Зображенняповерхня, створена у межах Сairo або завантажена з наявного файлу через ImageSurface (string filename).

Створення й використання попереднього пункту призначення

Починаючи з версії Сairo 1.2, найпростіше це здійснити з використанням методів:

Сairo завжди має активний шлях. Інколи порожній. Якщо викликати метод Stroke, він намалює контур. Якщо викликата метод Fill, буде заповнено внутрішню частину шляху. Якщо шлях порожній, обидва виклики не призведуть до жодних змін щодо пункту призначення. Після кожного наведення контуру або заповнення шлях буде спорожнено, щоб була можливість будувати наступний шлях "з нуля".

Cairo підтримує повторне використання шляхів, маючи альтернативні версії методів:

Обидва малюють те саме, але альтернативний не спорожнює шлях. Навіть установка кліпу має консервативний варіант.

Перетворенняце засіб встановлення зв’язку між двома системами координат:

які, як усталено, узгоджені.

Перетворення застосовують для такого:

Останнім способом перетворюють дугу кола на частину еліпса.

Наявні (вбудовані) методи

Наприклад, перетворення робочого середовища 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();
}

який малює чорний квадрат з червоним контуром.

Зберігання й відновлення стану

Більшість параметрів малювання залежать від стану. Серед таких властивостей, які можна встановити, маємо такі:

Підхід, заснований на понятті стану, вважають зручнішим, ніж визначення всіх параметрів малювання в одному виклику функції. Однак, при створенні власних функцій малювання інколи важко контролювати всі зміни стану, що охоплюють кілька методів. У більшості випадків розробники не хочуть піклуватися про певні модифікатори стану, припускаючи, що їх не встановлено.

Методи контролю стану

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

На початку програми потрібно замовити бібліотеку:

using System.Drawing;

Після цього можна використати такі вказівки:

Приклади використання Cairo проілюстровано кодами:

Посилання мають вигляд відповідно 2 і 3.

Примітка. Якщо немає потреби малювати на початковому чи попіксельно опрацьованому зображенні, то немає необхідності використовувати Mono.Cairo. Достатньо обмежитися бібліотекою GKT#. А саме: використати елемент керування Image і буфер Gdk.Pixbuf, який у свою чергу можна заповнити зображенням з файлу. Див., наприклад, код консольного проєкту, що створює таке саме наповнення вікна, як показано на малюнку вище. При цьому достатньо під'єднання таких пакунків.

Корисні поради


Текст упорядкував Олександр Рудик з використанням сторінок 1, 2, 3 сайту mono-project.com і сторінки zetcode.com/gui/gtksharp/drawingII/. Переклад подано зі скороченнями й пере­станов­ками.