From 5a1124426dfbf20ba02c22f7b4b2bb96b83745bd Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 24 Feb 2019 12:45:48 +0000 Subject: [PATCH] Rewrite screenshot saving. --- OpenRA.Game/Game.cs | 24 ++------ OpenRA.Game/Graphics/PlatformInterfaces.cs | 2 +- OpenRA.Game/Settings.cs | 3 - .../Sdl2GraphicsContext.cs | 56 ++++++++++++------- .../ThreadedGraphicsContext.cs | 8 +-- 5 files changed, 46 insertions(+), 47 deletions(-) diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 82ce1000d0..89bf641b1d 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -530,31 +530,19 @@ namespace OpenRA static void TakeScreenshotInner() { - Log.Write("debug", "Taking screenshot"); - - Bitmap bitmap; - using (new PerfTimer("Renderer.TakeScreenshot")) - bitmap = Renderer.Context.TakeScreenshot(); - - ThreadPool.QueueUserWorkItem(_ => + using (new PerfTimer("Renderer.SaveScreenshot")) { var mod = ModData.Manifest.Metadata; var directory = Platform.ResolvePath(Platform.SupportDirPrefix, "Screenshots", ModData.Manifest.Id, mod.Version); Directory.CreateDirectory(directory); var filename = TimestampedFilename(true); - var format = Settings.Graphics.ScreenshotFormat; - var extension = ImageCodecInfo.GetImageEncoders().FirstOrDefault(x => x.FormatID == format.Guid) - .FilenameExtension.Split(';').First().ToLowerInvariant().Substring(1); - var destination = Path.Combine(directory, string.Concat(filename, extension)); + var path = Path.Combine(directory, string.Concat(filename, ".png")); + Log.Write("debug", "Taking screenshot " + path); - using (new PerfTimer("Save Screenshot ({0})".F(format))) - bitmap.Save(destination, format); - - bitmap.Dispose(); - - RunAfterTick(() => Debug("Saved screenshot " + filename)); - }); + Renderer.Context.SaveScreenshot(path); + Debug("Saved screenshot " + filename); + } } static void InnerLogicTick(OrderManager orderManager) diff --git a/OpenRA.Game/Graphics/PlatformInterfaces.cs b/OpenRA.Game/Graphics/PlatformInterfaces.cs index e9880320bb..9a79ba649c 100644 --- a/OpenRA.Game/Graphics/PlatformInterfaces.cs +++ b/OpenRA.Game/Graphics/PlatformInterfaces.cs @@ -61,7 +61,7 @@ namespace OpenRA IShader CreateShader(string name); void EnableScissor(int left, int top, int width, int height); void DisableScissor(); - Bitmap TakeScreenshot(); + void SaveScreenshot(string path); void Present(); void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices); void Clear(); diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index d29e8723b9..099692ca58 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using System.Drawing.Imaging; using System.IO; using System.Linq; using OpenRA.Graphics; @@ -166,8 +165,6 @@ namespace OpenRA public string Language = "english"; public string DefaultLanguage = "english"; - - public ImageFormat ScreenshotFormat = ImageFormat.Png; } public class SoundSettings diff --git a/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs b/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs index 9250a7b421..77e045819b 100644 --- a/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs +++ b/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs @@ -11,7 +11,11 @@ using System; using System.Drawing; +using System.IO; +using System.Threading; +using OpenRA.FileFormats; using OpenRA.Graphics; +using OpenRA.Support; using SDL2; namespace OpenRA.Platforms.Default @@ -111,39 +115,49 @@ namespace OpenRA.Platforms.Default OpenGL.CheckGLError(); } - public Bitmap TakeScreenshot() + public void SaveScreenshot(string path) { - var rect = new Rectangle(Point.Empty, window.SurfaceSize); - var bitmap = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - var data = bitmap.LockBits(rect, - System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + var s = window.SurfaceSize; + var raw = new byte[s.Width * s.Height * 4]; OpenGL.glPushClientAttrib(OpenGL.GL_CLIENT_PIXEL_STORE_BIT); - OpenGL.glPixelStoref(OpenGL.GL_PACK_ROW_LENGTH, data.Stride / 4f); + OpenGL.glPixelStoref(OpenGL.GL_PACK_ROW_LENGTH, s.Width); OpenGL.glPixelStoref(OpenGL.GL_PACK_ALIGNMENT, 1); - OpenGL.glReadPixels(rect.X, rect.Y, rect.Width, rect.Height, OpenGL.GL_BGRA, OpenGL.GL_UNSIGNED_BYTE, data.Scan0); - OpenGL.glFinish(); - - OpenGL.glPopClientAttrib(); - - // Reset alpha channel to fully opaque unsafe { - var colors = (int*)data.Scan0; - var stride = data.Stride / 4; - for (var y = 0; y < rect.Height; y++) - for (var x = 0; x < rect.Width; x++) - colors[y * stride + x] |= 0xFF << 24; + fixed (byte* pRaw = raw) + OpenGL.glReadPixels(0, 0, s.Width, s.Height, + OpenGL.GL_BGRA, OpenGL.GL_UNSIGNED_BYTE, (IntPtr)pRaw); } - bitmap.UnlockBits(data); + OpenGL.glFinish(); + OpenGL.glPopClientAttrib(); - // OpenGL standard defines the origin in the bottom left corner which is why this is upside-down by default. - bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); + ThreadPool.QueueUserWorkItem(_ => + { + // Convert GL pixel data into format expected by png + // - Flip vertically + // - BGRA to RGBA + // - Force A to 255 (no transparent pixels!) + var data = new byte[raw.Length]; + for (var y = 0; y < s.Height; y++) + { + for (var x = 0; x < s.Width; x++) + { + var iData = 4 * (y * s.Width + x); + var iRaw = 4 * ((s.Height - y - 1) * s.Width + x); + data[iData] = raw[iRaw + 2]; + data[iData + 1] = raw[iRaw + 1]; + data[iData + 2] = raw[iRaw + 0]; + data[iData + 3] = byte.MaxValue; + } + } - return bitmap; + var screenshot = new Png(data, window.SurfaceSize.Width, window.SurfaceSize.Height); + screenshot.Save(path); + }); } public void Present() diff --git a/OpenRA.Platforms.Default/ThreadedGraphicsContext.cs b/OpenRA.Platforms.Default/ThreadedGraphicsContext.cs index de2c64d0bc..1d2174f229 100644 --- a/OpenRA.Platforms.Default/ThreadedGraphicsContext.cs +++ b/OpenRA.Platforms.Default/ThreadedGraphicsContext.cs @@ -43,13 +43,13 @@ namespace OpenRA.Platforms.Default Action doPresent; Func getGLVersion; Func getCreateTexture; - Func getTakeScreenshot; Func getCreateFrameBuffer; Func getCreateShader; Func> getCreateVertexBuffer; Action doDrawPrimitives; Action doEnableScissor; Action doSetBlendMode; + Action doSaveScreenshot; public ThreadedGraphicsContext(Sdl2GraphicsContext context, int batchSize) { @@ -86,7 +86,6 @@ namespace OpenRA.Platforms.Default doPresent = () => context.Present(); getGLVersion = () => context.GLVersion; getCreateTexture = () => new ThreadedTexture(this, (ITextureInternal)context.CreateTexture()); - getTakeScreenshot = () => context.TakeScreenshot(); getCreateFrameBuffer = s => new ThreadedFrameBuffer(this, context.CreateFrameBuffer((Size)s, (ITextureInternal)CreateTexture())); getCreateShader = name => new ThreadedShader(this, context.CreateShader((string)name)); getCreateVertexBuffer = length => new ThreadedVertexBuffer(this, context.CreateVertexBuffer((int)length)); @@ -103,6 +102,7 @@ namespace OpenRA.Platforms.Default context.EnableScissor(t.Item1, t.Item2, t.Item3, t.Item4); }; doSetBlendMode = mode => { context.SetBlendMode((BlendMode)mode); }; + doSaveScreenshot = path => context.SaveScreenshot((string)path); Monitor.Pulse(syncObject); } @@ -437,9 +437,9 @@ namespace OpenRA.Platforms.Default Post(doSetBlendMode, mode); } - public Bitmap TakeScreenshot() + public void SaveScreenshot(string path) { - return Send(getTakeScreenshot); + Post(doSaveScreenshot, path); } }