【WPF学习】第四十七章 WriteableBitmap类

  WPF允许使用Image元素显示位图。然而,按这种方法显示图片的方法完全是单向的。应用程序使用现成的位图,读取问题,并在窗口中显示位图。就其本身而言,Image元素没有提供创建和编辑位图信息的方法。

  这正是WriteableBitmap类的用武之地。该类继承自BitmapSource,BitmapSource类是当设置Image.Source属性时使用的类(不管是在代码中直接设置图像,还是在XAML中隐式地设置图像)。但BitmapSource是只读的位图数据映射,而WriteableBitmap类是可修改的像素数组,为实现许多有趣得效果提供了可能。

一、生成位图

  为使用WriteableBitmap类生成一幅位图,必须提供提供几部分重要信息:以像素为单位的宽度和高度、两个方向上的DPI分辨率以及图像格式。

  下面是创建一幅与当前图像元素尺寸相同的位图的示例:

// Create the bitmap, with the dimensions of the image placeholder.
            WriteableBitmap wb = new WriteableBitmap((int)img.Width,
                (int)img.Height, 96, 96, PixelFormats.Bgra32, null);

  PixelFormats枚举提供了许多像素格式,但只有一半格式呗认为是可写入的并且得到了WriteableBitmap类的支持。下面是可供使用的像素格式:

  •   Bgra32。这种格式使用32位sRGB颜色。这意味每个像素由32位(或4个字节)表示。第1个字节表示蓝色通道的贡献(作为从0到255之间的数字)。第2个字节用于绿色通道,第3个字节用于红色通道,第4个字节用于alpha值(0表示完全透明,255表示完全不透明)。正如可能看到的,颜色的顺序(蓝绿、红和alpha)与名称Bgra32中字母的顺序是匹配的。
  •   Bgr32。.这种格式为每个像素使用4个字节,就像Bgra32格式一样。区别是忽略了alpha通道。当不需要透明度时可使用这种格式。
  •   Pbgra32。就像Bgra32格式一样,该格式为每个像素使用4个字节。区别在于处理半透明像素的方式。为了提高透明度计算的性能,每个颜色字节是预先相乘的(因此在Pbgra32中有字母P)。这意味着每个颜色字节被乘上了alpha值并除以255.在Bgra32格式中具有B、G、R、A值(255,100,0,200)的半透明像素,在Pbgra32格式中变成了(200,78,0,200)。
  •   BlackWhite、Gray2、Gray4、Gray8。这些格式是黑白和灰度格式。单词Gray后面的数字和每像素的位置相对应。因此,这些格式是压缩的,但它们不支持颜色。
  •   Indexed1、Indexed2、Indexed4、Indexed8。这些是索引格式,这意味着每个像素指向颜色调色板中的一个值。当使用这些格式中的某种格式时,必须做出WriteableBitmap构造函数的最后一个参数传递相应的ColorPalette对象。单词Indexed后面的数字和每像素的位数相对应。索引格式是压缩的,使用这些格式稍微复杂一些,并且分别支持更少的颜色——2、4、16以及256种颜色。

  前三种格式——Bgra32、Bgr32以及Pbgra32——是最常见的选择。

二、写入WriteableBitmap对象

  开始时,WriteableBitmap对象中所有字节的值都是0。本质上,就是一个大的黑色的矩形。为使用内容填充WriteableBitmap对象,需要使用WritePixels()方法。WritePixels()方法将字节数组复制到指定位置的位图中,可调用WritePixels()方法设置单个像素、整幅位图或选择的某块矩形区域。为从WriteableBitmap对象中获取像素,需要使用CopyPixels()方法,该方法将希望获取的多个字节转换成字节数组。总之,WritePixels()和CopyPixels()方法没有提供可供使用的最方便编程模型,但这是低级像素访问需要付出的代价。

  为成功地使用WritePixels()方法,需要理解图像格式并需要理解如何将像素编码到字节。例如,在Bgra32类型的32位位图中,每个像素需要4个字节,每个字节分别用于蓝、绿、红以及alpha成分。下面的代码显示了如何手动设置这些数值,然后将它们转换成数组:

int alpha = 0;
int red = 0;
int green = 0;
int blue = 0;

byte[] colorData={blue,gree,red,alpha};

  需要注意,在此顺序是很关键的。字节数组必须遵循在Bgra32标准中设置的蓝、绿、红、alpha顺序。

  当调用WritePixels()方法时,提供Int32Rect对象以指定位图中希望更新的矩形区域。Int32Rect封装了4部分信息:更新区域左上角的X和Y坐标,以及更新区域的宽度和高度。

  下面的代码采用在前面代码中显示的colorData数组,并使用该数组设置WriteableBitmap对象中的第一个像素:

// Define the update square (which is as big as the entire image).
Int32Rect rect = new Int32Rect(0, 0, (int)img.Width, (int)img.Height);

wb.WritePixels(rect, colorData, 0, 0);

  使用这种方法,可创建生产WriteableBitmap对象的代码例程。只需要循环处理图像中的所有列和所有行,并在每次迭代中更新单个像素。

            Random rand = new Random();
            for (int x = 0; x < wb.PixelWidth; x++)
            {
                for (int y = 0; y < wb.PixelHeight; y++)
                {
                    int alpha = 0;
                    int red = 0;
                    int green = 0;
                    int blue = 0;

                    // Determine the pixel‘s color.
                    if ((x % 5 == 0) || (y % 7 == 0))
                    {
                        red = (int)((double)y / wb.PixelHeight * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)x / wb.PixelWidth * 255);
                        alpha = 255;
                    }
                    else
                    {
                        red = (int)((double)x / wb.PixelWidth * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)y / wb.PixelHeight * 255);
                        alpha = 50;
                    }

                    // Set the pixel value.
                    byte[] colorData = { (byte)blue, (byte)green, (byte)red, (byte)alpha }; // B G R

                    Int32Rect rect = new Int32Rect(x, y, 1, 1);
                    int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / 8;
                    wb.WritePixels(rect, colorData, stride, 0);

                    //wb.WritePixels(.[y * wb.PixelWidth + x] = pixelColorValue;
                }
            }

  上述代码包含一个额外细节:针对跨距(stride)的计算,WritePixels()方法需要跨距。从技术角度看,跨距是每行像素数据需要的字节数量。可通过将每行中像素的数量乘上所使用格式的每像素位数(通常为4,如本例使用的Bgra32格式),然后将所得结果除以8,进而将其从位数转换成字节数。

  完成每个像素的生产过程后,需要显示最终位图。通常将使用Image元素完成该工作:

img.Source = wb;

  即使是在写入和显示位图后,也仍可自由地读取和修改WriteableBitmap对象中的像素,从而可以构建更特殊的用于位图编辑以及位图命中测试的例程。

三、更高效的像素写入

  尽管上一节中显示的代码可以工作,但并非最佳方法。如果需要一次性写入大量像素数据——甚至是整幅图像——最好使用更大的块,因为调用WritePixels()方法需要一定的开销,并且调用该方法越频繁,应用程序的运行速度就越慢。

  下图显示了一个测试应用程序。该测试程序通过使用沿着规则网格线散步的基本随机模式填充像素来创建一幅动态位图。本章示例采用两种方法执行该任务:使用上一节中国解释的逐像素方法和稍后介绍看到的一次写入策略。如果测试该应用程序,将发现一次写入技术快很多。

  为一次更新多个像素,需要理解像素被打包进字节数组的方式。无论使用哪种格式,更新缓冲区都将包括一维字节数组。这个数组提供了用于图像矩形区域中像素的数值,从左向右延伸填充每行,然后自上而下延伸。

  为找到某个特定像素,需要使用以下公式,下移数行,然后一道该行中恰当的位置:

(x + y * wb.PixelWidth) * BitsPerPixel 

  例如,为设置一幅Bgra32格式(每个像素具有4个字节)的位图中额像素(40,100),需要使用下面的代码:

int pixelOffset = (x + y * wb.PixelWidth) * wb.Format.BitsPerPixel / 8;
pixels[pixelOffset] = (byte)blue;
pixels[pixelOffset + 1] = (byte)green;
pixels[pixelOffset + 2] = (byte)red;
pixels[pixelOffset + 3] = (byte)alpha;

  根据上面的方法,下面是创建前面示例的完整代码,首先在一个数组中填充所有数据,然后值通过一次WritePixels()方法调用将其复制到WriteableBitmap对象中:

// Create the bitmap, with the dimensions of the image placeholder.
            WriteableBitmap wb = new WriteableBitmap((int)img.Width,
                (int)img.Height, 96, 96, PixelFormats.Bgra32, null);

            // Define the update square (which is as big as the entire image).
            Int32Rect rect = new Int32Rect(0, 0, (int)img.Width, (int)img.Height);

            byte[] pixels = new byte[(int)img.Width * (int)img.Height * wb.Format.BitsPerPixel / 8];
            Random rand = new Random();
            for (int y = 0; y < wb.PixelHeight; y++)
            {
                for (int x = 0; x < wb.PixelWidth; x++)
                {
                    int alpha = 0;
                    int red = 0;
                    int green = 0;
                    int blue = 0;

                    // Determine the pixel‘s color.
                    if ((x % 5 == 0) || (y % 7 == 0))
                    {
                        red = (int)((double)y / wb.PixelHeight * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)x / wb.PixelWidth * 255);
                        alpha = 255;
                    }
                    else
                    {
                        red = (int)((double)x / wb.PixelWidth * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)y / wb.PixelHeight * 255);
                        alpha = 50;
                    }

                    int pixelOffset = (x + y * wb.PixelWidth) * wb.Format.BitsPerPixel / 8;
                    pixels[pixelOffset] = (byte)blue;
                    pixels[pixelOffset + 1] = (byte)green;
                    pixels[pixelOffset + 2] = (byte)red;
                    pixels[pixelOffset + 3] = (byte)alpha;

                }

                int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / 8;

                wb.WritePixels(rect, pixels, stride, 0);
            }

            // Show the bitmap in an Image element.
            img.Source = wb;

  在实际应用程序中,可选择折中方法。如果需要更新位图中一块较大的区域,不会一次写入一个像素,因为这种方法的运行速度太慢。但也不会在内存中同时保存全部图像数据,因为图像数据可能会很大(毕竟,一幅每像素需要4个字节的1000X1000像素的图像需要将近4MB的内存,这一要求不会很过分,但是耶比较高)。相反,应当写入一大块图像数据而不是单个像素,当一次生成一整幅位图时尤其如此。

本章示例完整标记:

<Window x:Class="Drawing.GenerateBitmap"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="GenerateBitmap" Height="460" Width="472" SizeToContent="WidthAndHeight">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Button Content="Button" Grid.Row="1" Height="81" HorizontalAlignment="Left" Margin="106,90,0,0" Name="button1" VerticalAlignment="Top" Width="193" />
        <Button Content="Generate Bitmap" Width="120" Margin="5" Padding="10" Click="cmdGenerate2_Click" HorizontalAlignment="Center"></Button>
        <Image Grid.Row="1" x:Name="img" Margin="5" Width="400" Height="300" IsHitTestVisible="False"></Image>
    </Grid>
</Window>

GenerateBitmap

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.Shapes;

namespace Drawing
{
    /// <summary>
    /// GenerateBitmap.xaml 的交互逻辑
    /// </summary>
    public partial class GenerateBitmap : Window
    {
        public GenerateBitmap()
        {
            InitializeComponent();
        }

        private void cmdGenerate_Click(object sender, RoutedEventArgs e)
        {
            // Create the bitmap, with the dimensions of the image placeholder.
            WriteableBitmap wb = new WriteableBitmap((int)img.Width,
                (int)img.Height, 96, 96, PixelFormats.Bgra32, null);

            // Define the update square (which is as big as the entire image).
            Int32Rect rect = new Int32Rect(0, 0, (int)img.Width, (int)img.Height);

            byte[] pixels = new byte[(int)img.Width * (int)img.Height * wb.Format.BitsPerPixel / 8];
            Random rand = new Random();
            for (int y = 0; y < wb.PixelHeight; y++)
            {
                for (int x = 0; x < wb.PixelWidth; x++)
                {
                    int alpha = 0;
                    int red = 0;
                    int green = 0;
                    int blue = 0;

                    // Determine the pixel‘s color.
                    if ((x % 5 == 0) || (y % 7 == 0))
                    {
                        red = (int)((double)y / wb.PixelHeight * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)x / wb.PixelWidth * 255);
                        alpha = 255;
                    }
                    else
                    {
                        red = (int)((double)x / wb.PixelWidth * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)y / wb.PixelHeight * 255);
                        alpha = 50;
                    }

                    int pixelOffset = (x + y * wb.PixelWidth) * wb.Format.BitsPerPixel / 8;
                    pixels[pixelOffset] = (byte)blue;
                    pixels[pixelOffset + 1] = (byte)green;
                    pixels[pixelOffset + 2] = (byte)red;
                    pixels[pixelOffset + 3] = (byte)alpha;

                }

                int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / 8;

                wb.WritePixels(rect, pixels, stride, 0);
            }

            // Show the bitmap in an Image element.
            img.Source = wb;
        }

        private void cmdGenerate2_Click(object sender, RoutedEventArgs e)
        {

            // Create the bitmap, with the dimensions of the image placeholder.
            WriteableBitmap wb = new WriteableBitmap((int)img.Width,
                (int)img.Height, 96, 96, PixelFormats.Bgra32, null);

            Random rand = new Random();
            for (int x = 0; x < wb.PixelWidth; x++)
            {
                for (int y = 0; y < wb.PixelHeight; y++)
                {
                    int alpha = 0;
                    int red = 0;
                    int green = 0;
                    int blue = 0;

                    // Determine the pixel‘s color.
                    if ((x % 5 == 0) || (y % 7 == 0))
                    {
                        red = (int)((double)y / wb.PixelHeight * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)x / wb.PixelWidth * 255);
                        alpha = 255;
                    }
                    else
                    {
                        red = (int)((double)x / wb.PixelWidth * 255);
                        green = rand.Next(100, 255);
                        blue = (int)((double)y / wb.PixelHeight * 255);
                        alpha = 50;
                    }

                    // Set the pixel value.
                    byte[] colorData = { (byte)blue, (byte)green, (byte)red, (byte)alpha }; // B G R

                    Int32Rect rect = new Int32Rect(x, y, 1, 1);
                    int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / 8;
                    wb.WritePixels(rect, colorData, stride, 0);

                    //wb.WritePixels(.[y * wb.PixelWidth + x] = pixelColorValue;
                }
            }

            // Show the bitmap in an Image element.
            img.Source = wb;
        }
    }
}

GenerateBitmap.xaml.cs

原文地址:https://www.cnblogs.com/Peter-Luo/p/12345402.html

时间: 2024-10-08 03:04:19

【WPF学习】第四十七章 WriteableBitmap类的相关文章

【WPF学习】第十七章 鼠标输入

鼠标事件执行几个关联的任务.当鼠标移到某个元素上时,可通过最基本的鼠标事件进行响应.这些事件是MouseEnter(当鼠标指针移到元素上时引发该事件)和MouseLeave(当鼠标指针离开元素时引发该事件).这两个事件都是直接事件,这意味着他们不使用冒泡和隧道过程,而是源自一个元素并且只被该元素引发.考虑到控件嵌入到WPF窗口的方式,这是合理的. 例如,如果有一个包含按钮的StackPanel面板,并将鼠标指针移到按钮上,那么首先会为这个StackPanel引发MouseEnter事件(当鼠标指

第四十七章

第四十七章1 听说在家就能悟道,是真的吗? 不出于户,以知天下:不窥于牖,以知天道. 悟道的人不用出门就可以知道天下之事:不用往窗外看就可以知道天道. 获取信息要尽量获取有用的知识,并且要依循天道的方向.而天道无须出门就可以领悟. 各位朋友大家好,今天我们接着来讲<道德经>.我这些天一直在外面走,讲课.访问.参观等等,虽然比较疲劳,但是很开心,为什么开心?其中一件特别开心的事就是,走到哪,大家都说罗老师,我们正在听您的<道德经>,从一开始一直听到现在,我听完很感动,尤其是我在惠州,

Gradle 1.12用户指南翻译——第四十七章. Build Init 插件

文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://github.com/msdx/gradledoc 本文翻译所在分支: https://github.com/msdx/gradledoc/tree/1.12. 直接浏览双语版的文档请访问: http://gradledoc.qiniudn.com/1.12/usergu

《构建之法》第四&amp;十七章读书笔记

 <构建之法>第四&十七章读书笔记 一.         前言 再次阅读<构建之法>,愈发被其中生动有趣的举例吸引.作为一本给予软件工程学生的书籍,其不以枯燥的理论知识为核心,而是基于对知识和方法的引导.本次研读的这两章内容主要涉及了代码规范,两人结对与多人合作的团队方面等相关知识,从其中逐渐明白与人相处作业等方面的技巧与艺术.以下是我对这两章节的思考与疑惑. 二.        第四章<两人合作>. 本章主要涉及代码规范,极限编程,结对编程,两人合作不同阶段,

“全栈2019”Java第八十七章:类中嵌套接口的应用场景(拔高题)

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第八十七章:类中嵌套接口的应用场景(拔高题) 下一章 "全栈2019"Java第八十八章:接口中嵌套接口的应用场景 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"J

“全栈2019”Java多线程第四十七章:判断锁是否为公平锁isFair()

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第四十七章:判断锁是否为公平锁isFair() 下一章 "全栈2019"Java多线程第四十八章:读写锁实战高并发容器 学习小组 加入同步学习小组,共同交流与进步. 方式一:加入编程圈子. 方式二:关注头条号Gorhaf,私信"Java学习小组". 方式三:关注公众

第十七章 特殊类成员

第十七章  特殊类成员 1.1  静态成员变量 假如我们要在一个类中的所有对象间共享某种数据,那不妨将其设置为静态成员变量/函数: static x 1.2  静态成员变量 静态成员变量与成员变量有4点不同: ①前者属于类②前者必须在全局定义③前者被调用只用说明那个类即可④前者在没有创建对象之前就已经存在 #include <iostream> using namespace std; class A { public: static int n; }; int A::n=0; //要使用类成

第十四——十七章作业

                                                                                                     第十四章 15.3.1 有些成功人士或公司认为不需要独立的测试角色(Test),你怎么看? 在一些软件公司中,QA的工作中包含了Test的角色,负责验证程序是否符合预先设计的功能和特性.但是QA的工作量是很多的,一个好的QA不仅需要对程序架构有着很好的理解,对程序功能和性能都有着较深的理解,并且要

【WPF学习】第十三章 理解路由事件

每个.NET开发人员都熟悉“事件”的思想——当有意义的事情发生时,由对象(如WPF元素)发送的用于通知代码的消息.WPF通过事件路由(event routing)的概念增强了.NET事件模型.事件路由允许源自某个元素的事件由另一个元素引发.例如,使用事件路由,来自工具栏按钮的单击事件可在被代码处理之前上传到工具栏,然后上传到包含工具栏的窗口. 事件路由为在最合适的位置编写紧凑的.组织良好的用于处理事件的代码提供了灵活性.要使用WPF内容模型,事件路由也是必需的,内容模型允许使用许多不同的元素构建