博客园地址
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
对应 ffmpeg 的 PixelFormat
是 PIX_FMT_RGBA
,就是说是实际上排列是 0xABGR(内存中)……抱歉我没从图像中看出来,对颜色不敏感哈……
这里就不展示错误的效果了,我附上了复现错误的注释,有兴趣的人可以自己试试。
下面就是代码。请预先添加两个引用:
using System.Drawing.Imaging; using System.Runtime.InteropServices;
|
创建一个 RenderTarget2D
对象(继承自 Texture2D
)保存截屏内容:
public Texture2D TakeScreenshot() { int w, h; w = GraphicsDevice.PresentationParameters.BackBufferWidth; h = GraphicsDevice.PresentationParameters.BackBufferHeight; RenderTarget2D screenshot; screenshot = new RenderTarget2D(GraphicsDevice, w, h, false, SurfaceFormat.Bgra32, DepthFormat.None); GraphicsDevice.SetRenderTarget(screenshot); Draw(_lastUpdatedGameTime != null ? _lastUpdatedGameTime : new GameTime()); GraphicsDevice.Present(); GraphicsDevice.SetRenderTarget(null); return screenshot; }
|
然后是对 Texture2D
的扩展(框架来自链接[2]):
public static void Save(this Texture2D texture, ImageFormat imageFormat, Stream stream) { var width = texture.Width; var height = texture.Height; using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb)) { IntPtr safePtr; BitmapData bitmapData; System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, width, height); int[] textureData = new int[width * height];
texture.GetData(textureData); bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); safePtr = bitmapData.Scan0; Marshal.Copy(textureData, 0, safePtr, textureData.Length); bitmap.UnlockBits(bitmapData); bitmap.Save(stream, imageFormat);
textureData = null; } GC.Collect(); }
|
最后是调用:
void mainButton4_MouseClick(object sender, MouseEventArgs e) { var screenshot = RootControlContainer.TakeScreenshot(); if (screenshot != null) { using (var fs = new System.IO.FileStream(@"screenshot.png", System.IO.FileMode.OpenOrCreate)) { screenshot.Save(System.Drawing.Imaging.ImageFormat.Png, fs); } screenshot.Dispose(); } }
|