Renders the display object referenced by displayObject into a JPEG image and saves it in filename. The display object must currently be in the display hierarchy; otherwise no image is saved. If the display object is a group object, then all children are rendered. The object to be saved must be on the screen and fully within the screen boundary.
Note: When Dynamic Scaling is enabled, display.save saves the image in the device's native resolution. For example, if this method is used to save a 100 x 200 pixel Display Object, it will be saved as a 100 x 200 image on iPhone3 but it will be a 200 x 400 image on iPhone4. (This assumes that the config.lua file specifies the content width/height as 320x480).
display.newImageRect should be used to load images when Dynamic Scaling is enabled, instead of display.newImage.
display.save( displayObject, filename [, baseDirectory] )
local myObject1 = display.newRect( 50, 50, 100, 150 ) -- Create a rectangle object local myObject2 = display.newCircle( 100, 300, 50 ) -- Create a circle object local g = display.newGroup() g:insert(myObject1) g:insert(myObject2) local baseDir = system.DocumentsDirectory display.save( g, "entireGroup.jpg", baseDir )
displayObject
object: Name of display object to save.
filename
string: Name of the file to save the JPEG.
baseDirectory
system path: Optional. Can be either system.DocumentsDirectory (the default) or system.TemporaryDirectory.
Nothing.
Higher resolution image saved to jpeg file in build #299.
Note1: There is a problem with object captures when the device orientation is different from the initial orientation. There is also a problem capturing the entire object on iPad and iPhone4 devices when Dynamic Scaling (in config.lua) is enabled. [case #928]
Note2: display.save uses the openGL buffer viewport for saving the specified Display Object. When using the Simulator, the size of the saved image will be smaller than expected if the simulator "skin" is not displayed full size. This generally occurs when the simulator skin is displayed on a smaller monitor (e.g., 13") or when Zoomed Out. When the skin is displayed full size, the saved image will match that of the simulated device.
The mask is part of the image which is part of the group so it should be saved along with the image if you are saving the group.
Well, the point of the mask is to only display a selected part of a masked image, not the mask itself, like with the X-Ray code sample.
So in my case, if I save the masked display object I see on screen --which is correctly masked, by the way--the resulting Jpeg file should show exactly that masked display object, right? That is, the part of the original image i ant to display... Well, this is not working for me.
I load a large image and only display a part of it using a mask. The mask does its job perfectly well and I can move the masked image around. I repeat that several times so there's a 3 x 2 grid on screen.
But if I display.save() each masked image, the resulting Jpeg doesn't show the that masked image. It partially shows the first one + a bit of the mask itself, like if maskX and maskY had been altered. The other Jpeg files only contain a black image (so a bit of the mask itself, I guess) and they're not even the dimensions of the image as it was displayed on screen.
I wouldn't dare to say that the mask function is buggy and there's probably a solution to my problem --I'm don't consider myself a real developer, anyway =) But if I made a mistake with the masks, they shouldn't be displayed correctly on screen, should they?
So if a masked image is shown properly masked on the screen, what could prevent saving it "as is"?
Ch.
@Tom and/or @Ansca,
Are there any plans to extend this function (display.save) to allow for a listener to be added?
It appears (in the simulator) to fire off in a separate thread (other?) allowing additional code to be executed regardless of the success/failure/io state of the function call.
In other words, how can I be sure the save is successful before executing addition code?
Sincerely,
Paul Allan Harrington
http://www.pahgroup.com
Paul, display.save() is a blocking call... as it should be since it was designed to capture the screen at the exact point in time it was called. This makes a listener unnecessary.
The only way to recognize if the screen was successfully captured to file is to check for the existence of the file via Lua io.open().
Thanks Joshua Quick.
Does the Corona Simulator "simulate" the "blocking call"?
It appears to me that the Corona Simulator does "not" replicate the "block" I experience on the device.
Thoughts?
Best wishes,
Paul Allan Harrington
http://www.pahgroup.com
Hello Paul,
The simulator definitely blocks during that call. Have a look at one of our sample projects under...
".\Storage\ScreenCaptureToFile"
Notice that in its "main.lua" file that we're displaying the image file just after the display.save() call. This would only work if it was blocking.
You might also want to give display.captureScreen() a try...
http://developer.anscamobile.com/reference/index/displaycapturescreen
I found a strange issue while using this function with the last stable version. The function display.save doesn't save properly the image.
1 2 3 4 5 6 7 8 9 | local sessionComplete = function(event) local g = display.newGroup() local img = event.target g:insert(img) display.save( g, filename, system.DocumentsDirectory ) g:removeSelf( ) return true end media.show( media.Camera, sessionComplete ) |
P.S. I apologize for this post. I didn't saw the last remark, which is probably related with my issue.
Best Regards,
B
Hello Bruno,
The display.save() function acts more like a screen capture, where it only captures the object of your choosing onscreen. This is the intent because it allows developers to capture a group of display objects together. But since it is a screen capture, display.save() can only capture the pixels displayed onscreen and not the parts of the display object that is shown outside of the screen. So, the display object parts that are shown off-screen will come out as black in the capture image, which is what you are seeing.
The picture taken by the camera likely has larger dimensions that your contentWidth/Height set up in your app, so the rest of the image is being displayed off-screen. The only work-around that I can think of in Corona is to scale down the image to fit the app's content width and height and then display.save() the image. It's not a great solution because the camera image will be scaled down, but it will work. That said, the image taken by the camera should exist on the device's Photo Album if that helps any.
I can't think of any other work-around.
The app runs in landscape mode only. That is why it doesn't work. As far as I know, only display.captureScreen saves pictures to device's Photo Album. Is there any temporary fix or idea about how to save the picture when in landscape mode ?
Hello Bruno,
There were bugs in capturing the screen in landscape mode in the release version that we've later fixed in the latest daily builds. Are you a subscriber? If so, then I recommend that you download the newest build. Just remember that the displayed image taken by the camera will likely be larger than your screen, so you'll need to scale it down. Other than that, we don't have any other means to save an image to the Photo Album at the moment.
Can you enable saving to PNG _with_ transparency? Currently you get black background instead.
Saving with transparency would be very useful (among other things) for baking effects. For example, you can write a sparkle effect with Corona very easily (much easier in fact than scripting in Illustrator). And then save individual frames of the effect to use in a sprite sheet. This would be a huge productivity feature!
Please look at this sample app: http://dl.dropbox.com/u/2776279/sparkles.7z
I'd be *super* excited if you added this.
You're not the first to request this feature. It's already on our "wish list".
Unfortunately, I don't see this happening any time soon. At the moment, display.save() works more like a screenshot of just a portion of the screen, where it captures the pixels exactly how you see it onscreen. I can't think of any work-around for what you are asking for at the moment... unless of course you are willing to modify the saved image file and manipulate the color pixels yourself.
Can't you make the frame buffer contain alpha value and use glReadPixels, at least in the simulator?
Yes, but based on our testing, doing a glReadPixels() on an offscreen framebuffer causes a crash on most Android devices even though these devices support offscreen framebuffers. It was just completely unreliable. Because of this, our work-around was to do a glReadPixels() from the main display which you would not normally set the background to alpha, but that would make it work. However, at the moment, I believe we always save these images as 24-bit (no alpha).
Well, hope things will soon improve on Android side then. Thanks for the explanation.
I'm still seeing problems with display.save() working correctly in landscape mode on the iPad and iPhone.
display.save() in portrait mode works exactly as it should; in landscape mode, the display group is saved with the correct size, however the pixel content is squashed vertically and the rest of the image is filled with black.
The bug only occurs on the device itself, not in the simulator.
I filed a bug report about this issue, here is an example code to demonstrate.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | --build.settings settings = { orientation = { default = "landscapeLeft", supported = { "portrait", "landscapeLeft", "landscapeRight", }, }, iphone = { plist= { UIStatusBarHidden=true, }, }, } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | --main.lua --test demonstrating how display.save works (or doesn't) in portrait vs landscape mode --draw on the white area of the screen; display.save should flatten all the graphics into one image local bgFill = display.newRect(0,0, display.contentWidth, display.contentHeight) bgFill:setFillColor( 0, 255, 0) local myLayer = display.newGroup() --drawBoard bg myLayer:setReferencePoint( display.CenterReferencePoint ) local drawBoard = display.newRect( 0, 0, display.contentWidth * 0.5, display.contentHeight * 0.5) drawBoard.x = display.contentWidth * 0.5 drawBoard.y = display.contentHeight * 0.5 myLayer:insert( drawBoard ) local prevPt = {} local tempFileCount = 0 function flattenBG( ) if (myLayer.numChildren > 10 ) then print("flattening background...") --figure out a unique filename tempFileCount = tempFileCount + 1 local filename = "flat" .. tempFileCount .. ".jpg" --save a flattened version of the background local flatBG = display.save( myLayer, filename, system.TemporaryDirectory) --load the saved flattened image as the new background, in the myLayer group local newBG = display.newImage( filename, system.TemporaryDirectory, 0, 0, true) --keep the scale of the screenshot at half-screen, centered newBG.xScale = ((display.contentWidth * 0.5) / newBG.width) newBG.yScale = ((display.contentHeight * 0.5) / newBG.height) newBG.x = display.contentWidth * 0.5 newBG.y = display.contentHeight * 0.5 --add to the front of the display group myLayer:insert(1, newBG) --remove all the other items in the display group for i = myLayer.numChildren, 2, -1 do local child = myLayer[i] child.alpha = 0 child.parent:remove( child ) table.remove(myLayer, i) end --delete the previous background file filename = "flat" .. (tempFileCount - 1) .. ".jpg" os.remove( system.pathForFile( filename, system.TemporaryDirectory ) ) end end function drawLines (event) local chalkColor = {255, 0, 0} local chalkAlpha = 255 local chalkWidth = 6 local e = event local ePhase = event.phase prevPt = { x = e.x, y = e.y } if (ePhase == "began" or ePhase == "moved") then if (e.x < display.contentWidth * 0.73 and e.x > display.contentWidth * 0.26 and e.y < display.contentHeight * 0.73 and e.y > display.contentHeight * 0.26) then local s = display.newRect(myLayer, e.x, e.y , 10, 10 ) s:setFillColor(chalkColor[1], chalkColor[2], chalkColor[3], chalkAlpha) end elseif (ePhase == "ended" or ePhase == "canceled") then flattenBG() prevPt = {} end end Runtime:addEventListener("touch", drawLines) |
Hello Mitch,
This is a known bug that Ansca is currently working on. This is only a bug on iOS devices. Screen captures work correctly on Android and the simulator for OS X and Windows. We're working on getting this fixed by the next release.
Could you please add an optional rectangle parameter to save a selected portion of the screen? I have the similar request for display.captureScreen too.
Joshua,
Is there an update for this issue? If so, which release version contains the fix?
Thanks
Nathan.
BoarK: The last stable version is working fine.
Joshua Quick: Is there any technical reason not to have an option to save the image to Photo album ?
Thanks,
B
Bruno,
I'm using 591 released on Aug 2nd and it's not working in landscape mode (works fine on the simulator not device). This issue was mentioned in the release notes as not working on both iOs and Android as of 591. Are you using a different version?
Nathan.
BoarK: Sorry I was wrong about the stable version. You have to download daily build 2011.600
Best Regards,
B
Bruno,
Thanks for the info.
Nathan.
Hello everyone,
The newest daily builds, starting with 600, fixes the display.save() issues. Unfortunately the fixes didn't make it into the release version, so you have to be a subscriber to get these changes.
Bruno, we would have to change the API a little bit to support saving to the photo library by doing away with the path\file name argument. This is because there is no path to the photo library on iOS and Android… and you have to be very careful about file naming to avoid overwriting the user's existing photos. The display.captureScreen() supports saving to the photo library, but it also auto-names the file on you. On iOS, it numbers the file and auto-increments it in case of file name collision. Android doesn't automatically number files "yet" and just overwrites the file.
There is definitely room for improvement in our screen capture APIs. All in due time. :)
Is there any difference in how sprites and regular bitmaps are rendered that would cause sprites not to be captured by display.save?
mateosolares,
There is no difference. display.save() will capture sprites exactly how they are displayed onscreen.
This is how it works. We render the object you pass into display.save() and its children objects (if any) into OpenGL and then we copy the pixels from the OpenGL surface to the bitmap. This also means alpha and tint settings are applied to the object(s) too and saved to the bitmap. So, what you see onscreen is what you get.
The only limitation is that display.save() cannot capture native input fields such as TextField, TextBox, WebPopup, etc. This is because these native fields are over-layed on top of Corona's OpenGL surface. Since we don't render these fields ourselves, they don't get captured.
I have noticed that display.save() will not overwrite an existing file. Is there any way to change this behavior or to remove the file in question before using display.save()?
display.save() is supposed to overwrite files. This is definitely true of Android. Are you seeing this issue on iOS? You might also want to play with sample app "Storage\ScreenCaptureToFile" which uses display.save().
Thanks Joshua,
I always ask myself why Ansca don't put enough parameters to let everybody have more options and more control over the SDK methods.
For example for this method, display.save(), we may have an option in the method to either use or not use overwriting files. What do you think?
Everyone,
The display.save() function is "supposed" to overwrite files. If it is not doing so, then please report it as a bug with a Lua script to reproduce the issue and indicate what platform this is happening on. Based on my testing on Windows and Android, it "is" overwriting files.
Is it possible to upload an image of a screen capture to a website? I am able to upload an image by giving the path if its in the same directory, but I am running in to difficulties now as I am not able to access the image taken from within the App, I have looked into the display.save api but it looking like this may not work either as how do I target the image once its saved in the DocumentsDirecotory?
Hi Ansca, the example code you provided does not work for me on build 657, it was working before but does not anymore. Am trying to download 671 but download was very slow for some reason just now. Thanks.
Oh boy... installed the 671, ran your example code and screen goes blank with this error in the console:
Corona Simulator[1935:4603] glError at end unbindRenderFBO
Please help Ansca! It's urgent for me... thank you.
Everyone,
In regards to display.save() not overwriting files, I have confirmed that it is in fact overwriting files. This is not a bug. What is actually happening is that Corona "caches" loaded image files into memory. So if you do a display.newImage() on the same image file multiple times, Corona will only access that file once and then re-use that image in memory for all subsequent calls for the same image file. This is by design and greatly improves performance.
So, if you want to display a different image onscreen every time you do a display.save(), then you'll need to change the file name. I recommend that you append a timestamp to the end of the filename. That will work-around this issue.
josh_pixel,
Regarding uploading image files with Corona, there isn't a simple way to do this, but it is possible. Have a look at the Code Exchange for some samples on how others perform this operation. Here are some links...
http://developer.anscamobile.com/code/upload-binary-corona-php-script
http://developer.anscamobile.com/code/how-upload-image-server-multipartform-data
If you have further questions on this topic, then I recommend that you start a new thread in our forums. This way we can keep this web page focused on the display.save() API topic. Thanks!
Hi Joshua,
The sample code provided above for display.save() works on build 638, fails on 639 onwards without error in the console. Problem is after saving, the image disappears. On build 671, an error shows up in the console saying:
Corona Simulator[1935:4603] glError at end unbindRenderFBO
Hope you can fix this asap... pretty please?
Simon,
This is an issue with the latest changes in our Corona Simulator for Mac. We've added some cool new features such as the ability to display native fields within the simulator, but it looks like we broke something along the way. I know one of our Mac developers is looking into this. To help expedite the matter, please click the "Report a Bug" link at the top of this web page. That will get this issue into our queue and make the issue visible to the rest of the team. Thanks!
Thanks Joshua, I kinda need the native field in the simulator too which is why I installed recent builds, but I guess for now I'll have to revert to build 638. Anyway, bug has been reported, thanks.
I think we have a fix for the black screen, but the change may not get pushed into the daily builds until next week because it is tied to a bunch of other changes that are not quite done yet.
Thanks Ewing, noted. I'll keep an eye on the daily builds.
Thanks Ewing, noted. I'll keep an eye on the daily builds.
Figured it out!
Any news about save a display object (group or sprite) into a PNG file with alpha value?.
As to the comments asking for a rectangle parameter...
This method does not capture the contents of the object/group you supply. What it does is capture whatever is on the screen using the bounding box of the object/group you supply. So you can just pass in the coordinates of a blank rectangle that defines the region you want to capture.
For example, I have a "camera" function to take a preview image of a level in my level builder, which does something like:
local camera = display.newRect(40, 40, 200, 200)
camera:setFillColor(0,0,0,0)
display.save(camera, "preview.png", system.DocumentsDirectory)
This works great (it captures what's inside the invisible rectangle "camera"). In my app I actually make a camera group with a drag handle and "take picture" button, and it has the "camera" invisible rectangle in the middle (but that was a little more code than would fit here).
And by the way ANSCA, if you ever do "fix" this to actually capture the display object passed in (perhaps offscreen), please don't break the current screen capture functionality (allowing the capture of a region of the screen to a file). I for one am relying on it.
Ansca - Can we get an "object.save" function - 1 vote
Re. Black background fix, assuming you were referring to eliminating the unwanted black background when using display.save, do you have an update on the black screen fix? I have checked today's build and seems the black background from display screen still persists in saved images.
In general, I would prefer to have an option to disable black background during a screen capture using display.save if that is possible. I am needing to save non-rectangular shaped objects and the black background is not part of the object so alpha=0 is required for this space.
Alternatively, can we get an object.save(object,filename,[directory],,,) function similar to display.save but such that it only saves the objects and not the black background? png&jpeg formats would be nice.
Hmm. Nevermind. The iOS and Android implementations of this method appear to be very different. The method above (using a transparent rectangle to grab a region of the screen) doesn't appear to work at all on iOS (you just get a black rectangle). I've tried several different ways to "trick" Corona into capturing a region of the screen in iOS with no success. Unless I'm missing something, I don't think I'm going to be able to make my app work - I need to select an area of a level (a group) and take a snapshot to use as a preview. It seems like anyone wanting to do something as simple as cropping an image would run into the same problem.
So yes, some parameters to specify the rect that you want saved would be very useful (making iOS work like Android would also be fine).
I'm not really sure how saving a specific display object is all that useful (even a group, since I can't specify its bounds).
I found a workaround for my need to save irregular shaped objects without the unwanted black background. I simply use masking after first saving the rectangular object using display.save. I still cannot save the irregular shaped objects to file, but I don't need to in the application, only manipulate the irregular shaped object without the black background. Thanks Ansca for creating the masking functionality. I guess the need for an object.save is only relevant if one needs to save as a file. My comment is now closed.
Where on a iOS device would the files from a Display.Save reside ?
Dave
I know there were some issues with the mask functionality when it was introduced, but is it still the case with build 484?
When I use display.save() to save a group containing a masked image, portions of the mask are saved as well in the Jpeg file.
Ch.