How we built a custom constraint-solving algorithm that generates 40+ layout candidates, uses seeded randomization, and implements 'boulders before sand' placement for gap-free photo galleries.
The Gallery Problem
Photo galleries face a fundamental tension. Images have varying aspect ratios—portraits, landscapes, squares, panoramas. Grids want uniformity—consistent rows, aligned edges, no gaps.
The naive solution crops everything to squares. This destroys composition. The photographer chose that framing deliberately. Cutting off half the image to fit a grid disrespects the work.
The other extreme—variable row heights based on natural aspect ratios—creates visual chaos. The eye can't find a rhythm. Navigation becomes disorienting.
Framing as Optimization
The insight is to treat layout as an optimization problem. Define what "good" means mathematically: complete space coverage, minimal cropping, visual variety, deterministic output. Then search for layouts that satisfy these constraints.
This reframing transforms an aesthetic judgment into something a computer can solve. The taste is in the constraints, not the search.
Discrete Approximation
Real aspect ratios are continuous—16:9, 4:3, 3:2, and everything between. Grid cells are discrete. We need a mapping from one to the other.
For each image, we enumerate grid sizes that approximately preserve its aspect ratio. A 16:9 image might become 2x1, 3x2, or 4x2 cells, each with different amounts of cropping. The set of acceptable sizes for each image defines our search space.
Packing Intuition
The layout problem resembles bin packing, one of the classic NP-hard problems. We have items of various sizes (images in their quantized grid dimensions) and a container (the gallery space). We want to fill the container with minimal waste.
Exact solutions are computationally infeasible for interesting problem sizes. But we don't need optimal—we need good. Heuristics that produce pleasing layouts in reasonable time are more valuable than perfect solutions that take forever.
Boulders Before Sand
The key heuristic comes from an old puzzle: how do you fit rocks, pebbles, and sand into a jar? Start with the boulders. Then add pebbles in the gaps. Finally, pour sand to fill the remaining space.
For layouts, this means placing large items first. Big images establish structure. Small images fill gaps. This ordering dramatically improves packing efficiency because large items are inflexible—they need contiguous space—while small items can squeeze into whatever remains.
Candidate Generation
Rather than searching for the optimal layout, we generate many candidates quickly and pick the best. Backtracking through possible placements, with randomization for variety, produces dozens of viable layouts in milliseconds.
The randomization is seeded deterministically from the image collection. Same images, same layout—every time. This matters for caching, for user expectations, for reproducible debugging.
Gap Filling
After placing all images, gaps may remain. The packing heuristics are good but not perfect. Several strategies address leftover space.
Adjacent images can expand into gaps, slightly increasing their crop but eliminating visual holes. Decorative elements can fill small gaps—solid colors, gradients, or repeated patterns that complement the images.
The goal is no empty space. Every pixel should contain something intentional.
Scoring Tradeoffs
With many candidate layouts, we need to choose. The scoring function encodes our aesthetic priorities.
Coverage matters most—empty gaps are unacceptable. Among layouts with similar coverage, prefer less cropping—respect the original compositions. Among similar cropping, prefer variety in sizes—visual interest comes from contrast between large and small.
These priorities can be tuned. A formal gallery might prefer uniform sizes. A magazine layout might emphasize dramatic scale differences. The algorithm accommodates different aesthetics by adjusting the scoring weights.
Computational Properties
The algorithm has predictable performance characteristics. Candidate generation is bounded—we stop after N candidates regardless of input size. Layout evaluation is linear in the number of placements. Total complexity is manageable even for large galleries.
Memoization helps when collections change incrementally. Adding one image doesn't require recomputing from scratch—we can often adapt the existing layout with local modifications.
The Design Philosophy
What makes this approach satisfying is how it separates concerns. The constraints express design intent. The algorithm does the search. The scoring encodes aesthetic judgment.
Changing the gallery's character doesn't require new algorithms—just new constraints and scores. Want tighter packing? Adjust the coverage weight. Want less cropping? Adjust the crop penalty. The mathematical machinery stays constant while the design parameters vary.
This separation is powerful for iteration. Designers can explore aesthetics by tweaking numbers rather than rewriting code. The conversation shifts from "how do we implement this layout" to "what layout do we want."
Beyond Galleries
The same pattern applies broadly. Many design problems can be framed as constrained optimization: fitting text into columns, placing icons on a desktop, arranging widgets in a dashboard.
The technique is always the same: define good mathematically, generate candidates, score and select. The specifics change, but the structure remains.
The insight isn't about image galleries. It's about how computers can collaborate with aesthetic judgment—not replacing taste, but amplifying it across a space of possibilities larger than any human could explore manually.