使用C#中的ImageAttributes旋转Hue

时间:2009-07-03 15:16:24

标签: c# .net image gdi+ colormatrix

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

8 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:3)

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

答案 2 :(得分:2)

您是否在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}并且你将有一个矩阵。取4种颜色红色(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)

注意:您进行多重复制的顺序(向量*矩阵或矩阵*向量)将确定变换点是垂直还是水平进入此矩阵,因为矩阵乘法是非交换的。我假设是矢量*矩阵。

答案 3 :(得分:1)

以下代码构造ColorMatrix以应用色调偏移。

我的洞察力是在色调空间60°偏移时:

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

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

enter image description here

因此,您可以将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 :(得分:1)

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

简单:

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

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

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

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

努力寻求解决方案

ColorMatrix代表线性转换。

显然,当Color为红色时,转换应该是标识。 当颜色为绿色时,转换应将红色映射为绿色,绿色映射为蓝色,蓝色映射为红色。

执行此操作的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

数学解决方案

&#34; Aha&#34;技巧是要认识到矩阵的实际形式是

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

希望这有帮助。

限制

  • 请注意,如果使用太亮的颜色,转换可能会饱和。为了保证不饱和,tt足以使R + G + B <= 1。
  • 您可以通过将cr,cg,cb除以cr + cg + cb来标准化转换,但要处理着色颜色为黑色或您将除以零的情况。

答案 5 :(得分:0)

我认为www.aforgenet.com可以提供帮助

答案 6 :(得分:0)

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

答案 7 :(得分:0)

由于stackoverflow是Wikipedia + reddit,因此我想保留此知识,直到它永远消失。

Matrix Operations for Image Processing

Paul Haeberli
1993年11月


简介

4×4矩阵通常用于转换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权重仅适用于gamma 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×4矩阵表示,类似于通常用于转换3D几何的矩阵。

这些转换使我们可以分别调整图像的对比度,亮度,色调和饱和度。此外,颜色矩阵变换的连接方式类似于几何变换。可以使用矩阵乘法将任何运算序列组合成一个矩阵。