Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How to wait for CaptureScreen to complete

Discussion in 'iOS and tvOS' started by steddyman, Feb 28, 2013.

  1. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    I'm trying to take a screenshot of the iPhone screen either using Prime31 takeScreenShot or Application.CaptureScreenshot and then insert it into a tweet or Facebook post.

    The problem I have is the async nature of both of the above calls. I can't find a way of waiting to be sure the screenshot has finished writing before trying to attach it. Everything I try ends up with no screenshot until the second time you trigger it, at which point it shows the previous one i captured. Waiting for the file to exist isn't enough, since that can mean it has started to be written but not completed.

    This is what I have tried so far:

    Code (csharp):
    1.  
    2. function WaitForFile(file : String)
    3. {
    4.     var t : float = 5.0;
    5.     // Wait for file to become valid or 5 seconds max
    6.     while (!System.IO.File.Exists(file)  t > 0f) {
    7.         yield WaitForSeconds(0.1);
    8.         t -= Time.deltaTime;
    9.     }
    10. }
    11.  
    12. function ShowTwitter()
    13. {
    14.     Application.CaptureScreenshot("screen.png");
    15. //  EtceteraBinding.takeScreenShot("screen.png");
    16.        
    17.     #if UNITY_IPHONE
    18.         var path_to_image = Application.persistentDataPath + "/screen.png";
    19.     #elif UNITY_STANDALONE_OSX
    20.         var path_to_image = Application.dataPath + "/Data/screen.png";
    21.     #endif
    22.    
    23.     // Wait for capture to complete
    24.     WaitForFile(path_to_image);
    25.    
    26.     var stars : int = PlayerPrefs.GetInt("lastStars", 0);
    27.     var score : int = PlayerPrefs.GetInt("lastScore", 0);
    28.                
    29.     SocialNetworksAPI.Social_TW_Post_With_ScreenShot("I got a score of "+score+" and "+score+" stars on Rock Run, can you beat that?", path_to_image, "http://appstore.com/gigabytesoltionsltd/rockrun");
    30. }
    31.  
     
    Last edited: Feb 28, 2013
  2. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    I also tried the following version, same result:

    Code (csharp):
    1.  
    2. function WaitForFile(file : String)
    3. {
    4.     var t : float = 5.0;
    5.     // Wait for file to become valid or 5 seconds max
    6.     var fi : FileInfo = FileInfo(file);
    7.     while ((fi==null || fi.Exists==false)  t > 0f) {
    8.         yield WaitForSeconds(0.1);
    9.         t -= Time.deltaTime;
    10.         fi = FileInfo(file);
    11.     }
    12. }
    13.  
     
  3. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,398
    I don't know about the other, but Application.CaptureScreenshot isn't async; the operation is complete and the file is closed by the next line of code. Nothing in Unity is async unless explicitly specified in the docs. So I'd suspect some logic error. (Speaking of which, not that you need it, but the WaitForFile function does nothing at all since you're not yielding on it.)

    --Eric
     
  4. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    It is async, there are lots of posts about this on Unity Answers stating as such.

    I've managed to get it working by changing the call to WaitForFile to yield WaitForFile. I always get confused with yield in Javascript.

    Thanks
     
  5. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    Actually, now it isn't working again!!!!

    It's back to showing the previous screenshot. Also tried this using the Prime31 plugin and still showing previous screenshot almost every time... grrr

    Code (csharp):
    1.  
    2.     yield StartCoroutine(EtceteraBinding.takeScreenShot("screen.png"));
    3.  
    Why does this seem impossible!!
     
  6. sama-van

    sama-van

    Joined:
    Jun 2, 2009
    Posts:
    1,720
    In c# I'll probably launch a first System.IO.File.Delete(yourPreviousScreenShoot) to be sure the previous screenshot goes away.

    After an Application.persistentDataPath + "/screen.png";
    Loading screen "please wait" locking interface with a cancel button showed.

    During the update : if(System.IO.file.Exist(your screenshoot)
    And if finally find it to unlock he interface and to pop the facebook window.
     
    mrneo1991 likes this.
  7. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    Thanks

    Deleting it first has worked. Not sure why I didn't realise that was going to be a problem first :)
     
  8. Alexey

    Alexey

    Unity Technologies

    Joined:
    May 10, 2010
    Posts:
    1,600
    it is async, it just tells unity to capture screenshot at the end of the frame. So basically you can open file at the next frame
     
  9. pankao

    pankao

    Joined:
    Feb 18, 2013
    Posts:
    12
    Hello to the past from the future!:p Well in unityscript it seems just to put yield between Application.CaptureScreenshot statement and any further statement is enough:

    Code (csharp):
    1. Application.CaptureScreenshot(fileName);
    2. yield;
    3. // ...now manipulate the image
    I also thought thatI just need to wait for the end of frame to be able to access the captured image data but it turn out that tha capture itself happens at the very end of the frame its being called.. The simple yield with no params just postpones further statement to the next frame, thus ensuring the capture is complete.

    Yields are sometimes really a bit confusing to mee too, especially th edifferent usage in C# and unityscript.
     
    Last edited: Apr 12, 2013
    SamCarey likes this.
  10. mog-mog-mog

    mog-mog-mog

    Joined:
    Feb 12, 2014
    Posts:
    266
    Bump, this is super confusing. How long should I wait for File to be completely written on mobile platform.
    For Android, file is written in packets say 500KB each and it can take upto 5 seconds. So am sure file is not written in one instant. Even if I yield for File.Exists, this will not be correct as it may only contain partial data.
    Is there a prescribed way to detect end of Application.captureScreenshot?
     
  11. fermmmm

    fermmmm

    Joined:
    Oct 18, 2013
    Posts:
    129
    Bump, same here.
     
  12. povilas

    povilas

    Unity Technologies

    Joined:
    Jan 28, 2014
    Posts:
    427
    @lilboylost @fermmmm The file is not written immediately, but at the end of the frame. The next frame starts only after the screenshot has been written.
     
    dnis3d likes this.
  13. mog-mog-mog

    mog-mog-mog

    Joined:
    Feb 12, 2014
    Posts:
    266
    Based on my results, I believe this is incorrect. Are you certain about this?

    File does not exist when next frame starts for sure. Also, I've noticed that the file is written in chunks. Let's say my image size is 5MB on Android. I've noticed that after 1 second, file exists of size 2.4 mb, then size increases to 5 mb after 3 seconds or so.
     
  14. xcmeathead

    xcmeathead

    Joined:
    Jun 1, 2015
    Posts:
    28
    I've been struggling with this a bit, but I think I've got it worked out. Here's what I found...

    As I see it, there are two parts to Application.CaptureScreenshot.
    1. Capture the screenshot (i.e. hold it in RAM). This seems to be consistently happening before the end of the frame (when called in Update), as some have described. I think this is the behaviour you'd probably hope for, as if it takes any longer than that, then it would capture the wrong frame.
    2. Save a PNG using the information captured. This is the async part, and can take a lot longer depending on the device. In the Unity Editor, this seems to happen almost instantly, but on a cheap Android tablet I tested on, it takes several seconds, as per other's experiences (I currently don't have any better Android hardware to test on, but I can imagine once you get up to the crazy high resolutions of phones like the GS6, you'll be waiting quite a while for the screenshot to save). This was the cause of my frustration, since what worked perfectly in the editor, didn't work at all in my builds!
    Working around it:
    One thing I did in an attempt to deal with the image not being ready by the time I come to use it, was the following line of code, which gives me a success boolean, imageLoadSuccess, so I can retry if needed. Not ideal, but the best method for dealing with it that I've come up with.

    Code (CSharp):
    1. Texture2D image;
    2.  
    3.     image = new Texture2D(Screen.width, Screen.height);
    4.     bool imageLoadSuccess = image.LoadImage(System.IO.File.ReadAllBytes(path));
    Hope this comes in useful for someone :)
     
    Last edited: Aug 19, 2015
  15. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,398
    You wouldn't use FixedUpdate, since that only applies to physics and is unrelated to the camera update.

    --Eric
     
  16. xcmeathead

    xcmeathead

    Joined:
    Jun 1, 2015
    Posts:
    28
    Oops, yup you're right! I meant Update() since that's per frame. I'll edit my reply to avoid confusion :)

    I've ended up making a screenshot class that may alleviate problems for some. I wanted to be able to show the screenshot in the scene straight away, but wasn't worried about accessing the file until a bit later, so this allows me to do these things separately. I can't promise it's the best way to do things, but it's working for me. Again, I hope this helps someone:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.IO;
    4.  
    5. public class Screenshot : MonoBehaviour {
    6.  
    7.     private static Screenshot sInstance = new Screenshot();
    8.  
    9.     public static Screenshot Instance {
    10.         get {
    11.             return sInstance;
    12.         }
    13.     }
    14.  
    15.     Texture2D tex;
    16.  
    17.     public string getPath() {
    18.         // where we want to save the image
    19.         return Application.persistentDataPath + "/screentoshare.png";
    20.     }
    21.  
    22.     public void takeNow() {
    23.         //takes the screenshot, but doesn't save a file. It's stored as a Texture2D instead
    24.         tex = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
    25.         tex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
    26.         tex.Apply();
    27.     }
    28.  
    29.     public Texture2D getTexture2D() {
    30.         //call to access the texture2D from outside this class
    31.         return tex;
    32.     }
    33.  
    34.     public void saveAsFile () {
    35.         //saves a PNG file to the path specified above
    36.         byte[] bytes = tex.EncodeToPNG();
    37.         File.WriteAllBytes(getPath (), bytes);
    38.     }
    39.  
    40.     public bool hasSaved() {
    41.         //it's not enough to just check that the file exists, since it doesn't mean it's finished saving
    42.         //we have to check if it can actually be opened
    43.         Texture2D image;
    44.         image = new Texture2D(Screen.width, Screen.height);
    45.         bool imageLoadSuccess = image.LoadImage(System.IO.File.ReadAllBytes(getPath()));
    46.         Destroy (image);
    47.         return imageLoadSuccess;
    48.     }
    49.  
    50. }
    No need to instantiate or anything, so to take the screenshot, just use the one line of code:
    Code (CSharp):
    1. Screenshot.Instance.takeNow();
    To show it in your UI, access the Texture2D, using:
    Code (CSharp):
    1. Texture2D myScreenshotTexture = Screenshot.Instance.getTexture2D();
    etc...
     
    davewall8 likes this.
  17. mrneo1991

    mrneo1991

    Joined:
    Nov 4, 2015
    Posts:
    1
    You saved my days, bro
     
    sama-van likes this.
  18. mog-mog-mog

    mog-mog-mog

    Joined:
    Feb 12, 2014
    Posts:
    266
    File exist does not ensure that the file is written completely. File is normally written in chunks, so you have to wait till file is fully written as exists will return true even if file is partially written returning corrupt texture.
     
  19. curiousbrandon

    curiousbrandon

    Joined:
    Sep 22, 2017
    Posts:
    6
    I had a problem with checking for file.exist because as mentioned above, the file might not be written completely. I ended up using this and it seems to work:

    Code (CSharp):
    1. IEnumerator delayedShare(string path, string text)
    2.     {
    3.  
    4.         while(IsFileUnavailable(path)) {
    5.             Debug.Log("file locked");
    6.             yield return new WaitForSeconds(.05f);
    7.         }
    8.  
    9.         // run your code that needs the file to be loaded here
    10.  
    11.  
    12.     }
    Code (CSharp):
    1. protected virtual bool IsFileUnavailable(string path)
    2.     {
    3.         // if file doesn't exist, return true
    4.         if (!File.Exists(path))
    5.             return true;
    6.  
    7.         FileInfo file = new FileInfo(path);
    8.         FileStream stream = null;
    9.  
    10.         try
    11.         {
    12.             stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
    13.         }
    14.         catch (IOException)
    15.         {
    16.             //the file is unavailable because it is:
    17.             //still being written to
    18.             //or being processed by another thread
    19.             //or does not exist (has already been processed)
    20.             return true;
    21.         }
    22.         finally
    23.         {
    24.             if (stream != null)
    25.                 stream.Close();
    26.         }
    27.  
    28.         //file is not locked
    29.         return false;
    30.     }
    Solution was found via https://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in-use/937558#937558
     
    Renato_Rewind likes this.