Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Benjamin Lehmann
Benjamin Lehmann

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).

A bar chart rendered with ScottPlot 4.1. There are three series with different hach patterns applied: Thin stripes, thicker stripes, and a checkerboard pattern.

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;}
Enter fullscreen modeExit fullscreen mode

And the bitmap itself is simplyCreateBitmap(SKColors.Red, SKColors.Blue):

A 20px wide stripe of red across the top, followed by 30px of 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));}
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

Alternating red and blue horizontal stripes

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));}
Enter fullscreen modeExit fullscreen mode

Now, if we callGetShader withStripeDirection.DiagonalUp, we get this result instead:

Alternating red and blue stripes going up and to the right

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

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
v_systems profile image
V_Systems
V Systems (VSYS) is an open-source network that supports the efficient and agile development of decentralized applications. Join our 2025 Hackathon: https://hackathon.v.systems/
  • Location
    Hong Kong
  • Work
    V 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!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

  • Joined

More fromBenjamin Lehmann

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp