应使用 Bitmap.LockBits + 指针替代 GetPixel,提速50倍以上;需正确处理 PixelFormat、及时 UnlockBits、统一颜色哈希策略(如 rgb = R<<16|G<<8|B)、按需选择调色板生成算法,并注意 Alpha 和格式兼容性。
用 Bitmap.GetPixel 遍历取色,但别真这么干
它能拿到每个像素的 color,但性能极差——一张 2000×1500 的图要调用 300 万次 getpixel,cpu 占用飙升,实际项目里基本不可用。
真正该做的是:用 Bitmap.LockBits + 指针直接读内存。它把图像数据按行连续铺开,跳过 GDI+ 封装层,速度能快 50 倍以上。
- 必须用
PixelFormat.Format32bppArgb或Format24bppRgb,否则指针偏移算不准 - 记得在
finally里调用UnlockBits,漏掉会锁死资源、后续Bitmap操作全报"Parameter is not valid" - 如果图是 PNG 且含透明通道,
A=0的像素要不要计入调色板得提前想清楚
颜色去重不能只靠 Color.Equals
Color 是结构体,.Equals 对比的是 ARGB 四个字节,看着一样的灰(比如 Color.FromArgb(128,128,128) 和 Color.Gray)可能底层值不同,导致重复存入。
更稳妥的做法是统一转成 RGB 整数或 HSV 分量再比较:
int rgb = color.R << 16 | color.G << 8 | color.B;
- 忽略 Alpha 时直接用
rgb当哈希键;需要保留透明度就用(rgb << 8) | color.A - 别用
HashSet<Color>直接塞——它默认用Color.GetHashCode(),而这个方法对不同构造方式但视觉相同的色值可能返回不同哈希 - 如果目标是“人眼感知相近色合并”,就得上
DeltaE计算,不是简单四舍五入
提取调色板前先决定“要多少种颜色”
原始图动辄几万种颜色,直接全留没意义。常见策略有三类:
-
固定数量截断:用
Octree或Median Cut算法压缩到 16/256 色,适合生成 GIF 或嵌入式显示 -
频次过滤:统计每种颜色出现次数,只取 Top N(比如占比 > 0.1% 的),
Dictionary<int, int>存rgb → count最直接 - 区域代表性采样:把图分块(如 8×8),每块取中位色,避免背景纯色占满列表
没想清楚这点,后面排序、导出、渲染全会踩坑——比如拿 256 色表去渲染只有 12 种主色的 Logo,反而引入噪点。
保存调色板时别忽略格式兼容性
如果下游是网页或设计软件,ColorTranslator.ToHtml(color) 输出 #RRGGBB 最通用;但要注意它自动忽略 Alpha,半透色全变不透明。
若需带透明度,得手拼: $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}" 。
- 导出为 PNG 调色板(
ColorPalette)时,必须确保颜色数 ≤ 256 且顺序与索引一一对应,否则用Bitmap.SetPalette后图片变花 - 写 JSON 给前端用?别直接序列化
Color对象——它有大量冗余字段,只提R/G/B/A四个属性就够了 - 批量处理多图时,注意不同图片的
PixelFormat可能不同(比如 JPG 是 24bpp,PNG 是 32bpp),统一转成Format32bppArgb再处理最省心
调色板不是颜色快照,它是图像语义的压缩表达——少一个阈值判断,多一行指针越界,结果就可能完全跑偏。










