在 C# 中使用 ImageAttributes 旋转色调

发布于 2024-07-26 06:22:23 字数 636 浏览 3 评论 0原文

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

How can I rotate the hue of an image using GDI+'s ImageAttributes (and presumably ColorMatrix)?

Note that I want to rotate the hue, not tint the image.

EDIT: By rotating the hue, I mean that each color in the image should be shifted to a different color, as opposed to making the entire image a shade of one color.

For example,

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

Rotated: http://www.codeguru.com/img/legacy/gdi/Tinter15.jpg or http://www.codeguru.com/img/legacy/gdi/Tinter17.jpg

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

暮倦 2024-08-02 06:22:24

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

I ended up porting QColorMatrix to C# and using its RotateHue method.

苏辞 2024-08-02 06:22:24

图像处理的矩阵运算

Paul Haeberli
1993 年 11 月


简介

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

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

颜色变换

RGB 颜色通过 4 x 4 矩阵进行变换,如下所示:

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 时,矩阵变为恒等式。 所有饱和矩阵都可以通过在这两个矩阵之间进行内插或外推来导出。

通过插值和外插进行图像处理。

将偏移应用于颜色分量

要偏移图像中颜色的 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 几何形状的矩阵。

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

Matrix Operations for Image Processing

Paul Haeberli
Nov 1993


Introduction

Four by four matrices are commonly used to transform geometry for 3D rendering. These matrices may also be used to transform RGB colors, to scale RGB colors, and to control hue, saturation and contrast. The most important advantage of using matrices is that any number of color transformations can be composed using standard matrix multiplication.

Please note that for these operations to be correct, we really must operate on linear brightness values. If the input image is in a non-linear brightness space RGB colors must be transformed into a linear space before these matrix operations are used.

Color Transformation

RGB colors are transformed by a four by four matrix as shown here:

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

The Identity

This is the identity matrix:

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

Transforming colors by the identity matrix will leave them unchanged.

Changing Brightness

To scale RGB colors a matrix like this is used:

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

Where rscale, gscale, and bscale specify how much to scale the r, g, and b components of colors. This can be used to alter the color balance of an image.

In effect, this calculates:

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

Converting to Luminance

To convert a color image into a black and white image, this matrix is used:

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

Where

  • rwgt is 0.3086
  • gwgt is 0.6094
  • bwgt is 0.0820

This is the luminance vector. Notice here that we do not use the standard NTSC weights of 0.299, 0.587, and 0.114. The NTSC weights are only applicable to RGB colors in a gamma 2.2 color space. For linear RGB colors the values above are better.

In effect, this calculates:

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

Modifying Saturation

To saturate RGB colors, this matrix is used:

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

Where the constants are derived from the saturation value s as shown below:

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;

One nice property of this saturation matrix is that the luminance of input RGB colors is maintained. This matrix can also be used to complement the colors in an image by specifying a saturation value of -1.0.

Notice that when s is set to 0.0, the matrix is exactly the "convert to luminance" matrix described above. When s is set to 1.0 the matrix becomes the identity. All saturation matrices can be derived by interpolating between or extrapolating beyond these two matrices.

This is discussed in more detail in the note on Image Processing By Interpolation and Extrapolation.

Applying Offsets to Color Components

To offset the r, g, and b components of colors in an image this matrix is used:

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

This can be used along with color scaling to alter the contrast of RGB images.

Simple Hue Rotation

To rotate the hue, we perform a 3D rotation of RGB colors about the diagonal vector [1.0 1.0 1.0]. The transformation matrix is derived as shown here:

If we have functions:

  • identmat(mat): that creates an identity matrix.
  • xrotatemat(mat,rsin,rcos): that multiplies a matrix that rotates about the x (red) axis.
  • yrotatemat(mat,rsin,rcos): that multiplies a matrix that rotates about the y (green) axis.
  • zrotatemat(mat,rsin,rcos): that multiplies a matrix that rotates about the z (blue) axis.

Then a matrix that rotates about the 1.0,1.0,1.0 diagonal can be constructed like this:

First we make an identity matrix

identmat(mat);

Rotate the grey vector into positive 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);

Rotate the hue

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

Rotate the grey vector back into place

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

The resulting matrix will rotate the hue of the input RGB colors. A rotation of 120.0 degrees will exactly map Red into Green, Green into Blue and Blue into Red. This transformation has one problem, however, the luminance of the input colors is not preserved. This can be fixed with the following refinement:

Hue Rotation While Preserving Luminance

We make an identity matrix

identmat(mmat);

Rotate the grey vector into positive 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);

Shear the space to make the luminance plane horizontal

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

Rotate the hue

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

Unshear the space to put the luminance plane back

zshearmat(mat,-zsx,-zsy);

Rotate the grey vector back into place

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

Conclusion

I've presented several matrix transformations that may be applied to RGB colors. Each color transformation is represented by a 4 by 4 matrix, similar to matrices commonly used to transform 3D geometry.

These transformations allow us to adjust image contrast, brightness, hue and saturation individually. In addition, color matrix transformations concatenate in a way similar to geometric transformations. Any sequence of operations can be combined into a single matrix using matrix multiplication.

浅黛梨妆こ 2024-08-02 06:22:24

您看过 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},您将得到一个矩阵。 取 4 种颜色:红色 (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)

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

Have you seen this article on CodeProject?

From an admittedly quick look at the page it looks like 4D maths. You can adopt a similar approach to contstructing matrices as you would for 2D or 3D maths.

Take a series of source "points" - in this case you'll need 4 - and the corresponding target "points" and generate a matrix. This can then be applied to any "point".

To do this in 2D (from memory so I could have made a complete howler in this):

Source points are (1, 0) and (0, 1). The targets are (0, -1) and (1,0). The matrix you need is:

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

Where the extra information is for the "w" value of the coordinate.

Extend this up to {R, G, B, A, w} and you'll have a matrix. Take 4 colours Red (1, 0, 0, 0, w), Green (0, 1, 0, 0, w), Blue (0, 0, 1, 0, w) and Transparent (0, 0, 0, 1, w). Work out what colours they map to in the new scheme and build up your matrix as follows:

(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)

NOTE: The order you do you mulitplication (vector * matrix or matrix * vector) will determine whether the transformed points go vertically or horizontally into this matrix, as matrix multiplication is non-commutative. I'm assuming vector * matrix.

夏花。依旧 2024-08-02 06:22:24

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

简单:

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

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

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

如何表示要应用的色调? 最简单的答案是:提供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;
    }
}

希望这会有所帮助。

限制

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

This is an old question but solutions posted are much more complicated than the simple answer I found.

Simple:

  • no external dependency
  • no complicated calculation (no figuring out a rotation angle, no applying some cosinus formula)
  • actually does a rotation of colors!

Restating the problem: what do we need ?

I prepared icons tinted red. Some areas are transparent, some are more or less saturated, but they all have red hue. I think it matches your use case very well. The images may have other colors, they will just be rotated.

How to represent the tint to apply ? The simplest answer is: supply a Color.

Working towards a solution

ColorMatrix represent a linear transformation.

Obviously when Color is red, the transformation should be identity.
When color is green, the transformation should map red to green, green to blue, blue to red.

A ColorMatrix that does this is :

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

Mathematical solution

The "Aha" trick is to recognize that the actual form of the matrix is

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

where R, G and B are simply the component of the tinting color!

Sample code

I took example code on https://code.msdn.microsoft.com/ColorMatrix-Image-Filters-f6ed20ae .

I adjusted it and actually use this in my project:

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

Hope this helps.

Limitation

  • Notice that the transformation may saturate if you use too bright colors. To guarantee no saturation, tt is sufficient that R+G+B<=1.
  • You may normalize the transformation by dividing cr, cg, cb by cr+cg+cb but handle the case where tinting color is black or you'll divide by zero.
送君千里 2024-08-02 06:22:24

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

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

The following code constructs a ColorMatrix for applying a hue shift.

The insight i had was that at 60° shift in hue space:

  • red --> green
  • green --> blue
  • blue --> red

is actually a 45° shift in RGB space:

enter image description here

So you can turn some fraction of a 120° shift into some fraction of a 90° shift.

The HueShift parameter must be a value between -1..1.

For example, in order to apply a 60° shift:

  • changing red to yellow,
  • changing yellow to green
  • changing green to cyan
  • changing cyan to blue
  • changing blue to magenta
  • changing magenta to red

you call:

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

Implementation:

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;

Note: Any code is released into the public domain. No attribution required.

岁月打碎记忆 2024-08-02 06:22:24

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

I build a method that implement @IanBoid code in c# language.

    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
            );
    }
高跟鞋的旋律 2024-08-02 06:22:24

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

I suppose that www.aforgenet.com could help

小霸王臭丫头 2024-08-02 06:22:23

我将其放在一起来解决这个问题(帖子底部链接了 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;
}

I threw this together for this question (ZIP file with c# project linked at the bottom of the post). It does not use ImageAttributes or ColorMatrix, but it rotates the hue as you've described:

//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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文