颜色提取与绘制阶段卡顿问题及进度合并优化咨询
解决颜色提取与绘制的卡顿问题
问题根源
- 提取与绘制完全分离:先等待所有颜色提取完成才开始绘制,中间出现空白/延迟
- 颜色提取效率低:使用
List<Color>.Contains()判断重复颜色,时间复杂度O(n),像素量大时耗时极长 - 绘制逻辑阻塞UI:在
OnPaint事件中循环绘制所有颜色+频繁更新进度条,直接阻塞UI线程导致卡顿
优化方案
1. 边提取边绘制,实时更新UI
将颜色提取与绘制改为增量式流程:后台每提取到一个新颜色,立即通知UI线程绘制该颜色,无需等待全部提取完成。
2. 优化颜色提取性能
用HashSet<Color>替代List<Color>存储唯一颜色,HashSet.Contains()的时间复杂度为O(1),大幅降低重复判断的耗时。
3. 合并进度展示
统一计算提取与绘制的整体进度,避免进度条出现断层。
4. 优化UI绘制逻辑
避免在OnPaint中做循环遍历,仅绘制新增的颜色,减少UI线程负担。
具体代码修改
第一步:新增事件与线程安全集合
在类中添加用于通知UI的事件,以及线程安全的颜色存储集合:
// 通知UI新增了一个颜色 private event Action<Color> ColorExtracted; // 通知UI总颜色数已确定(提取完成时触发) private event Action<int> TotalColorsDetermined; // 线程安全的增量颜色列表 private readonly List<Color> _incrementalColors = new List<Color>(); private readonly object _colorLock = new object(); private int _totalColors;
第二步:修改颜色提取方法
优化提取逻辑,改用HashSet提升效率,新增颜色时立即通知UI:
private async Task ExtractUniqueColorsAsync(Image image) { if (image == null) return; var uniqueColors = new HashSet<Color>(); Bitmap bitmap = new Bitmap(image); int width = bitmap.Width; int height = bitmap.Height; int totalPixels = width * height; int processedPixels = 0; Rectangle rect = new Rectangle(0, 0, width, height); BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); await Task.Run(() => { unsafe { byte* ptr = (byte*)bitmapData.Scan0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int offset = y * bitmapData.Stride + x * 4; byte blue = ptr[offset]; byte green = ptr[offset + 1]; byte red = ptr[offset + 2]; byte alpha = ptr[offset + 3]; Color pixelColor = Color.FromArgb(alpha, red, green, blue); // 用HashSet快速判断重复 if (uniqueColors.Add(pixelColor)) { // 新增颜色时通知UI,确保在UI线程执行 this.Invoke(() => { lock (_colorLock) { _incrementalColors.Add(pixelColor); } ColorExtracted?.Invoke(pixelColor); // 触发重绘 this.Invalidate(); }); } processedPixels++; int extractProgress = (int)((double)processedPixels / totalPixels * 80); // 更新提取阶段进度(占总进度的80%) UpdateProgressBar(extractProgress); } } } }); bitmap.UnlockBits(bitmapData); // 提取完成,通知总颜色数 this.Invoke(() => { _totalColors = uniqueColors.Count; TotalColorsDetermined?.Invoke(_totalColors); }); }
第三步:修改绘制与进度更新逻辑
简化绘制方法,仅绘制已提取的颜色,合并提取与绘制的进度:
private void DrawExtractedColorsProgressively(Graphics g, Rectangle circleRect) { lock (_colorLock) { if (_incrementalColors.Count == 0) return; float angle = 360f / _totalColors; for (int i = 0; i < _incrementalColors.Count; i++) { using (SolidBrush brush = new SolidBrush(_incrementalColors[i])) { float startAngle = i * angle; g.FillPie(brush, circleRect, startAngle, angle); } } // 计算绘制阶段进度(占总进度的20%) int drawProgress = (int)((double)_incrementalColors.Count / _totalColors * 20); // 总进度=提取进度+绘制进度 UpdateProgressBar(80 + drawProgress); } } private void UpdateProgressBar(int progressPercentage) { if (progressBar == null) return; if (progressBar.InvokeRequired) { progressBar.Invoke((MethodInvoker)delegate { progressBar.Value = Math.Clamp(progressPercentage, 0, 100); }); } else { progressBar.Value = Math.Clamp(progressPercentage, 0, 100); } }
第四步:修改OnPaint事件
保持原有UI绘制逻辑,仅调用优化后的绘制方法:
protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); int borderWidth = 2; Rectangle borderRect = new Rectangle(borderWidth, borderWidth, Width - 2 * borderWidth, Height - 2 * borderWidth); using Pen p = new Pen(Color.Red, borderWidth); e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; e.Graphics.DrawEllipse(p, borderRect); if (displayedImage != null) { using GraphicsPath path = new GraphicsPath(); path.AddEllipse(borderRect); using Region region = new Region(path); e.Graphics.Clip = region; e.Graphics.DrawImage(displayedImage, borderRect); e.Graphics.ResetClip(); } DrawExtractedColorsProgressively(e.Graphics, borderRect); }
第五步:初始化事件绑定
在类的构造函数或初始化方法中绑定事件:
public YourClassName() { InitializeComponent(); // 绑定颜色新增事件,触发重绘 ColorExtracted += (color) => this.Invalidate(); // 提取完成后确保最后一次重绘 TotalColorsDetermined += (total) => this.Invalidate(); }
额外优化建议
- 对提取的颜色进行降采样:比如每隔N个像素采样一次,减少计算量(适合大尺寸图片)
- 预计算绘制角度:避免每次OnPaint都重新计算
360f / _totalColors - 使用
DoubleBuffered = true:开启双缓冲,减少绘制时的闪烁
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

