22

I am working with Images in Java, I have designed more over 100+ images(.png) format, They were all Trasparent and Black Color Drawing.

The problem is, Now I have been asked to change the color of the Drawing (Black -to ).

I have searched many code snipped at google,that changes the Bitmap (pixels) of the Image, but i am not guessing what i have to do to match the exact pixel and replace specially when the images if in Transparent mode. Below is the code in .Net (C#)

        Bitmap newBitmap = new Bitmap(scrBitmap.Width, scrBitmap.Height);
        for (int i = 0; i < scrBitmap.Width; i++)
        {
            for (int j = 0; j < scrBitmap.Height; j++)
            {                    
                originalColor = scrBitmap.GetPixel(i, j);
                if(originalColor = Color.Black)
                  newBitmap.SetPixel(i, j, Color.Red);
            }
        }            
        return newBitmap;

but it was not matching at all, I debugged it, throughout the file, there was no value of Red,Green,Blue parameters of Color (originalColor) variable.

Anybody can help?

Bibi Tahira
  • 1,082
  • 5
  • 15
  • 39

3 Answers3

36

Here is the Solution I have done with Pixels.

Attaching the source code so one can try the exact and get the result.

I have sample images of 128x128 (Width x Height).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
//using System.Globalization;

namespace colorchange
{
   class Program
   {
      static void Main(string[] args)
      {
          try
          {
              Bitmap bmp = null;
              //The Source Directory in debug\bin\Big\
              string[] files = Directory.GetFiles("Big\\");
              foreach (string filename in files)
              {
                 bmp = (Bitmap)Image.FromFile(filename);                    
                 bmp = ChangeColor(bmp);
                 string[] spliter = filename.Split('\\');
                 //Destination Directory debug\bin\BigGreen\
                 bmp.Save("BigGreen\\" + spliter[1]);
              }                                                 
           }
           catch (System.Exception ex)
           {
              Console.WriteLine(ex.ToString());
           }            
       }        
       public static Bitmap ChangeColor(Bitmap scrBitmap)
       {
          //You can change your new color here. Red,Green,LawnGreen any..
          Color newColor = Color.Red;
          Color actualColor;            
          //make an empty bitmap the same size as scrBitmap
          Bitmap newBitmap = new Bitmap(scrBitmap.Width, scrBitmap.Height);
          for (int i = 0; i < scrBitmap.Width; i++)
          {
             for (int j = 0; j < scrBitmap.Height; j++)
             {
                //get the pixel from the scrBitmap image
                actualColor = scrBitmap.GetPixel(i, j);
                // > 150 because.. Images edges can be of low pixel colr. if we set all pixel color to new then there will be no smoothness left.
                if (actualColor.A > 150)
                    newBitmap.SetPixel(i, j, newColor);
                else
                    newBitmap.SetPixel(i, j, actualColor);
             }
          }            
          return newBitmap;
       }
   }
}

//Below is the sample image and different results by applying different color enter image description here

TylerH
  • 20,799
  • 66
  • 75
  • 101
DareDevil
  • 5,249
  • 6
  • 50
  • 88
  • 5
    Well this will replace every color to the new one (not just a selected one) and check on alpha will produce sub-optimal results if there is a gradient but if it satisfies OP... :) – Adriano Repetti Jun 20 '13 at 09:34
  • @DareDevil thanks. You code to change colour only object in the image. But I only want to change the color of the background image, not change a colour of the object. My origin image: http://imgur.com/LphfKdV You changed: http://imgur.com/DJc0N0U I want like: http://imgur.com/4pvWtp8 How to do this? Thanks. – Ave May 26 '16 at 08:08
18

Before we talk about perfromance let's check your code:

var originalColor = scrBitmap.GetPixel(i, j);
if (originalColor = Color.Black)
    newBitmap.SetPixel(i, j, Color.Red);

Here there are two errors:

  1. You do not compare to Color.Black but you assign Color.Black to originalColor.
  2. You do not handle transparency.

To check for transparency you should compare not the Color object but the R, G, B values, let's change to:

var originalColor = scrBitmap.GetPixel(i, j);
if (originalColor.R == 0 && originalColor.G == 0 && originalColor.B == 0)
    newBitmap.SetPixel(i, j, Color.FromArgb(originalColor.A, Color.Red));

Now you'll see that it works but it takes a very long time to process each image: GetPixel and SetPixel are pretty slow (primary because they check and calculate everything for each call). It's much better to handle bitmap data directly. If you know the image format in advance (and it's fixed for each image) then you can do it much much faster with little bit more code:

static unsafe Bitmap ReplaceColor(Bitmap source,
                                  Color toReplace,
                                  Color replacement)
{
  const int pixelSize = 4; // 32 bits per pixel

  Bitmap target = new Bitmap(
    source.Width,
    source.Height,
    PixelFormat.Format32bppArgb);

  BitmapData sourceData = null, targetData = null;

  try
  {
    sourceData = source.LockBits(
      new Rectangle(0, 0, source.Width, source.Height),
      ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

    targetData = target.LockBits(
      new Rectangle(0, 0, target.Width, target.Height),
      ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

    for (int y = 0; y < source.Height; ++y)
    {
      byte* sourceRow = (byte*)sourceData.Scan0 + (y * sourceData.Stride);
      byte* targetRow = (byte*)targetData.Scan0 + (y * targetData.Stride);

      for (int x = 0; x < source.Width; ++x)
      {
        byte b = sourceRow[x * pixelSize + 0];
        byte g = sourceRow[x * pixelSize + 1];
        byte r = sourceRow[x * pixelSize + 2];
        byte a = sourceRow[x * pixelSize + 3];

        if (toReplace.R == r && toReplace.G == g && toReplace.B == b)
        {
          r = replacement.R;
          g = replacement.G;
          b = replacement.B;
        }

        targetRow[x * pixelSize + 0] = b;
        targetRow[x * pixelSize + 1] = g;
        targetRow[x * pixelSize + 2] = r;
        targetRow[x * pixelSize + 3] = a;
      }
    }
  }
  finally
  {
    if (sourceData != null)
      source.UnlockBits(sourceData);

    if (targetData != null)
      target.UnlockBits(targetData);
  }

  return target;
}

Of course this can be further optimized and you may need to handle different formats ( see this list of pixel formats and this article about their layout) but consider it a starting point to work with bitmaps.

For completeness this is equivalent color without direct access to bitmap data. Please note that this should be rarely used because it's terribly slow.

static Bitmap ReplaceColor(Bitmap source,
                           Color toReplace,
                           Color replacement)
{
    var target = new Bitmap(source.Width, source.Height);

    for (int x = 0; x < source.Width; ++x)
    {
        for (int y = 0; y < source.Height; ++y)
        {
            var color = source.GetPixel(x, y);
            target.SetPixel(x, y, color == toReplace ? replacement : color);
        }
    }

    return target;
}

Also please note that this consider alpha channel in comparison (so 50% transparent green, for example, is not same color as 30% transparent green). To ignore alpha you may use something like this:

if (color.R == toReplace.R && color.G == toReplace.G && color.B == toReplace.B)

Finally if you know that pixels to replace are little you may create a raw copy of original image (using Graphics.FromImage to create a context and to draw into it source bitmap), in such way you'll call SetPixel() only when there is a replacement. IMO any optimization here is pretty useless: if you need performance use first solution...

Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • In Hurry, I did all and it got deleted == – Bibi Tahira Jun 20 '13 at 08:58
  • 1
    @BibiTahira what got deleted? – Adriano Repetti Jun 20 '13 at 09:02
  • when i was coparing my pixel color with system color – Bibi Tahira Jun 20 '13 at 09:26
  • @BibiTahira see code examples, you _mustn't_ compare Color objects but individual components (because of alpha in the original image). – Adriano Repetti Jun 20 '13 at 09:35
  • @AdrianoRepetti : I am thinhking to use this code, but i need to handle multiple image types. I can get pixel format from image type, but how can i calculate the 'pixelsize' – Iorn Man Aug 13 '14 at 11:08
  • Pixel size is easiest part (PixelFormat enumeration _tells_ you pixel size in bits) but also memory representation varies according to image format. I added 3 links in the last paragraph. To support them all can be a pain... – Adriano Repetti Aug 13 '14 at 11:31
  • @AdrianoRepetti : Thanks you for your help. ReplaceColor functions works perfectly in case of .bmp images however it doesnot work in case of .jpg images. what do i have to do for it ? – Iorn Man Aug 25 '14 at 07:54
  • @IornMan problem is format. JPG isng 32 bit (24+8 for alpha) then that code doesn't work. Try with 2nd (slower) version for non PixelFormat.Format32bppArgb images. – Adriano Repetti Aug 25 '14 at 08:20
  • @AdrianoRepetti I want to achieve same for UWP (Mobile and Desktop Apps). Can you suggest a solution? In UWP, `system.Drawing` and `Bitmap` is not available. – Kinjan Bhavsar Jan 12 '17 at 10:22
  • @kinjan just search here on SO, one for all (but there are more): http://stackoverflow.com/q/34837456/1207195 – Adriano Repetti Jan 12 '17 at 12:20
1

I will give you another solution since this does not calculate for every pixel.

Its short and simple. Convert time is 62 ms:

public Bitmap Color(Bitmap original)
        {
            //create a blank bitmap the same size as original
            Bitmap newBitmap = new Bitmap(original.Width, original.Height);

            //get a graphics object from the new Image
            Graphics g = Graphics.FromImage(newBitmap);

            //create the color you want ColorMatrix
            //now is set to red, but with different values 
            //you can get anything you want.
            ColorMatrix colorMatrix = new ColorMatrix(
                new float[][]
                {

                    new float[] {1f, .0f, .0f, 0, 0},
                    new float[] {1f, .0f, .0f, 0, 0},
                    new float[] {1f, .0f, .0f, 0, 0},
                    new float[] {0, 0, 0, 1, 0},
                    new float[] {0, 0, 0, 0, 1}
                });

            //create some image attributes
            ImageAttributes attributes = new ImageAttributes();

            //set the color matrix attribute
            attributes.SetColorMatrix(colorMatrix);

            //draw original image on the new image using the color matrix
            g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
                0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);

            //release sources used
            g.Dispose();
            return newBitmap;
        }
Nicholas
  • 3,529
  • 2
  • 23
  • 31