Working with Cells¶
Every cell in the grid carries data — brightness, color, position — and you use that data to drive your art.
Cell Data Properties¶
When a scene is created with from_image(), each cell samples the pixels beneath it:
| Property | Type | Range | Description |
|---|---|---|---|
cell.brightness |
float |
0.0 – 1.0 | Perceived luminance (0 = black, 1 = white) |
cell.color |
str |
Hex string | Average color as "#rrggbb" |
cell.rgb |
tuple |
(0-255, 0-255, 0-255) | RGB components |
cell.alpha |
float |
0.0 – 1.0 | Opacity from source image |
No image?
Cells from Scene.with_grid() default to brightness 0.5, color "#808080". Use normalized_position, math, or distance_to() instead.
Brightness-Driven Effects¶
Size by Brightness¶
The most classic effect — bright areas get larger marks:
scene = Scene.from_image("MonaLisa.jpg", grid_size=40, cell_size=10)
for cell in scene.grid:
r = cell.brightness * 0.48
if r > 0.03:
cell.add_dot(radius=r, color="#ffffff")
Rotation by Brightness¶
Drive shape rotation from the image:
for cell in scene.grid:
rotation = cell.brightness * 90
size = 0.4 + cell.brightness * 0.4
cell.add_polygon(
Polygon.square(size=size),
fill=cell.color,
opacity=0.7,
rotation=rotation,
)
Color Fill¶
The most direct approach — fill each cell with its sampled color:
Neighbors and Edge Detection¶
Every cell knows its 8 neighbors:
cell.above # Cell | None
cell.below # Cell | None
cell.left # Cell | None
cell.right # Cell | None
cell.above_left # Cell | None (diagonal)
cell.above_right # Cell | None
cell.below_left # Cell | None
cell.below_right # Cell | None
Comparing a cell's brightness to its neighbors reveals edges:
for cell in scene.grid:
edge = 0.0
if cell.right:
edge += abs(cell.brightness - cell.right.brightness)
if cell.below:
edge += abs(cell.brightness - cell.below.brightness)
edge = min(edge * 3, 1.0) # Amplify
if edge > 0.1:
cell.add_dot(radius=edge * 0.4375, color="#00d9ff", opacity=edge)
Position-Based Effects¶
Distance to a Point¶
cell.distance_to() measures pixel distance to any cell, point, or coordinate:
center = scene.grid[10, 10]
max_d = center.distance_to(scene.grid[0, 0])
for cell in scene.grid:
d = cell.distance_to(center)
t = 1 - (d / max_d) # 1 at center, 0 at corners
cell.add_dot(radius=t * 0.4375, color=colors.primary, opacity=0.3 + t * 0.7)
Normalized Position¶
cell.normalized_position returns (nx, ny) where both range from 0.0 (top-left) to 1.0 (bottom-right):
for cell in scene.grid:
nx, ny = cell.normalized_position
size = 0.3 + ny * 0.5 # Grow downward
# ... color gradient from left to right
Sub-Cell Sampling¶
For higher-detail effects, sample the image at multiple points within each cell:
for cell in scene.grid:
for (rx, ry), pos in [
((0.25, 0.25), "top_left"),
((0.75, 0.25), "top_right"),
((0.25, 0.75), "bottom_left"),
((0.75, 0.75), "bottom_right"),
]:
color = cell.sample_hex(rx, ry) # (1)!
brightness = cell.sample_brightness(rx, ry)
cell.add_dot(at=pos, radius=brightness * 0.219, color=color)
sample_hex(rx, ry)reads the pixel at relative position (rx, ry) within the cell. Only works withfrom_image().
Filtering with where()¶
Use grid.where() to select cells by any condition:
# Dark cells get fills, bright cells get stars
for cell in scene.grid.where(lambda c: c.brightness < 0.4):
cell.add_fill(color=cell.color, opacity=0.5)
for cell in scene.grid.where(lambda c: c.brightness >= 0.4):
cell.add_polygon(
Polygon.star(points=4, size=0.3 + cell.brightness * 0.5),
fill=cell.color, opacity=0.7,
)
What's Next?¶
You've mastered reading cell data. Now learn all the entity types you can place in cells: