MonoGame 3.2 下,截屏与 Texture2D 的保存

news/2024/7/10 20:02:13 标签: runtime, ffmpeg

10月20日注:后来发现了这篇博文(英文),XNA 中的 Color 实际上是与 Alpha 值自左乘(premultiplied)的,这也解释了直接用 0xARGB 转译而颜色异常的原因。


 

注意,由于采用的是 MonoGame 3.2,因此方法与 XNA 4.0 不完全相同。

目标是将当前 GraphicsDevice 的内容保存为一个 Texture2D,同时还要能输出至文件。

截屏,在 XNA 下早就有人做了,例如这个:XNA4.0 保存屏幕截图方法[1]。

同时,针对 Texture2D.SaveAsPng()(XNA 下),也有人早就发现了其内存泄露问题,并写出了自己的解决方案:Texture2d.SaveAsPng() Memory Leak[2]。

不过 MonoGame 在 Windows 平台下是不支持 Texture2D.SaveAsPng()Texture2D.SaveAsJpeg() 的,所以不管别人内存泄露啥的,这个保存方法还是要自己实现。(请参考 MonoGame 的代码;有相关外文帖子,不过地址我忘记了。)

其实呢,是我想偷懒,毕竟保存到 Texture2D 后还要写一段代码让其显示出来、消失、再进入原主循环,太麻烦;而且别人的代码还未必可行,先保存为文件看看结果如何。但是保存的时候就出问题了……

链接[2]中有保存的实现,不过那是针对 XNA 的,我在 MonoGame 上运行时出现了颜色错误。也就是说,到目前为止,至少还没有 MonoGame 开发者公开一个成功的保存方案。(当然,像 FEZ 那样的 Indie Game,写了也不会放在网上,对吧。)

其中最值得注意的是像素格式。GDI+ 是 0xARGB,但是 MonoGame 呢?(这里 MonoGame 可能没有遵循 XNA 的规范?)这个我不知道。之前几次用的都是 SurfaceFormat.Color,微软文档上说这是一个 RGB 颜色带 Alpha 通道,不过顺序未指明(从后面看来,应该是 ARGB)。不过 MonoGame 3.2 的 Windows(不是 WindowsGL)模板用的是 SharpDX,DirectX 这里定义的似乎是 A8R8G8B8。直接保存,颜色错误。对比同一个像素发现有这样的事情:0xff3dab0d(正确)→0xffd3abd0(直接保存),因此做这样的处理,还是失败。最后这个成功的结果应该说还是偶然吧。

* 2014-09-05 02:52 记录:在调试一个视频的时候,发现 SurfaceFormat.Color 对应 ffmpegPixelFormatPIX_FMT_RGBA,就是说是实际上排列是 0xABGR(内存中)……抱歉我没从图像中看出来,对颜色不敏感哈……

这里就不展示错误的效果了,我附上了复现错误的注释,有兴趣的人可以自己试试。

下面就是代码。请预先添加两个引用:

1 using System.Drawing.Imaging;
2 using System.Runtime.InteropServices;

创建一个 RenderTarget2D 对象(继承自 Texture2D)保存截屏内容:

 1 public Texture2D TakeScreenshot()
 2 {
 3     int w, h;
 4     w = GraphicsDevice.PresentationParameters.BackBufferWidth;
 5     h = GraphicsDevice.PresentationParameters.BackBufferHeight;
 6     RenderTarget2D screenshot;
 7     // 注意格式一定要是 SurfaceFormat.Bgra32,与 GDI+ 统一。默认的 SurfaceFormat.Color 会导致颜色错误。
 8     screenshot = new RenderTarget2D(GraphicsDevice, w, h, false, SurfaceFormat.Bgra32, DepthFormat.None);
 9     GraphicsDevice.SetRenderTarget(screenshot);
10     Draw(_lastUpdatedGameTime != null ? _lastUpdatedGameTime : new GameTime());
11     GraphicsDevice.Present();
12     GraphicsDevice.SetRenderTarget(null);
13     return screenshot;
14 }

然后是对 Texture2D 的扩展(框架来自链接[2]):

 1 public static void Save(this Texture2D texture, ImageFormat imageFormat, Stream stream)
 2 {
 3     var width = texture.Width;
 4     var height = texture.Height;
 5     using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb))
 6     {
 7         IntPtr safePtr;
 8         BitmapData bitmapData;
 9         System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, width, height);
10         // 这里用 int[width * height] 而不用原文的 byte[4 * width * height],否则图像大小异常
11         int[] textureData = new int[width * height];
12 
13         texture.GetData(textureData);
14         bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
15         safePtr = bitmapData.Scan0;
16         Marshal.Copy(textureData, 0, safePtr, textureData.Length);
17         bitmap.UnlockBits(bitmapData);
18         bitmap.Save(stream, imageFormat);
19 
20         textureData = null;
21     }
22     GC.Collect();
23 }

最后是调用:

 1 void mainButton4_MouseClick(object sender, MouseEventArgs e)
 2 {
 3     var screenshot = RootControlContainer.TakeScreenshot();
 4     if (screenshot != null)
 5     {
 6         // System.Drawing.Bitmap.Save() 方法的文件名必须是绝对路径
 7         // 而在 MonoGame 下难以获取绝对路径,因此若要保存至相对路径,请使用 System.IO.FileStream
 8         using (var fs = new System.IO.FileStream(@"screenshot.png", System.IO.FileMode.OpenOrCreate))
 9         {
10             screenshot.Save(System.Drawing.Imaging.ImageFormat.Png, fs);
11         }
12         screenshot.Dispose();
13     }
14 }

转载于:https://www.cnblogs.com/GridScience/p/3955916.html


http://www.niftyadmin.cn/n/1087135.html

相关文章

[前台]---js获取input标签中name相同的各个value值

页面: <input type"hidden" name"myname" value"aa"/> <input type"hidden" name"myname" value"bb"/> <input type"hidden" name"myname" value"cc"/> <…

MySQL数据库8(二十三)流程结构(if / while)

流程结构 流程结构&#xff1a;代码的执行顺序 If分支 基本语法 If在mysql中有两种基本用法&#xff1a; 1、用在select查询当中&#xff0c;当作一种条件来判断 基本语法&#xff1a;if&#xff08;条件&#xff0c;为真结果&#xff0c;为假结果&#xff09; 最好取别名 if&a…

软件测试方法

sfsdkhfafsadfdsafsd sa fsdfsd sd fsd f sdfs dfs dfds fsdfsdfsdf转载于:https://www.cnblogs.com/chenyonglin/p/11245606.html

map方法的简单使用

假设有一个数组a,将a中的数值以2倍的形式放到b数组中<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta htt…

HDU 6583 Typewriter(后缀自动机)

Typewrite \[ Time Limit: 1500 ms\quad Memory Limit: 262144 kB \] 题意 给出一个字符串 \(s\)&#xff0c;现在你需要构造出这个字符串&#xff0c;你每次可以花费 \(q\) 在末尾加上任意一个字符或者花费 \(p\) 复制任意一段已经构造出来的子串到末尾&#xff0c;问最少需要…

sql 取一条离当前时间最近的记录

mySql写法 select * from om_meeting_schedule s where s.is_use1 ORDER BY ABS(NOW() - s.meeting_begin_date) ASC limit 1 oracle写法 SELECT * FROM ( SELECT *, ABS(NOW() - startTime) AS diffTime FROM om_meeting_schedule ORDER BY diffTime ASC ) C WHERE ro…

面试官,求求你不要问我这么简单但又刁难的算法题了

有时候面试官往往会问我们一些简单&#xff0c;但又刁难的问题&#xff0c;主要是看看你对问题的处理思路。如果你没接触过这些问题&#xff0c;可能一时之间还真不知道怎么处理才比较好&#xff0c;这种题更重要的是一种思维的散发吧&#xff0c;今天就来分享几道题面试中遇到…

Java生成四位随机数,包含数字和字母区分大小写,特别适合做验证码

生成验证码工具类希望可以帮助到大家。 public class RandomFourNumUtils {/*** 生成一个四位数&#xff0c;包括字母* return*/public static String getRandomFourNum() {String[] beforeShuffle new String[] { "2", "3", "4", "5&qu…