Создание вращающегося логотипа с помощью ImageMagick и FFMPEG

Создание вращающегося логотипа с помощью ImageMagick и FFMPEG

791
ПОДЕЛИТЬСЯ

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

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

Что повлечет за собой внедрение утилиты bc, т.к. математика в bash достаточно обычная и тригонометрические функции вычислить проблемно. Для формирования команд утилите imagemagick будет употребляться bash. Для выполнения задачки пригодится система с установленными imagemagick и ffmpeg.

С помощью python, к примеру, тоже самое сделать еще проще, в нем-то синус и косинус рассчитываются.

Для сотворения видео сделаем N кадров, за которые логотип сделает полный оборот, потом превратим их в видеофайл.

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

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

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

Сформируем базисный набор кадров построим для вращения на 0-90 градусов, а кадры для других 270 получим незначительно их преобразовывая. Цикл, соответственно, будет не от 0 до 360 градусов, а от 0 до 90.

Пропишем путь к нашему логотипу, чтоб позже обращаться к нему маленьким $logo:

logo=../../logo/Moto_Gymkhana_transparent.png
Для удобства введем переменные равные ширине и высоте логотипа, так будет проще подправить скрипт под другую картину:

width=842
height=595
Она пригодится один раз — для вычисления шага, с которым должен рассчитываться угол поворота. Заведем переменную, равную количеству кадров, за которые логотип совершит четверть оборота.

Frames=15
В цикле пригодится:
— вычислить, какова будет ширина изображения при его повороте на данный переменной «n» угол;
— сделать пустой холст размером, скажем 850×600;
— добавить в его центр сжатое по ширине изображение.

for (( n=0; n<=90; n+=$(expr $(( 90/$Frames )))))
do
oWidth=$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l))

convert -size 850×600 xc:transparent -background none
( -alpha set -channel A -evaluate add -60% $logo -geometry $oWidth x$height! )
-gravity center -composite ./tmp/$n.png
done

Разберем код по частям.

Вычислить ширину изображения под данным углом:

oWidth=$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l))
Удобнее будет разбирать этот кусочек справа налево:
"| bc -l" показывает, что рассчитываться это будет утилитой bc.
Т.к. косинус в утилите «bc» воспринимает на вход значения в радианах, а числа пи в bash нет — используем последующее выражение:
c($n*4*a(1)/180) — где
«c» — косинус,
$n — угол в градусах узменяющийся в цикле;
Потому 4*a(1) это метод записать число пи. a(1) — арктангенс единицы, равный пи/4. На 180 делим чтоб перейти от радиан к градусам.
Таковым образом, "$width*(c($n*4*a(1)/180))" — ширина умноженная на косинус угла «n».
Попытку сделать изображение нулевой ширины не усвоит ImageMagick. "+1" опосля этого выражения для того, чтоб ширина изображения не воспринимала значения 0.
$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l)) — вычислить значение ширины для данного угла и вывести, округлив до целого.

)
-gravity center -composite ./tmp/$n.png convert -size 850×600 xc:transparent -background none
( -alpha set -channel A -evaluate add -60% $logo -geometry $oWidth x$height!
convert -size 850×600 xc:transparent -background none — сделать прозрачный холст размером 640×360 без заднего фона.

Это принципиально, по другому изменение размера коснется и холста. Следующая часть команды взята в экранированные "" скобки чтоб она выполнялась изолированно.
-alpha set — включить канал прозрачности.
-channel A -evaluate add -60% — добавить к значению канала А всех пикселей изображения "-60%".
$logo- путь к изображению.
-geometry $oWidth x$height! — поменять размер изображения.
Т.е. раз мы попытаемся убавить ширину — высота изображения тоже уменьшится чтоб сохранить соотношение «ширина х высота», а выше и ниже покажутся пустые области. "-geometry AxB" изменит размеры изображения, но сохранит соотношение сторон.
"-geometry AxB!" не бедут пробовать сохранить соотношение «ширина х высота», а изменит их независимо друг от друга.
-gravity center — расположить в центре.
-composite — объединить изображения.
./tmp/$n.png — сохранить файл в папку «tmp», назвав текущим значением переменной «n».

Вид с угла в 54 градуса:

Из этого эталонного набора изображений сделаем 4 кадра будущей анимации.

«Тень» на этих же кадрах будет добавлена не под начальное изображение, а поверх него, чтоб сделать эффект оборотной стороны логотипа. И отразим изображение слева-направо на кадрах, где логотип повернут к зрителю задней стороной. Для придания размера изображению добавим «тень» сдвинутую на право либо на лево в зависимости от того, куда на кадре вращается изображение. Пусть логотип вращается по часовой стрелке раз глядеть сверху.

Разглядим тщательно выражение для первой четверти поворота. Другие будут понятны по аналогии. Только акцентирую внимание на различиях от первого:

convert tmp/$n.png
( +clone -background ‘#cccf’ -shadow 100×8+$shadowShift-3 )
-background none -compose Src_Over -layers merge
-gravity center tmp/logo$(expr $(( 1000+n ))).png

convert tmp/$n.png — взять за базу начальное изображение.

( +clone -background ‘#cccf’ -shadow 100×8+$shadowShift-3 ) — склонировать и сделать тень изображения.
Цвет данный опцией background задаст цвет тени.
Как тень непрозрачна (в процентах) и размыта задается параметром «100×5».
Сдвиг тени относительно необычного изображения задается как +x+y. В нашем случае сдвиг по вертикали постоянен (3 пикселя ввысь), а сдвиг по горизонтали задается переменной shadowShift.
сдвиг при нулевом угле должен быть ноль. Лишь возьмем синус угла, а не косинус, т.к. Значение данной переменной вычислим аналогично сжатию начального кадра по вертикали.

shadowShift=$(printf %.$2f $(echo «15*(s($n*4*a(1)/180))+1» | bc -l))
Для первой и 2-ой четверти тень обязана быть сдвинута на право, потому значение shadowShift берем со знаком «плюс», для третьей и четвертой — со знаком «минус».

-background none -compose Dst_Over -layers merge -gravity center -depth 8 tmp/logo$(expr $(( 1000+n ))).png

Она задает какое из изображений будет размещено сверху. Здесь принципиальная функция это -compose.
Для первой четверти поворота, сверху размещается сам логотип, потому Dst_Over. Для 2-ой и третьей четверти будет прописано Src_Over (тень сверху).

-layers merge -gravity center — скооперировать слои, расположить в центре.

tmp/logo$(expr $(( 1000+n ))).png — сохранить под именованием logo + «1000+значение n» в папку tmp.

имя такое чтоб имена кадров сходу отсортировались в алфавитном порядке и их было комфортно дать на обработку ffmpeg’у.

Для кадров 2-ой четверти поворота будет «1800-n», для третьей «2000+n», для четвертой «2800-n».

Из-за этого, кстати, пригодится поменять символ смещения тени на противоположный. Для 2-ой и третьей четвертей покажется функция -flop — отразить изображение слева-направо.

Код для всех 4 четвертей поворота:

convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8+$shadowShift-3 )
-background none -compose Dst_Over -layers merge -gravity center -depth 8 tmp/logo$(expr $(( 1000+n ))).png

convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8-$shadowShift-3 )
-background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 1800-n ))).png

convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8+$shadowShift-3 )
-background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 2000+n ))).png

convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8-$shadowShift-3 )
-background none -compose Dst_Over -layers merge -gravity center tmp/logo$(expr $(( 2800-n ))).png

В итоге из начального кадра получены 4 с маленькими отличиями:

Сейчас дело за малым, собрать из этих изображений видео:

ffmpeg -pattern_type glob -i ‘tmp/logo*.png’ -pix_fmt argb -vcodec qtrle -r 30 rotatingLogo.mov
-pattern_type glob — дозволяет задать маску имени кадра в обычном по консоли виде
-i ‘tmp/logo*.png’ — взять как начальные данные изображения из папки tmp чье имя начинается с «logo»
-pix_fmt argb — задать формат изображения с прозрачностью. «a» в ARGB — это как раз альфа-канал
Из узнаваемых мне это «qtrle» и «png» -vcodec qtrle — задать кодек для видео поддерживающий прозрачность.
-r 30 — задать частоту итогового видео 30 кадров в секунду.
rotating_logo.mov — сохранить под именованием «rotatingLogo.mov»

Сейчас можем выполнить команду ffplay rotatingLogo.mov и поглядеть на итог:

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

Чтоб логотип вращался не повсевременно, а эпизодически сделаем кусочек видео с неподвижным логотипом. Для статьи пусть будет продолжительностью в 3 секунды, чтоб не приходилось долго ожидать вращения при просмотре:

ffmpeg -loop 1 -pattern_type glob -i ‘tmp/logo1000.png’ -pix_fmt argb -vcodec qtrle -r 30 -t 3 stillLogo.mov
В данной команде возникла функция "-loop 1" — указывающая ffmpeg повторять входную последовательность изображений (в данном случае одно единственное), и функция "-t 3" указывающая, что длительность итогового видео 3 секунды.

Чтоб склеить видео с неподвижным и вращающимся логотипом, сделаем файл со перечнем файлов, которые необходимо склеить. В данном случае 20 экземпляров наших файлов:
for (( i=0; i<20; i+=1 ))
do
echo file ‘stillLogo.mov’ >> list.txt
echo file ‘rotatingLogo.mov’ >> list.txt
done

Подадим этот файл на вход фильтру concat с указанием копировать кодеки, а не перекодировать (-c copy).

ffmpeg -f concat -i list.txt -c copy logoWithRotation.mov

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

ffmpeg -i video.mov -i logoWithRotation.mov
-filter_complex "[1:0]scale=iw/2:ih/2[logo];[0:0][logo]overlay=shortest=1:x=20:y=500"
-vcodec libx264 -crf 18 overlay.mov

ffmpeg -i video.mov -i logoWithRotation.mov — взять на вход два видео.

-filter_complex — применять filter_complex которому мы указываем, последующее:
Передать далее под именованием «logo». [1:0]scale=iw/3:ih/3[logo] — взять поток 0 из второго видео (1 отсчет идет с нуля) и уменьшить значения ширины (iw — input width) и высоты (ih — input height) в три раза.

[0:0][logo]overlay=shortest=1:x=20:y=0 — взять поток 0 из первого видео и поток «logo» и наложить их друг на друга.
«shortest=1» — говорит, прекратить по достижении конца хоть какого из потоков.
x=20:y=500 — показывает где располагать левый верхний угол накладываемого видео.

0 — вообщем без сжатия. Значение опосля crf показывает как сильно сжимать. -vcodec libx264 -crf 18 — применять кодек H264.

overlay.mov — сохранить под сиим именованием.

Поглядеть итог:

ffplay overlay.mov

В порядке возникновения в статье: Картинки и анимация для статьи сделаны с помощью тех же инструментов.

ffmpeg -i rotatingLogo.mov -filter_complex "[0:0]scale=iw/3:ih/3" rotatingLogo.gif

montage tmp/54.png -geometry 300×200 result.png

montage -mode concatenate -tile 2×2 tmp/logo1054.png tmp/logo1746.png tmp/logo2054.png tmp/logo2746.png -geometry 300×200 result.png

ffmpeg -i logoWithRotation.mov -t 5.5 -filter_complex "[0:0]scale=iw/3:ih/3" logoWithRotation.gif

Итоговый скрипт для сотворения вращающегося логотипа:

) -gravity center -composite -depth 8 ./tmp/$n.png
convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8+$shadowShift-3 ) -background none -compose Dst_Over -layers merge -gravity center -depth 8 tmp/logo$(expr $(( 1000+n ))).png
convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8-$shadowShift-3 ) -background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 1800-n ))).png
convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8+$shadowShift-3 ) -background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 2000+n ))).png
convert tmp/$n.png ( +clone -background ‘#cccf’ -shadow 100×8-$shadowShift-3 ) -background none -compose Dst_Over -layers merge -gravity center tmp/logo$(expr $(( 2800-n ))).png
done
ffmpeg -pattern_type glob -i ‘tmp/logo*.png’ -pix_fmt argb -vcodec qtrle -r 30 rotatingLogo.mov
ffmpeg -loop 1 -pattern_type glob -i ‘tmp/logo1000.png’ -pix_fmt argb -vcodec qtrle -r 30 -t 3 stillLogo.mov
rm list.txt
for (( i=0; i<20; i+=1 ))
do
echo file ‘stillLogo.mov’ >> list.txt
echo file ‘rotatingLogo.mov’ >> list.txt
done
ffmpeg -f concat -i list.txt -c copy logoWithRotation.mov logo=../../logo/Moto_Gymkhana_transparent.png
Frames=15
width=842
height=595
mkdir tmp
for (( n=0; n<=90; n+=$(expr $(( 90/$Frames )))))
do
oWidth=$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l))
shadowShift=$(printf %.$2f $(echo "15*(s($n*4*a(1)/180))+1" | bc -l))
convert -size 850×600 xc:transparent -background none ( -alpha set -channel A -evaluate add -50% $logo -geometry $oWidth x$height!

Наложение на файл video.mov с уменьшением логотипа в три раза:

ffmpeg -i video.mov -i logoWithRotation.mov -filter_complex "[1:0]scale=iw/3:ih/3[logo];[0:0][logo]overlay=shortest=1:x=20:y=500" -vcodec libx264 -crf 18 overlay.mov habrahabr.ru