How can I get rid of artifacts in ImageSource created with SkiaSharp

1k views Asked by At

I created an app in which I want to display text on top of google maps. I chose to use custom markers, but they can only be images, so I decided to create an image from my text utilizing SkiaSharp.

private static ImageSource CreateImageSource(string text)
    {
        int numberSize = 20;
        int margin = 5;
        SKBitmap bitmap = new SKBitmap(30, numberSize + margin * 2, SKImageInfo.PlatformColorType, SKAlphaType.Premul);
        SKCanvas canvas = new SKCanvas(bitmap);

        SKPaint paint = new SKPaint
        {
            Style = SKPaintStyle.StrokeAndFill,
            TextSize = numberSize,
            Color = SKColors.Red,
            StrokeWidth = 1,
        };

        canvas.DrawText(text.ToString(), 0, numberSize, paint);
        SKImage skImage = SKImage.FromBitmap(bitmap);
        SKData data = skImage.Encode(SKEncodedImageFormat.Png, 100);
        return ImageSource.FromStream(data.AsStream);
    }

The images I create however have ugly artifacts on the top of the resulting image and my feeling is that they get worse if I create multiple images. enter image description here I built an example app, that shows the artifacts and the code I used to draw the text. It can be found here: https://github.com/hot33331/SkiaSharpExample

How can I get rid of those artifacts. Am I using skia wrong?

2

There are 2 answers

1
hot33331 On BEST ANSWER

I got the following answer from Matthew Leibowitz on the SkiaSharp GitHub:

The chances are you are not clearing the canvas/bitmap first.

You can either do bitmap.Erase(SKColors.Transparent) or canvas.Clear(SKColors.Transparent) (you can use any color).

The reason for this is performance. When creating a new bitmap, the computer has no way of knowing what background color you want. So, if it was to go transparent and you wanted white, then there would be two draw operations to clear the pixels (and this may be very expensive for large images).

During the allocation of the bitmap, the memory is provided, but the actual data is untouched. If there was anything there previously (which there will be), this data appears as colored pixels.

0
Graham Murray On

When I've seen that before, it's been because the memory passed to SkiaSharp was not zeroed. As an optimization, though, Skia assumes that the memory block passed to it is pre zeroed. Resultingly, if your first operation is a clear, it will ignore that operation, because it thinks that the state is already clean. To resolve this issue, you can manually zero the memory passed to SkiaSharp.

public static SKSurface CreateSurface(int width, int height)
    {
        // create a block of unmanaged native memory for use as the Skia bitmap buffer.
        // unfortunately, this may not be zeroed in some circumstances.
        IntPtr buff = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(width * height * 4);

        byte[] empty = new byte[width * height * 4];

        // copy in zeroed memory.
        // maybe there's a more sanctioned way to do this.
        System.Runtime.InteropServices.Marshal.Copy(empty, 0, buff, width * height * 4);

        // create the actual SkiaSharp surface.
        var colorSpace = CGColorSpace.CreateDeviceRGB();
        var bContext = new CGBitmapContext(buff, width, height, 8, width * 4, colorSpace, (CGImageAlphaInfo)bitmapInfo);
        var surface = SKSurface.Create(width, height, SKColorType.Rgba8888, SKAlphaType.Premul, bitmap.Data, width * 4);

        return surface;
    }

Edit: btw, I assume this is a bug in SkiaSharp. The samples/apis that create the buffer for you should probably be zeroing it out. Depending on the platform it can be hard to repro as the memory alloc behaves differently. More or less likely to provide you untouched memory.