You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

颜色提取与绘制阶段卡顿问题及进度合并优化咨询

解决颜色提取与绘制的卡顿问题

问题根源

  1. 提取与绘制完全分离:先等待所有颜色提取完成才开始绘制,中间出现空白/延迟
  2. 颜色提取效率低:使用List<Color>.Contains()判断重复颜色,时间复杂度O(n),像素量大时耗时极长
  3. 绘制逻辑阻塞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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:47:33