How to use display.save() to save display object as image to arbitrary directory on desktop builds (simple guide / pointers)

Just decided to share my solution to the problem I had, that per default display.save() can write image files only to the system directories, because of app sandboxing. My task was to create a button (newRect) with text (newText) on it, and then save it to my desktop (or any arbitrary directory) as a PNG file, created from the button display group. I almost gave up, thinking it impossible, but in the end managed to do it (yay!).
(I am using macOS, but the technique should work on Windows as well):

used resources:

1.tinyfiledialogs plugin - to get user-friendly directory path selector
2. this guide - the part about “Copying Files to Subfolders”, with some modifications to the code

In a nutshell - use display.save() to save your display object (in my case the display group containing the rectangle and the text object on it) to the temporary directory ( system.TemporaryDirectory), then use tinyfiledialogs to get the string of the path you actually need, and the use the code from the guide (with the modifications below) to copy the image from the temp directory to the one you actually want.

The modified code, for copying files from the sandbox to wherever you want:

Check if the file in the system directories (sandbox) exists:

local function doesFileExistSB( fname, path )

 local results = false

-- Path for the file
 local filePath = system.pathForFile( fname, path )
 print("file path is "..filePath)
 if ( filePath ) then
    local file, errorString = io.open( filePath, "r" )

    if not file then
        -- Error occurred; output the cause
        print( "File error: " .. errorString )
    else
        -- File exists!
        print( "File found: " .. fname )
        results = true
        -- Close the file handle
        file:close()
    end
 end

 return results
end

Check if the file exists outside the sandbox:

local function doesFileExist( fname, path )

local results = false

-- Path for the file
local filePath = path..fname
print("file path is "..filePath)
if ( filePath ) then
    local file, errorString = io.open( filePath, "r" )

    if not file then
        -- Error occurred; output the cause
        print( "File error: " .. errorString )
    else
        -- File exists!
        print( "File found: " .. fname )
        results = true
        -- Close the file handle
        file:close()
    end
end

return results
end

copy from the sandbox to the directory you want:

M.copyFile = function( srcName, srcPath, dstName, dstPath, overwrite )

local results = false

local fileExists = doesFileExistSB( srcName, srcPath )
if ( fileExists == false ) then
    return nil  -- nil = Source file not found
end

-- Check to see if destination file already exists
if not ( overwrite ) then
    if ( doesFileExist( dstName, dstPath ) ) then
        return 1  -- 1 = File already exists (don't overwrite)
    end
end

-- Copy the source file to the destination file
local rFilePath = system.pathForFile( srcName, srcPath )
local wFilePath = dstPath..dstName

local rfh = io.open( rFilePath, "rb" )
local wfh, errorString = io.open( wFilePath, "wb" )

if not ( wfh ) then
    -- Error occurred; output the cause
    print( "File error: " .. errorString )
    return false
else
    -- Read the file and write to the destination directory
    local data = rfh:read( "*a" )
    if not ( data ) then
        print( "Read error!" )
        return false
    else
        if not ( wfh:write( data ) ) then
            print( "Write error!" )
            return false
        end
    end
end

results = 2  -- 2 = File copied successfully!

-- Close file handles
rfh:close()
wfh:close()

return results
end

A thing to be careful about:
If you build your app for the mac app store, and submit it to Apple, you probably need to set its entitlements in the build.settings file, so it gets access to directories outside its sandbox, probably like this:

osx = {
    entitlements = {
        ["com.apple.security.assets.pictures.read-write"] = true, -- get access to user's Pictures folder
        ["com.apple.security.files.user-selected.read-write"] = true, -- get arbitrary access?
    },
},

More info about entitlements here.

And that’s it. I hope this can be useful to someone. Cheers!

4 Likes