Зона кода

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

Графическая библиотека pgraph: копирование прямоугольных областей

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

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

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

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

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

Предварительные замечания

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

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

Перед тем, как данные функции создавать, мы должны ответить на два вопроса. Первый: будут ли наши функции проверять полученные параметры на корректность?

Действительно, теоретически можно передать функции, занимающейся копированием, информацию о прямоугольной области, частично или полностью выходящей за пределы первого (исходного) или второго (результирующего) изображений. Как с этим быть? Ведь любая дополнительная проверка, хоть и часто бывает весьма полезной, но, при этом, замедляет выполнение функции. Именно по этой причине стандартные библиотечные функции языка C, как правило, никаких проверок не производят, полностью "доверяя" пользователям функций.

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

Впрочем, возвращаемся к нашему вопросу. Ответ на него таков: мы создадим 2 варианта функции, выполняющей копирование (без учёта прозрачности).

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

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

Вторую из описанных функций будем называть "умной".

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

Заметим, что, в рассматриваемой ситуации, если область-донор, не пересекается с областью-реципиентом, то проблем нет никаких: можно вообще не учитывать, что исходное и результирующее изображения совпадают и действовать точно так же, как если бы изображения были бы разными. Проблемы могут возникнуть в случае, если эти области пересекаются. А могут и не возникнуть: всё зависит от способа копирования (об этом мы подробно поговорим в дальнейшем).

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

А второй вариант ("умная функция") будет учитывать возможность совпадения изображений. И в случае, если оно имеет место, копирование будет выполнено корректно.

Теперь поговорим о копировании с учётом прозрачности. Поясним, сначала, что понимается под таким копированием.

Речь идёт о том, что некоторый фиксированный цвет, передающийся функции, занимающейся копированием, объявляется "прозрачным". Цвета пикселей области-донора, совпадающие с прозрачным цветом, "не переносятся" на соответствующие пиксели области-реципиента, т. е. последние сохраняют свой исходный цвет. А "перекрашиванию" подлежат только те пиксели области-реципиента, соответствующие которым пиксели области-донора имеют цвета, отличные от прозрачного.

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

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

Переходим к программированию!

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

Помещаем следующий прототип функции copy_rect() в заголовочный файл pgraph.h.

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

Формальные параметры функции copy_rect() имеют следующий смысл:

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

Функция копирует содержимое области-донора в область-реципиент, после чего возвращает адрес того изображения, в которое осуществлялось копирование (т. е. значение указателя to). Никаких проверок переданных ей параметров на корректность функция copy_rect() не производит.

Ниже приведён код функции copy_rect(), помещаемый в файл pgraph.c:

//Копирование прямоугольной области
image *copy_rect(image *from, uint x1, uint y1, uint width, uint height,
                 image *to, uint x2, uint y2)
{
    for (uint i = y2, j = y1; i < y2 + height; i++, j++)
        memcpy(to->pixels + i * to->width + x2,
               from->pixels + j * from->width + x1, width * sizeof(color));
    return to;
}

Как видно из кода, мы копируем цвета пикселей, принадлежащих области-донору, в область-реципиент "построчно", в направлении "снизу-вверх" (т. е. в направлении возрастания ординат пикселей, расположенных в одной строке).

Таким образом, если исходное и результирующее изображения совпадают, копирование будет выполнено корректно, если прямоугольные области не пересекаются вообще, или пересекаются, но область-донор расположена "выше" области-реципиента.

Тестирование функции copy_rect()

Рассмотрим следующее изображение:

Первое исходное изображение

Первое исходное изображение

На нём изображён прогулочный катер на Неве вблизи Дворцового моста. Кстати, это кадр из видеоролика, демонстрирующего эффект "наплыва" (подробнее см. здесь).

Разумеется, исходная фотография, из которой было получено это изображение путём обрезки и масштабирования, значительно крупнее его. Вот фрагмент исходной фотографии:

Второе исходное изображение

Второе исходное изображение

Итак, в нашем распоряжении имеются два исходных изображения (назовём содержащие их файлы in1.bmp и in2.bmp). Заметим, что на первом изображении название катера неразличимо, зато на втором оно читается без проблем: "МОСКВА-183". Зададимся целью скопировать из второго изображения прямоугольный фрагмент, содержащий название катера, и вставить его в правую верхнюю часть первого изображения. Смысл задумки — "помочь" зрителю, рассматривающему "мелкое" изображение катера, прочитать его название.

Результирующее изображение сохраним в новом файле, который назовём out1.bmp.

Для реализации задуманного напишем следующую программу, код которой поместим в отдельный файл:

 1.#include "pgraph.h"
 2.
 3.int main()
 4.{
 5.    image *img1 = load_from_file("in1.bmp");
 6.    image *img2 = load_from_file("in2.bmp");
 7.    copy_rect(img2, 190, 190, 210, 110, img1, 400, 240);
 8.    save_to_file(img1, "out1.bmp");
 9.    free(img1);
10.    free(img2);
11.    return 0;
12.}

Разумеется, не забываем подключить графическую библиотеку pgraph (стр. 1).

Все действия нашей программы реализованы в функции main(). Сначала динамически создаём объекты тип image, загружая в них изображения из файлов in1.bmp и in2.bmp и сохраняя адреса объектов в указателях img1 и img2 соответственно (стр. 5, 6). Затем выполняем копирование фрагмента второго изображения и вставку его в первое (стр. 7). Далее сохраняем модернизированное первое изображение в новом файле out1.bmp (стр. 8). Наконец, освобождаем память, занимаемую уже ненужными нам объектами, содержащими наши изображения (стр. 9, 10).

Компилируем программу и помещаем в директорию исполняемого файла графические файлы in1.bmp и in2.bmp. Запускаем программу на выполнение. В результате, в той же директории появляется файл out1.bmp, содержащий изображение следующего вида:

Результирующее изображение

Результирующее изображение

Как мы видим, программа сработала успешно.

Об "умном" копировании

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

Затем выясняем, являются ли исходное и результирующее изображения одним и тем же объектом, а если да, — то пересекаются ли область-донор и область-реципиент, причём, таким образом, что первая находится "ниже" второй или "на одном уровне" с ней. Такого рода пересечение установить несложно: его критерием является принадлежность одной из верхних вершин области-донора области-реципиенту.

Если пересечение описываемого вида установлено, то выполняем копирование цветов пикселей, принадлежащих области-донору, в область-реципиент "построчно", в направлении "сверху-вниз" (т. е. в направлении убывания ординат пикселей, расположенных в одной строке).

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

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

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

"Умным" копированием прямоугольной области будет заниматься функция smart_copy_rect(), имеющая следующий прототип, который мы помещаем в файл graph.h:

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

Формальные параметры и возвращаемое значение имеют тот же самый смысл, что и в случае функции copy_rect().

Код самой функции помещаем разумеется, в файл graph.c:

 1.//"Умное" копирование прямоугольной области
 2.image *smart_copy_rect(image *from, uint x1, uint y1, uint width, uint height,
 3.                       image *to, uint x2, uint y2)
 4.{
 5.    int w1 = from->width - x1, h1 = from->height - y1;
 6.    int w2 = to->width - x2, h2 = to->height - y2;
 7.    if (w1 <= 0 || h1 <= 0 || w2 <= 0 || h2 <= 0 || !width || !height)
 8.        return to;
 9.    if (w1 > w2)
10.        w1 = w2;
11.    if (width > w1)
12.        width = w1;
13.    if (h1 > h2)
14.        h1 = h2;
15.    if (height > h1)
16.        height = h1;
17.    if (from == to)
18.    {
19.        if (x1 == x2 && y1 == y2)
20.            return to;
21.        uint x1w = x1 + width - 1;
22.        uint y1h = y1 + height - 1;
23.        uint x2w = x2 + width - 1;
24.        uint y2h = y2 + height - 1;
25.        if (y1h >= y2 && y1h <= y2h && (x1 >= x2 && x1 <= x2w || x1w >= x2 && x1w <= x2w))
26.        {
27.            for (int i = y2 + height - 1, j = y1 + height - 1; i >= y2; i--, j--)
28.                memmove(to->pixels + i * to->width + x2,
29.                        from->pixels + j * from->width + x1, width * sizeof(color));
30.            return to;
31.        }
32.    }
33.    return copy_rect(from, x1, y1, width, height, to, x2, y2);
34.}

Вначале вычисляем максимальные длину и ширину прямоугольника, имеющего левую нижнюю вершину с координатами x1 и y1, полностью принадлежащего исходному изображению, и помещаем их в переменные w1 и h1 соответственно (стр. 5). Затем находим максимальные длину и ширину прямоугольника, имеющего левую нижнюю вершину с координатами x2 и y2, полностью принадлежащего результирующему изображению, и помещаем их в переменные w2 и h2 соответственно (стр. 6).

Если хотя бы одно из четырёх найденных значений отрицательно (что означает, что соответствующая ему левая верхняя вершина прямоугольной области лежит вне "своего" изображения) или хотя бы один из параметров прямоугольной области (т. е. её длина или ширина), переданных функции, равен нулю, заканчиваем работу и возвращаем значение указателя to (стр. 7-8).

Присваиваем переменной width минимальное из значений, содержащихся в переменных w1, w2, width (если минимальное значение уже находится в width, присваивание не производится) (стр. 9-12). А переменной height присваиваем минимальное из значений, содержащихся в переменных h1, h2, height (если минимальное значение уже находится в height, присваивание не производится) (стр. 13-16).

На данном этапе выполнения функции можно утверждать, что значения формальных параметров x1, y1, width, height, x2, y2 корректны в том смысле, что определяемые ими прямоугольные области целиком лежат внутри "своих" изображений.

Обрабатываем ситуацию, при которой исходное и результирующее изображения являются одним объектом (стр. 17-32). Если при этом условии область-донор и область-реципиент совпадают, заканчиваем работу, возвращая значение указателя to (стр. 19-20). В противном случае вычисляем абсциссу и ординату правой верхней вершины области-донора и правой верхней вершины области-реципиента, помещая найденные значения в переменные x1w, y1h, x2w, y2h соответственно (стр. 21-24).

Если хотя бы одна из верхних вершин области-донора принадлежит области-реципиенту (стр. 25), то копируем цвета пикселей первой области во вторую "построчно" в направлении сверху-вниз (стр. 27-29) и возвращаем адрес результирующего изображения из функции (стр. 30).

Обратите внимание на то, что копирование цветов "строки пикселей" из одной области памяти в другую мы выполняем не с помощью функции memcpy(), как это делалось ранее (см. функцию copy_rect()), а с помощью функции memmove() (обе они, напомню, являются стандартными библиотечными).

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

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

Тестирование функции smart_copy_rect()

Обратимся к изображению Кунсткамеры, уже неоднократно использовавшемуся в предыдущих статьях:

Исходное изображение

Исходное изображение

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

int main()
{
    image *img = load_from_file("in3.bmp");
    smart_copy_rect(img, 270, 88, 106, 226, img, 210, 118);
    save_to_file(img, "out2.bmp");
    free(img);
    return 0;
}

Полагаю, что особых комментариев не требуется. Отметим лишь, что in3.bmp — это исходный файл (с изображением Кунсткамеры), который к моменту выполнения программы уже должен находиться в директории исполняемого файла, а out2.bmp — это результирующий файл, генерируемый программой.

В результате компиляции и запуска получаем файл out2.bmp, содержащий следующее изображение:

Результирующее изображение (корректное копирование)

Результирующее изображение (корректное копирование)

Как видим, копирование выполнено успешно.

Давайте теперь, для сравнения, попробуем выполнить то же самое копирование с помощью функции copy_rect(). Заменим в предыдущей версии main() вызовом этой функции вызов smart_copy_rect(), а имя выходного файла поменяем на out3.bmp:

int main()
{
    image *img = load_from_file("in3.bmp");
    copy_rect(img, 270, 88, 106, 226, img, 210, 118);
    save_to_file(img, "out3.bmp");
    free(img);
    return 0;
}

Вот какое изображение будет содержать файл out3.bmp:

Результирующее изображение (некорректное копирование)

Результирующее изображение (некорректное копирование)

На этот раз, как мы видим, результат выполнения программы неудовлетворителен.

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

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

image *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(). Отличие функции copy_rect2() от copy_rect() состоит в том, что первая не выполняет копирование цветов тех пикселей области-донора, которые имеют цвет, совпадающий со значением формального параметра col. Такие пиксели считаются "прозрачными".

А вот и код функции copy_rect2(), который мы заносим в файл pgraph.c.

 1.//Копирование прямоугольной области с учётом прозрачности
 2.image *copy_rect2(image *from, uint x1, uint y1, uint width, uint height,
 3.                  image *to, uint x2, uint y2, color col)
 4.{
 5.    for (uint i = y2, j = y1; i < y2 + height; i++, j++)
 6.        {
 7.            int count = 0;
 8.            for(color *p1 = from->pixels + j * from->width + x1,
 9.                      *p2 = to->pixels + i * to->width + x2;
10.                      count < width;
11.                      p1++, p2++, count++)
12.            if (!col_cmp(*p1, col))
13.                *p2 = *p1;
14.        }
15.    return to;
16.}

Код тела данной функции отличается от кода тела copy_rect(), как мы видим, лишь содержимым внешнего цикла for. Теперь мы не копируем цвета "построчно", как раньше, а обрабатываем каждый пиксель области-донора в отдельности в ходе итераций внутреннего цикла for: проверяем, не совпадает ли его цвет с col (стр. 12), и, лишь в случае несовпадения выполняем копирование цвета (стр. 13).

Напомним, на всякий случай, что функция col_cmp(), вызываемая в строке 12, сравнивает 2 цвета, переданные ей в качестве параметра. Она возвращает true в случае совпадения, или false в противном случае.

Тестирование функции copy_rect2()

Тестируя copy_rect(), мы переносили прямоугольный фрагмент одного изображения на другое. А теперь зададимся целью перенести не прямоугольный фрагмент, а фрагмент, имеющий форму эллипса.

Первое исходное изображение (файл in.bmp) будет тем же, что и раньше:

Первое исходное изображение

Первое исходное изображение

А второе исходное изображение изменим: наложим на него маску, представляющую собой прямоугольник, например, красного цвета, имеющий "вырез" в форме эллипса, "окружающего" тот фрагмент, который требуется перенести:

Второе исходное изображение

Второе исходное изображение

Файл, содержащий данное изображение, назовём "in4.bmp".

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

Вот код новой версии функции main(), решающей поставленную задачу:

int main()
{
    image *img1 = load_from_file("in1.bmp");
    image *img2 = load_from_file("in4.bmp");
    copy_rect2(img2, 190, 200, 210, 80, img1, 420, 270, RED);
    save_to_file(img1, "out4.bmp");
    free(img1);
    free(img2);
    return 0;
}

В результате выполнения программы получаем файл out4.bmp, содержащий следующее результирующее изображение:

Результирующее изображение

Результирующее изображение

Как видим, поставленная задача решена успешно.

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

Функция smart_copy_rect2(), занимающаяся копированием прямоугольной области, считающейся прозрачной, имеет следующий прототип, который мы помещаем в файл pgraph.h:

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, а также возвращаемое функцией значение имеют тот же самый смысл, что и в случае функции smart_copy_rect(). Отличие функции smart_copy_rect2() от smart_copy_rect() состоит в том, что первая не выполняет копирование цветов пикселей области-донора, имеющих цвет, содержащийся в col.

Код функции smart_copy_rect2() заносим в файл pgraph.c. Он приведён ниже.

 1.//"Умное" копирование прямоугольной области с учётом прозрачности
 2.image *smart_copy_rect2(image *from, uint x1, uint y1, uint width, uint height,
 3.                        image *to, uint x2, uint y2, color col)
 4.{
 5.    int w1 = from->width - x1, h1 = from->height - y1;
 6.    int w2 = to->width - x2, h2 = to->height - y2;
 7.    if (w1 <= 0 || h1 <= 0 || w2 <= 0 || h2 <= 0 || !width || !height)
 8.        return to;
 9.    if (w1 > w2)
10.        w1 = w2;
11.    if (width > w1)
12.        width = w1;
13.    if (h1 > h2)
14.        h1 = h2;
15.    if (height > h1)
16.        height = h1;
17.    if (from == to)
18.    {
19.        if (x1 == x2 && y1 == y2)
20.            return to;
21.        uint x1w = x1 + width - 1;
22.        uint y1h = y1 + height - 1;
23.        uint x2w = x2 + width - 1;
24.        uint y2h = y2 + height - 1;
25.        if (y1h >= y2 && y1h <= y2h)
26.            if (x1 >= x2 && x1 <= x2w)            //донор правее реципиента
27.            {
28.                for (int i = y2 + height - 1, j = y1 + height - 1; i >= (int) y2; i--, j--)
29.                {
30.                    int count = 0;
31.                    for(color *p1 = from->pixels + j * from->width + x1,
32.                        *p2 = to->pixels + i * to->width + x2;
33.                        count < width;
34.                        p1++, p2++, count++)
35.                    if (!col_cmp(*p1, col))
36.                        *p2 = *p1;
37.                }
38.                return to;
39.            }    
40.            else
41.                if (x1w >= x2 && x1w <= x2w)      //донор левее реципиента
42.                {
43.                    for (int i = y2 + height - 1, j = y1 + height - 1; i >= (int) y2; i--, j--)
44.                    {
45.                        int count = 0;
46.                        for(color *p1 = from->pixels + j * from->width + x1w,
47.                            *p2 = to->pixels + i * to->width + x2w;
48.                            count < width;
49.                            p1--, p2--, count++)
50.                        if (!col_cmp(*p1, col))
51.                            *p2 = *p1;
52.                    }                    
53.                    return to;
54.                }
55.    }
56.    return copy_rect2(from, x1, y1, width, height, to, x2, y2, col);
57.}

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

О случае, в котором исходное и результирующее изображения представляют собой один и тот же объект, обрабатываемом в строках 17-55, стоит поговорить особо. Если, в рамках этого случая, область-донор и область-реципиент пересекаются, причём первая находится "не выше" второй, то в ходе копирования "строки пикселей" областей перебираются в направлении "сверху вниз", а не наоборот, как это делается в функции smart_copy_rect2(). Кроме того, копирование строк, при этом, для разных вариантов осуществляется в различных направлениях.

Первый вариант: область-донор расположена "правее" области-реципиента. В этом случае пиксели строк обеих областей в ходе копирования перебираются в направлении "слева-направо" (см. стр. 26-39). А если имеет место второй вариант, при котором область-донор расположена "левее" области-реципиента, то направление копирования пикселей обратное — "справа-налево" (см. стр. 41-54).

Для чего же мы обрабатываем каждый из этих вариантов по-разному?

А делаем мы это для того, чтобы корректно обрабатывалась ситуация, при которой обе области расположены на "одном уровне", т. е. их нижние границы равноудалены от оси абсцисс. Если в данной ситуации выбрать неверное направление копирования цветов пикселей, принадлежащих одной строке области-донора, то такое копирование будет выполнено, в общем случае, некорректно: в ходе копирования некоторые пиксели строки области-донора, принадлежащие и области-реципиенту, приобретут "новые" цвета до того, как будут скопированы. Так что копироваться будут уже "новые" цвета, а не "старые".

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

Тестирование функции smart_copy_rect2()

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

Признаться, мне сложно придумать ситуацию, при которой такое копирование требуется произвести, поэтому наш пример будет весьма искусственным.

Добавим к изображению Кунсткамеры белую рамку слева и снизу:

Исходное изображение

Исходное изображение

Файл, содержащий данное изображение, назовём in5.bmp.

Сделаем вот что: "передвинем" прямоугольник, содержащий левую нижнюю часть изображения, включающую "куски" белых полей, вправо-вверх. При этом белый цвет укажем как "прозрачный". Вот код соответствующей функции main():

int main()
{
    image *img = load_from_file("in5.bmp");
    smart_copy_rect2(img, 0, 0, 220, 210, img, 100, 120, WHITE);
    save_to_file(img, "out5.bmp");
    free(img);
    return 0;
}

А вот как выглядит результат работы программы (файл out5.bmp):

Результирующее изображение (корректное копирование)

Результирующее изображение (корректное копирование)

Как видим, копирование прошло успешно.

А что, если бы мы выполняли копирование без учёта прозрачности? Изменим функцию main(), заменив вызов функции smart_copy_rect2() вызовом smart_copy_rect() (выходной файл назовём теперь out6.bmp):

int main()
{
    image *img = load_from_file("in5.bmp");
    smart_copy_rect(img, 0, 0, 220, 210, img, 100, 120);
    save_to_file(img, "out6.bmp");
    free(img);
    return 0;
}

Белые части полей ожидаемо скопировались:

Результирующее изображение (первое некорректное копирование)

Результирующее изображение (первое некорректное копирование)

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

int main()
{
    image *img = load_from_file("in5.bmp");
    copy_rect2(img, 0, 0, 220, 210, img, 100, 120, WHITE);
    save_to_file(img, "out7.bmp");
    free(img);
    return 0;
}

В результате получаем файл out7.bmp, изображение, содержащееся в котором, выглядит следующим образом:

Результирующее изображение (второе некорректное копирование)

Результирующее изображение (второе некорректное копирование)

Наконец, для "полноты картины", задействуем для копирования функцю copy_rect(), не учитывающую вообще ничего: ни прозрачность, ни особенности копирования пересекающихся областей в случае совпадения исходного и результирующего изображений. Будем использовать следующую версию функции main():

int main()
{
    image *img = load_from_file("in5.bmp");
    copy_rect(img, 0, 0, 220, 210, img, 100, 120);
    save_to_file(img, "out8.bmp");
    free(img);
    return 0;
}

Получаем файл out8.bmp, в котором хранится вот такое изображение:

Результирующее изображение (третье некорректное копирование)

Результирующее изображение (третье некорректное копирование)

Создание одного изображения из прямоугольного фрагмента другого: функция create_from()

Последняя функция, которую мы добавим к графической библиотеке pgraph, будет создавать новое изображение, являющееся прямоугольным фрагментом заданного. Эта функция, называющаяся create_from(), будет иметь следующий прототип, помещаемый нами в файл pgraph.h:

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

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

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

А вот и код функции create_from(), находящийся в файле pgraph.c:

 1.//Создание одного изображения из прямоугольного фрагмента другого
 2.image *create_from(const image *source, uint x, uint y, uint width, uint height)
 3.{
 4.    int w = source->width - x, h = source->height - y;
 5.    if (w <= 0 || h <= 0 || !width || !height)
 6.        return 0;
 7.    if (width > w)
 8.        width = w;
 9.    if (height > h)
10.        height = h;
11.    image *img = pmalloc(sizeof(image) + width * height * sizeof (color));
12.    img->width = width;
13.    img->height = height;
14.    img->cur_col = source->cur_col;
15.    img->cur_pnt = source->cur_pnt;
16.    return copy_rect((image *) source, x, y, width, height, img, 0, 0);
17.}

Вычисляем максимальные длину и ширину прямоугольника, имеющего левую нижнюю вершину с координатами x и y, полностью принадлежащего изображению-источнику и помещаем их в переменные w и h соответственно (стр. 4). Если хотя бы один из этих параметров оказался отрицательным, что означает непринадлежность левой нижней вершины изображению-источнику, или если хотя бы один из параметров (т. е. высота или ширина) копируемой области равен нулю, заканчиваем работу, возвращая нулевой адрес (стр. 5-6).

Корректируем, при необходимости, значения width и height (стр. 7-10). Теперь значения x, y, width, height заведомо корректны. Динамически создаём новое изображение, присваивая его адрес переменной img (стр. 11) и инициализируем поля созданного объекта (стр. 12-15). Заметим, что значения полей cur_col и cur_pnt берутся из соответствующих полей объекта, содержащего изображение-источник (стр. 14, 15).

Напомним, что вызываемая в строке 11 функция pmalloc(), определённая в файле string.c, пытается динамически выделить память переданного ей размера, возвращая в случае успеха адрес выделенного блока памяти. А в случае неудачи она прерывает выполнение программы, выводя предварительно сообщение об ошибке.

Остаётся лишь выполнить само копирование области из объекта, адресуемого source, в объект, адресуемый img, и вернуть из функции значение img. Делаем это в 16-й строке, посредством вызова функции copy_rect().

Тестирование функции create_from()

Снова обращаемся к многострадальному изображению Кунсткамеры, хранящемуся в файле in3.bmp:

Исходное изображение

Исходное изображение

Зададимся целью вырезать из этого изображения центральную часть и сохранить её в отдельном файле out9.bmp. Вот такой будет наша функция main(), решающая поставленную задачу:

int main()
{
    image *img1 = load_from_file("in3.bmp");
    image *img2 = create_from(img1, 75, 75, 495, 245);
    save_to_file(img2, "out9.bmp");
    free(img1);
    free(img2);
    return 0;
}

После компиляции и выполнения программы получаем следующее результирующее изображение:

Результирующее изображение

Результирующее изображение

Как видим, программа отработала корректно.

Заключение

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

Все 5 исходных изображений, которые мы обрабатывали в результате выполнения программ, можно скачать здесь. А весь исходный код, включающий 9 вариантов функции main(), можно скачать по ссылке, расположенной ниже.

Скачать исходный код