我正在尝试创建一个类,可以以尽可能小的延迟实时重新着色高分辨率图像。 我决定使用颜色矩阵,因为推荐的使用查找表进行像素级颜色替换的方法会受到限制并且计算量巨大。 然而,据我所知,以前没有人真正做到过这一点(我觉得很难相信)。 我使用各种资源拼凑出我的最佳猜测,其中最有用的是:
https://graficaobscura.com/matrix/index.html https://learn.microsoft.com/en-us/windows/win32/direct2d/hue-rotate?redirectedfrom=MSDN 在 C# 中旋转色调
问题是我想出的代码不能完全正常工作,不幸的是我无法仅通过查看颜色来判断出了什么问题,使得调试几乎不可能。
这是我目前的重新着色课程:
public class Recolorer
{
const float LumR = 0.2126f;
const float LumG = 0.7152f;
const float LumB = 0.0722f;
public bool UseSat = true;
public bool UseLum = false;
public bool UseBri = true;
public Bitmap Recolor(Bitmap bm, Color BaseColor, Color NewColor)
{
float r = 1;
float d = 0;
d = BrightnessDelta(BaseColor, NewColor);
float[][] H = HueMatrix(NewColor, BaseColor.GetHue());
float[][] S = SaturationMatrix(NewColor);
float[][] L = LuminosityMatrix();
float[][] B = LightnessMatrix(r, d);
float[][] T = H;
if (UseSat)
T = Multiply(S, T);
if (UseLum)
T = Multiply(L, T);
if (UseBri)
T = Multiply(B, T);
ColorMatrix cm = new ColorMatrix(T);
ImageAttributes ia = new ImageAttributes();
ia.SetColorMatrix(cm);
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
Bitmap temp = new Bitmap(bm.Width, bm.Height);
Graphics g = Graphics.FromImage(temp);
g.DrawImage(bm, rect, 0, 0, bm.Width, bm.Height, GraphicsUnit.Pixel, ia);
return temp;
}
public float[][] LuminosityMatrix(float B = 1f)
{
return new float[][] {
new float[] {LumR * B, LumR * B, LumR * B, 0, 0},
new float[] {LumG * B, LumG * B, LumG * B, 0, 0},
new float[] {LumB * B, LumB * B, LumB * B, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};
}
public float[][] SaturationMatrix(Color c)
{
float s = c.GetSaturation();
float sr = (1f - s) * LumR;
float sg = (1f - s) * LumG;
float sb = (1f - s) * LumB;
return new float[][] {
new float[] {sr+s, sr, sr, 0, 0},
new float[] {sg, sg+s, sg, 0, 0},
new float[] {sb, sb, sb+s, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};
}
public float[][] HueMatrix(Color c, float x = 0)
{
float angle = c.GetHue() - x;
angle /= 180f;
float cos = (float)Math.Cos(angle * Math.PI);
float sin = (float)Math.Sin(angle * Math.PI);
float A00 = LumR + (1 - LumR) * cos - LumR * sin;
float A01 = LumR - LumR * cos + 0.413f * sin;
float A02 = LumR - LumR * cos - (1 - LumR) * sin;
float A10 = LumG - LumG * cos - LumG * sin;
float A11 = LumG + (1 - LumG) * cos + 0.140f * sin;
float A12 = LumG - LumG * cos + LumG * sin;
float A20 = LumB - LumB * cos + (1 - LumB) * sin;
float A21 = LumB - LumB * cos - 0.283f * sin;
float A22 = LumB + (1 - LumB) * cos + LumB * sin;
return new float[][] {
new float[] { A00, A01, A02, 0, 0 },
new float[] { A10, A11, A12, 0, 0 },
new float[] { A20, A21, A22, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 0, 0, 1 }
};
}
public float GetBrightness(Color c)
{
return (((float)c.R * LumR / 255f) + ((float)c.G * LumG / 255f) + ((float)c.B * LumB / 255f));
}
public float BrightnessRatio(Color From, Color To)
{
return (GetBrightness(To) / GetBrightness(From));
}
public float BrightnessDelta(Color From, Color To)
{
return ((GetBrightness(To) - GetBrightness(From)) / 2f);
}
public float[][] LightnessMatrix(float f = 1, float l = 0)
{
return new float[][] {
new float[] { f, 0, 0, 0, 0 },
new float[] { 0, f, 0, 0, 0 },
new float[] { 0, 0, f, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { l, l, l, 0, 1 }
};
}
public float[][] Multiply(float[][] A, float[][] B)
{
const int size = 5;
float temp = 0;
float[][] C = new float[size][];
for (int sigh = 0; sigh<size; sigh++)
C[sigh] = new float[size];
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
temp = 0;
for (int k = 0; k < size; k++)
{
temp += A[i][k] * B[k][j];
}
C[i][j] = temp;
}
}
return C;
}
}
您可能会说,我很难保持亮度/明度/光度/亮度的直线,所以我很难弄清楚使用哪个以及如何应用它,但我尝试的任何方法似乎都无法获得正确的亮度。 正如下面的两个示例所示,重新着色的图像最终总是会被冲掉并具有压缩的亮度。 色调似乎工作正常,因为尝试将图像更改为红色会使其变红,将其更改为绿色会使其变绿,对于我尝试过的每种颜色都是如此。
(裙子原色是蓝色) 比较非常亮的红色和非常深的红色 比较非常亮的绿色和非常深的绿色
我认为仅使用颜色矩阵来旋转色调是不可能的。这些矩阵代表“线性变换”。 RGB -> HSL 不是线性变换,也不存在任何对应于任意色调旋转的线性 RGB->RGB 变换。 我解决这个问题的方法是使用
lockbits来直接访问像素数据。这避免了昂贵的操作(如 GetPixel 或创建图形对象)的需要。 然后您可以为每个像素实现转换代码。您可以尝试尽可能优化代码。
SIMD和/或多线程可能会有所帮助。不要低估现代 CPU 运行优化良好的代码的速度。 我也不会立即放弃查找表,对于 24 位 RGB,它应该约为 50Mb,因此其中大部分应该适合最后一级缓存。内存访问很慢,但分支也很慢,所以我不确定哪个选项会更快。
您还可以考虑使用
shader