【问题标题】:How can I transform only pixels of a specific colour using GDI+?如何使用 GDI+ 仅转换特定颜色的像素?
【发布时间】:2013-04-09 21:39:14
【问题描述】:

假设我有一张由黑白 RGB 像素组成的图像,具有不同的透明度。然后我想使用 GDI+ 来保持所有像素的透明度级别,并且只将黑色像素转换为红色,而白色像素不受影响。

我认为颜色图看起来像:

 |0 0 0 0 0|
 |0 0 0 0 0|
 |0 0 0 0 0|
 |0 0 0 1 0|
 |1 0 0 0 1|

但是,如果不依次循环遍历每个像素并测试其颜色,我可以将该映射仅应用于黑色像素吗?

【问题讨论】:

  • 您使用的是原生 .NET 库吗?因为你不想循环,我假设你已经尝试过Bitmap.LockBits?

标签: c# .net gdi+


【解决方案1】:

回答您的主要问题:没有方法可以使用矩阵替换特定颜色,因为矩阵只是 转换无条件

解决您的问题:因为您使用的是组合(黑白),所以可以使用矩阵!请参阅下面的详细答案...

一个很好的参考: Color Transformations and the Color Matrix.


您的矩阵猜测非常接近,但是,您并没有保持所有 RGBA 值不变。看这里:

  R   G   B   A   W
 ___________________
| 1 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 | 0 |
| 0 | 0 | 1 | 0 | 0 |
| 0 | 0 | 0 | 1 | 0 |
| 1 | 0 | 0 | 0 | 1 |
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

通过对角线1,您正在保持当前的 RGBA 值。通过在 [4][0] 上指定 1,您也表示您希望 的值达到最大值

在您使用黑白的特定情况下,只替换黑色会起作用,因为白色的 RGB 值已经达到最大值(255、255、255)。

因此,通过上述矩阵,您基本上是在说:将所有 RGB 保持在当前值,我还希望红色始终为 255!


使用序列(R、G、B、A)并应用上述规则的详细示例:最大红色 = 255,其他一切都相同。

  • 找到 (0, 0, 0, 128) = 结果 (255, 0, 0, 128);
  • 找到 (0, 0, 0, 192) = 结果 (255, 0, 0, 192);
  • 找到 (255, 255, 255, 128) = 结果 (255, 255, 255, 128);
  • 找到 (255, 255, 255, 255) = 结果 (255, 255, 255, 255);

如您所见,红色组件始终设置为最大值,其余部分保持不变。


示例图片:

左侧包含指定了 alpha 值的原始颜色,而右侧包含应用了上述转换的相同图像。左侧的圆圈按以下方式组织:

                  R    G    B    A   |   R    G    B    A
               ______________________|_______________________
circle[0,0] = | (255,   0,   0,   0) | (255,   0,   0,  64) | = circle[0,1]
      [1,0] = | (  0,   0,   0,   0) | (  0,   0,   0,  64) | =       [1,1]
      [2,0] = | (255, 255, 255,   0) | (255, 255, 255,  64) | =       [2,1]
      [3,0] = | (255,   0,   0, 128) | (255,   0,   0, 255) | =       [3,1]
      [4,0] = | (  0,   0,   0, 128) | (  0,   0,   0, 255) | =       [4,1]
      [5,0] = | (255, 255, 255, 128) | (255, 255, 255, 255) | =       [5,1]
               ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

生成示例图像的C#代码:

using (Bitmap myBitmap = new Bitmap(102, 302))
{
    Graphics g = Graphics.FromImage(myBitmap);
    g.Clear(Color.Transparent);
    g.FillEllipse(new SolidBrush(Color.FromArgb(0, Color.Red)), new Rectangle(1, 1, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(0, Color.Black)), new Rectangle(1, 51, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(0, Color.White)), new Rectangle(1, 101, 50, 50));

    g.FillEllipse(new SolidBrush(Color.FromArgb(64, Color.Red)), new Rectangle(51, 1, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(64, Color.Black)), new Rectangle(51, 51, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(64, Color.White)), new Rectangle(51, 101, 50, 50));

    g.FillEllipse(new SolidBrush(Color.FromArgb(128, Color.Red)), new Rectangle(1, 151, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(128, Color.Black)), new Rectangle(1, 201, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(128, Color.White)), new Rectangle(1, 251, 50, 50));

    g.FillEllipse(new SolidBrush(Color.FromArgb(255, Color.Red)), new Rectangle(51, 151, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(255, Color.Black)), new Rectangle(51, 201, 50, 50));
    g.FillEllipse(new SolidBrush(Color.FromArgb(255, Color.White)), new Rectangle(51, 251, 50, 50));
    g.DrawRectangle(Pens.Black, 0, 0, myBitmap.Width - 1, myBitmap.Height - 1);

    e.Graphics.DrawImage(myBitmap, 0, 0);

    ColorMatrix colorMatrix = new ColorMatrix(
        new Single[][]
        {
            new [] { 1f, 0, 0, 0, 0 },
            new [] { 0f, 1, 0, 0, 0 },
            new [] { 0f, 0, 1, 0, 0 },
            new [] { 0f, 0, 0, 1, 0 },
            new [] { 1f, 0, 0, 0, 1 }
        });

    using (ImageAttributes imageAttr = new ImageAttributes())
    {
        imageAttr.SetColorMatrix(colorMatrix);

        Rectangle rect = new Rectangle(150, 0, myBitmap.Width, myBitmap.Height);
        e.Graphics.DrawImage(myBitmap, rect, 0, 0, myBitmap.Width, myBitmap.Height, GraphicsUnit.Pixel, imageAttr);
    }
}

【讨论】:

  • 细节之多令人难以置信!非常感谢。我认为您轻松获得了 50 个代表 :)
  • @DavidArno 嘿。这是一个有趣的练习。我很高兴它有帮助 - 我忘了包含一个非常有用的链接:Color Transformations and the Color Matrix。我已经用它更新了我的答案。
【解决方案2】:

黑色用0表示, 白色用 1 表示

使用黑色像素进行数学运算,您应该执行以下操作:

  1. 反转图像的颜色(不损失透明度)。
  2. 将白色(原来的黑色)转换为 Aqua(红色的反色)。
  3. 将颜色反转回原来的颜色。

您在第 2 步中的颜色图将如下所示:

|0 0 0 0 0|
|0 1 0 0 0|
|0 0 1 0 0|
|0 0 0 1 0|
|0 0 0 0 1|

现在的结果应该如下:

  1. 黑色就是红色。
  2. 白色仍然是白色。
【解决方案3】:

除了描述的其他技术之外,Color Remap Table 还可用于转换图像中特定的单个颜色(一个简短列表)。重映射表是传递给 Graphics 对象的 DrawImage 方法的 ImageAttributes 对象的一部分。来自 MSDN 文档:

当 GDI+ 绘制图像时,图像的每个像素都会与 旧颜色的数组。如果一个像素的颜色与旧颜色匹配,它的 颜色更改为相应的新颜色。颜色是 只为渲染而改变——图像本身的颜色值 (存储在 Image 或 Bitmap 对象中)不会改变。

【讨论】:

  • 不幸的是,我试过这个,但重映射值匹配 exact RGBA(或 ARGB)颜色。如果像素与 just Alpha 通道不同,则不会被替换。 =\
  • 所以列出 256 个不同的 alpha 值;很容易在循环中创建。
  • 这可能有效,但可能不是此特定解决方案的答案。虽然不试图深入研究过早的优化,但 GDI+ 真正应用 256 个不同颜色图的速度有多快?
  • 至少和使用 GetPixel() 和 SetPixel() 一样快。由于是内部的,并且可能由图形硬件支持,可能会快得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-29
  • 1970-01-01
  • 1970-01-01
  • 2015-08-09
  • 2022-01-14
  • 1970-01-01
  • 2014-06-21
相关资源
最近更新 更多