Skip to content

Scenes & Grids

The Scene is your canvas, and the Grid gives it structure. Together they form the foundation of every PyFreeform artwork.

Creating Scenes

From an Image

Scene.from_image() loads a photo and divides it into a grid of cells, each sampling the image's colors and brightness.

from pyfreeform import Scene

scene = Scene.from_image("MonaLisa.jpg", grid_size=40, cell_size=10)

grid_size controls resolution — how many columns of cells across. More cells = more detail:

20 cells across

grid_size=20

40 cells across

grid_size=40

60 cells across

grid_size=60

Cell Ratio

cell_ratio changes cell proportions. A ratio of 2.0 makes cells twice as wide as tall:

Square cells

cell_ratio=1.0 (square)

Wide cells

cell_ratio=2.0 (wide)

Tall cells

cell_ratio=0.5 (tall)

From Scratch

Scene.with_grid() creates a grid with no image data — use position and math to drive visuals:

from pyfreeform import Scene, Palette

colors = Palette.midnight()
scene = Scene.with_grid(cols=15, rows=15, cell_size=22, background=colors.background)

for cell in scene.grid:
    nx, ny = cell.normalized_position
    radius = 0.091 + (nx * ny) * 0.364
    cell.add_dot(radius=radius, color=colors.primary, opacity=0.5 + nx * 0.5)

with_grid basic pattern

Dot size grows with position — no image needed.

Accessing Cells

The grid behaves like a list of lists — index by row, then column:

cell = scene.grid[2][5]       # Cell at row 2, column 5
row  = scene.grid[2]          # Entire row 2 as a list of cells
for cell in scene.grid:       # All cells, row by row
    ...

Grid Selections

The grid offers powerful selection methods for targeting specific cells.

Row & Column

for cell in scene.grid.row(3):        # All cells in row 3
    cell.add_fill(color=colors.primary, opacity=0.4)

for cell in scene.grid.column(6):     # All cells in column 6
    cell.add_fill(color=colors.accent, opacity=0.4)

Row and column highlighting

Row 3 in coral, column 6 in amber, intersection highlighted.

Border

for cell in scene.grid.border(thickness=2):  # (1)!
    cell.add_fill(color=colors.accent, opacity=0.7)
  1. thickness controls how many rows/columns deep the border extends.

Border selection

The outer 2 rows/columns highlighted as a border.

Region

for cell in scene.grid.region(2, 6, 3, 9):  # (1)!
    cell.add_polygon(Polygon.hexagon(size=0.6), fill=colors.primary)
  1. region(row_start, row_end, col_start, col_end) — end is exclusive.

Region selection with hexagons

Hexagons placed only in the selected rectangular region.

Checkerboard & Diagonal

for cell in scene.grid.checkerboard("black"):
    cell.add_polygon(Polygon.diamond(size=0.7), fill=colors.primary)

for cell in scene.grid.checkerboard("white"):
    cell.add_dot(radius=0.15, color=colors.accent)

Checkerboard

Checkerboard: diamonds and dots alternate.

Diagonal

Every 3rd diagonal highlighted with fills.

Merging Cells

Merge a row, column, or rectangular region into a single CellGroup — a virtual surface that spans multiple cells:

title_bar = scene.grid.merge_row(0)
title_bar.add_fill(color=colors.primary, opacity=0.2)
title_bar.add_text("TITLE BAR", at="center", font_size=0.50, color=colors.accent, bold=True)

Merged title bar

Row 0 merged into a CellGroup with text overlay.

A CellGroup has all the same add_* methods as a Cell — it's a full Surface.

Other merge methods

  • grid.merge_col(i) — merge a full column
  • grid.merge(start, end) — merge any rectangular region. Both args are (row, col) tuples, both inclusive.

See also

For the full Scene and Grid API, see Scene and Grid & Cells.

What's Next?

Now that you can create and navigate grids, learn how to read and use each cell's data:

Working with Cells →