Custom Image ProgressBar using SkiaSharp Part1

Ahmed Fouad
ITNEXT
Published in
5 min readMay 17, 2020

--

Drawing a custom progress image has always been a challenging task as it requires a bit of Math which is not loved by a lot of developers. In this article, we will see how this can be a peace ok cake using c#.

Step 1: Prepare the image

We will need to have the image represented as an array of points, likely there is plenty of online tools that convert svg to points array like this one

for simplicity, I created a static class Drawings that contains the images I will use

Step 2:Create a static class

In this task, we will do a lot of mappings and conversations the best way to keep our code clean is to use extension methods.

Step 3: Convert Points to complex numbers

The complex number was introduced by Gerolamo Cardano as an attempt to solve the cubic equation, without going into further details you can think of it as 2 dimension numbers,

the first dimension in the real number (equivalent to X) and the second one is the imaginary component (equivalent to Y).

public static Complex[] ToComplex(this IEnumerable<Point> source)
{
var complex = source
.Select(item => new Complex(item.x,item.y))
.ToArray();
return complex;
}

Step 4: Discrete Fourier Transformation

This part is the most interesting, the image points data we have are the result of a drawing activity happened before.

I can represent the drawing as a function

Drawing(Canvas,Point)=Canvas.Points.Add(Point);

and from the previous function, we can represent DrawingImage Function as

Drawing(Canvas,Points)=Points.Foreach(point=>Drawing(Canvas,Point));

so looping over the points and drawing them one by one should give you the drawing animation effect?

unfortunately, things are not so simple as there is a missing parameter which is the time you know that these points have been drawn by someone one at a time but you do not know the order of drawing and the time gap between them.

So to solve this problem we will need to move from this discrete-time domain to another domain in which time can be consistent so instead of trying to determine the time gap between drawing points we will determine how many drawing events(how many points) can happen at a constant time T, this new domain is called the Frequency domain.

likely there is a well-known transformation between these 2 domains called Fourier transformation, in our code, we will use the Accord.net library to do it

//Receive a List of Points and return a Frequency of waves
public static Complex[] DFT(this IEnumerable<Point> source)
{
var complex = source
.Select(item => new Complex(item.x, item.y)).ToArray();

FourierTransform2.DFT(complex,
FourierTransform.Direction.Backward);
return complex;
}

Step 5: Setup SkiaSharp and Drawing Loop

SkiaSharp is a powerful c# canvas library that supports the majority of platforms (xamarin, uwp, Uno,….)

<forms:SKCanvasView PaintSurface="SKCanvasView_OnPaintSurface"/>

SkiaSharp allows Drawing only in the PaintSurface event and this event fires only the canvas is ready for the first time or Invalidated using the InvalidateSurface method.

private  async void SKCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
time += CONSTANTS
if (time > 2f * Math.PI)
{
time = 0;
_path.Clear();
}

await Task.Delay(1);
SkCanvasView.InvalidateSurface();
}

So here we have an infinite loop because we are calling InvaliateSurface in the PaintSurface event handler.

Every time we increment the time by a constant value and we will constrain the time by (2π) so the image should be fully drawn before 2π.

The Task.Delay(1); allow us to control the speed of time so when we delay 1 millisecond we say that 1 millisecond of our time = CONSTANT of the drawing time. If you are doing it to reflect real work progress you will have to change it to be more dynamic.

Now we need to calculate the CONSTANT

if we have 2π (maximum time) to draw N points so our constant should be 2π/N.

CONSTANT =(2f * Math.PI) / Count;

Step 6: Convert the waves to points given time

Waves and complex numbers are different representations for the same thing in this part I will use the complex number representation as it is more straight forward.

By Complex_number_illustration.svg: The original uploader was Wolfkeeper at English Wikipedia.derivative work: Kan8eDie (talk) — Complex_number_illustration.svg, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=5922759

The R here represents the wave Amplitude which is the hight of the wave .

The φ is the wave phase which is the angle between a wave and its processor at the same time.

Now we can calculate the (X,Y) using trigonometry

X=R.cos(φ);Y=R.sin(φ);

Actually the previous 2 equations are correct given a time T but in our case, the time and location are continuously getting changed

x = item.Magnitude * Math.Cos(WaveOrder * time + item.Phase);                y = item.Magnitude * Math.Sin(WaveOrder * time +  item.Phase);

So now can calculate the corresponding point of each wave, we can calculate the point corresponding to the full frequency given time

public static SKPoint ToPoint(this IEnumerable<Complex> drawingData,
double time, double x, double y)
{
for (int i = 0; i < drawingData.Count(); i++)
{
var item = drawingData.ElementAt(i);
x += item.Magnitude * Math.Cos(i * time + item.Phase); y += item.Magnitude * Math.Sin(i * time + item.Phase); }return new SKPoint((float)x,(float)y); }

Step 7: Draw the Points

private  async void SKCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.Black);

var point=
_complex.ToPoint(time,this.Width/2+100,this.Height/2);
_path.Insert(0, point); canvas.DrawPoints(SKPointMode.Polygon,
_path.ToArray());
time += (2f * Math.PI) / Count;
if (time > 2f * Math.PI)
{
time = 0;
_path.Clear();
}
await Task.Delay(1);
SkCanvasView.InvalidateSurface();
}

you can find the code fully functional here

I have to thank Daniel Shiffman as this code is just a refactored version of his javascript you can find his video tutorial here

And I highly recommend you check his book The Nature of Code , a really awesome book that will help to understand the relation between code and nature and the inspirations.

and finally, you can share your coffee with me at https://ko-fi.com/ahmedfouad and please follow me on twitter to get the new updates

--

--

Hello, I’m Ahmed. I’m a software engineer living in Vienna, Austria. I am a fan of technology, web development, and programming. I’m also interested in xamarin