HomeJournalThis post

Image Quilting for Seamless Canvas Textures

Choose generated patches by overlap cost, backtrack minimum-error seams, and export a reproducible Canvas texture edition.

JP
JP Casabianca
AI Engineer and Product Designer · full-stack delivery · Bogotá

Image quilting builds a larger texture from overlapping source patches, then hides each join along a low-error boundary instead of blending a rectangular seam. This tutorial generates its own exemplar, freezes candidate selection, compares naive tiling with overlap and cut variants, and publishes every seam receipt.

Image quilting starts with an owned exemplar

Generate a small source texture whose motifs, palette, spacing, and edges are known. Dots, fibers, waves, tiles, scratches, or layered noise can provide local neighborhoods without importing copyrighted material or hiding where the visual vocabulary came from.

The Image Quilting project and paper are primary sources for patch-based texture synthesis with overlap and boundary cuts. This browser study implements a bounded teaching variation rather than claiming reproduction of every algorithmic detail.

The exemplar used by the image quilting artifact is generated during execution from a fixed seed. Its seam energy and hash proxy describe only that fixture, and no metric is presented as proof of human perceptual quality.

The exemplar is a constructed 32 by 32 pixel field built from three deterministic waves. Every selected patch references explicit source coordinates, so image quilting has a rights-clear and reproducible input ledger. Its pixel function and seed are part of the edition identity.

Choose patch and overlap as artistic scales

Patch size determines how much source structure survives together, while overlap determines how much context guides placement and how much room the cut has to avoid visible disagreement. Large patches preserve motifs but may repeat; small patches increase recombination and can destroy coherent forms.

Define patch width, height, horizontal and vertical overlap, output dimensions, scan order, edge wrapping, and source-coordinate bounds. Use dimensions divisible by neither patch step nor source size in tests so partial edges and final crops receive real coverage.

Image quilting becomes an art-direction tool when those scales correspond to the exemplar's visual rhythm. A woven source, cloudy field, and geometric tile should not inherit the same patch preset merely because one demo looked smooth.

Candidate patches are sampled on a two-pixel source grid. Their overlap SSD sums RGB differences only where output pixels already exist and normalizes by the compared pixel count before ranking. Stable ordering resolves equal costs before the seeded near-best choice.

Patch placement ledgerEach output slot selects a generated-exemplar coordinate after overlap SSD ranking and seeded near-best choice.source (sx, sy)output (ox, oy)rank + seedoverlap SSD
One placement record
Destinationoutput x and y
Candidatesource x and y
Selectionrank, seed, and near-best tolerance
Costnormalized RGB overlap SSD
Cutvertical seam path and accumulated cost
Figure 1: Patch lineage makes repetition and visible joins traceable to concrete choices.

Score candidates on the pixels that already exist

At each output position, compare a candidate's overlap against pixels already committed above, left, or both. Sum a documented difference such as squared error in a named color space, normalize for compared area, and reject non-finite or out-of-bounds candidates.

Choose from candidates within a declared tolerance of the best cost using a seeded random generator. Pure minimum selection can repeat the same patch, while unconstrained randomness exposes seams; seeded near-best selection balances variation and reproducibility.

The image quilting placement map should retain source coordinates, candidate costs, selected rank, seed state, overlap region, and placement index. A final texture without this lineage cannot explain why a repeated motif or abrupt join appeared.

A fixed seed chooses among candidates within eight percent of the best cost for the cut edition. Random and strict-lowest baselines use the same exemplar, patch size, overlap, output dimensions, and seed.

Runnable artifact — Generate a rights-clear source texture, place seeded candidates, and expose the overlap plus minimum-error seam receipt.

<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Image quilting texture studio</title><style>:root{color-scheme:dark}*{box-sizing:border-box}body{font:16px/1.5 system-ui;max-width:960px;margin:auto;padding:24px;background:#101b24;color:#f5f3ea}button,select,a{font:inherit;padding:.72rem 1rem;margin:.35rem;border:2px solid #7ce5c3;border-radius:.6rem;background:#172b36;color:#fff}button:focus-visible,select:focus-visible,a:focus-visible{outline:4px solid #ffd66b;outline-offset:3px}canvas{width:100%;height:auto;border:1px solid #78909c;background:#081116}output{display:block;white-space:pre-wrap;padding:1rem;background:#081116;border-radius:.6rem;margin-top:1rem;overflow-wrap:anywhere}small{display:block;color:#b8cbd4}.exports{display:flex;flex-wrap:wrap;gap:.5rem}@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}</style><main><h1>Image quilting texture studio</h1><p>A generated exemplar supplies every source patch. The lab evaluates seeded candidates, computes overlap SSD, backtracks minimum-error seams, and renders matched baselines.</p><button id="quiltButton">Quilt texture</button><canvas id="quilt" width="640" height="360" aria-label="Exemplar, random, lowest-overlap, and minimum-error-cut textures"></canvas><div class="exports"><a id="pngExport" download="quilt.png">Export PNG</a><a id="jsonExport" download="quilt.json">Export seam ledger</a></div><output id="receipt" aria-live="polite"></output></main><script>const EW=32,EH=32,OW=64,OH=48,P=12,O=3,idx=(x,y,w)=>(y*w+x)*4,seeded=s=>()=>((s=Math.imul(s,1664525)+1013904223>>>0)/4294967296),sha=async bytes=>[...new Uint8Array(await crypto.subtle.digest("SHA-256",bytes))].map(x=>x.toString(16).padStart(2,"0")).join("");function exemplar(){const p=new Uint8ClampedArray(EW*EH*4);for(let y=0;y<EH;y++)for(let x=0;x<EW;x++){const i=idx(x,y,EW),wave=Math.sin(x*.55)+Math.cos(y*.43)+Math.sin((x+y)*.27);p.set([70+wave*24,120+Math.sin(y*.38)*35,145+Math.cos(x*.31)*42,255],i)}return p}function ssd(out,source,ox,oy,sx,sy){let cost=0,count=0;for(let py=0;py<P;py++)for(let px=0;px<P;px++){const exists=(ox>0&&px<O)||(oy>0&&py<O);if(!exists||ox+px>=OW||oy+py>=OH)continue;const a=idx(ox+px,oy+py,OW),b=idx(sx+px,sy+py,EW);for(let c=0;c<3;c++)cost+=(out[a+c]-source[b+c])**2;count++}return cost/Math.max(1,count)}function verticalSeam(out,source,ox,oy,sx,sy){const grid=Array.from({length:P},()=>Array(O).fill(0));for(let y=0;y<P;y++)for(let x=0;x<O;x++){if(ox+x>=OW||oy+y>=OH){grid[y][x]=0;continue}const a=idx(ox+x,oy+y,OW),b=idx(sx+x,sy+y,EW);grid[y][x]=[0,1,2].reduce((s,c)=>s+(out[a+c]-source[b+c])**2,0)}let costs=grid[0].slice(),parents=Array.from({length:P},()=>Array(O).fill(0));for(let y=1;y<P;y++){const next=[];for(let x=0;x<O;x++){const options=[x-1,x,x+1].filter(v=>v>=0&&v<O).sort((a,b)=>costs[a]-costs[b]||a-b);parents[y][x]=options[0];next[x]=grid[y][x]+costs[options[0]]}costs=next}let x=costs.indexOf(Math.min(...costs)),path=Array(P);for(let y=P-1;y>=0;y--){path[y]=x;x=parents[y][x]}return{path,cost:Math.min(...costs)}}function synthesize(source,mode,seed){const out=new Uint8ClampedArray(OW*OH*4),r=seeded(seed),ledger=[],counts=new Map();for(let oy=0;oy<OH;oy+=P-O)for(let ox=0;ox<OW;ox+=P-O){const candidates=[];for(let sy=0;sy<=EH-P;sy+=2)for(let sx=0;sx<=EW-P;sx+=2)candidates.push({sx,sy,cost:ssd(out,source,ox,oy,sx,sy)});candidates.sort((a,b)=>a.cost-b.cost||a.sy-b.sy||a.sx-b.sx);let chosen;if(mode==="random")chosen=candidates[Math.floor(r()*candidates.length)];else if(mode==="lowest")chosen=candidates[0];else{const limit=candidates[0].cost*1.08+1,near=candidates.filter(c=>c.cost<=limit);chosen=near[Math.floor(r()*near.length)]}const seam=ox>0?verticalSeam(out,source,ox,oy,chosen.sx,chosen.sy):{path:Array(P).fill(0),cost:0};for(let py=0;py<P&&oy+py<OH;py++)for(let px=0;px<P&&ox+px<OW;px++){if(mode==="cut"&&ox>0&&px<seam.path[py])continue;const a=idx(ox+px,oy+py,OW),b=idx(chosen.sx+px,chosen.sy+py,EW);out.set(source.slice(b,b+4),a)}const key=chosen.sx+":"+chosen.sy;counts.set(key,(counts.get(key)||0)+1);ledger.push({at:[ox,oy],source:[chosen.sx,chosen.sy],rank:candidates.indexOf(chosen),overlapSSD:+chosen.cost.toFixed(3),verticalSeam:seam.path,seamCost:+seam.cost.toFixed(3)})}return{pixels:out,ledger,repetition:{unique:counts.size,maxReuse:Math.max(...counts.values()),placements:ledger.length},meanOverlap:+(ledger.reduce((s,x)=>s+x.overlapSSD,0)/ledger.length).toFixed(3),meanSeam:+(ledger.reduce((s,x)=>s+x.seamCost,0)/ledger.length).toFixed(3)}}function paint(source,results){const ctx=quilt.getContext("2d"),panels=[{pixels:source,w:EW,h:EH,label:"generated exemplar"},{...results.random,w:OW,h:OH,label:"random patches"},{...results.lowest,w:OW,h:OH,label:"lowest overlap"},{...results.cut,w:OW,h:OH,label:"minimum-error cut"}];panels.forEach((p,i)=>{const image=new ImageData(p.pixels,p.w,p.h),temp=document.createElement("canvas");temp.width=p.w;temp.height=p.h;temp.getContext("2d").putImageData(image,0,0);const ox=(i%2)*320,oy=Math.floor(i/2)*180;ctx.imageSmoothingEnabled=false;ctx.drawImage(temp,ox,oy,320,180);ctx.fillStyle="#fff";ctx.fillText(p.label,ox+8,oy+16)})}async function execute(){try{const source=exemplar(),results={random:synthesize(source,"random",73),lowest:synthesize(source,"lowest",73),cut:synthesize(source,"cut",73)};paint(source,results);const png=await new Promise(r=>quilt.toBlob(r,"image/png")),summary={fixture:"generated exemplar",seed:73,dimensions:{exemplar:[EW,EH],output:[OW,OH]},patch:P,overlap:O,baselines:{random:{meanOverlap:results.random.meanOverlap},lowest:{meanOverlap:results.lowest.meanOverlap},cut:{meanOverlap:results.cut.meanOverlap,meanSeam:results.cut.meanSeam}},paths:results.cut.ledger,repetition:results.cut.repetition},json=new Blob([JSON.stringify(summary,null,2)],{type:"application/json"});pngExport.href=URL.createObjectURL(png);jsonExport.href=URL.createObjectURL(json);summary.exports={png:{bytes:png.size,sha256:await sha(await png.arrayBuffer())},json:{bytes:json.size,sha256:await sha(await json.arrayBuffer())}};const pass=summary.paths.length>10&&summary.paths.every(x=>x.verticalSeam.length===P)&&summary.repetition.unique>1&&summary.exports.png.sha256.length===64;receipt.dataset.execution=JSON.stringify(summary);receipt.value=(pass?"PASS: ":"FAIL: ")+JSON.stringify(summary,null,2)}catch(error){receipt.dataset.execution=JSON.stringify({unexpectedError:error.name+": "+error.message});receipt.value="FAIL: unexpected "+error.message}}quiltButton.onclick=()=>void execute();void execute()</script></html>

Backtrack a minimum error boundary cut

For a vertical overlap, build a pixel-error grid and accumulate the cheapest path from top to bottom, allowing each row to connect to nearby columns. Store predecessors, choose a stable tie-break, and backtrack from the cheapest final cell to produce the seam mask.

Horizontal overlaps use the transposed problem. At a corner where top and left overlaps meet, define whether masks combine, one cut takes precedence, or a graph-cut-style solution is used; unexplained intersection logic often creates visible notches.

The Node image quilting fixture backtracks a tiny deterministic seam through its minimum-cost column. That proves the teaching dynamic program only, not the quality of every two-dimensional corner or Canvas composite.

Each left overlap becomes a three-column error grid. Dynamic programming stores predecessor columns, chooses stable ties, and backtracks a twelve-row minimum-error path that controls which existing pixels survive. The path is preserved per placement. Every row contributes exactly one seam coordinate to the ledger.

Composite across the seam without hiding the path

Pixels on the accepted side of the cut come from the existing quilt, and pixels on the other side come from the candidate patch. A narrow feather can soften sampling artifacts, but store the hard seam and feather width separately so the diagnostic still reveals the chosen boundary.

Show the overlap error heatmap, cumulative cost, backtracked path, binary mask, and final composite. Use line patterns or labels in addition to hue so a reader can understand source ownership without relying on red-versus-green encoding.

Image quilting should compare the cut with naive rectangular replacement and overlap-only blending on the same candidate. That matched comparison explains which visible improvement comes from patch choice and which comes from the minimum error boundary cut.

The Canvas renders four matched panels: exemplar, random patches, lowest overlap, and minimum-error cut. The labels identify algorithmic differences instead of presenting one polished quilt without its counterexamples. All panels share dimensions, patch scale, overlap, and source exemplar.

Minimum-error seam backtrackA three-column overlap error grid accumulates costs downward, stores predecessors, and recovers one twelve-row path.pixel SSDcumulative costallowed predecessor: x−1, x, x+1stable leftmost tie-break
  1. Compute RGB squared error for every overlap pixel.
  2. Initialize cumulative cost from the first row.
  3. For each later cell, choose the cheapest adjacent predecessor.
  4. Store that predecessor with a stable tie-break.
  5. Start at the cheapest final cell and backtrack twelve rows.
Figure 2: The accent path is the algorithm’s recovered cut, not a decorative squiggle.

Detect repetition and border discontinuity

Count repeated source coordinates, near-identical neighboring patches, motif autocorrelation, seam energy, and opposite-edge disagreement for the generated fixture. These diagnostics locate problems but do not combine into an objective beauty score.

For seamless texture generation, synthesize with periodic boundary constraints or crop a region whose opposing edges were considered during optimization. Simply wrapping the final bitmap can reveal a large border seam even when every interior patch join is excellent.

Compare image quilting with wave function collapse patterns: quilting copies pixel neighborhoods from an exemplar, while constraint propagation selects compatible symbolic tiles. Their repetition and failure modes deserve different diagnostics.

Every placement records output position, source position, candidate rank, normalized overlap SSD, seam path, and seam cost. Those fields let a reviewer inspect a visible join without reconstructing hidden random choices. Each candidate remains traceable to generated exemplar coordinates and its frozen seed. Reuse statistics derive from this same placement ledger.

Render and export through a stable Canvas path

The Canvas 2D standard defines the pixel surface, while the implementation owns decoding, color treatment, alpha, image smoothing, and export. Keep computation in typed arrays and paint frozen results so display scaling cannot alter seam costs.

Use Canvas dithering after synthesis when the final edition needs a reduced palette, preserving the undithered quilt and seam receipt. For scalable procedural line texture, SVG filters may be a better source than patch copying.

Image quilting interaction should work by keyboard, expose progress or completion in a live region, honor reduced motion, and provide a static generated preview. Cancel or replace long runs without leaving stale output labeled as the current seed.

Repetition metrics count unique source patches, maximum reuse, and total placements. They diagnose motif concentration but do not claim that a lower reuse count is always more beautiful or perceptually seamless. Border continuity still requires visual review of the matched outputs.

Publish the texture with its seam evidence

Archive exemplar generator and digest, palette, source and output dimensions, patch and overlap, candidate count, tolerance, seed, distance function, cut policy, corner rule, feather, placement coordinates, seam paths, repetition diagnostics, output digest, and browser identity.

Contrast the result with domain-warped marble to decide whether exemplar structure or continuous procedural fields better serve the visual thesis. Combining them can be fruitful only when each layer retains provenance.

Run one image quilting edition, inspect the error grid and seam path, then compare naive tile, overlap-only, and cut composites. The texture is ready when its joins support the artwork, its source is rights-clear, and every deterministic choice can be reconstructed from the receipt.

This bounded image quilting lab implements vertical minimum-error cuts for left overlaps. Top overlaps contribute to candidate SSD, but a two-axis corner-cut optimizer is outside the stated patch-based synthesis artifact scope. That limitation travels with the export. The receipt never labels the bounded method as full corner optimization.

Matched synthesis baselinesGenerated exemplar, random candidates, strict lowest overlap, and seeded minimum-error cut are displayed with common dimensions.exemplarrandomlowest SSDminimum cutsame patch · overlap · seed · output
Exemplar
Deterministic wave-generated source pixels.
Random baseline
Seeded candidate without overlap ranking.
Lowest-overlap baseline
First candidate after SSD sort.
Cut edition
Seeded near-best candidate plus dynamic-programming seam.
Receipts
Mean overlap, mean seam, reuse metrics, PNG and JSON hashes.
Figure 3: Matched baselines separate patch choice from seam-cut contribution.

Test seams with adversarial exemplars

Use a flat field, strong gradient, checkerboard, single diagonal, sparse motif, periodic weave, random noise, transparent edge, and an exemplar smaller than the requested patch. Each reveals a different weakness in normalization, tie-breaking, corner combination, repetition control, alpha, or source bounds.

Add mutations that produce equal-cost paths and confirm the seam tie-break remains stable. Verify that every backtracked step moves only through allowed neighbors, starts and ends at the correct boundary, stays inside the overlap, and yields a mask with no holes or unassigned pixels.

Image quilting should fail clearly when no valid candidate exists rather than reading outside the exemplar or placing an unscored patch. Retain the last valid Canvas, announce the rejection, and make parameter repair possible without losing the seed or already accepted placement ledger.

PNG and JSON exports receive cryptographic hashes and byte sizes. The JSON freezes seed, dimensions, patch, overlap, baseline summaries, complete path ledger, and repetition statistics.

Turn patch lineage into a visual composition

Candidate selection can become part of the artwork instead of hidden machinery. Draw subtle source-coordinate marks, alternate seam stitch styles, reveal a few overlap windows, or publish the placement map beside the finished texture; the visual can celebrate construction while the main field remains cohesive.

Control focal repetition by weighting source zones, reserving motifs for selected output regions, or rejecting immediate reuse, but record every intervention. These choices change the sampling distribution and should never be presented as neutral output from the original algorithm.

Image quilting supports a creative series when one generated exemplar yields editions at several patch scales and cut policies. Preserve matched crops, receipts, and seam overlays, then critique rhythm, density, and surprise across the series without reducing aesthetic judgment to seam energy alone.

The review date advances after seam backtracking, matched baselines, repetition metrics, and hashes rerun. A new generated exemplar becomes a new edition rather than an undocumented texture-synthesis input change.