Skip to content
Tags

62. 3D – Downloading images on demand

May 23, 2013

If you build a 3D scene and want to share it, you will also need to share all the images. How do we make that as easy as possible? Seeing as I’m about to start using images in these posts, it’s a good time to do something about it, to make it easy for you as well.

First, this has absolutely nothing to do with 3D modelling, and is really about Lua programming. You don’t need to read it if it doesn’t interest you – all you need to know is that any images required for these posts will download and save themselves automatically when you run the projects for the first time.

You may also notice that the setup function in future posts seems to only have one line in it, and all the code is now in a function called setup2. That’s because of issues with downloading images from the internet, and you don’t need to worry about it. Again, it has nothing to do with 3D.

Anyway – if you’re still with me – I thought I should explain what I did.

What I wanted to do is

  • automatically download (and save) images that aren’t stored already
  • make it easy to use this in different projects

.
The problem

It’s easy enough to try loading an image, and if you get nil, you know you need to download it.

But if you download images with http.request, it doesn’t happen straight away. In fact, Codea basically says, “I’ll let you know when it’s done, give me the name of a function to run when that happens”.

So if you want to load an image and then use it in a mesh, you have to tell your code to wait while it is being downloaded, before creating the mesh. You have to prevent any drawing, too.

How?

We need to do something like this

  • set a variable that tells draw to wait
  • try to load the images
  • download them if we need to
  • when we’re done, finish setting up
  • then tell draw it’s ok to start drawing

.

First, we’ll take all the code out of setup and put it in a function called setup2, because we don’t want it to run until the images are loaded. So setup will contain just one line of code

function setup()
    LoadImages() --do this first, on its own
end

Now the function that tries to load the images. It starts by setting a variable, imageStatus, to “Ready”, which means it’s ok to keep setting up and to start drawing. Then it tries to load all the images we will need in the project. If any of them need to be downloaded, imageStatus will change to “loading”, preventing anything else from happening until that is done.

function LoadImages()
    imageStatus='Ready' --tells draw it's ok to draw the scene (will be turned off if we have to download images)
    output.clear()
    --pass through Codea name of image and internet url
    --not in Codea, will be downloaded and saved
    img1=LoadImage('Dropbox:3D-tree',
            'http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-tree27c_zpsd56f4f5f.png')
    img2=LoadImage('SpaceCute:Background')
    img3=LoadImage('Dropbox:3D-wall',
            'http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-stonewall1_zps875ba0f0.png')
    if imageStatus=='Ready' then setup2() end
end

Below are the functions that do all the work. I’ll explain them underneath.

--downloads images one by one
function LoadImage(fileName,url)
        local i=readImage(fileName)
        if i~=nil then return i end
        --not found, we need to download, add to queue (ie table)
        if imageTable==nil then imageTable={} end
        imageTable[#imageTable+1]={name=fileName,url=url}
        print('Queueing',fileName)
        imageStatus='Loading'
        --if the first one, go ahead and download
        if #imageTable==1 then
            http.request(imageTable[1].url,ImageDownloaded)
            print('loading',imageTable[1].name)
        end
end

--saves downloaded images
function ImageDownloaded(img)
    print(imageTable[1].name,'loaded')
    saveImage(imageTable[1].name,img)  --save
    table.remove(imageTable,1)
    --load next one if we have any more to do
    if #imageTable>0 then
        http.request(imageTable[1].url,ImageDownloaded)
        print('loading',imageTable[1].name)
    else
        LoadImages()
    end
end

function

Perhaps the easiest way to see how this works, is to walk through it as it runs.

So let’s assume we’re trying to load the first image, called “Dropbox:3D-tree”. We call LoadImage, giving it this name, plus the internet url in case it needs to be downloaded.

LoadImage tries to open the image, and if the result is not nil, it worked, and LoadImage returns the image and exits.

If the result was nil, LoadImage stores the name and url in a table. The reason for this is that we only want to download one image at a time, but while we are waiting for that to happen, the code will keep running, and any other image requests will pile up. So we put them in a queue (ie table) to wait their turn.

If this is the first download request, we can go ahead and download it, telling Codea to run LoadImage again when the image is ready. Note we also set imageStatus to “loading”, to stop setup or draw from doing anything.

We’re done with that image for now, and we exit LoadImage, back to LoadImages, where we will ask for the next image, and if it’s missing, we’ll queue it in the table, and so on. At the end of LoadImages, we run setup2 only if imageStatus = “Ready”, to stop setup or draw from doing anything while images are downloading. The draw function also checks that imageStatus=”Ready” before it draws anything.

So if we already have all the images, we will successfully load them all, get to the bottom of LoadImages, run setup2 and then start drawing.

But if we have to download anything, we’ll store their details in a table, start downloading the first one, and stop setup or draw from doing anything.

So what happens when the first image is downloaded? We’ve told Codea to run ImageDownloaded when this happens, and it will provide 3 parameters – the image, plus two other items we don’t care about.

In this case, it saves the file to our library, deletes the first entry from our queue table, and if the table is not empty, it starts downloading the next one. When this image is downloaded, ImageDownloaded will run again, save the image and delete the table entry, and so on, until the table is empty. When it is empty, ImageDownloaded runs the original LoadImages function again, and this time all the images should load, and the program will go on to setup and draw.

Full demo code is here. I’ll be using this in future posts to save you the trouble of downloading images all the time. And you are welcome to use it yourself, or improve it on it if you can.

From → 3D, Graphics

One Comment

Trackbacks & Pingbacks

  1. 150. Where to store downloadable images | coolcodea

Leave a comment