Компания Сиджеко занимается поддержкой сайта организации Реконстрой, которая продаёт и доставляет кирпич, черепицу, архитектурный декор и многие другие строительные материалы в Центральном Черноземье.
В процессе работы над сайтом возникла идея конструктора кладки.
У немецкого концерна «Feldhaus Klinker» существуют модельные ряды кирпича «Vascu Mix» и «Sintra Mix», которые специально предназначены для смешивания в разных пропорциях и создания неповторимого рисунка кладки. К ним существует ряд замазок «Quick Mix», применяемых при замазывании швов кладки между кирпичами. Для демонстрации этого подхода мы решили сделать конструктор кирпичной кладки, аналогов которому в интернете я пока не видел (буду рад примерам).
На кирпичи и швы накладываются тени и царапинки, чтобы максимально приблизить рисунок к жизни. Переключатели решили стилизовать под тач-устройства. Для конструктора были обработаны сотни изображений: фотографии кладок, кирпичей, замазок для швов и т. п.
Режим «Пьяный мастер» — потехи ради:
Процесс
Оставляем дизайнера с картинками (на самом деле, объём работы там несоизмеримо больше программистской стороны дела), фронтендер Дима делает красиво на клиентской стороне, я стараюсь дотянуть до его мастерства на бекенде.
Итак…
Основные шаги
Берём все возможные фотографии кладок, которые только смогли найти в рекламных материалах компании, добавляем свои фото.
Вырезаем кирпичи, производим их обтравку с прозрачными полями.
Приводим изображения кирпичей к одному размеру. От каждой модели нам нужно несколько реальных фоток, чтобы в результате было нескучно.
Виртуальная кладка строится не в той же последовательности, что и реальная :-)
Так, сначала всё поле заполняем картинкой шва, размножив её до нужного размера картинки.
Затем заполняем слой кирпичей, в котором разные модели линейки смешаны в нужной пропорции. Чётные ряды смещаем по горизонтали на полкирпича от нечётных. (Существуют и другие рисунки кладок для любителей дела, но пока было решено остановиться на классическом, чтобы уложиться в сроки.)
Можно было бы и остановиться, но выглядит слишком рафинированно. Реальность всегда лучше: потёртости, царапины — внешний вид каждой вещи отражает её историю. Нужно искусственно «состарить» изображение шумами.
Можно нанести шум просто рандомно изменив каждый пиксель изображения. Но это работает долго, да и выглядит не очень: мы, всё-таки, хотим эмулировать реальную фактуру материалов, а не ISO-шумы фотоаппарата.
// Шум (долго)
for ($x = 0; $x < $allWidth; $x++)
for ($y = 0; $y < $allHeight; $y++)
imagesetpixel($image, $x, $y, imagecolorallocatealpha($image, 0xff, 0xff, 0xff, MyRand::rand(115, 127)));
Пойдём более интересным путём (который, к тому же, сильно сэкономит время генерации картинки) — нанесём «царапины».
Царапины — это, по сути, линии белого и чёрного цвета со случайной длиной, наклоном и прозрачностью. Если верхняя часть линии светлая, а нижняя тёмная, на изображении это выглядит как выпуклость, если верхняя часть тёмная, а нижняя светлая — как царапина (впуклость, да).
// Выборочный шум (быстро)
// Царапинки и выпуклости
for ($x = 0; $x < 5000; $x++) {
$dotX = MyRand::rand(0, $allWidth);
$dotY = MyRand::rand(0, $allHeight);
$scratchWidth = MyRand::rand(1, 8);
// 80% впадинок-царапин (+1) и 20% выпуклостей (-1)
$scratchAdj = MyRand::rand(0, 10) > 8 ? -1 : +1;
for ($i = 0; $i < $scratchWidth; $i++) {
imagesetpixel($image, $dotX+$i, $dotY+$scratchAdj, imagecolorallocatealpha($image, 0xff, 0xff, 0xff, MyRand::rand(95, 110)));
imagesetpixel($image, $dotX+$i, $dotY, imagecolorallocatealpha($image, 0x00, 0x00, 0x00, MyRand::rand(95, 110)));
}
}
Осталось нанести копирайт. Подгрузим шрифт (Убунту подойдёт).
Готово!
Конструктор на выходе может выдавать и JPEG, и PNG (все исходные картинки в PNG24), но ввиду большого размера результирующего PNG мы остановились на JPEG.
Кеширование и урлы
Второй раз строить изображение при тех же входных параметрах не нужно, пощадим процессор и пользователя; сохраняем картинку при первом обращении и выдаём её из кеша при последующих.
Идентификатор кеша картинки (он же — имя файла) должен быть одинаковым при любом порядке переменных в GET-части урла. Поэтому сначала удаляем все ненужные переменные (ключи и значения, которые можно вручную подставить в урл), а потом сортируем глобальный массив $_GET
по ключам:
// Сортируем ключи (чтобы идентификатор кеша всегда был одинаков при одинаковом наборе ключей)
ksort($_GET);
Затем получаем всю остальную часть идентификатора:
$fileIdentifier = 'images/cache/' . $randSeed;
$fileIdentifier .= sprintf(
'/lineup-%s_color-%d_drunk-%s_',
$lineup,
$backFile,
$isDrunk ? 'on' : 'off'
);
foreach ($_GET as $key => $value) {
if (!empty($value))
$fileIdentifier .= preg_replace('/[^a-z0-9_-]/ui', '', $key) . '-' . preg_replace('/[^a-z0-9_-]/ui', '', $value) . '_';
}
$fileIdentifier = rtrim($fileIdentifier, '_');
$fileIdentifier .= '.' . OUTPUT_FORMAT;
Не забудем и про HTTP-заголовки, связанные с кешированием:
header('Cache-Control: Public');
header('Pragma: Public');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $time) . ' GMT');
header('If-Modified-Since: ' . gmdate('D, d M Y H:i:s', $time) . ' GMT');
header('Expires: ' . gmdate('D, d M Y H:i:s', $time + 30*24*60*60) . ' GMT');
Генератор случайных чисел
Чтобы полностью следовать философии урлов, для каждой ссылки всегда должна выдаваться одна и та же картинка. Но у нас в системе много случайных факторов (шумы, неровности). Если быть предельно точным, нам нужен не генератор псевдослучайных чисел (ГПСЧ), а генератор псевдослучайных последовательностей (ГПСП). Каждая последовательность, определяемая неким идентификатором (seed
), должна быть уникальной и повторяемой.
В новых версиях PHP (⩾5.2.1), генератор mt_rand()
перестал выдавать одинаковые последовательности при одинаковых значениях параметра mt_srand($seed)
(по крайней мере, рекомендуется на это поведение не рассчитывать). Напишем свой велосипед. Поскольку криптографической безопасности от генератора нам не требуется, воспользуемся одним из самых популярных и простых методов — умножением с переносом.
/**
* Получение следующего псевдослучайного целого числа в заданном диапазоне.
* @param integer $min
* @param integer $max
* @return integer
*/
public static function rand($min = 0, $max = 0xffffffff)
{
self::$m_z = 36969 * (self::$m_z & 65535) + (self::$m_z >> 16);
self::$m_w = 18000 * (self::$m_w & 65535) + (self::$m_w >> 16);
$res = ((self::$m_z << 16) + self::$m_w) & 0xffffffff;
$res = $res > 0 ? $res : ~$res;
//var_dump($res, PHP_INT_MAX, 0xffffffff); die;
return intval($min + floor(($res / doubleval(0xffffffff)) * ($max - $min + 1)));
}
Где self::$m_z
и self::$m_w
— статические поля текущего состояния генератора. При одном и том же заданном наборе self::$m_z
и self::$m_w
генератор будет выдавать одинаковые псевдослучайные последовательности целых чисел.
Режим «Пьяный мастер»
Повернём каждый кирпич на случайный угол в узком диапазоне наклона, и выпивший мастер уже работает над вашей стеной.
Варьируя диапазон наклона в градусах, можно менять количество и градус выпитого (простите за каламбур). Я остановился на ±3,2°.
Ссылки
- Конструктор кладки — http://реконстрой.рф/конструктор-кладки.
- Концерн Feldhaus Klinker — http://feldhaus-klinker.de.
- Реконстрой — http://реконстрой.рф.
- Небольшое описание конструктора на нашем сайте — http://sijeko.ru/works/programming/brick-constructor.
Автор: MaximAL