Reputation: 191
In a WPF usercontrol I'm writing I have 3 sliders representing RGB with values from 0 - 255. Standard stuff ! Also displayed is a color swatch
Base class is
private class SwatchPixels
{
public Point P { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int R { get; set; }
public int G { get; set; }
public int B { get; set; }
public int A { get; set; }
}
private List<SwatchPixels> _points = new List<SwatchPixels>();
Now what I want to do is (what I have sorta works but no accurate enough !)
Load the List with all of the RGB values of the pixels in the color swatch and secondly find the closest pixel color to a supplied set of RGB values. I can then move an ellipse to the x,y location of that pixel. Reason I'm wanting this of course is that a color swatch generally will not contain all 16 million plus colors !
So if somebody can suggest a better method I would be most appreciative !
The swatch sits in a canvas object of the same size.
<Canvas Name="CanvasImage" Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Width="150" Height="150"
HorizontalAlignment="Center" Background="Transparent" VerticalAlignment="Top" Margin="2"
MouseMove="CanvasImage_MouseMove" MouseDown="CanvasImage_MouseDown" MouseUp="CanvasImage_MouseUp">
<Ellipse Name="EllipsePixel" Width="8" Height="8" Stroke="Black" Fill="White"
Canvas.Left="0" Canvas.Top="0"/>
</Canvas>
Method to load all of the pixel values
private List<SwatchPixels> FindAllPixelLocations()
{
// http://stackoverflow.com/questions/1176910/finding-specific-pixel-colors-of-a-bitmapimage
var img = new BitmapImage(new Uri(@"Resources/Cws.png", UriKind.RelativeOrAbsolute));
Image Ti = new Image();
Ti.Source = img;
ImageSource ims = Ti.Source;
BitmapImage bitmapImage = (BitmapImage)ims;
try
{
int stride = bitmapImage.PixelWidth * 4;
int size = bitmapImage.PixelHeight * stride; // stride
pixels = new byte[size];
bitmapImage.CopyPixels(pixels, stride, 0);
for (int y = 0; y < bitmapImage.PixelHeight; y++)
{
for (int x = 0; x < bitmapImage.PixelWidth; x++)
{
int index = y * stride + 4 * x;
byte red = pixels[index];
byte green = pixels[index + 1];
byte blue = pixels[index + 2];
byte alpha = pixels[index + 3];
var swatchPixels = new SwatchPixels
{
X = x,
Y = y,
P = new Point(x, y),
R = red,
G = green,
B = blue,
A = alpha
};
_points.Add(swatchPixels);
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
return _points;
}
The find a point method follows (the bit I'm not happy with !)
private const int initVariance = 40;
private const int dropPercent = 5;
private SwatchPixels FindXy(string a, string r, string g, string b)
{
var iR = 0;
var iG = 0;
var iB = 0;
if (RgbDec.IsChecked == true)
{
iR = Convert.ToByte(r);
iG = Convert.ToByte(g);
iB = Convert.ToByte(b);
}
else
{
// Number in hex format so convert to dec first
iR = Convert.ToByte(int.Parse(r, NumberStyles.HexNumber));
iG = Convert.ToByte(int.Parse(g, NumberStyles.HexNumber));
iB = Convert.ToByte(int.Parse(b, NumberStyles.HexNumber));
}
List<SwatchPixels> closepoints = new List<SwatchPixels>();
List<SwatchPixels> prevCp = new List<SwatchPixels>(closepoints);
var v = Convert.ToInt32(initVariance);
foreach (var p in _points)
{
if ((iR >= p.R - v && iR <= p.R + v) &&
(iG >= p.G - v && iG <= p.G + v) &&
(iB >= p.B - v && iB <= p.B + v))
{
closepoints.Add(p);
}
}
var c = closepoints.Count;
while (c > 10)
{
v = v - (v * dropPercent / 100);
closepoints = LoopAgain(closepoints, v, iR, iG, iB);
c = closepoints.Count;
//Variance.Text += c.ToString() + Environment.NewLine;
if (c == 0)
{
closepoints = new List<SwatchPixels>(prevCp);
break;
}
else
{
prevCp = new List<SwatchPixels>(closepoints);
}
}
if (c > 1)
return closepoints[0];
return null;
}
private List<SwatchPixels> LoopAgain(IEnumerable<SwatchPixels> cpoints, int v, int iR, int iG, int iB)
{
var closepoints = new List<SwatchPixels>();
foreach (var p in cpoints)
{
if ((iR >= p.R - v && iR <= p.R + v) &&
(iG >= p.G - v && iG <= p.G + v) &&
(iB >= p.B - v && iB <= p.B + v))
{
closepoints.Add(p);
}
}
return closepoints;
}
Here is my modified ColorCanvas. Changes include individual color rectangles for RGB, apply variances to a chosen color and use different color models for entry. Also you can copy the Hex contents to the clipboard with 1 click.
Upvotes: 1
Views: 1216
Reputation: 539
I am not totally sure, but let's see if i can help you:
Your "Color swatch" looks mostly, but not totally like a control to pick the HUE component of a HSL color selector, except the strange shadow. This also means there is only a subset of RGB in it, which you already know.
Now i see two main disadvantages in your picture:
when a certain color is given, it is impossible to determine whether to place the ellipse near the center or near the border, since the colors are the SAME at all radii.
the swatch starts at green and ends at blue. a Hue-Selector usualle starts at red and ends at red: The following one is from Inkscape:
Generally, you can easily extract the HSL components from an RGB-Color, for example with the following function:
public static void FromRGB(byte ARed, byte AGreen, byte ABlue, out double AHue, out double ASaturation, out double AValue)
{
double h = 0;
double s = 0;
double r = (double)ARed / 255;
double g = (double)AGreen / 255;
double b = (double)ABlue / 255;
double max = Math.Max(r, Math.Max(g, b));
double min = Math.Min(r, Math.Min(g, b));
if (r == g && g == b) h = 0;
if (r == max) h = 60.0 * ((g - b) / (max - min));
else if (g == max) h = 60.0 * (2.0 + (b - r) / (max - min));
else if (b == max) h = 60.0 * (4.0 + (r - g) / (max - min));
if (max != 0) s = (max - min) / max;
AHue = h;
ASaturation = s;
AValue = max;
}
Let's take a look at another color selector, which looks almost like yours:
This selector has some advantages:
To answer your actual question:
You will find the best matching pixel in your selector when you convert your RGB to HSV, set the saturation and value components to maximum and convert it back to RGB.
Hope it helps you a bit...
EDIT
As requested i'll provide an example about how to make an own rgb picker image:
if (w <= 0 || h <= 0) return;
WriteableBitmap wb = new WriteableBitmap(w, h, 96, 96, PixelFormats.Rgb24, null);
byte[] pixels = new byte[wb.PixelWidth * wb.PixelHeight * 3];
Color base_color = GetBaseColorFromHue(Hue * 1.5);
for (int y = 0; y < h; y++)
{
double pos_v = (double)y / (double)h;
for (int x = 0; x < w; x++)
{
double pos_s = (double)x / (double)w;
pixels[(y * wb.PixelWidth + x) * 3] = (byte)((base_color.R * (pos_s) + 255 * (1 - pos_s)) * (1.0 - pos_v));
pixels[(y * wb.PixelWidth + x) * 3 + 1] = (byte)((base_color.G * (pos_s) + 255 * (1 - pos_s)) * (1.0 - pos_v));
pixels[(y * wb.PixelWidth + x) * 3 + 2] = (byte)((base_color.B * (pos_s) + 255 * (1 - pos_s)) * (1.0 - pos_v));
}
}
wb.WritePixels(new Int32Rect(0, 0, wb.PixelWidth, wb.PixelHeight), pixels, wb.PixelWidth * wb.Format.BitsPerPixel / 8, 0);
Upvotes: 0
Reputation: 338
Don't reinvent the wheel, try with Extended WPF Toolkit™ Community Edition, you have two controls ColorPicker and ColorCanvas.
Upvotes: 1