6

Compose, Camera and Canvas

 3 years ago
source link: https://proandroiddev.com/compose-camera-and-canvas-87b8cfed8cda
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Compose, Camera and Canvas

0*rdgFts3lj_gSgGDx?q=20
compose-camera-and-canvas-87b8cfed8cda
Photo by Kelli Tungay on Unsplash

To commemorate Jetpack Compose first beta release, Google launched the #AndroidDevChallenge. In the 2nd round, contestants were asked to create a simple countdown app and, the idea to create a kitchen timer, popped in my head.

By then, I didn’t had the time to accomplish it in one week, so I submitted a simpler one, but now that I have it, I invite you to join me in this journey.

1*N1V1g1Q8XkFjLgQQMvgtsQ.png?q=20
compose-camera-and-canvas-87b8cfed8cda
(image source)

The idea

At some point in our life we must have ​​had contact with this kind of funny timers. The tomato is my favourite so I decided to replicate it.

After choosing the picture on the left to inspire myself, a few problems and question raised in my mind.

  1. How would I spread minutes in the 2D plane simulating 3D?
  2. How would I shift them in a “circular” way?

Spherical coordinate system

To help me accomplish this task, I decided that my timer would have the form of a sphere. Then, I would create “points” to represent minutes (or seconds) so that I could latter move them through rotation. The coordinates of a point along a sphere don’t use the Cartesian Coordinates System, instead, it uses different values from the Polar Coordinate System. The polar coordinate system is extended into 3D with two different coordinate systems, the Cylindrical and Spherical coordinate system.

We’ll be using the second and switch from (x ,y , z) to (r, θ, φ):

1*gPj_y6qdRRUVpBuNqf1tFQ.png?q=20
compose-camera-and-canvas-87b8cfed8cda

Also, we cannot forget about the OpenGL coordinate system, which is the Right Hand System, thus:

1*5WMITsrziSY5AZAddXf4uA.png?q=20
compose-camera-and-canvas-87b8cfed8cda
Spherical Coordinates for orbit rotations when camera is Y-up. (image source)
  1. Phi φ (vertical position) angle between -90° and 90° [0 ; π]
  2. Theta θ (y rotation)angle between 0 and 360° [0 ; 2π]
  3. Radius r [0; +00[

In our case, the radius will be fixed and φ the same for every minute, leaving only θ to be calculated:

Next we need to transform 3D space information into 2D taking into account rotation in y-axis which will represent the progress between 0 and 1 ([0 ; 2π]):

This piece of code was inspired on Alex Lockwood bees and bombs compose

And the output is:

1*StHrgXxQEloz4HA3mjfbTQ.png?q=20
compose-camera-and-canvas-87b8cfed8cda
1*VvyP0LpNQGUOfKYr7Jx_7A.png?q=20
compose-camera-and-canvas-87b8cfed8cda
left: π/2 | right: π/1.7

Camera transformations

First challenge is solved, we have minutes positioned across a circular path, but we’re not quite there yet. By analysing the above images, you can see each minute is facing forward - it’s more noticeable with the bigger strokes -, we need to apply a few transformations in order to rotate them:

1*em3Hoza9mcG_17elir5Q0Q.png?q=20
compose-camera-and-canvas-87b8cfed8cda

To do that we’ll be using android.graphics.Camera

A camera instance can be used to compute 3D transformations and generate a matrix that can be applied, for instance, on a android.graphics.Canvas.

Simply put, we use the Camera to rotate a view on any axis.

Since Camera uses android.graphics.Matrix we can’t use the compose androidx.compose.ui.graphics.Matrix operations with it. Are we in trouble? No, we can have access to the native canvas by calling canvas.nativeCanvas, and this compose canvas instance is returned by drawIntoCanvas.

So, to achieve it, we use the following code:

  1. We get both Camera and Matrix instances;
  2. Apply the desired transformations (we’ll get there soon);
  3. Compute the Matrix corresponding to those transformations;
  4. Move to origin since Matrix uses 0,0 as it’s transform point;
  5. Move back to where it belongs (after concatenating);
  6. Save a copy of the current Canvas transform;
  7. Apply the transformations by concatenating the Matrix;
  8. Do our drawing logic;
  9. Restore the previous state.

Regarding the Camera transformations, let’s dive into it.

First things first, since we are going to simulate a 3D object, we won’t be seeing all the minutes:

1*RjDfvzeyq7mqKr0z6nj4cA.png?q=20
compose-camera-and-canvas-87b8cfed8cda
visible portion
1*cnbLZU4wQ5AQIrKR-PIviw.png?q=20
compose-camera-and-canvas-87b8cfed8cda
green and yellow = rotation thresholds | blue = fade threshold

This means that we can discard “invisible” information and create a logic that applies rotation only taking into account x-values from Canvas[start; center[ and [center; end]. Also, a fade effect using a y-value fixed threshold will be applied. The rotation in the y-axis uses values from 90° to 0°, and 0° to -90°:

And this is the result:

1*Hehc-crDGEBH1dzCZW4qBA.gif?q=20
compose-camera-and-canvas-87b8cfed8cda
1*spINx6vmmde-jcG3O9Q70w.gif?q=20
compose-camera-and-canvas-87b8cfed8cda
left image uses 80°/-80° | right image uses 90°/-90°

Note: actually the values will be from 80° to 0°, and 0° to -80° and an alpha fade in/out will be applied, this is less “real” but it will improve this little visual glitch on the limits that you can see on the right image.

Using the same logic for numbers we get:

1*MKg5EQkcQU6Jm-C6GES1lw.gif?q=20
compose-camera-and-canvas-87b8cfed8cda

Canvas operations

Before we come back again to adjust our transformations, let’s first design our timer to understand why we’ll be revisiting our beloved Camera.

First we draw a circle and use a Brush to apply a linear gradient. This will help us simulate a point of light and achieve a volumetric effect:

1*YwILWHGYAi5zHbT4gJirjw.png?q=20
compose-camera-and-canvas-87b8cfed8cda
1*5UULXjhIzfrIo2Y-pa8JSg.png?q=20
compose-camera-and-canvas-87b8cfed8cda
left: solid | right: linear gradient

Then, since the timer is composed (pun intended) by one moving part and a fixed one, we simulate it by drawing two arcs:

1*iAtimF-RKhWFr38h7ogsWw.png?q=20
compose-camera-and-canvas-87b8cfed8cda

And we finish with the timer track:

1*ouWmg1VWuuZz3_NzhnXgdQ.png?q=20
compose-camera-and-canvas-87b8cfed8cda

Remember when I said we would be revisiting Camera transformations? Well, the following animation show us why, and the reason is that we must simulate a curvature effect of the strokes and numbers so we can avoid this:

1*tNTYPcQh_PxvX6vB3UDWmg.gif?q=20
compose-camera-and-canvas-87b8cfed8cda
1*AK_YcXGxgkVkHVngr3yOBQ.jpeg?q=20
compose-camera-and-canvas-87b8cfed8cda
no curvature

Let’s fix it by applying rotations in the 3 axis:

The order of operations matter
1*WdjoERFTGAaIMtVrNugFtA.gif?q=20
compose-camera-and-canvas-87b8cfed8cda
1*-2Ejt47fpp2ii3p-VnOWsw.jpeg?q=20
compose-camera-and-canvas-87b8cfed8cda

Looks better, but we still have to do something about the limits. Let’s add a translation in the x-axis and y-axis to avoid the number “leaving” the sphere:

The order of operations matter
1*ufltf1OgC0QHumGo8Zj-eg.gif?q=20
compose-camera-and-canvas-87b8cfed8cda
1*Q0i6S5bgyJEKoVErUGsyLA.jpeg?q=20
compose-camera-and-canvas-87b8cfed8cda

Done, the only thing missing now is to apply a similar logic for the strokes. In this case lets add a z-axis rotation in addition to the one we have in the y-axis:

And the final result will be:

1*1DIPzSLJXBuSgfj_YwSLkg.gif?q=20
compose-camera-and-canvas-87b8cfed8cda
🎉 All right everyone, that’s a wrap! 🎉

You can find the full code of this project here:

Or you can play with it by downloading it from Google Play. Give it a go, I’ve added a few sounds to make it fun 😊

I hope you find this article useful, thanks for reading.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK