【问题标题】:Rotate Hue using ImageAttributes in C#在 C# 中使用 ImageAttributes 旋转色调
【发布时间】:2010-11-07 23:11:30
【问题描述】:

如何使用 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

【问题讨论】:

  • 链接的图像不再存在。理想情况下,您应该在 StackOverflow 上的问题中嵌入图像,而不是在其他地方链接到它们。

标签: c# .net image gdi+ colormatrix


【解决方案1】:

由于 stackoverflow 是一个 wikipedia+reddit,我想在它永远丢失之前保存这些知识。

Matrix Operations for Image Processing

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
  • bwgt 为 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 时,矩阵变为恒等式。所有饱和度矩阵都可以通过在这两个矩阵之间插值或外插得到。

这在Image Processing By Interpolation and Extrapolation.的注释中有更详细的讨论

对颜色组件应用偏移

为了抵消图像中颜色的 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 度的旋转将准确地将红色映射到绿色,将绿色映射到蓝色,将蓝色映射到红色。这种变换有一个问题,但是输入颜色的亮度没有被保留。这可以通过以下改进来解决:

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

我们制作一个单位矩阵

identmat(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 几何的矩阵。

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

【讨论】:

    【解决方案2】:

    我构建了一个用 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
                );
        }
    

    【讨论】:

      【解决方案3】:

      以下代码构造了一个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;
      

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

      【讨论】:

        【解决方案4】:

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

        简单:

        • 没有外部依赖
        • 无需复杂计算(无需计算旋转角度,无需应用余弦公式)
        • 实际上是在旋转颜色!

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

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

        如何表示要应用的色调?最简单的答案是:提供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
        • 您可以通过将 cr、cg、cb 除以 cr+cg+cb 来标准化转换,但要处理着色颜色为黑色的情况,否则您将除以零。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

            I threw this together 回答这个问题(帖子底部链接的带有 c# 项目的 ZIP 文件)。它不使用ImageAttributesColorMatrix,但它会按照您的描述旋转色调:

            //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;
            }
            

            【讨论】:

              【解决方案7】:

              你在 CodeProject 上见过this article 吗?

              从公认的快速浏览页面来看,它看起来像是 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)。计算出它们在新方案中映射到的颜色,并按如下方式构建矩阵:

              (R1, G1, B1, A1, 0)
              (R2, G2, B2, A2, 0)
              (R3, G3, B3, A3, 0)
              (R4, G4, B4, A4, 0)
              (0,   0,   0,   0,   1)

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

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2023-03-26
                  • 2011-04-04
                  • 1970-01-01
                  • 2018-06-21
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多