WPF-изменение цвета изображения на лету (C#)

можно ли изменить цвета изображения в WPF с помощью кода (или даже с помощью шаблонов)?

Предположим, у меня есть изображение, которое мне нужно применить к плитке, которая по умолчанию будет иметь белый цвет переднего плана и прозрачный фон. Что-то вроде следующего PNG (он где-то здесь!):

Add New 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;

это работает в некотором роде, но есть проблема. Вот как выглядит результат, если я покажу его на темном фоне:

red cross on black background

как вы можете видеть, у него есть своего рода белая граница. Тут случилось, что ваш белый крест сглаженные края, смысл что пиксели по краям на самом деле полупрозрачный оттенок серого.

мы можем справиться с этим, используя немного более сложную технику в цикле модификации пикселей:

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;
}

вот как это выглядит на черном фоне:

red on black without white border

как вы можете видеть, это выглядит правильно. (Хорошо, вы хотели черный, а не красный, но основной подход будет одинаковым для любого целевого цвета.)

редактировать 2015/1/21 As ar_j указал в комментариях, формат Prgba требует premultiplication. Для примера, который я привел, на самом деле безопасно игнорировать его, но если вы изменяете цветовые каналы каким-либо образом, кроме как установив их в 0, вам нужно будет умножить каждое значение на (a/255). Например, как показывает aj_j для канала G:pixels[i * 4 + 1] = (byte)(g * a / 255); С g равен нулю в моем коде, это не имеет значения, но для неосновных цветов вам нужно будет сделать это таким образом.

вот он на градиентной заливке фон просто чтобы показать, что прозрачность работает:

red cross on gradient background

вы также можете написать измененную версию:

var enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmp));
using (Stream pngStream = File.OpenWrite(@"c:\temp\modified.png"))
{
    enc.Save(pngStream);
}

вот результат:

modified PNG

вы можете увидеть красный крест, и он будет поверх любого цвета фона, который использует StackOverflow. (Белый, когда я пишу это, но, возможно, однажды они переделают дизайн.)

будет ли это работать для изображений, которые вы хотите использовать, труднее знать конечно, потому что это зависит от вашего определения "белого" - в зависимости от того, как были созданы ваши изображения, вы можете обнаружить, что вещи немного не белые (особенно по краям), и вам может потребоваться дополнительная настройка. Но основной подход должен быть в порядке.