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
相关产品推荐
相关产品推荐

