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, , , 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 = ;
int red = ;
int green = ;
int blue = ; 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(, , (int)img.Width, (int)img.Height); wb.WritePixels(rect, colorData, , );

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

            Random rand = new Random();
for (int x = ; x < wb.PixelWidth; x++)
{
for (int y = ; y < wb.PixelHeight; y++)
{
int alpha = ;
int red = ;
int green = ;
int blue = ; // Determine the pixel's color.
if ((x % == ) || (y % == ))
{
red = (int)((double)y / wb.PixelHeight * );
green = rand.Next(, );
blue = (int)((double)x / wb.PixelWidth * );
alpha = ;
}
else
{
red = (int)((double)x / wb.PixelWidth * );
green = rand.Next(, );
blue = (int)((double)y / wb.PixelHeight * );
alpha = ;
} // Set the pixel value.
byte[] colorData = { (byte)blue, (byte)green, (byte)red, (byte)alpha }; // B G R Int32Rect rect = new Int32Rect(x, y, , );
int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / ;
wb.WritePixels(rect, colorData, stride, ); //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 / ;
pixels[pixelOffset] = (byte)blue;
pixels[pixelOffset + ] = (byte)green;
pixels[pixelOffset + ] = (byte)red;
pixels[pixelOffset + ] = (byte)alpha;

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

// Create the bitmap, with the dimensions of the image placeholder.
WriteableBitmap wb = new WriteableBitmap((int)img.Width,
(int)img.Height, , , PixelFormats.Bgra32, null); // Define the update square (which is as big as the entire image).
Int32Rect rect = new Int32Rect(, , (int)img.Width, (int)img.Height); byte[] pixels = new byte[(int)img.Width * (int)img.Height * wb.Format.BitsPerPixel / ];
Random rand = new Random();
for (int y = ; y < wb.PixelHeight; y++)
{
for (int x = ; x < wb.PixelWidth; x++)
{
int alpha = ;
int red = ;
int green = ;
int blue = ; // Determine the pixel's color.
if ((x % == ) || (y % == ))
{
red = (int)((double)y / wb.PixelHeight * );
green = rand.Next(, );
blue = (int)((double)x / wb.PixelWidth * );
alpha = ;
}
else
{
red = (int)((double)x / wb.PixelWidth * );
green = rand.Next(, );
blue = (int)((double)y / wb.PixelHeight * );
alpha = ;
} int pixelOffset = (x + y * wb.PixelWidth) * wb.Format.BitsPerPixel / ;
pixels[pixelOffset] = (byte)blue;
pixels[pixelOffset + ] = (byte)green;
pixels[pixelOffset + ] = (byte)red;
pixels[pixelOffset + ] = (byte)alpha; } int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / ; wb.WritePixels(rect, pixels, stride, );
} // 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, , , PixelFormats.Bgra32, null); // Define the update square (which is as big as the entire image).
Int32Rect rect = new Int32Rect(, , (int)img.Width, (int)img.Height); byte[] pixels = new byte[(int)img.Width * (int)img.Height * wb.Format.BitsPerPixel / ];
Random rand = new Random();
for (int y = ; y < wb.PixelHeight; y++)
{
for (int x = ; x < wb.PixelWidth; x++)
{
int alpha = ;
int red = ;
int green = ;
int blue = ; // Determine the pixel's color.
if ((x % == ) || (y % == ))
{
red = (int)((double)y / wb.PixelHeight * );
green = rand.Next(, );
blue = (int)((double)x / wb.PixelWidth * );
alpha = ;
}
else
{
red = (int)((double)x / wb.PixelWidth * );
green = rand.Next(, );
blue = (int)((double)y / wb.PixelHeight * );
alpha = ;
} int pixelOffset = (x + y * wb.PixelWidth) * wb.Format.BitsPerPixel / ;
pixels[pixelOffset] = (byte)blue;
pixels[pixelOffset + ] = (byte)green;
pixels[pixelOffset + ] = (byte)red;
pixels[pixelOffset + ] = (byte)alpha; } int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / ; wb.WritePixels(rect, pixels, stride, );
} // 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, , , PixelFormats.Bgra32, null); Random rand = new Random();
for (int x = ; x < wb.PixelWidth; x++)
{
for (int y = ; y < wb.PixelHeight; y++)
{
int alpha = ;
int red = ;
int green = ;
int blue = ; // Determine the pixel's color.
if ((x % == ) || (y % == ))
{
red = (int)((double)y / wb.PixelHeight * );
green = rand.Next(, );
blue = (int)((double)x / wb.PixelWidth * );
alpha = ;
}
else
{
red = (int)((double)x / wb.PixelWidth * );
green = rand.Next(, );
blue = (int)((double)y / wb.PixelHeight * );
alpha = ;
} // Set the pixel value.
byte[] colorData = { (byte)blue, (byte)green, (byte)red, (byte)alpha }; // B G R Int32Rect rect = new Int32Rect(x, y, , );
int stride = (wb.PixelWidth * wb.Format.BitsPerPixel) / ;
wb.WritePixels(rect, colorData, stride, ); //wb.WritePixels(.[y * wb.PixelWidth + x] = pixelColorValue;
}
} // Show the bitmap in an Image element.
img.Source = wb;
}
}
}

GenerateBitmap.xaml.cs

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

  1. 【WPF学习】第二十七章 Application类的任务

    上一章介绍了有关WPF应用程序中使用Application对象的方式,接下来看一下如何使用Application对象来处理一些更普通的情况,接下俩介绍如何初始化界面.如何处理命名行参数.如何处理支付窗 ...

  2. 【WPF学习】第十七章 键盘输入

    当用户按下键盘上的一个键时,就会发生一系列事件.下表根据他们的发生顺序列出了这些事件: 表 所有元素的键盘事件(按顺序) 键盘处理永远不会像上面看到的这么简单.一些控件可能会挂起这些事件中的某些事件, ...

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

    鼠标事件执行几个关联的任务.当鼠标移到某个元素上时,可通过最基本的鼠标事件进行响应.这些事件是MouseEnter(当鼠标指针移到元素上时引发该事件)和MouseLeave(当鼠标指针离开元素时引发该 ...

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

    本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

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

     <构建之法>第四&十七章读书笔记 一.         前言 再次阅读<构建之法>,愈发被其中生动有趣的举例吸引.作为一本给予软件工程学生的书籍,其不以枯燥的理论知识 ...

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

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  7. “全栈2019”Java第四十七章:继承与方法

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. “全栈2019”Java第三十七章:类与字段

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. SODBASE CEP学习(四)续:类SQL语言EPL与Storm或jStorm集成-使用分布式缓存

    流式计算在一些情况下会用到分布式缓存,从而实现(1)想把统计或计算结果保存在分布缓存中.供其他模块或其他系统调用. (2)某一滑动时间窗体上计数.比如实时统计1小时每一个Cookie的訪问量.实时统计 ...

随机推荐

  1. 1.常用的cmd命令

    dir      =>  查看当前目录下的所有文件夹 cd..    =>  返回上一级目录 cd/     =>  返回根目录 cd 文件夹  =>  打开当前目录下指定的子 ...

  2. 《企业IT架构转型之道:阿里巴巴中台战略思想与架构实战》-总结

      一.什么是业务中台 概念来自于阿里,介于前台和后台(此后台指的是云计算.数据库.消息队列.缓存等基础服务) 采用共享式架构设计解决以往烟囱式架构设计的资源浪费.重复造轮.试错成本高的问题 阿里的中 ...

  3. Java动态编译技术原理

    这里介绍Java动态编译技术原理! 编译,一般来说就是将源代码转换成机器码的过程,比如在C语言中中,将C语言源代码编译成a.out,,但是在Java中的理解可能有点不同,编译指的是将java 源代码转 ...

  4. 关于爬虫的日常复习(9)—— 实战:分析Ajax抓取今日头条接拍美图

  5. IDEA使用 磨刀霍霍向代码

    工欲善其事,必先利其器 ,当下有数不清的 Java 程序员将石器时代的 Eclipse 替换成了现代化的智能开发工具 InteliJ IDEA ,写代码的小日子过得不亦乐乎(玩笑话,两者各有千秋,看个 ...

  6. Qt Installer Framework翻译(0)

    本人主攻C++和Qt. 以前一直看人家的博客,找资料学习.今天我也终于开博客啦. 最近在研究Qt install framework(IFW)应用程序安装框架. google也没发现有正儿八经的官方文 ...

  7. Dart语言学习( 一) 为什么学习Dart?

    为什么学习Dart? Google及全球的其他开发者,使用 Dart 开发了一系列高质量. 关键的 iOS.Android 和 web 应用. Dart 非常适合移动和 web 应用的开发. 高效 D ...

  8. Java中SMB的应用

    目录 SMB 服务操作 Ⅰ SMB简介 Ⅱ SMB配置 2.1 Windows SMB Ⅲ 添加SMB依赖 Ⅳ 路径格式 Ⅴ 操作共享 Ⅵ 登录验证 SMB 服务操作 Ⅰ SMB简介 ​ SMB(全称 ...

  9. 玩转Django2.0---Django笔记建站基础九(一)(Auth认证系统)

    第九章 Auth认证系统 Django除了有强大的Admin管理系统之外,还提供了完善的用户管理系统.整个用户管理系统可分为三大部分:用户信息.用户权限和用户组,在数据库中分别对应数据表auth_us ...

  10. python3学习笔记一

    install 安装软件包download 下载安装包uninstall 卸载安装包freeze 按照req uirements 格式输出安装包,可以到其他服务器上执行pip install -r r ...