【转】【WPF】WriteableBitmap应用及图片数据格式转换
使用 WriteableBitmap 类基于每个框架来更新和呈现位图。这对于生成算法内容(如分形图像)和数据可视化(如音乐可视化工具)很有用。
WriteableBitmap 类使用两个缓冲区。“后台缓冲区”在系统内存中分配,它可以累计当前未显示的内容。“前台缓冲区”在系统内存中分配,它包含当前显示的内容。呈现系统将前台缓冲区复制到视频内存中以便显示。
两个线程使用这两个缓冲区。“用户界面 (UI) 线程”生成 UI 但不将其呈现在屏幕上。UI 线程响应用户输入、计时器以及其他事件。一个应用程序可以具有多个 UI 线程。“呈现线程”撰写和呈现 UI 线程的变化。每个应用程序只有一个呈现线程。
UI 线程将内容写入后台缓冲区。呈现线程从前台缓冲区读取内容,然后将其复制到视频内存中。将使用更改的矩形区域跟踪对后台缓冲区的更改。
WriteableBitmap使用两个线程来使用这两个缓冲区。UI 线程响应用户输入、计时器以及其他事件。“呈现线程”撰写和呈现 UI 线程的变化。在WriteableBitmap中,UI 线程将内容写入后台缓冲区。呈现线程从前台缓冲区读取内容,然后将其复制到视频内存中,最后切换两个缓存区。
WritePixel方法
可以使用WritePixel方法来更新缓冲区:
. public void WritePixels(Int32Rect sourceRect, Array pixels, int stride, int offset);
. public void WritePixels(Int32Rect sourceRect, IntPtr buffer, int bufferSize, int stride);
. public void WritePixels(Int32Rect sourceRect, Array sourceBuffer, int sourceBufferStride,
int destinationX, int destinationY);
. public void WritePixels(Int32Rect sourceRect, IntPtr sourceBuffer, int sourceBufferSize,
int sourceBufferStride, int destinationX, int destinationY);
关于Stride
Stride是Bitmap里一个令人头痛的东西,它代表着一张图片每一行的扫描宽度(跨距)。跨距总是大于或等于实际像素宽度。如果跨距为正,则位图自顶向下。如果跨距为负,则位图颠倒。Stride是指图像每一行需要占用的字节数。根据BMP格式的标准,Stride一定要是4的倍数。据个例子,一幅1024*768的24bppRgb的图像,每行有效的像素信息应该是1024*3 = 3072。因为已经是4的倍数,所以Stride就是3072。那么如果这幅图像是35*30,那么一行的有效像素信息是105,但是105不是4的倍数,所以填充空字节(也就是0),Stride应该是108。
在WritableBitmap中,可以使用:
.BackBufferStride
属性来获得Stride值。
明白了Stride的意义,让我们来看看以下代码:
WriteableBitmap bitmap = new WriteableBitmap(, , , , PixelFormats.Bgr24, null);
Byte[] buffer = new Byte[bitmap.BackBufferStride * bitmap.PixelHeight];
for (byte i = ; i < buffer.Length; i++)
{
buffer[i] = i;
}
bitmap.WritePixels(new Int32Rect(, , , ), buffer, bitmap.BackBufferStride, ); Marshal.Copy(bitmap.BackBuffer, buffer, , buffer.Length);
在上面的代码里,我们新建了一个15X2的24位位图,它的行跨距是(15*3+3)/4*4=48,没行多了3个填充字节。我们把一个由0到95的连续数组复制到该位图中,然后重新复制回来,可以看到,第45、46、47个元素值是0:
WritePixel函数中的Stride
在WritePixel的四个重载函数里,同样有一个stride的参数(后两个重载函数用的是sourceBufferStride),该参数表示源数组(函数里的sourceBuffer参数)的跨度。
举个例子,假设WriteableBitmap是8位的,源数组是一个长度为12的一维数组,如果stride为4,则表示填充时,该一维数组代表的一个4X3的图像:
如果stride为3,则该数组代表的是一个3X4的图像:
WritePixel方法就是用这个图像来填充缓存区域:
sourceRect和stride
刚才说到,在WritePixel函数里,通过传入参数stride来设定填充源数组(sourceBuffer参数)的尺寸,把它看做一个“二维数组”,WritePixel把这个“二维数组”的值写入WriteableBitmap对象的缓冲区中。那么,如果我只想写这个“二维数组”的一部分,该怎么做呢?
于是第一个参数sourceRect就是设定这个区域。
设想一下,如下图,我们使用stride把一个长度为81的sourceBuffer分割成9X9的“二维数组”,因为这里使用的WriteableBitmap是24位的,因此在图上我用三种颜色表示RGB值:
显然,该数组一次最多可以填充一个3X9的像素的缓冲区(因为WriteableBitmap是24位的,所以填充时三个字节才能填充一个像素),如果需要填充的区域大于3X9,会抛出异常:ArgumentException。
如果我打算填充的区域是2X4的区域呢?这意味着仅仅使用一部分sourceBuffer:
这时,可以把sourceRect设置为(1,2,2,4)
总之,WritePixel方法的本质就是把sourceBuffer看做一副图片,使用stride来设定该图片的宽度,利用sourceRect来确定使用该图片的哪部分填充,而destinationX、destinationY则是指定把图片填充到缓冲区的位置坐标。函数调用完成后,会自动调用更新,重新绘制屏幕上的图像。
Lock、Unlock和AddDirtyRect
一般的,WritePixel方法使用于快速填充图像,如果我们仅仅想对图像进行“像素级”的改变,那么,WritePixel未免过于粗犷。这时候,需要使用Lock、AddDirtyRect、Unlock方法了。
以下代码,通过使用上述函数,把一副图片“反色”:
unsafe
{
var bytes = (byte*)m_Bitmap.BackBuffer.ToPointer();
m_Bitmap.Lock();
for (int i = ; i < m_Bitmap.BackBufferStride * m_Bitmap.PixelHeight; i++)
{
bytes[i] = (byte)(-bytes[i]);
}
m_Bitmap.AddDirtyRect(new Int32Rect(, , m_Bitmap.PixelWidth, m_Bitmap.PixelHeight));
m_Bitmap.Unlock();
}
在进行操作前,先使用Lock方法,锁定后台缓冲区,这时,呈现系统得不到后台缓冲区的数据,因此不发送更新,屏幕不发生变化。
其次,直接修改图片内存,c#不推荐使用指针,因此该方法是unsafe的。
再次,调用AddDirtyRect方法,指示代码对后台缓冲区所做的更改,通知呈现系统,缓冲区哪部分发生了改变。
最后,使用Unlock方法,解除对后台缓冲区的锁定。
一般来说,为了提高效率,不需要更新整幅图片,因此,可以通过调用多次AddDirtyRect方法,进行局部修改:
m_Bitmap.AddDirtyRect(new Int32Rect(, , , ));
m_Bitmap.AddDirtyRect(new Int32Rect(, , , ));
m_Bitmap.AddDirtyRect(new Int32Rect(, , , ));
WPF Image控件 Source: Byte[] ,BitmapImage 相互转换
文件转为byte[]
FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read);
byte[] desBytes = new byte[fs.Length];
fs.Read(desBytes, , desBytes.Length);
fs.Close();
byte[]转换为BitmapImage:
public static BitmapImage ByteArrayToBitmapImage(byte[] byteArray)
{
BitmapImage bmp = null;
try
{
bmp = new BitmapImage();
bmp.BeginInit();
bmp.StreamSource = new MemoryStream(byteArray);
bmp.EndInit();
}
catch
{
bmp = null;
}
return bmp;
}
BitmapImage转换为byte[]:
public static byte[] BitmapImageToByteArray(BitmapImage bmp)
{
byte[] byteArray = null;
try
{
Stream sMarket = bmp.StreamSource;
if (sMarket != null && sMarket.Length > )
{
//很重要,因为Position经常位于Stream的末尾,导致下面读取到的长度为0。
sMarket.Position = ; using (BinaryReader br = new BinaryReader(sMarket))
{
byteArray = br.ReadBytes((int)sMarket.Length);
}
}
}
catch
{
//other exception handling
}
return byteArray;
}
WriteableBitmap wb = new WriteableBitmap(img.Source as BitmapSource);//将Image对象转换为WriteableBitmap
byte[] b = Convert.FromBase64String(GetBase64Image(wb));//得到byte数组
WriteableBitmap转为BitmapImage对象
var bi= new BitmapImage();
bi.SetSource(wb.ToImage().ToStream()); //其中wb是WriteableBitmap对象。
BitmapImage转为WriteableBitmap对象
WriteableBitmap wb = new WriteableBitmap(bi.Source as BitmapSource);
将WriteableBitmap转为字节数组
byte[] b = Convert.FromBase64String(GetBase64Image(wb));
//这里通过base64间接处理,效率不是很高。
将字节数组/Stream流 转为BitmapImage对象
MemoryStream ms = new MemoryStream(b); // b为byte[]
using (var stream = new MemoryStream(data))// data为byte[]
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = stream;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
}
//以下方法为Stream转BitmapFrame
using (var stream = new MemoryStream(data))
{
var bi = BitmapFrame.Create(stream , BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
}
Bitmap 转 Imagesource
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteObject(IntPtr hObject); IntPtr hBitmap = bitmap.GetHbitmap();
ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions()
);
DeleteObject(hBitmap);
补充:2016-7-26
WriteableBitmap wtbBmp;//Image对象
RenderTargetBitmap rtbitmap = new RenderTargetBitmap(wtbBmp.PixelWidth, wtbBmp.PixelHeight, wtbBmp.DpiX, wtbBmp.DpiY, PixelFormats.Default);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
context.DrawImage(wtbBmp, new Rect(, , wtbBmp.Width, wtbBmp.Height));
}
rtbitmap.Render(drawingVisual);
JpegBitmapEncoder bitmapEncoder = new JpegBitmapEncoder();
bitmapEncoder.Frames.Add(BitmapFrame.Create(rtbitmap));
using (System.IO.FileStream fs = new System.IO.FileStream("D:\\mq.jpg", System.IO.FileMode.Create))
jpeg.Save(fs);
再次补充:2019-6-22:
将GDI的Bitmap显示到WriteableBitmap上(两张图都是32位)
private unsafe void DrawBitmap(WriteableBitmap dst, Bitmap src, System.Drawing.Rectangle rect)
{
int srcw = src.Width;
int srch = src.Height;
int left = rect.Left < ? : rect.Left;
int top = rect.Top < ? : rect.Top;
int right = rect.Right > srcw ? srcw : rect.Right;
int bottom = rect.Bottom > srch ? srch : rect.Bottom;
if (left >= right || top >= bottom) return;
BitmapData srcdata = src.LockBits(new System.Drawing.Rectangle(, , srcw, srch), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
dst.Lock();
var srcints = (int*)srcdata.Scan0;
var dstints = (int*)dst.BackBuffer.ToPointer();
for (int j = top; j < bottom; ++j)
{
int y = j * srcw;
for (int i = left; i < right; ++i)
{
int index = y + i;
dstints[index] = srcints[index];
}
}
dst.AddDirtyRect(new Int32Rect(left, top, right - left, bottom - top));
src.UnlockBits(srcdata);
dst.Unlock();
}
反向绘制当然也是可以的:
private unsafe void DrawWriteableBitmap(Bitmap dst, WriteableBitmap src, System.Drawing.Rectangle rect)
{
int dstw = dst.Width;
int dsth = dst.Height;
int left = rect.Left < ? : rect.Left;
int top = rect.Top < ? : rect.Top;
int right = rect.Right > dstw ? dstw : rect.Right;
int bottom = rect.Bottom > dsth ? dsth : rect.Bottom;
if (left >= right || top >= bottom) return;
BitmapData dstdata = dst.LockBits(new System.Drawing.Rectangle(, , dstw, dsth), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var dstints = (int*)dstdata.Scan0;
var srcints = (int*)src.BackBuffer.ToPointer();
for (int j = top; j < bottom; ++j)
{
int y = j * dstw;
for (int i = left; i < right; ++i)
{
int index = y + i;
dstints[index] = srcints[index];
}
}
dst.UnlockBits(dstdata);
}
参考文章:http://www.cnblogs.com/carekee/articles/2039142.html
http://www.oschina.net/question/54100_39186?fromerr=uHZl9PH1
【转】【WPF】WriteableBitmap应用及图片数据格式转换的更多相关文章
- 【C#/WPF】图像数据格式转换时,透明度丢失的问题
问题:工作中涉及到图像的数据类型转换,经常转着转着发现,到了哪一步图像的透明度丢失了! 例如,Bitmap转BitmapImage的经典代码如下: public static BitmapImage ...
- WPF中实现图片文件转换成Visual对象,Viewport3D对象转换成图片
原文:WPF中实现图片文件转换成Visual对象,Viewport3D对象转换成图片 1.图片文件转换成Visual对象 private Visual CreateVisual(string imag ...
- js如何将选中图片文件转换成Base64字符串?
如何将input type="file"选中的文件转换成Base64的字符串呢? 1.首先了解一下为什么要把图片文件转换成Base64的字符串 在常规的web开发过程中,大部分上传 ...
- c#代码 天气接口 一分钟搞懂你的博客为什么没人看 看完python这段爬虫代码,java流泪了c#沉默了 图片二进制转换与存入数据库相关 C#7.0--引用返回值和引用局部变量 JS直接调用C#后台方法(ajax调用) Linq To Json SqlServer 递归查询
天气预报的程序.程序并不难. 看到这个需求第一个想法就是只要找到合适天气预报接口一切都是小意思,说干就干,立马跟学生沟通价格. 不过谈报价的过程中,差点没让我一口老血喷键盘上,话说我们程序猿的人 ...
- [开源]基于WPF实现的Gif图片分割器,提取GIf图片中的每一帧
不知不觉又半个月没有更新博客了,今天终于抽出点时间,来分享一下前段时间的成果. 在网上,我们经常看到各种各样的图片,尤其是GIF图片的动态效果,让整个网站更加富有表现力!有时候,我们看到一些比较好看的 ...
- 【VC++技术杂谈007】使用GDI+进行图片格式转换
本文主要介绍如何使用GDI+对图片进行格式转换,可以转换的图片格式为bmp.jpg.png. 1.加载GDI+库 GDI+是GDI图形库的一个增强版本,提供了一系列Visual C++ API.为了使 ...
- zw版【转发·台湾nvp系列Delphi例程】Delphi 使用 HALCON库件COM控件数据格式转换
zw版[转发·台湾nvp系列Delphi例程]Delphi 使用 HALCON库件COM控件数据格式转换 Delphi 使用 HALCON库件COM控件数据格式转换,与IHObjectX接口有关 va ...
- Java将其他数据格式转换成json字符串格式
package com.wangbo.util; import java.beans.IntrospectionException; import java.beans.Introspector; i ...
- oracle 报“无效数字”异常和“ORA-01830: 日期格式图片在转换整个输入字符串之前结束”
1.问题1 执行下列SQL: sql = "select count(1) as totle from vhl_model_data a where a.OBTAIN_CREATE_TIME ...
随机推荐
- 在Java中调用C
在Java代码中通过JNI调用C函数的步骤如下: 第一步:编写Java代码 第二步:编译Java代码(javac Java文件) 第三步:生成C代码头文件(javah java类名,自动生成) 第四步 ...
- android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件
简介 XStream 是一个开源项目,一套简单实用的类库,用于序列化对象与 XML 对象之间的相互转换. 将 XML 文件内容解析为一个对象或将一个对象序列化为 XML 文件. 1.下载工具 xstr ...
- C安全编码--数组
建议和规则 建议: 理解数组的工作方式 获取数组的长度时不要对指针应用sizeof操作符 显示地指定数组的边界,即使它已经由初始化值列表隐式地指定 规则: 保证数组索引位于合法的范围内 在所有源文件中 ...
- iOS菜单滚动联动内容区域功能实现
平时开发APP中关于此功能还是比较经常碰到,本实例借用三个开源的插件,并对其中一个进行修改调整实现出想要的效果:本文重点介绍修改的内容跟三个插件的运用,这三个插件还可以各自扩展到其它项目的运用: 效果 ...
- OC知识梳理-NSArray与NSMutableArray相关知识
知识普及: 1.数组中的元素在系统中都会有其默认对应的下标,下标是一个整形的数字,默认从0开始. 例:NSArray *arr3 = @["345","234" ...
- 【读书笔记】iOS-GCD-Dispatch Source
一,Dispatch Source是BSD系内核惯有功能kqueue的包装. 参考资料:<Objective-C高级编程 iOS与OS X多线程和内存管理>
- android学习笔记 对话框合集
package com.zhangbz.dialog; import android.app.Activity; import android.app.AlertDialog; import andr ...
- iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载+使用输出流代替文件句柄
前言:本篇讲解,在前篇iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载的基础上,使用输出流代替文件句柄实现大文件断点续传. 在实际开发中,输入输出流用的比较少,但 ...
- iOS开发之网络编程--使用NSURLConnection实现大文件下载
主要思路(实现下载数据分段写入缓存中) 1.使用NSURLConnectionDataDelegate以及代理方法.2.在成功获取响应的代理方法中,获得沙盒全路径,并在该路径下创建空文件和文件句柄.3 ...
- 转 Android学习 之 ColorStateList按钮文字变色
Windows平台VC,对于不同的按钮状态,采用不同的颜色显示文字,实现起来比较复杂,一般都得自绘按钮.但是Android里面实现起来非常方便. 我们首先添加一个ColorStateList资源XML ...