Лист папоротника в D

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

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

DGui — это маленькая графическая библиотека (но тем не менее, в ней есть все необходимое; а если чего-то нет, то можно писать свои графические компоненты на основе существующих) для построения GUI под Windows, что немного огорчает пользователей Linux (ммм… приложения, собранные с применением этой библиотеки неплохо стартуют в такой штуке, как Wine). DGui компактна, в силу того, что не использует других библиотек в качестве зависимостей, и проста в установке, так как ее можно установить используя dub.

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

Чтобы отобразить листочек папоротника, потребуется сделать свою реализацию IFS (Iterated Function System — Итерированная Система Функций), применение преобразований из которой даст множество точек, слагающих лист уже упомянутого растения: преобразования весьма просты — это ряд несложных уравнений, которые переводят предыдущую точку в последующую, и представляющих собой простейшие афинные преобразования плоскости.

Вот эти уравнения:

[1]
 X' = 0.84 * X + 0.04 * Y;
 Y' = 0.04 * X + 0.85 * Y + 1.6;

[2]
 X' = 0.20 * X - 0.26 * Y;
 Y' = 0.26 * X + 0.212 * Y + 0.44;

[3]    
 X' = -0.15 * X + 0.28 * Y;
 Y' = 0.26 * X + 0.24 * Y + 0.44;

[4]
 X' = 0.0;
 Y' = 0.16 * Y;

X, Y - старые значения координат
X', Y' - значения координат после преобразований

Уравнений несколько, но вот применяются они отнюдь не последовательно, а в зависимости от того, в какой интервал попадает некоторая распределенная в диапазоне [0,1] случайным образом величина. Роль этой величины сыграет униформное случайное распределение, которое имеется в std.random стандартной библиотеки Phobos, и которое будет обновляться в течении некоторого, достаточно большого количества итераций.

Естественно, алгоритм не был придуман нами и является достаточно широко известным: стоит набрать «фрактальный лист папоротника» — и вы получите кучу ссылок на алгоритм, который генерирует подобное множество точек, в связи с этим, не буду слишком подробно описывать сам алгоритм, а покажу основные особенности сборки проектов на основе DGui.

Для начала необходимо установить DGui, для чего лучше всего воспользоваться dub (а тем у кого нет этого замечательного инструмента, вначале необходимо установить его), для чего открываем командную строку Windows (она же cmd.exe) и отдаем следующую команду:

dub fetch dgui

(надеюсь не надо напоминать, что подключение к Интернету, в этот момент, должно быть активно ?)
после чего в командной строке, в случае успеха команды, будет выведено сообщение о том, что неплохо бы было запустить результат на выполнение (и/или сборку) — в этом случае, действия просты — вводим команду:

dub run dgui

Все! Библиотека собрана и готова к работе.

Теперь создаем свой проект (используя командную строку), отдавая в нужной папке команду:

dub init <имя_проекта>

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

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

Что нас интересует в этом файле ?

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

"dependencies": {
    "dgui": ">=1.0.1"
},

что добавит в качестве зависимости к вашему проекту библиотеку DGui, а также убережет от множества проблем, связанных с ручной сборкой проекта.

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

"lflags" : ["-L/SUBSYSTEM:WINDOWS"]

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

После всех этих нехитрых манипуляций, ваш файл dub.json должен выглядеть примерно так:

{
   "name": "ifs_3",
   "description": "A Simple IFS Demo.",
   "copyright": "Copyright © 2014, Daniel Rihman",
   "authors": ["Daniel Rihman"],
   "dependencies": {
       "dgui": ">=1.0.1"
    },
   "lflags" : ["-L/SUBSYSTEM:WINDOWS"]
}

Следующий файл, который нас интересует, это файл app.d, который находится в папке source и который представляет собой основной файл проекта, содержащий весь связующий различные компоненты проекта код.

Открываем файл app.d и вносим туда следующий код:

import dgui.all, std.random;

// отрисовка отдельных точек
void drawPoint(Canvas c, int x, int y) {
  Pen p = new Pen(SystemColors.darkGreen, 1, PenStyle.solid);
  c.drawLine(p, x, y, x + 1, y + 1);
};

void drawIFS(Canvas c)
{
    float X = 1.0, Y = 1.0;
    for (uint i = 0; i <= 50_000; i++)
    {
        auto gen = Random(unpredictableSeed);
        double rn = uniform(0.0, 1.0, gen);
        float tmp = X;

        if (rn <= 0.85)
        {
            X = 0.84 * X + 0.04 * Y;
            Y = 0.04 * tmp + 0.85 * Y + 1.6;
        }
        else if (rn < 0.93)
        {
            X = 0.20 * X - 0.26 * Y;
            Y = 0.26 * tmp + 0.212 * Y + 0.44;
        }
        else if (rn < 0.99)
        {
            X = -0.15 * X + 0.28 * Y;
            Y = 0.26 * tmp + 0.24 * Y + 0.44;
        }
        else
        {
            X = 0.0;
            Y = 0.16 * Y;
        }

        auto coordX = cast(int) (250 - 65 * X);
        auto coordY = cast(int) (480 - (35 * Y + 1));
        drawPoint(c, coordX, coordY);
    }
};


class MainForm : Form
{
    public this()
    {
        this.text = "Итерированная Система Функций";
        this.size = Size(500, 550);
        this.startPosition = FormStartPosition.centerScreen;
    };

    protected override void onPaint(PaintEventArgs e)
    {

        Canvas c = e.canvas;
        drawIFS(c);

        super.onPaint(e);
    }
};


int main(string[] args)
{
    return Application.run(new MainForm());
}

Небольшое пояснение: я так и не обнаружил в DGui примитив для отображения некоторой точки с координатами (x, y) и потому пришлось придумать свое, заместительное решение, которое выражается в функции drawPoint. Эта функция, используя примитив drawLine, рисует линию единичной длины, но поскольку, линия длиной и толщиной в один пиксель для глаза не воспринимается как линия, то результат будет выглядеть так, будто нарисована не линия, а точка.

Кроме того, для отрисовки точки, эта функция принимает на вход (помимо координат) объект Canvas (он же — холст), на котором и будет отображено рисуемое множество точек, а для того, чтобы точки были нарисованы, используется отображение точек некоторой «ручкой» (или «пером»), которое настраивается как объект Pen (принимает аргументы: цвет в виде объекта Color или SystemColors, толщина линии и стиль линии, в виде свойства объекта Pen).

Сама программка работает довольно нехитро: создаем объект типа Form (точнее, создаем новый объект, наследуемый от объекта Form) и перегружаем в нем метод OnPaint, отвечающий за отрисовку внутренностей окна, а затем внутри данного метода вызываем функцию drawIFS, принимающую аргумент типа Canvas и отрисовывающая всю нашу IFS.

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

dub build

Однако, после этой манипуляции, вы увидите в папке проекта файл размерностью около 2.5 Мб, что не очень-то и компактно!

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

dub build --arch=x86 --build=release --force

где, —arch=x86 — архитектура компьютера, на котором идет сборка; —build=release — тип сборки (release-версия); —force — ускоренная пересборка (без этой команды, dub откажется пересобрать приложение)
после выполнения которой вы можете лицезреть приложение размером около 600 Кб.

Но и это не предел для уменьшения размера приложения !

Для того, чтобы еще уменьшить размер приложения, можно воспользоваться моей утилиткой (использует upx), которая сжимает exe и dll-файлы: запускаем эту миниатюрную программку, указываем где лежит исполняемый файл и выставляем флаги «Best compression» и «Use all compression methods»:

Вот и все: получается файл, который меньше исходного в 4 — 5 раз!

Скриншот получившегося приложения:

И скриншот под Wine:

UPD: Утилита PackFace mod, ссылка на которую дана в статье больше не поддерживается, вместо этого рекомендуется пользоваться утилитой PackFaceD, которую можно найти на этом сайте в разделе «Софт».

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