通常,WPF中的位图是不可变的。不可变的位图非常有效,如果您希望进行大量的动态更改,创建和销毁它们的开销会变得非常昂贵。在这种情况下,您需要一些更灵活的东西——WriteableBitmap。
WriteableBitmap,正如它的名字所暗示的,不是不可变的,你可以得到它的单个像素,并尽可能多地操纵它们。当您需要动态位图时,这是理想的工作方式。我们来看看WriteableBitmap,它是如何工作的,以及如何使用它来做动态的事情。
要使用WriteableBitmap,需要添加:using System.Windows.Media.Imaging;它包含我们需要的所有其他位图工具。
有两种方法可以创建WriteableBitmap。最常用的是简单地指定位图的大小和格式:
WriteableBitmap wbmap = new WriteableBitmap(100, 100, 300,300, PixelFormats.Bgra32, null);
它使用Bgra32像素格式指定一个100×100像素的WriteableBitmap,分辨率为300dpi×300dpi。每个像素是一个32位的int,使用一个4字节的BGRA——也就是说,像素由一个字节组成,该字节给出蓝色、绿色、红色和Alpha值。如果格式需要,最后一个参数用于指定调色板。
创建WriteableBitmap的第二种方法是基于现有的BitmapSource或派生类。
例如,您应该能够使用构造函数从标准位图创建WriteableBitmap:
WriteableBitmap(bitmapsource);
但是如果您尝试使用从URI加载的BitmapImage,您将得到一个null对象错误。原因是位图可能还没有下载。对于本地文件,位图加载会阻塞执行,直到加载完成:
Uri uri = new Uri(@"pack://application:,,,/Resources/mypic.jpg");
BitmapImage bmi = new BitmapImage(uri);
WriteableBitmap bmi2 = new WriteableBitmap(bmi);
image1.Source = bmi2;
在这种情况下,WriteableBitmap的创建没有任何问题。但是,如果URI是HTTP URL,负载不会阻塞执行,结果将是一个错误。
使用像素
一旦你有了WriteableBitmap,你就可以开始使用它的像素了。提供了两种方法,它们提供了在低级API中找到的与BitBlt操作等价的有限的方法。在这种情况下,您可以将一个像素矩形复制到一个数组中,也可以将一个数组中的数据复制到一个像素矩形中。在每一种情况下,数据数组都被视为一个字节流,并简单地从位图中指定的像素矩形中存储或检索。
WriteableBitmap了解它所存储的位图的格式,因此它会自动计算出每个像素使用了多少字节,以及如何找到给定坐标下的像素数据。但是,它不能知道您如何组织数组中的数据,或者在向数组读取像素时,您可能需要如何组织数据。
最简单的方案是将一行像素数据存储在与前一行相邻的数组中。也就是说,如果原始图像有p个像素,每个像素用b字节表示,那么数组中的第一行用pb字节存储,下一行从pb+1开始,以此类推。
可以看到,列x行y中的像素(从0开始计数)存储在字节xb+pby中。(这是存储映射函数的一个例子。)这个简单方案的唯一复杂之处在于一行像素可能不会在整个字节中结束。例如,如果你有一个黑白图像格式,你只需要1位每像素和第一行的10×10位图只需要10位存储。然而,为了简单起见,每一行都必须从一个新字节开始,因此存储第一行所需的存储空间是两个字节。
注意,在这种情况下,根据每个像素的比特数或字节数,分配给一行的存储量超出了严格的需求,也就是说,它不仅仅是pb。
为什么一行所需的存储空间并不总是最小值,还有其他原因——例如Windows坚持行总是从一个4字节的边界开始。
因此,我们定义并使用“stride”。
stride S被定义为存储图像一行所需的存储量,包括确保下一行正确对齐所需的任何填充。
在这种情况下,WriteableBitmap负责像素的内部表示,您不需要担心它使用的是什么值。但是,您必须担心在您负责的数据数组中使用的stride。在大多数情况下,您可以简单地将stride设置为存储位图的一行所需的字节数——四舍五入,以确保必要时每一行都从一个新字节开始。
您只需要担心其他问题,比如从一个4字节的边界开始,如果它是由系统的其他部分强加的,比如从使用特定步幅的源获取数据。
二,WriteableBitmap,它是一个基于内存的图像管理类:
大家可以把它认为图像是一堆存储在内存中的数据,这些数据可由WriteableBitmap管理和分配。
WriteableBitmap的一些使用技巧:
2.1,实现自绘
众所周知,目前为止,微软还没有开放自绘接口,如果你真的想在界面上自已绘制一个字符串,都有些困难呢。下面的代码正是使用WriteableBitmap来实现自绘的方案
private void RenderString(WriteableBitmap bitmap, string stringToRender)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = stringToRender;
//设置 font, size,等等
bitmap.Render(textBlock, null);
bitmap.Invalidate();
}
他是通过将TextBlock中的文本绘制到WriteableBitmap来实现的,
在这里我发挥一下,那不就可以通过这个方法,来实现一个图片水印的功能么,
顺便说一点,这里我要介绍一个更强大的开源的库writeablebitmapex,如果大家想要绘制更复杂的的图像如:点,线,曲线,阴影,形状,以及实现一些常用的图像数据处理功能,那么这个库将是大家最好的选择。
2.2,图像的缩放存储
如果你想将一张图片改变大小,那么你可以用以下的方法去实现
WriteableBitmap resizedImage = new WriteableBitmap(imageToResize);//imageToResize is BitmapImage
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (System.IO.IsolatedStorage.IsolatedStorageFileStream isfs =
new IsolatedStorageFileStream(fileName, FileMode.Create, isf))
{
double maxHeight = newWidth;
double maxWidth = newHeight;
double scaleX = 1;
double scaleY = 1;
if (pixHt > maxHeight)
caleY = maxHeight / pixHt;
if (pixWt > maxWidth)
scaleX = maxWidth / pixWt;
double scale = Math.Min(scaleY, scaleX);
int newWidth1 = Convert.ToInt32(pixWt * scale);
int newHeight1 = Convert.ToInt32(pixHt * scale);
resizedImage.SaveJpeg(isfs, newWidth1, newHeight1, 0, 70);
isfs.Close();
}
}
在这里,imageToResize就是你输入的图像,你可以将它存储为目标大小的图像文件
2.3,对控件(全屏)进行截图
在很多应用中,如果要对当前页面进行截图,这时WriteableBitmap就能帮助到你了。把页面截图,并保存到内存
//将UI页面的根元素传入,可将当面页面的截图保存到WriteableBitmap
WriteableBitmap wb = new WriteableBitmap(UiRoot, null);
MemoryStream ms = new MemoryStream();
wb.SaveJpeg(ms, myWidth, myHeight, 0, 100);//保存到内存MemoryStream
BitmapImage bmp = newBitmapImage(); //把截图转化为BitmapImage
bmp.SetSource(ms);
using (var isoFileStream =newIsolatedStorageFileStream("myPicture.jpg",
FileMode.OpenOrCreate,IsolatedStorageFile.GetUserStoreForApplication()))
{
wb.SaveJpeg(isoFileStream, myWidth, myHeight,0,100); //把截图存储到独立存储
}
4.4,将存储在Sql数据库的图片二进制数据载入到内存
有些时候图片数据是以二进制数据保存到sqlite数据库中的,下面将是,如何把这些二进制数据还原成图像格式
public static byte[] ConvertToBytes(String imageLocation)
{
StreamResourceInfo sri = Application.GetResourceStream(
new Uri(imageLocation, UriKind.RelativeOrAbsolute));
BinaryReader binary = new BinaryReader(sri.Stream);
byte[] imgByteArray = binary.ReadBytes((int)(sri.Stream.Length));
binary.Close();
binary.Dispose();
return imgByteArray;
}
public static WriteableBitmap ConvertToImage(Byte[] inputBytes)
{
MemoryStream ms = new MemoryStream(inputBytes);
WriteableBitmap img = new WriteableBitmap(400, 400);
img.LoadJpeg(ms);
return (img);
}