Почитывая раздел Хабрахабра под названием «Ненормальное программирование» я наткнулся на интересную статью про узор, который как я понял, порождается определенными закономерностями во фрактале Герасимова.
Честно говоря, статью читал уже давно и поэтому я мельком глянул в аннотацию и увидел там набор квадратных паттернов, который меня заинтересовал и за который было решено взяться чисто ради эксперимента.
Если интересно, что получилось, то добро пожаловать в эту статью.
Меня заинтерсовали сами паттерны, которые выглядели достаточно просто:
Но, я не стал перечитывать упоминавшуюся статью, а решил пойти своим путем и попробовать получить из них какой-нибудь узор, для чего воспользовался библиотекой dlib.
Сначала, надо нарисовать сами квадратные паттерны (далее, я буду их именовать просто «квадратики») и потом использовать их для заполнения свободного от всего лишнего изображения. Для того, чтобы нарисовать «квадратики» создадим сначала перечисление с описанием их видов, а затем воспользуемся весьма простыми и прямолинейными функциями для рисования прямоугольника по координатам и рисования линии методом Брезенхема, которые описывались ранее в одной из наших статей.
Объединяя выше сказанное имеем следующий код, который использует некоторые математические предположения относительно самих линий в «квадратиках» (предполагаем, что линии которые дают образ конверта в паттернах идут точно до середины прямоугольника, и используем этот факт при рисовании):
import std.stdio; import dlib.image; // рисование прямоугольника void drawRectangle(ref SuperImage simg, Color4f color, int x, int y, uint w, uint h) { for (uint a = 0; a < h; a++) { simg[x, y + a] = color; } for (uint b = 0; b < w; b++) { simg[x + b, y + h] = color; } for (uint c = 0; c < h; c++) { simg[x + w, y + c] = color; } for (uint d = 0; d < w; d++) { simg[x + d, y] = color; } } // рисование линии по алгоритму Брезенхэма void drawBresenhamLine(ref SuperImage simg, Color4f color, int x1, int y1, int x2, int y2) { import std.algorithm : max; import std.math : abs; int dx = (x2 - x1 >= 0 ? 1 : -1); int dy = (y2 - y1 >= 0 ? 1 : -1); int lengthX = abs(x2 - x1); int lengthY= abs(y2 - y1); int length = max(lengthX, lengthY); if (length == 0) { simg[x1, y1] = color; } if (lengthY <= lengthX) { int x = x1; float y = y1; length++; while (length--) { simg[x, cast(int) y] = color; x += dx; y += dy * (cast(float) (lengthY)) / lengthX; } } else { float x = x1; int y = y1; length++; while(length--) { simg[cast(int) x, y] = color; x += dx * (cast(float) (lengthX)) / lengthY; y += dy; } } } enum SquarePattern : int { UP, DOWN, NOP } auto drawSquarePattern ( SuperImage superImage, Color4f color, SquarePattern pattern, int x, int y, int d ) { superImage.drawRectangle(color, x, y, d, d); superImage.drawBresenhamLine(color, x, y, x + (d / 2), y + (d / 2)); final switch (pattern) with (SquarePattern) { case UP: superImage.drawBresenhamLine(color, x + d, y, x + (d / 2), y + (d / 2)); break; case DOWN: superImage.drawBresenhamLine(color, x + (d / 2), y + (d / 2), x, y + d); break; case NOP: break; } }
Также, на всякий случай, я поместил в начале кода уже упоминавшиеся процедуры построения прямоугольника и линии, поскольку (пока ?) методов построения этих объектов непосредственно в составе dlib нет.
А теперь, возьмем некоторое пустое изображение, а затем разделим его на квадраты со стороной, равной стороне одного «квадратика», пройдемся в цикле по всем получившимся областям и случайным образом выберем вид «квадратика»:
auto drawPattern(SuperImage superImage, Color4f color, int size) { auto w = superImage.width; auto h = superImage.height; import std.random : uniform; for (int x = 0; x < w; x += size) { for (int y = 0; y < h; y += size) { int q = uniform(0,3); superImage.drawSquarePattern(color, cast(SquarePattern) q, x, y, size); } } }
Испытаем, создав для примера десять картинок размера 513 на 513, а сторону «квадратика» сделаем равным 8:
void main() { import std.conv : to; for (int i = 0; i < 10; i++) { auto img = image(513, 513); img.drawPattern(Color4f(0.5f, 0.0f, 0.85f), 8); img.savePNG("test" ~ to!string(i) ~ ".png"); } }
Вот пример одного такого изображения:
Интересный эффект можно получить просто избавившись от строки с отрисовкой прямоугольной области, для этого необходимо убрать или закоментировать строку с superImage.drawRectangle(color, x, y, d, d) из процедуры drawSquarePattern:
Ачто если мы возьмем некоторое изображение, пройдем по каждому из его пикселей, вычисляя его цвет и используя полученное значение для того, чтобы отобразить каждый пиксель в виде случайно выбранного «квадратика» в новом изображении ?
Сказано — сделано:
auto drawWithPatterns(SuperImage source, int size) { auto width = source.width; auto height = source.height; auto img = image(width * size, height * size); import std.random : dice, uniform; foreach (x; 0..width) { foreach (y; 0..height) { auto nx = x * size; auto ny = y * size; auto q = dice(33,33,33); Color4f current = source[x, y]; img.drawSquarePattern(current, cast(SquarePattern) q, nx, ny, size); } } return img; }
Код прост , и единственное, что мы использовали нового: это приведение целого числа к типу перечисления. В итоге, это избавляет от final…switch для выбора типа «квадратика» в зависимости от выпавшего случайного числа.
Испытаем, используя стандартное изображение Lenna:
void main() { auto img = load(`/home/aquareji/Downloads/220px-Lenna.png`); img.drawWithPatterns(8).savePNG(`Lenna_exp.png`); }
Результат:
И вот сейчас, я предложу кое-что любопытное: давайте вместо цвета некоторого пикселя из исходного изображения возьмем его яркость по стандарту 601 (также известному, как luminance) и умножим это значение на случайно выбранный цвет из палитры RGB. Полученный таким образом цвет будем использовать как цвет выбранного квадратика, осуществляя тем самым замену пикселей на «квадратики».
Процедура замены:
auto drawWithPatterns(SuperImage source, int size) { auto width = source.width; auto height = source.height; auto img = image(width * size, height * size); import std.random : dice, uniform; foreach (x; 0..width) { foreach (y; 0..height) { auto nx = x * size; auto ny = y * size; auto q = dice(33,33,33); Color4f current = Color4f( uniform(0.0f, 1.0f), uniform(0.0f, 1.0f), uniform(0.0f, 1.0f) ) * source[x, y].luminance601; img.drawSquarePattern(current, cast(SquarePattern) q, nx, ny, size); } } return img; }
А теперь результат для «квадратика» со стороной 4 пикселя:
Интересный факт: практически каждое изображение, которое можно прогнать через эту процедуру, будет выглядеть «серым». Но, чем больше различных яркостей есть в изображении, тем меньше видно разноцветный шум и тем более равномерным будет «общий» серый фон.
А вот еще одно изображение, обработанное схожим образом:
Гепард застрял в текстурах утонул в море шума.
Попробуем применить процедуру замены на только полученном изображении Лены (исходная картинка для преобразования на одну выше):
Первоначально, меня удивили результаты таких замен: любая картинка становится грязно-серой, разница только в количестве «разноцветных потертостей» в итоговом изображении, но потом я вспомнил, что нечто подобное я уже видел и причем очень давно…
В детстве, я очень увлекался физикой и моими пособиями были книги вроде «Опыты без приборов» Ф.Рабизы. Так вот, в таких книгах описывался очень занятный и простой опыт, который я даже делал сам и показывал родителям:
- берем лист бумаги (лучше лист белого картона) и вырезаем из него круг диаметров примерно сантиметров 5;
- делим его на 7 одинаковых секторов и раскрашиваем каждый из них в свой цвет, взятый из спектральной палитры (цвета радуги: красный, оранжевый, желтый, зеленый, голубой, синий и фиолетовый);
- после чего берем обычную спичку и заостряем один из ее концов;
- проделываем в центре круга спичкой отверстие;
- насаживаем круг на спичку, получая тем самым самодельный волчок
- запускаем волчок
Если волчок достаточно быстро раскрутить, то цвета смешаются в один, который будет близок к белому, но скорее всего получится какой-то из оттенков серого. То, что мы наблюдаем в этом опыте называется аддитивным смешением в белый цвет, и я полагаю, что в эксперименте с квадаратиками мы видим тоже самое, но на несколько иной основе.
Дело в том, что человеческое зрение не идеально и имеет ряд дефектов, как физиологических, так и психологических, именно из-за них, а также благодаря распределению генератора случайных чисел, мы наблюдаем оттенки серого. У нас просто сам мозг производит слияние «квадратиков», а их обилие провоцирует некое «размывание» картинки, сглаживая «разноцветность» и отфильтровывая часть шума.
На этом все, а я надеюсь что наш скромный эксперимент с dlib вас заинтересовал и сподвиг на новые изыскания в области цифровых экспериментов.