位图的更快对比度算法

本文关键字:对比度 算法 位图 | 更新日期: 2023-09-27 18:21:13

我有一个带有轨迹条滑块控件的工具,用于调整图像的亮度、对比度、伽玛等。

当用户拖动滑块时,我正在尝试实时更新我的图像。亮度和伽玛算法是可接受的速度(约170ms)。但对比度算法大约为380ms。

基本上,我的表单是一个带有滑块的工具窗口。每次更新图像时,它都会向父对象发送一个事件,以重新绘制新图像。工具窗口将原始未修改的图像锁定在内存中,因此我始终可以访问其中的字节。因此,基本上,每次滑块(如对比度滑块)的ValueChanged事件更改时,我都会这样做。

  • 工作(目标)位图的LockBits格式为24bppRgb(原始位图格式为32bppPArgb)
  • Marshal。将位复制到byte[]数组
  • 检查我正在执行的操作(选择了哪个滑块)
  • 使用以下代码进行对比:

代码:

double newValue = 0;
double c = (100.0 + contrast) / 100.0;
c *= c;
for (int i = 0; i < sourcePixels.Length; i++)
{
    newValue = sourcePixels[i];
    newValue /= 255.0;
    newValue -= 0.5;
    newValue *= c;
    newValue += 0.5;
    newValue *= 255;
    if (newValue < 0)
        newValue = 0;
    if (newValue > 255)
        newValue = 255;
    destPixels[i] = (byte)newValue;
}

我读过一篇关于使用整数而不是浮点值来提高对比度的文章,但我再也找不到那篇文章了。

我尝试使用不安全的代码(指针),但实际上注意到速度下降了。我认为这是因为代码使用嵌套的for循环来迭代x和y,而不是单个循环。

位图的更快对比度算法

根据运行该程序的机器的不同,您的技术可能相当缓慢。如果你使用的是没有FPU的ARM系统,那么每一项操作都需要相当长的时间。由于对每个字节应用相同的操作,因此更快的技术是为对比度级别创建一个256项的查找表,然后通过该表转换每个图像字节。然后你的循环看起来像:

byte contrast_lookup[256];
double newValue = 0;
double c = (100.0 + contrast) / 100.0;
c *= c;
for (int i = 0; i < 256; i++)
{
    newValue = (double)i;
    newValue /= 255.0;
    newValue -= 0.5;
    newValue *= c;
    newValue += 0.5;
    newValue *= 255;
    if (newValue < 0)
        newValue = 0;
    if (newValue > 255)
        newValue = 255;
    contrast_lookup[i] = (byte)newValue;
}
for (int i = 0; i < sourcePixels.Length; i++)
{
    destPixels[i] = contrast_lookup[sourcePixels[i]];
}

@BitBank按要求回答了您的问题,我想补充一点,如果您在追求性能,您应该考虑获取像素数据并随后进行设置的代码。

使用指针的完整工作代码(支持for循环代码上的@BitBank):

private unsafe void ApplyContrast(double contrast, Bitmap bmp)
{
    byte[] contrast_lookup = new byte[256];
    double newValue = 0;
    double c = (100.0 + contrast) / 100.0;
    c *= c;
    for (int i = 0; i < 256; i++)
    {
        newValue = (double)i;
        newValue /= 255.0;
        newValue -= 0.5;
        newValue *= c;
        newValue += 0.5;
        newValue *= 255;
        if (newValue < 0)
            newValue = 0;
        if (newValue > 255)
            newValue = 255;
        contrast_lookup[i] = (byte)newValue;
    }
    var bitmapdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 
        System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    int PixelSize = 4;
    for (int y = 0; y < bitmapdata.Height; y++)
    {
        byte* destPixels = (byte*)bitmapdata.Scan0 + (y * bitmapdata.Stride);
        for (int x = 0; x < bitmapdata.Width; x++)
        {
            destPixels[x * PixelSize] = contrast_lookup[destPixels[x * PixelSize]]; // B
            destPixels[x * PixelSize + 1] = contrast_lookup[destPixels[x * PixelSize + 1]]; // G
            destPixels[x * PixelSize + 2] = contrast_lookup[destPixels[x * PixelSize + 2]]; // R
            //destPixels[x * PixelSize + 3] = contrast_lookup[destPixels[x * PixelSize + 3]]; //A
        }
    }
    bmp.UnlockBits(bitmapdata);
}

如果您使用Marshal.Copy设置图像像素数据,您会发现它的性能更好。

这应该比您当前的代码执行得更快,而且还可以减少内存占用,这在处理非常大的图像时是很好的。