Sitemap
Doubletapp

We tell you about mobile development, data sience, ux/ui design, web development

The story about the contest from telegram

--

Introduction

In January 2021, Telegram announced the iOS Animation Contest 2021. The participant had to create animations in Telegram for iOS using the provided layouts. They had to optimize them on older devices and develop an editor with which to customize their functionality. Junior iOS developer Aleksey Muraveinik took part in the competition from Doubletapp. That’s what came out of it.

Contest in a nutshell

The organizers provided photo and video files demonstrating animations that need to be implemented and embedded in the Telegram client on the iOS platform.

The code must be written in pure Swift; using third party UI frameworks like Flutter and React Native is prohibited.

The task consists of several parts:

  1. Implement animations for sending messages to chat

Messages should look like bubbles that pop up.

2. Implement animation editor

In this editor, you can set the speed and movement of an animated object.

3. Implement animated background

The background is a four-color gradient, the color centers of which are set at arbitrary points. Animation is obtained by moving color centers

The work is evaluated according to 3 criteria:

  1. How much the implemented animations are similar to those presented in the demo materials.
  2. How smoothly animations play on modern devices.
  3. How smoothly animations play on older devices (including the iPhone 6 and SE1).

The duration of the first round is -16 full days, until January 31 23:59 GMT + 4 (Dubai). Closer to the end of the competition, access will open to send works of telegrams to the bot @ContestBot

Participation in the competition

It was interesting to participate in the contest from Telegram. The task at hand turned out to be difficult, requiring a special approach. I had to smash my head and look for a solution, but it was definitely worth it — the experience was not only exciting, but also useful.

Gradient

On January 19, I was assigned to do some research on gradient rendering. It was immediately known that the standard method that I used in “Edge” and in “Practice”, most likely, will not work.

More about the standard way.

If you search Google for “swift gradient”, chances are you will find one of the following two ways:

  1. CAGradientLayer is a higher level one.
  2. CGGradient is a lower level one.
  3. CAGradientLayer.

CAGradientLayer
The first method really justifies its position in the top — the simplest gradient can be created in just 4 lines and get the following result

Additional colors and direction can be added

Set transition points from one color to another

For most, this functionality is sufficient, but not for us.

If you look closely at the background preview from the demo, you will notice that for each of the four colors a point is set from which the color spreads in all directions. For each point, a radius is specified, within which the color does not change in any way in relation to the center color.

This gives us a hint that we need to look for a way to draw gradients not linearly, but in the form of circles.

CGGradient
Fortunately, there is a standard solution for rendering gradient circles. This method is more low-level and uses 2D drawing techniques.

Already somewhat remotely reminiscent of the circles that we need.

But the main problem with this approach is that these kinds of gradients do not overlap, and instead of the expected overlap with the correct calculation of intermediate colors between the color centers, we get the following

Such a picture suggests that you need to come up with a solution that will calculate the color of each pixel on the screen.

How color components in a gradient are calculated
Let’s say we have 2 points:

  1. Red (R), in RGB has the color (255, 0, 0).
  2. Blue (B), RGB has color (0, 0, 255)

Let’s choose an arbitrary point for which we want to calculate the color. Let’s call it P.

Then the calculation of RGB color components for P will be as follows:

count the distance from R to B, let’s call this distance RB;
we calculate the distance from R to P, let’s call this distance RP;
count the distance from B to P, let’s call this distance BP;
We calculate the color components for P:

P.r = (RP / RB) * R.r + (BP / RB) * B.r;
P.g = (RP / RB) * R.g + (BP / RB) * B.g;
P.b = (RP / RB) * R.b + (BP / RB) * B.b.
This approach gives the expected result.

What if the gradient is on a plane?
In our problem, 4 points of different colors are given, so we use the following principle for the calculation: “Each specified color affects the color of any other point on the screen, if the distance from the center of the specified color to the point is less than some distance L.”

I used CMYK (Cyan, Magenta, Yellow, Key) dots at the corners of the screen as the perfect four-color gradient to target.

Obviously, black (bottom left) should not affect turquoise (top left). From this it follows that L should be less than the height of the screen. But if we take the height as L, then it turns out that the turquoise will have an effect on the magenta, because the distance between them is the width of the screen, that is, less than L.

If we take the width as L, then there will be points on the screen that are more than L from all colors, and it is not known how to calculate the color for them.

Therefore, the decision comes to use the width of the screen as L, generate the gradient image itself in the form of a square, and then stretch it to fill the screen.

Now, for each point, we calculate the influence factor of each of the colors on it.

This is done using the following formula:

Let d be the distance from a point to a color.
Let m be the maximum of {1 — (d / L)} and {0}. Here it turns out that if the distance from a point to a color is greater than L, then the influence factor of this color is zero.
Let f = m ^ 4.
f is the required factor. The degree in the formula is responsible for the smoothness / sharpness of color transitions. The higher the degree, the sharper the transition.

Now that we know the influence factor of each of the four colors on a point, we can calculate the color components using the following formula:

Let f1, f2, f3, f4 be the factors of influence of CMYK colors on the point
Let s = f1 + f2 + f3 + f4
Let sR = f1 * C.r + f2 * M.r + f3 * Y.r + f4 * K.r
Let sG = f1 * C.g + f2 * M.g + f3 * Y.g + f4 * K.g
Let sB = f1 * C.b + f2 * M.b + f3 * Y.b + f4 * K.b
Then the components of the point have the form:

r = sR / s
g = sG / s
b = sB / s
Using this method, you can calculate the components for any of the points on the screen, and get the expected result.

Gradient animation

Now that we are able to generate a gradient image with colors at arbitrary points, we can proceed to the animation itself.

Animation is changing pictures, that is, frames. The two standards for frame rate are considered to be 30 frames / s and 60 frames / s.
The demo animation shows that the color centers change for each frame.

There are 8 points in total, the movement of colors to which occurs counterclockwise. For every two adjacent points, you need to generate 30 or 60 frames, in which the center of the color will be located at one of the intermediate points.

And here we are faced with two problems.

Optimization of frame generation

At the moment, the algorithm for each frame calculates the number of points equal to the width of the screen in a square. On iPhone 11, this number is 171396. And this number of color calculations for dots needs to be done in 1 / 60th of a second. Of course this is not possible. There are several steps to solve this problem.

Caching

You can cache color influences for specified distances. Regardless of the point at which the center of the color is located on the screen, the factor of its influence on a point located at a distance m from it will always be the same. Therefore, for the first time — we count the factors, and in all subsequent times — we just get the stored value.

Compression

You can reduce the width of the image by 10 times, and the image itself can be stretched to fill the entire screen. For example, on iPhone 11, the number of dots for which the color calculation will need to be made will be 1681. The gradient will still look as it should.

Buffering

If you prepare all the frames at once before drawing, then the animation may delay at the beginning or memory will run out. If you prepare one frame at a time, then the gradient generator will not have time to form a new image during the period of time while the current one is being drawn, this will create a slowdown effect and kill the smoothness of the animation.

Solution: harvest 10 frames each and store them in the buffer. At the moment when the current 10 frames are drawn, the generator draws new 10 frames and places them in the buffer. When the current frames are drawn, they are replaced with those in the buffer, and the generator draws another 10 and places them in the buffer

Non-linearity of displacement of color centers

As you can see in the demo materials — the graph of the movement of points is a curve, that is, the points move nonlinearly. In the competition, no function was specified that could describe such a movement, so it was decided to set the function in a discrete form.

The horizontal axis is the timeline, we divide it into 60 parts (corresponds to 60 frames per second).

The vertical axis is the displacement scale, we divide it into 27 parts (conventionally).

​In the meantime, there is no need to know about it. ”
Then, in order to find the center of the color moving from point A to point B, for each of the 60 frames we do the following:

Let dx = (B.x — A.x) / 27
Let dy = (B.y — A.y) / 27
Defining the animationCurve mapping
And the color center for each frame is defined as

x = A.x + dx * animationCurve [frameNumber]

y = A.y + dy * animationCurve [frameNumber]

Let’s summarize

Gradient Image Generator:

  1. We cache the factors of influence of colors.
  2. Generate a square image of size {(screen width) / 10}.

Animation

  1. We prepare 10 frames to the buffer while the current 10 frames are being drawn
  2. The nonlinearity of the movement of points is described using a function given in a discrete form

The final result

Conclusion

It was difficult, but interesting.

The code for the final version of this project (and not only this one 😉) can be found in our repository on GitHub

--

--

Doubletapp
Doubletapp

Published in Doubletapp

We tell you about mobile development, data sience, ux/ui design, web development

Doubletapp
Doubletapp

No responses yet