Папоротник Барнсли для Rosetta Code

Данный пост практически не содержит ничего нового или того, что мы не делали раньше. Этот пост носит скорее агитационный характер и содержит весьма простой пример, про который мы рассказывали уже дважды. Команда блога настоятельно рекомендует ознакомится с данным постом всем, кого интересует дальнейшее распространение информации по D и кто обладает достаточным опытом для решения на нем задач; также к прочтению приглашаются все те, кому просто интересно.

Rosetta Code — это очень интересный и познавательный ресурс, названный в честь Розеттского камня, и который содержит на своих страницах множество примеров решения некоторых задач на разных языках программирования. Прежде всего этот ресурс — это свободная wiki, статьи в которой имеют несколько способов группировки: по задаче и по языку программирования, на котором сделано решение. Решения, как и сами задачи, очень разные — и это позволяет сопоставлять языки программирования, а также очень быстро найти качественную реализацию некоторого алгоритма или просто посмотреть любопытный пример кода. Сайт Rosetta Code служит неплохой хрестоматией, а иногда даже помогает разрешать проблемы, вот почему написание статей для этой wiki очень полезно, и мы также не остались в стороне.

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

Примечание редактора. Папоротник Барнсли — фрактал, названный в честь Майкла Барнсли, британского математика, который первым описал его в своей книге «Фракталы Повсюду».

У нас уже было несколько различных вариантов построения этого фрактала, но ни один из них не подходил, т.к использовались GUI-библиотеки или же наши собственные надстройки. Также предыдущие наши реализации не проходили потому, что код был слишком большой и из-за этого утрачивалась наглядность. Именно поэтому решено было посмотреть текущие решения этой задачи на соответствующей странице Rosetta Code.

Вы удивитесь, но в качестве исходного материала была выбрана реализация на Perl 6:

use Image::PNG::Portable;
 
my ($w, $h) = (640, 640);
 
my $png = Image::PNG::Portable.new: :width($w), :height($h);
 
my ($x, $y) = (0, 0);
 
for ^2e5 {
    my $r = 100.rand;
    ($x, $y) = do given $r {
        when  $r <=  1 { (                     0,              0.16 * $y       ) }
        when  $r <=  8 { ( 0.20 * $x - 0.26 * $y,  0.23 * $x + 0.22 * $y + 1.60) }
        when  $r <= 15 { (-0.15 * $x + 0.28 * $y,  0.26 * $x + 0.24 * $y + 0.44) }
        default        { ( 0.85 * $x + 0.04 * $y, -0.04 * $x + 0.85 * $y + 1.60) }
    };
    $png.set(($w / 2 + $x * 60).Int, $h - ($y * 60).Int, 0, 255, 0);
}
 
$png.write: 'Barnsley-fern-perl6.png';

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

#!/usr/bin/env dub
/+ dub.sdl:
	dependency "dlib" version="~>0.21.0"
+/
import std.random;
 
import dlib.image;
 
void main()
{
	enum WIDTH = 640;
	enum HEIGHT = 640;
	enum ITERATIONS = 2E6;
 
	float x = 0.0f;
	float y = 0.0f;
 
	auto rng = Random(unpredictableSeed);
	auto color = Color4f(0.0f, 1.0f, 0.0f);
	auto img = image(WIDTH, HEIGHT);
 
	foreach (_; 0..ITERATIONS)
	{
		auto r = uniform(0, 101, rng);
 
		if (r <= 1)
		{
			x = 0.0;
			y = 0.16 * y;
		}
		else
		{
			if (r <= 8)
			{
				x = 0.20 * x - 0.26 * y;  
				y = 0.23 * x + 0.22 * y + 1.60;
			}
			else
			{
				if (r <= 15)
				{
					x = -0.15 * x + 0.28 * y; 
					y = 0.26 * x + 0.24 * y + 0.44;
				}
				else
				{
					x = 0.85 * x + 0.04 * y;
					y = -0.04 * x + 0.85 * y + 1.6;
				}
			}
		}
		auto X = cast(int) (WIDTH / 2.0 + x * 60);
		auto Y =  HEIGHT - cast(int)(y * 60);
		img[X, Y] = color;
	}
	img.saveImage(`barnsley_dlib.png`);
}

Результат выглядит так:

И хотя первоначальный вариант включал в себя использование ppmformats, для примера под Rosetta Code, мы выбрали dlib, поскольку посчитали, что ppmformats еще слишком мало известная библиотека (хотя и присутствует в реестре dub). Кроме того, первоначальный вариант вообще не требовал использование dub для сборки и собирался с помощью redo.

Сборочный рецепт для redo (предполагается, что исходный код для примера находится в файле barnsley.d, а библиотека в файле — ppmformats.d):

redo-ifchange barnsley.d ppmformats.d
ldc2 -Oz -release barnsley.d ppmformats.d -of $3
strip -s $3
chmod +rx $3
./$3
rm $3
echo $1

Исходный код примера:

import std.random;
 
import ppmformats;

void main()
{
	enum WIDTH = 640;
	enum HEIGHT = 640;
	enum ITERATIONS = 2E6;
 
	float x = 0.0f;
	float y = 0.0f;
 
	auto rng = Random(unpredictableSeed);
	auto color = new RGBColor(0, 255, 0);
	auto img = image(WIDTH, HEIGHT);
 
	foreach (_; 0..ITERATIONS)
	{
		auto r = uniform(0, 101, rng);
 
		if (r <= 1)
		{
			x = 0.0;
			y = 0.16 * y;
		}
		else
		{
			if (r <= 8)
			{
				x = 0.20 * x - 0.26 * y;  
				y = 0.23 * x + 0.22 * y + 1.60;
			}
			else
			{
				if (r <= 15)
				{
					x = -0.15 * x + 0.28 * y; 
					y = 0.26 * x + 0.24 * y + 0.44;
				}
				else
				{
					x = 0.85 * x + 0.04 * y;
					y = -0.04 * x + 0.85 * y + 1.6;
				}
			}
		}
		auto X = cast(int) (WIDTH / 2.0 + x * 60);
		auto Y =  HEIGHT - cast(int)(y * 60);
		img[X, Y] = color;
	}
	img.save(`barnsley_ppmformats.ppm`);
}

Стоит сказать, что мы честно разместили пример на Rosetta Code, почти сразу после заимствования кода для рисования окружности оттуда. Более того, это был очень занимательный опыт и потому мы рекомендуем вам ознакомится со списком нерешенных задач и написать пример хотя бы для одной из них — это сильно поможет в развитии языка.

P.S: Долг отдан.

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