WPF-изменение цвета изображения на лету (C#)
можно ли изменить цвета изображения в WPF с помощью кода (или даже с помощью шаблонов)?
Предположим, у меня есть изображение, которое мне нужно применить к плитке, которая по умолчанию будет иметь белый цвет переднего плана и прозрачный фон. Что-то вроде следующего PNG (он где-то здесь!):
вместо добавления разных изображений-с разными цветами, я просто хочу манипулировать белым-и сказать, измените его на Черный.
Если это можно сделать, может кто-нибудь дать мне несколько советов о том, что мне нужно сделать/посмотреть.
1 ответов
один из способов сделать это будет использовать BitmapDecoder
класс для извлечения необработанных данных пикселей. Затем вы можете изменить пиксели и построить новый WriteableBitmap
из этих измененных данных пикселей:
// Copy pixel colour values from existing image.
// (This loads them from an embedded resource. BitmapDecoder can work with any Stream, though.)
StreamResourceInfo x = Application.GetResourceStream(new Uri(BaseUriHelper.GetBaseUri(this), "Image.png"));
BitmapDecoder dec = BitmapDecoder.Create(x.Stream, BitmapCreateOptions.None, BitmapCacheOption.Default);
BitmapFrame image = dec.Frames[0];
byte[] pixels = new byte[image.PixelWidth * image.PixelHeight * 4];
image.CopyPixels(pixels, image.PixelWidth*4, 0);
// Modify the white pixels
for (int i = 0; i < pixels.Length/4; ++i)
{
byte b = pixels[i * 4];
byte g = pixels[i * 4 + 1];
byte r = pixels[i * 4 + 2];
byte a = pixels[i * 4 + 3];
if (r == 255 &&
g == 255 &&
b == 255 &&
a == 255)
{
// Change it to red.
g = 0;
b = 0;
pixels[i * 4 + 1] = g;
pixels[i * 4] = b;
}
}
// Write the modified pixels into a new bitmap and use that as the source of an Image
var bmp = new WriteableBitmap(image.PixelWidth, image.PixelHeight, image.DpiX, image.DpiY, PixelFormats.Pbgra32, null);
bmp.WritePixels(new Int32Rect(0, 0, image.PixelWidth, image.PixelHeight), pixels, image.PixelWidth*4, 0);
img.Source = bmp;
это работает в некотором роде, но есть проблема. Вот как выглядит результат, если я покажу его на темном фоне:
как вы можете видеть, у него есть своего рода белая граница. Тут случилось, что ваш белый крест сглаженные края, смысл что пиксели по краям на самом деле полупрозрачный оттенок серого.
мы можем справиться с этим, используя немного более сложную технику в цикле модификации пикселей:
if ((r == 255 &&
g == 255 &&
b == 255 &&
a == 255) ||
(a != 0 && a != 255 &&
r == g && g == b && r != 0))
{
// Change it to red.
g = 0;
b = 0;
pixels[i * 4 + 1] = g;
pixels[i * 4] = b;
}
вот как это выглядит на черном фоне:
как вы можете видеть, это выглядит правильно. (Хорошо, вы хотели черный, а не красный, но основной подход будет одинаковым для любого целевого цвета.)
редактировать 2015/1/21 As ar_j указал в комментариях, формат Prgba требует premultiplication. Для примера, который я привел, на самом деле безопасно игнорировать его, но если вы изменяете цветовые каналы каким-либо образом, кроме как установив их в 0, вам нужно будет умножить каждое значение на (a/255)
. Например, как показывает aj_j для канала G:pixels[i * 4 + 1] = (byte)(g * a / 255);
С g
равен нулю в моем коде, это не имеет значения, но для неосновных цветов вам нужно будет сделать это таким образом.
вот он на градиентной заливке фон просто чтобы показать, что прозрачность работает:
вы также можете написать измененную версию:
var enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmp));
using (Stream pngStream = File.OpenWrite(@"c:\temp\modified.png"))
{
enc.Save(pngStream);
}
вот результат:
вы можете увидеть красный крест, и он будет поверх любого цвета фона, который использует StackOverflow. (Белый, когда я пишу это, но, возможно, однажды они переделают дизайн.)
будет ли это работать для изображений, которые вы хотите использовать, труднее знать конечно, потому что это зависит от вашего определения "белого" - в зависимости от того, как были созданы ваши изображения, вы можете обнаружить, что вещи немного не белые (особенно по краям), и вам может потребоваться дополнительная настройка. Но основной подход должен быть в порядке.