Best way to upload an image?

I have a whole series of apps that work together with web-based server code to carry out a suite of business and engineering functions. You can see them at UtilityInField.com.

Some of these apps have now been rewritten in Objective-C (Swift coming soon) but every one of them started out as a Codea app. I still use Codea to prototype each app because I have frameworks (based off the ancient Cider library) that let me throw idea together quickly, I can make fast changes in the field, and Codea is just such a joy to use. Building an app in XCode with its million moving parts never stops feeling like work. Prototyping in Codea … that’s the best kind of play.

So now, the question: Is there a good way to send an image in Codea? I don’t care where it goes. I’ve tried writing a service to catch it, but a normal json or SOAP API means converting the image to a string, and that alone takes too long to be practical. The image save to Dropbox would be fine, because I could just drop the file name to my own API to let it know where to find the image. But it seems to not sync until the image control is opened outside the app.

Ultimately this app (a new version of a safety checklist app) will be in swift, and I’ll be dropping the images on a secure site with a link stored in the database. But right now, I don’t care where or how the prototype stores an image, just that it does it quickly and cleanly within the app.

So … what’s the best solution? Thanks. (Note: I’m not asking about getting an image. Good old HTTP request does that all day. Just the best way to upload.)

I’m not sure I understand you, but you can do to your image whatever you want.

“Stringifying” an image does not take that long, so don’t worry.

You could encode the image by Base64 (mime.b64) into a String and store that String into a persistent database (e.g. mysql) or send it to another device or upload it to an ftp server. You can do all that with Lua Sockets. - Or you could simply save the image to your Dropbox and access from anywhere.

You have to be more specific about what you try to do with the image…

PS: When I get home, I’ll drop you some code example for encoding an image into a Base64 string, which even browsers can understand and render without any additional changes.

@Mark Not sure exactly what you need, but here’s an example of loading/saving an image. More code needs to be added depending on what you need.

displayMode(FULLSCREEN)

function setup()
    getImg()
end

function draw()
    background(40, 40, 50)
    fill(255)
    if img~=nil then
        sprite(img,WIDTH/2,HEIGHT/2,700)
        text("image saved",WIDTH/2,200)
    else
        fill(255)
        text("Loading image",WIDTH/2,HEIGHT/2)
    end
end

function getImg()
    http.request('https://dl.dropboxusercontent.com/s/3149mj9xjx1w71e/Costa%20Rican%20Frog.jpg',gotImage)
end

function gotImage(image,status,header)
    img=image
    saveImage("Dropbox:img1",img)
end

@dave1707
That saveImage statement doesn’t seem to actually send anything to Dropbox unless I later open the image selector and sync with dropbox. It appears to just queue it up to go to Dropbox. So in terms of being written into an app you want to hand to someone for testing, it has limited utility.

I’m not sure what you mean about the base64 code. Which is why I’m asking.

The only example I could find of base64 encoding in Codea took conversion to a string. If I iterate an image, even a small image, using Image:get(x,y) to grab the contents of pixels and convert the whole image to a string, as per old examples of encoding images, that encoding takes a LONG time. The longer the string, the more the whole thing seems to bog. Even a 540 x 960 image, half scale on my aging iPad, took more than two minutes to encode.

@Mark No. Dave was right. His code does download an image from Dropbox and saves it again into Dropbox. But Codea has its own “cache” of the Dropbox folder, so you won’t see until you sync.

Here’ s the code snippet I promised.

It takes an image and encodes it. Now you could upload that string somewhere. Use http or sockets. When you need it back, download the string again and decode back to image data. Then simply display.

That base64 encoded string also runs in every web browser out of the box with <img src="...put_the_string_in_here..."/>

(Main tab)

-- base64 image data
--
-- This way you can save the hash into remote and persistent database (mysql)
-- later read back out and display it.. kinda as shown here..
--
-- One might also backup with git or upload to fpt server
-- Another use case is for obscuring lua code or even have it download from db and run in background

function setup()
    local mime = require("mime")
    local file_content, mime_type = lfs.read(lfs.DROPBOX.."/image.jpg")
    
    print(mime_type)
    --print(file_content)
    
    local base64_hash = mime.b64(file_content) -- convert to base64 format
    
    print("data:"..mime_type..";base64,"..base64_hash) -- format ready to use on web <img src="...">
    
    local byte_hash = mime.unb64(base64_hash) -- translate back to bytecode
    img = image(file_content)
end

function draw()
    noSmooth()
    background(40, 40, 50)
    translate(WIDTH/2, HEIGHT/2)
    scale(4)
    sprite(img)
end

This is the library I use to directly read file contents…
(lfs tab)

lfs = {}

lfs.ENV = os.getenv("HOME")
lfs.DOCUMENTS = "Documents"
lfs.DROPBOX = lfs.DOCUMENTS.."/Dropbox.assets"

local MIME = {
    [".text"] = "text/plain",
    [".txt"] = "text/plain",
    [".md"] = "text/markdown",
    [".markdown"] = "text/markdown",
    [".lua"] = "text/x-lua",
    [".luac"] = "application/x-lua-bytecode",
    [".pdf"] = "application/pdf",
    [".jpeg"] = "image/jpeg",
    [".jpg"] = "image/jpeg",
    [".gif"] = "image/gif",
    [".png"] = "image/png",
    [".tiff"] = "image/tiff",
    [".html"] = "text/html",
    [".htm"] = "text/html",
    [".css"] = "text/html",
    [".js"] = "application/javascript",
    [".json"] = "application/json",
}

local function breadcrumbs(path)
    return path:gsub(":", "/"):match("(.+)/(.+)(%.[^.]+)$")
end

function lfs.read(file)
    local DIR, FILE, EXT = breadcrumbs(file)
    local data = io.open(string.format("%s/%s/%s", lfs.ENV, DIR, FILE..EXT), "r")
    
    if data then
        local content = data:read("*all")
        data:close()
        return content, MIME[EXT]
    end
    
    return false
end

function lfs.write(file, content)
    local DIR, FILE, EXT = breadcrumbs(file)
    local data = io.open(string.format("%s/%s/%s", lfs.ENV, DIR, FILE..EXT), "w")
    
    if data then
        wFd:write(td)
        wFd:close()
        return true
    end
    
    return false
end

-- also an example how to read sequentially
function lfs.read_binary(file)
    local DIR, FILE, EXT = breadcrumbs(file)
    local data = io.open(string.format("%s/%s/%s", lfs.ENV, DIR, FILE..EXT), "rb")
    
    if data then
        local chunks = 512
        local content = ""
        
        while true do
            local bytes = data:read(chunks) -- Read only n bytes per iteration
            if not bytes then break end
            content = content..bytes
            break
        end
        
        data:close()
        
        return content, MIME[EXT]
    end
    
    return false
end

function lfs.write_binary(file, content)
    local DIR, FILE, EXT = breadcrumbs(file)
    local data = io.open(string.format("%s/%s/%s", lfs.ENV, DIR, FILE..EXT), "wb")
    
    if data then
        data:write(content) -- You could do it in parts, but oh.
        data:close()
        return true
    end
    
    return false
end

@Mark If you want give someone an app to test, use the code I show above in your app to read an image into that app the first time. The next time the app is opened, the image will be there and won’t need to be downloaded again.

@Mark Here’s an example of what I said above. You put your images somewhere where they can be read by whoever. In your program you check if the image is already loaded in the Dropbox folder. If it’s not there, you download the image and save it in the Dropbox folder for the next time you need it.

displayMode(FULLSCREEN)

function setup()
    frog=readImage("Dropbox:frog")  -- load image
    if frog==nill then
        getImg()    -- if frog doesnt exist, download it
    end
end

function draw()
    background(40, 40, 50)
    fill(255)
    if frog~=nil then
        sprite(frog,WIDTH/2,HEIGHT/2,700)
    else
        text("Loading image",WIDTH/2,HEIGHT/2)
    end
end

function getImg()
    http.request('https://dl.dropboxusercontent.com/s/3149mj9xjx1w71e/Costa%20Rican%20Frog.jpg',gotImage)
end

function gotImage(image,status,header)
    saveImage("Dropbox:frog",image)  -- save frog image in Dropbox folder
    frog=image  -- load frog with the image to use
end

Thanks, @dave1707 and @se24vad. The mime library seems to give me exactly what I needed. This was one of those things that looked easy on the Swift side, where a conversion from binary to base64EncodedString is a given, but I didn’t see the obvious solution on the Codea end.

Much appreciated.

Coming back to this after a long pause to work on another app and finding I’m still banging my head on this issue.

The conversation to base 64 is exactly what I need. I’ve coded a web service to receive images as base 64 strings, I can hit it from Swift or C all day, and the backend is properly storing images in the DB where I need them.

But when I try to pass an image to the mime.b64 above, I get an error indicating the mime method can’t handle a Codea image, but expects a string. Which takes me right back where I was. I end up having to iterate the whole image, at a huge performance hit.

I take an image from the camera and… I’m still stuck with no clear way from binary to string.

I can’t go to Dropbox. The info has to be saved in the secure DB.

@Mark have you seen my “save to camera roll” attempt? It was inspired by this thread. It gets the codea image, saves it with saveImage, then reads the raw data back with io. It’s no good for me, but it may be of some use to you.

Thanks. Off to look…

And hey, what do you know. After considerable blundering around, I come to this …

        -- Save image
        saveImage("Documents:temp", img)
        -- Get io pointer
        local path = os.getenv("HOME").."/Documents/temp.png"
        local file = io.open(path,"rb")
        -- Read binary contents
        local contents = file:read("*all")
        file:close()
        -- convert to base 64 string
        local base64 = mime.b64(contents)

Which, paired with the mime library, seems to do the trick. The only warning is that not only do large images slow down transmission, they tend to generate 500 internal server errors. Keep the images smaller, and things seem to work well.

Here’s a fast base64 library ripped straight from lua-users wiki.

local bs = { [0] =
   'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
   'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
   'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
   'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
}

local function base64(s)
   local byte, rep = string.byte, string.rep
   local pad = 2 - ((#s-1) % 3)
   s = (s..rep('\\0', pad)):gsub("...", function(cs)
      local a, b, c = byte(cs, 1, 3)
      return bs[a>>2] .. bs[(a&3)<<4|b>>4] .. bs[(b&15)<<2|c>>6] .. bs[c&63]
   end)
   return s:sub(1, #s-pad) .. rep('=', pad)
end

I found that slow encoding slows down everything. Also, maybe it will help if you read the file in chunks, like what @Jmv38 showed above? Images are binary, so that should work. I hoped my suggestions helped.

It looks like the whole thing can be brought down to …

function stringToBase64(s)
    local bs = { [0] =
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
    }
    local byte, rep = string.byte, string.rep
    local pad = 2 - ((#s-1) % 3)
    s = (s..rep('\\0', pad)):gsub("...", function(cs)
        local a, b, c = byte(cs, 1, 3)
        return bs[a>>2] .. bs[(a&3)<<4|b>>4] .. bs[(b&15)<<2|c>>6] .. bs[c&63]
    end)
    return s:sub(1, #s-pad) .. rep('=', pad)
end
 
function imageToBase64(img)
    -- Save image
    saveImage("Documents:tempImage", img)
    -- Get io pointer
    local path = os.getenv("HOME").."/Documents/tempImage.png"
    local file = io.open(path,"rb")
    -- read binary contents
    local contents = file:read("*all")
    file:close()
    -- convert to base 64 string
    local base64 = stringToBase64(contents)
    return base64 
end

Cool! This should be put on the wiki.

One more thing: saveImage also produces a @2x version of the image, and both it and the normal version should be deleted afterward.

Unfortunately, with the latest version of Codea and iOS, this is no longer working. While I can save the image file without an issue, this line of code:

 local path = os.getenv("HOME").."/Documents/tempImage.png"

Is no longer returning the image. I’m not sure where the file has moved, but I’m searching for an example that shows me how to change this.