Coding a Generative Picture Frame: A Journey through Computational Thinking as a Problem-Solving Method
This article covers both the theory and practice of creative coding. If you’re inpatient, you can skip ahead to the Algorithm Design section to see the final code or click here. However, if you’re interested in learning about the theoretical basis behind the method, or if you’re new to the world of creative coding, keep reading! You’ll discover how Computational Thinking (CT) can be used to solve design problems related to computational mediums.
> Tutorial Code on openProcessing.
Background & Motivation
Have you ever found yourself drawn to a new idea, eager to explore its depths and uncover its creative potential? That’s the motivation behind my latest project: a virtual picture frame. As a curious individual, I find immense satisfaction in diving deep into technical or theoretical concepts, seeking out new ways to express my creativity.
The journey of learning is much like reading about a particular topic for years, refining your thoughts and perspectives. For instance, as a JavaScript front-end developer, you may already be familiar with the language’s syntax and grammar. However, continuously seeking out tutorials and learning from others’ experiences can lead to unexpected discoveries and insights. Immersing oneself in a subject is an alternative pathway to creative inspiration.
If you were a 90s kid, you probably remember the MacGyver TV series. He was known for using ordinary materials to create extraordinary inventions in order to defeat the villains. MacGyver always had a moment of inspiration whenever he found himself in a difficult situation with limited resources.
Constraints and limitations can be both a blessing and a curse when it comes to creativity. Working within specific programming languages or mediums may initially seem restrictive, but with time and dedicated practice, it can lead to more unique and personalized results. Embracing these constraints allows for the development of a valuable skill set that enhances the creative process.
Creativity, as defined by Bown (2021), encompasses three essential qualities: value, novelty, and non-obviousness or surprise. These components of computational creativity may vary in interpretation depending on the context. However, programming offers a unique avenue for generating fresh, surprising, and innovative outputs.
The intricate nature of creativity is influenced by individual differences, environmental factors, and personal experiences. Research supports the notion that programming can boost creativity (Duch, 2006; Chen et al., 2018). Nonetheless, inherent limitations exist within the creative process (Varshney, 2009). To surmount these obstacles, dedication, practice, and learning from failures are crucial. The more one encounters setbacks, the better equipped they become to achieve success in subsequent attempts. This principle holds true not only in programming but across all aspects of life.
In the realm of imagery, a frame is merely a construct — a word or concept imbued with meaning. This tutorial chronicles my journey in creating a virtual frame via interpreting Computational Thinking as a methodology. I assure you, the process of transforming a real-life object into the virtual realm becomes intriguing, presenting challenges along the way.
In the following sections, I will delve into the technical aspects of creating a virtual picture frame, sharing insights, challenges, and solutions encountered during the development process. Through this exploration, I aim to inspire fellow programmers and creatives to embrace their own unique projects and discover the limitless potential that lies within constraints.
Computational Thinking
To keep things simple, I will be using p5js and openProcessing as an IDE for the following code. Of course, you can use any programming language of your choice to implement the code and method. Let’s put on our academic hats and analyze the problem at hand. It is crucial to define the problem as clearly as possible, providing a focal point to maintain determined focus.
The Problem: How can we implement a basic picture frame using p5.Js?
Just as MacGyver resourcefully uses his paperclip, Swiss army knife, and duct tape to solve complex problems, we too can leverage our programming tools and knowledge to create innovative solutions within the constraints we’ve have. In any problematic situation, MacGyver first assesses the problem, determines the requirements, and then scans his environment to find the necessary resources. Often, he combines different artifacts or hacks a device to repurpose it, breaking down the problem into smaller parts and following a step-by-step approach.
Similarly, we can adopt this methodology to create a basic picture frame using p5.js. By employing the principles of Computational Thinking (CT), we can systematically tackle the problem and develop a solution. CT encompasses four essential steps: Decomposition, Pattern Recognition, Abstraction, and Algorithmic Design. These principles provide a structured approach to problem-solving and can help guide us through the process of creating a basic picture frame using p5.js.
- Decomposition: Break down the problem into smaller, manageable parts. In the case of creating a picture frame, this may involve identifying the various components, such as the frame shape, border, and background.
- Pattern Recognition: Identify patterns and connections between the different parts of the problem. For example, we may notice that the frame shape and border share similar properties, such as their rectangular structure.
- Abstraction: Simplify complex concepts by focusing on their essential features. In the context of creating a picture frame, this may involve defining the basic properties of the frame, such as its size, color, and position.
- Algorithmic Design: Develop a step-by-step procedure to solve the problem. This may involve writing code to create the frame shape, add the border, and set the background.
It is worth noting that Seymour Papert (1980) was one of the earliest researchers to introduce the term “Computational Thinking” in his Constructionist Learning/Teaching Theory, predating Wing’s seminal article. By utilizing the principles of CT, we can approach the problem of creating a picture frame with a clear and systematic mindset, ultimately leading to a successful solution. For further references, please refer to the end of the document.
Method Exemplified
The workflow for creating a basic picture frame using p5.js and employing the principles of Computational Thinking (CT) will proceed in the following order:
- Decomposition
- Pattern Recognition
- Abstraction
- Algorithmic Design
It is essential to engage actively in the learning process rather than passively reading a tutorial. To truly absorb and understand the material, it is important to put your hands “in the mud” and take your own notes. Writing down key concepts and ideas not only helps to reinforce your understanding but also frees up mental space, allowing you to focus on unleashing your creativity.
Let’s start
Decomposition involves breaking down a problem into manageable pieces. In the reference image above (Figure 3), there are some obvious formalistic features. Begin by noting down what you see. For instance, you may see a rectangular object on a wall. While this is the first thing you notice, it is not enough to describe the frame in its entirety. Everyone reading this article will likely see different things at first glance. However, it is important to note each feature in your own words, as this is part of the methodology (Figure 4). This is the first chunk. Break the sentence down into every detail. There is a surface with a specific size and color, and a rectangular frame on it with specific width, height, and color. Additionally, there is a white mat inside the frame. This provides us with some data that we can use in programming. There are no limits to this stage, as we can continue to break things down to an atomic level. The last two chunks are the shadow of the frame that mimics the light rays, and the artwork itself. Let’s identify what we can and move on to the Abstraction step, where we can eliminate unnecessary details for the time being.
Abstraction is a practice of structuring steps as if you were explaining them to a five-year-old child, and determining the necessary parameters for each item. This step is up to you and what you want to achieve in a formal sense. Remember that there is no limit, but we need to stop at some point and be clear about our intentions. We need to articulate the problem as clearly as possible. If you feel that something is missing, you can turn back to the Decomposition step. Suppose CT as an iterative design approach, for the sake of this tutorial, let’s continue with Abstraction. We need to note down each detail as clearly as possible, keeping in mind that we will tell everything to a computer. Here are my notes:
- The rectangle for the frame must have position, width, and height variables. Since we will work on a 2D space, we need horizontal and vertical position values for the frame rectangle.
- The color of the frame needs to be defined by a variable.
- The thickness of the frame sides needs to be defined by a number.
- The rectangle for the mat needs to have variables for color, width, and height to create empty space between the frame borders and the artwork.
- The shadow of the frame requires a color variable, a variable for the amount of blurriness, and the position of the shadow.
During the Pattern Recognition phase, we attempt to solve the puzzle by identifying any relationships between the chunks that were previously identified and abstracted. We may ask ourselves questions such as:
- Are there any recurring behaviors or patterns?
- How do the parameters relate to one another?
Ultimately, we are searching for deterministic factors or uncovering connections between the chunks. After all, they are all particles of a single object. So, it’s up to us to be creative like MacGyver and utilize ordinary tools like a paperclip to create something extraordinary. In this step, we are transferring materials from one medium to another, such as moving a print from paper to stone.
- The surface is our canvas and must be larger than anything on the canvas. Canvas is the surface and affects everything inside of it.
- The frame must be smaller than the frame. Its position, width, and height determine the mat and shadow. If we change any frame property, the mat and shadow are also modified. Its color can be independent.
- The size and position of the mat depend on the frame properties. Its color can be independent.
- The shadow is dependent on the position and the size of the frame.
- Artwork depends on the Frame and Mat position and size.
Algorithmic Design is the final step in which you write code based on logical and relational concepts that you identified in previous steps. It involves negotiating between your intended purpose and your actual knowledge. This is the part where you can modify your decisions and go with the flow.
However, as a beginner, you may encounter limitations. Yet, possibilities are endless as long as you follow the method. Additionally, CT can be recursive as mentioned before, meaning that you may encounter new problems in the process of algorithmic design and apply CT to that specific problem. Once you are satisfied, you can return to where you paused.
Additionally, be mindful of the “Problem of Induction” (Henderson, 2022), as some problems may be difficult to resolve. They can bias you to go into an endless problem inferences. In short, don’t get stuck in a recursive problem splitting period. Sometimes unsolved problems give us out-of-sighted concepts that we cannot see because of the domination of the main problem. Although there are still many phenomena that cannot be explained using scientific methods, the research journey can lead to different perspectives that are relevant to the main problem.
To keep everything organized, let’s divide Algorithmic Design into two sub-sections: Translation and Script.
A. Translation: This step is like using a dictionary. The computer has different syntactical rules than our spoken language. So, we need to translate the notes into the Javascript domain with the help of the P5js tool and additional libraries. This step requires explicitly declaring the parameters and actions (functions). In Figure 8, you can see the actual syntax necessary for P5js. Opening the documentation page of P5js and Javascript is crucial to learning how to utilize specific functions. Suppose that you are learning a spoken language. What you need is a dictionary, most of the time, to use the appropriate words to express yourself clearly.
We will also use p5.Utils() library is used to draw the shadow of the frame. Because the library utilizes CSS to create shadows, making it more efficient and less CPU-intensive.
The simple method to find required functions is to navigate to the P5js reference page. If you started to type “rectangle” on the search bar, you will notice that relevant functions are listed. Click on the “rect” method to see the example codes. Scrolling down to the bottom, you will see the “Syntax” section, which documents how to use the syntactical format (Figure 9).
The p5Utils library also includes documentation and example use cases on how to utilize its methods. For our case, we need to create a shadow (Figure 10). Have a look at other functions and utilities that might be helpful for you in your P5js projects.
B. Script:
The term is borrowed from Akrich’s Script Theory. Akrich states that a Script is a set of instructions embedded into a technological system by its designer (Akrich, 1992). We plan and determine the conditions required to draw a virtual picture frame from the beginning. The algorithm (Script) will be the program’s blueprint and tell the computer how to act according to our written instructions as code blocks. Let us start with creating all the global variables required that we identified in the Abstraction step. Suppose that you are a director and casting actors and actresses for your film;
Goto openprocessing page. If you don’t have an account, create one for free. After you create your project, do not forget to include p5.Utils() library to your project, as shown in Figure 11.
/****************************************************/
/* Frame Parameters *********************************/
/****************************************************/
let fx; // x position of the frame
let fy; // y position of the frame
let fw; // width of the frame
let fh; // height of the frame
let fThickness; // thickness of the frame
let fColor; // color of the frame
/*****************************************************/
/* Shadow Parameters *********************************/
/*****************************************************/
let shadowColor; // color of the shadow
let shadowBlur; // blur value of the shadow
let shadowOffsetX; // horizontal distance of the shadow
let shadowOffsetY; // vertical distance of the shadow
/*****************************************************/
/* Instantiate p5.Utils Library **********************/
/*****************************************************/
let utils = new p5.Utils();
/*****************************************************/
/* Mat Parameters ************************************/
/*****************************************************/
let mx; // x position of the mat
let my; // y position of the mat
let mw; // width of the mat
let mh; // height of the mat
let matColor; // color of the mat
let mThickness; // Thickness of the mat
/*****************************************************/
/* Artwork Parameters ********************************/
/*****************************************************/
let ax; // x position of the artwork
let ay; // y position of the artwork
let aw; // width position of the artwork
let ah; // height position of the artwork
let aBgColor; // Background color for the artwork
Now, let’s create the world for our scenario and assign values to the parameters referring to Pattern Recognition. We need to implement the relation between variables according to Pattern Recognition. Check the comments below;
function setup() {
createCanvas(1080, 1080); // The size of the surface
background(241, 236, 230); // The color of the surface
initParameters(); // Keep the code clean. Define a custom function
noLoop(); // Since we don't need animation, it is a good practice turn off rendering to use less CPU.
}
function initParameters() {
// Assign shadow values
shadowColor = "#00000055";
shadowBlur = 10;
shadowOffsetX = 6;
shadowOffsetY = 6;
// Assign frame values
fw = 500;
fh = 500;
// Position the frame in the middle relative to the space size
fx = width * 0.5 - fw * 0.5
fy = height * 0.5 - fh * 0.5;
fThickness = 20;
fColor = color(25, 3, 4);
// Assign values for the Mat
mThickness = 30;
// Position the mat in the middle of the frame relative to the frame size and position
mx = fx + fThickness;
my = fy + fThickness;
// Calculate the size of the mat relative to thickness of the frame
mw = fw - fThickness * 2;
mh = fh - fThickness * 2;
matColor = color(250, 240, 220);
// Assign values for the Artwork
// Calculate the size of the mat relative to thickness of the frame
aw = mw - mThickness * 2 - fThickness * 2;
ah = mh - mThickness * 2 - fThickness * 2;
// Calculate the top-left position of the artwork to position it on canvas
ax = mx + mThickness + fThickness;
ay = my + mThickness + fThickness;
aBgColor = color(20, 20, 20);
}
function draw() {
// Main function that displays objects
}
Now is the time for ordering the Instructions. This step is setting the drawing order identified in Figure 6. Suppose that you are ordering layers in an image editor like Photoshop or Illustrator. Let’s implement the drawing order inside `draw()` function;
function draw() {
// Start applying shadow using p5.Utils()
utils.beginShadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY);
noStroke(); // Disable strokes around the frame
fill(fColor); // Set the color of the frame
rect(fx, fy, fw, fh); // Draw the rectangle of the frame
utils.endShadow(); // End applying shadow
// Draw the Mat
fill(matColor); // Set the color of the Mat
rect(mx, my, mw, mh); // Draw the rectangle of the Mat
// Draw The artwork
// The following method will be your algorithmic art
// Pass the values to the function of the artwork.
generateArtwork(ax, ay, aw, ah, aBgColor);
}
function generateArtwork(_x, _y, _w, _h, _aBgColor) {
push(); // Save the current matrix positions and styles
fill(_aBgColor); // Background color
rect(_x, _y, _w, _h); // Draw the rectangle (this is the paper)
translate(_x + _w / 2, _y + _h / 2);// Move the whole artwork into the middle of the Frame
let num = 30; // Number of circles to draw
let circleRad = 110; // Radius of each circle
let rad = _w / 2 - circleRad; // how far the each circle from the center point relative to size of the each circle
noFill(); // Display circles as lines
for (let i = 0; i < num; i++) { // for loop to calculate each circle position
stroke(200 + random(-70, 20)); // Randomize the stroke color of each circle to make them naturalistic
strokeWeight(1 + random(2)); // Randomize the thickness of each circle
let a = (TAU / num) * i; // Calculate the angle according to polar coordinates of each circle
let x = cos(a) * (rad + random(10)); // x position of each circle
let y = sin(a) * (rad + random(10)); // y position of each circle
circle(x, y, circleRad); // Display the circles
}
pop(); // Return back to original matrix positions and styles
}
Space For Creativity
You should see the following top-left output if you can follow the tutorial step by step. Here is the complete code and the output. In this final stage, play around with parameters to discover the possibilities of the system you created. Test your code by changing parameters. Improve your code for your specific purpose. This part is pushing the boundaries that we create and surprising results. Try to randomize frame size, colors, and artwork parameters using random() and noise() functions. The other three images are the result of using random function to set width and height of the canvas.
/****************************************************/
/* Frame Parameters *********************************/
/****************************************************/
let fx; // x position of the frame
let fy; // y position of the frame
let fw; // width of the frame
let fh; // height of the frame
let fThickness; // thickness of the frame
let fColor; // color of the frame
/*****************************************************/
/* Shadow Parameters *********************************/
/*****************************************************/
let shadowColor; // color of the shadow
let shadowBlur; // blur value of the shadow
let shadowOffsetX; // horizontal distance of the shadow
let shadowOffsetY; // vertical distance of the shadow
/*****************************************************/
/* Instantiate p5.Utils Library **********************/
/*****************************************************/
let utils = new p5.Utils();
/*****************************************************/
/* Mat Parameters ************************************/
/*****************************************************/
let mx; // x position of the mat
let my; // y position of the mat
let mw; // width of the mat
let mh; // height of the mat
let matColor; // color of the mat
let mThickness; // Thickness of the mat
/*****************************************************/
/* Artwork Parameters ********************************/
/*****************************************************/
let ax; // x position of the artwork
let ay; // y position of the artwork
let aw; // width position of the artwork
let ah; // height position of the artwork
let aBgColor; // Background color for the artwork
function setup() {
createCanvas(1080, 1080); // The size of the surface
background(241, 236, 230); // The color of the surface
initParameters(); // Keep the code clean. Define a custom function
noLoop(); // Since we don't need animation, it is a good practice turn off rendering to use less CPU.
}
function initParameters() {
// Assign shadow values
shadowColor = "#00000055";
shadowBlur = 10;
shadowOffsetX = 6;
shadowOffsetY = 6;
// Assign frame values
fw = 500;
fh = 500;
// Position the frame in the middle relative to the space size
fx = width * 0.5 - fw * 0.5;
fy = height * 0.5 - fh * 0.5;
fThickness = 20;
fColor = color(25, 3, 4);
// Assign values for the Mat
mThickness = 30;
// Position the mat in the middle of the frame relative to the frame size and position
mx = fx + fThickness;
my = fy + fThickness;
// Calculate the size of the mat relative to thickness of the frame
mw = fw - fThickness * 2;
mh = fh - fThickness * 2;
matColor = color(250, 240, 220);
// Assign values for the Artwork
// Calculate the size of the mat relative to thickness of the frame
aw = mw - mThickness * 2 - fThickness * 2;
ah = mh - mThickness * 2 - fThickness * 2;
// Calculate the top-left position of the artwork to position it on canvas
ax = mx + mThickness + fThickness;
ay = my + mThickness + fThickness;
aBgColor = color(20, 20, 20);
}
function draw() {
// Start applying shadow using p5.Utils()
utils.beginShadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY);
noStroke(); // Disable strokes around the frame
fill(fColor); // Set the color of the frame
rect(fx, fy, fw, fh); // Draw the rectangle of the frame
utils.endShadow(); // End applying shadow
// Draw the Mat
fill(matColor); // Set the color of the Mat
rect(mx, my, mw, mh); // Draw the rectangle of the Mat
// Draw The artwork
// The following method will be your algorithmic art
// Pass the values to the function of the artwork.
generateArtwork(ax, ay, aw, ah, aBgColor);
}
function generateArtwork(_x, _y, _w, _h, _aBgColor) {
push(); // Save the current matrix positions and styles
fill(_aBgColor); // Background color
rect(_x, _y, _w, _h); // Draw the rectangle (this is the paper)
translate(_x + _w / 2, _y + _h / 2);// Move the whole artwork into the middle of the Frame
let num = 30; // Number of circles to draw
let circleRad = 110; // Radius of each circle
let rad = _w / 2 - circleRad; // how far the each circle from the center point relative to size of the each circle
noFill(); // Display circles as lines
for (let i = 0; i < num; i++) { // for loop to calculate each circle position
stroke(200 + random(-70, 20)); // Randomize the stroke color of each circle to make them naturalistic
strokeWeight(1 + random(2)); // Randomize the thickness of each circle
let a = (TAU / num) * i; // Calculate the angle according to polar coordinates of each circle
let x = cos(a) * (rad + random(10)); // x position of each circle
let y = sin(a) * (rad + random(10)); // y position of each circle
circle(x, y, circleRad); // Display the circles
}
pop(); // Return back to original matrix positions and styles
}
Conclusion
In this hybrid article, we learn how to solve a design problem which was drawing a parametric virtual frame using Computational Thinking as a methodological approach. By embracing constraints, we uncovered the creative potential of programming. We then broke down the problem into smaller parts using Decomposition, Pattern Recognition, Abstraction, and Algorithmic Design. Through active engagement in the learning process, we deepened our understanding of the material and applied it to create a generative picture frame using p5.js.
Computational Thinking is just a problem-solving method which has four fundamental steps. The implementation of depends of the methodology may differentiate according to your theoretical and paradigmatic frame. This is my own implementation. For example, I extend the last step with two sub-steps for the Algorithmic Design. But the core idea is, determining a solid purpose and following the steps.
One of the most important aspect is how we articulate the main problem accurately. Because of that I find it useful to start with pen and paper. As Constructivist learning theories claiming utilizing existing knowledge when learning new stuff acts as a catalyzer and helps you to understand the new concept more effectively (Papert, 1980). Using analogies and conceptual metaphors allow us to link between different concepts. Pen and paper are your existing domain of knowledge. You have enough practice and physical skills to write down abstract ideas and solidify them on paper. This approach gives you enough space to focus on the problem rather than dealing with unknown semantics of computer programming. You don’t need to think how to write using a pen, or how to draw basic figures on a paper. It allows expressing your self in a visual and semantic way in a domain that you are familiar. Following a step by step approach is another way of training your cognitive skills to solve the identified problem.
For this tutorial, our main concern is to generate parametric picture frame. Maybe you are above the beginner level, and you may think that the algorithm design could be much more efficient if we used object oriented programming paradigms to make the algorithm modular and reusable for later implementations. It is all up to you and how you articulate your problem and main purpose.
The reason why I include such a theory for a tutorial is to show possibilities of using theoretical grounding greatly enhance our perspective and support us intellectually. Technical aspects and how to tutorials are of course very important. However, in my opinion, having a paradigmatic approach combining different theories will help you synthesize ideas and contributes to what we call as creativity.
Lastly, if you read and implement the tutorial from start to end, it would be great to see your reviews and comments. I appreciate all kinds of feedback.
References
- Akrich, M. (1992). The De-Scription of Technical Objects. In Shaping Technology/Building Society: Studies in Sociotechnical Change. (pp. 205–224).
- Levin, G. & Brain, T. (2021). Code as Creative Medium: A Handbook for Computational Art and Design
- Bown, O. (2021). Beyond the Creative Species: Making Machines That Make Art and Music
- Duch, W. (2006). Computational Creativity
- Chen, L., Wang, P., Shi, F., Han, J., & Childs, P. (2018). A computational approach for combinational creativity in design. In DS 92: Proceedings of the DESIGN 2018 15th International Design Conference (pp. 1815–1824).
- Varshney, L. R. (2019). Mathematical limit theorems for computational creativity. IBM Journal of Research and Development, 63(1), 2–1.
- Perplexity (2024). MacGyver
- Lodi, M., Martini, S. Computational Thinking, Between Papert and Wing. Sci & Educ 30, 883–908 (2021). https://doi.org/10.1007/s11191-021-00202-5
- Papert, S. (1980). Mindstorms: Children, computers, and powerful ideas. Basic Books.
- Wing, J. M. (2006). Computational Thinking. Communications of the ACM, 49(3), 33–35.
- Henderson, L. (2022). The Problem of Induction. In E. N. Zalta & U. Nodelman (Eds.), The Stanford Encyclopedia of Philosophy (Winter 2022). Metaphysics Research Lab, Stanford University. https://plato.stanford.edu/archives/win2022/entries/induction-problem/