Possible to resize jpg images?

Hi all,

any news on this bug?

I’m experiencing the same problem with CoronaSDK 2015.2637

The camera is unusable on Android and iOS.

Seem that corona apply about a 200% zoom on the captured picture

The original Photo from camera and the captured images are both 1536 × 2048 pixel

I have attached 2 pics taked by my iPad 2 with iOS 8.3 running Corona Camera Demo, to show you the problem.

Thanks in advance for any feedbacks.

Ale

Yes, I would also like to know what is happening in this department.

A] Has ANYONE made an app with Corona that captures images with the camera and store them with a sensible (the native image resolution on a modern phone is far too high) size?

B] Is this at all POSSIBLE with Corona (without fetching single pixels, write a rescaler and then put the pixels back somehow)?

I would really like someone in the know to answer question B.

If it is not possible I can stop trying and you would have saved me a lot of time. At the same time it would make the SDK kind of unimpressive.

You should be able to scale the image to fit the screen, put it in a group and then save that group.  You can’t do anything bigger than the screen except for the full sized image.

Rob

Hi Rob,

I don’t need anything bigger than the screen. I need to scale to a max width/height of, say, 500. That part is ok.

But can you confirm that you can do you do this this successfully, with images taken from the camera with several different devices?

If that is the case: How?

Hi @runewinse,

I would suggest omitting the “destination” table of .capturePhoto(), thus placing the object in the display. Then, resize the object to maximum 500 width or height (while preserving the aspect ratio with some basic math), then display.save() it out to the temporary directory and proceed with your upload as normal.

Best regards,

Brent

Hi,

I don’t see how this is supposed to work. Below I’ve added my complete test code. The app shows some text along the bottom that when touched fires up the camera, which in turn calls an event handler which again calls saveScaledImage() (which is a (too?) simplified version of something jonjonsson on this forum has written).

\_W = display.contentWidth \_H = display.contentHeight local function saveScaledImage(photo, saveName, maxPixels) -- Calculate scaled image size local numPix = photo.contentWidth \* photo.contentHeight print("saveScaledImage() - Orig num pix = " .. photo.contentWidth .. " x " .. photo.contentHeight .. " = " .. numPix) local scaledWidth = photo.contentWidth local scaledHeight = photo.contentHeight -- Rescale image if pixel number exceeds max pixels if (numPix \> maxPixels) then local ar = photo.contentWidth / photo.contentHeight print("saveScaledImage() - ar = " .. ar) scaledWidth = math.floor(math.sqrt(ar \* maxPixels)) scaledHeight = math.floor(scaledWidth / ar) print("saveScaledImage() - new image dimensions = " .. scaledWidth .. " x " .. scaledHeight) end photo.width = scaledWidth photo.height = scaledHeight print("saveScaledImage() - Scaled image W x H = " .. photo.width .. " x " .. photo.height) display.save( photo, saveName, system.TemporaryDirectory) photo:removeSelf() photo = nil end local function itemPhotoOnComplete( event ) if (event.completed == true) then local photo = event.target print( "photo W x H = " .. photo.width .. " x " .. photo.height ) -- Save 200000 pix version of image as img.jpg in temp folder local imgSaveName = "img.jpg" saveScaledImage(photo, imgSaveName, 200000) -- Show rescaled image scaledImage = display.newImage(imgSaveName, system.TemporaryDirectory, \_W\*0.5, \_H\*0.5) print("Scaled image size = " .. scaledImage.width .. " x " .. scaledImage.height) end end local function takePhotoTouchHandler(event) if (event.phase == "ended") then -- Get image from camera if media.hasSource( media.Camera ) then print("Camera detected - starting camera") media.capturePhoto( { listener = itemPhotoOnComplete } ) else native.showAlert("Error", "No camera found", { "OK" } ) end end end print("\n\n\n\n-------------------- TEST APP STARTED ---------------------") local takePhoto = display.newText("TAKE PHOTO", \_W\*0.5, \_H\*0.9, native.systemFont, 40) takePhoto:addEventListener("touch", takePhotoTouchHandler)

Running the app (on an Android device) results in the following log:

I/Corona (13735): -------------------- TEST APP STARTED --------------------- I/Corona (13735): Camera detected - starting camera V/Corona (13735): Downsampling image file '/storage/emulated/0/Pictures/Picture56.jpg' to fit max pixel size of 4096. I/Corona (13735): photo W x H = 1494 x 2656 I/Corona (13735): saveScaledImage() - Orig num pix = 1494 x 2656 = 3968064 I/Corona (13735): saveScaledImage() - ar = 0.5625 I/Corona (13735): saveScaledImage() - new image dimensions = 335 x 595 I/Corona (13735): saveScaledImage() - Scaled image W x H = 335 x 595 V/Corona (13735): Downsampling image file '/storage/emulated/0/Pictures/Picture56.jpg' to fit max pixel size of 4096. I/Corona (13735): Scaled image size = 225 x 483
  • The scaling code seems to work as expected.

  • The image saved with display.save() seems to be saved ok since I get no errors/warnings in the log for the newImage call:

    scaledImage = display.newImage(imgSaveName, system.TemporaryDirectory, _W*0.5, _H*0.5)

But the SIZE of the loaded image is all wrong (225x483).

Another thing is these lines:

Downsampling image file '/storage/emulated/0/Pictures/Picture56.jpg' to fit max pixel size of 4096. 

I can understand (kind of) imagine why the first line is written, but where does the second line come from?

Is the above way you suggested to do such a rescaling, or have I misunderstood something royally?

Hi @runewinse,

I actually think this is slightly over-complicated. Instead of doing all the multiplying of width and height, why not just find either the maximum width OR height of the photo, and if it exceeds 500 (your proposed maximum pixels for width or height), just downsample from there, for example:

[lua]

local maxWidth = 500

local maxHeight = 500

–rescale width

if ( photo.width > maxWidth ) then

   local ratio = maxWidth / photo.width

   photo.width = maxWidth

   photo.height = photo.height * ratio

end

–rescale height

if ( photo.height > maxHeight ) then

   local ratio = maxHeight / photo.height

   photo.height = maxHeight

   photo.width = photo.width * ratio

end

[/lua]

Then, just save the photo, but don’t remove it from the display. You’re keeping it on screen anyway, so there’s no need to remove it, just to immediately reload it from the temporary directory.

Hope this helps,

Brent

Well, as I wrote above, it is  not  the scaling calculation that is the problem. The scaling calculation works just fine. I want control over the total pixel count and not the max dimensions.


No, I’m not keeping the photo on screen anyway. In my real app the photo is supposed to be uploaded to a server and not necessarily be shown on the screen.

Yes, in the example above, I load the rescaled image with newImage immediately after the call to saveScaledImage():

 saveScaledImage(photo, imgSaveName, 200000) -- Show rescaled image scaledImage = display.newImage(imgSaveName, system.TemporaryDirectory, \_W\*0.5, \_H\*0.5) print("Scaled image size = " .. scaledImage.width .. " x " .. scaledImage.height)

But this is done just to check the dimensions of the scaled image, not to display the image on screen.

So, neither simplifying the scaling, nor keeping the photo object on screen helps me at all. I only want the saving to work properly. 

WHY does the scaledImage object above get other dimensions than what is calculated in the saveScaledImage() ?

Does the actual saved image get scaled properly? If so, the following lines don’t really matter, correct?

Yes, if the image is scaled/saved properly, the following lines doesn’t matter, that is true. But how do I know that the image file is scaled/saved correctly?

That is exactly what I try to check with the lines:

 scaledImage = display.newImage(imgSaveName, system.TemporaryDirectory, \_W\*0.5, \_H\*0.5) print("Scaled image size = " .. scaledImage.width .. " x " .. scaledImage.height) 

I have also tried setting the fullResolution parameter to true (the results are the same):

scaledImage = display.newImage(imgSaveName, system.TemporaryDirectory, \_W\*0.5, \_H\*0.5, true)

But according to the output from print statement above, the loaded image is NOT of the correct pixel size. As the log show (did you get other results when running the app???), the dimensions of the loaded image (scaledImage) is  225x483 , while the dimensions of the saved image is (suposed to be)  335 x 595.

So I’m completely confused. WHY does the scaledImage get other dimensions than what is calculated in the saveScaledImage() ?

Is it my dimension check that is done poorly? Then how do I check it properly?

Thank you for the effort!

Hi @runewinse,

This may be related to how you’re using .contentWidth in some places and .width in other places (and same for the height params). These will actually vary, which is why they’re separate APIs.

Is it possible for you to perform all of this stuff in a series of events wherein you can actually inspect what’s going on before you move to the next step? As in, maybe try sorting this into a series of steps where each is triggered by a button press, and make sure that you’re getting the results you need before the next step… sort of a step-by-step debugging, so you can figure out where it’s going off the expected results.

Take care,

Brent

Hi,

The contentWidth/Height stuff was something from the code example that I have used, so I don’t even know the purpose of using that.

I have, however, removed all mention of content completely and are using only .width/.height. I have also reduced the example to the bare minimum:

\_W = display.contentWidth \_H = display.contentHeight local function itemPhotoOnComplete( event ) if (event.completed == true) then local photo = event.target print( "photo W x H = " .. photo.width .. " x " .. photo.height ) -- Rescale, save and remove photo photo.width = 512 photo.height = 512 display.save(photo, "img.jpg", system.TemporaryDirectory) print( "rescaled photo W x H = " .. photo.width .. " x " .. photo.height ) photo:removeSelf() photo = nil -- Show rescaled image scaledImage = display.newImage("img.jpg", system.TemporaryDirectory, \_W\*0.5, \_H\*0.5) print("Scaled image size = " .. scaledImage.width .. " x " .. scaledImage.height) end end local function takePhotoTouchHandler(event) if (event.phase == "ended") then -- Get image from camera if media.hasSource( media.Camera ) then print("Camera detected - starting camera") media.capturePhoto( { listener = itemPhotoOnComplete } ) else native.showAlert("Error", "No camera found", { "OK" } ) end end end local takePhoto = display.newText("TAKE PHOTO", \_W\*0.5, \_H\*0.9, native.systemFont, 40) takePhoto:addEventListener("touch", takePhotoTouchHandler)

The log from the app above looks like this:

Camera detected - starting camera

Downsampling image file ‘/storage/emulated/0/Pictures/Picture69.jpg’ to fit max pixel size of 4096.

photo W x H = 1494 x 2656

Downsampling image file ‘/storage/emulated/0/Pictures/Picture69.jpg’ to fit max pixel size of 4096.

rescaled photo W x H = 512 x 512

Scaled image size = 346 x 428

In this example I hardcode the final image size to 512x512. For each step I log the results. The problem  persists, however. The saved image appears to be of the wrong size.

Sure, I can make some extra code to step, but is it any point as long as I log the results for each step as I do now? 

The attached app above is simple to the extreme. Could you please try to run it yourself (on an Android device) and tell me what kind of results you get?

I suspect the problem is trying to size the image by setting it’s width and height parameters.  This might work for vector images, but I don’t know how reliable it is for images.  .height and .width should almost be considered read only.  You really should be scaling your image.  First taking a rectangular (typically a 4:6 shaped image) and making it a square will cause the image to be squeezed.  Normally you want to maintain the image’s aspect ratio.  Brent’s scaling code should work, but let me present you my variant of that:

local scale

if img.height > img.width then

     scale = 512 / image.height

else

     scale = 512 / image.width

end

img:scale( scale, scale )

After that img should be the right aspect ratio, with the long side being 512 pixels.   Now when you use display.save() it should save the image at the right size.

Rob

Thanks, Rob, I will try with scaling instead of setting height/width. 

To know that setting the height/width is extremely important info for us users (at least for this user) and I cannot se any mention of this in the docs?

I know that display.newImage() returns a DisplayObject, and in the docs for DisplayObjects -> object.width it says:

"Overview

Retrieve or change the width of a display object. For text objects, this property can be used to get (but not set) the width.

For images, the returned value is the original bitmap height, including any transparent area."

Furhter it says:

On Android, images are limited (downsized) to 2048×2048, even though the maximum texture memory size is higher on the device. The reason is most Android devices don’t have enough heap memory to load the image. It’s recommended that you don’t load image files larger than 2048×2048.

Does this has anything to do with this log message?

Downsampling image file '/storage/emulated/0/Pictures/Picture69.jpg' to fit max pixel size of 4096.

And how in the world can I avoid loading images larger than 2048x2048 in my case? In my Samsung GS5, the camera resolution in 5312x2988…

And just for info: The 512x512 size was just hardcoded values to see if anything scaled correctly. If I get basic image scaling thing to work I will most certainly preserve the aspect ratio  :slight_smile:

Ok, now I’ve done some testing using scaling as per advice.

The complete code looks like this:

\_W = display.contentWidth \_H = display.contentHeight local function itemPhotoOnComplete( event ) if (event.completed == true) then local photo = event.target print(" Original photo dimensions = (" .. photo.width .. " x " .. photo.height .. ")") local scaling = 0.25 local scaledW = math.floor(photo.width\*scaling) local scaledH = math.floor(photo.height\*scaling) -- Rescale, save and remove photo print(" Scaled photo dimensions = (" .. scaledW .. " x " .. scaledH .. ")") photo:scale(scaling, scaling) display.save(photo, "img.jpg", system.TemporaryDirectory) photo:removeSelf() photo = nil -- Show rescaled image loadedImage = display.newImage("img.jpg", system.TemporaryDirectory, \_W\*0.5, \_H\*0.5) print(" Loaded image size = (" .. loadedImage.width .. " x " .. loadedImage.height .. ")") end end local function takePhotoTouchHandler(event) if (event.phase == "ended") then -- Get image from camera if media.hasSource( media.Camera ) then print("Camera detected - starting camera") media.capturePhoto( { listener = itemPhotoOnComplete } ) else native.showAlert("Error", "No camera found", { "OK" } ) end end end local takePhoto = display.newText("TAKE PHOTO", \_W\*0.5, \_H\*0.9, native.systemFont, 40) takePhoto:addEventListener("touch", takePhotoTouchHandler)

I’ve just set the scaling to 0.25 for now - just to see if anything works.

It doesn’t. Still the dimension of the loaded image does not correspond to the dimensions of the scaled saved image:

I/Corona  (32042): Camera detected - starting camera

V/Corona  (32042): Downsampling image file ‘/storage/emulated/0/Pictures/Picture83.jpg’ to fit max pixel size of 4096.

I/Corona  (32042):  Original photo dimensions = (1494 x 2656)

I/Corona  (32042):  Scaled   photo dimensions = (373 x 664)

V/Corona  (32042): Downsampling image file ‘/storage/emulated/0/Pictures/Picture83.jpg’ to fit max pixel size of 4096.

I/Corona  (32042):  Loaded image size         = (251 x 531)

Another thing that may or may not shed some light on this issue is how the screen looks on the device while running this app.

After pressing the “TAKE PHOTO” text, the camera opens after I have taken a photo, this is how it looks:

cam01.png

(The texts says “TRY AGAIN” and “OK”, in case you don’t know norwegian…)

Then after pressing ok, the screen looks like this:

cam02.png

I don’t know if the scaling is right, but the image is also  cropped. This was not part of the deal. Why is it cropped. I’m sure the scaling issue and the cropping is part of the same problem.

Do you consider me rude if I ask you if you have actually tried to run this app on an Android device? It’s the third time I ask and I do not get any reply on that, so there must be something rude/wrong about the way I’m asking.

English is not my native tongue so it may be something I write that comes out a bit wrong. I apologize for any such thing.

You can use width and height.  We allow that.  I just find scaling better for this type work since it protects the aspect ratio of the photo better.

By default we resize images down to fit in a 2048x2048 box since that’s the max texture size on many Android devices, though it looks like we may be looking at the device’s actual maxTextureSize which is why it’s resampling to a fit into a 4096x4096 box.  That’s to be expected.

As for testing your code:  Make a complete project with build.settings, config.lua and a main.lua that is needed to complete it and depending on my workload for the day, I’ll see if I can fit it in.

Rob

Strange that the image size is:  1494 x 2656  then. I don’t see how that corresponds to a 4096x4096 box.

Ok then.

config.lua

application = { content = { width = 800, height = 1300, scale = "letterbox", xAlign = "center", yAlign = "center", imageSuffix = { ["@2x"] = 1.5, ["@4x"] = 3.0, }, fps = 30, } }

build.settings:

settings = { orientation = { default = "portrait", supported = { "portrait" }, }, android = { usesPermissions = { "android.permission.INTERNET", "android.permission.ACCESS\_FINE\_LOCATION", "android.permission.VIBRATE", "android.permission.ACCESS\_COARSE\_LOCATION", "android.permission.CAMERA", "android.permission.WRITE\_EXTERNAL\_STORAGE", }, usesFeatures = { { name = "android.hardware.location", required = false }, { name = "android.hardware.location.gps", required = true }, { name = "android.hardware.location.network", required = false }, }, }, plugins = { ["plugin.pasteboard"] = { publisherId = "com.coronalabs", }, }, }

main.lua:

\_W = display.contentWidth \_H = display.contentHeight local function itemPhotoOnComplete( event ) if (event.completed == true) then local photo = event.target print(" Original photo dimensions = (" .. photo.width .. " x " .. photo.height .. ")") local scaling = 0.25 local scaledW = math.floor(photo.width\*scaling) local scaledH = math.floor(photo.height\*scaling) -- Rescale, save and remove photo print(" Scaled photo dimensions = (" .. scaledW .. " x " .. scaledH .. ")") photo:scale(scaling, scaling) display.save(photo, "img.jpg", system.TemporaryDirectory) photo:removeSelf() photo = nil -- Show rescaled image loadedImage = display.newImage("img.jpg", system.TemporaryDirectory, \_W\*0.5, \_H\*0.5) print(" Loaded image size = (" .. loadedImage.width .. " x " .. loadedImage.height .. ")") end end local function takePhotoTouchHandler(event) if (event.phase == "ended") then -- Get image from camera if media.hasSource( media.Camera ) then print("Camera detected - starting camera") media.capturePhoto( { listener = itemPhotoOnComplete } ) else native.showAlert("Error", "No camera found", { "OK" } ) end end end local takePhoto = display.newText("TAKE PHOTO", \_W\*0.5, \_H\*0.9, native.systemFont, 40) takePhoto:addEventListener("touch", takePhotoTouchHandler)

I’m sorry for not just zipping the files and attaching them, but I’m not allowed to attach anything in this forum it seems. I get “This upload failed” for all file types and all file sizes.

Okay, I think I might know what the problems is…

photo is being drawn at 0, 0, which means only the bottom right quarter is on screen.  At least that’s the behavior I’m seeing.  Once I did:

photo.x = display.contentCenterX

photo.y = display.contentCenterY

I now get a print out that looks like:
 

I/Corona  ( 5157):  Original photo dimensions = (960 x 1280)
I/Corona  ( 5157):  Scaled   photo dimensions = (240 x 320)
I/Corona  ( 5157):  Loaded image size         = (222 x 297)

The loaded image is a few pixels less than the scaled photo (not sure the cause of it), but I get a photo that fits within the required box.  I don’t know if the lost pixels are part of the JPEG compression or that the original coming off my Nexus 7’s front camera (the only one) contains some transparency around it that gets lost saving a JPEG.

Rob

Hi Rob,

I’m sorry, but JPG compression does not throw away pixels like that. The transparency theory is also a bit far fetched, IMHO.

And besides, in my test my loaded image size is actually larger than it should be, not smaller:

I/Corona  ( 1818): Camera detected - starting camera

V/Corona  ( 1818): Downsampling image file ‘/storage/emulated/0/Pictures/Picture19.jpg’ to fit max pixel size of 4096.

I/Corona  ( 1818): itemPhotoOnComplete()

I/Corona  ( 1818):  Original photo dimensions = (1494 x 2656)

I/Corona  ( 1818):  Scaled   photo dimensions = (373 x 664)

V/Corona  ( 1818): Downsampling image file ‘/storage/emulated/0/Pictures/Picture19.jpg’ to fit max pixel size of 4096.

I/Corona  ( 1818):  Loaded image size         = (504 x 896)

So, no “lost” pixels there. The stuff seems to simply not be working.

My current code:

\_W = display.contentWidth \_H = display.contentHeight local function itemPhotoOnComplete( event ) print("itemPhotoOnComplete()") if (event.completed == true) then local photo = event.target photo.x = \_W/2 photo.y = \_H/2 print(" Original photo dimensions = (" .. photo.width .. " x " .. photo.height .. ")") local scaling = 0.25 local scaledW = math.floor(photo.width\*scaling) local scaledH = math.floor(photo.height\*scaling) -- Rescale, save and remove photo print(" Scaled photo dimensions = (" .. scaledW .. " x " .. scaledH .. ")") photo:scale(scaling, scaling) display.save(photo, "img.jpg", system.TemporaryDirectory) photo:removeSelf() photo = nil -- Show rescaled image loadedImage = display.newImage("img.jpg", system.TemporaryDirectory, \_W\*0.5, \_H\*0.5) print(" Loaded image size = (" .. loadedImage.width .. " x " .. loadedImage.height .. ")") end end local function takePhotoTouchHandler(event) if (event.phase == "ended") then -- Get image from camera if media.hasSource( media.Camera ) then print("Camera detected - starting camera") media.capturePhoto( { listener = itemPhotoOnComplete } ) else native.showAlert("Error", "No camera found", { "OK" } ) end end end print("\n\nApp started") local takePhoto = display.newText("TAKE PHOTO", \_W\*0.5, \_H\*0.9, native.systemFont, 40) takePhoto:addEventListener("touch", takePhotoTouchHandler)

Go ahead and file a bug report.  Make sure it’s a .zip file with a config.lua and build.settings in it.

Rob