You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SkiaSharp中SKBitmap.GetPixel/SetPixel性能优化方案咨询

SkiaSharp大图片像素高效读写方案

问题背景

用C#结合SkiaSharp实现边缘检测,测试代码逻辑可行,但处理200KB以上的图片时,SKBitmap.GetPixel()和SetPixel()性能极差。对比SkiaSharp滤镜类API(如SKColorFilter.CreateTable)的高速运行,希望找到更高效的像素读写方式。

测试代码

canvas.Clear(SKColors.White);
//  USerskbitmapNEW has been loaded earlier.
SKBitmap image = new SKBitmap(USerskbitmapNEW.Width, USerskbitmapNEW.Height);

for (int i = 0; i < image.Width; i++)
{
    for (int j = 0; j < image.Height; j++)
    {
        SKColor c1 = USerskbitmapNEW.GetPixel(i, j - 1);
        SKColor c2 = USerskbitmapNEW.GetPixel(i, j + 1);
        int xval = 0;
        int yval = 0;
        xval = (int)(c1.Red * .3 + c2.Blue * 0.11 - (c2.Red * .3));
        yval = (int)(c2.Red * .3 + c2.Green * .29 + c2.Blue * 0.11);

        if (xval > yval )
        {
            SKColor c = SKColors.Black;
            image.SetPixel(i, j, c);
        }
    }
}

canvas.DrawBitmap(image, myframe);

高效解决方案

1. 直接操作像素缓冲区(CPU端最优)

GetPixel/SetPixel每次调用都会做边界检查和格式转换,批量处理时开销极大。改用LockPixels()获取像素内存的直接指针,绕开逐次调用的损耗:

canvas.Clear(SKColors.White);
SKBitmap image = new SKBitmap(USerskbitmapNEW.Width, USerskbitmapNEW.Height);

// 锁定源图和目标图的像素缓冲区
USerskbitmapNEW.LockPixels();
image.LockPixels();

try
{
    IntPtr srcPtr = USerskbitmapNEW.GetPixels();
    IntPtr dstPtr = image.GetPixels();
    int stride = USerskbitmapNEW.RowBytes;
    // 初始化目标图为白色
    byte[] dstPixels = new byte[stride * image.Height];
    Array.Fill(dstPixels, (byte)255);
    Marshal.Copy(srcPtr, dstPixels, 0, dstPixels.Length);

    for (int j = 0; j < USerskbitmapNEW.Height; j++)
    {
        for (int i = 0; i < USerskbitmapNEW.Width; i++)
        {
            // 处理边界越界问题
            int prevJ = Math.Max(0, j - 1);
            int nextJ = Math.Min(USerskbitmapNEW.Height - 1, j + 1);

            // 按ARGB8888格式计算像素索引(每个像素占4字节)
            int srcIndexPrev = prevJ * stride + i * 4;
            int srcIndexNext = nextJ * stride + i * 4;
            int dstIndexCurr = j * stride + i * 4;

            // 读取RGB分量(ARGB顺序:字节0=Alpha,1=Red,2=Green,3=Blue)
            byte c1R = dstPixels[srcIndexPrev + 1];
            byte c2R = dstPixels[srcIndexNext + 1];
            byte c2G = dstPixels[srcIndexNext + 2];
            byte c2B = dstPixels[srcIndexNext + 3];

            int xval = (int)(c1R * .3 + c2B * 0.11 - c2R * .3);
            int yval = (int)(c2R * .3 + c2G * .29 + c2B * 0.11);

            if (xval > yval)
            {
                // 设置为黑色
                dstPixels[dstIndexCurr + 1] = 0;
                dstPixels[dstIndexCurr + 2] = 0;
                dstPixels[dstIndexCurr + 3] = 0;
            }
        }
    }

    // 将处理后的像素写回目标Bitmap
    Marshal.Copy(dstPixels, 0, dstPtr, dstPixels.Length);
}
finally
{
    // 必须解锁像素缓冲区,避免内存泄漏
    USerskbitmapNEW.UnlockPixels();
    image.UnlockPixels();
}

canvas.DrawBitmap(image, myframe);

如果追求极致性能,可使用unsafe代码直接操作指针,省去数组拷贝开销(需在项目属性中开启“允许不安全代码”):

unsafe
{
    byte* srcBytes = (byte*)srcPtr.ToPointer();
    byte* dstBytes = (byte*)dstPtr.ToPointer();

    for (int j = 0; j < USerskbitmapNEW.Height; j++)
    {
        for (int i = 0; i < USerskbitmapNEW.Width; i++)
        {
            int prevJ = Math.Max(0, j - 1);
            int nextJ = Math.Min(USerskbitmapNEW.Height - 1, j + 1);

            byte* srcPrev = srcBytes + prevJ * stride + i * 4;
            byte* srcNext = srcBytes + nextJ * stride + i * 4;
            byte* dstCurr = dstBytes + j * stride + i * 4;

            byte c1R = srcPrev[1];
            byte c2R = srcNext[1];
            byte c2G = srcNext[2];
            byte c2B = srcNext[3];

            int xval = (int)(c1R * .3 + c2B * 0.11 - c2R * .3);
            int yval = (int)(c2R * .3 + c2G * .29 + c2B * 0.11);

            if (xval > yval)
            {
                dstCurr[1] = 0;
                dstCurr[2] = 0;
                dstCurr[3] = 0;
            }
        }
    }
}

2. 利用SkiaSharp内置GPU加速滤镜(性能最优)

如果边缘检测逻辑可标准化,直接使用SkiaSharp内置的GPU加速滤镜,性能远超CPU逐像素操作:

canvas.Clear(SKColors.White);

// 创建边缘检测滤镜,参数为模糊半径(控制边缘敏感度)
using var edgeFilter = SKImageFilter.CreateEdgeDetection(1.0f);
using var paint = new SKPaint { ImageFilter = edgeFilter };

// 直接绘制原图并应用滤镜
canvas.DrawBitmap(USerskbitmapNEW, myframe, paint);

若需自定义边缘检测逻辑,可通过SKShader.CreateMatrixConvolution实现自定义卷积核(如Sobel算子),同样享受GPU加速:

// Sobel Y方向卷积核,匹配原代码上下像素对比逻辑
float[] sobelY = new float[]
{
    -1, 0, 1,
    -2, 0, 2,
    -1, 0, 1
};

using var convolutionShader = SKShader.CreateMatrixConvolution(
    new SKSizeI(3, 3),
    sobelY,
    1, // 卷积核权重总和
    0, // 偏移量
    new SKPointI(1, 1), // 锚点
    SKShaderTileMode.Clamp,
    true // 保留Alpha通道
);

using var paint = new SKPaint { Shader = convolutionShader };
canvas.DrawBitmap(USerskbitmapNEW, myframe, paint);

注意事项

  • 操作像素缓冲区前必须调用LockPixels(),完成后调用UnlockPixels(),否则会引发内存泄漏或程序崩溃。
  • 注意像素格式差异(如ARGB8888、RGBA8888),不同格式的字节顺序不同,需对应调整读写索引。
  • 必须处理边界像素越界问题,避免抛出异常。

内容的提问来源于stack exchange,提问作者Michalis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 13:08:11