#2d #rendering #skia

tiny-skia

A tiny Skia subset ported to Rust

2 unstable releases

0.2.0 Nov 16, 2020
0.1.0 Jul 4, 2020

#16 in Rendering

Download history 78/week @ 2020-08-04 89/week @ 2020-08-11 128/week @ 2020-08-18 167/week @ 2020-08-25 79/week @ 2020-09-01 79/week @ 2020-09-08 57/week @ 2020-09-15 52/week @ 2020-09-22 56/week @ 2020-09-29 86/week @ 2020-10-06 367/week @ 2020-10-13 844/week @ 2020-10-20 308/week @ 2020-10-27 250/week @ 2020-11-03 384/week @ 2020-11-10 207/week @ 2020-11-17

930 downloads per month
Used in 12 crates (via resvg)

BSD-3-Clause

585KB
13K SLoC

tiny-skia

Build Status Crates.io Documentation Rust 1.45+

tiny-skia is a tiny Skia subset ported to Rust.

The goal is to provide an absolute minimal, CPU only, 2D rendering library for the Rust ecosystem, with a focus on a rendering quality, speed and binary size.

And while tiny-skia is definitely tiny, it support all the common 2D operations like: filling and stroking a shape with a solid color, gradient or pattern; stroke dashing; clipping; images blending; PNG load/save. The main missing feature is text rendering (see #1).

Note: this is not a Skia replacement and never will be. It's more of a research project.

Motivation

The main motivation behind this library is to have a small, high-quality 2D rendering library that can be used by resvg. And the choice is rather limited. You basically have to choose between cairo, Qt and Skia. And all of them are relatively bloated, hard to compile and distribute. Not to mention that none of them is written in Rust.

But if we ignore those problems and focus only on quality and speed alone, Skia is by far the best one. However, the main problem with Skia is that it's huge. Really huge. It supports CPU and GPU rendering, multiple input and output formats (including SVG and PDF), various filters, color spaces, color types and text rendering. It consists of 370 KLOC without dependencies (around 7 MLOC with dependencies) and requires around 4-8 GiB of disk space to be built from sources. And the final binary is 3-8 MiB big, depending on enabled features. Not to mention that it requires clang and no other compiler, uses an obscure build system (gn) which still uses Python2 and doesn't really support 32bit targets.

tiny-skia tries to be small, simple and easy to build. Currently, it has around 14 KLOC, compiles in less than 5s on a modern CPU and adds around 200KiB to your binary.

Performance

Currently, tiny-skia is 20-100% slower than Skia. Which is still faster than cairo and raqote in many cases.

The heart of Skia's CPU rendering is SkRasterPipeline. And this is an extremely optimized piece of code. But to be a bit pedantic, it's not really a C++ code. It relies on clang's non-standard vector extensions, which means that it works only with clang. You can actually build it with gcc/msvc, but it will simply ignore all the optimizations and become 15-30 times slower! Which makes it kinda useless.

Skia also supports ARM NEON instructions, which are unavailable in a stable Rust at the moment. Therefore a fallback scalar implementation will be used instead on ARM and other non-x86 targets. So if you're targeting ARM, you better stick with Skia.

Also note, that neither Skia or tiny-skia are supporting dynamic CPU detection, so by enabling newer instructions you're making the resulting binary non-portable.

Essentially, you will get a decent performance on x86 targets by default. But if you are looking for an even better performance, you should compile your application with RUSTFLAGS="-Ctarget-cpu=haswell" env variables to enable AVX instructions.

You can find more information in benches/README.md.

Rendering quality

Unless there is a bug, tiny-skia must produce exactly the same results as Skia.

API overview

The API is a bit unconventional. It doesn't look like cairo, QPainter (Qt), HTML Canvas or even Skia.

The core ideas are that almost everything is stateless, immutable and valid.

  • Canvas provides a fairly spartan and low-level API. We don't have a draw_path method. Instead, there are fill_path and stroke_path.
  • Canvas contains just two states: world transform and clip path. We don't have save/restore functionality.
  • The only truly mutable type is Pixmap, which is our raster image.
  • Path cannot be modified after creation. It can be transformed, but this function consumes the object.
  • All geometry types are always valid and immutable. You cannot create a negative Size. And so on.
  • All types that store f32 guarantee that it is finite.

Out of scope

Skia is a huge library and we support only a tiny part of. And more importantly, we do not plan to support many feature at all.

  • GPU rendering.
  • Text rendering (maybe someday).
  • PDF generation.
  • Non-RGBA8888 images.
  • Non-PNG image formats.
  • Advanced Bézier path operations.
  • Conic path segments.
  • Path effects (except dashing).
  • Any kind of resource caching.
  • ICC profiles.

Notable changes

Despite being a port, we still have a lot of changes even in the supported subset.

  • No global alpha.
    Unlike Skia, only Pattern is allowed to have opacity. In all other cases you should adjust colors opacity manually.
  • No bilinear + mipmap down-scaling support.
  • tiny-skia uses just a simple alpha mask for clipping, while Skia has a very complicated, but way faster algorithm.

Notes about the port

tiny-skia should be viewed as a Rust 2D rendering library that uses Skia algorithms internally. We have a completely different public API. The internals are also extremely simplified. But all the core logic and math is borrowed from Skia. Hence the name.

As for the porting process itself, Skia uses goto, inheritance, virtual methods, linked lists, const generics and templates specialization a lot, and all of this features are unavailable in Rust. There are also a lot of pointers magic, implicit mutations and caches. Therefore we have to compromise or even rewrite some parts from scratch.

Alternatives

Right now, the only pure Rust alternative is raqote.

  • It doesn't support high-quality antialiasing (hairline stroking in particular).
  • It's very slow (see benchmarks).
  • There are some rendering issues (like gradient transparency).
  • Raqote has a very rudimentary text rendering support, while tiny-skia has none.

Safety

The project relies on some unsafe code.

License

The same as used by Skia: New BSD License

Dependencies

~1MB
~22K SLoC