转换后的WPF像素化黑白图像

Posted

技术标签:

【中文标题】转换后的WPF像素化黑白图像【英文标题】:WPF pixelated black and white image after conversion 【发布时间】:2020-07-18 08:04:03 【问题描述】:

我有一个带有Image Control 的 wpf 应用程序。我画线和文字,根据文字的type在图像上显示不同颜色的文字字段。

对于这个例子,所有颜色都是白色的。

然后我将按下一个按钮,将图像转换为BlackWhite,将其显示到图像控件,然后生成一个光栅文件。

我遇到的问题是,当我将其转换为黑白时,线条和文本周围的阴影会变为 Pixelated

有没有办法摆脱阴影或阻止它们进入 像素化?

我什至可以同时绘制一个额外隐藏的黑白图像,并从该图像而不是用户看到的图像生成光栅。

最小可行示例

xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="500">
    <Grid>
        <Image x:Name="originalImage" HorizontalAlignment="Left" Height="100" Margin="30,100,0,0" VerticalAlignment="Top" Width="183" />
        <Image x:Name="convertedImage" HorizontalAlignment="Left" Height="100" Margin="250,100,0,0" VerticalAlignment="Top" Width="183" />
    </Grid>
</Window>

xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    
        public MainWindow()
        
            InitializeComponent();
            DrawingVisual visual = new DrawingVisual();
            using (DrawingContext dc = visual.RenderOpen())
            
                int fs = 28;
                string textContain = "Testing 123";
                Point p = new Point(0, 0);
                dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, originalImage.Width, originalImage.Height));
                dc.DrawText(new FormattedText(textContain, System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight,
                            new Typeface("Arial"), fs, Brushes.White, VisualTreeHelper.GetDpi(visual).PixelsPerDip), p);
            
            RenderTargetBitmap target = 
                new RenderTargetBitmap((int)originalImage.Width, (int)originalImage.Height, 96, 96, PixelFormats.Default);
            target.Render(visual);
            FormatConvertedBitmap grayBitmapSource1 =
            new FormatConvertedBitmap(target as BitmapSource, PixelFormats.Bgr24, null, 0);

            var grayBitmapSource = new FormatConvertedBitmap();
            grayBitmapSource.BeginInit();
            grayBitmapSource.Source = grayBitmapSource1.Source as BitmapSource;
            grayBitmapSource.DestinationFormat = PixelFormats.BlackWhite;
            grayBitmapSource.EndInit();
            originalImage.Source = target;
            convertedImage.Source = grayBitmapSource;
        
    

【问题讨论】:

如果没有一个好的minimal reproducible example 可以可靠地重现问题,您不太可能找到愿意花时间解决问题的人。也就是说:您所说的“像素化”在我看来只是由转换为黑白时使用的阈值算法引起的抗锯齿伪影。尝试更仔细地控制转换,并允许任何非零像素值在黑白图像中成为前景。或者,您可以将文本渲染为路径,然后将其直接绘制到黑白图像中,而不是转换原始彩色图像。 @PeterDuniho 感谢伙伴的提醒。希望现在可以了。在我转换它之前,我正在考虑一次“读取”图像一个像素,然后在另一个图像上“写入”该像素。如果 r 或 g 或 b 不为零,则在 1 位 bmp 图像上绘制一个黑点。但我快速浏览了一下,找不到从图像控件获取 rgb 值并制作 1bpp 图像(并在其上设置像素)的方法 我现在正在探索的另一件事是在原始图像旁边绘制一个 System.Drawing.Image,然后从中导出光栅。我遇到的问题是带有内存流的drawing.image中的字节数组与我使用CopyPixels得到的原始字节数组不同(对我来说似乎是倒置的)。 现在回想起来,我隐约记得WPF 不允许RenderTargetBitmap 使用1bpp 格式。这是不幸的,因为这确实是最好的方法。我认为第二好的方法是按照您的建议使用WriteableBitmap,直接自己转换像素。使用 GDI+(即System.Drawing.Image)也是一种选择,如下所示:***.com/questions/6550731/… 【参考方案1】:

我想我正在做某事。我拍摄了图像,将其制作为 gray8 图像,将该图像写入一个数组,其中每个字节都是一个像素。然后使用该数组生成另一个数组,其中每个字节为 8 个像素,然后我生成最终图片。 此图用于比较:

这是一个例子:

xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="500">
    <Grid>
        <Image x:Name="originalImage" HorizontalAlignment="Left" Height="100" Margin="30,100,0,0" VerticalAlignment="Top" Width="200" />
        <Image x:Name="convertedImage" HorizontalAlignment="Left" Height="100" Margin="250,39,0,0" VerticalAlignment="Top" Width="200" />
        <Image x:Name="CustomConvertedImage" HorizontalAlignment="Left" Height="100" Margin="250,156,0,0" VerticalAlignment="Top" Width="200" />
    </Grid>
</Window>

xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    
        public MainWindow()
        
            InitializeComponent();
            DrawingVisual visual = new DrawingVisual();
            using (DrawingContext dc = visual.RenderOpen())
            
                int fs = 28;
                string textContain = "Testing 123";
                Point p = new Point(10, 10);
                dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, originalImage.Width, originalImage.Height));
                dc.DrawText(new FormattedText(textContain, System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight,
                            new Typeface("Arial"), fs, Brushes.White, VisualTreeHelper.GetDpi(visual).PixelsPerDip), p);
                dc.DrawLine(new Pen(Brushes.White, 3), new Point(5, 5), new Point(originalImage.Width - 5, 5));
                dc.DrawLine(new Pen(Brushes.White, 3), new Point(5, originalImage.Height - 5), new Point(originalImage.Width - 5, originalImage.Height - 5));
                dc.DrawLine(new Pen(Brushes.White, 3), new Point(5, 5), new Point(5, originalImage.Height));
                dc.DrawLine(new Pen(Brushes.White, 3), new Point(originalImage.Width - 5, 0), new Point(originalImage.Width - 5, originalImage.Height - 5));
            
            RenderTargetBitmap target =
                new RenderTargetBitmap((int)originalImage.Width, (int)originalImage.Height, 96, 96, PixelFormats.Default);
            target.Render(visual);
            FormatConvertedBitmap grayBitmapSource1 =
            new FormatConvertedBitmap(target as BitmapSource, PixelFormats.Bgr24, null, 0);

            var grayBitmapSource = new FormatConvertedBitmap();
            grayBitmapSource.BeginInit();
            grayBitmapSource.Source = grayBitmapSource1.Source as BitmapSource;
            grayBitmapSource.DestinationFormat = PixelFormats.BlackWhite;
            grayBitmapSource.EndInit();
            originalImage.Source = target;
            convertedImage.Source = grayBitmapSource;
            CustomConvertedImage.Source = ConvertToBlackAndWhiteAliased(originalImage, 80).Item1;
        

        private Tuple<System.Windows.Media.Imaging.BitmapSource, byte[]> ConvertToBlackAndWhiteAliased(Image _sourceImage, int _grayFilter = 80)
        
            FormatConvertedBitmap grayed8Image = new FormatConvertedBitmap(_sourceImage.Source as BitmapSource, PixelFormats.Gray8, null, 0);
            double bytePerPixel_Test = grayed8Image.Format.BitsPerPixel / 8.0;
            int width_Test = grayed8Image.PixelWidth;
            int height_Test = grayed8Image.PixelHeight;
            int stride_Test = (int)(width_Test * bytePerPixel_Test);
            byte[] resultLine_Test = new byte[height_Test * stride_Test];
            grayed8Image.CopyPixels(resultLine_Test, stride_Test, 0);

            double bytePerPixel = 1 / 8.0;
            int width = grayed8Image.PixelWidth;
            int height = grayed8Image.PixelHeight;
            int stride = (int)(width * bytePerPixel);
            byte[] resultLine = new byte[height * stride];

            byte tempByter;
            for (int enum8bpp = 0; enum8bpp < resultLine_Test.Length; enum8bpp += 8)
            
                tempByter = 0;

                for (int tempByterEnumer = 0; tempByterEnumer < 8; tempByterEnumer++)
                
                    tempByter |= (byte)(((resultLine_Test[enum8bpp + tempByterEnumer] > _grayFilter) ? 1 : 0) << 7 - tempByterEnumer);
                

                resultLine[enum8bpp / 8] = tempByter;
            
            WriteableBitmap bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.BlackWhite, null);
            bitmap.WritePixels(new Int32Rect(0, 0, width, height), resultLine, (int)(width * (bitmap.Format.BitsPerPixel / 8.0)), 0);
            return new Tuple<System.Windows.Media.Imaging.BitmapSource, byte[]>(bitmap, resultLine);
        
    

您可以从函数中获取图像(使用 .item1)或直接从数组中获取图像(item2)。 _grayFilter 表示您希望转换器的敏感程度(0 到 255,表示 gray8 像素格式中的 256 种灰度)。 我遇到的问题并且出于某种原因(由于锁定,大脑可能就像一个土豆泥)我无法解决它,是原始图像宽度需要可被 8 整除,否则会引发异常。我的意思是我明白为什么,你需要 8 个字节的原始图像在第二个数组上创建一个字节,但我找不到添加空值的方法。明天可能会解决,但请随时提供帮助:)

【讨论】:

“原始图像宽度需要被 8 整除,否则会引发异常”——我没有费心仔细查看您的新尝试。但是,通常当您遇到仅在特定宽度上消失的问题时,您的位图步幅就会出现问题。要么你一开始就没有正确设置步幅,要么在其他地方没有正确地尊重步幅。对于不同的错误,您通常会看到不同的异常(或者有时没有异常,而位图看起来就是错误的)。

以上是关于转换后的WPF像素化黑白图像的主要内容,如果未能解决你的问题,请参考以下文章

尝试在给定阈值处将所有像素更改为黑白

在黑白图像上查找未连接区域(岛)的算法[关闭]

图像处理Matlab——图像灰度值

Java - 将图像转换为黑白 - 色彩鲜艳失败

CSS:如何将彩色图像转换为灰色或黑白图像[重复]

如何通过覆盖现有图像将一堆图像转换为黑白图像?