diff --git a/OpenRA.Game/FieldLoader.cs b/OpenRA.Game/FieldLoader.cs index 6c4bb59bb8..a186a678e7 100644 --- a/OpenRA.Game/FieldLoader.cs +++ b/OpenRA.Game/FieldLoader.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; +using System.Drawing.Imaging; using System.Globalization; using System.Linq; using System.Reflection; @@ -328,6 +329,24 @@ namespace OpenRA return InvalidValueAction(value, fieldType, fieldName); } } + else if (fieldType == typeof(ImageFormat)) + { + switch (value.ToLowerInvariant()) + { + case "bmp": + return ImageFormat.Bmp; + case "gif": + return ImageFormat.Gif; + case "jpg": + case "jpeg": + return ImageFormat.Jpeg; + case "tif": + case "tiff": + return ImageFormat.Tiff; + default: + return ImageFormat.Png; + } + } else if (fieldType == typeof(bool)) return ParseYesNo(value, fieldType, fieldName); else if (fieldType.IsArray) diff --git a/OpenRA.Game/FieldSaver.cs b/OpenRA.Game/FieldSaver.cs index 94d6b20724..68642475e2 100644 --- a/OpenRA.Game/FieldSaver.cs +++ b/OpenRA.Game/FieldSaver.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; +using System.Drawing.Imaging; using System.Globalization; using System.Linq; using System.Reflection; @@ -77,6 +78,11 @@ namespace OpenRA if (t == typeof(double)) return ((double)v).ToString(CultureInfo.InvariantCulture); + if (t == typeof(ImageFormat)) + { + return ((ImageFormat)v).ToString(); + } + if (t == typeof(Rectangle)) { var r = (Rectangle)v; diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index c875403264..1433810319 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -11,6 +11,7 @@ using System; using System.Diagnostics; using System.Drawing; +using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Net; @@ -47,14 +48,14 @@ namespace OpenRA { IConnection connection = new NetworkConnection(host, port); if (recordReplay) - connection = new ReplayRecorderConnection(connection, ChooseReplayFilename); + connection = new ReplayRecorderConnection(connection, TimestampedFilename); var om = new OrderManager(host, port, password, connection); JoinInner(om); return om; } - static string ChooseReplayFilename() + static string TimestampedFilename() { return DateTime.UtcNow.ToString("OpenRA-yyyy-MM-ddTHHmmssZ"); } @@ -399,6 +400,35 @@ namespace OpenRA public static void RunAfterTick(Action a) { delayedActions.Add(a); } public static void RunAfterDelay(int delay, Action a) { delayedActions.Add(a, delay); } + static void TakeScreenshotInner() + { + Log.Write("debug", "Taking screenshot"); + + Bitmap bitmap; + using (new PerfTimer("Renderer.TakeScreenshot")) + bitmap = Renderer.TakeScreenshot(); + + ThreadPool.QueueUserWorkItem(_ => + { + var mod = ModData.Manifest.Mod; + var directory = Platform.ResolvePath("^", "Screenshots", mod.Id, mod.Version); + Directory.CreateDirectory(directory); + + var filename = TimestampedFilename(); + 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)); + + using (new PerfTimer("Save Screenshot ({0})".F(format))) + bitmap.Save(destination, format); + + bitmap.Dispose(); + + Game.RunAfterTick(() => Debug("Saved screenshot " + filename)); + }); + } + static void InnerLogicTick(OrderManager orderManager) { var tick = RunTime; @@ -482,6 +512,8 @@ namespace OpenRA InnerLogicTick(worldRenderer.World.OrderManager); } + public static bool TakeScreenshot = false; + static void RenderTick() { using (new PerfSample("render")) @@ -515,6 +547,12 @@ namespace OpenRA using (new PerfSample("render_flip")) Renderer.EndFrame(new DefaultInputHandler(OrderManager.World)); + + if (TakeScreenshot) + { + TakeScreenshot = false; + TakeScreenshotInner(); + } } PerfHistory.Items["render"].Tick(); diff --git a/OpenRA.Game/Graphics/IGraphicsDevice.cs b/OpenRA.Game/Graphics/IGraphicsDevice.cs index 3cfd8fe645..38bd38eb03 100755 --- a/OpenRA.Game/Graphics/IGraphicsDevice.cs +++ b/OpenRA.Game/Graphics/IGraphicsDevice.cs @@ -57,6 +57,7 @@ namespace OpenRA void Clear(); void Present(); + Bitmap TakeScreenshot(); void PumpInput(IInputHandler inputHandler); string GetClipboardText(); void DrawPrimitives(PrimitiveType type, int firstVertex, int numVertices); diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs index eaa5f5d337..a67f409c52 100644 --- a/OpenRA.Game/Graphics/Renderer.cs +++ b/OpenRA.Game/Graphics/Renderer.cs @@ -160,6 +160,11 @@ namespace OpenRA.Graphics DrawBatch(tempBuffer, 0, numVertices, type); } + public Bitmap TakeScreenshot() + { + return Device.TakeScreenshot(); + } + public void DrawBatch(IVertexBuffer vertices, int firstVertex, int numVertices, PrimitiveType type) where T : struct diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index c2de62e201..5d9117db95 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; +using System.Drawing.Imaging; using System.IO; using System.Linq; using OpenRA.Graphics; @@ -94,6 +95,8 @@ namespace OpenRA public string Language = "english"; public string DefaultLanguage = "english"; + + public ImageFormat ScreenshotFormat = ImageFormat.Png; } public class SoundSettings @@ -178,6 +181,7 @@ namespace OpenRA public Hotkey TogglePixelDoubleKey = new Hotkey(Keycode.PERIOD, Modifiers.None); public Hotkey DevReloadChromeKey = new Hotkey(Keycode.C, Modifiers.Ctrl | Modifiers.Shift); + public Hotkey TakeScreenshotKey = new Hotkey(Keycode.P, Modifiers.Ctrl); public Hotkey Production01Key = new Hotkey(Keycode.F1, Modifiers.None); public Hotkey Production02Key = new Hotkey(Keycode.F2, Modifiers.None); diff --git a/OpenRA.Game/Widgets/RootWidget.cs b/OpenRA.Game/Widgets/RootWidget.cs index 95a94e7c6b..66bd63f2f8 100644 --- a/OpenRA.Game/Widgets/RootWidget.cs +++ b/OpenRA.Game/Widgets/RootWidget.cs @@ -30,6 +30,14 @@ namespace OpenRA.Widgets ChromeProvider.Initialize(Game.ModData.Manifest.Chrome); return true; } + + if (hk == Game.Settings.Keys.TakeScreenshotKey) + { + if (e.Event == KeyInputEvent.Down) + Game.TakeScreenshot = true; + + return true; + } } return base.HandleKeyPress(e); diff --git a/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs index 9c15f96441..d95086f2b8 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs @@ -464,7 +464,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic { var hotkeys = new Dictionary() { - { "DevReloadChromeKey", "Reload Chrome" } + { "DevReloadChromeKey", "Reload Chrome" }, + { "TakeScreenshotKey", "Take screenshot" } }; var header = ScrollItemWidget.Setup(hotkeyHeader, returnTrue, doNothing); diff --git a/OpenRA.Renderer.Null/NullGraphicsDevice.cs b/OpenRA.Renderer.Null/NullGraphicsDevice.cs index 699a3c3933..24d1a2f753 100644 --- a/OpenRA.Renderer.Null/NullGraphicsDevice.cs +++ b/OpenRA.Renderer.Null/NullGraphicsDevice.cs @@ -50,6 +50,7 @@ namespace OpenRA.Renderer.Null public void Clear() { } public void Present() { } + public Bitmap TakeScreenshot() { return new Bitmap(1, 1); } public string GetClipboardText() { return ""; } public void PumpInput(IInputHandler ih) diff --git a/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs b/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs index 0153b30c84..1ae8375289 100755 --- a/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs +++ b/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs @@ -330,6 +330,31 @@ namespace OpenRA.Renderer.Sdl2 ErrorHandler.CheckGlError(); } + public Bitmap TakeScreenshot() + { + var rect = new Rectangle(Point.Empty, size); + 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); + + GL.PushClientAttrib(ClientAttribMask.ClientPixelStoreBit); + + GL.PixelStore(PixelStoreParameter.PackRowLength, data.Stride / 4f); + GL.PixelStore(PixelStoreParameter.PackAlignment, 1); + + GL.ReadPixels(rect.X, rect.Y, rect.Width, rect.Height, PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); + GL.Finish(); + + GL.PopClientAttrib(); + + bitmap.UnlockBits(data); + + // OpenGL standard defines the origin in the bottom left corner which is why this is upside-down by default. + bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); + + return bitmap; + } + public void Present() { SDL.SDL_GL_SwapWindow(window); } public void PumpInput(IInputHandler inputHandler) { input.PumpInput(inputHandler); } public string GetClipboardText() { return input.GetClipboardText(); }