Capture and save screenshot with ScreenCapture.Cap

2019-01-20 05:42发布

问题:

I've been trying to take a screenshot and then immediately after, use it to show some sort of preview and some times it works and some times it doesn't, I'm currently not at work and I don't have unity in this computer so I'll try to recreate it on the fly (there might be some syntax mistakes here and there)

public GameObject screenshotPreview;

public void TakeScreenshot () {

        string imageName = "screenshot.png";

        // Take the screenshot
        ScreenCapture.CaptureScreenshot (imageName);

        // Read the data from the file
        byte[] data = File.ReadAllBytes(Application.persistentDataPath + "/" + imageName);

        // Create the texture
        Texture2D screenshotTexture = new Texture2D(Screen.width, Screen.height);

        // Load the image
        screenshotTexture.LoadImage(data);

        // Create a sprite
        Sprite screenshotSprite = Sprite.Create (screenshotTexture, new Rect(0, 0, Screen.width, Screen.height), new Vector2(0.5f, 0.5f) );

        // Set the sprite to the screenshotPreview
        screenshotPreview.GetComponent<Image> ().sprite = screenshotSprite;

}

As far as I've read, ScreenCapture.CaptureScreenshot is not async so the image should have been written right before I try to load the data, but the problem is as I've said before some times it doesn't work and it loads an 8x8 texture with a red question mark, which apparently is the texture failing to be loaded but the file should've been there so I cannot understand why it's not getting loaded properly.

another thing I've tried (which is disgusting but I'm getting tired of this and running out of ideas) is to put in the update method to wait for some time and then execute the code to load the data and create the texture, sprite and display it but even so, it fails some times, less frequently than before but it still fails, which leads me to belive that even if the file was created it hasn't finish beign written, does anyone know a workaround to this? any advice is appreciated.

As extra information this project is being run in an iOS device.

回答1:

The ScreenCapture.CaptureScreenshot function is known to have many problems. Here is another one of it.

Here is a quote from its doc:

On Android this function returns immediately. The resulting screenshot is available later.

The iOS behavior is not documented but we can just assume that the behavior is the-same on iOS. Wait for few frames after taking the screenshot before you attempt to read/load it.

public IEnumerator TakeScreenshot()
{

    string imageName = "screenshot.png";

    // Take the screenshot
    ScreenCapture.CaptureScreenshot(imageName);

    //Wait for 4 frames
    for (int i = 0; i < 5; i++)
    {
        yield return null;
    }

    // Read the data from the file
    byte[] data = File.ReadAllBytes(Application.persistentDataPath + "/" + imageName);

    // Create the texture
    Texture2D screenshotTexture = new Texture2D(Screen.width, Screen.height);

    // Load the image
    screenshotTexture.LoadImage(data);

    // Create a sprite
    Sprite screenshotSprite = Sprite.Create(screenshotTexture, new Rect(0, 0, Screen.width, Screen.height), new Vector2(0.5f, 0.5f));

    // Set the sprite to the screenshotPreview
    screenshotPreview.GetComponent<Image>().sprite = screenshotSprite;

}

Note that you must use StartCoroutine(TakeScreenshot()); to call this function.


If that did not work, don't use this function at-all. Here is another way to take and save screenshot in Unity:

IEnumerator captureScreenshot()
{
    yield return new WaitForEndOfFrame();

    string path = Application.persistentDataPath + "Screenshots/"
            + "_" + screenshotCount + "_" + Screen.width + "X" + Screen.height + "" + ".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);
    screenImage.Apply();
    //Convert to png
    byte[] imageBytes = screenImage.EncodeToPNG();

    //Save image to file
    System.IO.File.WriteAllBytes(path, imageBytes);
}


回答2:

I see nothing in the docs that says its not Async. In fact, for Android (if I'm reading this correctly), it explicitly says it's async.

That said, I'd try stalling while the file is not found. Throw it in a coroutine and

FileInfo yourFile = new FileInfo("YourFile.png");
while (File.Exists(yourFile.name) || IsFileLocked(yourFile)) 
    yield return null;

IsFileLocked

You could also try throwing in some debug checks in there to see how long it takes (seconds or frames) before the file appears (assuming it ever appears).

Edit: As derHugo pointed out, the file existing doesn't mean the file is ready yet. I have edited the code to handle that! But it still doesn't cover the case where the file already existed, in which case you probably want a dynamic file name like with a timestamp, or you want to delete the file first!



回答3:

Programmer's code worked successfully by being called like the following. It is designed as coroutine, so it would not interfere frame rate. Hence it should be called as coroutine. Make sure the CallerObject is inheriting from "MonoBehaviour".

 public class CallerObject : MonoBehaviour
{
    public void Caller()
    {
        String imagePath = Application.persistentDataPath + "/image.png";
        StartCoroutine(captureScreenshot(imagePath));
    }


    IEnumerator captureScreenshot(String imagePath)
    {
        yield return new WaitForEndOfFrame();
        //about to save an image capture
        Texture2D screenImage = new Texture2D(Screen.width, Screen.height);


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

        Debug.Log(" screenImage.width" + screenImage.width + " texelSize" + screenImage.texelSize);
        //Convert to png
        byte[] imageBytes = screenImage.EncodeToPNG();

        Debug.Log("imagesBytes=" + imageBytes.Length);

        //Save image to file
        System.IO.File.WriteAllBytes(imagePath, imageBytes);
    }
}