P5js + Typescript: Load image from file on datgui button click

Date: 2020-07-20 | p5js | typescript | datgui | images |

problem

I do a lot of personal coding projects using images. I often use datgui as a quick, easy way to parameterize these projects. When you put them together, that also means that I'm often dealing with file inputs and like to put buttons for that in the gui so it's all in one place and out of the way.

I'm going to break down how I load files into my P5js + Typescript projects when a user clicks a button in datgui.

solution

I'm using parcel to package up my code, so your import code may look a bit different depending on your build process. The p5js sketch itself is in instance mode though so everything else should look almost exactly the same. If you're using vanilla javascript instead of Typescript, that's cool too - I think the only things that would change are the imports and the removal of types.

At a high level, here's what's going on:

  • On sketch instantiation, we create a params object to store the values of the parameters we'll feed into dat.gui. It looks a little unnecessary with only one dat.gui option, but this is a very clean approach as you add in more options. We set the loadSourceImage param to click the hidden input element we'll create a bit later.
  • We create some vars to store references to our gui object, hidden input element, and the actual image we're going to be loading for later use
  • At the start of the sketch, we create the canvas, set the frame rate and angle mode, create a hidden input element (as dat.gui will serve as our button so no need for this), then add our dat.gui params to the canvas
  • Now on click of the button in dat.gui, we're going to trigger a click of the hidden source element (which will pop up the file picker), then handle the return value of the file picker by loading the files into blobs, and then loading those blobs into p5js. I've added console logs so you can see each step of the process.

The code:

import * as dat from 'dat.gui'
import P5 from "p5"

export const sketch = (sketch: P5) => {
    let params = {
        loadSourceImage: () => {
            console.log('hamy - source image')
            sourceInputElement.click()
        },
    }

    let gui: any
    let sourceInputElement: HTMLInputElement
    let sourceP5Image: P5.Image = null

    sketch.setup = () => {
        sketch.createCanvas(sketch.windowWidth, sketch.windowHeight)
        sketch.frameRate(30)
        sketch.angleMode(sketch.DEGREES)

        sourceInputElement = createHiddenFileInputElement()
        sourceInputElement.onchange = handleSourceInputElement

        gui = new dat.GUI()
        gui.add(params, 'loadSourceImage').name('Load source')
    }

    sketch.draw = () => {
        sketch.background(0)
    } 

    const handleSourceInputElement = (event) => {
        let fileUrls = getLocalUrlsFromFileInput(event)
        sourceP5Image = sketch.loadImage(
            fileUrls[0],
            img => {
                console.log('hamy - image finished loading')
            }
        )
    }

    const getLocalUrlsFromFileInput = (event) => {
        console.log('hamy - file input!')
        let files = event.target.files
        let localUrls: Array<string> = []
        console.log(files)

        for(let file of files) {
            let localUrl = URL.createObjectURL(file)
            localUrls.push(localUrl)

            console.log('hamy - loading file')
        }

        return localUrls
    }

    const createHiddenFileInputElement = (): HTMLInputElement => {
        let inputElement = document.createElement('input')
        inputElement.type = 'file'
        inputElement.style.visibility = 'hidden'
        inputElement.setAttribute('multiple', 'true')
        document.body.appendChild(inputElement)

        return inputElement
    }
}

Happy coding!

Want more like this?

The best / easiest way to support my work is by subscribing for future updates and sharing with your network.