Search code examples
c#winformscolors

How to convert a Color to KnownColor name?


tried this:

in the List<Color> colors there are many different colors over 39,000 and the first item for example is:

{Name = ffcecece, ARGB = (255, 206, 206, 206)}

and now I want to convert the ARGB to KnownColor name for example if it was Blue or Red or any color name.

the problem is that the method GetKnownColorName return only "Unknown".

private void label1_Click(object sender, EventArgs e)
{
    if(colorsAdded)
    {
        for(int i =0; i < colors.Count; i++)
        {
            colorsNames.Add(GetKnownColorName(colors[i]));
        }
    }
}

Then the conversion:

private string GetKnownColorName(Color color)
{
    foreach (KnownColor knownColor in Enum.GetValues(typeof(KnownColor)))
    {
        Color knownColorValue = Color.FromKnownColor(knownColor);

        if (knownColorValue.ToArgb() == color.ToArgb())
        {
            return knownColor.ToString();
        }
    }

    return "Unknown";
}

Full Code:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace Colors_Scanner
{
    public partial class Form1 : Form
    {
        private Bitmap imageBitmap;
        private Bitmap imageBitmapComparison;
        private Color currentColor;
        private List<string> colorsNames = new List<string>();
        private List<Color> colors = new List<Color>();
        private Bitmap bmp;
        private bool colorsAdded = false; // Flag to track if colors have been added

        public Form1()
        {
            InitializeComponent();

            // Load images into PictureBox controls
            LoadImages();

            // Initialize the bitmap for pictureBox2
            bmp = new Bitmap(pictureBox2.Image);
        }

        private void LoadImages()
        {
            // Load an image into the PictureBox control
            imageBitmap = new Bitmap(@"D:\Ffmpeg Capture\radar1.jpg"); // Replace with your image
            pictureBox1.Size = imageBitmap.Size;
            pictureBox1.Image = imageBitmap;

            imageBitmapComparison = new Bitmap(@"D:\Ffmpeg Capture\colorslevels1.jpg"); // Replace with your ruler image
            pictureBox2.Size = imageBitmapComparison.Size;
            pictureBox2.Image = imageBitmapComparison;
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (IsMouseWithinPictureBoxBounds(e.Location, pictureBox1))
            {
                UpdateColorLabel(e.Location);
                currentColor = GetColorAtCursor(e.Location, imageBitmap);
                pictureBox2.Invalidate();
            }
        }

        private void pictureBox2_Paint(object sender, PaintEventArgs e)
        {
            Pen pen = new Pen(currentColor);
            e.Graphics.DrawRectangle(pen, new Rectangle(0, 0, 3, 3));

            if (!colorsAdded)
            {
                // Add colors from pictureBox2 only once
                AddColorsFromPictureBox2();
                colorsAdded = true; // Set the flag to true after adding colors
            }
        }

        private List<Color> AddColorsFromPictureBox2()
        {
            if (bmp != null)
            {
                for (int i = 0; i < bmp.Width; i++)
                {
                    if (i == bmp.Width - 1)
                    {
                        break;
                    }

                    for (int j = 0; j < bmp.Height; j++)
                    {
                        Color c = bmp.GetPixel(i, j);
                        // You can add the color to your list here
                        colors.Add(c);
                    }
                }
            }

            return colors;
        }

        private void UpdateColorLabel(Point location)
        {
            Color color = GetColorAtCursor(location, imageBitmap);
            label1.Text = $"RGB: ({color.R}, {color.G}, {color.B})";
            label1.BackColor = color;
        }

        private Color GetColorAtCursor(Point location, Bitmap bitmap)
        {
            int x = location.X;
            int y = location.Y;

            if (IsMouseWithinPictureBoxBounds(location, pictureBox1))
            {
                BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                    ImageLockMode.ReadOnly, bitmap.PixelFormat);

                try
                {
                    int bytesPerPixel = Bitmap.GetPixelFormatSize(bitmap.PixelFormat) / 8;
                    IntPtr ptr = bmpData.Scan0;
                    int pixelOffset = y * bmpData.Stride + x * bytesPerPixel;

                    byte b = System.Runtime.InteropServices.Marshal.ReadByte(ptr, pixelOffset);
                    byte g = System.Runtime.InteropServices.Marshal.ReadByte(ptr, pixelOffset + 1);
                    byte r = System.Runtime.InteropServices.Marshal.ReadByte(ptr, pixelOffset + 2);

                    return Color.FromArgb(r, g, b);
                }
                finally
                {
                    bitmap.UnlockBits(bmpData);
                }
            }

            return Color.Black; // Default color if outside of PictureBox bounds
        }

        private bool IsMouseWithinPictureBoxBounds(Point location, PictureBox pictureBox)
        {
            return location.X >= 0 && location.X < pictureBox.Width && location.Y >= 0 && location.Y < pictureBox.Height;
        }

        private void label1_Click(object sender, EventArgs e)
        {
            if(colorsAdded)
            {
                for(int i =0; i < colors.Count; i++)
                {
                    colorsNames.Add(GetKnownColorName(colors[i]));
                }
            }
        }

        private string GetKnownColorName(Color color)
        {
            foreach (KnownColor knownColor in Enum.GetValues(typeof(KnownColor)))
            {
                Color knownColorValue = Color.FromKnownColor(knownColor);

                if (knownColorValue.ToArgb() == color.ToArgb())
                {
                    return knownColor.ToString();
                }
            }

            return "Unknown";
        }
    }
}

Solution

  • If you want to get closest KnownColor for an arbitrary Color you have to define some Distance method and then you can query for known color with minimum distance from originalColor:

    using System.Linq;
    
    ...
    
    Color originColor = Color.FromArgb(255, 206, 206, 206);
    
    // Simplest, but not necessary the best one
    static int Distance(Color left, Color right) =>
      Math.Abs(left.R - right.R) +
      Math.Abs(left.G - right.G) +
      Math.Abs(left.B - right.B) +
      Math.Abs(left.A - right.A);
    
    var result = Enum
      .GetValues<KnownColor>()
      .MinBy(color => Distance(Color.FromKnownColor(color), originColor));
    
    Console.Write(result);
    

    Outcome (note, that LightGray is {255, 211, 211, 211}):

    LightGray
    

    If you have custom collection of known colors, you can organize it as a Dictionary<Color, string>, e. g.

    private static readonly Dictionary<Color, string> KnownColors = new() {
      { Color.FromArgb(255, 255, 255, 255), "Black Color"},
      { Color.FromArgb(255, 206, 206, 206), "Light Gray Color"},
      // etc.
    };
    

    Then

    if (KnownColors.TryGetValue(originColor, out string name)) {
      // originColor is known under "name" name
      Console.Write(name);
    }
    

    Fiddle

    Edit: You can exclude SystemColors such as ActiveBorder if you like:

    private static HashSet<string> SystemColorsNames() => typeof(SystemColors)
      .GetProperties(BindingFlags.Public | BindingFlags.Static)
      .Where(prop => prop.CanRead && prop.PropertyType == typeof(Color))
      .Select(prop => prop.Name)
      .ToHashSet();
    
    ...
    
    var exclude = SystemColorsNames();  
          
    var result = Enum
      .GetValues<KnownColor>()
      .Where(color => !exclude.Contains(Enum.GetName(color)))   
      .MinBy(color => Distance(Color.FromKnownColor(color), originColor));