我写过的十二行不安全代码
做个证件照处理的小工具,纯托管的 Bitmap.GetPixel/SetPixel 循环灰度化,一张 3000x4000 的图要 400 毫秒,用户体感就是"卡死了"。换 LockBits 加指针直取像素数据,压到 90 毫秒。核心就十二行:
var bmp = (Bitmap)img;
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var data = bmp.LockBits(rect, ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
unsafe
{
byte* p = (byte*)data.Scan0;
int stride = data.Stride;
for (int y = 0; y < rect.Height; y++)
{
byte* row = p + y * stride;
for (int x = 0; x < rect.Width; x++)
{
byte g = (byte)(row[x * 3] * 0.11f
+ row[x * 3 + 1] * 0.59f
+ row[x * 3 + 2] * 0.30f);
row[x * 3] = row[x * 3 + 1] = row[x * 3 + 2] = g;
}
}
}
bmp.UnlockBits(data);
几个细节值得记。Stride 不一定等于宽度乘 3,内存对齐会补空隙,逐行扫描必须用 stride 定位行首,跨行直接当一维数组扫就会踩到补位字节。24 位格式每个像素 BGR 顺序(不是 RGB),搞反了灰度会偏色,肉眼看是"发蓝"。加权系数用的是感知亮度权重,直接取平均会偏暗。
unsafe 不是洪水猛兽,它是被 GC 卡住时的那把梯子。但用之前先确认三件事:数据边界自己负责、指针生命周期不逃逸出代码块、以及——先试试 Span<T> 能不能解决。这个场景后来用 Marshal.Copy 加 Span 重写了一版,托管代码也到了 110 毫秒,只慢 20%,多数项目其实够用。