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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20856424/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 10:20:59  来源:igfitidea点击:

WPF - Modifying Image Colors on the Fly (C#)

wpfimagebackground-foreground

提问by Andez

Is it possible to modify the colors of an Image in WPF via code (or even using Templates)?

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

Suppose I have an image which I need to apply to a Tile - which will have a White Foreground color by default and a Transparent Background. Something like the following PNG (it is somewhere here!):

假设我有一个图像,我需要将它应用到一个平铺上——默认情况下它会有一个白色的前景色和一个透明的背景。类似于以下 PNG(它在这里的某个地方!):

Add New PNG

添加新的 PNG

Instead of adding different images - with different colors, I just want to manipulate the White - and say change it to Black.

而不是添加不同的图像 - 具有不同的颜色,我只想操纵白色 - 并说将其更改为黑色。

If it can be done, can someone give me a few pointers on what I need to do/look into.

如果可以完成,有人可以给我一些关于我需要做什么/调查的指示。

回答by Ian Griffiths

One way to do this would be to use the BitmapDecoderclass to retrieve the raw pixel data. You can then modify the pixels, and build a new WriteableBitmapfrom that modified pixel data:

一种方法是使用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;

This works after a fashion, but there's a problem. Here's how the result looks if I show it on a dark background:

这在时尚之后有效,但有一个问题。如果我在深色背景上显示结果,结果如下所示:

red cross on black background

黑色背景上的红十字

As you can see, it's got a sort of white border. What's happened here is that your white cross had anti-aliased edges, meaning that the pixels around the edges are actually a semi-transparent shade of grey.

如您所见,它有一种白色边框。这里发生的事情是,您的白色十字具有抗锯齿边缘,这意味着边缘周围的像素实际上是半透明的灰色阴影。

We can deal with that using a slightly more sophisticated technique in the pixel modification loop:

我们可以在像素修改循环中使用稍微复杂的技术来解决这个问题:

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

Here's how that looks on a black background:

这是在黑色背景上的样子:

red on black without white border

红底黑字,无白边

As you can see, that looks right. (OK, you wanted black not red, but the basic approach will be the same for any target colour.)

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

EDIT 2015/1/21As ar_j pointed out in the comments, the Prgba format requires premultiplication. For the example I've given it is actually safe to ignore it, but if you were modifying colour channels in any way other than by setting them to 0, you'd need to multiple each value by (a/255). E.g., as aj_j shows for the G channel: pixels[i * 4 + 1] = (byte)(g * a / 255);Since gis zero in my code, this makes no difference but for non-primary colours you would need to do it that way.

编辑 2015/1/21正如 ar_j 在评论中指出的那样,Prgba 格式需要预乘。对于我给出的示例,忽略它实际上是安全的,但是如果您以任何方式修改颜色通道而不是将它们设置为 0,则需要将每个值乘以(a/255). 例如,如 aj_j 显示的 G 通道:pixels[i * 4 + 1] = (byte)(g * a / 255);由于g在我的代码中为零,这没有区别,但对于非原色,您需要这样做。

Here it is on a gradient fill background just to show that the transparency is working:

这是在渐变填充背景上,只是为了显示透明度正在工作:

red cross on gradient background

渐变背景上的红十字

You could also write out the modified version:

你也可以写出修改后的版本:

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

Here's the result:

结果如下:

modified PNG

修改后的PNG

You can see the red cross, and it'll be on top of whatever background colour StackOverflow is using. (White, as I write this, but maybe they'll redesign one day.)

你可以看到红十字,它会在 StackOverflow 使用的任何背景颜色之上。(怀特,在我写这篇文章的时候,但也许他们有一天会重新设计。)

Whether this will work for the images you want to use is harder to know for certain, because it depends on what your definition of 'white' is - depending on how your images were produced, you may find things are ever so slightly off-white (particularly around the edges), and you may need further tweaking. But the basic approach should be OK.

这是否适用于您想要使用的图像更难确定,因为这取决于您对“白色”的定义 - 根据您的图像的制作方式,您可能会发现东西有点灰白色(尤其是边缘),您可能需要进一步调整。不过基本的做法应该没问题。