Математическая графика в dlib

Графическая библиотека dlib – это замечательный и интересный инструмент как для работы, так и для проведения разного рода математических экспериментов. Однако, несмотря на свое богатство, dlib достаточно скромная и минималистическая библиотека, и очень часто встроенных примитивов не хватает под некоторые задачи отрисовки, а иногда бывает и так, что хочется иметь простой и удобный интерфейс для уже привычных, ставших рутинными, действий.

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

Итак, процедура рисования графика некоторой функции:

import std.algorithm;
import std.range;
import std.traits; 

auto proceduralDraw(alias func, R)(R r, ref SuperImage simg, Color4f pointColor = Color4f(0.0f, 0.0f, 0.0f))
    if ((isInputRange!(Unqual!R)))
{
    auto xs = map!(a => cast(int) a)(r);
    auto ys = map!(a => cast(int) (func(a)))(r);
    each!(a => simg[a[0], a[1]] = pointColor)(zip(xs, ys));
} 

Лаконично и изящно !

Шаблон proceduralDraw принимает некоторую функцию (которую проще всего задать через анонимную функцию) и диапазон ввода, который является диапазоном иксов, по которым рассчитывается значение функции. Помимо аргументов времени компиляции, шаблон принимает и аргументы времени исполнения, обязательными из которых являются сам диапазон ввода и массив, в котором хранится изображение, на котором мы рисуем. Ограничение сигнатуры isInputRange!(Unqual!R) проверяет является ли поступивший на вход шаблона диапазон диапазоном ввода и гарантирует корректную работу с поступившими в обработку данными.

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

import std.algorithm;
import std.math;
import std.range;
import std.traits;
import dlib.image;

void main()
{
    SuperImage simg = image(500, 500);
    for (int i = 0; i < simg.width; i++)
    {
        for (int j = 0; j < simg.height; j++)
        {             
            simg[i, j] = Color4f(1.0f, 1.0f, 1.0f);
        }   
    }     

    auto ix = iota(0.0, 500.0, 0.001);     
    auto f = function(float x) {return x * PI / 180.0;};     
    proceduralDraw!(a => 250 + 20 * sin(a / 20.0))(ix, simg, Color4f(0.0f, 0.0f, 0.9f));
    proceduralDraw!(a => 250 + 200 * cos(a / 20.0))(ix, simg, Color4f(0.9f, 0.0f, 0.0f));
    simg.savePNG("sample.png");
}

auto proceduralDraw(alias func, R)(R r, ref SuperImage simg, Color4f pointColor = Color4f(0.0f, 0.0f, 0.0f))
    if ((isInputRange!(Unqual!R)))
{
    auto xs = map!(a => cast(int) a)(r);
    auto ys = map!(a => cast(int) (func(a)))(r);
    each!(a => simg[a[0], a[1]] = pointColor)(zip(xs, ys));
} 

Для генерации множества x был использован алгоритм (в терминологии D, алгоритм – это некоторая функция, которая принимает диапазон и возвращает некоторое значение или же новый диапазон) iota, который позволяет получить диапазон чисел с заданными границами и заданным шагом. Чтобы графики синуса и косинуса были более качественными, вместо исходных функций sin и cos в анонимной функции использованы их измененные аналоги, учитывающие масштаб по обеим осям и смещающие начало координат: координаты нового центра – (250.0, 250.0), одна единица длины составляет 200 пикселей изображения и коэффициент растяжения по оси OX равен 20.

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

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

Особая благодарность Тимуру Гафарову и журналу FPS за возможность публикации статей :) Кроме того, хочу сказать отдельное спасибо Тимуру за его прекрасные статьи в журнале FPS, именно благодаря им я понял всю мощь шаблонов в D и наконец-то стал их применять сам.

Добавить комментарий