Take same size Screenshots regardless of resolutions | Unity

When we create a game, we often support the multiple-device. Even if the screen resolutions are different, canvas config can realize it easily. It changes the UI ratio to fit each resolution.
To take a screenshot on a part of a screen, we need input a resolution but we cannot know the object size on each device. Do we need calculate each device ratio for taking a screenshot on a game result and a user profile?
No. We can do it just with using another canvas or changing CanvasScaler by a script.

Environment: Unity 2018.4.6f

 

How to take a screenshot in Unity

We can screenshot a game scene with Unity’s API.  The API is several types in Unity, each of which has slightly different features.*1 I often use Texture2D. It’s easy, yet flexible.

using System.Collections;
using UnityEngine;
using System.IO;
using System;
public class ScreenshotGenerater : MonoBehaviour {
    [SerializeField] Vector2Int imageSize = new Vector2Int (500, 250);
    //Set to ButtonEvent.
	public void LoadTexsture() {
        StartCoroutine("TakeScreenshot");
    }

    IEnumerator TakeScreenshot() {
        //Wait until rendered.
        yield return new WaitForEndOfFrame();

        //Create a new texture with the width and height of the screen
        Texture2D texture = new Texture2D(imageSize.x, imageSize.y, TextureFormat.RGB24, false);

        //case: we take a pic on the center of screen
        //If you take a ScreenShot of the resolution size, you can use the following code.
        //texture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
        float imagePosX = (Screen.width - imageSize.x) / 2;
        float imagePosY = (Screen.height - imageSize.y) / 2;

        texture.ReadPixels(new Rect(imagePosX, imagePosY, imageSize.x, imageSize.y), 0, 0);
        texture.Apply();
        //Can select file extention
        byte[] jpgFile = texture.EncodeToJPG();
        //Release explicitly.     
        DestroyImmediate(texture);
        
        //Use the date in the file name,
        //as it will be overwritten if the same file is present.
        string savePath = "/screenshot/" + DateTime.Now.ToString("MMdd_hhmmss") + ".jpg";

        File.WriteAllBytes(Application.dataPath + savePath, jpgFile);
    }
}

Screen.width and Screen.height are the actual width of the player window.*2 This means that if we support multiple resolutions, the value of screen size is not always fixed number.

So, when we take a ScreenShot on other resolution, the images change. 

  

If we take a screenshot of the entire screen, there is no problem. But we cannot use it to share at a specific size in a social media.

 

Take a screenshot regardless of Resolution by using other Canvas

This cause is Scale With Screen Size on Canvas Scaler to support multiple resolutions. We just use the other Canvas instead of it to take a screenshot regardless of resolution!

The Canvas that set Constant Pixel Size has the fixed size value.

We duplicate the contents making a image to the other Canvas, and all we need is activate it before taking a screenshot.

We need also fix the code to switch the Canvas active.

using System.Collections;
using UnityEngine;
using System.IO;
using System;
public class ScreenshotGenerater : MonoBehaviour {
    [SerializeField] Vector2Int imageSize = new Vector2Int (500, 250);
    [SerializeField] GameObject screenshotCanvas;

    //Set to ButtonEvent.
	public void LoadTexsture() {
        //Change Canvas active.
        screenshotCanvas.SetActive(true);
        StartCoroutine("TakeScreenshot");
    }

    IEnumerator TakeScreenshot() {
        //Wait until rendered.
        yield return new WaitForEndOfFrame();

        //Create a new texture with the width and height of the screen.
        Texture2D texture = new Texture2D(imageSize.x, imageSize.y, TextureFormat.RGB24, false);

        //case: we take a pic on the center of screen
        //If you take a ScreenShot of the resolution size, you can use the following code.
        //texture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
        float imagePosX = (Screen.width - imageSize.x) / 2;
        float imagePosY = (Screen.height - imageSize.y) / 2;

        texture.ReadPixels(new Rect(imagePosX, imagePosY, imageSize.x, imageSize.y), 0, 0);
        texture.Apply();
        //Can select file extention.
        byte[] jpgFile = texture.EncodeToJPG();
        //Release explicitly.     
        DestroyImmediate(texture);
        //Change Canvas active.
        screenshotCanvas.SetActive(false);
        
        //Use the date in the file name,
        //as it will be overwritten if the same file is present.
        string savePath = "/" + DateTime.Now.ToString("MMdd_hhmmss") + ".jpg";

        File.WriteAllBytes(Application.dataPath + savePath, jpgFile);
    }
}

The results are as follows. The content is now the same and the ideal screenshot is created!

  

 

Switch the Canvas config before screenshot

We can always get the same size screenshot by using another Canvas, but this method is not practical. It’s good as long as the data is small, but it’s not for taking a complex data. This is because all the data must be reflected in another Canvas too.

So, Let’s switch the configuration of the Canvas where the user profile or game results are placed from the script, and take a screenshot. Note that the change is seen in game scene: changing the CanvasScaler needs one frame, and drawing it one more frame. The result is we can see a UI for screenshot ratios.

using System.Collections;
using UnityEngine.UI; //For CanvasScale
using UnityEngine;
using System.IO;
using System;
public class ScreenshotGenerater2 : MonoBehaviour {
    [SerializeField] Vector2Int imageSize = new Vector2Int (500, 250);
    //Get reference on UnityEditor.
    [SerializeField] CanvasScaler screenshotCanvas;

    //Set to ButtonEvent.
	public void LoadTexsture() {
        //Switch ScaleMode
        screenshotCanvas.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize;
        StartCoroutine("TakeScreenshot");
    }

    IEnumerator TakeScreenshot() {
        //Wait until rendered.
        yield return null; //Switch CanvasScaler
        yield return new WaitForEndOfFrame(); //Draw it

        //Create a new texture with the width and height of the screen.
        Texture2D texture = new Texture2D(imageSize.x, imageSize.y, TextureFormat.RGB24, false);

        //case: we take a pic on the center of screen
        //If you take a ScreenShot of the resolution size, you can use the following code.
        //texture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
        float imagePosX = (Screen.width - imageSize.x) / 2;
        float imagePosY = (Screen.height - imageSize.y) / 2;

        texture.ReadPixels(new Rect(imagePosX, imagePosY, imageSize.x, imageSize.y), 0, 0);
        texture.Apply();
        //Can select file extention.
        byte[] jpgFile = texture.EncodeToJPG();
        //Release explicitly.     
        DestroyImmediate(texture);
        //Switch ScaleMode
        screenshotCanvas.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        screenshotCanvas.matchWidthOrHeight = 0f;

        //Use the date in the file name,
        //as it will be overwritten if the same file is present.
        string savePath = "/" + DateTime.Now.ToString("MMdd_hhmmss") + ".jpg";

        File.WriteAllBytes(Application.dataPath + savePath, jpgFile);
    }
}

 

Summary

Screenshot is necessary to share play data on social media. It’s easy to take a full screen shot, but it takes a bit of work to process the image for ideal. You should try this method!

References 

*APIs for screenshot
Unity – Scripting API: ScreenCapture.CaptureScreenshot
Unity – Manual: Render Texture
Unity – Scripting API: Texture2D.ReadPixels 

Unity – Scripting API: Screen.width
RenderTexture coordinate system origin is bottom-left on iPad, but top-left on iPhone 5S – Unity Forum
Unity – Scripting API: UI.CanvasScaler.uiScaleMode