Saving images to a size... How best to handle this?

This is in a way the reverse scenario to the following excellent tutorial from Rob : 

http://coronalabs.com/blog/2014/06/10/tutorial-fitting-images-to-a-size/

My question is, how can I save a display object from varying devices and end up with an identically sized jpg or png file on my device (more on that need later…).

I am using some excellent information shared by @schroederapps and @Renato in the following thread to create an circular avatar. 

http://forums.coronalabs.com/topic/48985-how-can-i-implement-this-function/

Won’t repeat the code here as it can easily be seen in the thread linked above but in summary, I create a circle with a radius of 120 and fill it with a camera image and then save it to device using display.save().

On an iPad I end up with a 512x512 file. On iPhone the file ends up being 480x480. On many Android devices I will end up with many different sizes… 

In order to use the Avatar with various backend services, I need it to be a fixed size conforming to the demands of the service in question. I am hoping to be able to therefore control the size of the final file saved. 

Possible Strategies

a ) Identify device DPI and do math to determine what ‘120’ (circle radius) equate to in pixels and then do some scaling on the display object before saving it so that the final output is a fixed size regardless of device.

Challenge with this one is the Android device variability and the fact that I can’t always get a reliable DPI reading off the device… 

b ) Save in whatever size display.save ends up providing and then use a backend service to do some resizing magic. 

Challenge with this one is additional transfer time & process complexity. I’d rather keep all pre-processing on device if at all possible.

What am I missing? Is there an option c, d, e and maybe even f? How would you tackle this challenge?

Thanks in advance for your help. Wishing you all a great weekend. 

Regards,

Kerem

In your makeAvatar function you can call

photo:scale(display.contentScaleX, display.contentScaleY)

This will scale the photo to double the circle radius on any device (which would be 240 pixels in your example).

There is a small caveat though, since we’re dealing with scaling there can be a +/- 1 pixel difference in the output.

Thats great! Thanks much your help. On iPhone I get 240x240 and on the iPad I get 241x241. Wonder if I could pre-calculate the output size on device ahead of the photo:scale call and then adjust the scale parameters. Or, is there way for me to get the result of photo:scale and tweak the parameters and call photo:scale again? Thanks once again for your help.

Instead of using display.contentScaleX/Y directly you could assign both X, and Y scales to variables as start values, and then make a short loop that checks for your desired output size and changes the scale slightly +/- until you hit your target.

Maybe a bit of an overkill, but it should work…

Yes. This is doable I think. Question is how to check the size for desired output size. Are you suggesting I check for photo.width after each call to photo:scale for example?

I was thinking something along the lines of this:

local scale = display.contentScaleX local sign = math.modf(photo.width \* scale) \> cameraLens.width and -1 or 1 while math.modf(photo.width \* scale) ~= cameraLens.width do scale = scale + 0.00001 \* sign end photo:scale(scale, scale) 

However, this doesn’t help.

I tested this with your code and it looks like display:save() is rounding up the value and adding an extra pixel in certain cases.

Bummer…

Thanks much for taking your time to try this solution. Most appreciated. I wonder if there is a way to check the size in pixels of an image file. Wonder if I can load it into a display object and check its size and if its not the size I want then reprocess it etc. Anyways, maybe getting a server to do my processing might be the better way to go for the time being. 

Edit : I found that I can read back the image using isFullResolution = true in a display.newImage call. This will be a long way to go about it but I think I can use your approach (ie minute adjustments to the scale) while checking the actual result and then repeating it all over.

Edit2 : Argh, another couple hours wasted until I remembered that Corona SDK does not reload an image file even if you remove it from display, nil it and then reload it. The original from memory is loaded again. This means you have to be sneaky in order to recreate the file, read it in, check its width etc… More on this later.

Ok. I give up on the option described above.

I set my scaling to get a 200x200 image saved. On iPhone I get 200x200. Then on iPad initial file saved is 198x198. I increment the scale factor by 0.00001 and rerun the process, the file size remains at 198x198 for a while and then in one whoop it jumps to 203x203… It is hard to fight the Corona SDK graphics engine. I think I will try and live with that initial 198x198 and see how things go. 

Any other ideas from anyone? Short of doing all the image resizing on the server do we have any other options? Are there any lua graphics libraries I’m overlooking which might allow me to resize a saved jpg/png file on the mobile device? Thanks for all your help.

If you have find a solution, we would be be very grateful. We have try so many combination to get it right, but it is impossible to get the same value depending on the device.

In your makeAvatar function you can call

photo:scale(display.contentScaleX, display.contentScaleY)

This will scale the photo to double the circle radius on any device (which would be 240 pixels in your example).

There is a small caveat though, since we’re dealing with scaling there can be a +/- 1 pixel difference in the output.

Thats great! Thanks much your help. On iPhone I get 240x240 and on the iPad I get 241x241. Wonder if I could pre-calculate the output size on device ahead of the photo:scale call and then adjust the scale parameters. Or, is there way for me to get the result of photo:scale and tweak the parameters and call photo:scale again? Thanks once again for your help.

Instead of using display.contentScaleX/Y directly you could assign both X, and Y scales to variables as start values, and then make a short loop that checks for your desired output size and changes the scale slightly +/- until you hit your target.

Maybe a bit of an overkill, but it should work…

Yes. This is doable I think. Question is how to check the size for desired output size. Are you suggesting I check for photo.width after each call to photo:scale for example?

I was thinking something along the lines of this:

local scale = display.contentScaleX local sign = math.modf(photo.width \* scale) \> cameraLens.width and -1 or 1 while math.modf(photo.width \* scale) ~= cameraLens.width do scale = scale + 0.00001 \* sign end photo:scale(scale, scale) 

However, this doesn’t help.

I tested this with your code and it looks like display:save() is rounding up the value and adding an extra pixel in certain cases.

Bummer…

Thanks much for taking your time to try this solution. Most appreciated. I wonder if there is a way to check the size in pixels of an image file. Wonder if I can load it into a display object and check its size and if its not the size I want then reprocess it etc. Anyways, maybe getting a server to do my processing might be the better way to go for the time being. 

Edit : I found that I can read back the image using isFullResolution = true in a display.newImage call. This will be a long way to go about it but I think I can use your approach (ie minute adjustments to the scale) while checking the actual result and then repeating it all over.

Edit2 : Argh, another couple hours wasted until I remembered that Corona SDK does not reload an image file even if you remove it from display, nil it and then reload it. The original from memory is loaded again. This means you have to be sneaky in order to recreate the file, read it in, check its width etc… More on this later.

Ok. I give up on the option described above.

I set my scaling to get a 200x200 image saved. On iPhone I get 200x200. Then on iPad initial file saved is 198x198. I increment the scale factor by 0.00001 and rerun the process, the file size remains at 198x198 for a while and then in one whoop it jumps to 203x203… It is hard to fight the Corona SDK graphics engine. I think I will try and live with that initial 198x198 and see how things go. 

Any other ideas from anyone? Short of doing all the image resizing on the server do we have any other options? Are there any lua graphics libraries I’m overlooking which might allow me to resize a saved jpg/png file on the mobile device? Thanks for all your help.

I also have the same issue and am very interested in the solution!

On our side, we have finally come up with a method that is outputting the same image size regardless of the device, regardless of the original image size and regardless of our config file with the following:

local function cropImage(photo, newFileName, cropW, cropH)

    

    – adjust width and height of the resulting image

    local endWidth  = cropW * display.contentScaleX

    local endHeight = cropH * display.contentScaleY

    

    – set the masking container

    local tempGroup = display.newSnapshot(endWidth, endHeight )

    tempGroup.x = 0

    tempGroup.y = 0

    tempGroup.anchorX = 0

    tempGroup.anchorY = 0

    

    – Define a solid color background, in case the final image is

    – smaller than the cropping output

    local whiteRc = display.newRect( 0, 0, endWidth, endHeight )

    tempGroup.group:insert(whiteRc)

    whiteRc.anchorX = 0.5

    whiteRc.anchorY = 0.5

    whiteRc:setFillColor( 1,1,1 )

    

    – insert the photo

    tempGroup.group:insert(photo)

    

    – scale the photo to your need.

    local scale = math.max(endWidth / photo.width, endHeight / photo.height) 

    photo:scale(scale, scale)

    

    – center the photo inside the container or position it

    – the way you want inside the container area. In our case

    – we center it.

    photo.x = 0

    photo.y = 0

    photo.anchorX = 0.5

    photo.anchorY = 0.5

    – save the cropped image

    display.save( tempGroup, { filename = newFileName, baseDir = system.TemporaryDirectory, isFullResolution = true } )

    – clean up the mess

    tempGroup:removeSelf()

    tempGroup = nil

end

Jedi,

Thank You! I will give it a shot today!

Worked like a champ!

Thanks!