A brief experience with SkiaSharp

Andris
4 min readMay 28, 2020

--

I’m not an expert in SkiaSharp but I used it in a project — rendering puzzles on morepuzzles.com — and wanted to share my experience. I hope you will find it useful.

Where to start

You can find the source code on github at github.com/mono/SkiaSharp. There are some examples and documentation there. There is also documentation on the Microsoft documentation page: docs.microsoft.com/en-us/dotnet/api/skiasharp?view=skiasharp-1.68.1

The easiest way to get SkiaSharp is by adding it to your project via NuGet.

Code structure

I saw some projects where Skia was deeply integrated into the code and the drawing process was scattered in many classes. That might be okay for some projects, but I prefer more modularity. I created a separate class that turns the incoming puzzle data into a document or image. The puzzle data is containing the grid structure and the text to display.

In our case we have separate functions for the different puzzles as they differ in the way they have to be displayed, but in regards of Skia they work the same. We process the puzzle grid, calculate document margins, cell size, font sizes, etc. and then ask Skia to draw lines and text. The puzzle data containing the grid and text comes from our puzzle generator.

Initialization

To use Skia for drawing we need a canvas. In the case of PNG, we can create a surface that has a canvas:

private static SKSurface CreateSurface(int width, int height){    var imageInfo = new SKImageInfo(width, height);    var surface = SKSurface.Create(imageInfo);    surface.Canvas.Clear(SKColors.White);    return surface;}

We will use the surface later to get the data from the canvas.

In the case of SVG we have to do things a bit differently and create an SvgCanvas, and other variables that we will use to get the data from the canvas like in the case of the PNG, just a bit differently.

private static SvgData CreateSvgData(MemoryStream memoryStream, float width, float height){    var skManagedWStream = new SKManagedWStream(memoryStream);    var skXmlStreamWriter = new SKXmlStreamWriter(skManagedWStream);    var svgCanvas = SKSvgCanvas.Create(SKRect.Create(0, 0, width, height), skXmlStreamWriter);    svgCanvas.Clear(SKColors.White);    return new SvgData {Canvas = svgCanvas, SkManagedWStream = skManagedWStream, SkXmlStreamWriter = skXmlStreamWriter};}

Cleanup

To use Skia without errors you have to close specific resources after you finished using them.

One way is to create a wrapper class to store all the necessary Skia data and create a Dispose method implementing IDisposable. We created two disposable classes one for Skia resource like paint and the other is for SVG because it uses different resources than the PNG surface that need to be closed.

public class YourSkiaWrapperClassName : IDisposable{    public SKPaint Paint { get; }    public SKTypeface Typeface { get; }    […]    public void Dispose()    {        Paint.Dispose();        Typeface.Dispose();    }    […]}public class SvgData : IDisposable{    public SKCanvas Canvas { get; set; }    public SKManagedWStream SkManagedWStream { get; set; }    public SKXmlStreamWriter SkXmlStreamWriter { get; set; }    public void Dispose()    {        Canvas.Dispose();        SkXmlStreamWriter.Dispose();        SkManagedWStream.Dispose();    }}

Getting the image

We return the image as a byte array as we need it in that format.

In case of PNG:

using (var surface = CreateSurface(width, height)){    var canvas = surface.Canvas;    DrawSudoku(data, canvas, style);    using (var image = surface.Snapshot())    using (var skData = image.Encode(SKEncodedImageFormat.Png, 100))    {        return skData.ToArray();    }}

In case of SVG:

using (var memoryStream = new MemoryStream()){    using (var svgData = CreateSvgData(memoryStream, width, height))    {        var canvas = svgData.Canvas;        DrawSudoku(data, canvas, style);    }    return memoryStream.ToArray();}

Using the data

You can either return the byte array and display it on the frontend for example as we did, or you can save it as a file.

File.WriteAllBytes(“image.png”, bytes);

Drawing settings

Load font family from embedded resource, set font color, size, stroke options:

const string fileName = “Your.Project.Name.And.Font.Path.FontName.ttf”;var manifestResourceStream = Assembly.GetAssembly(typeof(YourClassName)).GetManifestResourceStream(fileName);Typeface = SKFontManager.Default.CreateTypeface(manifestResourceStream);DefaultPaint = new SKPaint {Color = SKColors.Black, IsAntialias = true, Style = SKPaintStyle.Stroke, StrokeCap = SKStrokeCap.Round};TextPaint = new SKPaint {Typeface = Typeface, IsAntialias = true, Color = SKColors.Black, IsStroke = false, TextAlign = SKTextAlign.Center};

How to draw lines, shapes, text, etc.

When you have a canvas — either PNG or SVG — the drawing process is the same.

Drawing a line:

canvas.DrawLine(p1.x, p1.y, p2.x, p2.y, paint);

Drawing a square:

DrawSquare(canvas, style.DefaultPaint, xPos, yPos, style.CellSize);private static void DrawSquare(SKCanvas canvas, SKPaint paint, float xPos, float yPos, float cellSize){    using (var path = new SKPath())    {        path.MoveTo(xPos, yPos);        path.LineTo(xPos + cellSize, yPos);        path.LineTo(xPos + cellSize, yPos + cellSize);        path.LineTo(xPos, yPos + cellSize);        path.LineTo(xPos, yPos);        path.Close();        canvas.DrawPath(path, paint);    }}

Example:

https://morepuzzles.com/assets/img/thumbnail_sudoku.png

You can draw any shape if you write a method and do the math.

In you are interested in wrapped text you can read about it in this post: Text wrap with SkiaSharp

--

--

Andris

indie game dev. generative art, creative coding, science visualisation, machine learning, artificial intelligence, skateboarding