Take screenshot in Unity3D with no lags

11,226

Solution 1

The cause of lag while taking picture is when you try to perform all steps at the same time without waiting after each step. To fix this, you perform one action and wait for many frames then perform the next action then wait again. And if you decide to save the picture during game-play, use Thread to save the picture. EncodeToPNG() is another problem but I will talk about another solution for it later on.

The code below is good if you want to take screen shot then display it right away while game is running. It will take picture right way then take about 1.5 seconds to save the file.

 IEnumerator screenShotCoroutine()
{
    yield return new WaitForEndOfFrame();
    string path = Application.persistentDataPath + "/DrukaloScreenshot.png";

    Texture2D screenImage = new Texture2D(Screen.width, Screen.height);

    //Get Image from screen
    screenImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);

    //Wait for a long time
    for (int i = 0; i < 15; i++)
    {
        yield return null;
    }

    screenImage.Apply();

    //Wait for a long time
    for (int i = 0; i < 15; i++)
    {
        yield return null;
    }

    //Convert to png(Expensive)
    byte[] imageBytes = screenImage.EncodeToPNG();

    //Wait for a long time
    for (int i = 0; i < 15; i++)
    {
        yield return null;
    }

    //Create new thread then save image to file
    new System.Threading.Thread(() =>
    {
        System.Threading.Thread.Sleep(100);
        File.WriteAllBytes(path, imageBytes);
    }).Start();
}

If you want to take the picture and you are not required to display the image right away, you can save the picture as raw Texture2D, then when you want to access/load/display the picture later on, you can read it from file then convert it to PNG later on. The reason for this is that EncodeToPNG() is very expensive so if you don't need the picture right away, there is no need to convert it to png. You can do that only when you actually need it as png. So now we can just save it as ".t2D" then load it after and convert the loaded image to ".png".

Save as Texture2D ".t2D"

IEnumerator screenShotCoroutine()
    {
        yield return new WaitForEndOfFrame();
        string path = Application.persistentDataPath + "/DrukaloScreenshot.t2D";

        Texture2D screenImage = new Texture2D(Screen.width, Screen.height);

        //Get Picture
        screenImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);

        //Wait for a long time
        for (int i = 0; i < 15; i++)
        {
            yield return null;
        }

        screenImage.Apply();

        //Wait for a long time
        for (int i = 0; i < 15; i++)
        {
            yield return null;
        }

        byte[] rawData = screenImage.GetRawTextureData();

        //Wait for a long time
        for (int i = 0; i < 15; i++)
        {
            yield return null;
        }

        //Create new thread then save image
        new System.Threading.Thread(() =>
        {
            System.Threading.Thread.Sleep(100);
            File.WriteAllBytes(path, rawData);
        }).Start();
    }

Then when you actually need the image, load Texture2D ".t2D" and then covert it to ".png" followed by deleting the old ".t2D".

IEnumerator screenShotCoroutine()
{
    string path = Application.persistentDataPath + "/DrukaloScreenshot.t2D";
    string newPath = Application.persistentDataPath + "/DrukaloScreenshot.png";

    Texture2D screenImage = new Texture2D(Screen.width, Screen.height);

    byte[] rawData = null;

    //Create new thread then Load image
    new System.Threading.Thread(() =>
    {
        System.Threading.Thread.Sleep(100);
        rawData = File.ReadAllBytes(path);
    }).Start();

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    //Put loaded bytes to Texture2D 
    screenImage.LoadRawTextureData(rawData);

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    screenImage.Apply();

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    //convert to png
    byte[] pngByte = screenImage.EncodeToPNG();

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    //Do whatever you want with the png file(display to screen or save as png)


    //Create new thread and Save the new png file, then Delete Old image(DrukaloScreenshot.t2D)
    new System.Threading.Thread(() =>
    {
        System.Threading.Thread.Sleep(100);
        File.WriteAllBytes(newPath, pngByte); //Save the new png file(DrukaloScreenshot.png)
        File.Delete(path); //Delete old file (DrukaloScreenshot.t2D)
    }).Start();
}

Solution 2

Application.CaptureScreenshot() is slow in general, a faster way is to use a Texture2D and read all the pixels on the screen on it using ReadPixels() function than you can encode and save the texture to the hard drive.

This is an example of this method:

Texture2D screenShot = new Texture2D(Screen.width, Screen.height);
screenShot.ReadPixels(new Rect(0,0,Screen.width,Screen.height),0,0);
screenShot.Apply();
byte[] bytes = tex.EncodeToPNG();
Object.Destroy(tex);
File.WriteAllBytes(Application.dataPath + "/../myScreenShot.png", bytes);

For a full reference you can use Unity scripting reference page here.

Share:
11,226
Drukalo
Author by

Drukalo

Updated on June 04, 2022

Comments

  • Drukalo
    Drukalo almost 2 years

    I have tried each of variants: http://wiki.unity3d.com/index.php/ScreenCapture Afterall, simple Application.Capturescreenshot(...) is the least laggy.

    Please, tell me, How I can take screenshots without ANY lags? Lots of games on Android has thiis feature, so it's not impossible.