Last active
December 10, 2024 10:33
-
-
Save ogxd/48527a80039b778792b3f4ece8db013c to your computer and use it in GitHub Desktop.
Unity Editor tool to take high quality screenshots with transparent background, high resolution and auto cropping.
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
using System.IO; | |
using UnityEditor; | |
using UnityEngine; | |
[InitializeOnLoad] | |
public static class Screenshot | |
{ | |
static Screenshot() | |
{ | |
EditorApplication.delayCall += () => { | |
Resolution = Resolution; | |
IsTransparentBackground = IsTransparentBackground; | |
IsCropTransparent = IsCropTransparent; | |
}; | |
} | |
#region MenuItems | |
/// <summary> | |
/// Take a screenshot of the current view (Scene or Game) | |
/// Shortcut : Shift + P | |
/// </summary> | |
[MenuItem("📷/Take Screenshot #p", priority = 100)] | |
public static void TakeScreenshot() | |
{ | |
string filePath = EditorUtility.SaveFilePanel("Save Screenshot", "*", "screenshot.png", "png"); | |
if (string.IsNullOrEmpty(filePath)) | |
return; | |
int width = 1000; | |
int height = 1000; | |
switch (Resolution) { | |
case 1: | |
width = 1280; | |
height = 720; | |
break; | |
case 2: | |
width = 1920; | |
height = 1080; | |
break; | |
case 3: | |
width = 3840; | |
height = 2160; | |
break; | |
case 4: | |
width = 7680; | |
height = 4320; | |
break; | |
} | |
Texture2D texture; | |
if (IsTransparentBackground) { | |
texture = CaptureTransparentScreenshot(GetCamera(), width, height); | |
} else { | |
texture = CaptureScreenshot(GetCamera(), width, height); | |
} | |
if (IsCropTransparent) | |
CropTransparent(texture); | |
texture.SaveToDisk(filePath); | |
Object.DestroyImmediate(texture); | |
} | |
/// <summary> | |
/// If set to true, the background is replace with a transparent background (handles semi transparency) | |
/// </summary> | |
[MenuItem("📷/Transparent Background", priority = 300)] | |
private static void ToggleTransparentBackground() | |
{ | |
IsTransparentBackground = !IsTransparentBackground; | |
if (!IsTransparentBackground) | |
IsCropTransparent = false; | |
} | |
/// <summary> | |
/// IF set to true, crops the image to remove all unused space | |
/// </summary> | |
[MenuItem("📷/Crop Transparent", priority = 301)] | |
private static void ToggleCropTransparent() | |
{ | |
IsCropTransparent = !IsCropTransparent; | |
} | |
[MenuItem("📷/Crop Transparent", true)] | |
private static bool CanCrop() | |
{ | |
return IsTransparentBackground; | |
} | |
[MenuItem("📷/720p", priority = 400)] | |
private static void Set720p() => Resolution = 1; | |
[MenuItem("📷/1080p", priority = 401)] | |
private static void Set1080p() => Resolution = 2; | |
[MenuItem("📷/2160p", priority = 402)] | |
private static void Set4K() => Resolution = 3; | |
[MenuItem("📷/4320p", priority = 403)] | |
private static void Set8K() => Resolution = 4; | |
#endregion | |
private static bool IsCropTransparent { | |
get { return EditorPrefs.GetBool("SCREENSHOT_CROPTRANSPARENT", true); } | |
set { | |
EditorPrefs.SetBool("SCREENSHOT_CROPTRANSPARENT", value); | |
Menu.SetChecked("📷/Crop Transparent", value); | |
} | |
} | |
private static bool IsTransparentBackground { | |
get { return EditorPrefs.GetBool("SCREENSHOT_ISTRANSPARENTBACKGROUND", true); } | |
set { | |
EditorPrefs.SetBool("SCREENSHOT_ISTRANSPARENTBACKGROUND", value); | |
Menu.SetChecked("📷/Transparent Background", value); | |
} | |
} | |
private static int Resolution { | |
get { return EditorPrefs.GetInt("SCREENSHOT_RESOLUTION", 2); } | |
set { | |
EditorPrefs.SetInt("SCREENSHOT_RESOLUTION", value); | |
Menu.SetChecked("📷/720p", value == 1); | |
Menu.SetChecked("📷/1080p", value == 2); | |
Menu.SetChecked("📷/2160p", value == 3); | |
Menu.SetChecked("📷/4320p", value == 4); | |
} | |
} | |
private static Camera GetCamera() | |
{ | |
if (Camera.current) | |
return Camera.current; | |
return Object.FindObjectOfType<Camera>(); | |
} | |
public static Texture2D CaptureTransparentScreenshot(Camera camera, int width, int height) | |
{ | |
// This is slower, but seems more reliable. | |
RenderTexture cameraTargetTexture = camera.targetTexture; | |
CameraClearFlags cameraClearFlags = camera.clearFlags; | |
RenderTexture renderTextureActive = RenderTexture.active; | |
var shotWhiteBg = new Texture2D(width, height, TextureFormat.ARGB32, false); | |
var shotBlackBg = new Texture2D(width, height, TextureFormat.ARGB32, false); | |
var shotTrnspBg = new Texture2D(width, height, TextureFormat.ARGB32, false); | |
// Must use 24-bit depth buffer to be able to fill background. | |
var renderTexture = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32); | |
var grabArea = new Rect(0, 0, width, height); | |
RenderTexture.active = renderTexture; | |
camera.targetTexture = renderTexture; | |
camera.clearFlags = CameraClearFlags.SolidColor; | |
camera.backgroundColor = Color.black; | |
camera.Render(); | |
shotBlackBg.ReadPixels(grabArea, 0, 0); | |
shotBlackBg.Apply(); | |
camera.backgroundColor = Color.white; | |
camera.Render(); | |
shotWhiteBg.ReadPixels(grabArea, 0, 0); | |
shotWhiteBg.Apply(); | |
Color[] whitePixels = shotWhiteBg.GetPixels(); | |
Color[] blackPixels = shotBlackBg.GetPixels(); | |
// Create Alpha from the difference between black and white camera renders | |
for (int y = 0; y < height; y++) { | |
for (int x = 0; x < width; x++) { | |
float alpha = whitePixels[x + y * width].r - blackPixels[x + y * width].r; | |
alpha = 1.0f - alpha; | |
Color color; | |
if (alpha == 0) { | |
color = Color.clear; | |
} else { | |
color = blackPixels[x + y * width] / alpha; | |
} | |
color.a = alpha; | |
blackPixels[x + y * width] = color; | |
} | |
} | |
shotTrnspBg.SetPixels(blackPixels); | |
// Clear temporary textures | |
Object.DestroyImmediate(shotBlackBg); | |
Object.DestroyImmediate(shotWhiteBg); | |
// Revert properties back | |
camera.clearFlags = cameraClearFlags; | |
camera.targetTexture = cameraTargetTexture; | |
RenderTexture.active = renderTextureActive; | |
RenderTexture.ReleaseTemporary(renderTexture); | |
return shotTrnspBg; | |
} | |
public static Texture2D CaptureScreenshot(Camera camera, int width, int height) | |
{ | |
// This is slower, but seems more reliable. | |
RenderTexture cameraTargetTexture = camera.targetTexture; | |
CameraClearFlags cameraClearFlags = camera.clearFlags; | |
RenderTexture renderTextureActive = RenderTexture.active; | |
var shotWidthBg = new Texture2D(width, height, TextureFormat.ARGB32, false); | |
// Must use 24-bit depth buffer to be able to fill background. | |
var renderTexture = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32); | |
var grabArea = new Rect(0, 0, width, height); | |
RenderTexture.active = renderTexture; | |
camera.targetTexture = renderTexture; | |
camera.clearFlags = CameraClearFlags.SolidColor; | |
//camera.backgroundColor = Color.white; | |
camera.Render(); | |
shotWidthBg.ReadPixels(grabArea, 0, 0); | |
shotWidthBg.Apply(); | |
// Revert properties back | |
camera.clearFlags = cameraClearFlags; | |
camera.targetTexture = cameraTargetTexture; | |
RenderTexture.active = renderTextureActive; | |
RenderTexture.ReleaseTemporary(renderTexture); | |
return shotWidthBg; | |
} | |
const float CROP_ALPHA_THRESHOLD = 0.01f; | |
public static void CropTransparent(Texture2D texture) | |
{ | |
Color[] pixels = texture.GetPixels(); | |
int BottomToTop() | |
{ | |
for (int y = 0; y < texture.height; y++) | |
for (int x = 0; x < texture.width; x++) | |
if (pixels[x + y * texture.width].a > CROP_ALPHA_THRESHOLD) | |
return y; | |
return 0; | |
} | |
int TopToBottom() | |
{ | |
for (int y = texture.height - 1; y > 0; y--) | |
for (int x = 0; x < texture.width; x++) | |
if (pixels[x + y * texture.width].a > CROP_ALPHA_THRESHOLD) | |
return y; | |
return texture.height; | |
} | |
int LeftToRight() | |
{ | |
for (int x = 0; x < texture.width; x++) | |
for (int y = 0; y < texture.height; y++) | |
if (pixels[x + y * texture.width].a > CROP_ALPHA_THRESHOLD) | |
return x; | |
return 0; | |
} | |
int RightToLeft() | |
{ | |
for (int x = texture.width - 1; x >= 0; x--) | |
for (int y = 0; y < texture.height; y++) | |
if (pixels[x + y * texture.width].a > CROP_ALPHA_THRESHOLD) | |
return x; | |
return texture.width; | |
} | |
int cx = LeftToRight(); | |
int cy = BottomToTop(); | |
int cwidth = RightToLeft() - cx; | |
int cheight = TopToBottom() - cy; | |
Color[] croppedPixels = texture.GetPixels(cx, cy, cwidth, cheight); | |
Debug.Log($"Cropped from {texture.width}x{texture.height} to {cwidth}x{cheight}"); | |
texture.Resize(cwidth, cheight); | |
texture.SetPixels(croppedPixels); | |
} | |
public static void SaveToDisk(this Texture2D texture, string filePath) | |
{ | |
byte[] bytes = ImageConversion.EncodeToPNG(texture); | |
File.WriteAllBytes(filePath, bytes); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In 2021 I get empty image.
Fix: in GetCamera()
if (SceneView.lastActiveSceneView.camera) return SceneView.lastActiveSceneView.camera;