GetPixel 因坐标校验、颜色转换和 GDI+ 封装开销而极慢;应改用 LockBits + Marshal.Copy 或 unsafe 指针直接操作内存,注意 stride 对齐与像素格式匹配。

GetPixel 为什么慢到不能用
直接调用 Bitmap.GetPixel 读取单个像素在循环中会严重拖慢性能,因为它每次都会做坐标校验、颜色空间转换和 GDI+ 封装开销。1000×1000 的图片遍历一次可能耗时几百毫秒,而实际需求常是毫秒级处理。
真正可行的方式是绕过封装,直接操作内存。核心思路:用 Bitmap.LockBits 获取原始像素内存指针,再用 Marshal.Copy 或 unsafe 指针读写。
- 必须指定
PixelFormat.Format24bppRgb或Format32bppArgb,避免格式不匹配导致字节错位 - 锁定区域要用
Rectangle.FromLTRB(0, 0, bmp.Width, bmp.Height),别漏掉右下边界 - 解锁前必须调用
UnlockBits,否则资源泄漏,后续Dispose可能抛InvalidOperationException
用 LockBits + Marshal.Copy 读取整张图的 RGB 值
这是最稳妥、无需 unsafe 且兼容 .NET Framework / .NET Core 的方式。适用于需要批量分析像素(如灰度化、阈值分割)的场景。
关键点在于理解 stride:每行字节数 = (width * bytesPerPixel + 3) & ~3(4 字节对齐)。例如 3 像素宽的 24bpp 图,stride 是 12 而不是 9。
var bmp = new Bitmap("input.jpg");
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var bits = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
<p>// 分配缓冲区:按 stride <em> height,不是 width </em> height <em> 3
var bytes = bits.Stride </em> bits.Height;
var rgbValues = new byte[bytes];
Marshal.Copy(bits.Scan0, rgbValues, 0, bytes);</p><p>// 逐像素读取:y 行、x 列 → 索引 = y <em> stride + x </em> 3
for (int y = 0; y < bmp.Height; y++) {
for (int x = 0; x < bmp.Width; x++) {
int i = y <em> bits.Stride + x </em> 3;
byte b = rgbValues[i];
byte g = rgbValues[i + 1];
byte r = rgbValues[i + 2];
// r/g/b 即当前像素 RGB 值
}
}
bmp.UnlockBits(bits);unsafe 模式下用指针直接访问(最快但需开启允许)
如果项目已启用 AllowUnsafeBlocks(csproj 中加 <allowunsafeblocks>true</allowunsafeblocks>),指针访问比 Marshal.Copy 快 2–3 倍,尤其适合实时图像处理。
-
PixelFormat.Format32bppArgb更常用,因为每像素 4 字节对齐,stride == width * 4,无需额外计算偏移 - 注意 BGRA 顺序:
ptr[0]是蓝,ptr[1]是绿,ptr[2]是红,ptr[3]是 Alpha - 务必用
fixed固定数组或直接用Scan0指针,别让 GC 移动内存
unsafe {
var ptr = (byte*)bits.Scan0.ToPointer();
for (int y = 0; y < bmp.Height; y++) {
for (int x = 0; x < bmp.Width; x++) {
int i = y * bits.Stride + x * 4;
byte b = ptr[i];
byte g = ptr[i + 1];
byte r = ptr[i + 2];
byte a = ptr[i + 3];
}
}
}修改像素后保存要注意 Alpha 和格式兼容性
改完像素数据后调用 UnlockBits,再用 Save 输出。但常见坑是:原图是 PNG(带 Alpha)却用 Format24bppRgb 锁定,结果透明通道丢失;或者保存为 JPEG 却保留了 Alpha,导致报 Parameter is not valid。
- 读取时用什么
PixelFormat,写回时保持一致;若要保留 Alpha,必须选Format32bppArgb或Format32bppPArgb - JPEG 不支持透明,保存前确保 Alpha 全为 255,或转成
Format24bppRgb再保存 - 调用
bmp.Save(...)前,确认 bitmap 没被其他代码 dispose ——ObjectDisposedException很容易在这里出现
实际处理高分辨率图时,内存布局和格式匹配比算法本身更容易出错。别省略 stride 计算,也别假设像素排列和你想象的一样。


















