Loading UI images for GraphicsContext elements at lightning speed

The Problem

I’ve been creating a UI element with the GraphicsContext dialog, and noticed that loading UI elements from images not included in the theme takes quite a long time since you need to first load it with:

local image = Image{ fromFile=some_image_file }

This really slows things down if you load the images every time the application starts and you need to load more than a couple.

Here’s the UI element I was needing to load images for, it loads 10 images in total:

This would take upwards of 2 seconds each time the application opened using the previous method.

The Solution

I looked for a while for a good solution to this, and eventually came across the ability to load bytes directly into an image using:

local file = io.open(file, "rb")
local bytes = file:read("*all")
image = Image(width, height)
image.bytes = bytes

This is nearly instant, especially compared to loading images from an image file.
The only tricky part is that you need to save the images to bytes first, along with the width and height.

I decided to include the width and height in the file name, but you could just as easily store it in the file itself as a header that you remove before reading the bytes.
Went back and changed the code to do this, since it just makes more sense.

Here is the final code to save images and then later load them super fast from bytes directly:

-- Just some code to find the script execution directory
function script_path()
	local str = debug.getinfo(2, "S").source:sub(2)
	str = str:gsub("\\","/")
	return str:match("(.*/)") or "."
end
local path = script_path()

-- Image saving function, saves bytes to "data/bytes/name.bytes"
function saveImageBytes(image, name)
    local file = io.open(path.."data/editor/bytes/"..name..".bytes", "wb")
    file:write(image.width.."\n"..image.height.."\n"..image.bytes)
    file:close()
end

-- Function to load image from bytes file
function loadImageBytes(file)
    local file = io.open(file, "rb")
    local width = tonumber(file:read("*line"))
    local height = tonumber(file:read("*line"))
	local bytes = file:read("*a")
    image = Image(width, height)
    image.bytes = bytes
    return image
end

And here is how to use these two functions:

-- Save the image as bytes (only needs to be done once for each image)
-- Assumes that the image is in the "data" folder in the extension location, but could point to any file
saveImageBytes(Image{fromFile=path.."data/some_image.png"}, "some_image")

-- Loads the image from the bytes file nearly instantly
local image = loadImageBytes(path.."data/bytes/some_image.bytes")

Hope this helps anyone running into similar issues with the time it takes to load an image from a file.

The only disadvantage is that images must be converted before loading them, and they are larger files than storing something like a png image.

4 Likes