Изменение цвета в изображении с сохранением текстуры - C#
Формулировка задачи:
Имеется изображение PNG с прозрачностью. Допустим вот такое
Имеется задача менять цвет этого изображения, конкретно отображаемых элементов, не затрагивая прозрачность и не теряя текстуру. При помощи ColorDialog указывать нужный цвет. Тупая замена пикселей на нужный цвет не подходит, ибо теряется текстура. В интернете была найден способ данного преобразования, основанный на переводе цвета в цветовую схему HSV. Конкретнее вот:
Вот только мне непонятно, как это собственно реализовать. Что значит пробегаем по цветам? То есть это будет таже самая попиксельная замена? И как собственно определять смещение и конструировать новый HSV-цвет?
заранее спасибо за пояснения и примеры кода.
P.S. ссылка на статью из цитаты http://vhbit.net/blog/2012/07/07/colorcube-color-replacement/
В приведенном примере происходит хитрая замена цвета со всеми оттенками. Для удобства используется работа с цветом в формате HSV, т.к. в этом случае гораздо легче выцеплять схожие цвета - они будут попадать в сектор.
Представление цвета в HSV:
...пробегаем по всем цветам, смотрим попадает ли HSV аналог этого цвета в нужный сектор и если да - запоминаем его смещение относительно центрального. После этого конструируем новый HSV цвет с таким же смещением относительно зеленого и конвертируем обратно в RGB.
Решение задачи: «Изменение цвета в изображении с сохранением текстуры»
textual
Листинг программы
private void button1_Click(object sender, EventArgs e)
{
float hue = hueBar.Value;
float saturation = saturBar.Value / 100f;
float brightness = brightBar.Value / 100f;
var result = ColorizeBitmap(bmp, hue, saturation, brightness, Colorizer);
pictureBox1.Image = result;
}
private Color Colorizer(Color from, float hue, float saturation, float brightness)
{
float oHue, oSatur, oBright;
ColorToHSV(from, out oHue, out oSatur, out oBright);
return ColorFromHSV(hue, (oSatur * saturation) + (1 - saturation), (oBright * brightness) + (1 - brightness));
}
private unsafe Bitmap ColorizeBitmap(Bitmap source, float hue, float saturation, float brightness, Func<Color, float, float, float, Color> func)
{
var result = new Bitmap(source);
BitmapData data = result.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, source.PixelFormat);
int PixelSize = 4;
for (int y = 0; y < data.Height; y++)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
for (int x = 0; x < data.Width; x++)
{
byte* r = &row[x * PixelSize + 2];
byte* g = &row[x * PixelSize + 1];
byte* b = &row[x * PixelSize];
Color newColor = func(Color.FromArgb(*r, *g, *b), hue, saturation, brightness);
*b = newColor.B;
*g = newColor.G;
*r = newColor.R;
}
}
result.UnlockBits(data);
return result;
}
public static void ColorToHSV(Color color, out float hue, out float saturation, out float value)
{
int max = Math.Max(color.R, Math.Max(color.G, color.B));
int min = Math.Min(color.R, Math.Min(color.G, color.B));
hue = color.GetHue();
saturation = (max == 0) ? 0 : 1f - (1f * min / max);
value = max / 255f;
}
public static Color ColorFromHSV(double hue, double saturation, double value)
{
int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
double f = hue / 60 - Math.Floor(hue / 60);
value = value * 255;
int v = Convert.ToInt32(value);
int p = Convert.ToInt32(value * (1 - saturation));
int q = Convert.ToInt32(value * (1 - f * saturation));
int t = Convert.ToInt32(value * (1 - (1 - f) * saturation));
switch (hi)
{
case 0:
return Color.FromArgb(255, v, t, p);
case 1:
return Color.FromArgb(255, q, v, p);
case 2:
return Color.FromArgb(255, p, v, t);
case 3:
return Color.FromArgb(255, p, q, v);
case 4:
return Color.FromArgb(255, t, p, v);
default:
return Color.FromArgb(255, v, p, q);
}
}