在位图中更改色相/饱和度/亮度的更快算法

3
我正在尝试过滤位图图像以增加或减少色相、饱和度和亮度值。我的代码运行得很好,但速度较慢。我在内存中锁定了两个位图,原始源和当前目标。用户可以移动各种滑动条控件来修改每个值,然后将其转换为HSL值。例如,滑动条上的值对应于-1.0到1.0的范围。每次触发滑动条值更改的事件时,我都会运行一个函数,该函数锁定目标位图并使用源位图应用HSL值,然后将结果存储在目标位图中。完成后,我解锁目标位图并在屏幕上绘制图像。之前,由于我进行的是按字节操作,所以我使用了查找表来进行其他过滤器。然而,我不知道如何使用HSL来应用它。这是我正在使用的代码:
byte red, green, blue;

for (int i = 0; i < sourceBytes.Length; i += 3)
{
    blue = sourceBytes[i];
    green = sourceBytes[i + 1];
    red = sourceBytes[i + 2];

    Color newColor = Color.FromArgb(red, green, blue);

    if (ModifyHue)
        newColor = HSL.ModifyHue(newColor, Hue);

    if (ModifySaturation)
        newColor = HSL.ModifySaturation(newColor, Saturation);

    if (ModifyLightness)
        newColor = HSL.ModifyBrightness(newColor, Lightness);

    destBytes[i] = newColor.B;
    destBytes[i + 1] = newColor.G;
    destBytes[i + 2] = newColor.R;
}

这是我的修改亮度函数:

public static Color ModifyBrightness(Color color, double brightness)
{
    HSL hsl = FromRGB(color);
    hsl.L *= brightness;
    return hsl.ToRGB();
}

简单来说,如果亮度滑块在正中间,其值为0,我在传递到函数中时将其转换为“1.0”,这样它将乘以1.0,这意味着它不会改变亮度。如果将滑块拖到最右边,它的值将为100,这将导致修饰符为2.0,所以我将光度值乘以2.0来使其加倍。


1
我会做的第一件事是缓存结果,并使用不安全的代码来更快地访问数组。 - SimpleVar
+1. 有这样的微码,数组访问完全会让你崩溃。每次访问都要检查最小/最大值。开心地使用不安全指针代码吧。同时将字节转换为具有所有值的结构体。 - TomTom
你们有这方面的文档参考吗?据我所知,当数组是指针时,数组语法等同于指针语法,不是吗?无论如何,没有最小/最大限制。 - Rotem
@OP 如果你只需要修改HSL,那么你可以使用带有ColorMatrix的ImageAttributes,它将比你当前的代码执行速度更快。 - Rotem
@Moozhe 我知道这不是你想听到的,但是使用ImageAttributes类可以轻松实现你提到的所有效果,只需一次调用DrawImage即可。我不认为有必要重新发明轮子。以下是一些链接:http://www.graficaobscura.com/matrix/index.html http://www.codeproject.com/Articles/3772/ColorMatrix-Basics-Simple-Image-Color-Adjustment - Rotem
显示剩余3条评论
2个回答

6

我最终研究了ImageAttributes和ColorMatrix,发现它的表现非常出色。

以下是我为饱和度和亮度滤镜实现的方法:

// Luminance vector for linear RGB
const float rwgt = 0.3086f;
const float gwgt = 0.6094f;
const float bwgt = 0.0820f;

private ImageAttributes imageAttributes = new ImageAttributes();
private ColorMatrix colorMatrix = new ColorMatrix();
private float saturation = 1.0f;
private float brightness = 1.0f;

protected override void OnPaint(object sender, PaintEventArgs e)
{
    base.OnPaint(sender, e);

    e.Graphics.DrawImage(_bitmap, BitmapRect, BitmapRect.X, BitmapRect.Y, BitmapRect.Width, BitmapRect.Height, GraphicsUnit.Pixel, imageAttributes);
}

private void saturationTrackBar_ValueChanged(object sender, EventArgs e)
{
    saturation = 1f - (saturationTrackBar.Value / 100f);

    float baseSat = 1.0f - saturation;

    colorMatrix[0, 0] = baseSat * rwgt + saturation;
    colorMatrix[0, 1] = baseSat * rwgt;
    colorMatrix[0, 2] = baseSat * rwgt;
    colorMatrix[1, 0] = baseSat * gwgt;
    colorMatrix[1, 1] = baseSat * gwgt + saturation;
    colorMatrix[1, 2] = baseSat * gwgt;
    colorMatrix[2, 0] = baseSat * bwgt;
    colorMatrix[2, 1] = baseSat * bwgt;
    colorMatrix[2, 2] = baseSat * bwgt + saturation;

    imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

    Invalidate();
}

private void brightnessTrackBar_ValueChanged(object sender, EventArgs e)
{
    brightness = 1f + (brightnessTrackBar.Value / 100f);

    float adjustedBrightness = brightness - 1f;

    colorMatrix[4, 0] = adjustedBrightness;
    colorMatrix[4, 1] = adjustedBrightness;
    colorMatrix[4, 2] = adjustedBrightness;

    imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

    Invalidate();
}

1
有趣的部分是你可以使用单个矩阵计算许多不同的效果,然后将这些矩阵相乘以合并这些效果。 - Rotem

1
你需要对你的应用程序进行分析,找出问题所在。
随机建议:
  • 使用每像素32位格式以避免未对齐读取。(并将整个32位作为单个操作读取)
  • 避免多次RGB <-> HSL转换

我对比度、RGB亮度、伽马、反转等都使用相同的代码,而且速度非常快。因此,RGB/HSL转换是主要的瓶颈。也许你可以详细说明一下我如何避免多重转换。我考虑过使用查找表,但如何为双精度值使用LUT呢?对于字节值,这很容易,因为只有256种可能性。 - Trevor Elliott
@Moozhe,你的代码一开始就进行了最多两次无用的转换(如果你需要更改所有三个 HSL 值)。否则,请查看你的 FromRGB 代码并进行优化。在最坏的情况下,预计算表中的 16M 整数值也不是世界末日... - Alexei Levenkov
@AlexeiLevenkov 实际上是16.7m种颜色* 4个字节= 67MB,不太可行。更不用说这些值可能比图像中实际存在的值更多,使得LUT生成比计算更慢。 - Rotem
@Rotem 67Mb是现代相机中一张图像的大小,因此每个项目使用的内存量并不罕见。OP应该能够测量并查看在这种情况下CPU与内存是否适用于该特定情况。 - Alexei Levenkov
@AlexeiLevenkov,每次用户更改参数(交互式),都需要重新计算。我们不是在讨论使用相同参数处理多个图像,而是在讨论使用多个参数处理同一图像。 - Rotem
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接