在C#中替换图像中的颜色

14

可能是 https://dev59.com/82Qm5IYBdhLWcg3w8iyD 的重复问题。 - TylerH
6个回答

14

使用重新映射表是一种高效地替换颜色的方法。在下面的示例中,图像被绘制在图片框内。在绘画事件中,将颜色Color.Black更改为Color.Blue:

private void pictureBox_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    using (Bitmap bmp = new Bitmap("myImage.png"))
    {

        // Set the image attribute's color mappings
        ColorMap[] colorMap = new ColorMap[1];
        colorMap[0] = new ColorMap();
        colorMap[0].OldColor = Color.Black;
        colorMap[0].NewColor = Color.Blue;
        ImageAttributes attr = new ImageAttributes();
        attr.SetRemapTable(colorMap);
        // Draw using the color map
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        g.DrawImage(bmp, rect, 0, 0, rect.Width, rect.Height, GraphicsUnit.Pixel, attr);
    }
}

更多信息:http://msdn.microsoft.com/en-us/library/4b4dc1kz%28v=vs.110%29.aspx


6

在进行研究后,我发现没有一种高效/顺畅的方法来做到这一点,所以我自己动手实现了它。代码可以大量优化,但它能够完成任务,虽然不够高效,但更加顺畅,并允许您设置容差。

public static Image ColorReplace(this Image inputImage, int tolerance, Color oldColor, Color NewColor)
    {
        Bitmap outputImage = new Bitmap(inputImage.Width, inputImage.Height);
        Graphics G = Graphics.FromImage(outputImage);
        G.DrawImage(inputImage, 0, 0);
        for (Int32 y = 0; y < outputImage.Height; y++)
            for (Int32 x = 0; x < outputImage.Width; x++)
            {
                Color PixelColor = outputImage.GetPixel(x, y);
                if (PixelColor.R > oldColor.R - tolerance && PixelColor.R < oldColor.R + tolerance && PixelColor.G > oldColor.G - tolerance && PixelColor.G < oldColor.G + tolerance && PixelColor.B > oldColor.B - tolerance && PixelColor.B < oldColor.B + tolerance)
                {
                    int RColorDiff = oldColor.R - PixelColor.R;
                    int GColorDiff = oldColor.G - PixelColor.G;
                    int BColorDiff = oldColor.B - PixelColor.B;

                    if (PixelColor.R > oldColor.R) RColorDiff = NewColor.R + RColorDiff;
                    else RColorDiff = NewColor.R - RColorDiff;
                    if (RColorDiff > 255) RColorDiff = 255;
                    if (RColorDiff < 0) RColorDiff = 0;
                    if (PixelColor.G > oldColor.G) GColorDiff = NewColor.G + GColorDiff;
                    else GColorDiff = NewColor.G - GColorDiff;
                    if (GColorDiff > 255) GColorDiff = 255;
                    if (GColorDiff < 0) GColorDiff = 0;
                    if (PixelColor.B > oldColor.B) BColorDiff = NewColor.B + BColorDiff;
                    else BColorDiff = NewColor.B - BColorDiff;
                    if (BColorDiff > 255) BColorDiff = 255;
                    if (BColorDiff < 0) BColorDiff = 0;

                    outputImage.SetPixel(x, y, Color.FromArgb(RColorDiff, GColorDiff, BColorDiff));
                }
            }
        return outputImage;
    }

我发现如果不删除“this”,就无法使用它,但是如何实现这个例子呢? - Burgo855
非常好用!非常感谢。 - Jhollman
在以下网址找到了相同但尺寸稍微改进的图像:https://www.codeproject.com/Articles/42869/Color-Replacer - Somnath Kadam

5

尝试这个:

Color color = Color.Black; //Your desired colour

byte r = color.R; //For Red colour

Bitmap bmp = new Bitmap(this.BackgroundImage);
for (int x = 0; x < bmp.Width; x++)
{
    for (int y = 0; y < bmp.Height; y++)
    {
        Color gotColor = bmp.GetPixel(x, y);
        gotColor = Color.FromArgb(r, gotColor.G, gotColor.B);
        bmp.SetPixel(x, y, gotColor);
    }
}

GetPixel非常慢,请参见:https://dev59.com/H1LTa4cB1Zd3GeqPWw1- - Andrew Bullock
是的,它不是很快,但对于小图像的颜色更改非常有效。 - Alex
更改图像颜色的一个问题是它不具有选择性。例如,在图像中,如果您想要将某个部分的黑色更改为红色,则无法实现。例如,如果您想要将人物图像中的头发颜色更改为红色,则无法实现。它将替换图像中出现的所有黑色像素为红色,而不管它们在哪里。 - Nikhil Agrawal
您可以通过其X和Y检查图像的部分,以便不是所有包含红色的部分都变为黑色,但我同意您的观点,这不会100%准确。 - Alex
谢谢,我认为这应该是从源到目标的某种颜色范围/调色板操作,但我不知道如何做到这一点... - Oren

2
找到了解决方法。这需要进行RGB<->HSL转换(我使用了Rich Newman的this class来进行HSLColor转换)。
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace ColorDemo
{
   public class HSLColor
   {
       // Private data members below are on scale 0-1
       // They are scaled for use externally based on scale
       private double hue = 1.0;
       private double saturation = 1.0;
       private double luminosity = 1.0;

       private const double scale = 240.0;

       public double Hue
       {
           get { return hue * scale; }
           set { hue = CheckRange(value / scale); }
       }
       public double Saturation
       {
           get { return saturation * scale; }
           set { saturation = CheckRange(value / scale); }
       }
       public double Luminosity
       {
           get { return luminosity * scale; }
           set { luminosity = CheckRange(value / scale); }
       }

       private double CheckRange(double value)
       {
           if (value < 0.0)
               value = 0.0;
           else if (value > 1.0)
               value = 1.0;
           return value;
       }

       public override string ToString()
       {
           return String.Format("H: {0:#0.##} S: {1:#0.##} L: {2:#0.##}", Hue, Saturation, Luminosity);
       }

       public string ToRGBString()
       {
           Color color = (Color)this;
           return String.Format("R: {0:#0.##} G: {1:#0.##} B: {2:#0.##}", color.R, color.G, color.B);
       }

       #region Casts to/from System.Drawing.Color
       public static implicit operator Color(HSLColor hslColor)
       {
           double r = 0, g = 0, b = 0;
           if (hslColor.luminosity != 0)
           {
               if (hslColor.saturation == 0)
                   r = g = b = hslColor.luminosity;
               else
               {
                   double temp2 = GetTemp2(hslColor);
                   double temp1 = 2.0 * hslColor.luminosity - temp2;

                   r = GetColorComponent(temp1, temp2, hslColor.hue + 1.0 / 3.0);
                   g = GetColorComponent(temp1, temp2, hslColor.hue);
                   b = GetColorComponent(temp1, temp2, hslColor.hue - 1.0 / 3.0);
               }
           }
           return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
       }

       private static double GetColorComponent(double temp1, double temp2, double temp3)
       {
           temp3 = MoveIntoRange(temp3);
           if (temp3 < 1.0 / 6.0)
               return temp1 + (temp2 - temp1) * 6.0 * temp3;
           else if (temp3 < 0.5)
               return temp2;
           else if (temp3 < 2.0 / 3.0)
               return temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - temp3) * 6.0);
           else
               return temp1;
       }
       private static double MoveIntoRange(double temp3)
       {
           if (temp3 < 0.0)
               temp3 += 1.0;
           else if (temp3 > 1.0)
               temp3 -= 1.0;
           return temp3;
       }
       private static double GetTemp2(HSLColor hslColor)
       {
           double temp2;
           if (hslColor.luminosity < 0.5)  //<=??
               temp2 = hslColor.luminosity * (1.0 + hslColor.saturation);
           else
               temp2 = hslColor.luminosity + hslColor.saturation - (hslColor.luminosity * hslColor.saturation);
           return temp2;
       }

       public static implicit operator HSLColor(Color color)
       {
           HSLColor hslColor = new HSLColor();
           hslColor.hue = color.GetHue() / 360.0; // we store hue as 0-1 as opposed to 0-360 
           hslColor.luminosity = color.GetBrightness();
           hslColor.saturation = color.GetSaturation();
           return hslColor;
       }
       #endregion

       public void SetRGB(int red, int green, int blue)
       {
           HSLColor hslColor = (HSLColor)Color.FromArgb(red, green, blue);
           this.hue = hslColor.hue;
           this.saturation = hslColor.saturation;
           this.luminosity = hslColor.luminosity;
       }

       public HSLColor() { }
       public HSLColor(Color color)
       {
           SetRGB(color.R, color.G, color.B);
       }
       public HSLColor(int red, int green, int blue)
       {
           SetRGB(red, green, blue);
       }
       public HSLColor(double hue, double saturation, double luminosity)
       {
           this.Hue = hue;
           this.Saturation = saturation;
           this.Luminosity = luminosity;
       }

   }
}
  1. 获取代表您想要替换的颜色的参考值(以hsl表示)
  2. 获取目标颜色的hsl值
  3. 获取图像像素并对每个像素进行以下操作:
  4. 计算像素的hsl值,并用 (pixelHsl / refHsl) * targetHsl 替换它

是的,它像魔法一样运行良好。你能否评论一下原因? - user2440074
3
请问您用来替换它的代码是什么? - Developer Nation

2
尝试读取所有像素并将它们装入三个数组(rgb),然后你可以设置一个算法来替换你的颜色。

嗨,谢谢 - 但这与我所需的不相关。我实际需要的是以一种保留纹理的方式替换图像中的颜色。 - Oren
那是相似的,只是色彩上有所变化。就像蓝天和白云可以变成紫色天空和略深的紫色云彩之类的东西。 - jorne

0

主要参考了C#.NET中如何更改像素颜色的答案,作者为DareDevil

public Bitmap ChangeColor(Bitmap scrBitmap,Color newColor)
{           
    Color actualColor;
    
    Bitmap newBitmap = new Bitmap(scrBitmap.Width, scrBitmap.Height);
    for (int i = 0; i < scrBitmap.Width; i++)
    {
        for (int j = 0; j < scrBitmap.Height; j++)
        {                
            actualColor = scrBitmap.GetPixel(i, j);
            if (actualColor.A > 150)
                newBitmap.SetPixel(i, j, newColor);
            else
               newBitmap.SetPixel(i, j, actualColor);
        }
    }
    return newBitmap;
}

1
虽然这段代码可能解决了问题,但是如果能够包含一个解释(点击此处)来说明为什么以及如何解决这个问题,将会极大地提高你的帖子质量,并且可能会得到更多的赞同。请记住,你的回答是为未来的读者而写的,而不仅仅是对当前提问者的回答。请编辑你的回答,添加解释并指出适用的限制和假设。 - Yunnosch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接