3

如何使用 GDI+ ImageAttributes(可能是ColorMatrix)旋转图像的色调?

请注意,我想旋转色调,而不是给图像着色。

编辑:通过旋转色调,我的意思是图像中的每种颜色都应转换为不同的颜色,而不是使整个图像成为一种颜色的阴影。

例如,

原文: http: //www.codeguru.com/img/legacy/gdi/Tinter03.jpg

旋转: http: //www.codeguru.com/img/legacy/gdi/Tinter15.jpghttp://www.codeguru.com/img/legacy/gdi/Tinter17.jpg

4

8 回答 8

4

我将这个问题放在一起(在帖子底部链接了带有 c# 项目的 ZIP 文件)。它不使用ImageAttributesor ColorMatrix,但它会按照您的描述旋转色调:

//rotate hue for a pixel
private Color CalculateHueChange(Color oldColor, float hue)
{
    HLSRGB color = new HLSRGB(
        Convert.ToByte(oldColor.R),
        Convert.ToByte(oldColor.G),
        Convert.ToByte(oldColor.B));

    float startHue = color.Hue;
    color.Hue = startHue + hue;
    return color.Color;
}
于 2009-07-03T17:12:03.747 回答
3

我最终将QColorMatrix移植到 C# 并使用它的RotateHue方法。

于 2009-08-25T22:43:56.107 回答
2

你在 CodeProject 上看过这篇文章吗?

从公认的快速浏览页面来看,它看起来像 4D 数学。您可以采用与 2D 或 3D 数学类似的方法来构造矩阵。

获取一系列源“点”——在这种情况下,您需要 4 个——以及相应的目标“点”并生成一个矩阵。然后可以将其应用于任何“点”。

要在 2D 中执行此操作(根据记忆,我可以在此制作完整的咆哮):

源点为 (1, 0) 和 (0, 1)。目标是 (0, -1) 和 (1,0)。您需要的矩阵是:

(0, -1, 0)
(1,  0, 0)
(0,  0, 1)

额外信息用于坐标的“w”值。

将其扩展到 {R, G, B, A, w},您将拥有一个矩阵。取红色(1、0、0、0、w)、绿色(0、1、0、0、w)、蓝色(0、0、1、0、w)和透明(0、0、0、 1, w)。计算出它们在新方案中映射到的颜色,并按如下方式构建矩阵:

(R 1 , G 1 , B 1 , A 1 , 0)
(R 2 , G 2 , B 2 , A 2 , 0)
(R 3 , G 3 , B 3 , A 3 , 0)
(R 4 , G 4 , B 4 , A 4 , 0)
(0, 0, 0, 0, 1)

注意:乘法的顺序(向量 * 矩阵或矩阵 * 向量)将确定转换后的点是垂直还是水平进入该矩阵,因为矩阵乘法是不可交换的。我假设向量 * 矩阵。

于 2009-07-03T15:42:07.007 回答
2

图像处理的矩阵运算

Paul Haeberli
1993 年 11 月


介绍

四乘四矩阵通常用于转换几何以进行 3D 渲染。这些矩阵还可用于转换 RGB 颜色、缩放 RGB 颜色以及控制色调、饱和度和对比度。使用矩阵最重要的优点是可以使用标准矩阵乘法组合任意数量的颜色变换。

请注意,要使这些操作正确,我们必须对线性亮度值进行操作。如果输入图像在非线性亮度空间中,则在使用这些矩阵运算之前,必须将 RGB 颜色转换为线性空间。

颜色转换

RGB 颜色由四乘四矩阵转换,如下所示:

xformrgb(mat, r, g, b, tr, tg, tb)
float mat[4][4];
float r,g,b;
float *tr,*tg,*tb;
{
    *tr = r*mat[0][0] + g*mat[1][0] + b*mat[2][0] + mat[3][0];
    *tg = r*mat[0][1] + g*mat[1][1] + b*mat[2][1] + mat[3][1];
    *tb = r*mat[0][2] + g*mat[1][2] + b*mat[2][2] + mat[3][2];
}

身份

这是单位矩阵:

float mat[4][4] = {
    1.0,    0.0,    0.0,    0.0,
    0.0,    1.0,    0.0,    0.0,
    0.0,    0.0,    1.0,    0.0,
    0.0,    0.0,    0.0,    1.0,
};

通过单位矩阵变换颜色将使它们保持不变。

改变亮度

要缩放 RGB 颜色,使用这样的矩阵:

float mat[4][4] = {
    rscale, 0.0,    0.0,    0.0,
    0.0,    gscale, 0.0,    0.0,
    0.0,    0.0,    bscale, 0.0,
    0.0,    0.0,    0.0,    1.0,
};

其中rscalegscalebscale指定缩放颜色的 r、g 和 b 分量的程度。这可以用来改变图像的色彩平衡。

实际上,这计算:

tr = r*rscale;
tg = g*gscale;
tb = b*bscale;

转换为亮度

要将彩色图像转换为黑白图像,使用此矩阵:

float mat[4][4] = {
    rwgt,   rwgt,   rwgt,   0.0,
    gwgt,   gwgt,   gwgt,   0.0,
    bwgt,   bwgt,   bwgt,   0.0,
    0.0,    0.0,    0.0,    1.0,
};

在哪里

  • rwgt 为 0.3086
  • gwgt 是 0.6094
  • 体重为 0.0820

这是亮度向量。请注意,我们不使用标准 NTSC 权重 0.299、0.587 和 0.114。NTSC 权重仅适用于伽马 2.2 颜色空间中的 RGB 颜色。对于线性 RGB 颜色,上述值更好。

实际上,这计算:

tr = r*rwgt + g*gwgt + b*bwgt;
tg = r*rwgt + g*gwgt + b*bwgt;
tb = r*rwgt + g*gwgt + b*bwgt;

修改饱和度

为了使 RGB 颜色饱和,使用了这个矩阵:

 float mat[4][4] = {
    a,      b,      c,      0.0,
    d,      e,      f,      0.0,
    g,      h,      i,      0.0,
    0.0,    0.0,    0.0,    1.0,
};

其中常数来源于饱和值s,如下所示:

a = (1.0-s)*rwgt + s;
b = (1.0-s)*rwgt;
c = (1.0-s)*rwgt;
d = (1.0-s)*gwgt;
e = (1.0-s)*gwgt + s;
f = (1.0-s)*gwgt;
g = (1.0-s)*bwgt;
h = (1.0-s)*bwgt;
i = (1.0-s)*bwgt + s;

这个饱和度矩阵的一个很好的特性是保持输入 RGB 颜色的亮度。此矩阵还可用于通过指定饱和度值 -1.0 来补充图像中的颜色。

请注意,当 s 设置为 0.0 时,该矩阵正是上述“转换为亮度”矩阵。当 s 设置为 1.0 时,矩阵变为恒等式。所有饱和度矩阵都可以通过在这两个矩阵之间进行插值或外插得到。

这在关于通过插值和外插进行图像处理的注释中进行了更详细的讨论。

将偏移应用到颜色组件

为了抵消图像中颜色的 r、g 和 b 分量,使用此矩阵:

float mat[4][4] = {
    1.0,    0.0,    0.0,    0.0,
    0.0,    1.0,    0.0,    0.0,
    0.0,    0.0,    1.0,    0.0,
    roffset,goffset,boffset,1.0,
};

这可以与颜色缩放一起使用以改变 RGB 图像的对比度。

简单的色相旋转

为了旋转色调,我们对对角线向量 [1.0 1.0 1.0] 执行 RGB 颜色的 3D 旋转。转换矩阵的推导如下所示:

如果我们有函数:

  • identmat(mat): 创建一个单位矩阵。
  • xrotatemat(mat,rsin,rcos):乘以围绕 x(红色)轴旋转的矩阵。
  • yrotatemat(mat,rsin,rcos):乘以围绕 y(绿色)轴旋转的矩阵。
  • zrotatemat(mat,rsin,rcos):乘以围绕 z(蓝色)轴旋转的矩阵。

然后可以像这样构造一个围绕 1.0,1.0,1.0 对角线旋转的矩阵:

首先我们制作一个单位矩阵

identmat(mat);

将灰度向量旋转为正 Z

mag = sqrt(2.0);
xrs = 1.0/mag;
xrc = 1.0/mag;
xrotatemat(mat, xrs, xrc);

mag = sqrt(3.0);
yrs = -1.0/mag;
yrc = sqrt(2.0)/mag;
yrotatemat(mat, yrs, yrc);

旋转色调

zrs = sin(rot*PI/180.0);
zrc = cos(rot*PI/180.0);
zrotatemat(mat, zrs, zrc);

将灰色矢量旋转回原位

yrotatemat(mat, -yrs, yrc);
xrotatemat(mat, -xrs, xrc);

生成的矩阵将旋转输入 RGB 颜色的色调。120.0 度的旋转将准确地将红色映射到绿色,将绿色映射到蓝色,将蓝色映射到红色。这种变换有一个问题,但是输入颜色的亮度没有被保留。这可以通过以下改进来解决:

在保持亮度的同时进行色相旋转

我们制作一个单位矩阵

识别码(mmat);

将灰度向量旋转为正 Z

mag = sqrt(2.0);
xrs = 1.0/mag;
xrc = 1.0/mag;
xrotatemat(mmat, xrs, xrc);
mag = sqrt(3.0);
yrs = -1.0/mag;
yrc = sqrt(2.0)/mag;
yrotatemat(mmat, yrs, yrc);
matrixmult(mmat, mat, mat);

剪切空间使亮度平面水平

xformrgb(mmat,rwgt,gwgt,bwgt,&lx;,&ly;,&lz;);
zsx = lx/lz;
zsy = ly/lz;
zshearmat(mat,zsx,zsy);

旋转色调

zrs = sin(rot*PI/180.0);
zrc = cos(rot*PI/180.0);
zrotatemat(mat,zrs,zrc);

取消剪切空间以放回亮度平面

zshearmat(mat,-zsx,-zsy);

将灰色矢量旋转回原位

yrotatemat(mat, -yrs, yrc);
xrotatemat(mat, -xrs, xrc);

结论

我已经介绍了几种可以应用于 RGB 颜色的矩阵变换。每个颜色变换由一个 4 x 4 矩阵表示,类似于通常用于变换 3D 几何的矩阵。

这些转换允许我们单独调整图像对比度、亮度、色调和饱和度。此外,颜色矩阵变换以类似于几何变换的方式连接。任何操作序列都可以使用矩阵乘法组合成一个矩阵。

于 2020-11-05T13:27:52.797 回答
1

下面的代码构造了一个ColorMatrix用于应用色相偏移的代码。

我的见解是在色调空间发生 60° 偏移时:

  • 红色 --> 绿色
  • 绿色 --> 蓝色
  • 蓝色 --> 红色

实际上是 RGB 空间的 45° 偏移:

在此处输入图像描述

因此,您可以将 120° 偏移的一部分转换为 90° 偏移的一部分。

HueShift参数必须是 之间的值-1..1

例如,为了应用 60° 偏移:

  • 将红色变为黄色,
  • 将黄色变为绿色
  • 将绿色变为青色
  • 将青色更改为蓝色
  • 将蓝色变为洋红色
  • 将洋红色变为红色

你打电话:

var cm = GetHueShiftColorMatrix(60/360); //a value between 0..1

实施

function GetHueShiftColorMatrix(HueShift: Real): TColorMatrix;
var
    theta: Real;
    c, s: Real;
const
    wedge = 120/360;
begin
    if HueShift > 1 then
        HueShift := 0
    else if HueShift < -1 then
        HueShift := 0
    else if Hueshift < 0 then
        HueShift := 1-HueShift;

    if (HueShift >= 0) and (HueShift <= wedge) then
    begin
        //Red..Green
        theta := HueShift / wedge*(pi/2);
        c := cos(theta);
        s := sin(theta);

        cm[0, 0] :=  c; cm[0, 1] :=  0; cm[0, 2] :=  s; cm[0, 3] :=  0; cm[0, 4] := 0;
        cm[1, 0] :=  s; cm[1, 1] :=  c; cm[1, 2] :=  0; cm[1, 3] :=  0; cm[1, 4] := 0;
        cm[2, 0] :=  0; cm[2, 1] :=  s; cm[2, 2] :=  c; cm[2, 3] :=  0; cm[2, 4] := 0;
        cm[3, 0] :=  0; cm[3, 1] :=  0; cm[3, 2] :=  0; cm[3, 3] :=  1; cm[3, 4] := 0;
        cm[4, 0] :=  0; cm[4, 1] :=  0; cm[4, 2] :=  0; cm[4, 3] :=  0; cm[4, 4] := 1;
    end
    else if (HueShift >= wedge) and (HueShift <= (2*wedge)) then
    begin
        //Green..Blue
        theta := (HueShift-wedge) / wedge*(pi/2);
        c := cos(theta);
        s := sin(theta);

        cm[0, 0] :=  0; cm[0, 1] :=  s; cm[0, 2] :=  c; cm[0, 3] :=  0; cm[0, 4] := 0;
        cm[1, 0] :=  c; cm[1, 1] :=  0; cm[1, 2] :=  s; cm[1, 3] :=  0; cm[1, 4] := 0;
        cm[2, 0] :=  s; cm[2, 1] :=  c; cm[2, 2] :=  0; cm[2, 3] :=  0; cm[2, 4] := 0;
        cm[3, 0] :=  0; cm[3, 1] :=  0; cm[3, 2] :=  0; cm[3, 3] :=  1; cm[3, 4] := 0;
        cm[4, 0] :=  0; cm[4, 1] :=  0; cm[4, 2] :=  0; cm[4, 3] :=  0; cm[4, 4] := 1;
    end
    else
    begin
        //Blue..Red
        theta := (HueShift-2*wedge) / wedge*(pi/2);
        c := cos(theta);
        s := sin(theta);

        cm[0, 0] :=  s; cm[0, 1] :=  c; cm[0, 2] :=  0; cm[0, 3] :=  0; cm[0, 4] := 0;
        cm[1, 0] :=  0; cm[1, 1] :=  s; cm[1, 2] :=  c; cm[1, 3] :=  0; cm[1, 4] := 0;
        cm[2, 0] :=  c; cm[2, 1] :=  0; cm[2, 2] :=  s; cm[2, 3] :=  0; cm[2, 4] := 0;
        cm[3, 0] :=  0; cm[3, 1] :=  0; cm[3, 2] :=  0; cm[3, 3] :=  1; cm[3, 4] := 0;
        cm[4, 0] :=  0; cm[4, 1] :=  0; cm[4, 2] :=  0; cm[4, 3] :=  0; cm[4, 4] := 1;
    end;

    Result := cm;
end;

注意:任何代码都会发布到公共领域。无需归属。

于 2013-12-09T22:13:10.483 回答
1

这是一个老问题,但发布的解决方案比我找到的简单答案要复杂得多。

简单的:

  • 没有外部依赖
  • 没有复杂的计算(没有计算出旋转角度,没有应用一些余弦公式)
  • 实际上会旋转颜色!

重申问题:我们需要什么?

我准备了红色的图标。有些区域是透明的,有些区域或多或少是饱和的,但它们都有红色调。我认为它非常适合您的用例。图像可能有其他颜色,它们只会被旋转。

如何表示要应用的色调?最简单的答案是:提供一个Color.

努力寻找解决方案

ColorMatrix表示线性变换。

显然,当颜色为红色时,转换应该是身份。当颜色为绿色时,转换应将红色映射到绿色,绿色到蓝色,蓝色到红色。

执行此操作的 ColorMatrix 是:

0 1 0 0 0
0 0 1 0 0
1 0 0 0 0
0 0 0 1 0
0 0 0 0 1

数学解

“啊哈”的诀窍是认识到矩阵的实际形式是

R G B 0 0
B R G 0 0
G B R 0 0
0 0 0 1 0
0 0 0 0 1

其中 R、G 和 B 只是着色颜色的组成部分!

示例代码

我在https://code.msdn.microsoft.com/ColorMatrix-Image-Filters-f6ed20ae上获取了示例代码。

我对其进行了调整,并在我的项目中实际使用了它:

static class IconTinter
{
    internal static Bitmap TintedIcon(Image sourceImage, Color tintingColor)
    {
        // Following https://code.msdn.microsoft.com/ColorMatrix-Image-Filters-f6ed20ae
        Bitmap bmp32BppDest = new Bitmap(sourceImage.Width, sourceImage.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        float cr = tintingColor.R / 255.0f;
        float cg = tintingColor.G / 255.0f;
        float cb = tintingColor.B / 255.0f;

        // See [Rotate Hue using ImageAttributes in C#](http://stackoverflow.com/a/26573948/1429390)
        ColorMatrix colorMatrix = new ColorMatrix(
            new float[][]
                       {new float[] { cr,  cg,  cb,  0,  0}, 
                        new float[] { cb,  cr,  cg,  0,  0}, 
                        new float[] { cg,  cb,  cr,  0,  0}, 
                        new float[] {  0,   0,   0,  1,  0}, 
                        new float[] {  0,   0,   0,  0,  1}
                       }
                       );

        using (Graphics graphics = Graphics.FromImage(bmp32BppDest))
        {
            ImageAttributes bmpAttributes = new ImageAttributes();
            bmpAttributes.SetColorMatrix(colorMatrix);

            graphics.DrawImage(sourceImage,
                new Rectangle(0, 0, sourceImage.Width, sourceImage.Height),
                0, 0,
                sourceImage.Width, sourceImage.Height,
                GraphicsUnit.Pixel, bmpAttributes);

        }

        return bmp32BppDest;
    }
}

希望这可以帮助。

局限性

  • 请注意,如果您使用太亮的颜色,转换可能会饱和。为保证不饱和,只要 R+G+B<=1,tt 就足够了。
  • 您可以通过将 cr、cg、cb 除以 cr+cg+cb 来标准化转换,但要处理着色颜色为黑色的情况,否则您将除以零。
于 2014-10-26T14:19:53.957 回答
1

我构建了一个用 c# 语言实现 @IanBoid 代码的方法。

    public void setHueRotate(Bitmap bmpElement, float value) {

        const float wedge = 120f / 360;

        var hueDegree = -value % 1;
        if (hueDegree < 0) hueDegree += 1;

        var matrix = new float[5][];

        if (hueDegree <= wedge)
        {
            //Red..Green
            var theta = hueDegree / wedge * (Math.PI / 2);
            var c = (float)Math.Cos(theta);
            var s = (float)Math.Sin(theta);

            matrix[0] = new float[] { c, 0, s, 0, 0 };
            matrix[1] = new float[] { s, c, 0, 0, 0 };
            matrix[2] = new float[] { 0, s, c, 0, 0 };
            matrix[3] = new float[] { 0, 0, 0, 1, 0 };
            matrix[4] = new float[] { 0, 0, 0, 0, 1 };

        } else if (hueDegree <= wedge * 2)
        {
            //Green..Blue
            var theta = (hueDegree - wedge) / wedge * (Math.PI / 2);
            var c = (float) Math.Cos(theta);
            var s = (float) Math.Sin(theta);

            matrix[0] = new float[] {0, s, c, 0, 0};
            matrix[1] = new float[] {c, 0, s, 0, 0};
            matrix[2] = new float[] {s, c, 0, 0, 0};
            matrix[3] = new float[] {0, 0, 0, 1, 0};
            matrix[4] = new float[] {0, 0, 0, 0, 1};

        }
        else
        {
            //Blue..Red
            var theta = (hueDegree - 2 * wedge) / wedge * (Math.PI / 2);
            var c = (float)Math.Cos(theta);
            var s = (float)Math.Sin(theta);

            matrix[0] = new float[] {s, c, 0, 0, 0};
            matrix[1] = new float[] {0, s, c, 0, 0};
            matrix[2] = new float[] {c, 0, s, 0, 0};
            matrix[3] = new float[] {0, 0, 0, 1, 0};
            matrix[4] = new float[] {0, 0, 0, 0, 1};
        }

        Bitmap originalImage = bmpElement;

        var imageAttributes = new ImageAttributes();
        imageAttributes.ClearColorMatrix();
        imageAttributes.SetColorMatrix(new ColorMatrix(matrix), ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        grpElement.DrawImage(
            originalImage, elementArea,
            0, 0, originalImage.Width, originalImage.Height,
            GraphicsUnit.Pixel, imageAttributes
            );
    }
于 2016-05-07T18:31:28.840 回答
0

我想www.aforgenet.com可以提供帮助

于 2009-07-03T15:45:33.753 回答