【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, , , 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类的更多相关文章
- 【WPF学习】第二十七章 Application类的任务
上一章介绍了有关WPF应用程序中使用Application对象的方式,接下来看一下如何使用Application对象来处理一些更普通的情况,接下俩介绍如何初始化界面.如何处理命名行参数.如何处理支付窗 ...
- 【WPF学习】第十七章 键盘输入
当用户按下键盘上的一个键时,就会发生一系列事件.下表根据他们的发生顺序列出了这些事件: 表 所有元素的键盘事件(按顺序) 键盘处理永远不会像上面看到的这么简单.一些控件可能会挂起这些事件中的某些事件, ...
- 【WPF学习】第十七章 鼠标输入
鼠标事件执行几个关联的任务.当鼠标移到某个元素上时,可通过最基本的鼠标事件进行响应.这些事件是MouseEnter(当鼠标指针移到元素上时引发该事件)和MouseLeave(当鼠标指针离开元素时引发该 ...
- Gradle 1.12用户指南翻译——第四十七章. Build Init 插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- 《构建之法》第四&十七章读书笔记
<构建之法>第四&十七章读书笔记 一. 前言 再次阅读<构建之法>,愈发被其中生动有趣的举例吸引.作为一本给予软件工程学生的书籍,其不以枯燥的理论知识 ...
- “全栈2019”Java第八十七章:类中嵌套接口的应用场景(拔高题)
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- “全栈2019”Java第四十七章:继承与方法
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- “全栈2019”Java第三十七章:类与字段
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- SODBASE CEP学习(四)续:类SQL语言EPL与Storm或jStorm集成-使用分布式缓存
流式计算在一些情况下会用到分布式缓存,从而实现(1)想把统计或计算结果保存在分布缓存中.供其他模块或其他系统调用. (2)某一滑动时间窗体上计数.比如实时统计1小时每一个Cookie的訪问量.实时统计 ...
随机推荐
- 生产环境中使用docker注意点
是否对容器使用的资源进行合理限制,比如内存 CPU 网络带宽等. 是否设置合理的网络访问限制,如 非root用户 iptables. 是否有在docker无法使用时的备选方案,如提供非docker环境 ...
- C++ 链式继承下的虚函数列表
目录 1.虚函数列表的位置 2.虚函数列表的内容 3.链式继承中虚函数列表的内容 注: 虚函数列表 又称为虚表, vtbl , 指向它的指针称为vptr, vs2019中称为__vfptr 操作系 ...
- 12.instanceof和类型转换
Instanceof: 判断一个对象是什么类型的~,可以判断两个类之间是否存在父子关系 package com.oop.demo07; public class Person { public voi ...
- 迈向Angular 2
目录 序言 XV第1章 Angular 2快速上手 1Web的进化——新框架时代 2ECMAScript的进化 2Web Component 3WebWorker 4从AngularJS 1.x中学到 ...
- doT 这个模板 是怎么实现的?
之前做过一个微信有关的站 模板用 doT 嗯 这个 用起来很 不错. 但是 它是怎么实现的,想过没有? ps:https://github.com/olado/doT 源码总共 140行. 第90行里 ...
- MyBatis-Plus学习笔记(1):环境搭建以及基本的CRUD操作
MyBatis-Plus是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,使用MyBatis-Plus时,不会影响原来Mybatis方式的使用. SpringBoot+M ...
- 【WPF学习】第二十章 内容控件
内容控件(content control)是更特殊的控件类型,它们可包含并显示一块内容.从技术角度看,内容控件时可以包含单个嵌套元素的控件.与布局容器不同的是,内容控件只能包含一个子元素,而布局容器主 ...
- 【模板】普通平衡树(权值splay)
安利splay讲解: [洛谷日报第62期]Splay简易教程 [模板]普通平衡树(luogu) Description 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下 ...
- Tiny Linux -- tce-load
Tiny Linux which has its own package manager called "tce-load". There's a list of packages ...
- webpack入门系列1
一.什么是webpack?为什么要使用它? Webpack 是一个前端资源加载/打包工具.它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源. 为什么我们要使用它呢?因 ...