I want to be able to blend two or more Color objects. Say I start with a semi-transparent red:
var red = Color.FromArgb(140, 255, 0, 0);
I would then like to blend a semi-transparent green into it:
var green = Color.FromArgb(140, 0, 255, 0);
The colour mixing code snippets that I've come across usually result in a shade of brown here, but what I'm really looking for is an effect like what you'd get if you were to draw one colour over another in (say) Paint.Net, resulting in more of a dark green:-
I'd then like to mix in a third colour, say a semi-transparent blue:
var blue = Color.FromArgb(140, 0, 0, 255);
This time, I would like to end up with the teal-ish colour seen in the centre of this image:
(Again if I try to use the usual code snippets then I usually end up with a grey or brown).
As an aside, it's probably worth mentioning that the following code pretty much achieves what I'm after:
using (var bitmap = new Bitmap(300, 300))
{
using (var g = Graphics.FromImage(bitmap))
{
var c1 = Color.FromArgb(alpha: 128, red: 255, green: 0, blue: 0);
var c2 = Color.FromArgb(alpha: 200, red: 0, green: 255, blue: 0);
var c3 = Color.FromArgb(alpha: 100, red: 0, green: 0, blue: 255);
g.FillRectangle(new SolidBrush(c1), 100, 0, 100, 100);
g.FillRectangle(new SolidBrush(c2), 125, 75, 100, 100);
g.FillRectangle(new SolidBrush(c3), 75, 50, 100, 100);
}
}
I guess it's the code equivalent of my Paint.Net steps, drawing one semi-transparent coloured rectangle over another. However I want to be able to calculate the final blended colour, and use that in one call to g.FillRectangle(), rather than call the method three times to achieve the blending effect.
Finally, this is an example of the kind of colour mixing code snippet that I referred to earlier, that typically yield shades of brown when I use them for my colours:-
private Color Blend(Color c1, Color c2)
{
var aOut = c1.A + (c1.A * (255 - c1.A) / 255);
var rOut = (c1.R * c1.A + c2.R * c2.A * (255 - c1.A) / 255) / aOut;
var gOut = (c1.G * c1.A + c2.G * c2.A * (255 - c1.A) / 255) / aOut;
var bOut = (c1.B * c1.A + c2.B * c2.A * (255 - c1.A) / 255) / aOut;
return Color.FromArgb(aOut, rOut, gOut, bOut);
}
Thanks to the earlier comment from #TaW (otherwise I'd never have solved this!), I was able to write a method to blend two colours in the style of Paint.net, Paintshop, etc:-
var r = Color.FromArgb(140, 255, 0, 0);
var g = Color.FromArgb(140, 0, 255, 0);
var b = Color.FromArgb(140, 0, 0, 255);
// How to use:
var rg= AlphaComposite(r, g);
var rb= AlphaComposite(r, b);
var gb= AlphaComposite(g, b);
var rgb= AlphaComposite(AlphaComposite(r, g), b);
...
// A cache of all opacity values (0-255) scaled down to 0-1 for performance
private readonly float[] _opacities = Enumerable.Range(0, 256)
.Select(o => o / 255f)
.ToArray();
private Color AlphaComposite(Color c1, Color c2)
{
var opa1 = _opacities[c1.A];
var opa2 = _opacities[c2.A];
var ar = opa1 + opa2 - (opa1 * opa2);
var asr = opa2 / ar;
var a1 = 1 - asr;
var a2 = asr * (1 - opa1);
var ab = asr * opa1;
var r = (byte)(c1.R * a1 + c2.R * a2 + c2.R * ab);
var g = (byte)(c1.G * a1 + c2.G * a2 + c2.G * ab);
var b = (byte)(c1.B * a1 + c2.B * a2 + c2.B * ab);
return Color.FromArgb((byte)(ar * 255), r, g, b);
}
Related
I have this little code to use AddArc() method in a label, but when I execute the code the label disappears. I believe it is the numbers I have used, I followed instructions from the Windows documentation and it had these parameters there too.
GraphicsPath gp = new GraphicsPath();
Rectangle rec = new Rectangle(20, 20, 50, 100);
gp.AddArc(rec, 0 , 180);
label2.Region = new Region(gp);
label2.Invalidate();
I used another code to make the correct way or curve in a text
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var center = new Point(Width / 2, Height / 2);
var radius = Math.Min(Width, Height) / 3;
var text = "Hello";//txtUp.Text;
var font = new Font(FontFamily.GenericSansSerif, 24, FontStyle.Bold);
for (var i = 0; i < text.Length; ++i)
{
var c = new String(text[i], 1);
var size = e.Graphics.MeasureString(c, font);
var charRadius = radius + size.Height;
var angle = (((float)i / text.Length) - 2);
var x = (int)(center.X + Math.Cos(angle) * charRadius);
var y = (int)(center.Y + Math.Sin(angle) * charRadius);
e.Graphics.TranslateTransform(x, y);
e.Graphics.RotateTransform((float)(90 + 360 * angle / (2 * Math.PI)));
e.Graphics.DrawString(c, font, Brushes.Red, 0, 0);
e.Graphics.ResetTransform();
e.Graphics.DrawArc(new Pen(Brushes.Transparent, 2.0f), center.X - radius, center.Y - radius, radius * 2, radius * 2, 0, 360);
}
}
but it wont show in front of a panel is it possible.
This is what it looks like:
Is it possible to move that text in front of the green circle?
Let's say I have an array with colors (with the whole color spectrum, from red to red.). A shorter version would look like this:
public Color[] ColorArray = new Color[360] { Color.FromArgb(255, 245, 244, 242), Color.FromArgb(255, 245, 244, 240), Color.FromArgb(255, 245, 244, 238) }
Now if I have a seperate
Color object (Color c = Color.FromArgb(255, 14, 4, 5))
How can I get the value in the Array that is the closest to the selected color? And is this even possible?
Color distance is not a precisely defined thing. So here are three methods to measure it:
One method that checks only the hues of the colors, ignoring both saturation and brightness
One that only measures the direct distance in RGB space
And one that weighs hue, saturation and brightness in some way.
Obviously you may want to change the magic numbers in the 3rd measurement: hue is in 0-360, brightness and saturation are in 0-1, so with these numbers hue weighs about 3.6 times stronger than saturation and brightness..
Update: The original solution I posted contained several errors:
The Linq I used didn't find the closest but the closestFromBelow; this meant a 50% chance of being off by one.
In some places I used the color.GetBrightness() method. This is, to put it mildly, totally useless. To wit: Blue and Yellow have the same value of 0.5!
The values for hue go from 0-360, but of course they wrap around! I missed that completely..
I have replaced most of the original answer with corrected code:
These now are the new versions of the methods, each returning the index of the closest match found:
// closed match for hues only:
int closestColor1(List<Color> colors, Color target)
{
var hue1 = target.GetHue();
var diffs = colors.Select(n => getHueDistance(n.GetHue(), hue1));
var diffMin = diffs.Min(n => n);
return diffs.ToList().FindIndex(n => n == diffMin);
}
// closed match in RGB space
int closestColor2(List<Color> colors, Color target)
{
var colorDiffs = colors.Select(n => ColorDiff(n, target)).Min(n =>n);
return colors.FindIndex(n => ColorDiff(n, target) == colorDiffs);
}
// weighed distance using hue, saturation and brightness
int closestColor3(List<Color> colors, Color target)
{
float hue1 = target.GetHue();
var num1 = ColorNum(target);
var diffs = colors.Select(n => Math.Abs(ColorNum(n) - num1) +
getHueDistance(n.GetHue(), hue1) );
var diffMin = diffs.Min(x => x);
return diffs.ToList().FindIndex(n => n == diffMin);
}
A few helper functions:
// color brightness as perceived:
float getBrightness(Color c)
{ return (c.R * 0.299f + c.G * 0.587f + c.B *0.114f) / 256f;}
// distance between two hues:
float getHueDistance(float hue1, float hue2)
{
float d = Math.Abs(hue1 - hue2); return d > 180 ? 360 - d : d; }
// weighed only by saturation and brightness (from my trackbars)
float ColorNum(Color c) { return c.GetSaturation() * factorSat +
getBrightness(c) * factorBri; }
// distance in RGB space
int ColorDiff(Color c1, Color c2)
{ return (int ) Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R)
+ (c1.G - c2.G) * (c1.G - c2.G)
+ (c1.B - c2.B) * (c1.B - c2.B)); }
Here is the handy little helper I used for the screenshot texts:
Brush tBrush(Color c) {
return getBrightness(c) < 0.5 ? Brushes.White : Brushes.Black; }
I have updated the screenshot to display not only 13 colors but also a number of mostly reddish colors for testing; all colors are shown with their values for hue, saturation and brightness. The last three numbers are the results of the three methods.
As you can see, the simple distance method is quite misleading hue-wise for bright and non-saturated colors: The last color (Ivory) is in fact a bright and pale yellow!
The third method which gauges all color properties is best imo. You should play around with the gauging numbers, though!
In the end it really depends on what you want to achieve; if, as it seems, you only care about the hues of the colors, simply go for the first method! You can call it, using your array like this:
int indexInArray = closestColor1(clist.ToList(), someColor);
For more on color distances see Wikipedia!
// the colors I used:
// your array
Color[] clist = new Color[13];
clist[0] = Color.Blue;
clist[1] = Color.BlueViolet;
clist[2] = Color.Magenta;
clist[3] = Color.Purple;
clist[4] = Color.Red;
clist[5] = Color.Tomato;
clist[6] = Color.Orange;
clist[7] = Color.Yellow;
clist[8] = Color.YellowGreen;
clist[9] = Color.Green;
clist[10] = Color.SpringGreen;
clist[11] = Color.Cyan;
clist[12] = Color.Ivory;
// and a list of color to test:
List<Color> targets = new List<Color>();
targets.Add(Color.Pink);
targets.Add(Color.OrangeRed);
targets.Add(Color.LightPink);
targets.Add(Color.DarkSalmon);
targets.Add(Color.LightCoral);
targets.Add(Color.DarkRed);
targets.Add(Color.IndianRed);
targets.Add(Color.LavenderBlush);
targets.Add(Color.Lavender);
Try this:
static void Main()
{
Color[] ColorArray =
{
Color.FromArgb(255, 245, 244, 242),
Color.FromArgb(255, 245, 244, 240),
Color.FromArgb(255, 245, 244, 238)
};
var closest = GetClosestColor(ColorArray, Color.FromArgb(255, 245, 244, 241));
Console.WriteLine(closest);
}
private static Color GetClosestColor(Color[] colorArray, Color baseColor)
{
var colors = colorArray.Select(x => new {Value = x, Diff = GetDiff(x, baseColor)}).ToList();
var min = colors.Min(x => x.Diff);
return colors.Find(x => x.Diff == min).Value;
}
private static int GetDiff(Color color, Color baseColor)
{
int a = color.A - baseColor.A,
r = color.R - baseColor.R,
g = color.G - baseColor.G,
b = color.B - baseColor.B;
return a*a + r*r + g*g + b*b;
}
here I interpret closest as Euclidean distance in ARGB space
I am working on an app and one of the main functions is it adjusts a gray scale image and apply a color to it.
this is the main math I am using to calculate this:
Color replaceWhite = Color.FromArgb(255, byte.Parse(acent.Substring(0, 2),
System.Globalization.NumberStyles.HexNumber), byte.Parse(acent.Substring(2, 2),
System.Globalization.NumberStyles.HexNumber), byte.Parse(acent.Substring(4, 2),
System.Globalization.NumberStyles.HexNumber));
WriteableBitmap source = await GetImageFile(sourceImage);
byte[] byteArray = null;
using (Stream stream = source.PixelBuffer.AsStream())
{
long streamLength = stream.Length;
byteArray = new byte[streamLength];
await stream.ReadAsync(byteArray, 0, byteArray.Length);
if (streamLength > 0)
{
for (int i = 0; i < streamLength; i += 4)
{
if (byteArray[i + 3] != 0)
{
int b = Convert.ToInt32(byteArray[i]);
int g = Convert.ToInt32(byteArray[i + 1]);
int r = Convert.ToInt32(byteArray[i + 2]);
int rB = ((((b * replaceBlack.B) / 255) + (((255 - b) * replaceWhite.B) / 255)) / 2);
int rG = ((((g * replaceBlack.G) / 255) + (((255 - g) * replaceWhite.G) / 255)) / 2);
int rR = ((((r * replaceBlack.R) / 255) + (((255 - r) * replaceWhite.R) / 255)) / 2);
byte blue = Convert.ToByte(rB);
byte green = Convert.ToByte(rG);
byte red = Convert.ToByte(rR);
byteArray[i] = blue; // Blue
byteArray[i + 1] = green; // Green
byteArray[i + 2] = red; // Red
}
}
}
}
if (byteArray != null)
{
WriteableBitmap result = await PixelBufferToWritableBitmap(byteArray, source.PixelWidth, source.PixelHeight);
StorageFile image = await WriteableBitmapToStorageFile(result, fileName, folderName);
BitmapImage imageSource = await StorageFileToBitmapImage(image);
return imageSource;
}
The code luckily works however when an image is passing through it the colors appear much darker than the original. I know it is most likely a problem with the mathe but I cant pinpoint where it is.
its worth mentioning I am using color stored in the app settings:
settings.Values["FlatWallpaperColor"] = theme.ColorCode;
string color = theme.ColorCode.Replace("#", "");
if (color.Length == 6)
{
SelectFlatWallpaperColorButton.Background = new SolidColorBrush (ColorHelper.FromArgb(255,
byte.Parse(color.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
byte.Parse(color.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
byte.Parse(color.Substring(4, 2), System.Globalization.NumberStyles.HexNumber)));
}
Does anyone have experience in this and can help?
The problem is in the calculation - you are taking a "mean" value of what already is a weighted average. You rather need to use the weighted average itself for each of the components of the color. So for example for rb:
int rb = (byte)((b/255.0)*replaceBlack.B + ((255-b)/255.0)*replaceWhite.B));
So we calculate how much intensive should be the replaceBlack color in by (dividing the amount of "white" in the original color by 255, and the same thing we do for replaceWhite. Then we can safely sum those two numbers, because it can never go above 255 (at worst you will be adding up r * 255 + ( 1 - r ) * 255 = 255) and if it does because some double rounding, the cast will still cut off the decimal part so we will have at most 255.
The original code was almost correct, but it basically used half the correct value - so everything got darker. Also using double values for the calculation is better, as you will avoid potential rounding errors.
Let's say I have an array with colors (with the whole color spectrum, from red to red.). A shorter version would look like this:
public Color[] ColorArray = new Color[360] { Color.FromArgb(255, 245, 244, 242), Color.FromArgb(255, 245, 244, 240), Color.FromArgb(255, 245, 244, 238) }
Now if I have a seperate
Color object (Color c = Color.FromArgb(255, 14, 4, 5))
How can I get the value in the Array that is the closest to the selected color? And is this even possible?
Color distance is not a precisely defined thing. So here are three methods to measure it:
One method that checks only the hues of the colors, ignoring both saturation and brightness
One that only measures the direct distance in RGB space
And one that weighs hue, saturation and brightness in some way.
Obviously you may want to change the magic numbers in the 3rd measurement: hue is in 0-360, brightness and saturation are in 0-1, so with these numbers hue weighs about 3.6 times stronger than saturation and brightness..
Update: The original solution I posted contained several errors:
The Linq I used didn't find the closest but the closestFromBelow; this meant a 50% chance of being off by one.
In some places I used the color.GetBrightness() method. This is, to put it mildly, totally useless. To wit: Blue and Yellow have the same value of 0.5!
The values for hue go from 0-360, but of course they wrap around! I missed that completely..
I have replaced most of the original answer with corrected code:
These now are the new versions of the methods, each returning the index of the closest match found:
// closed match for hues only:
int closestColor1(List<Color> colors, Color target)
{
var hue1 = target.GetHue();
var diffs = colors.Select(n => getHueDistance(n.GetHue(), hue1));
var diffMin = diffs.Min(n => n);
return diffs.ToList().FindIndex(n => n == diffMin);
}
// closed match in RGB space
int closestColor2(List<Color> colors, Color target)
{
var colorDiffs = colors.Select(n => ColorDiff(n, target)).Min(n =>n);
return colors.FindIndex(n => ColorDiff(n, target) == colorDiffs);
}
// weighed distance using hue, saturation and brightness
int closestColor3(List<Color> colors, Color target)
{
float hue1 = target.GetHue();
var num1 = ColorNum(target);
var diffs = colors.Select(n => Math.Abs(ColorNum(n) - num1) +
getHueDistance(n.GetHue(), hue1) );
var diffMin = diffs.Min(x => x);
return diffs.ToList().FindIndex(n => n == diffMin);
}
A few helper functions:
// color brightness as perceived:
float getBrightness(Color c)
{ return (c.R * 0.299f + c.G * 0.587f + c.B *0.114f) / 256f;}
// distance between two hues:
float getHueDistance(float hue1, float hue2)
{
float d = Math.Abs(hue1 - hue2); return d > 180 ? 360 - d : d; }
// weighed only by saturation and brightness (from my trackbars)
float ColorNum(Color c) { return c.GetSaturation() * factorSat +
getBrightness(c) * factorBri; }
// distance in RGB space
int ColorDiff(Color c1, Color c2)
{ return (int ) Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R)
+ (c1.G - c2.G) * (c1.G - c2.G)
+ (c1.B - c2.B) * (c1.B - c2.B)); }
Here is the handy little helper I used for the screenshot texts:
Brush tBrush(Color c) {
return getBrightness(c) < 0.5 ? Brushes.White : Brushes.Black; }
I have updated the screenshot to display not only 13 colors but also a number of mostly reddish colors for testing; all colors are shown with their values for hue, saturation and brightness. The last three numbers are the results of the three methods.
As you can see, the simple distance method is quite misleading hue-wise for bright and non-saturated colors: The last color (Ivory) is in fact a bright and pale yellow!
The third method which gauges all color properties is best imo. You should play around with the gauging numbers, though!
In the end it really depends on what you want to achieve; if, as it seems, you only care about the hues of the colors, simply go for the first method! You can call it, using your array like this:
int indexInArray = closestColor1(clist.ToList(), someColor);
For more on color distances see Wikipedia!
// the colors I used:
// your array
Color[] clist = new Color[13];
clist[0] = Color.Blue;
clist[1] = Color.BlueViolet;
clist[2] = Color.Magenta;
clist[3] = Color.Purple;
clist[4] = Color.Red;
clist[5] = Color.Tomato;
clist[6] = Color.Orange;
clist[7] = Color.Yellow;
clist[8] = Color.YellowGreen;
clist[9] = Color.Green;
clist[10] = Color.SpringGreen;
clist[11] = Color.Cyan;
clist[12] = Color.Ivory;
// and a list of color to test:
List<Color> targets = new List<Color>();
targets.Add(Color.Pink);
targets.Add(Color.OrangeRed);
targets.Add(Color.LightPink);
targets.Add(Color.DarkSalmon);
targets.Add(Color.LightCoral);
targets.Add(Color.DarkRed);
targets.Add(Color.IndianRed);
targets.Add(Color.LavenderBlush);
targets.Add(Color.Lavender);
Try this:
static void Main()
{
Color[] ColorArray =
{
Color.FromArgb(255, 245, 244, 242),
Color.FromArgb(255, 245, 244, 240),
Color.FromArgb(255, 245, 244, 238)
};
var closest = GetClosestColor(ColorArray, Color.FromArgb(255, 245, 244, 241));
Console.WriteLine(closest);
}
private static Color GetClosestColor(Color[] colorArray, Color baseColor)
{
var colors = colorArray.Select(x => new {Value = x, Diff = GetDiff(x, baseColor)}).ToList();
var min = colors.Min(x => x.Diff);
return colors.Find(x => x.Diff == min).Value;
}
private static int GetDiff(Color color, Color baseColor)
{
int a = color.A - baseColor.A,
r = color.R - baseColor.R,
g = color.G - baseColor.G,
b = color.B - baseColor.B;
return a*a + r*r + g*g + b*b;
}
here I interpret closest as Euclidean distance in ARGB space
How do I achieve this kind of color replacement programmatically?
So this is the function I have used to replace a pixel:
Color.FromArgb(
oldColorInThisPixel.R + (byte)((1 - oldColorInThisPixel.R / 255.0) * colorToReplaceWith.R),
oldColorInThisPixel.G + (byte)((1 - oldColorInThisPixel.G / 255.0) * colorToReplaceWith.G),
oldColorInThisPixel.B + (byte)((1 - oldColorInThisPixel.B / 255.0) * colorToReplaceWith.B)
)
Thank you, CodeInChaos!
The formula for calculating the new pixel is:
newColor.R = OldColor;
newColor.G = OldColor;
newColor.B = 255;
Generalizing to arbitrary colors:
I assume you want to map white to white and black to that color. So the formula is newColor = TargetColor + (White - TargetColor) * Input
newColor.R = OldColor + (1 - oldColor / 255.0) * TargetColor.R;
newColor.G = OldColor + (1 - oldColor / 255.0) * TargetColor.G;
newColor.B = OldColor + (1 - oldColor / 255.0) * TargetColor.B;
And then just iterate over the pixels of the image(byte array) and write them to a new RGB array. There are many threads on how to copy an image into a byte array and manipulate it.
Easiest would be to use ColorMatrix for processing images, you will even be able to process on fly preview of desired effect - this is how many color filters are made in graphic editing applications. Here and here you can find introductions to color effects using Colormatrix in C#. By using ColorMatrix you can make colorizing filter like you want, as well as sepia, black/white, invert, range, luminosity, contrast, brightness, levels (by multi-pass) etc.
EDIT: Here is example (update - fixed color matrix to shift darker values into blue instead of previous zeroing other than blue parts - and - added 0.5f to blue because on picture above black is changed into 50% blue):
var cm = new ColorMatrix(new float[][]
{
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 1, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0.5f, 0, 1}
});
var img = Image.FromFile("C:\\img.png");
var ia = new ImageAttributes();
ia.SetColorMatrix(cm);
var bmp = new Bitmap(img.Width, img.Height);
var gfx = Graphics.FromImage(bmp);
var rect = new Rectangle(0, 0, img.Width, img.Height);
gfx.DrawImage(img, rect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, ia);
bmp.Save("C:\\processed.png", ImageFormat.Png);
You'll want to use a ColorMatrix here. The source image is grayscale, all its R, G and B values are equal. Then it is just a matter of replacing black with RGB = (0, 0, 255) for dark blue, white with RGB = (255, 255, 255) to get white. The matrix thus can look like this:
1 0 0 0 0 // not changing red
0 1 0 0 0 // not changing green
0 0 0 0 0 // B = 0
0 0 0 1 0 // not changing alpha
0 0 1 0 1 // B = 255
This sample form reproduces the right side image:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private Image mImage;
protected override void OnPaint(PaintEventArgs e) {
if (mImage != null) e.Graphics.DrawImage(mImage, Point.Empty);
base.OnPaint(e);
}
private void button1_Click(object sender, EventArgs e) {
using (var srce = Image.FromFile(#"c:\temp\grayscale.png")) {
if (mImage != null) mImage.Dispose();
mImage = new Bitmap(srce.Width, srce.Height);
float[][] coeff = {
new float[] { 1, 0, 0, 0, 0 },
new float[] { 0, 1, 0, 0, 0 },
new float[] { 0, 0, 0, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 1, 0, 1 }};
ColorMatrix cm = new ColorMatrix(coeff);
var ia = new ImageAttributes();
ia.SetColorMatrix(new ColorMatrix(coeff));
using (var gr = Graphics.FromImage(mImage)) {
gr.DrawImage(srce, new Rectangle(0, 0, mImage.Width, mImage.Height),
0, 0, mImage.Width, mImage.Height, GraphicsUnit.Pixel, ia);
}
}
this.Invalidate();
}
}
Depends a lot on what your image format is and what your final format is going to be.
Also depends on what tool you wanna use.
You may use:
GDI
GD+
Image Processing library such as OpenCV
GDI is quite fast but can be quite cumbersome. You need to change the palette.
GDI+ is exposed in .NET and can be slower but easier.
OpenCV is great but adds dependency.
(UPDATE)
This code changes the image to blue-scales instead of grey-scales - image format is 32 bit ARGB:
private static unsafe void ChangeColors(string imageFileName)
{
const int noOfChannels = 4;
Bitmap img = (Bitmap) Image.FromFile(imageFileName);
BitmapData data = img.LockBits(new Rectangle(0,0,img.Width, img.Height), ImageLockMode.ReadWrite, img.PixelFormat);
byte* ptr = (byte*) data.Scan0;
for (int j = 0; j < data.Height; j++)
{
byte* scanPtr = ptr + (j * data.Stride);
for (int i = 0; i < data.Stride; i++, scanPtr++)
{
if (i % noOfChannels == 3)
{
*scanPtr = 255;
continue;
}
if (i % noOfChannels != 0)
{
*scanPtr = 0;
}
}
}
img.UnlockBits(data);
img.Save(Path.Combine( Path.GetDirectoryName(imageFileName), "result.png"), ImageFormat.Png);
}
This code project article covers this and more: http://www.codeproject.com/KB/GDI-plus/Image_Processing_Lab.aspx
It uses the AForge.NET library to do a Hue filter on an image for a similar effect:
// create filter
AForge.Imaging.Filters.HSLFiltering filter =
new AForge.Imaging.Filters.HSLFiltering( );
filter.Hue = new IntRange( 340, 20 );
filter.UpdateHue = false;
filter.UpdateLuminance = false;
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );
It also depends on what you want: do you want to keep the original and only adjust the way it is shown? An effect or pixelshader in WPF might do the trick and be very fast.
If any Android devs end up looking at this, this is what I came up with to gray scale and tint an image using CodesInChaos's formula and the android graphics classes ColorMatrix and ColorMatrixColorFilter.
Thanks for the help!
public static ColorFilter getColorFilter(Context context) {
final int tint = ContextCompat.getColor(context, R.color.tint);
final float R = Color.red(tint);
final float G = Color.green(tint);
final float B = Color.blue(tint);
final float Rs = R / 255;
final float Gs = G / 255;
final float Bs = B / 255;
// resultColor = oldColor + (1 - oldColor/255) * tintColor
final float[] colorTransform = {
1, -Rs, 0, 0, R,
1, -Gs, 0, 0, G,
1, -Bs, 0, 0, B,
0, 0, 0, 0.9f, 0};
final ColorMatrix grayMatrix = new ColorMatrix();
grayMatrix.setSaturation(0f);
grayMatrix.postConcat(new ColorMatrix(colorTransform));
return new ColorMatrixColorFilter(grayMatrix);
}
The ColorFilter can then be applied to an ImageView
imageView.setColorFilter(getColorFilter(imageView.getContext()));