Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I transform only pixels of a specific colour using GDI+?

Tags:

c#

.net

gdi+

Let's say I have an image composed of black and white RGB pixels, with varying levels of transparency. I then want to use GDI+ to maintain the transparency levels of all pixels and transform just the black pixels to red, leaving the white ones unaffected.

I think the colormap would look something like:

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

but how, short of looping over each pixel in turn and testing its colour, can I apply that map just to the black pixels?

like image 953
David Arno Avatar asked Dec 16 '22 12:12

David Arno


2 Answers

To answer you main question: there is no way to use matrices to replace a specific color, because matrices are merely transformations without conditions.

To solve your problem: because of the combination you are using (Black and White), matrices can be used! See detailed answer below...

A very good reference: Color Transformations and the Color Matrix.


You are very close in your matrix guess, however, you weren't keeping all of the RGBA values intact. Take a look here:

  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 |
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Through the diagonal 1's, you are maintaining the current RGBA values. By specifying a 1 on [4][0], you are also saying that you want that value at its maximum!

In your specific case of using Black and White, replacing only the Black color will work, because White has its RGB values already at maximum (255, 255, 255).

Hence, through the above matrix you are basically saying: keep all RGB at their current values, but I also want Red to always be 255!


Detailed example using sequence (R, G, B, A), and applying the above rules: Red at maximum = 255, everything else the same.

  • Found (0, 0, 0, 128) = Results (255, 0, 0, 128);
  • Found (0, 0, 0, 192) = Results (255, 0, 0, 192);
  • Found (255, 255, 255, 128) = Results (255, 255, 255, 128);
  • Found (255, 255, 255, 255) = Results (255, 255, 255, 255);
  • etc.

As you can see, the Red component is always set to its maximum, and the rest is maintained.


Sample picture:

Sample color transformation

The left side contains original colors with an alpha value specified, while the right contains the same image with the above transformation applied. The circles on the left side are organized in the following manner:

                  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# code to generate the sample image:

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);
    }
}
like image 166
Jesse Avatar answered Apr 28 '23 11:04

Jesse


The black color is represented by 0, The white color is represented by 1

To get your mathematics work with the black pixels only you should the following:

  1. Invert the colors of your image (without loosing the transparency).
  2. Transform the white colors (black in the original) to Aqua (inverse of red).
  3. Invert the colors back to the original.

your colormap in step 2 will look like:

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

The result now should be as the following:

  1. Black colors are red.
  2. White colors still white.
like image 35
Mahmoud Samy Avatar answered Apr 28 '23 09:04

Mahmoud Samy