CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)

效果图

本文解决了将OpenGL渲染出来的内容保存到PNG图片的方法。

下载

CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)

glReadPixels

C#里声明glReadPixels的形式如下:

 1         /// <summary>
 2         /// Reads a block of pixels from the frame buffer.
 3         /// </summary>
 4         /// <param name="x">Top-Left X value.</param>
 5         /// <param name="y">Top-Left Y value.</param>
 6         /// <param name="width">Width of block to read.</param>
 7         /// <param name="height">Height of block to read.</param>
 8         /// <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>
 9         /// <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>
10         /// <param name="pixels">Storage for the pixel data received.</param>
11         [DllImport(Win32.OpenGL32, EntryPoint = "glReadPixels", SetLastError = true)]
12         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窗口是以左上角为原点的。所以用的时候要注意转换一下。

数据结构

为方便起见,我先定义一个描述像素的数据结构。

 1     struct Pixel
 2     {
 3         public byte r;
 4         public byte g;
 5         public byte b;
 6         public byte a;
 7
 8         public Pixel(byte r, byte g, byte b, byte a)
 9         {
10             this.r = r; this.g = g; this.b = b; this.a = a;
11         }
12
13         public Color ToColor()
14         {
15             return Color.FromArgb(a, r, g, b);
16         }
17
18         public override string ToString()
19         {
20             return string.Format("{0}, {1}, {2}, {3}", r, g, b, a);
21         }
22     }

struct Pixel

为了使用非托管数组,还需要用到 UnmanagedArray<T> 。关于这个类型详情见(C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword))。

方法一:Bitmap.SetPixel()

最直接的方法是用Bitmap.SetPixel()来一个一个地指定图片的像素值。

 1         /// <summary>
 2         /// 把OpenGL渲染的内容保存到图片文件。
 3         /// </summary>
 4         /// <param name="x">左下角坐标为(0, 0)</param>
 5         /// <param name="y">左下角坐标为(0, 0)</param>
 6         /// <param name="width">宽度</param>
 7         /// <param name="height">高度</param>
 8         /// <param name="filename"></param>
 9         public static void Save2Picture(int x, int y, int width, int height, string filename)
10         {
11             var pdata = new UnmanagedArray<Pixel>(width * height);
12             GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
13             var bitmap = new Bitmap(width, height);
14             int index = 0;
15             for (int j = height - 1; j >= 0; j--)
16             {
17                 for (int i = 0; i < width; i++)
18                 {
19                     Pixel v = pdata[index++];
20                     Color c = v.ToColor();
21                     bitmap.SetPixel(i, j, c);
22                 }
23             }
24
25             bitmap.Save(filename);
26         }

方法二:Marshal.Copy

方法一用到的SetPixel()速度是很慢的。

先把读到的内容写入一个byte[],然后再用Marshal.Copy()复制到Bitmap。这个方法的思路是(非托管数组->托管数组->bmpData.Scan0)

 1         /// <summary>
 2         /// 把OpenGL渲染的内容保存到图片文件。
 3         /// </summary>
 4         /// <param name="x">左下角坐标为(0, 0)</param>
 5         /// <param name="y">左下角坐标为(0, 0)</param>
 6         /// <param name="width">宽度</param>
 7         /// <param name="height">高度</param>
 8         /// <param name="filename"></param>
 9         public static void Save2Picture(int x, int y, int width, int height, string filename)
10         {
11             var pdata = new UnmanagedArray<Pixel>(width * height);
12             GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
13             var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
14             var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
15             var bitmap = new Bitmap(width, height, format);
16             var bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
17             System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
18             {
19                 int length = Math.Abs(bmpData.Stride) * bitmap.Height;
20                 byte[] bitmapBytes = new byte[length];
21                 int index = 0;
22                 for (int j = height - 1; j >= 0; j--)
23                 {
24                     for (int i = 0; i < width; i++)
25                     {
26                         Pixel v = pdata[index++];
27                         bitmapBytes[j * bmpData.Stride + i * 4 + 0] = v.b;
28                         bitmapBytes[j * bmpData.Stride + i * 4 + 1] = v.g;
29                         bitmapBytes[j * bmpData.Stride + i * 4 + 2] = v.r;
30                         bitmapBytes[j * bmpData.Stride + i * 4 + 3] = v.a;
31                     }
32                 }
33
34                 System.Runtime.InteropServices.Marshal.Copy(bitmapBytes, 0, bmpData.Scan0, length);
35             }
36             bitmap.UnlockBits(bmpData);
37
38             bitmap.Save(filename);
39         }

方法三:直接写入bmpData.Scan0

上一个方法里,通过托管数组byte[]进行过渡,是为了使用Marshal.Copy()这个method。但是我明明可以直接操作bmpData.Scan0啊,何必弄个byte[]。

 1         /// <summary>
 2         /// 把OpenGL渲染的内容保存到图片文件。
 3         /// </summary>
 4         /// <param name="x">左下角坐标为(0, 0)</param>
 5         /// <param name="y">左下角坐标为(0, 0)</param>
 6         /// <param name="width">宽度</param>
 7         /// <param name="height">高度</param>
 8         /// <param name="filename"></param>
 9         public static void Save2Picture(int x, int y, int width, int height, string filename)
10         {
11             var pdata = new UnmanagedArray<Pixel>(width * height);
12             GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
13             var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
14             var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
15             var bitmap = new Bitmap(width, height, format);
16             Rectangle bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
17             System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
18             unsafe
19             {
20                 var array = (byte*)bmpData.Scan0.ToPointer();
21                 int index = 0;
22                 for (int j = height - 1; j >= 0; j--)
23                 {
24                     for (int i = 0; i < width; i++)
25                     {
26                         Pixel v = pdata[index++];
27                         array[j * bmpData.Stride + i * 4 + 0] = v.b;
28                         array[j * bmpData.Stride + i * 4 + 1] = v.g;
29                         array[j * bmpData.Stride + i * 4 + 2] = v.r;
30                         array[j * bmpData.Stride + i * 4 + 3] = v.a;
31                     }
32                 }
33             }
34             bitmap.UnlockBits(bmpData);
35
36             bitmap.Save(filename);
37         }

方法四:ReadPixels直接搞定

在上面的方法里,思路是(非托管数组->非托管数组)。

显然这个转换步骤也是多余的,直接让ReadPixels写入bmpData.Scan0的位置不就好了嘛。

 1         /// <summary>
 2         /// 把OpenGL渲染的内容保存到图片文件。
 3         /// </summary>
 4         /// <param name="x">左下角坐标为(0, 0)</param>
 5         /// <param name="y">左下角坐标为(0, 0)</param>
 6         /// <param name="width">宽度</param>
 7         /// <param name="height">高度</param>
 8         /// <param name="filename"></param>
 9         public static void Save2Picture(int x, int y, int width, int height, string filename)
10         {
11             var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
12             var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
13             var bitmap = new Bitmap(width, height, format);
14             var bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
15             System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
16             GL.ReadPixels(x, y, width, height, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, bmpData.Scan0);
17             bitmap.UnlockBits(bmpData);
18             bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
19
20             bitmap.Save(filename);
21         }

总结

从OpenGL窗口读取出图片,是非常有用的。

原CSharpGL的其他功能(UI、3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。

欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL)

CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)相关推荐

  1. Android 如何将Canvas上绘制的内容保存成本地图片

    效果如下图所示 保存在sd卡上的文件为 手机上显示效果为: 1>>在Manifest文件中增加相应权限 <!-- 在SDCard中创建与删除文件权限 --><uses-p ...

  2. 浅谈高大上的微信小程序中渲染html内容—技术分享

    大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题.但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢? 解决方案 wxP ...

  3. php渲染页面简单例子,微信小程序如何渲染html内容(示例讲解)

    本篇文章给大家带来的内容是关于微信小程序如何渲染html内容(示例讲解),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 最近又做了一个新的小程序关于物流订单查询 遇到了一个小问题:数 ...

  4. 2020年阴阳师服务器维护,2020阴阳师2月19日更新官方公告及内容汇总

    小编今天给各位玩家朋友们带来的是2020阴阳师2月19日更新官方公告及内容汇总,阴阳师今天进行了版本更新,大家期待已久的超鬼王活动也是正式上线了,那么还有哪些精彩的活动?哪些新的内容呢?相信不少的玩家 ...

  5. 微信 小程序 python 渲染_微信小程序渲染html内容

    最近又做了一个新的小程序关于物流订单查询欢迎来体验 遇到了一个小问题:数据中返回电话号码的字符串识别出来并且高亮和可以绑定事件.比如数据中包含您的派送员黄xx正在派件,电话:137xxxx41460已 ...

  6. iOS学习笔记(1)— UIView 渲染和内容管理

    iOS中应用程序基本上都是基于MVC模式开发的.UIView就是模型-视图-控制器中的视图,在iOS终端上看到的.摸到的都是UIView. UIView在屏幕上定义了一个矩形区域和管理区域内容的接口. ...

  7. 实操-微信小程序渲染html内容

    实操-微信小程序渲染html内容 在小程序中渲染html内容--wxParse 下载 wxParse 组件并配置到项目当中 使用 wxParse 1.在 js文件中引入 2.在wxml中使用 保存一下 ...

  8. 在微信小程序中渲染HTML内容

    大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题.但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢? 解决方案 wxP ...

  9. 微信小程序中嵌套html_在微信小程序中渲染HTML内容的方法示例

    大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题.但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢? 解决方案 wxP ...

最新文章

  1. AI一分钟 | 特斯拉再融46亿;腾讯AI Lab宣布开源多标签图像数据集
  2. Nginx 变量漫谈
  3. groovy oracle数据库,使用Groovy /搖籃到用什麼驅動程序連接到Oracle數據庫
  4. 手动脱Mole Box壳实战总结
  5. 拓扑排序(完整案列及C语言完整代码实现)
  6. window下安装Oracle11G安装
  7. 简单三分钟,本地搭建k8s
  8. Qt样式表之一:Qt样式表和盒子模型介绍
  9. python sanic orm_基于sanic的微服务框架 - 架构分析
  10. 高级版本VS打开低版本VS工程,无法调试的问题
  11. 关于前段与后端数据库的连接
  12. Netty in action—Bootstraping
  13. 随手记_英语_学术写作_标点符号的使用
  14. 第一周例行报告及作业汇总
  15. Excel表VLOOKUP多个条件匹配数据
  16. 练字格子纸模板pdf_十字田字格模板空格40格-练字用书十字格a4打印版下载最新excel版-西西软件下载...
  17. 源自神话的写作要义之英雄之旅
  18. 数字IC笔试题,大疆校招16题(仅供参考)
  19. 【题解】【循环】幂级数求和
  20. CSP202109-4 收集卡牌

热门文章

  1. 设计模式 - 结构型 - 装饰者模式
  2. spring-boot的access日志格式修改
  3. net core 使用 rabbitmq
  4. CI框架css引入出现问题
  5. Struts详细用法
  6. HEOI2012 朋友圈
  7. [PHP] PHP 格式化日期 format the datetime in PHP
  8. Asp.net团队疯了(同时发布WebMatrix, Razor, MVC3和Orchard)
  9. 小程序点击按钮 关闭小程序
  10. SSO 单点登录会话管理