Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Best way to upload an image?

in Questions Posts: 1,207

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.)

Tagged:

Comments

  • edited June 26 Posts: 449

    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.

  • dave1707dave1707 Mod
    Posts: 6,107

    @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 Rican Frog.jpg',gotImage)
    end
    
    function gotImage(image,status,header)
        img=image
        saveImage("Dropbox:img1",img)
    end
    
  • Posts: 1,207

    @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.

  • edited June 26 Posts: 449

    @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
    
  • dave1707dave1707 Mod
    Posts: 6,107

    @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.

  • dave1707dave1707 Mod
    Posts: 6,107

    @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 Rican Frog.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
    
  • Posts: 1,207

    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.

  • Posts: 1,207

    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.

  • em2em2
    edited September 14 Posts: 92

    @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.

  • Posts: 1,207

    Thanks. Off to look...

  • Posts: 1,207

    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.

  • em2em2
    edited September 15 Posts: 92

    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.

  • Posts: 1,207

    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
    
  • em2em2
    Posts: 92

    Cool! This should be put on the wiki.

  • em2em2
    Posts: 92

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

Sign In or Register to comment.