Skip to content

Instantly share code, notes, and snippets.

@kraj0t
Created December 12, 2019 01:33
Show Gist options
  • Save kraj0t/d6a402c50ae2f8a9555c6e3c79796cac to your computer and use it in GitHub Desktop.
Save kraj0t/d6a402c50ae2f8a9555c6e3c79796cac to your computer and use it in GitHub Desktop.
Unity Tool - copy Game window to system clipboard
/*
* @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