Uploading and Manipulating Images in Pyscript
A curious dev on the PyScript Discord (which you should really come check out) asked:
I am taking file input in HTML where I am selecting image, how to show image when submit button is hit in PyScript?
Actually, I need to use that file in PyScript to process. How can I do that?
For those looking to skip to the punchline - here's a working demo. We'll show off both the ability to upload and display images, as well as manipulating them with the Pillow image manipulation library:
If all has gone to plan, images uploaded in the first dialog should just appear onscreen full-size; images uploaded in the second dialog should appear below the upload dialog, having been (1) "embossed", (2) rotated 45 degrees, (3) had any empty space filled with a dark green background, and (4) been rescaled to 300x300 pixels
Simple Image Upload and Display
The HTML portion of this project is very straightforward - an input with
type=file and an ID to refer to it by, as well as an empty div for us to shove output in later:
The Pyscript portion of this example is only slightly more involved. We use
addEventListener() to trigger a function when the selected file in the input field changes. Then we get the file targetted by that input, and create a temporary URL for it using
window.URL.createObjectURL(). Finally, we create a new
<img> tag and stick it inside our output div.
If desired, this functionality could be trigged by submitting a form, clicking a separate "Process Image" button, or any other event. This demo just slaps the image up as soon as its chosen, for brevity of example.
Image Processing with PILLOW
Things get slightly more involved if we want to use the Python Image Library (or its kinder wrapper, PILLOW) to work with the images. The HTML looks almost identical, but we do need to add Pillow to a new
<py-env> tag so that micropip will install Pillow into our environment for us.
However, the Pyscript in this case is somewhat more involved, getting the bytes back and forth from Pyscript and the browser in formats they like. Full caveat - through testing, I think all of these castings and conversions are necessary for this to work, but if anyone finds a shorter way, please let me know!
That said, to load the image data into Pillow, we:
- Get the raw bytes of data from the image using
- Cast that data into a
bytearrayand then as an
io.BytesIOobject, which is an in-memory object that behaves as a file-like object for IO purposes.
- Load that BytesIO object into Pillow using
Once we have the image loaded, we can do all of our usual Pillow-based adjustments to it - in this case, I'm having it filter, rotate, fill, and resize the image using a succession of operations.
Finally, to retrieve the data in a format that we can use in the DOM, we:
- Create another
BytesIOfile-link object, and use
Image.save()to write our image out to it.
- Create a new
Fileobject containing the bytes of our image, with a placeholder name and a MIME type of
- Create an URL we can use for this File using
- Use that URL as the src of a new img tag (made with
document.createElement()) and append that as a child of our div.