Зона кода

А давайте немного попрограммируем...

Всё о графической библиотеке pgraph

C99Библиотека pgraph

В нескольких статьях этого сайта (первая, вторая, третья, четвёртая) был описан процесс построения на языке C99 примитивной графической библиотеки, которую я назвал pgraph. Библиотека позволяет создавать на холсте изображения, используя достаточно простой набор инструментов, с помощью которых можно рисовать точки, линии, осуществлять заливку одноцветных областей. Имеются возможности загружать 24-битные изображения из графических BMP-файлов и записывать изображения в файлы данного формата.

Данная статья носит справочный характер и призвана описать все возможности библиотеки, но не вдаваться, при этом, в подробности их реализации. Однако, для каждой рассматриваемой в статье функции будет приведена ссылка на статью, в которой содержится подробно прокомментированный программный код данной функции. Читатель после прочтения статьи получит достаточно полное представление о библиотеке и сможет полноценно её использовать.

Если после написания этой статьи библиотека pgraph будет расширена, то описания новых функций будут обязательно добавлены в статью. В конце статьи будут приведены ссылки на статьи, в которых описываются программы, использующие библиотеку, а также ссылка на скачивание исходного кода библиотеки.

Структура библиотеки pgraph

Библиотека состоит из трёх файлов: pgraph.c, pgraph.h, custom_stack.h. В файле pgraph.h определены ключевые типы библиотеки — point, color, image. Все они, а также другие типы данных и макросы, определённые в pgraph.h, будут описаны далее.

Кроме того, в pgraph.h содержатся прототипы функций, предназначенных для вызова их пользователями библиотеки (назовём эти функции пользовательскими). Все эти функции будут рассмотрены в данной статье.

Все функции библиотеки, включая как пользовательские, так и функции, не предназначенные для непосредственного их вызова пользователями (назовём их внутренними; в данной статье они не будут рассматриваться) определены в файле pgraph.c, к которому файл pgraph.h подключается с помощью директивы #include.

Таким же способом к pgraph.c подключается и файл custom_stack.h, содержащий типы данных и прототипы функций, необходимых для использования библиотекой pgraph кастомизированного стека, которому посвящена отдельная статья. Пользователь библиотеки не имеет дела с этими типами и функциями, поэтому рассказывать о них в этой статье мы не будем.

Для использования библиотеки pgraph необходимо подключить к файлу, из функций которого предполагается вызов библиотечных функций, файл pgraph.h посредством директивы #include. К файлу pgraph.h в свою очередь, подключены стандартные библиотеки stdlib.h, stdio.h, math.h, stdbool.h, так что эти библиотеки можно использовать, не подключая их, при условии подключения pgraph.h.

А теперь рассмотрим типы данных и макросы, определённые в pgraph.h.

Типы данных uint, uchar, ushort

Типы данных uint, uchar, ushort определены в файле pgraph.h следующим образом:

typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned short ushort;

Эти типы данных вводятся лишь для удобства; их использование позволяет обращаться к беззнаковым целым типам, избегая излишней громоздкости.

Тип данных color

Библиотека pgraph оперирует цветами, заданными в RGB-формате. Информация о цвете, записанная в данном формате, представляет собой совокупность интенсивностей красной, зелёной и синей компонент цвета. Интенсивность каждой компоненты задаётся числом от 0 до 255 (чем больше число, тем меньше интенсивность). Таким образом, для хранения каждой интенсивности требуется 8-разрядная переменная, например, переменная типа uchar. В итоге, для хранения полной информации о цвете требуется 24 разряда или 3 байта.

Для хранения цветов пикселей в библиотеке pgraph используются переменные типа color. Структура color описана в файле pgraph.h следующим образом:

typedef struct
{
    uchar red;
    uchar green;
    uchar blue;
} color;

Назначение полей переменных типа color, полагаю, понятно из их названия: каждое поле предназначено для хранения интенсивности соответствующей компоненты цвета.

Предопределённые цвета

В файле pgraph.h определены 16 макросов, имя каждого из которых является названием одного из 16-ти предопределённых цветов. Значение каждого такого макроса — это составной литерал типа color, задающий цвет, соответствующий данному макросу. Определения всех этих макросов приведены ниже.

#define AQUA (color) {0, 255, 255}
#define BLACK (color) {0, 0, 0}
#define BLUE (color) {0, 0, 255}
#define FUCHSIA (color) {255, 0, 255}
#define GRAY (color) {128, 128, 128}
#define GREEN (color) {0, 128, 0}
#define LIME (color) {0, 255, 0}
#define MAROON (color) {128, 0, 0}
#define NAVY (color) {0, 0, 128}
#define OLIVE (color) {128, 128, 0}
#define PURPLE (color) {128, 0, 128}
#define RED (color) {255, 0, 0}
#define SILVER (color) {192, 192, 192}
#define TEAL (color) {0, 128, 128}
#define WHITE (color) {255, 255, 255}
#define YELLOW (color) {255, 255, 0}

Благодаря данным макроопределениям, при использовании библиотеки pgraph к 16-ти предопределённым цветам можно обращаться по их названиям, а не по RGB-кодам.

Холст для рисования

Все графические объекты (точки, линии и т. д.) создаются (рисуются) на так называемом холсте для рисования, который можно рассматривать как прямоугольную таблицу с квадратными ячейками, в роли которых выступают пиксели. Количество столбцов таблицы называется шириной холста, а количество строк — его высотой. Обозначим ширину буквой w, а высоту — буквой h.

Введём на холсте для рисования систему координат следующим образом. Пронумеруем строки таблицы числами от 0 до h − 1 снизу вверх, а столбцы таблицы — числами от 0 до w − 1 слева направо. Координатами пикселя назовём упорядоченную пару чисел (xy), первое из которых (назовём его абсциссой пикселя) представляет собой номер столбца, в котором расположен пиксель, а второе (ордината пикселя) — номер строки.

Тип данных point

Итак, помимо цвета, каждому пикселю холста для рисования соответствует упорядоченная пара координат, с помощью которой можно к этому пикселю обратиться. Для того, чтобы с этой парой координат можно было обращаться как с единым объектом, создана структура point, описанная в файле pgraph.h следующим образом:

typedef struct
{
    uint x;
    uint y;
} point;

Объект типа point содержит в своём поле x абсциссу пикселя, а в поле y — его ординату в системе координат, введённой на холсте для рисования.

Объекты типа point будем называть точками. Отметим, пиксель можно рассматривать как совокупность точки и соответствующего ей цвета, а точку — как "бесцветный" пиксель.

Тип данных image

Информация, полностью описывающая холст для рисования (будем называть его также изображением), содержится в объектах типа image. image — это структура, описанная в файле pgraph.h следующим образом:

typedef struct
{
    uint width;
    uint height;
    color cur_col;
    point cur_pnt;
    color pixels[];
} image;

Ниже описаны поля объектов типа image (эти объекты также в дальнейшем будем называть "изображениями"):

  • width — ширина изображения;
  • height — высота изображения;
  • cur_col — текущий цвет изображения;
  • cur_pnt — текущая точка изображения;
  • pixels — массив, содержащий цвета всех пикселей изображения.

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

В массиве pixels цвета пикселей отсортированы в порядке возрастания ординат пикселей. Цвета пикселей, имеющих одинаковые ординаты, отсортированы в порядке возрастания абсцисс. Таким образом, цвет пикселя с координатами (xy) содержится в элементе массива с индексом y w + x.

Отметим, что предполагается динамическое создание объектов типа image и управление ими через указатели.

Перейдём к рассмотрению пользовательских функций, определённых в библиотеке pgraph.

Создание изображений: функции create_image() и create_from()

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

image *create_image(uint width, uint height);

Здесь width и height — это, соответственно, ширина и высота создаваемого изображения. Функция динамически создаёт изображение с заданными размерами и устанавливает цвет каждого пикселя белым. В качестве текущего цвета устанавливается чёрный, а в качестве текущей точки — точка с координатами (0, 0). Функция возвращает адрес динамически созданного изображения.

Подробное описание функции create_image() содержится здесь.

Также можно создать изображение из прямоугольной области другого изображения (изображения-источника). Для этого нужно использовать функцию create_from(), прототип которой приведён ниже.

image *create_from(const image *source, uint x, uint y, uint width, uint height);

Если параметры, переданные функции, корректны, то она пытается динамически создать изображение, представляющее собой прямоугольную область изображения-источника, адресуемого указателем source. Координаты левой нижней вершины этой области хранятся в формальных параметрах x и y, а ширина и высота — в width и height соответственно. В случае успеха возвращается адрес созданного изображения, а в противном случае — нулевой адрес (значение source должно быть корректно в любом случае).

Ширина и высота прямоугольной области считаются корректными даже в ситуации, при которой область выходит за пределы изображения-источника. В этом случае перед созданием нового изображения значения width и (или) height корректируются (т. е. уменьшаются) таким образом, чтобы область полностью входила в изображение-источник, но корректируемые параметры имели, при этом, максимально возможные значения.

Отметим, что левая нижняя вершина области должна принадлежать изображению-источнику, а её ширина и высота должны быть ненулевыми. Нарушение этих требований приводит к возврату из функции нулевого адреса.

Новое изображение будет иметь ширину и высоту, совпадающие со значениями width и height (скорректированными, при необходимости). Значения полей cur_col и cur_pnt нового изображения берутся из одноимённых полей изображения-источника.

Подробное описание функции create_from() содержится здесь.

Удаление изображений

В библиотеке отсутствует функция, освобождающая память, динамически выделенную для объекта типа image, поскольку данное действие может быть осуществлено стандартной библиотечной функцией free(), которой передаётся адрес уничтожаемого объекта.

Получение и изменение цветов пикселей изображения: функции get_color() и set_color()

Получить цвет пикселя изображения можно с помощью функции get_color(), имеющей следующий прототип:

color get_color(image *img, uint x, uint y);

Функция возвращает цвет пикселя изображения, адресуемого указателем img, с координатами, содержащимися в переменных x и y, при условии, что координаты заданы корректно. В противном случае функция возвращает белый цвет.

Изменить цвет произвольного пикселя изображения можно с помощью функции set_color(), прототип которой приведён ниже.

image *set_color(image *img, uint x, uint y, color col);

Функция "окрашивает" пиксель изображения, адресуемого указателем img, с координатами, содержащимися в переменных x и y, в цвет, содержащийся в переменной col, при условии, что координаты заданы корректно. В противном случае никаких действий не производится. Функция возвращает адрес обработанного ею изображения, т. е. значение формального параметра img.

Подробное описание обеих функций содержится здесь.

Преобразования форматов хранения цвета: функции from_color() и to_color()

Как уже было сказано, библиотека pgraph работает с цветами, заданными в формате RGB, причём для хранения цветов используются объекты типа color. Каждое из трёх полей такого объекта содержит интенсивность одной компоненты цвета — красной, зелёной или синей.

Но, в то же самое время, цвет, заданный в формате RGB, можно хранить в четырёхбайтовой переменной следующим образом. В 1-й байт записываем ноль, а во 2-й, 3-й и 4-й байты — значения интенсивностей красной, зелёной и синей компонент соответственно (здесь мы пронумеровали байты в порядке от старшего к младшему). Такой способ хранения цвета называется COLORREF.

В библиотеке имеются функции, позволяющие преобразовывать цвета из формата color в COLORREF и обратно. Уточним, ещё раз, что речь идёт о форматах хранения цветов, а формат, в котором цвета задаются в обоих случаях, один и тот же — RGB.

В библиотеке pgraph отсутствует тип, предназначенный для хранения в его переменных цветов в формате COLORREF, так как на эту роль прекрасно подходит тип uint.

Начнём с преобразования из color в COLORREF, осуществляемого функцией from_color(), имеющей следующий прототип:

uint from_color(color col);

Функция возвращает цвет, содержащийся в переменной col, в виде значения формата COLORREF.

Теперь об обратном преобразовании. За него отвечает функция to_color(). Её прототип приведён ниже.

color to_color(uint colorref);

Функция возвращает цвет, содержащийся в переменной colorref, в виде значения типа color.

Подробное описание обеих функций содержится здесь.

Установка текущих цвета и точки: функции set_cur_col() и set_cur_pnt()

Установить текущий цвет изображения можно с помощью функции set_cur_col(), прототип которой таков:

image *set_cur_col(image *img, color col);

Функция в качестве текущего цвета изображения, адресуемого указателем img, устанавливает цвет, содержащийся в переменной col. Функция возвращает адрес обработанного ею изображения, т. е. значение формального параметра img.

Отметим, что в библиотеке pgraph отсутствует функция, возвращающая текущий цвет изображения. Чтобы его получить, следует непосредственно обратиться к полю cur_col изображения.

Установить текущую точку точку изображения можно с помощью функции set_cur_pnt(), имеющей следующий прототип:

image *set_cur_pnt(image *img, uint x, uint y);

Функция в качестве текущей точки изображения, адресуемого указателем img, устанавливает точку с координатами, содержащимися в переменных x и y. Функция возвращает адрес обработанного ею изображения, т. е. значение формального параметра img.

Следует иметь в виду, что в библиотеке pgraph отсутствует функция, возвращающая текущую точку изображения. Чтобы её получить, следует непосредственно обратиться к полю cur_pnt изображения.

Подробное описание обеих функций содержится здесь.

Построение отрезков: функции line() и line_to()

В библиотеке pgraph имеются функции, позволяющие строить на холсте отрезки минимальной толщины. Здесь мы говорим, разумеется, не об отрезках в математическом смысле, имеющих нулевые толщины, а об отрезках в "графическом" смысле.

Сначала рассмотрим функцию line(), имеющую следующий прототип:

image *line(image *img, uint x1, uint y1, uint x2, uint y2);

Функция рисует на холсте, адресуемом указателем img, отрезок, первая вершина которого имеет координаты, содержащиеся в переменных x1 и y1, а вторая — в x2 и y2 в случае, если координаты вершин заданы корректно. В противном случае никаких действий не производится. В качестве цвета отрезка берётся текущий цвет изображения. Функция возвращает адрес обработанного ею изображения, т. е. значение формального параметра img.

Теперь перейдём к функции line_to(), прототип которой приведён ниже:

image *line_to(image *img, uint x, uint y);

Отличие данной функции от предыдущей заключается лишь в том, что в качестве первой вершины отрезка берётся текущая точка изображения, а в качестве второй — точка с координатами, содержащимися в переменных x и y.

Подробное описание обеих функций содержится здесь.

Заливка областей: функции fill() и fill_all().

Функция fill() "перекрашивает" одноцветную область изображения в новый цвет. Мы не будем останавливаться на понятии "одноцветной" области, предлагая читателю за подробностями обращаться к соответствующей статье. Прототип функции fill() приведён ниже.

image *fill(image *img, uint x, uint y, color col);

Функция "закрашивает" все пиксели одноцветной области изображения, адресуемого указателем img, которой принадлежит пиксель с координатами, содержащимися в переменных x и y, цветом, содержащимся в переменной col, в случае, если координаты пикселя одноцветной области заданы корректно. В противном случае никаких действий не производится. Функция возвращает адрес обработанного ею изображения, т. е. значение формального параметра img.

А теперь приведём прототип функции fill_all().

image *fill_all(image *img, color col);

Функция "окрашивает" все пиксели изображения, адресуемого указателем img, в цвет, содержащийся в переменной col. Функция возвращает адрес обработанного ею изображения, т. е. значение формального параметра img.

Подробное описание обеих функций содержится здесь.

Запись и чтение графических файлов: функции save_to_file() и load_from_file()

В библиотеке pgraph имеются функции для работы с графическими 24-разрядными файлами формата BMP, позволяющие записывать изображения в графические файлы и загружать их из них. Начнём с записи изображения в файл, за которую отвечает функция save_to_file(), имеющая следующий прототип:

bool save_to_file(image *img, const char *filename);

Функция пытается записать изображение, адресуемое указателем img, в файл, путь к которому содержится в C-строке, адресуемой указателем filename. В случае успеха функция возвращает true, а в случае неудачи — false.

Подробное описание функции save_to_file() содержится здесь.

Переходим к чтению файлов, реализованному в функции load_from_file(), прототип которой приведён ниже.

image *load_from_file(const char *filename);

Функция пытается прочитать изображения из файла, путь к которому содержится в C-строке, адресуемой указателем filename. В случае успеха динамически создаётся объект типа image, в него загружается изображение из файла, а адрес этого объекта возвращается функцией. В случае неудачи функция возвращает нулевой адрес.

Нужно иметь в виду, что функция load_from_file() загружает изображения только из таких файлов, которые (гипотетически) могут быть созданы функцией save_to_file(). Так что 24-х разрядные графические файлы формата BMP, созданные другими способами, функцией load_from_file() могут не обрабатываться.

Подробное описание функции load_from_file() содержится здесь.

Копирование прямоугольных областей: функции copy_rect() и smart_copy_rect()

Копированием прямоугольных областей занимаются функции copy_rect() и smart_copy_rect(), прототипы которых приведены ниже.

image *copy_rect(image *from, uint x1, uint y1, uint width, uint height,
                 image *to, uint x2, uint y2);
image *smart_copy_rect(image *from, uint x1, uint y1, uint width, uint height,
                       image *to, uint x2, uint y2);

Функции выполняют копирование прямоугольной области из изображения-источника, адресуемого указателем from, и её вставку в изображение-приёмник, адресуемое указателем to (или, по крайней мере, пытаются выполнить данные операции). Координаты левой нижней вершины копируемой области содержатся в формальных параметрах x1 и y1, а её ширина и высота — в параметрах width и height соответственно. Скопированная область вставляется в изображение-приёмник таким образом, что её левая нижняя вершина приобретает координаты x2 и y2.

Функции возвращают адрес изображения-приёмника, т. е. значение формального параметра to.

Различия между функциями заключаются в следующем.

Во-первых, предполагается, что все данные, переданные функции copy_rect(), являются корректными. Никакие проверки полученных данных этой функцией не осуществляются.

А функция smart_copy_rect() выполняет проверку значений формальных параметров x1, y1, width, height, x2, y2 на корректность. Если ширина и (или) высота таковы, что область выходит за пределы одного изображения или обоих, то значения width и (или) height корректируются (т. е. уменьшаются) таким образом, чтобы этого не было, но, при этом, корректируемые формальные параметры имели максимально возможные значения. И только после этого выполняется копирование.

Если же область вообще не пресекается хотя бы с одним из изображений или имеет нулевую ширину и (или) высоту, то копирование функцией smart_copy_rect() не выполняется.

Во-вторых, функция copy_rect() может работать некорректно, если изображение-источник и изображение-приёмник совпадают, и, при этом, область, из которой выполняется копирование, и область в которую происходит вставка, пересекаются. А функция smart_copy_rect() всегда обрабатывает описанную ситуацию корректно.

Подробное описание обеих функций содержится здесь.

Копирование прямоугольных областей с учётом прозрачности: функции copy_rect2() и smart_copy_rect2()

Копированием прямоугольных областей с учётом прозрачности занимаются функции copy_rect2() и smart_copy_rect2(), прототипы которых приведены ниже.

image *copy_rect2(image *from, uint x1, uint y1, uint width, uint height,
                  image *to, uint x2, uint y2, color col);
image *smart_copy_rect2(image *from, uint x1, uint y1, uint width, uint height,
                        image *to, uint x2, uint y2, color col);

Формальные параметры функций from, x1, y1, width, height, to, x2, y2, а также возвращаемые функциями значения имеют тот же смысл, что и в случае функций copy_rect() и smart_copy_rect(), описанных в предыдущем разделе.

Функции copy_rect2() и smart_copy_rect2() отличаются от функций copy_rect() и smart_copy_rect() соответственно только тем, что первые две функции выполняют "частичное" копирование областей: копируются только те их части, которые имеют цвета, отличные от так называемого "прозрачного" цвета, заданного значением формального параметра col.

Подробное описание обеих функций содержится здесь.

Программы, использующие библиотеку pgraph

Ниже приведён список статей сайта, содержащих описания графических программ, в которых задействована библиотека pgraph.

Заключение

Итак, мы рассмотрели типы, определённые в библиотеке pgraph, а также входящие в неё пользовательские функции. Представленной информации вполне достаточно, чтобы в полной мере использовать все возможности библиотеки.

Скачать исходный код библиотеки pgraph