Welcome to Bloom
Make art with code. No experience needed.
All distances in Bloom are measured in pixels (tiny dots on your screen). A typical phone screen is about 400 pixels wide.
Your Very First Line of Code
Let's start with the simplest possible program. This just shows a message:
print("Hello, world!")
What happened? The word print tells the computer to show a message.
The message goes inside quotes " " and parentheses ( ).
Look at the console - the text area below the code editor!
The console is a text area that shows messages from your program. It's below the code editor.
print("Hello")shows "Hello" in the consoleprint(x)shows the value of x - great for debugging!print("x is: " + x)shows text and a value together (the+glues them)
Joining text: Use + to combine text and values: "Score: " + score becomes "Score: 42" if score is 42.
Tip: When something isn't working, add print() to see what values your variables have!
Try changing "Hello, world!" to your name, then tap Run.
Now let's draw! Change any number and watch what happens:
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
// Move your mouse or finger over the canvas!
fill(orange)
circle(mouseX, mouseY, 40)
// These circles follow with a delay
fill(pink)
circle(mouseX - 20, mouseY + 30, 25)
circle(mouseX + 20, mouseY + 30, 25)
}You just drew with code!
- The code on the left tells the computer what to draw
- The canvas (drawing area) on the right shows the result in real-time
- Move your mouse or finger over the canvas to see coordinates
- Change any number and watch the result update instantly
Here's All You Need to Know
Why use functions? Instead of writing the same code 10 times, save it once and reuse it.
fn setup() { } runs once when your program starts. Always call size(width, height) here to create your canvas.fn draw() { } runs 60 times per second (like a flipbook creating animation).
let size = 40 creates a box labeled "size" containing the number 40.
if mousePressed { } means "if the mouse is pressed, do this."
for i in 0..5 { } repeats 5 times, with i being 0, then 1, then 2, then 3, then 4.
Tap any keyword in the code editor to learn more about it.
What's a "value"? Any number, color, or text. 100, red, and "Hello" are all values.
What's a "function"? A command that does something. circle() draws a circle. The values inside the parentheses tell it where and how big.
Why quotes around text? Quotes like "Hello" tell the computer "this is text, not a command." Without quotes, Bloom would try to run it as code.
( )
Parentheses: hold inputs for a command
circle(100, 100, 40) gives circle 3 inputs
{ }
Braces: contain a block of instructions
fn draw() { ...code here... }
[ ]
Brackets: hold a list of values
[10, 20, 30] is a list of 3 numbers
..
Range: shorthand for a sequence
0..5 means 0, 1, 2, 3, 4
//
Comment: a note the computer ignores
// remind yourself what code does
New to coding? Follow this order for the best experience:
- Start here: Your First Sketch - Draw your first shape
- Common pitfalls: Mistakes Everyone Makes - Avoid frustration
- Get creative: Shapes and Colors - More drawing tools
- Add movement: Animation - Make things move
- Go deeper: Variables and Loops - Real programming power
Or just explore! Click any topic in the sidebar that interests you.
Mistakes Everyone Makes
These are totally normal! Every programmer makes these mistakes at first. Here's how to spot and fix them.
fill(color) before drawing
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(coral) // Add this line!
circle(100, 100, 40)
}
background(color) as the first line inside draw()
Key rule: background() must be the first line inside draw(). If you put it at the end, it erases everything you just drew!
fn setup() {
size(200, 200)
}
fn draw() {
background(white) // Clear canvas each frame
fill(coral)
circle(mouseX, mouseY, 30)
}
let x = 0 // Outside draw = remembers!
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(coral)
circle(x, 100, 30)
x = x + 2 // Now x grows each frame
}
= (assignment) instead of == (comparison) in if statements
== to compare values
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Wrong: x = 5 (assigns 5 to x)
// Right: x == 5 (checks if x equals 5)
if mousePressed == true {
fill(coral)
} else {
fill(skyblue)
}
circle(100, 100, 60)
}
How to Debug (Find and Fix Problems)
When your code isn't working, don't panic! Follow these steps:
Bloom shows errors in the console with hints. The error tells you what's wrong and often how to fix it.
Add print(x) to see what a variable actually is. This often reveals the problem instantly.
// Not sure why your circle is off-screen?
print("x is: " + x) // See the actual value!
print("y is: " + y)
Comment out lines (add // at the start) to find which line causes the problem. Remove complexity until it works, then add back one piece at a time.
- Did you spell everything correctly?
- Are your brackets
{ }and parentheses( )balanced? - Is your variable defined before you use it?
// Debugging Example: Why isn't my circle moving?
let x = 100
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Step 2: Add print() to see what x is
print("x = " + x)
fill(coral)
circle(x, 100, 30)
// The circle moves because x changes each frame
x = x + 1
}Your First Sketch
Let's build your first program together, step by step. Don't worry about memorizing anything - just follow along and experiment!
The Setup
Every Bloom program starts with fn setup(). This runs once
when your program starts. We use it to create the drawing area:
fn setup() {
size(200, 200) // Makes a 200x200 pixel canvas
}
The Draw Loop
The fn draw() function runs 60 times per second - that's what
makes animation possible! Each time it runs, we draw a fresh picture:
fn draw() {
background(white) // Clear with white
// Your drawing code goes here
}
background(), you're drawing on the same page over and over, leaving trails.
setup() if:- It only needs to happen once (canvas size, initial background)
- You're setting up starting conditions
draw() if:- It should update or animate over time
- It responds to mouse, keyboard, or frame count
- You want to see changes every frame
Draw Something!
Let's draw a blue circle. First we set the color with fill(),
then we draw with circle(x, y, size):
fill(blue) // Set the fill color circle(100, 100, 40) // Draw circle at center
Here's the complete program. Try changing the numbers and colors!
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(blue)
circle(100, 100, 40)
}- Change
bluetocoralorpurple - Make the circle bigger by changing
40to80 - Move the circle to the bottom-right by changing
100, 100to150, 150 - Add a second circle with a different color!
The circle(100, 100, 40) draws a circle at position (100, 100) with a radius of 40.
Try changing 100, 100 to move it around, or change 40 to make it bigger or smaller.
Understanding the Canvas
The canvas is like a piece of graph paper. Every point has two numbers: x (how far right) and y (how far down).
Unlike math class, y goes down (like reading a book from top to bottom). Computer screens draw from the top-left corner.
Move your mouse or finger over the canvas below to see the coordinates:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Draw grid lines
stroke(220)
strokeWeight(1)
line(100, 0, 100, 200)
line(0, 100, 200, 100)
// Mark corners with coordinates
fill(150)
textSize(10)
text("(0,0)", 4, 12)
text("(200,0)", 160, 12)
text("(0,200)", 4, 195)
text("(200,200)", 145, 195)
// Draw crosshair at mouse position
stroke(coral)
strokeWeight(1)
line(mouseX, 0, mouseX, 200)
line(0, mouseY, 200, mouseY)
// Draw a dot where your mouse is
fill(coral)
noStroke()
circle(mouseX, mouseY, 12)
// Show the coordinates prominently
fill(black)
textSize(14)
text("x: " + floor(mouseX), 80, 95)
text("y: " + floor(mouseY), 105, 115)
}Tip: If a shape disappears, it might be outside the canvas boundaries. Check that your x is between 0 and the canvas width, and y is between 0 and the height.
Beginner Shapes
Bloom gives you a handful of basic shapes to work with. Each shape needs different information to know where and how big to draw itself.
Every position on the canvas is described by two numbers:
- x = distance from the left edge (increases going right)
- y = distance from the top edge (increases going down)
Why the difference? Screens display text line-by-line from top to bottom, like reading a book. Line 1 is at the top (y=0), line 2 is below it (y=1), and so on. This became the standard for all computer graphics.
Circles and Ellipses
A circle needs a center point (x, y) and a radius.
An ellipse is like a stretched circle - it needs a center point plus width and height.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(blue)
circle(60, 100, 30)
fill(purple)
ellipse(140, 100, 60, 80)
}The radius is the distance from the center to the edge - it's half the total width.
circle(100, 100, 30)= 30px radius = 60px wide circlecircle(100, 100, 50)= 50px radius = 100px wide circle
If your circle looks twice as big as expected, you probably wanted diameter (total width) instead of radius (half width).
Rectangles
A rect needs a top-left corner (x, y), then width and height.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(orange)
rect(40, 40, 50, 80)
fill(teal)
rect(110, 60, 60, 60)
}Lines and Triangles
Lines go from one point to another. Triangles need three corner points.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
stroke(red)
strokeWeight(3)
line(20, 20, 180, 180)
fill(green)
triangle(100, 30, 40, 170, 160, 170)
}Quadrilaterals
A quad is any four-sided shape. You give it four corner points (8 numbers total).
Unlike rectangles, the corners don't have to form right angles.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(coral)
// Parallelogram shape
quad(30, 50, 90, 30, 110, 100, 50, 120)
fill(teal)
// Trapezoid shape
quad(120, 60, 180, 60, 190, 140, 110, 140)
}Beginner Colors
Bloom comes with a set of named colors you can use right away. Just type the name!
More colors: rose, tomato, amber, honey, jade, sage, lime, azure, cobalt, indigo, lavender, plum, peach, cream, ivory, beige, and many more! See the Reference for the full list.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(red)
circle(50, 50, 30)
fill(green)
circle(100, 100, 30)
fill(blue)
circle(150, 150, 30)
}Custom Colors with RGB
Want more control? Use rgb() to mix your own colors by combining
Red, Green, and Blue light.
Each color has a number from 0 (none) to 255 (maximum):
rgb(255, 0, 0)= full red, no green, no blue = redrgb(0, 255, 0)= no red, full green, no blue = greenrgb(255, 255, 0)= full red + full green = yellowrgb(255, 255, 255)= all colors full = whitergb(0, 0, 0)= no colors = black
Why 0-255? Think of it like a volume knob with 256 positions (0 to 255). 0 means "off" and 255 means "full blast." The middle (128) is half strength. This range became standard because it gives enough shades to look smooth to our eyes.
Tip: If you type a number higher than 255 (like 300), Bloom just treats it as 255. Nothing breaks!
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
fill(rgb(255, 100, 100))
circle(60, 100, 40)
fill(rgb(100, 200, 255))
circle(140, 100, 40)
}Transparency
Add a fourth number to rgba() for transparency.
It goes from 0 (invisible) to 1 (solid).
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(rgba(255, 0, 0, 0.7))
circle(80, 80, 50)
fill(rgba(0, 0, 255, 0.7))
circle(120, 120, 50)
}HSB Colors (Hue, Saturation, Brightness)
Sometimes it's easier to think about colors in a different way. hsb() lets you
describe colors using Hue (which color), Saturation (how vivid), and Brightness (how light/dark).
- Hue (0-360): The color on the rainbow wheel. 0 = red, 120 = green, 240 = blue, 360 = back to red
- Saturation (0-100): How vivid. 0 = gray, 100 = pure color
- Brightness (0-100): How light. 0 = black, 100 = full brightness
Why use HSB? It's great for color animations! Change the hue to cycle through rainbow colors, or the brightness to fade in/out.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Rainbow colors by changing hue
for i in 0..6 {
fill(hsb(i * 60, 80, 90))
circle(35 + i * 30, 70, 25)
}
// Same hue, different saturation
for i in 0..4 {
fill(hsb(200, i * 30, 90))
circle(50 + i * 40, 140, 25)
}
}The top row shows different hues (colors around the rainbow). The bottom row shows the same blue with increasing saturation (from gray to vivid blue).
Modifying Colors
Bloom provides functions to adjust any color. These work with named colors or custom colors:
lighten(color, amount)
Make lighter (amount 0-100)
darken(color, amount)
Make darker (amount 0-100)
saturate(color, amount)
More vivid (amount 0-100)
desaturate(color, amount)
More muted (amount 0-100)
mix(color1, color2)
Blend two colors together
rotateHue(color, degrees)
Shift around color wheel
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Original color
fill(blue)
circle(40, 100, 30)
// Lighter version
fill(lighten(blue, 30))
circle(100, 100, 30)
// Darker version
fill(darken(blue, 30))
circle(160, 100, 30)
}Color Harmony
Use color theory to find colors that look good together. These functions return colors that are harmonious with your starting color:
complement(color)
Opposite on color wheel
analogous(color)
Returns 3 similar colors
triadic(color)
Returns 3 evenly spaced colors
splitComplement(color)
Returns 3 balanced colors
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
let baseColor = coral
// Original color
fill(baseColor)
circle(50, 100, 35)
// Complementary (opposite)
fill(complement(baseColor))
circle(110, 100, 35)
// A triadic color
let triad = triadic(baseColor)
fill(triad[1])
circle(170, 100, 35)
}Color Palettes
Bloom includes pre-made color palettes - collections of colors that look good together. Perfect for quickly making beautiful art without picking colors yourself!
palette(name, index)
Get a color from a named palette
paletteSize(name)
How many colors in the palette
randomFromPalette(name)
Pick a random color from palette
paletteNames()
List all available palette names
Available palettes: sunset, ocean, forest, candy, neon, pastel, earth, jewel, autumn, winter, retro, midnight, tropical, nordic, desert, cherry, lavender, mint
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 40))
// Draw circles using the "sunset" palette
for i in 0..paletteSize("sunset") {
fill(palette("sunset", i))
circle(30 + i * 35, 60, 25)
}
// Draw with the "ocean" palette
for i in 0..paletteSize("ocean") {
fill(palette("ocean", i))
circle(30 + i * 35, 120, 25)
}
// Random colors from "neon"
fill(randomFromPalette("neon"))
circle(100, 170, 30)
}Styling
Shapes have two parts: the fill (inside color) and the stroke (outline). You can control both independently.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(yellow)
stroke(orange)
strokeWeight(4)
circle(70, 100, 40)
noFill()
stroke(blue)
strokeWeight(2)
circle(130, 100, 40)
}
Use noFill() to draw just the outline, or noStroke()
to draw without an outline.
Once you set a style, it applies to ALL shapes drawn after it - until you change it again:
fill(red) circle(50, 100, 20) // Red circle(100, 100, 20) // Still red! fill(blue) circle(150, 100, 20) // Now blue noFill() circle(200, 100, 20) // No fill - just outline circle(250, 100, 20) // Still no fill!
Tip: If your shape colors seem wrong, check what style was set earlier in your code.
Text Styling
You can customize how text looks with a few functions:
textSize(pixels)
Set font size (default is 12)
textFont(name)
"serif", "sans-serif", or "monospace"
textAlign(align)
"left", "center", or "right"
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(navy)
textSize(24)
textFont("serif")
text("Hello!", 100, 60)
textSize(14)
textFont("sans-serif")
textAlign("center")
text("Centered text", 100, 100)
textSize(12)
textFont("monospace")
textAlign("right")
text("Right aligned", 190, 140)
}The y coordinate positions the baseline (bottom of letters like "a", not the top):
text("Hello", 10, 50) // Baseline is at y=50, text appears ABOVE this line
textAlign() changes the x anchor point:
textAlign("left")- x is the left edge (default)textAlign("center")- x is the center of the texttextAlign("right")- x is the right edge
Tip: If text seems too low, subtract from y. If centering on screen, use textAlign("center") with x = width / 2.
Beginner Variables
Variables let you store values and use them later.
Create one with let and give it a name.
let x = 100
let radius = 60
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(blue)
circle(x, 100, radius)
}
Try changing the values of x and radius at the top.
Variables make it easy to adjust your sketch without hunting through all the code.
Variables work differently depending on where you create them:
- Outside functions - The variable keeps its value between frames. Perfect for animation (like tracking position).
- Inside draw() - The variable starts fresh every frame. Good for calculations that depend on the current moment.
If your animation is stuck, you probably put the variable inside draw():
fn draw() {
let x = 0 // Oops!
x = x + 1
circle(x, 100, 20)
}
Circle stays at x=1 forever
let x = 0 // Outside!
fn draw() {
x = x + 1
circle(x, 100, 20)
}
Circle moves across screen
let x = 0 inside draw():let x = 0 outside:Intermediate Animation
Bloom creates animation like a flipbook. Your draw() function runs
60 times per second. Each time it runs is called a "frame":
By changing values a little bit each frame, you create smooth motion!
Here's where it gets fun. Since draw() runs over and over,
you can change values each frame to create motion.
let x = 0
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(blue)
circle(x, 100, 20)
x = x + 2
if x > 200 {
x = 0
}
}
Each frame, we add 2 to x, making the circle move right.
When it goes past the edge, we reset it to 0.
The Frame Counter
Bloom gives you a built-in variable called frame - a counter that starts at 0 and increases by 1 every time draw() runs.
Try adding text("Frame: " + frame, 10, 20) to any sketch to watch the counter grow!
Smooth Motion with sin() and cos()
Combined with sin() and cos(), the frame counter creates smooth, looping motion.
Why use these instead of just counting? When you do x = x + 1, the value goes up forever and eventually leaves the screen. But sin() and cos() automatically loop back and forth - they never escape!
- sin() and cos() output smooth waves that go back and forth between -1 and 1
- They're perfect for making things swing, pulse, or orbit without writing reset logic
- Multiply by a number to control how far it moves (e.g.,
* 50means 50 pixels)
x = x + 1
Goes 0, 1, 2, 3... forever (escapes screen)
x = sin(frame * 0.05) * 50
Goes -50 to +50 and back, forever (stays on screen)
The frame counter increases by 1 every frame (60 times per second). That's too fast for smooth animation!
frame * 1= very fast (completes a full cycle in ~6 frames)frame * 0.1= medium speedframe * 0.05= slow and smooth (completes a full cycle in ~125 frames, about 2 seconds)frame * 0.01= very slow
Try it: Change 0.05 to 0.2 in the example below to see faster motion!
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
// sin and cos together create circular motion
let x = 100 + sin(frame * 0.05) * 50
let y = 100 + cos(frame * 0.05) * 50
fill(rgb(255, 150, 100))
circle(x, y, 20)
}Animation Helper Functions
Bloom provides shortcuts for common animation patterns. These are easier to use than
raw sin() and cos() - just pick the one that matches the motion you want!
oscillate(min, max, speed)
Smoothly swing between min and max
bounce(min, max, speed)
Like oscillate but with bouncy easing
wave(min, max, speed)
Smooth wave motion (similar to oscillate)
pulse(speed)
Pulsing value from 0 to 1 and back
pingpong(t, length)
Bounces between 0 and length
smoothstep(edge0, edge1, x)
Smooth transition between two values
repeat(t, length)
Loops value: 0 to length, then restarts
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
// oscillate: smooth back and forth
fill(coral)
circle(oscillate(30, 170, 0.03), 40, 20)
// bounce: snappy bouncing motion
fill(cyan)
circle(bounce(30, 170, 0.03), 80, 20)
// pulse: size from 0-1 (multiply to scale)
fill(lime)
circle(100, 120, 10 + pulse(0.05) * 20)
// pingpong: goes 0 to 100 and back
fill(pink)
circle(50 + pingpong(frame, 100), 160, 20)
}Easing Functions
Control how animations speed up and slow down. These take a value from 0 to 1 and return a modified value (also 0 to 1):
easeIn(t)
Starts slow, speeds up (accelerate)
easeOut(t)
Starts fast, slows down (decelerate)
easeInOut(t)
Slow start and end, fast middle
spring(t, damping, stiffness)
Bouncy spring motion
Easing makes animations feel more natural. In real life, things don't move at constant speed - they accelerate and decelerate.
- easeIn - Use for objects "leaving" (zoom out, fade out)
- easeOut - Use for objects "arriving" (zoom in, fade in, landing)
- easeInOut - Use for continuous motion that feels organic
- spring - Use for playful, bouncy UI interactions
Making Decisions
Programs can make choices using if.
If a condition is true, the code inside the braces runs.
+ add- subtract* multiply/ divide% remainder (makes numbers loop!)== equals!= not equals> greater than< less than>= greater or equal<= less or equaland both trueor either truenot opposite
Important: Use = to set a value, but == to compare.
x = 5 sets x to 5. x == 5 asks "is x equal to 5?"
What's %? It gives the remainder after division. 7 % 3 = 1 (7 ÷ 3 = 2 remainder 1).
Great for making values loop: frame % 60 goes 0, 1, 2... 59, 0, 1, 2... (restarts every 60).
Order of operations: * and / happen before + and -, just like in math class.
2 + 3 * 4 equals 14, not 20. Use parentheses to be clear: (2 + 3) * 4 = 20.
let x = 0
let speed = 2
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
if x > 100 {
fill(red)
} else {
fill(blue)
}
circle(x, 100, 20)
x = x + speed
if x > 200 {
x = 0
}
}The circle changes color when it passes the middle of the canvas.
You can add more branches to your decisions:
else= "otherwise, do this instead"else if= "otherwise, check this other condition"
if score > 90 {
fill(green) // Great score!
} else if score > 70 {
fill(yellow) // Good score
} else {
fill(red) // Needs improvement
}
How it works: The computer checks each condition from top to bottom. As soon as one is true, it runs that code and skips the rest. If none are true, it runs the else code (if you have one).
Intermediate Loops
Loops let you repeat things without writing the same code over and over.
A for loop is perfect for drawing patterns.
The .. symbol creates a range of numbers:
0..5
0..5. Want 10? Use 0..10.The number after
.. is exactly how many times it repeats!Why start at 0? Computers count from 0, not 1. So
0..5 gives you: 0, 1, 2, 3, 4 (that's 5 numbers).
Same start and end: 0..0 or 5..5 produces zero iterations (nothing happens).
for i in 5..5 { print(i) } // Prints nothing - the range is empty!
Backwards range: 5..0 also produces zero iterations. Ranges only count up.
for i in 5..0 { print(i) } // Prints nothing - can't count backwards!
// To count down, you can do math instead:
for i in 0..5 {
let n = 4 - i // n goes 4, 3, 2, 1, 0
print(n)
}
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
for i in 0..5 {
fill(blue)
circle(40 + i * 30, 100, 20)
}
}
The loop runs 5 times. Each time, i takes the next value (0, then 1, then 2...).
We use i to position each circle differently.
Nested Loops
Put a loop inside a loop to create grids. The inner loop runs completely for each step of the outer loop:
row=0: col=0, col=1, col=2, col=3, col=4row=1: col=0, col=1, col=2, col=3, col=4row=2: col=0, col=1, col=2, col=3, col=4...and so on. This creates a 5x5 grid = 25 circles!
Another way to create colors using 3 numbers:
- Hue (0-360) = which color on the rainbow: 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=purple, 360=red again
- Saturation (0-100) = how colorful: 0=gray, 100=vivid
- Lightness (0-100) = how bright: 0=black, 50=normal, 100=white
HSL is great for creating color gradients because changing just the hue cycles through the rainbow!
Bloom also has hsb(h, s, b) which uses Brightness instead of Lightness:
hsl(0, 100, 50)= pure red (Lightness 50 = normal)hsb(0, 100, 100)= pure red (Brightness 100 = full brightness)
When to use which? HSL is often easier because 50 lightness = normal color. HSB is common in design tools like Photoshop. Both work great - pick whichever feels more natural!
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
for row in 0..5 {
for col in 0..5 {
let x = 30 + col * 35
let y = 30 + row * 35
// Change hue based on position for rainbow effect
fill(hsl(row * 40 + col * 20, 70, 60))
circle(x, y, 12)
}
}
}Looping Over Arrays
You can also loop directly over the items in an array using for item in array:
By index (when you need the position):
let colors = [red, green, blue]
for i in 0..len(colors) {
fill(colors[i]) // i = 0, 1, 2
circle(50 + i * 40, 100, 15)
}
By item (simpler when you just need the values):
let colors = [red, green, blue]
for color in colors {
fill(color) // color = red, then green, then blue
circle(50, 100, 15)
}
Use for item in array when you just need each value. Use for i in 0..len(array) when you need the index number too.
The grid() Helper
For grids, Bloom provides a grid() helper function that handles the math for you:
grid(cols, rows, cellWidth, cellHeight, fn(col, row, x, y) {
// col = column number (0, 1, 2...)
// row = row number (0, 1, 2...)
// x, y = center position of this cell
circle(x, y, 10)
})
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Create a 5x4 grid with 40x50 pixel cells
grid(5, 4, 40, 50, fn(col, row, x, y) {
fill(hsl(col * 50 + row * 30, 70, 60))
circle(x, y, 15)
})
}
grid() is a shortcut for nested loops. It calculates the center x,y position of each cell for you.
While Loops
A while loop keeps running as long as a condition is true. Use it when you don't know how many times you need to repeat:
let x = 0
while x < 200 {
circle(x, 100, 10)
x = x + 30 // Move right each time
}
// Draws circles until x reaches 200
- for loop: When you know how many times to repeat.
for i in 0..10 - while loop: When you repeat until something happens.
while x < 200
Most of the time, for loops are simpler. Use while when the number of repetitions depends on a condition.
Shortcut: x++ and x--
Instead of writing x = x + 1, you can use x++ as a shortcut:
let count = 0 count++ // Same as: count = count + 1 (now count is 1) count++ // count is now 2 count-- // Same as: count = count - 1 (now count is 1)
Functions
When you find yourself writing the same code in multiple places, it's time to make a function. Functions bundle up code so you can reuse it.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
flower(50, 50)
flower(100, 120)
flower(150, 70)
}
fn flower(x, y) {
fill(pink)
circle(x - 15, y, 12)
circle(x + 15, y, 12)
circle(x, y - 15, 12)
circle(x, y + 15, 12)
fill(yellow)
circle(x, y, 10)
}
Now you can draw flowers anywhere by just calling flower(x, y).
Try adding more flowers or changing the flower function.
The names in parentheses (like x, y) are placeholders that take on different values each time you call the function:
flower(50, 50)
→
Inside function: x=50, y=50
flower(100, 120)
→
Inside function: x=100, y=120
flower(150, 70)
→
Inside function: x=150, y=70
Think of it like a recipe: "Add [amount] of sugar" - the recipe works no matter what amount you use. x and y are like those blanks that get filled in when you call the function.
Returning Values
Functions can also send back (return) a value. Use return to give back the result:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Our function calculates distance, then returns it
let d = distance(0, 0, mouseX, mouseY)
fill(black)
text("Distance from corner: " + round(d), 20, 100)
// Use the returned value to size a circle
fill(coral)
circle(100, 100, d / 3)
}
// This function RETURNS a number
fn distance(x1, y1, x2, y2) {
let dx = x2 - x1
let dy = y2 - y1
return sqrt(dx * dx + dy * dy)
}Functions that DO something (like flower) don't need return - they draw or change things.
Functions that CALCULATE something (like distance) use return to send back the answer.
Built-in functions like sqrt(), random(), and len() all return values. That's why you can write let x = random(100).
Arrays (Lists)
An array is a list that holds multiple values. Perfect for storing collections of things like positions, colors, or scores.
Use square brackets [ ] to create an array:
let colors = [red, blue, green] let scores = [10, 25, 42, 8] let empty = [] // An empty array
Accessing Items
Get items by their position (called "index"). Remember: counting starts at 0!
If you try to access an index that doesn't exist, you'll get nil (nothing):
let colors = [red, blue, green] // Has indices 0, 1, 2 colors[5] // nil - index 5 doesn't exist! colors[-1] // nil - negative indices don't work
To get the last item: Use array[len(array) - 1]
let colors = [red, blue, green] let last = colors[len(colors) - 1] // green (the last item)
Array Functions
len(array)
How many items? len(colors) → 3
push(array, item)
Add to end: push(colors, yellow)
pop(array)
Remove last item and return it
choose(array)
Pick a random item
shuffle(array)
Randomize the order
includes(array, item)
Check if item exists (true/false)
indexOf(array, item)
Find position of item (-1 if not found)
reverse(array)
Flip the order
sort(array)
Put in order (numbers or alphabetical)
Create arrays of numbers automatically instead of typing them out:
range(5) // [0, 1, 2, 3, 4] - 5 numbers starting at 0 range(2, 6) // [2, 3, 4, 5] - from 2 up to (not including) 6 range(0, 10, 2) // [0, 2, 4, 6, 8] - count by 2s
Great for loops: for i in range(5) is the same as for i in 0..5
push() and pop() modify the original array - they don't create a new one:
let nums = [1, 2, 3] push(nums, 4) // nums is now [1, 2, 3, 4] let last = pop(nums) // last = 4, nums is now [1, 2, 3]
Note: push() returns nothing (nil). To add and get the new array, just use push then use the array: push(arr, item) then use arr.
let sizes = [10, 20, 30, 40, 50]
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Loop through the array
for i in 0..len(sizes) {
fill(hsl(i * 60, 70, 60))
circle(40 + i * 35, 100, sizes[i])
}
}Building Arrays Dynamically
You can create an empty array and add items as your program runs:
let trail = []
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 40))
// Add current mouse position to the end
push(trail, {x: mouseX, y: mouseY})
// Keep only the last 20 positions
// We trim from the start by rebuilding the array
if len(trail) > 20 {
let newTrail = []
for i in (len(trail) - 20)..len(trail) {
push(newTrail, trail[i])
}
trail = newTrail
}
// Draw trail - older positions are smaller/fainter
for i in 0..len(trail) {
let pos = trail[i]
let progress = i / len(trail) // 0 = oldest, 1 = newest
fill(rgba(255, 150, 100, progress))
circle(pos.x, pos.y, 3 + progress * 8)
}
}Objects (Grouped Data)
An object lets you group related values together with labels. Think of it as a labeled container - instead of numbered slots (like arrays), each value has a name.
Array: Values accessed by number (position)
let colors = ["red", "blue", "green"] colors[0] // "red" - first item
Object: Values accessed by name (label)
let ball = {x: 100, y: 50, size: 20}
ball.x // 100 - the x value
Creating Objects
Use curly braces { } with name: value pairs:
let player = {
x: 100,
y: 100,
speed: 5,
color: blue
}
Accessing Values
Use a dot . followed by the property name:
let ball = {x: 100, y: 100, size: 30}
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
fill(coral)
circle(ball.x, ball.y, ball.size)
}Changing Values
You can update object properties just like variables:
let ball = {x: 100, y: 100}
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Move ball toward mouse
ball.x = ball.x + (mouseX - ball.x) * 0.1
ball.y = ball.y + (mouseY - ball.y) * 0.1
fill(teal)
circle(ball.x, ball.y, 25)
}Adding New Properties
You can add new properties to an object at any time:
let ball = {x: 100, y: 100}
ball.speed = 5 // adds a new "speed" property
ball.color = red // adds a new "color" property
When to Use Objects
Objects are perfect when you have related data that belongs together:
- Position:
{x: 100, y: 50} - Circle:
{x: 100, y: 50, radius: 20, color: red} - Player:
{x: 0, y: 0, speed: 5, score: 0}
Strings (Text)
A string is text wrapped in quotes. You can combine, split, and transform strings.
Creating Strings
let name = "Bloom" let greeting = "Hello, world!"
String Interpolation
Put variables inside strings using {curly braces}:
let name = "Artist"
let score = 42
print("Hello, {name}!")
print("Your score is {score} points")When Bloom sees {something} inside quotes, it replaces it with the value of that variable:
let x = 100
let y = 50
print("Position: {x}, {y}") // Prints: Position: 100, 50
You can even put expressions inside the braces:
let a = 5
print("Double: {a * 2}") // Prints: Double: 10
Combining Strings
Use + to join strings together:
let first = "Hello" let second = "World" let message = first + " " + second // "Hello World"
{braces} when:
- Putting a variable inside a sentence
- Building messages with multiple values
"Score: {score} points"
+ when:
- Combining separate string variables
- Building strings piece by piece
firstName + " " + lastName
Both work! Pick whichever feels clearer to you.
When you use + with a string, numbers automatically become text:
"Score: " + 42 // "Score: 42" "Position: " + x + ", " + y // "Position: 100, 50"
Watch out: + means different things based on context:
5 + 3 // 8 (math - both are numbers) "5" + "3" // "53" (text - joins strings) "Score: " + 5 // "Score: 5" (text - number becomes string)
Accessing Characters
Get individual characters using bracket notation, just like arrays:
let word = "BLOOM" word[0] // "B" - first character (index 0) word[1] // "L" - second character word[4] // "M" - fifth character (index 4)
You can read characters, but you cannot change them directly:
let word = "hello" word[0] // "h" - this works (reading) word[0] = "H" // ERROR - can't modify strings like this
Instead: Build a new string using concatenation or string functions.
String Functions
len(string)
How many characters? len("hello") → 5
upper(string)
UPPERCASE: upper("hi") → "HI"
lower(string)
lowercase: lower("HI") → "hi"
trim(string)
Remove spaces from ends
split(string, separator)
Break into array: split("a,b,c", ",") → ["a", "b", "c"]
join(array, separator)
Combine array: join(["a","b"], "-") → "a-b"
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
let message = "BLOOM"
let letters = split(message, "")
// Draw each letter with different color
for i in 0..len(letters) {
fill(hsl(i * 60, 80, 60))
text(letters[i], 30 + i * 35, 110)
}
}Mouse & Touch
Bloom tracks the mouse position in two variables: mouseX and mouseY.
You can also check if the mouse button is pressed with mousePressed.
Your finger works just like a mouse:
mouseXandmouseY= where your finger is touchingmousePressed= true while your finger is on the screen- Drag your finger to "draw" or move things around
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
if mousePressed {
fill(red)
} else {
fill(blue)
}
circle(mouseX, mouseY, 30)
}Move your mouse (or finger) over the canvas. Tap or click to change the color.
Drawing App
Here's a simple drawing program. Notice we removed background() from draw,
so each frame builds on the last.
fn setup() {
size(200, 200)
background(white)
}
fn draw() {
if mousePressed {
fill(blue)
noStroke()
circle(mouseX, mouseY, 8)
}
}Multi-Touch (Advanced)
For tablets and phones, Bloom can track multiple fingers at once using the touches array.
Each touch point has x, y, and id properties.
On desktop with a mouse, touches contains one point when clicking and is empty otherwise. Use mouseX/mouseY for desktop mouse tracking.
touches |
Array of all current touch points: [{x: 100, y: 50, id: 0}, ...] |
touchCount |
How many fingers are currently touching the screen |
Each item in touches is an object with three properties you access with a dot:
for touch in touches {
touch.x // x position of this finger
touch.y // y position of this finger
touch.id // unique number for tracking this finger
}
The .id stays the same for a finger as it moves, useful for tracking individual fingers.
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
// Draw a circle for each finger
for touch in touches {
fill(rgb(255, 150, 100))
circle(touch.x, touch.y, 30)
}
// Show how many fingers are touching
fill(white)
text("Fingers: " + touchCount, 10, 20)
}Try this on a phone or tablet with multiple fingers. Each finger creates its own circle.
Keyboard
Use keyPressed to check if any key is down,
and key to see which key it is.
let x = 100
let y = 100
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
if keyPressed {
if key == "ArrowUp" { y = y - 3 }
if key == "ArrowDown" { y = y + 3 }
if key == "ArrowLeft" { x = x - 3 }
if key == "ArrowRight" { x = x + 3 }
}
fill(green)
circle(x, y, 20)
}
Tap or click on the canvas first to give it focus, then use arrow keys to move.
On mobile? Keyboard examples work best with a Bluetooth keyboard. Try the touch-friendly mouse examples above, or use the code keyboard button { } in the bottom-right to type code.
Advanced Noise
Perlin noise is a special kind of randomness that flows smoothly.
Unlike random() which jumps around, noise()
creates organic, natural-looking variation. It's perfect for terrain,
clouds, textures, and organic movement.
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
for i in 0..40 {
let x = i * 5
let n = noise(i * 0.1 + frame * 0.02)
let y = 100 + n * 80 - 40
fill(hsl(200 + n * 60, 80, 60))
circle(x, y, 4)
}
}
The noise() function takes 1 to 3 numbers and returns a value between 0 and 1.
Nearby inputs give similar outputs, which is what makes it flow smoothly.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
for y in 0..20 {
for x in 0..20 {
let n = noise(x * 0.2, y * 0.2, frame * 0.01)
fill(hsl(0, 0, n * 100))
rect(x * 10, y * 10, 10, 10)
}
}
}Color Palettes
Choosing colors that work well together is hard. Bloom includes several
curated color palettes you can use right away with palette().
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
for i in 0..5 {
fill(palette("sunset", i))
circle(30 + i * 35, 60, 15)
}
for i in 0..5 {
fill(palette("ocean", i))
circle(30 + i * 35, 100, 15)
}
for i in 0..5 {
fill(palette("forest", i))
circle(30 + i * 35, 140, 15)
}
}Available palettes:
Blending Colors
Use lerpColor() to smoothly blend between two colors.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
for i in 0..10 {
let t = i / 9
let c = lerpColor("#ff6b6b", "#4ecdc4", t)
fill(c)
rect(i * 20, 80, 20, 40)
}
}Complex Shapes
Beyond basic shapes, Bloom provides polygon() and star()
for more interesting geometry.
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
fill(rgb(100, 200, 255))
polygon(60, 100, 30, 6)
fill(rgb(255, 200, 100))
polygon(140, 100, 30, 8)
}
polygon(x, y, radius, sides) draws a regular polygon.
Try different numbers of sides: 3 for triangle, 5 for pentagon, 6 for hexagon.
Stars
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(20, 20, 40))
fill(rgb(255, 220, 100))
star(60, 100, 35, 15, 5)
fill(rgb(255, 150, 200))
star(140, 100, 35, 20, 8)
}
star(x, y, outerRadius, innerRadius, points) draws a star shape.
The inner radius determines how pointy the star is.
Arcs and Curves
For more complex shapes, Bloom provides arcs and bezier curves:
arc(x, y, w, h, start, stop)
Draw part of an ellipse. Angles in radians (0 to TWO_PI)
bezier(x1, y1, cx1, cy1, cx2, cy2, x2, y2)
Smooth curve from (x1,y1) to (x2,y2) with control points
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Arc: partial circle (like a pie slice)
fill(coral)
arc(60, 100, 80, 80, 0, PI) // Half circle
// Bezier curve: smooth S-shape
noFill()
stroke(blue)
strokeWeight(3)
bezier(120, 50, 180, 50, 120, 150, 180, 150)
}
Arcs use radians: PI = half circle, TWO_PI = full circle. Bezier curves need practice - the control points (cx1, cy1, cx2, cy2) shape the curve.
Advanced Easing and Motion
Real things don't move at constant speed. They accelerate, decelerate, and bounce. Bloom provides easing functions and oscillators to create natural-feeling motion.
oscillate(min, max)
Smooth back-and-forth between two values
bounce(min, max)
Like oscillate but with a bouncy feel
pulse(speed)
Returns 0-1 pulsing value for effects
wave(freq, amp)
Sine wave for wavy motion effects
spring(target)
Bouncy motion toward a target value
pingpong(t, length)
Value bounces between 0 and length
Oscillators: Automatic Movement
These functions create automatic movement that changes over time (based on the frame count). You don't need to manage any state - just call them and they animate!
oscillate(min, max, speed?, offset?)
Smoothly moves between min and max. The optional speed
controls how fast (default: 1), and offset shifts the starting point.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
let size1 = oscillate(20, 50)
let size2 = oscillate(20, 50, 1.5)
let size3 = oscillate(20, 50, 2, 1)
fill(red)
circle(50, 100, size1)
fill(green)
circle(100, 100, size2)
fill(blue)
circle(150, 100, size3)
}bounce(min, max, speed?)
Like oscillate but uses absolute sine for a bouncy, elastic feel:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Bouncing ball effect
let y = bounce(50, 150, 2)
fill(orange)
circle(100, y, 25)
}pulse(speed?)
Returns a value from 0 to 1 that pulses over time. Perfect for fading effects:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Pulsing opacity
let p = pulse(1)
let alpha = p * 255
fill(rgba(100, 150, 255, alpha))
circle(100, 100, 60)
}wave(frequency?, amplitude?, offset?)
A sine wave returning values from -amplitude to +amplitude.
Great for wavy motion:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
noStroke()
// Draw wavy dots
for i in 0..10 {
let x = 20 + i * 18
let y = 100 + wave(1, 30, i * 0.5)
fill(coral)
circle(x, y, 12)
}
}spring(target, stiffness?, damping?)
Creates a bouncy spring motion toward a target value. Higher stiffness = faster spring, higher damping = less bounce:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Spring toward 100
let x = spring(100, 0.15, 0.9)
fill(purple)
circle(100, x, 30)
}Utility Functions
pingpong(t, length)
Takes a value t and bounces it between 0 and length.
Useful with frame count:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// pingpong makes frame bounce 0->100->0->100...
let x = pingpong(frame, 100)
fill(teal)
circle(50 + x, 100, 25)
}repeat(t, length)
Wraps a value between 0 and length (like modulo but always positive):
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Repeating position
let x = repeat(frame * 2, 200)
fill(coral)
circle(x, 100, 20)
}smoothstep(edge0, edge1, x)
Returns 0 if x < edge0, 1 if x > edge1, and a smooth S-curve in between. Perfect for smooth transitions:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Smooth transition based on mouseX
let t = smoothstep(50, 150, mouseX)
// Interpolate color and size
let r = 50 + t * 200
let size = 20 + t * 40
fill(rgb(r, 100, 150))
circle(100, 100, size)
}Easing Functions
Easing functions transform linear motion (0 to 1) into more interesting curves. Pass a value from 0-1 and get back a curved version.
easeIn(t)
Starts slow, ends fast (accelerates)
easeOut(t)
Starts fast, ends slow (decelerates)
easeInOut(t)
Smooth start and end (S-curve)
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
let t = (frame % 120) / 120
let linear = t * 160
let easeI = easeIn(t) * 160
let easeO = easeOut(t) * 160
let easeIO = easeInOut(t) * 160
stroke(gray)
strokeWeight(1)
line(20, 40, 20, 160)
line(180, 40, 180, 160)
noStroke()
fill(red)
circle(20 + linear, 50, 8)
fill(green)
circle(20 + easeI, 90, 8)
fill(blue)
circle(20 + easeO, 130, 8)
fill(purple)
circle(20 + easeIO, 170, 8)
}Advanced Transforms
Transforms let you move, rotate, and scale your entire drawing system. Instead of calculating complex positions, you can move the "paper" itself.
Imagine your canvas is a piece of paper:
translate(x, y)= slide the paper to a new positionrotate(angle)= spin the paper aroundscale(x, y)= zoom in (values > 1) or out (values < 1). Usescale(2, 2)to double size
Important: After a transform, all drawing happens relative to the new position/rotation!
Translate (Move)
translate() moves the origin point. This is useful for drawing complex shapes around a center point:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Move origin to center of canvas
translate(100, 100)
// Now (0, 0) is at the center!
fill(blue)
circle(0, 0, 30)
// These draw relative to center
fill(red)
circle(50, 0, 15)
circle(-50, 0, 15)
}Rotate
rotate(angle) spins everything around the current origin.
Angles are in radians (PI = 180 degrees, TWO_PI = 360 degrees):
rotate(45) does NOT rotate 45 degrees - it rotates 45 radians (about 7 full circles)!
rotate(PI / 4)= 45 degreesrotate(PI / 2)= 90 degreesrotate(PI)= 180 degreesrotate(TWO_PI)= 360 degrees (full circle)
Quick formula: degrees * PI / 180 converts to radians.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Move to center, then rotate
translate(100, 100)
rotate(frame * 0.02)
// This rectangle spins around the center
fill(coral)
rect(-40, -10, 80, 20)
}Push and Pop (Saving Transform State)
Use pushMatrix() and popMatrix() to save and restore your transform state.
Think of it like a stack of saved positions:
pushMatrix()
saves current state to stack
popMatrix()
restores most recent saved state
Each pushMatrix() adds to the stack. Each popMatrix() removes the top item
and restores that saved position/rotation/scale.
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// Draw first rotating shape
pushMatrix()
translate(60, 100)
rotate(frame * 0.03)
fill(blue)
rect(-20, -20, 40, 40)
popMatrix()
// Back to normal! Draw second shape
pushMatrix()
translate(140, 100)
rotate(-frame * 0.02)
fill(coral)
rect(-25, -25, 50, 50)
popMatrix()
}Without push/pop, transforms "stack up" and affect everything after:
// Without push/pop (PROBLEM!) translate(50, 50) // Move right and down circle(0, 0, 20) // Circle at (50, 50) - OK! translate(50, 50) // Move MORE right and down circle(0, 0, 20) // Circle at (100, 100) - Wait, why so far?
With push/pop (FIXED!):
pushMatrix() translate(50, 50) circle(0, 0, 20) // Circle at (50, 50) popMatrix() // Reset! Back to (0, 0) pushMatrix() translate(50, 50) circle(0, 0, 20) // Circle at (50, 50) - Correct! popMatrix()
Think of it like bookmarks: pushMatrix() = save my place, popMatrix() = go back to saved place.
Grids
Grids are essential for patterns, tiles, and organized layouts.
Bloom offers two ways to create grids: nested loops (flexible) or the grid() function (simpler).
The grid() Function
The easiest way to draw a grid! Just tell it how many columns and rows, the cell size, and what to draw in each cell:
Your callback function receives 4 values:
col= which column (0, 1, 2...)row= which row (0, 1, 2...)x= center x position for this celly= center y position for this cell
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
// 5 columns, 5 rows, 40px wide, 40px tall
grid(5, 5, 40, 40, fn(col, row, x, y) {
let hue = (col + row) * 30
fill(hsl(hue, 70, 60))
circle(x, y, 15)
})
}
Notice how you don't need to calculate positions - x and y are
automatically set to the center of each cell!
Interactive Grid
Use the callback parameters to create interactive effects:
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(30, 30, 50))
grid(8, 8, 25, 25, fn(col, row, x, y) {
// Distance from mouse
let d = dist(x, y, mouseX, mouseY)
let size = max(3, 20 - d * 0.15)
fill(coral)
circle(x, y, size)
})
}Using Nested Loops
For more control, use nested for loops. You calculate positions yourself,
but get complete flexibility:
fn setup() {
size(200, 200)
}
fn draw() {
background(white)
for row in 0..5 {
for col in 0..5 {
let hue = (col + row) * 30
fill(hsl(hue, 70, 60))
circle(20 + col * 40, 20 + row * 40, 15)
}
}
}Animated Grid
Combined with noise and animation, grids can create mesmerizing effects:
fn setup() {
size(200, 200)
}
fn draw() {
background(rgb(20, 20, 40))
grid(8, 8, 25, 25, fn(col, row, x, y) {
let n = noise(col * 0.3, row * 0.3, frame * 0.02)
let r = 5 + n * 15
fill(palette("ocean", floor(n * 5)))
circle(x, y, r)
})
}Debugging Your Code
When something isn't working, here's how to figure out what's wrong:
Use print() to See Values
The most powerful debugging tool is print(). It shows values in the console (bottom panel).
Step 1: Add print() to see what values your variables actually have:
let x = 100
print("x is: " + x) // Shows: x is: 100
print("mouseX: " + mouseX) // Shows mouse position
Step 2: Check if your code is running at all:
fn draw() {
print("draw is running!") // If you see this, draw() is working
// ... rest of code
}
Step 3: Print inside conditions to see which branch runs:
if x > 100 {
print("x is big")
} else {
print("x is small")
}
Common Debugging Checks
print(x)
Show a variable's value
print("At line 5")
Check if code reaches this point
print(len(array))
Check array length
print(mouseX + ", " + mouseY)
Check multiple values at once
Since draw() runs 60 times per second, you might flood the console with messages.
To print less often, use the frame counter:
// Only print every 60 frames (once per second)
if frame % 60 == 0 {
print("x is: " + x)
}
Troubleshooting
Stuck? Don't worry - everyone makes mistakes when learning to code! Here are solutions to common problems:
- Make sure you have both
fn setup()andfn draw() - Check that
size()is called inside setup - Your shape might be off-screen - try position (100, 100)
- Add
background(white)at the start of draw
- Check for typos in the variable name
- Make sure you declared it with
let name = value - Variables are case-sensitive:
myVaris different frommyvar
- Check for missing closing brackets
}or parentheses) - Make sure strings have matching quotes:
"text" - Count your opening and closing braces - they should match!
- Use
framein your calculations - it increases each frame - Don't set variables to fixed values inside draw()
- Make sure you're using
draw()not justsetup()
- Call
fill(color)before drawing the shape - Check for
noFill()- it disables fill color - Try a named color like
coralorblue
- Reduce the number of shapes being drawn
- Use smaller loop ranges (e.g.,
0..50instead of0..500) - Avoid nested loops with large ranges
- Check x is between 0 and canvas width (e.g., 0-200)
- Check y is between 0 and canvas height (e.g., 0-200)
- Use
print(x)to see where your shape actually is - Remember: (0, 0) is top-left, not center!
- Check spelling:
cricleshould becircle - Function names are case-sensitive:
Circlewon't work - Make sure custom functions are defined with
fn name() { }
Still stuck? Tap or hover over keywords in the editor to see explanations, check the Glossary below, or try simplifying your code to find where the problem is.
Using Bloom on Different Devices
Bloom works on phones, tablets, and computers. Here are some tips for each:
- Use the code keyboard: Tap the
{ }button at the bottom right to open a special keyboard with programming symbols like parentheses, brackets, and operators - Tap to position cursor: Tap exactly where you want to type in the code editor
- Scroll horizontally: Some code examples are wider than the screen - swipe left/right to see everything
- Use landscape mode: Rotating your device gives you more space to see both code and canvas
- Prev/Next navigation: Use the section links at the bottom of each section to move through the docs
- Hover for hints: Move your mouse over keywords to see what they do
- Resize the canvas: Drag the divider between code and canvas to adjust the view
- Auto-save: Your code is saved automatically - come back anytime to continue
- Dark/Light mode: Click the theme button in the corner to switch
- Works offline: Once loaded, Bloom works without an internet connection
Glossary
New to programming? Here's what these words mean:
let x = 10fn myRecipe() { ... } defines it, myRecipe() uses it.for i in 0..5 repeats 5 times.x > 100 is true when x is bigger than 100.circle(100, 50, 20), the numbers are parameters.size(w, h).[red, blue, green]{x: 100, y: 50} - access with .x or .yball.x, the x is the property.return x + y sends the sum back to where the function was called."" (empty string - still a string, just with no characters) and 0 (a number, not nothing). You get nil when accessing an array index that doesn't exist, or using a variable that hasn't been set."Hello" is a string. Use + to join strings together.true or false. Used in conditions like if x > 10.5 + 3, x * 2, and sin(angle) are all expressions.+, -, *, /, ==, and, or are operators.colors[0] gets the first item (indexes start at 0).Understanding Error Messages
When something goes wrong, Bloom tries to tell you what happened. Here's what those messages mean:
let first.( but forgot to close it. Count your opening and closing parentheses - they should match.{ but forgot to close it. Every { needs a matching }.circle() needs 3 values: x, y, and radius.() after a variable name by mistake.fn draw() {
circle(100, 100, 40)
// Missing closing }
fn draw() {
circle(100, 100, 40)
} // Added closing brace
Tip: Every { needs a matching }. If you're lost, try adding } at the end of your code and see if the error changes.
Reference
Here's a quick reference of everything Bloom offers.
Canvas
size(w, h)- Set canvas sizebackground(color)- Clear with color
Shapes
circle(x, y, r)- Circle at x,y with radius rellipse(x, y, w, h)- Oval at x,y with width/heightrect(x, y, w, h)- Rectangle from top-left cornerline(x1, y1, x2, y2)- Line between two pointstriangle(x1, y1, x2, y2, x3, y3)- Triangle with 3 cornersquad(x1, y1, x2, y2, x3, y3, x4, y4)- Quadrilateral with 4 cornerspoint(x, y)- Single pixel pointtext(str, x, y)- Draw text at position
Text Styling
textSize(size)- Set font size in pixelstextFont(name)- Set font ("serif", "sans-serif", "monospace")textAlign(align)- Alignment ("left", "center", "right")
Styling
fill(color),stroke(color),strokeWeight(w),noFill(),noStroke()
Colors
rgb(r, g, b)- RGB color (0-255 each)rgba(r, g, b, a)- RGB + alpha (a: 0-1)hsb(h, s, b)- Hue (0-360), Saturation (0-100), Brightness (0-100)hsl(h, s, l)- Hue, Saturation, Lightness- Named: white, black, red, green, blue, yellow, orange, purple, pink, cyan, gray, navy, teal, coral, gold, mint, sky, violet, salmon, crimson, emerald, midnight
Variables
width,height- Canvas sizeframe- Current frame number (starts at 0)mouseX,mouseY- Mouse/touch positionmousePressed- True when mouse button or finger is downkey,keyPressed- Current key and whether any key is pressedtouches,touchCount- Multi-touch points array and count
Math
sin,cos,tan,atan2(y, x),sqrt,pow,abs,floor,ceil,round,min,maxrandom(),random(max),random(min, max),noise(x, y?, z?)map(val, s1, e1, s2, e2),lerp(a, b, t),dist(x1, y1, x2, y2),clamp(val, min, max)- Constants:
PI,TWO_PI,HALF_PI
Transforms
translate(x, y),rotate(angle),scale(x, y),pushMatrix(),popMatrix()
Language
let x = value,fn name(args) { },if cond { } else { }for i in 0..n { },while cond { },[1, 2, 3],return valx++,x--- Increment/decrement"Hello {name}!"- String interpolation
Advanced Shapes
polygon(x, y, r, sides),star(x, y, r1, r2, pts),quad(x1,y1,x2,y2,x3,y3,x4,y4)arc(x, y, w, h, start, stop),bezier(...)
Animation
oscillate(min, max, speed?, offset?),bounce(min, max, speed?),pulse(speed?)easeIn(t),easeOut(t),easeInOut(t),smoothstep(e0, e1, x)
Grid Helper
grid(cols, rows, cellW, cellH, fn(col, row, x, y) { })- Call function for each cell
Color Helpers
palette(name, i)- Get color i from named palettelerpColor(c1, c2, t)- Blend between colors (t: 0-1)complement(c)- Opposite color on wheelanalogous(c)- Array of 3 similar colorstriadic(c)- Array of 3 evenly spaced colorssplitComplement(c)- Array of 3 balanced colorslighten(c, amt),darken(c, amt)- Adjust brightness (0-100)saturate(c, amt),desaturate(c, amt)- Adjust vividness (0-100)rotateHue(c, deg)- Shift color on wheelmix(c1, c2)- Blend two colors equally
Palettes
- sunset, ocean, forest, fire, pastel, mono, neon, earth, nordic, japan, miami, cyberpunk, vintage
More Colors
- rose, tomato, amber, honey, jade, sage, moss, lime, azure, cobalt, indigo, sapphire, turquoise, aqua, seafoam, lavender, plum, grape, orchid, fuchsia, blush, peach, slate, charcoal, silver, cream, ivory, beige, sand, taupe, brown, coffee, snow, cloud, mist, fog, shadow
Utilities
print(val),len(arr),push(arr, val),pop(arr),constrain(val, min, max)
Array Functions
choose(arr)- Pick random elementshuffle(arr)- Randomly reorderrange(end),range(start, end),range(start, end, step)- Generate sequenceincludes(arr, val),indexOf(arr, val),reverse(arr),sort(arr)join(arr, sep)- Join into string
String Functions
split(str, sep)- Split into arrayupper(str),lower(str),trim(str)
Angle Conversion
radians(deg)- Convert degrees to radiansdegrees(rad)- Convert radians to degrees
Coming Soon
Features we're planning to add:
- IDE Support - VS Code extension with syntax highlighting, autocomplete, and inline error hints
- LSP Server - Language Server Protocol for editor integration
- Image Loading -
loadImage()andimage()functions - Sound - Audio synthesis and playback
- Particle Systems - Built-in particle emitters
- Vector Math -
vec2andvec3types with operators - Gradients - Linear and radial gradient fills
- Sprites - Sprite sheets and animation
- Video Export - Save animations as video (WebM/MP4)
- Libraries - Import community-created libraries