Create a spinning logo with ImageMagick and FFMPEG

The article may be of interest to those who want to slightly revitalize the design of their video channel, as well as those who have just started working with the tools indicated in the header. Confident users, I hope, will complement my material.

I thought about how you can revive the logo, which I put on the videos of some sporting events. There was an idea to make a logo rotating around its vertical axis every 20 seconds. As a result, I wanted to get something similar:



To complete the task, you need a system with imagemagick and ffmpeg installed. Imagemagick will use bash to generate commands. What will entail the use of the bc utility, because math in bash is fairly simple and trigonometric functions are difficult to compute.

Using python, for example, it’s even easier to do the same, in it the sine and cosine are calculated.

To create a video, create N frames, for which the logo will make a full revolution, then turn them into a video file.

In fact, rotation is a sequential compression and expansion of the image in one of the dimensions. If we want to get the effect of uniform rotation around the vertical axis, the image width will change by a multiple of the value of the sine or cosine functions.

The initial state for us is the logo visible in full size, therefore, to change the width of the image, we will multiply its width by cosine (its value at zero is exactly equal to unity).

The rotation of the image for these purposes can be divided into 4 stages:
- the image narrows;
- expands, but turned to the viewer with the reverse side;
- tapers back to the viewer;
- expands to its original size.

We will form the basic set of frames; we will construct it for rotation by 0-90 degrees, and the frames for the remaining 270 will be obtained by converting them a little. The cycle, accordingly, will not be from 0 to 360 degrees, but from 0 to 90.

We will write the path to our logo, then to access it with the short $ logo:

logo=../../logo/Moto_Gymkhana_transparent.png

For convenience, we introduce variables equal to the width and height of the logo, so it will be easier to adjust the script for a different picture:

width=842
height=595

Let's get a variable equal to the number of frames for which the logo will make a quarter turn. It will be needed once - to calculate the step with which the rotation angle should be calculated.

Frames=15

In the loop you will need:
- calculate what the width of the image will be when it is rotated through the angle given by the variable “n”;
- create an empty canvas of size, say 850x600;
- add an image compressed in width to its center.

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 850x600 xc:transparent -background none \
\( -alpha set -channel A -evaluate add -60% $logo -geometry $oWidth\ x$height\! \)\
 -gravity center -composite ./tmp/$n.png
done


Let's analyze the code in parts.

Calculate the image width at a given angle:

oWidth=$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l))

It will be more convenient to parse this piece from right to left:
"| bc -l" indicates that this will be calculated by the bc utility.
Because the cosine in the “bc” utility takes values ​​in radians, but there is no pi in bash — use the following expression:
c ($ n * 4 * a (1) / 180) - where
“c” is the cosine,
$ n is the angle in degrees naming in a cycle;
a (1) is the arctangent of unity equal to pi / 4. Therefore 4 * a (1) is a way to write the number pi. Divide by 180 to go from radians to degrees.
Thus, "$ width * (c ($ n * 4 * a (1) / 180))" is the width multiplied by the cosine of the angle "n".
"+1" after this expression so that the image width does not take the value 0. ImageMagick will not understand an attempt to make an image of zero width.
$ (printf%. $ 2f $ (echo "$ width * (c ($ n * 4 * a (1) / 180)) + 1" | bc -l)) - calculate the width value for a given angle and print out, rounded to the whole.

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

convert -size 850x600 xc: transparent -background none - create a transparent canvas with a size of 640x360 without a background.

The subsequent part of the command is taken in shielded "\" brackets so that it is executed in isolation. This is important, otherwise resizing will affect the canvas.
-alpha set - enable the transparency channel.
-channel A -evaluate add -60% - add “-60%” to the value of channel A of all image pixels.
$ logo is the path to the image.
-geometry $ oWidth \ x $ height \! - resize image.
"-geometry AxB" will resize the image, but retain the aspect ratio. Those. if we try to reduce the width, the image height will also decrease to maintain the ratio “width x height”, and empty areas will appear above and below.
"-geometry AxB \!" they don’t need to try to maintain the ratio “width x height”, but will change them independently of each other.
-gravity center - place in the middle.
-composite - combine images.
./tmp/$n.png - save the file to the “tmp” folder, naming the current value of the variable “n”.

View from an angle of 54 degrees:



From this reference set of images, we create 4 frames of the future animation.

Let the logo rotate clockwise when viewed from above. To add volume to the image, add a “shadow” shifted to the right or left, depending on where the image rotates on the frame. And reflect the image from left to right in the frames where the logo is turned to the viewer with the back side. “Shadow” on the same frames will not be added under the original image, but on top of it to create the effect of the reverse side of the logo.

Let us consider in detail the expression for the first quarter of the rotation. The rest will be understood by analogy. I only focus on the differences from the first:

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


convert tmp / $ n.png - take the original image as a basis.

\ (+ clone -background '#cccf' -shadow 100x8 + $ shadowShift-3 \) - tilt and create a shadow image.
The color specified by the background option will set the shadow color.
How much the shadow is opaque (in percent) and blurred is set by the “100x5” parameter.
The shadow offset relative to the original image is set to + x + y. In our case, the vertical shift is constant (3 pixels up), and the horizontal shift is set by the shadowShift variable.
We calculate the value of this variable in the same way as vertical compression of the original frame. Just take the sine of the angle, not the cosine, because the shift at zero angle should be zero.

shadowShift = $ (printf%. $ 2f $ (echo "15 * (s ($ n * 4 * a (1) / 180)) + 1" | bc -l))
For the first and second quarters the shadow should be shifted to the right, therefore we take the shadowShift value with a plus sign, for the third and fourth with a minus sign.

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

Here, an important option is -compose. It sets which of the images will be located on top.
For the first quarter of the turn, the logo itself is located on top, therefore Dst_Over. For the second and third quarters, Src_Over (shadow on top) will be registered.

-layers merge -gravity center - combine layers, arrange in the middle.

tmp / logo $ (expr $ ((1000 + n))). png - save under the name logo + “1000 + value n” in the tmp folder.

the name is such that the frame names are sorted immediately in alphabetical order and it was convenient to give them to ffmpeg for processing.

For the frames of the second quarter turn will be “1800-n”, for the third “2000 + n”, for the fourth “2800-n”.

For the second and third quarters, the -flop option will appear - flip the image from left to right. Because of this, by the way, you will need to change the sign of the shadow offset to the opposite.

Code for all four quarters of a turn:

convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8+$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 100x8-$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 100x8+$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 100x8-$shadowShift-3 \) \
-background none -compose Dst_Over -layers merge -gravity center tmp/logo$(expr $(( 2800-n ))).png


As a result, 4 with slight differences were obtained from the original frame:



Now it's up to the small, assemble the video from these images:

ffmpeg -pattern_type glob -i 'tmp/logo*.png' -pix_fmt argb -vcodec qtrle -r 30 rotatingLogo.mov

-pattern_type glob - allows you to set the mask for the name of the frame in the usual form on the console
-i 'tmp / logo * .png' - take the source image data from the tmp folder whose name starts with “logo”
-pix_fmt argb - set the image format with transparency. "A" in ARGB is just the alpha channel
-vcodec qtrle - set the codec for the video supporting transparency. Of the ones I know, “qtrle” and “png”
-r 30 - set the frequency of the final video to 30 frames per second.
rotating_logo.mov - save as “rotatingLogo.mov”

Now we can execute the command
ffplay rotatingLogo.mov
and look at the result:



The resulting video is only a couple of seconds long, incl. compatible many of these pieces together.
To do this, use the same ffmpeg.

So that the logo does not rotate constantly, but occasionally create a piece of video with a fixed logo. For an article, let it be 3 seconds long so that you do not have to wait long for rotation when viewing:

ffmpeg -loop 1 -pattern_type glob -i 'tmp/logo1000.png' -pix_fmt argb -vcodec qtrle -r 30 -t 3 stillLogo.mov

The option “-loop 1” appeared in this command - telling ffmpeg to repeat the input image sequence (in this case, the only one), and the option “-t 3” indicating that the duration of the final video is 3 seconds.

To glue a video with a fixed and rotating logo, create a file with a list of files that need to be glued. In this case, 20 instances of our files:
for (( i=0; i<20; i+=1 ))
do
echo file 'stillLogo.mov' >> list.txt
echo file 'rotatingLogo.mov' >> list.txt
done


We pass this file to the input of the concat filter with the instruction to copy the codecs, rather than transcode (-c copy).

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




It remains to overlay the resulting logo on the video.
The logo is quite large, incl. when applying, scale it, reducing it by three times.

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 - take two videos as input.

-filter_complex - use filter_complex to which we indicate the following:
[1: 0] scale = iw / 3: ih / 3 [logo] - take stream 0 from the second video (1 count goes from zero) and reduce the width (iw - input width) and height (ih - input height) three times. Pass on under the name "logo".

[0: 0] [logo] overlay = shortest = 1: x = 20: y = 0 - take stream 0 from the first video and the stream “logo” and lay them on top of each other.
“Shortest = 1” - says to terminate upon reaching the end of any of the threads.
x = 20: y = 500 - indicates where to place the upper left corner of the video overlay.

-vcodec libx264 -crf 18 - use the H264 codec. The value after crf indicates how much to compress. 0 - no compression at all.

overlay.mov - save under this name.

View result:

ffplay overlay.mov






Drawings and animations for the article are created using the same tools. In order of appearance in the article:

ffmpeg -i rotatingLogo.mov -filter_complex "[0:0]scale=iw/3:ih/3" rotatingLogo.gif
montage tmp/54.png -geometry 300x200 result.png
montage -mode concatenate -tile 2x2 tmp/logo1054.png tmp/logo1746.png tmp/logo2054.png tmp/logo2746.png -geometry 300x200 result.png
ffmpeg -i logoWithRotation.mov -t 5.5 -filter_complex "[0:0]scale=iw/3:ih/3" logoWithRotation.gif




The final script for creating a rotating logo:

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 850x600 xc:transparent -background none \( -alpha set -channel A -evaluate add -50% $logo -geometry $oWidth\ x$height\! \) -gravity center -composite -depth 8 ./tmp/$n.png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8+$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 100x8-$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 100x8+$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 100x8-$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


Overlay on the video.mov file with the logo reduced by three times:

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

Also popular now: