storing images (camera/gallery) in lua module for global access - works in simulator, not on device

Dear corona community,

I have been struggling with this for months now and I just dont understand whats going on here. Here is what I’d like to do simplified:

I have three Composer scenes:

Scene A just shows a “welcome” screen, nothing special here

Scene B has two display.newContainers on it. They show images I set in Scene C, or, if no images have been taken yet, just a “tap to add image” text.

Scene C just shows a “+” icon to start the camera. The picture that is taken is stored in a modul “globals.lua”

So, if everything goes well, the app is started, user goes from Scene A to B, tapps on the first container to move on to Scene C and takes a picture. Since this picture is stored in globals.lua, once he goes back to scene B, the first container now shows this picture. The user then can tap on the second container, again is moved to scene C, where he takes the second picture. When he goes back to scene B, he now sees both pictures.

Everything works perfectly in the simulator, but as soon as I upload it on my android smartphone, the following happens:

I can perfectly set the first picture. But when I set the second picture and go back to scene B that is now supposed to show both pictures, the first picture got removed.

So, why does it work in the simulator and not on my device?

Scene A “passPic1” shouldnt be relevant, but here is the scene:create part:

function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen local buttonScene1, buttonScene2, buttonScene3 local gotoScene1, gotoScene2, gotoScene3 local welcomeText -- listener functions for scene change function gotoScene1() composer.gotoScene("passPic1") end function gotoScene2() composer.gotoScene("passPic2") end function gotoScene3() composer.gotoScene("passPic3") end -- add button1 buttonScene1 = display.newImageRect( "button1.jpg", 30,30 ) buttonScene1.x = display.contentCenterX - 50 buttonScene1.y = display.viewableContentHeight - 50 buttonScene1:addEventListener( "tap", gotoScene1 ) -- add button2 buttonScene2 = display.newImageRect( "button2.jpg", 30,30 ) buttonScene2.x = display.contentCenterX buttonScene2.y = display.viewableContentHeight - 50 buttonScene2:addEventListener( "tap", gotoScene2 ) -- add button3 buttonScene3 = display.newImageRect( "button3.jpg", 30,30 ) buttonScene3.x = display.contentCenterX + 50 buttonScene3.y = display.viewableContentHeight - 50 buttonScene3:addEventListener( "tap", gotoScene3 ) -- welcome text welcomeText = display.newText(sceneGroup, "welcome", display.contentWidth/2, display.contentCenterY, native.systemFontBold, 22 ) welcomeText:setFillColor( 0 ) end

Scene B “passPic2” where the two containers check if pictures have been taken:

local composer = require( "composer" ) local globalSpace = require("globals") local scene = composer.newScene() local topTemplate, bottomTemplate local topImage, bottomImage local choosePic1, choosePic2 -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create() function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen -- add top image template container topTemplate = display.newContainer(100,100) topTemplate.x = display.contentWidth/2 topTemplate.y = display.viewableContentHeight/2 - 55 sceneGroup:insert(topTemplate) -- add bottom image template container bottomTemplate = display.newContainer(100,100) bottomTemplate.x = display.contentWidth/2 bottomTemplate.y = display.viewableContentHeight/2 + 55 sceneGroup:insert(bottomTemplate) -- listener functions to add images function choosePic1 ( event ) local options = { params = {img = 1} } composer.gotoScene("passPic3", options) end function choosePic2 ( event ) local options = { params = {img = 2} } composer.gotoScene("passPic3", options) end -- add listeners to add image topTemplate:addEventListener( "tap", choosePic1 ) bottomTemplate:addEventListener( "tap", choosePic2 ) end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) -- fill top image container topImage = globalSpace.getImg1() if(topImage) then topImage = globalSpace.getImg1() topTemplate:insert(topImage) topImage.x = 0 topImage.y = 0 else topImage = display.newImageRect(topTemplate, "addImage.jpg", 100,100 ) end -- fill bottom image container bottomImage = globalSpace.getImg2() if(bottomImage) then bottomImage = globalSpace.getImg2() bottomTemplate:insert(bottomImage) bottomImage.x = 0 bottomImage.y = 0 else bottomImage = display.newImageRect(bottomTemplate, "addImage.jpg", 100,100 ) end elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen end end -- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene

and finally Scene C “passPic” where the camera action takes place:

local composer = require( "composer" ) local globalSpace = require("globals") local photo local cameraButton local params local imageBounds local finalImage local addImageFromCamera, cameraListener local scene = composer.newScene() -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create() function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen -- function to add picture from camera function addImageFromCamera() if media.hasSource( media.Camera ) then -- take picture media.capturePhoto( { listener=cameraListener } ) else -- load default image photo = display.newImage("default.jpg") photo.width = 300 photo.height = 300 photo.x = display.contentCenterX photo.y = display.contentCenterY sceneGroup:insert(photo) -- set bounds to capture part of image imageBounds = { xMin = display.contentCenterX - 50, xMax = display.contentCenterX + 50, yMin = display.contentCenterY - 50, yMax = display.contentCenterY + 50 } -- capture part of image finalImage = display.captureBounds( imageBounds, false ) finalImage.x = display.contentCenterX finalImage.y = display.contentCenterY sceneGroup:insert(finalImage) photo:removeSelf() photo = nil if(option == 1) then globalSpace.setImg1(finalImage) else globalSpace.setImg2(finalImage) end end end -- listener function to receive image from camera function cameraListener(event) photo = event.target photo.width = 300 photo.height = 300 photo.x = display.contentCenterX photo.y = display.contentCenterY sceneGroup:insert(photo) -- set bounds to capture part of image imageBounds = { xMin = display.contentCenterX - 50, xMax = display.contentCenterX + 50, yMin = display.contentCenterY - 50, yMax = display.contentCenterY + 50 } -- capture part of image finalImage = display.captureBounds( imageBounds, false ) finalImage.x = display.contentCenterX finalImage.y = display.contentCenterY sceneGroup:insert(finalImage) photo:removeSelf() photo = nil if(option == 1) then globalSpace.setImg1(finalImage) else globalSpace.setImg2(finalImage) end end -- button to take picture with camera (or take default if no camera) cameraButton = display.newImage(sceneGroup, "buttonAdd.png") cameraButton.x = display.contentCenterX cameraButton.y = 20 cameraButton:addEventListener( "tap", addImageFromCamera ) end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) -- get scene parameters to decide whether image1 or image2 is set local params = event.params local options if(params) then option = params.img else option = 1 end elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen end end -- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene

here is the globals.lua:

local globalSpace = {} local img1, img2 local function setImg1(img) img1 = img end local function setImg2(img) img2 = img end local function getterImg1() return img1 end local function getterImg2() return img2 end globalSpace.setImg1 = setImg1 globalSpace.setImg2 = setImg2 globalSpace.getImg1 = getterImg1 globalSpace.getImg2 = getterImg2 return globalSpace

I would be more than happy if anyone could have a look at this. Any ideas and suggestions are highly appreciated.

Thanks in advance

If you zip up your project (including images) I’ll take a look at it for you.

Wow, great. You find the zipped project attached. Thank you very much for your efforts!

OK it would appear that media.capturePhoto() crashes the app instantly on Android 7 (with permissions enabled obviously) - which is definitely a Corona issue.  It worked on Android 5.

On Android 5 device there was corruption on the container objects on switching scenes and sometimes ANRs too.  I think it is down to scoping.  The captured image goes out of scope and therefore the reference to it in your globals also contains an invalid image handle.

Personally, I think the way you are going about this is wrong.  You should use an overlay to capture the image and then persist it to disk. You can then load the image on demand in another scene.

Thank you Sphere Game Studio. I am running it on a Cyagonenmod and at least I never got a crash on media.capturePhoto(). It’s a good hint though to check it also on Android 7 devices in the future!

May I ask you to explain what you mean by corruption on the container objects? Is there something wrong with my code? I thought that my simplified code is pretty basic on the one hand and also not something fancy that noone has done before.

At least I cannot imagine that taking two pictures with your camera and providing them globally to all scenes should be that big of an issue, or am I wrong?

The overlay idea sounds good and definitely worth a try. Never thought about that, I will give that a try. Thank you very much. As far as I know, an overlay would provide the pictures to its parent scene, which might be enough in my case. In contrast: How would an expert handle the global providing of images? Would saving to disk and loading it every time really be the best option?

I would love to know what exactly the issue is here. I have been trying everything in regards to scoping, but whenever it worked smoothly on the simulator, Android “lost” the first image taken once a second one was taken.

Thanks again for any efforts.

Working on simulator ~= working on device!

It is always good practice to persist stuff to disk.  Android is a real pain, for example in your code you never asked permission to use the camera and so it completely failed on Android 6+.

It is difficult when you have multiple scenes active to trace the issue, generally you should have a single scene active at any one time and show alternative scenes as overlays - especially if they are only transitional (by that I mean the serve a single purpose like capturing an image, getting user input, etc).  

Remember if you create an image lets call it imgA and then you say imgB = imgA.  You are not creating imgB you are merely creating a memory pointer to imgA.  The minute imgA goes out of scope so does imgB.

Thank you Sphere Game Studio, I really appreciate your explanations.

So, that said, could it be a good idea to make this single scene (that is active all the time) serving as the global “storage” and show all the other scenes as overlays? Or, in other words. Put the stuff I have in globals.lua in a scene that is active the whole time and then put all the elements like containers, buttons and stuff in scenes that appear as overlays?

It is not so much a case of storage but more a case of scope.  The image corruption you are seeing is when the reference to the image is no longer pointing to a valid image.  If you are unclear what a memory pointer is then think of it like the index in a book.

It is good practise to purge a scene when you have loaded another screen so don’t rely on scenes for storage.  This keeps memory requirements low.  If your app starts using too much memory then Corona will star automatically purging your scenes which could lead to undesired behaviour.

Read this to understand how/why to use overlays - https://docs.coronalabs.com/api/library/composer/showOverlay.html

Jup, I at least got an idea of how pointers and their referenced object work. I did some more attempts to find out what causes the image (or the scene responsible for it) to get purged. In my scene C, where images are taken by the camera, the following cameraListener (as seen above) does work on the simulator, but not on my device (where first picture gets purged once a second one has been taken):

function cameraListener(event)         photo = event.target             photo.width = 300         photo.height = 300         photo.x = display.contentCenterX         photo.y = display.contentCenterY         sceneGroup:insert(photo)                  -- set bounds to capture part of image         imageBounds =         {             xMin = display.contentCenterX - 50,             xMax = display.contentCenterX + 50,             yMin = display.contentCenterY - 50,             yMax = display.contentCenterY + 50         }                  -- capture part of image         finalImage = display.captureBounds( imageBounds, false )         finalImage.x = display.contentCenterX         finalImage.y = display.contentCenterY         sceneGroup:insert(finalImage)         photo:removeSelf()         photo = nil                  if(option == 1) then             globalSpace.setImg1(finalImage)         else             globalSpace.setImg2(finalImage)         end     end

But, when I leave out the display.captureBounds part and save the original photo in my globals lua instead:

function cameraListener(event) photo = event.target photo.width = 300 photo.height = 300 photo.x = display.contentCenterX photo.y = display.contentCenterY sceneGroup:insert(photo) -- set bounds to capture part of image imageBounds = { xMin = display.contentCenterX - 50, xMax = display.contentCenterX + 50, yMin = display.contentCenterY - 50, yMax = display.contentCenterY + 50 } -- capture part of image --finalImage = display.captureBounds( imageBounds, false ) --finalImage.x = display.contentCenterX --finalImage.y = display.contentCenterY --sceneGroup:insert(finalImage) --photo:removeSelf() --photo = nil if(option == 1) then globalSpace.setImg1(photo) else globalSpace.setImg2(photo) end end

it perfectly works on the device as well. So, storing the photo returned by event.target in my globals.lua works, whereas storing the display.captureBounds part of the photo in my globals.lua leads to a lost image when going back to SceneB. Both variables ‘photo’ and ‘finalImage’ are created outside of the scene-functions and they are both only used in the same coding-part.

That is what I do not understand: I thought they are both displayobjects, where is the difference? I hope it gets clear which part confuses me.

Thanks to anyone taking his time trying to help…

Wow, never thought this could be that frustrating. I switched to an overlay scene, where a function saveImage() calls a parent function:

-- listener function called when user is happy with image function saveImage(event) -- capture image part finalImage = display.captureBounds( imageBounds, false ) -- save finalImage in parent scene parent:setImg1(finalImage) -- go back to addfaver composer.hideOverlay() end

parent:setImg1:

function scene:setImg1(pic) globalImg1 = pic self.view:insert(globalImg1) globalImg1.x = display.contentCenterX globalImg1.y = display.contentCenterY - 2 - imageSize/2 globalImg1:scale(imageSize/globalImg1.contentWidth, imageSize/globalImg1.contentWidth) end

Again, the same problem: I can set the two images in my parent scene via the overlay scene perfectly on the simulator, but as soon as I try it on my device, the first image is lost once I set the second image. Jesus christ, this feels again like why I gave up a couple of months ago…

/edit: Another maybe interesting observation: The first image gets losts once I start the camera or gallery for the second picture. So, even before I call the saveImage() function in the overlay for the second image, I can see that the parent scene already lost the first image. So, it may have something to do with media.capturePhoto or media.selectPhoto, but I dont see whey they could kill an image that is displayed in a parent scene. Weird, just weird (at least to me) :frowning:

Just save the picture to disk in your overlay and then load it as a new image and it should work and not get wiped out

Jep, working on that one now. Guess it’s time to stop struggling with any other solution. Thanks Sphere Game Studio for your support.

In case anyone else has a clue what exactly causes the wiping or which role the media.capturePhoto and media.selectPhoto play, I’d be more than happy to learn. 

If you zip up your project (including images) I’ll take a look at it for you.

Wow, great. You find the zipped project attached. Thank you very much for your efforts!

OK it would appear that media.capturePhoto() crashes the app instantly on Android 7 (with permissions enabled obviously) - which is definitely a Corona issue.  It worked on Android 5.

On Android 5 device there was corruption on the container objects on switching scenes and sometimes ANRs too.  I think it is down to scoping.  The captured image goes out of scope and therefore the reference to it in your globals also contains an invalid image handle.

Personally, I think the way you are going about this is wrong.  You should use an overlay to capture the image and then persist it to disk. You can then load the image on demand in another scene.

Thank you Sphere Game Studio. I am running it on a Cyagonenmod and at least I never got a crash on media.capturePhoto(). It’s a good hint though to check it also on Android 7 devices in the future!

May I ask you to explain what you mean by corruption on the container objects? Is there something wrong with my code? I thought that my simplified code is pretty basic on the one hand and also not something fancy that noone has done before.

At least I cannot imagine that taking two pictures with your camera and providing them globally to all scenes should be that big of an issue, or am I wrong?

The overlay idea sounds good and definitely worth a try. Never thought about that, I will give that a try. Thank you very much. As far as I know, an overlay would provide the pictures to its parent scene, which might be enough in my case. In contrast: How would an expert handle the global providing of images? Would saving to disk and loading it every time really be the best option?

I would love to know what exactly the issue is here. I have been trying everything in regards to scoping, but whenever it worked smoothly on the simulator, Android “lost” the first image taken once a second one was taken.

Thanks again for any efforts.

Working on simulator ~= working on device!

It is always good practice to persist stuff to disk.  Android is a real pain, for example in your code you never asked permission to use the camera and so it completely failed on Android 6+.

It is difficult when you have multiple scenes active to trace the issue, generally you should have a single scene active at any one time and show alternative scenes as overlays - especially if they are only transitional (by that I mean the serve a single purpose like capturing an image, getting user input, etc).  

Remember if you create an image lets call it imgA and then you say imgB = imgA.  You are not creating imgB you are merely creating a memory pointer to imgA.  The minute imgA goes out of scope so does imgB.

Thank you Sphere Game Studio, I really appreciate your explanations.

So, that said, could it be a good idea to make this single scene (that is active all the time) serving as the global “storage” and show all the other scenes as overlays? Or, in other words. Put the stuff I have in globals.lua in a scene that is active the whole time and then put all the elements like containers, buttons and stuff in scenes that appear as overlays?

It is not so much a case of storage but more a case of scope.  The image corruption you are seeing is when the reference to the image is no longer pointing to a valid image.  If you are unclear what a memory pointer is then think of it like the index in a book.

It is good practise to purge a scene when you have loaded another screen so don’t rely on scenes for storage.  This keeps memory requirements low.  If your app starts using too much memory then Corona will star automatically purging your scenes which could lead to undesired behaviour.

Read this to understand how/why to use overlays - https://docs.coronalabs.com/api/library/composer/showOverlay.html

Jup, I at least got an idea of how pointers and their referenced object work. I did some more attempts to find out what causes the image (or the scene responsible for it) to get purged. In my scene C, where images are taken by the camera, the following cameraListener (as seen above) does work on the simulator, but not on my device (where first picture gets purged once a second one has been taken):

function cameraListener(event)         photo = event.target             photo.width = 300         photo.height = 300         photo.x = display.contentCenterX         photo.y = display.contentCenterY         sceneGroup:insert(photo)                  -- set bounds to capture part of image         imageBounds =         {             xMin = display.contentCenterX - 50,             xMax = display.contentCenterX + 50,             yMin = display.contentCenterY - 50,             yMax = display.contentCenterY + 50         }                  -- capture part of image         finalImage = display.captureBounds( imageBounds, false )         finalImage.x = display.contentCenterX         finalImage.y = display.contentCenterY         sceneGroup:insert(finalImage)         photo:removeSelf()         photo = nil                  if(option == 1) then             globalSpace.setImg1(finalImage)         else             globalSpace.setImg2(finalImage)         end     end

But, when I leave out the display.captureBounds part and save the original photo in my globals lua instead:

function cameraListener(event) photo = event.target photo.width = 300 photo.height = 300 photo.x = display.contentCenterX photo.y = display.contentCenterY sceneGroup:insert(photo) -- set bounds to capture part of image imageBounds = { xMin = display.contentCenterX - 50, xMax = display.contentCenterX + 50, yMin = display.contentCenterY - 50, yMax = display.contentCenterY + 50 } -- capture part of image --finalImage = display.captureBounds( imageBounds, false ) --finalImage.x = display.contentCenterX --finalImage.y = display.contentCenterY --sceneGroup:insert(finalImage) --photo:removeSelf() --photo = nil if(option == 1) then globalSpace.setImg1(photo) else globalSpace.setImg2(photo) end end

it perfectly works on the device as well. So, storing the photo returned by event.target in my globals.lua works, whereas storing the display.captureBounds part of the photo in my globals.lua leads to a lost image when going back to SceneB. Both variables ‘photo’ and ‘finalImage’ are created outside of the scene-functions and they are both only used in the same coding-part.

That is what I do not understand: I thought they are both displayobjects, where is the difference? I hope it gets clear which part confuses me.

Thanks to anyone taking his time trying to help…