wpf 将位图转换为灰度并保持透明度

idv4meu8  于 2023-04-13  发布在  其他
关注(0)|答案(2)|浏览(262)

你能告诉我如何在wpf c#应用程序和png格式的资源图像的情况下,以编程方式确保转换为灰度后的透明度吗?
我创建了一个最小工作项目来测试,你可以在这里找到它:Github GrayTransparencyTest

编辑2:Github存储库已经更新为用户“Just Answer the Question”和“Clemens”的前两个解决方案。即使转换为灰度看起来与xaml稍有不同,透明度也得到了保留。

问题看起来像这样(现在包括前两个解决方案):

  • 我需要第4张图像在转换为灰度后保留原始图像的透明度。*

使用XAML一切工作:

<Image>
  <Image.Source>
    <FormatConvertedBitmap DestinationFormat="Gray16">
      <FormatConvertedBitmap.Source>
        <BitmapImage UriSource="pack://application:,,,/GrayTransparencyTest;component/Media/priority.png" />
      </FormatConvertedBitmap.Source>
    </FormatConvertedBitmap>
  </Image.Source>
  <Image.OpacityMask>
    <ImageBrush>
      <ImageBrush.ImageSource>
        <BitmapImage UriSource="pack://application:,,,/GrayTransparencyTest;component/Media/priority.png" />
      </ImageBrush.ImageSource>
    </ImageBrush>
  </Image.OpacityMask>
</Image>

FormatConvertedBitmap使用绑定Image和C#方法,没有机会:

string imageKey = "priority";
Uri imageURI = new Uri($"pack://application:,,,/GrayTransparencyTest;component/Media/{imageKey}.png", UriKind.Absolute);
BitmapImage bitmapImage = new BitmapImage(imageURI);
ImageBrush opacityMask = new ImageBrush()
{
  ImageSource = bitmapImage
};
FormatConvertedBitmap bitmapGreyscale = new FormatConvertedBitmap();
bitmapGreyscale.BeginInit();
bitmapGreyscale.Source = new BitmapImage(imageURI);
bitmapGreyscale.DestinationFormat = PixelFormats.Gray16;
bitmapGreyscale.EndInit();
ImageGray = new Image()
{
  Source = bitmapGreyscale,
  OpacityMask = opacityMask,
};

看起来OpacityMask不起作用。请问如何为Image添加透明度?您能告诉我这个OpacityMask = opacityMask是否真的不应该以我期望的方式工作,或者我如何修复它以像XAML一样工作?(解决方法:直接作为资源的图像的灰度版本)。
根据到目前为止的答案,它看起来像是不能做其他直接编辑位图像素的图像。到目前为止,没有人向我解释为什么原来的代码不工作。

zpjtge22

zpjtge221#

这里有一个简单的解决方案,没有系统。绘图。位图和GDI。
它从保留alpha通道的原始BitmapSource的灰度转换缓冲区创建一个新的BitmapSource。该代码适用于通常的PixelFormats.Bgra32PixelFormats.Pbgra32,但可以轻松地适应其他格式。

public static BitmapSource ConvertToGrayscale(BitmapSource source)
{
    var stride = (source.PixelWidth * source.Format.BitsPerPixel + 7) / 8;
    var pixels = new byte[stride * source.PixelWidth];

    source.CopyPixels(pixels, stride, 0);

    for (int i = 0; i < pixels.Length; i += 4)
    {
        // this works for PixelFormats.Bgra32
        var blue = pixels[i];
        var green = pixels[i + 1];
        var red = pixels[i + 2];
        var gray = (byte)(0.2126 * red + 0.7152 * green + 0.0722 * blue);
        pixels[i] = gray;
        pixels[i + 1] = gray;
        pixels[i + 2] = gray;
    }

    return BitmapSource.Create(
        source.PixelWidth, source.PixelHeight,
        source.DpiX, source.DpiY,
        source.Format, null, pixels, stride);
}

这样使用它:

ImageGray = new Image { Source = ConvertToGrayscale(bitmapImage) };
bvpmtnay

bvpmtnay2#

我不确定是否有更简单或更有效的方法来实现这一点,但如果你不害怕一些低级的图像操作,这应该可以做到这一点。它将返回一个BitmapSource,给定一个表示PNG文件的流,你可以获得它,无论它是嵌入式资源还是其他什么。辅助方法:

using SysDraw = System.Drawing;
using System.Runtime.InteropServices;
// update Image property
ImageGray = new Image()
{
  Source = GetTransparentGrayscale(imageUri)
};
// conversion resource to stream
public ImageSource? GetTransparentGrayscale(Uri imageUri)
{
  StreamResourceInfo sri = Application.GetResourceStream(imageUri);
  ImageSource? imageSource = default;
  if (sri != null)
  {
    using (Stream s = sri.Stream)
    {
      imageSource = ConvertToTransparentGrayscale(s);
    }
  }
  return imageSource;
}
// delete GDI object method
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject([In] IntPtr hObject);

将PNG流转换为灰度的方法:

public static unsafe ImageSource? ConvertToTransparentGrayscale(Stream png)
{
    SysDraw.Bitmap? bmp = SysDraw.Bitmap.FromStream(png) as SysDraw.Bitmap;
    if (bmp == null) { return null; }
    SysDraw.Imaging.BitmapData bits = bmp.LockBits(
        new SysDraw.Rectangle(0, 0, (int)bmp.Width, (int)bmp.Height),
        SysDraw.Imaging.ImageLockMode.ReadWrite,
        SysDraw.Imaging.PixelFormat.Format32bppArgb);

    byte* buf = (byte*)bits.Scan0;
    for (int y = 0; y < bmp.Height; y++)
    {
        for (int x = 0; x < bmp.Width; x++)
        {
            int offset = y * bits.Stride + x * 4;
            byte gray = (byte)(0.2126 * buf[offset + 2] + 0.7152 * buf[offset + 1] + 0.0722 * buf[offset]);
            buf[offset + 1] = gray; // blue channel
            buf[offset + 2] = gray; // green channel
            buf[offset] = gray; // red channel
        }
    }
    bmp.UnlockBits(bits);
    IntPtr hBmp = bmp.GetHbitmap();
    try
    {
        return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
            hBmp,
            IntPtr.Zero,
            System.Windows.Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());
    }
    finally { DeleteObject(hBmp); }
}
  • 注意:转换为灰色的值是常见的,但可能需要根据您的情况进行调整。*

相关问题