display.save - is it asynchronous?

I’ve been having a lot of trouble trying to use display.save and display.capture.  I’m not sure I fully understand how they work. Are they asynchronous? Nothing in the documentation (that I can find) says they are asynchronous, but in my experiments, it seems like they are.

I’ve rewritten my code a bunch, but the basic idea was this:

  1. Get an image from the camera/photo library

  2. Pop it onscreen, but resize it to be really small (like a thumbnail)

  3. display.save() it

  4. go to the next scene

  5. display.newImage(my previously saved file from step 3)

And step 5 fails a lot on an iPad 3.  It worked fine on iPhone 6 and an Android 4.1 device.  I assumed it must be taking longer to save in step 3, so I added a timer.performWithDelay before moving to the next scene, and that worked.

So, if the display.save() is asynchronous, it would really help to have a listener to know when the save is complete.  My current workaround is to use the version of media.selectPhoto (and its camera equivalent) that supports saving.  This *seems* to not fire the completed listener until it’s saved, but maybe I just got lucky.

Can someone from Corona let me know how that works exactly or update the docs to explain if it’s an asynchronous operation? I’d rather use display.save in this case because of some specific UI reasons that are too complicated to explain. Basically, it doesn’t “feel” right because there’s a long delay when you pick something from the photo library before the screen updates. I’m guessing that’s because it’s taking awhile to save the file, but if it was a separate thing, i could show some kind of “Processing…” message. I can’t do that with it all as one call because it would be showing “Processing” in the background on an iPad while the photo library is on-screen, before they’ve picked anything. It would be weird.

Hope this makes sense.

Thanks,

Dave

@Dave,

First. Great post.  I love it when people post a question and bother to tell you what they are trying to do, as well as spend some time formatting.  

Second.  I’m not an Corona staff member, but I can tell you about my experiences with display.save():

I have always had to put a small pause after the save, before trying to use the image.  I usually wait 100ms to give it plenty of time to actually save the image.  You can probably trim that down quite a bit (maybe wait just one or two frames instead), however I use that number so I know I’m always safe.

Oh, and don’t forget, to save a display object, it must be on-screen or you’ll get nothing.  If you try to save and then immediately, transition to another screen, this may signal to the SDK (or underlying code) that it is OK to clear the buffer the image is in which may also be sabotaging your save.  This is just a guess however.

-Ed

You see, the fact that you’ve also noticed you have to put a short delay in there makes me think it must be asynchronous.  I’d just like to know for sure from a Corona person.

Have you tried that on a retina iPad?  The short delay worked fine for me too – it’s one of the things I tried – but on the iPad 3 (or “new iPad”) it fails, so I kept increasing the delay, finally just putting up to about 3 seconds, and bam – it started working.  It’s just too long for the app to appear unresponsive, with me having no way to show that anything is happening, and only occurs on a retina iPad (so far).  All other devices we’ve tried work fine without the long delay.

I’m building a business app that I think is really pushing the limits of what Corona can do, but so far I’ve been able to find workarounds for every problem thanks to either a forum member or Corona staff.

Thanks,

Dave

i don’t know if the actual saving is async, but the capturing does appear to be “deferred” – it’s like it sets a flag internally saying “next render will be to texture instead of screen”.  (of course, i don’t know the exact frame timing, but you can sort of test this for yourself by by putting a display.save inside an enterFrame loop and trying to save EVERY frame, and you’ll stop getting screen updates)  so you need at least one extra frame worth of rendering the scene as-is before you transition away - i think that’s the source of the timer delay workaround.  you could also set up a repeating timer or enterframe logic to “poll” for the existence of the output file, in essence creating your own callback mechanism once file found.

I had a brief email exchange with Corona staff, and this is what was said:

Saving files synchronously. Are you seeing that they are not synchronous? To my knowledge, they are synchronous. And just for kicks, I took a quick look on the iOS implementation where we call [NSData writeToFile:atomically:] with the YES param, so the writes should be synchronous.

http://stackoverflow.com/questions/10049245/writetofile-how-to-tell-when-it-is-completed

I just wanted to add this to this discussion for everyone’s benefit. I’m going to do some more experimentation today.

Okay, here’s my current solution to this.  It’s not perfect yet:

    local function savePhoto(photo, filename, desiredWidth)         local deviceWidth = ( display.contentWidth - (display.screenOriginX \* 2) ) / display.contentScaleX         local scaleFactor = deviceWidth / display.actualContentWidth         local ratio = (photo.contentWidth / desiredWidth)                  photo.width = math.floor(desiredWidth / scaleFactor)         photo.height = math.floor((photo.contentHeight / ratio) / scaleFactor)         photo.x = display.contentWidth \* .5         photo.y = display.contentHeight \* .5         photo.isVisible = true         display.getCurrentStage():insert(photo) -- make sure it's on top of everything         display.save(photo, {             filename = filename,             baseDir = system.TemporaryDirectory,             isFullResolution = false, -- we don't need full resolution             backgroundColor = {0,0,0,1},             jpegQuality = .6         })                  photo.isVisible = false     end               ----------------------------------------------------------------------------     -- Event that occurs after user has selected a photo (from library or camera)     ----------------------------------------------------------------------------     local function onPhotoComplete( event )         if (event.completed) then             local photo = event.target             photo.isVisible = false                          savePhoto(photo, "temp.jpg", event.target.contentWidth)             savePhoto(photo, "upload.jpg", 256)             display.remove(photo) -- remove photo                          local event = {                 name = "photoPicker",                 phase = "photoComplete"             }                          pg.parent:dispatchEvent( event )         end     end

My previous post (and proposed solution) turned out to not work.  After some testing, I found that it was cropping my photos due to them not being fully on screen.

I have had to change my code back to the method of saving it as you get it from the photo library or camera, like so:

media.selectPhoto({

                    mediaSource = media.PhotoLibrary, 

                    listener = onPhotoComplete, 

                    origin = libraryButton.contentBounds,

                    permittedArrowDirections = {“right”},

                    destination = {baseDir=system.TemporaryDirectory, filename=“temp.jpg”, type=“image”} 

                })

It still seems slow, though, on an iPad 3. I’m not sure what’s up with it, but at this point, I think I’m just going to live with that.

@Dave,

First. Great post.  I love it when people post a question and bother to tell you what they are trying to do, as well as spend some time formatting.  

Second.  I’m not an Corona staff member, but I can tell you about my experiences with display.save():

I have always had to put a small pause after the save, before trying to use the image.  I usually wait 100ms to give it plenty of time to actually save the image.  You can probably trim that down quite a bit (maybe wait just one or two frames instead), however I use that number so I know I’m always safe.

Oh, and don’t forget, to save a display object, it must be on-screen or you’ll get nothing.  If you try to save and then immediately, transition to another screen, this may signal to the SDK (or underlying code) that it is OK to clear the buffer the image is in which may also be sabotaging your save.  This is just a guess however.

-Ed

You see, the fact that you’ve also noticed you have to put a short delay in there makes me think it must be asynchronous.  I’d just like to know for sure from a Corona person.

Have you tried that on a retina iPad?  The short delay worked fine for me too – it’s one of the things I tried – but on the iPad 3 (or “new iPad”) it fails, so I kept increasing the delay, finally just putting up to about 3 seconds, and bam – it started working.  It’s just too long for the app to appear unresponsive, with me having no way to show that anything is happening, and only occurs on a retina iPad (so far).  All other devices we’ve tried work fine without the long delay.

I’m building a business app that I think is really pushing the limits of what Corona can do, but so far I’ve been able to find workarounds for every problem thanks to either a forum member or Corona staff.

Thanks,

Dave

i don’t know if the actual saving is async, but the capturing does appear to be “deferred” – it’s like it sets a flag internally saying “next render will be to texture instead of screen”.  (of course, i don’t know the exact frame timing, but you can sort of test this for yourself by by putting a display.save inside an enterFrame loop and trying to save EVERY frame, and you’ll stop getting screen updates)  so you need at least one extra frame worth of rendering the scene as-is before you transition away - i think that’s the source of the timer delay workaround.  you could also set up a repeating timer or enterframe logic to “poll” for the existence of the output file, in essence creating your own callback mechanism once file found.

I had a brief email exchange with Corona staff, and this is what was said:

Saving files synchronously. Are you seeing that they are not synchronous? To my knowledge, they are synchronous. And just for kicks, I took a quick look on the iOS implementation where we call [NSData writeToFile:atomically:] with the YES param, so the writes should be synchronous.

http://stackoverflow.com/questions/10049245/writetofile-how-to-tell-when-it-is-completed

I just wanted to add this to this discussion for everyone’s benefit. I’m going to do some more experimentation today.

Okay, here’s my current solution to this.  It’s not perfect yet:

    local function savePhoto(photo, filename, desiredWidth)         local deviceWidth = ( display.contentWidth - (display.screenOriginX \* 2) ) / display.contentScaleX         local scaleFactor = deviceWidth / display.actualContentWidth         local ratio = (photo.contentWidth / desiredWidth)                  photo.width = math.floor(desiredWidth / scaleFactor)         photo.height = math.floor((photo.contentHeight / ratio) / scaleFactor)         photo.x = display.contentWidth \* .5         photo.y = display.contentHeight \* .5         photo.isVisible = true         display.getCurrentStage():insert(photo) -- make sure it's on top of everything         display.save(photo, {             filename = filename,             baseDir = system.TemporaryDirectory,             isFullResolution = false, -- we don't need full resolution             backgroundColor = {0,0,0,1},             jpegQuality = .6         })                  photo.isVisible = false     end               ----------------------------------------------------------------------------     -- Event that occurs after user has selected a photo (from library or camera)     ----------------------------------------------------------------------------     local function onPhotoComplete( event )         if (event.completed) then             local photo = event.target             photo.isVisible = false                          savePhoto(photo, "temp.jpg", event.target.contentWidth)             savePhoto(photo, "upload.jpg", 256)             display.remove(photo) -- remove photo                          local event = {                 name = "photoPicker",                 phase = "photoComplete"             }                          pg.parent:dispatchEvent( event )         end     end

My previous post (and proposed solution) turned out to not work.  After some testing, I found that it was cropping my photos due to them not being fully on screen.

I have had to change my code back to the method of saving it as you get it from the photo library or camera, like so:

media.selectPhoto({

                    mediaSource = media.PhotoLibrary, 

                    listener = onPhotoComplete, 

                    origin = libraryButton.contentBounds,

                    permittedArrowDirections = {“right”},

                    destination = {baseDir=system.TemporaryDirectory, filename=“temp.jpg”, type=“image”} 

                })

It still seems slow, though, on an iPad 3. I’m not sure what’s up with it, but at this point, I think I’m just going to live with that.

Thanks for your workaround. It tooks me 4 days finding out display.save/capture trickiness…

Thanks for your workaround. It tooks me 4 days finding out display.save/capture trickiness…