WPF容许使用Image元素显示位图。然而,按这种方法显示图片的方法彻底是单向的。应用程序使用现成的位图,读取问题,并在窗口中显示位图。就其自己而言,Image元素没有提供建立和编辑位图信息的方法。编程
这正是WriteableBitmap类的用武之地。该类继承自BitmapSource,BitmapSource类是当设置Image.Source属性时使用的类(不论是在代码中直接设置图像,仍是在XAML中隐式地设置图像)。但BitmapSource是只读的位图数据映射,而WriteableBitmap类是可修改的像素数组,为实现许多有趣得效果提供了可能。数组
1、生成位图dom
为使用WriteableBitmap类生成一幅位图,必须提供提供几部分重要信息:以像素为单位的宽度和高度、两个方向上的DPI分辨率以及图像格式。ide
下面是建立一幅与当前图像元素尺寸相同的位图的示例:函数
// 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类的支持。下面是可供使用的像素格式:性能
前三种格式——Bgra3二、Bgr32以及Pbgra32——是最多见的选择。测试
2、写入WriteableBitmap对象编码
开始时,WriteableBitmap对象中全部字节的值都是0。本质上,就是一个大的黑色的矩形。为使用内容填充WriteableBitmap对象,须要使用WritePixels()方法。WritePixels()方法将字节数组复制到指定位置的位图中,可调用WritePixels()方法设置单个像素、整幅位图或选择的某块矩形区域。为从WriteableBitmap对象中获取像素,须要使用CopyPixels()方法,该方法将但愿获取的多个字节转换成字节数组。总之,WritePixels()和CopyPixels()方法没有提供可供使用的最方便编程模型,但这是低级像素访问须要付出的代价。spa
为成功地使用WritePixels()方法,须要理解图像格式并须要理解如何将像素编码到字节。例如,在Bgra32类型的32位位图中,每一个像素须要4个字节,每一个字节分别用于蓝、绿、红以及alpha成分。下面的代码显示了如何手动设置这些数值,而后将它们转换成数组:code
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对象中的像素,从而能够构建更特殊的用于位图编辑以及位图命中测试的例程。
3、更高效的像素写入
尽管上一节中显示的代码能够工做,但并不是最佳方法。若是须要一次性写入大量像素数据——甚至是整幅图像——最好使用更大的块,由于调用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>
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; } } }