如何摆脱使用 SkiaSharp 创建的 ImageSource 中的伪影

Posted

技术标签:

【中文标题】如何摆脱使用 SkiaSharp 创建的 ImageSource 中的伪影【英文标题】:How can I get rid of artifacts in ImageSource created with SkiaSharp 【发布时间】:2017-09-14 18:20:03 【问题描述】:

我创建了一个应用程序,我想在其中在谷歌地图上显示文本。我选择使用自定义标记,但它们只能是图像,因此我决定使用 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);
    

然而,我创建的图像在生成的图像顶部有难看的伪影,我的感觉是,如果我创建多个图像,它们会变得更糟。 我构建了一个示例应用程序,它显示了我用来绘制文本的工件和代码。在这里能找到它: https://github.com/hot33331/SkiaSharpExample

我怎样才能摆脱那些文物。我是不是用错了skia?

【问题讨论】:

请在您的问题中发布相关代码,不要仅仅链接到外部源,并期望我们花时间挖掘您的代码。如果您特别询问“视觉伪影”,包括显示问题的图像将是一个非常好的主意。 【参考方案1】:

我在 SkiaSharp GitHub 上从 Matthew Leibowitz 那里得到以下答案:

您可能没有先清除画布/位图。

您可以执行 bitmap.Erase(SKColors.Transparent) 或 canvas.Clear(SKColors.Transparent)(您可以使用任何颜色)。

这样做的原因是性能。创建新位图时,计算机无法知道您想要什么背景颜色。所以,如果它是透明的并且你想要白色,那么将有两个绘制操作来清除像素(这对于大图像来说可能非常昂贵)。

在分配位图的过程中,提供了内存,但实际数据没有被触及。如果之前有任何东西(将会有),则此数据显示为彩色像素。

【讨论】:

我认为这是新的和改进的行为。我过去看到的是,在新的 Skia 表面上,清除操作被忽略了,因为它被假定为已经清除。如果适当地为新的表面函数调用 clear ,那么它们一定已经修复了幕后的行为,这很棒。【参考方案2】:

我之前看到过,这是因为传递给 SkiaSharp 的内存没有归零。但是,作为一种优化,Skia 假设传递给它的内存块是预置零的。结果,如果您的第一个操作是清除的,它将忽略该操作,因为它认为状态已经是干净的。要解决此问题,您可以手动将传递给 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;
    

编辑:顺便说一句,我认为这是 SkiaSharp 中的错误。为您创建缓冲区的示例/api 可能应该将其归零。根据平台的不同,由于内存分配的行为不同,因此可能很难重现。或多或少可能会为您提供未受影响的记忆。

【讨论】:

以上是关于如何摆脱使用 SkiaSharp 创建的 ImageSource 中的伪影的主要内容,如果未能解决你的问题,请参考以下文章

C# SkiaSharp OpenTK Winform - 如何从后台线程中绘制?

如何使用 SkiaSharp 查找解码位图的 PixelFormat

如何使用 SkiaSharp 测量尾随空格?

如何提高使用 SkiaSharp 绘制大多边形的性能?

如何使用 SkiaSharp 加载“独立于平台”的图像?

SkiaSharp 和 GPU 加速