Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

storing images (camera/gallery) in lua module for global access - works in simulator, not on device
Started by christoph-graf Apr 19 2017 03:47 AM

- - - - -
11 replies to this topic
[TOPIC CONTROLS]
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

christoph-graf

[GLOBAL: userInfoPane.html]
christoph-graf
  • Enthusiast

  • 34 posts
  • Corona SDK

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



[TOPIC: post.html]
#2

Sphere Game Studios

[GLOBAL: userInfoPane.html]
Sphere Game Studios
  • Corona Geek

  • 1,201 posts
  • Corona SDK

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



[TOPIC: post.html]
#3

christoph-graf

[GLOBAL: userInfoPane.html]
christoph-graf
  • Enthusiast

  • 34 posts
  • Corona SDK

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

Attached Files



[TOPIC: post.html]
#4

Sphere Game Studios

[GLOBAL: userInfoPane.html]
Sphere Game Studios
  • Corona Geek

  • 1,201 posts
  • Corona SDK

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.



[TOPIC: post.html]
#5

christoph-graf

[GLOBAL: userInfoPane.html]
christoph-graf
  • Enthusiast

  • 34 posts
  • Corona SDK

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.



[TOPIC: post.html]
#6

Sphere Game Studios

[GLOBAL: userInfoPane.html]
Sphere Game Studios
  • Corona Geek

  • 1,201 posts
  • Corona SDK

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.



[TOPIC: post.html]
#7

christoph-graf

[GLOBAL: userInfoPane.html]
christoph-graf
  • Enthusiast

  • 34 posts
  • Corona SDK

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?



[TOPIC: post.html]
#8

Sphere Game Studios

[GLOBAL: userInfoPane.html]
Sphere Game Studios
  • Corona Geek

  • 1,201 posts
  • Corona SDK

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



[TOPIC: post.html]
#9

christoph-graf

[GLOBAL: userInfoPane.html]
christoph-graf
  • Enthusiast

  • 34 posts
  • Corona SDK

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...



[TOPIC: post.html]
#10

christoph-graf

[GLOBAL: userInfoPane.html]
christoph-graf
  • Enthusiast

  • 34 posts
  • Corona SDK

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) :(



[TOPIC: post.html]
#11

Sphere Game Studios

[GLOBAL: userInfoPane.html]
Sphere Game Studios
  • Corona Geek

  • 1,201 posts
  • Corona SDK

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



[TOPIC: post.html]
#12

christoph-graf

[GLOBAL: userInfoPane.html]
christoph-graf
  • Enthusiast

  • 34 posts
  • Corona SDK

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. 




[topic_controls]
[/topic_controls]