
andrewicus.neocities.org/p5-photo
For my Nature of Code final project, I chose to extend my previous cellular automata project to create a web-based photo editor application.
The development of this project was a long and winding road! I will do my best to detail the highlights here.
Beginnings
I began my photoshop journey by first establishing the visual style for the user interface. I was originally torn between a pixelated and vintage look or a more modern and clean design. After talking with a few friends, I decided the retro style would be a departure from what I normally work on and would present a fun challenge. Knowing this, I began to do some research and settled upon the 1980s Macintosh interface, designed by Susan Kare. I used the assets found on her website (https://kare.com/apple-icons/) and a fan-made recreation of her Chicago font (https://fontsarena.com/sysfont-by-alina-sava/). Bringing all of this into Figma, I began tracing icons and laying out the webpage.



Having a decent amount of experience with HTML and CSS allowed me to build the actual webpage in a relatively short amount of time. Now came the more difficult part, writing the p5!
p5 Foundations
I started the p5 development, as I do with my other projects, by building an MVP. I knew I wanted the user to be able to upload multiple images and move and stretch those multiple images around the canvas. Using this code as a base, the uploading functionality came quickly.

Now to make things draggable, I made an uploadedImg class so that each image can have its own x and y values. This sketch was instrumental in figuring out how to properly offset the image so that no matter where the user grabs it, it moves in relation to that specific point.

You may notice from the gif, however, that images on top of each other are being grabbed and moved at the same time. In this early version, there was no layering, so as long as the mouse is in the bounds of the x,y values of the image, it thinks it is being selected. More on this later!
I brought this basic functionality into my webpage and things were coming together! Now it was time to implement image resizing. This took a bit of playing around to get right and figure out the proper mouse offset calculations. One interesting thing I discovered was that using the scale() function resamples the image. So, as you resize it, the image gets compressed and blurry.

To fix this, I just changed width and height values of the image directly. This took a little work to get done because image loading occurs asynchronously. So, when creating the image, it was impossible to know what the width and height values were until it was loaded. It took me a while, but I learned how to use callback functions to properly instantiate image width and height once it gets loaded.


For drawing, I learned how to use createGraphics() to draw a second canvas. This other canvas has an invisible background and is placed on top of the default canvas. For the image layers the background has to be refreshed each draw loop to avoid smearing. This, however, does not allow for drawing, so having two separate canvasses (one with a background and one without) are required.
Now for user testing!
User Testing
Some beautiful drawings made by testers:





Having the chance to user test my early version was extremely helpful in guiding development. I learned some key things from the people I had the chance to talk to:
Confusing Icons – people were confused about what the icons did. Image upload was confused for export and background color was confused for a dynamic paint bucket that can fill in shapes.

Redesigned icons:
I tried to make it more clear that you are (1) adding an image and (2) changing the background color of the canvas (not drawn shapes).

Image Layering – people were getting confused when dragging images around, so I knew I needed to add some sort of layering. I decided to simplify this by making it so that the image on top is the image that can be grabbed. Users can then use the up and down arrows to ‘vertically’ rearrange image layers as they wish. This is accomplished by having two imgs loops. One that runs from 0 to length-1 to draw the images and one that runs from length-1 to 0 to see which is being hovered first.

I then added functionality for reordering. On reordering, new uploadedImg objects are created at the front or the end of the array. The current uploadedImg is then spliced out. It works well!

Delete Singular Elements – people wanted to delete individual images rather than clearing the whole canvas. This was as simple as adding a trash can icon on hover that splices this image out of the array.
Undo / Redo – oh boy! I could not really find a way to allow for undoing of image manipulations (movement, scaling, etc…). Perhaps an action Object that stores the previous manipulations? Argh. I was, however, able to implement undos and redos on pen drawings.
As mentioned before, drawing is done on a different canvas than the images. So, as a user draws, I can save the strokes (the drawing done between mousePressed and mouseReleased) as a pixel array using get(). The arrays are then stored in a larger curDrawings array. If a user undos, I remove the most recent pixel array from curDrawings and save that pixel information in an undidDrawings array (only curDrawings is rendered in the draw loop). Storing undo strokes allows for redoing them later on. There was a lot of minutiae that had to be figured out as when to clear certain arrays or the drawingBuffer canvas. A benefit of storing drawings as images is that they can now be moved and deleted once they are added to the original canvas. Perfect!
I made it so drawing is a separate ‘mode’ that is done all at once on a layer. This was done so that if the user draws a pair of glasses, for example, that drawing is saved all as a single image. This means that the glasses can be moved as one (as opposed to individual strokes being added to the canvas and having to be moved one by one).
Image Filters
Luckily I had already written image filters for a previous assignment. Unluckily, however, reusing this code was not an easy task. For the longest time I simply could not get it to work. I realized the two things that were going wrong.
- background in
draw– as mentioned before, redrawing the background in each loop is required so that images can be moved around the canvas without smearing. I do not, however, want to be applying the filter 30 times a second as that would greatly slow down the program. This meant that to apply filters, the user first has to pause the draw loop to allow for filters to be applied. There is an alert that pops up letting the user know this will happen. - Canvas Sizing – After adding the ability to stop draw, I got some sort of filtering to appear on the canvas, but it was not applying to the whole image.

I was setting the pixelDensity to 1, but it still wasn’t working!! This took me forever to figure out, but p5 hates using a canvas where the width and height are not whole numbers. I was using windowWidth / 1.2 to size the canvas, which resulted in the canvas being something like 1278.3 pixels wide. How do you load the pixel values of that 0.3 pixel? You can’t! So, I used floor to truncate the decimal values off.

Finishing Up
Now that I had a solid set of features, I began implementing the smaller details to truly tie the experience together. This involved things like:
- ‘system prompts’ to alert the user of un-doable or confusing actions
- text blurbs to help on confusing tools
- drop-down menu bar functionality

- tightening up CSS
- page favicon
I could have kept working on this project for hours and hours, but had to eventually get to a stopping point and decide it was good enough to present.
Conclusion
Overall, I immensely enjoyed working on this project. It was incredibly refreshing to work in an older visual style and I learned a ton about images, pixel arrays, cellular automata, and combining p5 with HTML and CSS. After working on this project, I feel much stronger in my p5 abilities and the abilities of the library. I am looking forward to having some free time this summer to work on other projects or develop this one further!
Todo
Some things that would be great to add (the list could go on forever):
- Vintage cursors
- Minimum image size
- Image rotation (I tried implementing this but it messed up hovering states)
- Undo drawing reset
- Layers display
- Copying images
- Better filter UI
- Histogram
- Text
- Drag to upload
Gallery
Some random things I made using p5 Photo


