Генерируем красивые картинки для социальных сетей

в 11:43, , рубрики: imagemagick, php, social networks, Программирование

Генерируем красивые картинки для социальных сетей - 1

Код для генерирования именно этого изображения

$generator = new imgGenerator();
$textGenerator=new imgTextGenerator();
$textGeneratorTop=new imgTextGenerator();

$label=$textGeneratorTop
	->seTextShadow("#000000", 75, 1, 2, 2)
	->setText("Test Site","#ffffff",imgGenerator::position_center_top,"1/12",0 )
	->setBackground("#000000",'3%')
	->setFont(DR."/upload/fonts/fonts2_7/hinted-PTF55F.ttf");

$text=$textGenerator
	->seTextShadow("#000000", 75, 1, 2, 2)
	->setText("Морковь как двигатель прогресса человечества","#ffffff",imgGenerator::position_center_center,"1/7",array(0,'5%',0,'5%'))
	->setFont(DR."/upload/fonts/fonts2_7/hinted-PTF55F.ttf");

$generator
	->addText($text)
	->addText($label)
	->fromImg($_SERVER["DOCUMENT_ROOT"] . "/upload/dynamic/2016-08/15/carrot-big.jpg")
	->resizeFor("autodetect")
	->addOverlay(0.5,"#000000")
	->show();

Глядя на красивые картинки для соц. сетей, которые в последнее генерируют многие новостные (и не только) сайты — захотелось написать свой генератор.

Примеры картинок

Генерируем красивые картинки для социальных сетей - 2

Скрипт работает на PHP, с использованием модуля Imagick. Писать это на GD2 что-то я не решился.

Алгоритм работы предполагался такой:

  • Берем за основу картинку или цвет
  • Уменьшаем до нужного размера
  • Накладываем сверху полупрозрачный фон
  • Устанавливаем логотип
  • Добавляем надпись
  • Кешируем результат

Помимо всего этого нужна возможность установки отступов, позиционирования, автоматического размера шрифта.

Ниже я буду писать куски кода из готового скрипта, скрипт полностью можно посмотреть на Github.

Создаем основу

Основа может быть либо из цвета, либо из картинки. Тут все просто. Создаем Imagick объект:

Для картинки:

$this->im = new Imagick($this->opts["img"]);

Для цвета:

$this->im = new Imagick();
$this->im->newImage(100,100,$this->opts["color"]);

Уменьшаем

Далее уменьшаем и обрезаем картинку до нужного размера, так как Imgick этого сам не умеет, пишем небольшой метод для этого:

$oldGeometry=$im->getImageGeometry();
$max=max($this->opts["resize_and_crop"]["width"],$this->opts["resize_and_crop"]["height"]);
if($max==$this->opts["resize_and_crop"]["width"]) {
	$otn=$oldGeometry["height"]/$oldGeometry["width"];
	$width=$max;
	$height=$max*$otn;
    if($height-$this->opts["resize_and_crop"]["height"] < 0) {
		$height=$this->opts["resize_and_crop"]["height"];
		$width=$height/$otn;
		$x=($width-$this->opts["resize_and_crop"]["width"])/2;
	} else {
		$x = 0;
	}
	if($position==imgGenerator::position_center_center) {
		$y=($height-$this->opts["resize_and_crop"]["height"])/2;
	}
} else {
	$otn=$oldGeometry["width"]/$oldGeometry["height"];
	$height=$max;
	$width=$max*$otn;
	if($width-$this->opts["resize_and_crop"]["width"] < 0) {
		$width=$this->opts["resize_and_crop"]["width"];
		$height=$width/$otn;
		$y=($width-$this->opts["resize_and_crop"]["height"])/2;
	} else {
		$y = 0;
	}
	if($position==imgGenerator::position_center_center) {
		$x=($width-$this->opts["resize_and_crop"]["width"])/2;
	}
}
$im->resizeImage($width,$height,Imagick::FILTER_LANCZOS,1,false);
$im->cropimage($this->opts["resize_and_crop"]["width"],$this->opts["resize_and_crop"]["height"],$x,$y);

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

Сами параметры оказались такими:

Facebook 1200x630
Twitter 978x511
Google+ 2120x1192 (победитель!)
Вконтакте 537x240
Однокласники 780x585 (уменьшил до 780x385)

При определении социальной сети, скрипт смотрит на User Agent, но тут была одна проблема, не все следуют своей собственной документации.

Так делает Вконтакте. Написано, что обращаясь к сайту он использует vkShare в качестве User Agent. На практике оказалось, что он это делает иногда. Я не знаю с чем это связано, но при попытке расшарить новую ссылку в VK, на страницу заходили несколько раз с совершенно разными браузерами. Иногда там был vkShare.

В итоге, после ряда экспериментов, решил сделать так, что если User Agent не определился, то считаем, что это VK.

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

  • facebookexternalhit
  • vkShare
  • Twitterbot
  • Google
  • OdklBot

Во время тестирования, в офисе прозвучал от меня довольно смешной вопрос «Кто-нибудь есть в однокласниках?». Никто не признался. Оказалось, что я там сам зарегистрировался когда-то.

Пока писал статью, скрипт обзавелся методом withoutCrop, смысл его в том, что он позволяет уменьшить и спозиционировать картинку, без ее обрезания. Это позволяет улучшить положение, если исходная картинка почти всегда является горизонтальной (например, если это обложка фильма, книги, игры и т.д.).

Накладываем полупрозрачную подложку

$geometry=$this->im->getImageGeometry();
$color=new ImagickPixel($this->opts["overlay"]["color"]);

$overlay->newImage($geometry["width"],$geometry["height"],$color);
$overlay->setImageOpacity($this->opts["overlay"]["opacity"]);

Установка логотипа

После некоторых экспериментов, пришел к выводу, что если логотип будет занимать не более 25% по ширине и высоте от картинки, то смотреться он будет вполне хорошо.

Скрипт позволяет установить лого в любое место на картинке, в том числе и по центру.

Настройки по умолчанию подойдут почти под все случаи, но скрипт позволяет менять размер лого, отступы и позиционирование.

Надпись

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

Итак, создаем экземпляр ImagickDraw и устанавливаем у него различные параметры: шрифт, размер шрифта, цвет, стиль, сглаживание:

$draw=new ImagickDraw();
$draw->setFont($this->opts["big_text_font"]);
$draw->setFontSize($fs);
$draw->setFillColor(new ImagickPixel($this->opts["big_text"]["color"]));
$draw->setStrokeAntialias(true);
$draw->setTextAntialias(true);

После этого, до установки выравнивания, разбиваем нашу строку на несколько строк, если она не влезает. Для этого используем queryFontMetrics, которая, о чудо (об этом — ниже), в данном случае работает как надо.

function splitToLines($draw,$text,$maxWidth)
{
	$ex=explode(" ",$text);
	$checkLine="";
	$textImage=new Imagick();
	foreach ($ex as $val) {
		if($checkLine) {
			$checkLine.=" ";
		}
		$checkLine.=$val;
		$metrics=$textImage->queryFontMetrics($draw, $checkLine);
		if($metrics["textWidth"]>$maxWidth) {
			$checkLine=preg_replace('/s(?=S*$)/',"n",$checkLine);
		}
	}
	return $checkLine;
}

Устанавливаем выравнивание:

$draw->setTextAlignment(Imagick::ALIGN_LEFT);

Используем метод annotation, для отрисовки надписи:

$draw->annotation(0, 0, $this->opts["big_text"]["text"]);

После этого, наш объект ImagickDraw был бы готов и осталось только создать объект Imagick, написать на нем наш текст, при помощи метода drawImage:

$textImage=new Imagick();
$textImage->newImage($textwidth,$textheight,"none");
$textImage->drawImage($draw);

$textwidth $textheight берем из queryFontMetrics, как и при разбивке большой строки. Но не тут-то было. Это все работает более или менее корректно, при выравнивании по левому краю, но при выравнивании нескольких строк по центру или по правому краю, начинало происходить что-то странное. Текст постоянно обрезался то с одной стороны, то с другой и непонятно было каким образом спозиционировать текст так, чтоб он влез в изображение.

В комментариях к методу, на php.net кто-то написал формулу вида:

$baseline = $metrics['boundingBox']['y2'];
$textwidth = $metrics['textWidth'] + 2 * $metrics['boundingBox']['x1'];
$textheight = $metrics['textHeight'] + $metrics['descender'];

Но эта формула тоже не работала.

Честно сказать, как я ни бился, пытаясь найти смысл в массиве от queryFontMetrics в разных вариантах позиционирования текста, разным количеством строк — мне это так и не удалось.

В итоге родился такой метод: высчитываем размеры по подсказке с php.net, но увеличиваем немного ширину и высоту.

$textIm=new Imagick();
$metrics=$textIm->queryFontMetrics($draw, $this->opts["big_text"]["text"]);
$baseline = $metrics['boundingBox']['y2'];
$textwidth = $metrics['textWidth'] + 2 * $metrics['boundingBox']['x1'];
$textheight = $metrics['textHeight'] + $metrics['descender'];
$draw->annotation ($textwidth*1.3, $textheight*1.3, $this->opts["big_text"]["text"]);

Далее создаем картинку в 3 раза больше и рисуем на ней нашу надпись:

$textImage=new Imagick();
$textImage->newImage($textwidth*3,$textheight*3,"none");
$textImage->drawImage($draw);

После чего обрезаем края, при помощи:

$textImage->trimImage(0);

И не забываем после этого использовать setImagePage, это нужно для того, чтоб координаты начала, высота и ширина возвращали новые значения:

$textImage->setImagePage(0, 0, 0, 0);

Тень под текстом

Imagick не умеет ставить тень у текста, но умеет делать тень из картинки. Ок, делаем копию с текстом, превращаем в тень, накладываем одно на другое:

$shadow_layer = clone $textImage;
$shadow_layer->setImageBackgroundColor(new ImagickPixel($this->opts["big_text_shadow"]["color"]));
$shadow_layer->shadowImage($this->opts["big_text_shadow"]["opacity"], $this->opts["big_text_shadow"]["sigma"], $this->opts["big_text_shadow"]["x"], $this->opts["big_text_shadow"]["y"]);
$shadow_layer->compositeImage($textImage, Imagick::COMPOSITE_OVER, 0, 0);
$textImage=clone $shadow_layer;

Кстати, $textImage->trimImage(0); конечно же нужно делать уже после установки тени.

Теперь все работает как надо.

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

Примеры работы скрипта (размер для VK):

Генерируем красивые картинки для социальных сетей - 3

Генерируем красивые картинки для социальных сетей - 4

Генерируем красивые картинки для социальных сетей - 5

Генерируем красивые картинки для социальных сетей - 6

Генерируем красивые картинки для социальных сетей - 7

Генерируем красивые картинки для социальных сетей - 8

Есть несколько идей, для развития скрипта, например сделать возможность ставить тест относительно друг друга, метод setLogo превратить в addImage и сделать возможным накладывать несколько картинок.

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

Автор: Vallefor

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js