Posted on • Originally published atbclehmann.github.io
SkiaSharp: Hatched fills with SKShader
This article was originally publishedhere
Coming from System.Drawing.Common, one of the things I missed most about SkiaSharp was the lack of support for hatched fills out of the box. If you're unfamiliar, hatching allows you to paint with a pattern applied (source:ScottPlot 4.1 docs).
In SkiaSharp, you have two options:
If you want very simple results, SKPathEffect may be appropriate, but I would not recommend it for interactive or real-time rendering. SKShader isn't much harder to implement, and SKPathEffect has significantly poorer performance, especially if the hatched area takes up large portions of the screen. The same zoom level could yield ~10 FPS with SKPathEffect, and ~300 FPS with shaders.
Additionally, SKPathEffect has some further drawbacks. If you draw a circle with SKPathEffect, the effect will bleed over significantly. And SKPathEffect will not tile up to the edges of the circle, yielding poor-looking results. These can be countered by clipping and shrinking the tiling unit respectively, but for my purposes it wasn't worth it. Especially as reducing the size of each tile further exacerbates the performance problems. The rest of this post will be completely focused on SKShader.
SKShader
So, you've decided to use SKShader? Despite the name, it's really not too hard to use, you don't have to (and to my knowledge, cannot) write shaders from scratch in SkiaSharp. Instead, we're going to create a small bitmap, and useSKShader.CreateBitmap
to create a shader that tiles it across the fill.
For a striped hatch, the code to create the bitmap looks like this:
publicstaticSKBitmapCreateBitmap(SKColorhatchColor,SKColorbackgroundColor){varbitmap=newSKBitmap(20,50);usingvarpaint=newSKPaint(){Color=hatchColor};usingvarpath=newSKPath();usingvarcanvas=newSKCanvas(bitmap);canvas.Clear(backgroundColor);canvas.DrawRect(newSKRect(0,0,20,20),paint);returnbitmap;}
And the bitmap itself is simplyCreateBitmap(SKColors.Red, SKColors.Blue)
:
Doesn't look like much, does it? In any case, it's enough to create a striped pattern. Now we have to create a shader:
publicstaticSKShaderGetShader(SKColorhatchColor,SKColorbackgroundColor){returnSKShader.CreateBitmap(CreateBitmap(hatchColor,backgroundColor),SKShaderTileMode.Repeat,SKShaderTileMode.Repeat,SKMatrix.CreateScale(0.25f,0.25f));}
Now, if we use this shader to paint a square:
varshader=GetShader(SKColors.Red,SKColors.Blue);usingvarbmp=newSKBitmap(128,128);usingvarcanvas=newSKCanvas(bmp);usingvarpaint=newSKPaint(){Shader=shader};canvas.DrawRect(new(0,0,128,128),paint);WriteBitmapToFile(bmp,"hatch.png");// This function is included in the github link at the bottom
Note that the 2nd and 3rd parameters set the tiling mode for the x and y directions respectively. In our case, we want it to repeat, but if you want it to mirror or clamp in one direction you can. Clamping in this context means displaying the image once and stretching the last pixel to the edge of the fill area.
The last parameter is for applying a transformation to the shader. For now, we just want to rescale it, but next we'll use it for rotations.
Rotating the shader
Now, what if you want vertical or diagonal stripes? You could rotate the bitmap, but it's simpler to rotate the shader. That way, you can reuse the same bitmap for different rotations.
Since the last parameter toSKShader.CreateBitmap
was a transformation matrix, we can simply multiply by our desired rotation matrix to get what we want:
publicstaticSKShaderGetShader(SKColorhatchColor,SKColorbackgroundColor,StripeDirectionstripeDirection=StripeDirection.Horizontal){varrotationMatrix=stripeDirectionswitch{StripeDirection.DiagonalUp=>SKMatrix.CreateRotationDegrees(-45),StripeDirection.DiagonalDown=>SKMatrix.CreateRotationDegrees(45),StripeDirection.Horizontal=>SKMatrix.Identity,StripeDirection.Vertical=>SKMatrix.CreateRotationDegrees(90),_=>thrownewNotImplementedException(nameof(StripeDirection))};returnSKShader.CreateBitmap(CreateBitmap(hatchColor,backgroundColor),SKShaderTileMode.Repeat,SKShaderTileMode.Repeat,SKMatrix.CreateScale(0.25f,0.25f).PostConcat(rotationMatrix));}
Now, if we callGetShader
withStripeDirection.DiagonalUp
, we get this result instead:
A quick note on the transformation matrix, when you callA.PostConcat(B)
it represents this matrix multiplicationA * B
. These matrices represent a linear transformation, but matrix multiplication is not commutative, so the order matters. Unintuitively, the multiplicationA * B
corresponds toB(A(x))
, notA(B(x))
.
So while our code might look like it scales the shader followed by a rotation, it actually rotates and then scales. In this case, it doesn't make a difference, but it's worth noting in case you get up to anything more complicated.
Using a mask and colour filter
For most people's purposes, this is as far as they need to go. But if you're astute, you may have noticed that we baked the colours (in our case red and blue) into the bitmap. What if we need to be able to provide the same pattern with multiple colour palettes, do we have to regenerate the bitmap each time? Or do we have to write extra code to invalidate the bitmap should the colours change?
Instead, we can store the bitmaps as black and white, and then add the colour later. Then we can cache these bitmaps without ever needing to invalidate them. Seeing as they're so small, you could even bundle them with your distribution, should you be so inclined.
This is covered inthis post.
Links
Example code on github:https://github.com/bclehmann/SkiaSharpHatching
The pull request from which this code is based on:https://github.com/ScottPlot/ScottPlot/pull/2221
Top comments(1)

- LocationHong Kong
- WorkV Systems
- Joined
Apply your GLSL skills to the V Shader Hackathon, running until 22 May 2025!
Create unique Shader art to win up to $1000 – in collaboration with Gamedevjs.com
- Create your original Shader
- Upload the Javascript to the V Systems blockchain to turn it into an NFT
- Be one of the 16 winners of prizes ranging $500-$1000
How to join:medium.com/vsystems/13-26-april-cr...
Any questions? Join our community!
- Telegram:t.me/vhackathon
- Discord:discord.gg/Y2sKBbPAeD
For further actions, you may consider blocking this person and/orreporting abuse