14

How can I replace a color for some parts of an image without affecting its texture?

You can see good example of the result in this screenshot

enter image description here

Source: https://www.leadtools.com/sdk/image-processing/functions/function.asp?id=158

TylerH
  • 20,799
  • 66
  • 75
  • 101
Oren
  • 937
  • 1
  • 10
  • 34
  • Possible duplicate of https://stackoverflow.com/questions/17208254/how-to-change-pixel-color-of-an-image-in-c-net – TylerH Apr 18 '23 at 20:49

5 Answers5

6

Upon doing research i found no efficient/smoothe way of doing this, so i dont it myself, the code could be cleaned up ALOT but it gets the job done, its not efficient but it is smoother and allows you to set a tolerance.

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;
    }
  • I found i couldn't use this without removing "this" and then it doesn't have any errors occur but how does one implement this example? – Burgo855 Jul 30 '18 at 22:42
  • same but minor improvemet in image size found at : https://www.codeproject.com/Articles/42869/Color-Replacer – Somnath Kadam Aug 31 '21 at 11:32
5

try this:

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);
    }
}
Alex
  • 5,971
  • 11
  • 42
  • 80
  • `GetPixel` is very slow, see: http://stackoverflow.com/questions/4235731/is-there-a-faster-alternative-to-gdi-getpixel – Andrew Bullock Mar 26 '12 at 11:38
  • yes not very fast, but for small images color changing it is very effective. – Alex Mar 26 '12 at 11:44
  • The one problem with changing colors of an image is that is not selective. i.e. in an image you want to change black to red in a certain portion it is not possible. for eg if in a person image you want to change the colors of hair to red then it is not possible. it will replace all black pixels with red no matter where they appear in image. – Nikhil Agrawal Mar 26 '12 at 11:45
  • you can check the parts of the image by its X & Y so that not all parts that contain red change to black for example, but I agree with you it won't be accurate 100% – Alex Mar 26 '12 at 11:47
  • Thanks, I think it's supposed to be some sort of color range / pallete manipulation from source to target but I don't know what is the way to do this... – Oren Mar 26 '12 at 11:51
2

Try to read out al the pixels and stuff them in an 3 array's (rgb) there you can set in a alogrithm to replace your colors.

Matt
  • 74,352
  • 26
  • 153
  • 180
jorne
  • 894
  • 2
  • 11
  • 23
  • Hi, thanks - however this is not relevant for what I need. What I actually need is to apply a replace a color in an image in a way that will keep the texture – Oren Mar 26 '12 at 15:22
  • that is somme what the same, the texture is just a variation in colours. like blue sky and a white cloud can be a purple sky and somewhat darker purple cloud or sommething like that. – jorne Mar 27 '12 at 06:18
2

Found the way to do it. This requires RGB<->HSL conversions (I used this class by Rich Newman for 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. Get a reference value (in hsl) representing the color you want to replace
  2. Get the hsl value for your target color
  3. Get image pixels and for each pixel:
  4. calculate the hsl value of the pixel, and replace it with (pixelHsl / refHsl) * targetHsl
TylerH
  • 20,799
  • 66
  • 75
  • 101
Oren
  • 937
  • 1
  • 10
  • 34
0

Taken largely from this answer to How to Change Pixel Color of an Image in C#.NET by 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;
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
Baby Love
  • 46
  • 3
  • 1
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – Yunnosch Apr 10 '23 at 14:17