CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)
CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)
效果图
本文解决了将OpenGL渲染出来的内容保存到PNG图片的方法。


下载
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
glReadPixels
C#里声明glReadPixels的形式如下:
/// <summary>
/// Reads a block of pixels from the frame buffer.
/// </summary>
/// <param name="x">Top-Left X value.</param>
/// <param name="y">Top-Left Y value.</param>
/// <param name="width">Width of block to read.</param>
/// <param name="height">Height of block to read.</param>
/// <param name="format">Specifies the format of the pixel data. The following symbolic values are accepted: OpenGL.COLOR_INDEX, OpenGL.STENCIL_INDEX, OpenGL.DEPTH_COMPONENT, OpenGL.RED, OpenGL.GREEN, OpenGL.BLUE, OpenGL.ALPHA, OpenGL.RGB, OpenGL.RGBA, OpenGL.LUMINANCE and OpenGL.LUMINANCE_ALPHA.</param>
/// <param name="type">Specifies the data type of the pixel data.Must be one of OpenGL.UNSIGNED_BYTE, OpenGL.BYTE, OpenGL.BITMAP, OpenGL.UNSIGNED_SHORT, OpenGL.SHORT, OpenGL.UNSIGNED_INT, OpenGL.INT or OpenGL.FLOAT.</param>
/// <param name="pixels">Storage for the pixel data received.</param>
[DllImport(Win32.OpenGL32, EntryPoint = "glReadPixels", SetLastError = true)]
public static extern void ReadPixels(int x, int y, int width, int height, uint format, uint type, IntPtr pixels);
这个函数的功能是:将指定范围(x, y, width, height)的像素值读入指定的内存处(pixels)。能把像素信息读到内存里,就可以保存到文件了。
其中(x, y)是以窗口左下角为原点(0, 0)的。而Windows窗口是以左上角为原点的。所以用的时候要注意转换一下。
数据结构
为方便起见,我先定义一个描述像素的数据结构。
struct Pixel
{
public byte r;
public byte g;
public byte b;
public byte a; public Pixel(byte r, byte g, byte b, byte a)
{
this.r = r; this.g = g; this.b = b; this.a = a;
} public Color ToColor()
{
return Color.FromArgb(a, r, g, b);
} public override string ToString()
{
return string.Format("{0}, {1}, {2}, {3}", r, g, b, a);
}
}
struct Pixel
为了使用非托管数组,还需要用到 UnmanagedArray<T> 。关于这个类型详情见(C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword))。
方法一:Bitmap.SetPixel()
最直接的方法是用Bitmap.SetPixel()来一个一个地指定图片的像素值。
/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var pdata = new UnmanagedArray<Pixel>(width * height);
GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
var bitmap = new Bitmap(width, height);
int index = ;
for (int j = height - ; j >= ; j--)
{
for (int i = ; i < width; i++)
{
Pixel v = pdata[index++];
Color c = v.ToColor();
bitmap.SetPixel(i, j, c);
}
} bitmap.Save(filename);
}
方法二:Marshal.Copy
方法一用到的SetPixel()速度是很慢的。
先把读到的内容写入一个byte[],然后再用Marshal.Copy()复制到Bitmap。这个方法的思路是(非托管数组->托管数组->bmpData.Scan0)
/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var pdata = new UnmanagedArray<Pixel>(width * height);
GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
var bitmap = new Bitmap(width, height, format);
var bitmapRect = new Rectangle(, , bitmap.Width, bitmap.Height);
System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
{
int length = Math.Abs(bmpData.Stride) * bitmap.Height;
byte[] bitmapBytes = new byte[length];
int index = ;
for (int j = height - ; j >= ; j--)
{
for (int i = ; i < width; i++)
{
Pixel v = pdata[index++];
bitmapBytes[j * bmpData.Stride + i * + ] = v.b;
bitmapBytes[j * bmpData.Stride + i * + ] = v.g;
bitmapBytes[j * bmpData.Stride + i * + ] = v.r;
bitmapBytes[j * bmpData.Stride + i * + ] = v.a;
}
} System.Runtime.InteropServices.Marshal.Copy(bitmapBytes, , bmpData.Scan0, length);
}
bitmap.UnlockBits(bmpData); bitmap.Save(filename);
}
方法三:直接写入bmpData.Scan0
上一个方法里,通过托管数组byte[]进行过渡,是为了使用Marshal.Copy()这个method。但是我明明可以直接操作bmpData.Scan0啊,何必弄个byte[]。
/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var pdata = new UnmanagedArray<Pixel>(width * height);
GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
var bitmap = new Bitmap(width, height, format);
Rectangle bitmapRect = new Rectangle(, , bitmap.Width, bitmap.Height);
System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
unsafe
{
var array = (byte*)bmpData.Scan0.ToPointer();
int index = ;
for (int j = height - ; j >= ; j--)
{
for (int i = ; i < width; i++)
{
Pixel v = pdata[index++];
array[j * bmpData.Stride + i * + ] = v.b;
array[j * bmpData.Stride + i * + ] = v.g;
array[j * bmpData.Stride + i * + ] = v.r;
array[j * bmpData.Stride + i * + ] = v.a;
}
}
}
bitmap.UnlockBits(bmpData); bitmap.Save(filename);
}
方法四:ReadPixels直接搞定
在上面的方法里,思路是(非托管数组->非托管数组)。
显然这个转换步骤也是多余的,直接让ReadPixels写入bmpData.Scan0的位置不就好了嘛。
/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
var bitmap = new Bitmap(width, height, format);
var bitmapRect = new Rectangle(, , bitmap.Width, bitmap.Height);
System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
GL.ReadPixels(x, y, width, height, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, bmpData.Scan0);
bitmap.UnlockBits(bmpData);
bitmap.RotateFlip(RotateFlipType.Rotate180FlipX); bitmap.Save(filename);
}
总结
从OpenGL窗口读取出图片,是非常有用的。
原CSharpGL的其他功能(UI、3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。
欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL)
CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)的更多相关文章
- Android 如何将Canvas上绘制的内容保存成本地图片(转)
效果如下图所示 保存在sd卡上的文件为 手机上显示效果为: 1>>在Manifest文件中增加相应权限 <!-- 在SDCard中创建与删除文件权限 --> <uses- ...
- CSharpGL(14)用geometry shader渲染模型的法线(normal)
+BIT祝威+悄悄在此留下版了个权的信息说: CSharpGL(14)用geometry shader渲染模型的法线(normal) +BIT祝威+悄悄在此留下版了个权的信息说: 2016-08-13 ...
- CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探
CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码 ...
- iOS学习笔记(1)— UIView 渲染和内容管理
iOS中应用程序基本上都是基于MVC模式开发的.UIView就是模型-视图-控制器中的视图,在iOS终端上看到的.摸到的都是UIView. UIView在屏幕上定义了一个矩形区域和管理区域内容的接口. ...
- 高大上的微信小程序中渲染html内容—技术分享
大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题.但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢? 解决方案 wxP ...
- 3月19日 html(一) html基础内容
---恢复内容开始--- 今天学习了html的第一节课,是些比较简单的基础知识,知道如何向网页里添加文本.图片.表格.超链接之类的,如何去编写这些代码. html(hyper text makeup ...
- iOS开发之聊天模块--内容保存逻辑实现
需求详解: 在实际开发中,有可能是在后期优化的时候,会有这么需要优化的需求:聊天输入框保存之前输入的文本,提高用户的良好体验. 在聊天模块中,用户可能会在输入框输入若干字符,但是没有点击发送就点击退出 ...
- Delphi RichEdit的内容保存为图片
uses RichEdit; {将RichEdit1的内容保存为图片,此函数也适合于RxRichEdit,即RichEdit: TRxRichEdit}procedure RichEditToCanv ...
- 如何给wordpress首页自动显示文章内容的第一个图片
敏捷个人手机应用中使用到的数据来源于wordpress中,因为自己写的页面,所以可以自己写代码获取文章内容的第一个图片作为文章缩略图来显示,这样用户看到首页时图文并茂,感觉会好一些. 现在后台简单的使 ...
随机推荐
- 读书笔记:JavaScript DOM 编程艺术(第二版)
读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...
- ASP.NET Core 折腾笔记一
前言: 在ASP.NET Core 1.0时,曾折腾过一次,后因发现不了System.Data而停止. 更因VS2015提示过期Delete掉VS了,其实主要还是笔记本的硬盘空间吃紧. 快双十一了,本 ...
- .NET Core系列 :4 测试
2016.6.27 微软已经正式发布了.NET Core 1.0 RTM,但是工具链还是预览版,同样的大量的开源测试库也都是至少发布了Alpha测试版支持.NET Core, 这篇文章 The Sta ...
- ABP文档 - SignalR 集成
文档目录 本节内容: 简介 安装 服务端 客户端 连接确立 内置功能 通知 在线客户端 帕斯卡 vs 骆峰式 你的SignalR代码 简介 使用Abp.Web.SignalR nuget包,使基于应用 ...
- MVC常遇见的几个场景代码分享
本次主要分享几个场景的处理代码,有更好处理方式多多交流,相互促进进步:代码由来主要是这几天使用前端Ace框架做后台管理系统,这Ace是H5框架里面的控件效果挺多的,做兼容也很好,有点遗憾是控件效果基本 ...
- 【原创分享·微信支付】 C# MVC 微信支付教程系列之扫码支付
微信支付教程系列之扫码支付 今天,我们来一起探讨一下这个微信扫码支付.何为扫码支付呢?这里面,扫的码就是二维码了,就是我们经常扫一扫的那种二维码图片,例如,我们自己添 ...
- PHP代码优化
1 代码优化 1 尽量静态化 如果一个方法能被静态,那就声明它为静态的,速度可提高1/4,甚至我测试的时候,这个提高了近三倍. 当然了,这个测试方法需要在十万级以上次执行,效果才明显. 其实静态方法和 ...
- VS2015在创建项目时的一些注意事项
一.下面是在创建一个新的项目是我最常用的,现在对他们一一做一个详细的介绍: 1.Win32控制台应用程序我平时编写小的C/C++程序都用它,它应该是用的最多的. 2.名称和解决方案名称的区别:名称是项 ...
- 【干货分享】流程DEMO-外出申请
流程名: 外出申请 流程相关文件: 流程包.xml 流程说明: 直接导入流程包文件,即可使用本流程 表单: 流程: 图片:2.png DEMO包下载: http://files.cnblog ...
- ASP.NET Aries DataGrid 配置表头说明文档
DataGrid 配置表头 字段 中文 说明 Field 字段 注意:mg_ 开头的字段为层级表头 Title 列称 OrderNum 序号 显示的顺序(冻结和非冻结列是两个组的序号) Width 列 ...