WPF - 即时修改图像颜色(C#)

时间:2013-12-31 11:10:50

标签: wpf image background-foreground

是否可以通过代码(甚至使用模板)修改WPF中图像的颜色?

假设我有一个我需要应用于平铺的图像 - 默认情况下将具有白色前景色和透明背景。像下面的PNG(它在某处!):

Add New PNG

不是添加不同的图像 - 使用不同的颜色,我只想操纵白色 - 然后将其更改为黑色。

如果可以做到,有人可以给我一些关于我需要做什么/研究的指示。

1 个答案:

答案 0 :(得分:8)

执行此操作的一种方法是使用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

如您所见,这看起来是正确的。 (好吧,你想黑色不是红色,但基本方法对于任何目标颜色都是一样的。)

EDIT 2015/1/21 正如ar_j在评论中指出的那样,Prgba格式需要预乘。对于我给出的示例实际上可以安全地忽略它,但如果您以任何方式修改颜色通道而不是将它们设置为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正在使用的任何背景颜色之上。 (白色,正如我写的那样,但也许有一天他们会重新设计。)

这是否适用于你想要使用的图像更难以确定,因为它取决于你对“白色”的定义 - 取决于你的图像是如何产生的,你可能会发现事情是如此轻微灰白色(特别是在边缘附近),您可能需要进一步调整。但基本方法应该没问题。