Created
December 12, 2019 01:33
-
-
Save kraj0t/d6a402c50ae2f8a9555c6e3c79796cac to your computer and use it in GitHub Desktop.
Unity Tool - copy Game window to system clipboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* @brief Editor menu item and shortcut for capturing the game view into the system clipboard. | |
* | |
* @setup The System.Drawing and System.Windows.Forms Mono DLLs need to be referenced. See the documentation for | |
* CopyTextureToClipboard below. | |
* | |
* @usage Access from the menu item or use the shortcut. Both are defined below as attribute of GameViewToClipboard(), | |
* defaults to Shift+S | |
* | |
* @author Aurelio Provedo | |
* Contact: [email protected] | |
* | |
*/ | |
using System.Collections; | |
using System.Drawing; | |
using System.IO; | |
using System.Windows.Forms; | |
using JetBrains.Annotations; | |
using UnityEngine; | |
using UnityEditor; | |
namespace kraj0tEditor | |
{ | |
public static class CopyGameScreenshotToClipboard | |
{ | |
private delegate void ScreenshotDelegate(GrabScreenTexture dummy); | |
/// <summary> | |
/// The code below requires that the Mono DLLs for System.Drawing and System.Windows.Forms are accessible to | |
/// your script assembly. I recommend adding this script to an editor-only .asmdef that references the DLLs | |
/// explicitly (Override References). That way, you can also disable the DLLs' Auto Reference option, and the | |
/// rest of your code won't be affected by the DLLs. | |
/// | |
/// More info where I copied this code from: | |
/// https://answers.unity.com/questions/1263891/how-to-copy-an-image-to-the-system-clipboard.html | |
/// </summary> | |
/// <param name="tex"></param> | |
public static void CopyTextureToClipboard(Texture2D tex) | |
{ | |
var s = new MemoryStream(tex.width * tex.height); | |
var bytes = tex.EncodeToPNG(); | |
s.Write(bytes, 0, bytes.Length); | |
var image = Image.FromStream(s); | |
Clipboard.SetImage(image); | |
s.Close(); | |
s.Dispose(); | |
} | |
private class GrabScreenTexture : MonoBehaviour | |
{ | |
private Texture2D _screenTexture; | |
/// <summary> | |
/// The texture is destroyed everytime it is updated with GrabScreen(). | |
/// The texture is also destroyed when the component is destroyed. | |
/// Make sure to make a copy of it with Graphics.CopyTexture() if you need to. | |
/// </summary> | |
public Texture2D ScreenTexture | |
{ | |
get => _screenTexture; | |
private set | |
{ | |
if (_screenTexture) | |
{ | |
DestroyImmediate(_screenTexture); | |
} | |
_screenTexture = value; | |
} | |
} | |
/// <summary> | |
/// Grabs the screen into the ScreenTexture. | |
/// Calling destroys any texture that was currently in ScreenTexture. | |
/// </summary> | |
/// <param name="callback">Optional</param> | |
public void GrabScreenAsync([CanBeNull] ScreenshotDelegate callback) | |
{ | |
StartCoroutine(GrabScreenCoroutine(callback)); | |
} | |
private IEnumerator GrabScreenCoroutine(ScreenshotDelegate callback) | |
{ | |
// IMPORTANT: ReadPixels is TERRIBLY SLOW for runtime. I mean TERRIBLY SLOW. | |
yield return new WaitForEndOfFrame(); | |
var tex = new Texture2D(UnityEngine.Screen.width, UnityEngine.Screen.height, TextureFormat.RGB24, false); | |
tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0); | |
tex.Apply(false, false); | |
ScreenTexture = tex; | |
callback?.Invoke(this); | |
} | |
private void OnDestroy() | |
{ | |
if (ScreenTexture) | |
{ | |
DestroyImmediate(ScreenTexture); | |
} | |
StopAllCoroutines(); | |
} | |
} | |
/// <summary> | |
/// Default shortcut in editor is Shift+S. This will be hardcoded in Unity 2018 and older. | |
/// </summary> | |
#if UNITY_2019_1_OR_NEWER | |
[UnityEditor.ShortcutManagement.Shortcut("kraj0t/Technical Art/Copy Game Screenshot to Clipboard", KeyCode.S, UnityEditor.ShortcutManagement.ShortcutModifiers.Shift)] | |
#else | |
[UnityEditor.MenuItem("kraj0t/Technical Art/Copy Game Screenshot to Clipboard #_s", false, -100)] | |
#endif | |
private static void GameViewToClipboard() | |
{ | |
// NOTE: after some googling, this seems to be the fastest and cleanest approach that I could think of. | |
// Granted, the code does not look pretty, but I found many obstacles until I got to this, namely: | |
// - Texture2D.ReadPixels() does not work out of play mode | |
// - Screen.width returns random sizes out of the editor application windows, not the size of the game view. | |
// - I did not want to abuse Reflection for this. | |
// - When capturing to a file, it looks like it works fine. But I do not want to have to write to disk! | |
// - There is some code online for Editor Coroutines. That's cool. But no, thanks. | |
var tempGO = new GameObject(); | |
tempGO.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector | | |
HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor; | |
var dummy = tempGO.AddComponent<GrabScreenTexture>(); | |
var gameWindow = | |
EditorWindow.GetWindow(typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GameView")); | |
gameWindow.Focus(); | |
dummy.GrabScreenAsync(EditModeScreenshotCallback); | |
// Force repaint. Out of play mode, Unity will not paint constantly. And for grabbing the screen, we need to | |
// first paint it. | |
gameWindow.Repaint(); | |
} | |
private static void EditModeScreenshotCallback(GrabScreenTexture dummy) | |
{ | |
// We need to make sure to always destroy the temporary gameObject. | |
try | |
{ | |
var tex = dummy.ScreenTexture; | |
if (!tex) | |
{ | |
Debug.LogError("Unknown error! The screenshot was null."); | |
} | |
else | |
{ | |
CopyTextureToClipboard(tex); | |
Debug.Log("Copied the Game window to the system clipboard"); | |
} | |
} | |
finally | |
{ | |
GameObject.DestroyImmediate(dummy.gameObject); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment