是否可以通过代码(甚至使用模板)修改 WPF 中图像的颜色?
假设我有一个需要应用到 Tile 的图像 - 默认情况下将具有白色前景颜色和透明背景。类似于以下 PNG 的东西(它在这里的某个地方!):
而不是添加不同的图像 - 使用不同的颜色,我只想操纵白色 - 并说将其更改为黑色。
如果可以做到,有人可以给我一些关于我需要做什么/调查的指示。
是否可以通过代码(甚至使用模板)修改 WPF 中图像的颜色?
假设我有一个需要应用到 Tile 的图像 - 默认情况下将具有白色前景颜色和透明背景。类似于以下 PNG 的东西(它在这里的某个地方!):
而不是添加不同的图像 - 使用不同的颜色,我只想操纵白色 - 并说将其更改为黑色。
如果可以做到,有人可以给我一些关于我需要做什么/调查的指示。
一种方法是使用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正如 ar_j 在评论中指出的那样,Prgba 格式需要预乘。对于我给出的示例,忽略它实际上是安全的,但是如果您以任何方式修改颜色通道,而不是将它们设置为 0,则需要将每个值乘以(a/255)
. 例如,如 G 通道的 aj_j 所示: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 使用的任何背景颜色之上。(怀特,在我写这篇文章的时候,但也许他们有一天会重新设计。)
这是否适用于您想要使用的图像更难确定,因为这取决于您对“白色”的定义 - 根据您的图像的制作方式,您可能会发现事情有点偏白(特别是在边缘附近),您可能需要进一步调整。但是基本的做法应该没问题。