IT教程 ·

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

解释为什么不能依赖fail-fast

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()要领须要肯定的开支,而且挪用该要领越频仍,应用程序的运转速率就越慢。

下图显现了一个测试应用程序。该测试程序经由过程运用沿着划定规矩网格线漫步的基础随机形式添补像夙来建立一幅动态位图。本章示例采纳两种要领实行该使命:运用上一节中国诠释的逐像素要领和稍后引见看到的一次写入战略。假如测试该应用程序,将发明一次写入手艺快许多。

【WPF学习】第四十七章 WriteableBitmap类 IT教程 第1张

 

为一次更新多个像素,须要明白像素被打包进字节数组的体式格局。不管运用哪一种花样,更新缓冲区都将包含一维字节数组。这个数组供应了用于图象矩形地区中像素的数值,从左向右延长添补每行,然后自上而下延长。

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

(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的内存,这一请求不会很太过,然则耶比较高)。相反,应该写入一大块图象数据而不是单个像素,当一次生成一整幅位图时特别云云。

本章示例完全标记:

【WPF学习】第四十七章 WriteableBitmap类 IT教程 第2张

<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
【WPF学习】第四十七章 WriteableBitmap类 IT教程 第2张

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

 

小程序websocket心跳库——websocket-heartbeat-miniprogram

参与评论