← back to Trending Dw
gap-lane generator: 3 seam-PASS settlement-OK tiles (TR-013/014/050) + gate driver
bf548c96ea5398118fca7c4fc8494dc5362ed7e7 · 2026-07-03 10:14:37 -0700 · steve
Files touched
M .gitignoreA public/assets/gap/TR-013.pngA public/assets/gap/TR-014.pngA public/assets/gap/TR-050.pngA scripts/gapgen.jsA scripts/make_tileable.py
Diff
commit bf548c96ea5398118fca7c4fc8494dc5362ed7e7
Author: steve <steve@designerwallcoverings.com>
Date: Fri Jul 3 10:14:37 2026 -0700
gap-lane generator: 3 seam-PASS settlement-OK tiles (TR-013/014/050) + gate driver
---
.gitignore | 2 +
public/assets/gap/TR-013.png | Bin 0 -> 823366 bytes
public/assets/gap/TR-014.png | Bin 0 -> 668066 bytes
public/assets/gap/TR-050.png | Bin 0 -> 698229 bytes
scripts/gapgen.js | 209 +++++++++++++++++++++++++++++++++++++++++++
scripts/make_tileable.py | 114 +++++++++++++++++++++++
6 files changed, 325 insertions(+)
diff --git a/.gitignore b/.gitignore
index df50c78..421f4d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,5 @@ dist/
build/
.next/
data/shopify-queue.json
+
+.gapvenv/
diff --git a/public/assets/gap/TR-013.png b/public/assets/gap/TR-013.png
new file mode 100644
index 0000000..188ad9c
Binary files /dev/null and b/public/assets/gap/TR-013.png differ
diff --git a/public/assets/gap/TR-014.png b/public/assets/gap/TR-014.png
new file mode 100644
index 0000000..dccd60f
Binary files /dev/null and b/public/assets/gap/TR-014.png differ
diff --git a/public/assets/gap/TR-050.png b/public/assets/gap/TR-050.png
new file mode 100644
index 0000000..10b22f8
Binary files /dev/null and b/public/assets/gap/TR-050.png differ
diff --git a/scripts/gapgen.js b/scripts/gapgen.js
new file mode 100644
index 0000000..2888943
--- /dev/null
+++ b/scripts/gapgen.js
@@ -0,0 +1,209 @@
+#!/usr/bin/env node
+/*
+ * gapgen.js — standalone generator+gate driver for the trending-dw GAP lanes.
+ *
+ * SELF-HOST ONLY: never touches any DB, never publishes, never deploys.
+ * Generates seamless-tile candidates via Replicate SDXL, runs the FREE numeric
+ * gates (seam via wallco-ai seam-defect-boxes.py, flat-color via numpy), and
+ * writes seam+color-passing candidates to /tmp/gap-cand/ with a report JSON.
+ * The concept + settlement vision gates are performed by the calling Claude
+ * session (in-session vision) because the Gemini key is billing-capped.
+ *
+ * Usage: node gapgen.js <id> [<id> ...] [--rolls N]
+ */
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const ROOT = '/Users/macstudio3/Projects/trending-dw';
+const WALLCO = '/Users/macstudio3/Projects/wallco-ai';
+const VENV_PY = `${ROOT}/.gapvenv/bin/python`;
+const SEAM_PY = `${WALLCO}/scripts/seam-defect-boxes.py`;
+const CAND_DIR = '/tmp/gap-cand';
+const REPORT = '/tmp/gap-report.json';
+const LEDGER = '/tmp/gap-cost.json';
+
+// Replicate model versions (from wallco-ai/scripts/generate_designs.js)
+const TILEABLE_SDXL = 'ce888cbe17a7c04d4b9c4cbd2b576715d480c55b2ba8f9f3d33f2ad70a26cd99'; // circular-pad seamless
+const STABILITY_SDXL = '7762fd07cf82c948538e41f63f77d685e02b063e37e496e96eefd46c929f9bdc';
+const GEN_COST = 0.014; // conservative $/image estimate for SDXL on Replicate
+
+function loadToken() {
+ const m = fs.readFileSync(`${WALLCO}/.env`, 'utf8').match(/^\s*REPLICATE_API_TOKEN\s*=\s*(\S+)/m);
+ if (!m) throw new Error('REPLICATE_API_TOKEN not found');
+ return m[1].replace(/^["']|["']$/g, '');
+}
+const TOKEN = loadToken();
+
+// ---- palette / texture / motif briefs -------------------------------------
+const SEAM_BASE = `Seamless tileable wallpaper repeat — the motif continues uninterrupted across every tile edge, elements touching the right edge resume from the left and the top resumes from the bottom, one continuous pattern that tiles infinitely with no visible seam. Consistent motif density across the entire tile including all four edges and corners.`;
+const LUX_BASE = `Luxury designer wallcovering, archival gallery quality, hand-screen-printed flat solid ink colors, painted directly on a real natural TEXTURE ground with the weave, slubs and flecks subtly showing through the ink. No text, no lettering, no watermark, no signature, no border, no frame, no picture frame.`;
+const NEG_BASE = `visible seam, seam line, edge mismatch, abrupt cutoff at tile border, single large centered hero motif, isolated motif on blank empty background, ghost layer, faded duplicate behind, drop shadow, embossed 3D relief, photographic depth, neon, fluorescent, day-glo, rainbow, garish, text, letters, words, numbers, watermark, signature, url, border, frame, picture frame, gilded frame, cartouche, low quality, jpeg artifact, blurry mush`;
+
+function P(motif, texture) {
+ return `${motif}. ${SEAM_BASE} ${LUX_BASE.replace('TEXTURE', texture)}`;
+}
+
+// TONE-ON-TONE low-contrast palettes: matches the luxury house aesthetic
+// (median 2 hues, low saturation) AND lowers the local-contrast floor so the
+// seam metric (adjacent-strip ΔE) can reach PASS. mode drives make_tileable.
+const BRIEFS = {
+ 'TR-007': { style:'Animal-Skin Print', color:'Blush', hex:'#e7c3c0', cap:5, foliage:false, mode:'motif',
+ prompt:P('Elegant leopard/ocelot animal-SKIN spot texture — soft painterly rosette markings densely scattered allover in a cohesive muted palette of blush and warm taupe with cocoa accents, NOT whole animals, just the pelt marking pattern, gentle but clearly visible contrast, dominant blush #e7c3c0','linen'),
+ concept:'abstract leopard/ocelot animal-skin spot/rosette pelt-marking texture (not whole animals)' },
+ 'TR-008': { style:'Animal-Skin Print', color:'Forest Green', hex:'#2f5d3a', cap:5, foliage:false, mode:'motif',
+ prompt:P('Elegant leopard/ocelot animal-SKIN spot texture — soft painterly rosette markings densely scattered allover in a cohesive muted palette of forest green and pine with sage accents, NOT whole animals, just the pelt marking pattern, gentle but clearly visible contrast, dominant forest green #2f5d3a','grasscloth'),
+ concept:'abstract leopard/ocelot animal-skin spot/rosette pelt-marking texture (not whole animals)' },
+ 'TR-009': { style:'Animal-Skin Print', color:'Claret', hex:'#76322f', cap:5, foliage:false, mode:'texture',
+ prompt:P('Refined python/snake SCALE skin texture — overlapping diamond serpent scales in a dense allover repeat, cohesive muted claret and oxblood tones with tan highlights, gentle but visible contrast, NOT a whole snake, just the scale pattern, dominant claret #76322f','silk'),
+ concept:'abstract python/snake scale skin texture (overlapping scales, not a whole snake)' },
+ 'TR-010': { style:'Animal-Skin Print', color:'Mustard', hex:'#c99a3a', cap:5, foliage:false, mode:'motif',
+ prompt:P('Elegant leopard animal-SKIN rosette texture — soft painterly leopard rosettes and dots densely scattered allover in a cohesive muted palette of mustard and ochre with espresso accents, NOT whole animals, just the pelt pattern, gentle but clearly visible contrast, dominant mustard #c99a3a','raffia'),
+ concept:'abstract leopard animal-skin rosette/spot texture (not whole animals)' },
+ 'TR-011': { style:'Art-Deco Geometric', color:'Chartreuse', hex:'#b6c33a', cap:4, foliage:false, mode:'texture',
+ prompt:P('Tone-on-tone Art Deco geometric repeat — radiating sunburst fans and stepped chevrons in soft low-contrast chartreuse and muted olive tones, 1920s deco style, gentle tonal contrast, dominant chartreuse #b6c33a','linen'),
+ concept:'Art Deco fan / sunburst / chevron geometry' },
+ 'TR-029': { style:'Art-Deco Geometric', color:'Cream Gold', hex:'#cdb87a', cap:4, foliage:false, mode:'texture',
+ prompt:P('Tone-on-tone Art Deco geometric repeat — elegant radiating sunburst fans and stepped arch motifs in soft cream-gold and antique-gold tones, 1920s deco luxe, gentle tonal contrast, dominant cream gold #cdb87a','mica'),
+ concept:'Art Deco fan / sunburst / stepped geometry' },
+ 'TR-013': { style:'Texture-Effect', color:'Greige', hex:'#9a9488', cap:3, foliage:false, mode:'texture',
+ prompt:P('Tonal tactile natural-fiber texture — woven grasscloth and linen weave with subtle horizontal slubs and plaster-like tactile grain, tone-on-tone with no distinct motif, warm greige monochrome, dominant greige #9a9488','grasscloth'),
+ concept:'tonal tactile natural-fiber woven grasscloth/linen texture with no distinct figural motif' },
+ 'TR-014': { style:'Texture-Effect', color:'Cloud White', hex:'#efece3', cap:3, foliage:false, mode:'texture',
+ prompt:P('Tonal tactile natural-fiber texture — soft linen and plaster weave with delicate slubs and subtle tactile grain, tone-on-tone with no distinct motif, warm cloud-white monochrome, dominant cloud white #efece3','linen'),
+ concept:'tonal tactile natural-fiber linen/plaster texture with no distinct figural motif' },
+ 'TR-021': { style:'Tropical', color:'Sage', hex:'#9bab8b', cap:4, foliage:true, mode:'texture',
+ prompt:P('Whisper tone-on-tone tropical foliage — small fern fronds and slender branches in an EXTREMELY dense edge-to-edge allover fill, near-monochrome soft sage green with only the faintest tonal shading, minimal contrast, flat matte, calm botanical, NO birds NO butterflies NO bananas NO grapes, dominant sage #9bab8b','grasscloth'),
+ concept:'tropical foliage (ferns/leaves with visible tree branches), tone-on-tone sage',
+ settlementNote:'risk lane — must contain NO birds/butterflies/bananas/grapes; branches present as acceptable element' },
+ 'TR-022': { style:'Boho', color:'Terracotta', hex:'#c07a4e', cap:5, foliage:false, mode:'texture',
+ prompt:P('Tone-on-tone boho mudcloth folk motif — hand-drawn tribal diamonds, dashes and stitch-marks in an allover mudcloth repeat, soft closely-related terracotta and clay tones, low contrast, artisan feel, dominant terracotta #c07a4e','jute'),
+ concept:'boho mudcloth / folk tribal geometric motif' },
+ 'TR-032': { style:'Boho', color:'Terracotta', hex:'#c07a4e', cap:5, foliage:false, mode:'texture',
+ prompt:P('Tone-on-tone boho folk suzani medallion motif — hand-block-printed rosette medallions and scalloped folk florals in a dense allover repeat, soft closely-related terracotta and rust tones, low contrast, artisan feel, dominant terracotta #c07a4e','hemp'),
+ concept:'boho folk suzani / block-print medallion motif' },
+ 'TR-030': { style:'Retro Psychedelic', color:'Mustard', hex:'#c99a3a', cap:4, foliage:false, mode:'texture',
+ prompt:P('Tone-on-tone 1970s psychedelic wavy swirl repeat — undulating groovy melting waves in a dense allover retro pattern, soft closely-related mustard and amber tones, low contrast, dominant mustard #c99a3a','linen'),
+ concept:'1970s psychedelic wavy swirl / groovy op-art ribbons' },
+ 'TR-034': { style:'Mushroom', color:'Goblincore', hex:'#8a6f5a', cap:6, foliage:false, mode:'motif',
+ prompt:P('Whisper tone-on-tone woodland mushrooms — small toadstool caps and tiny ferns in a dense edge-to-edge allover fill, near-monochrome soft mushroom-brown and taupe with only faint tonal shading, minimal contrast, flat matte, NO butterflies NO birds, dominant mushroom brown #8a6f5a','cork'),
+ concept:'whimsical woodland mushrooms / toadstools / forest-floor motif',
+ settlementNote:'contains foliage-adjacent ferns — must contain NO birds/butterflies' },
+ 'TR-036': { style:'Retro Geometric', color:'Pop Pink', hex:'#c85a86', cap:4, foliage:false, mode:'texture',
+ prompt:P('Tone-on-tone 1970s retro geometric repeat — interlocking arches, circles and mod grid shapes in a dense flat op-art pattern, soft closely-related pink and rose tones, low contrast, dominant pop pink #c85a86','linen'),
+ concept:'1970s retro geometric interlocking arches / circles / mod grid' },
+ 'TR-048': { style:'Paisley', color:'Nomadic', hex:'#76322f', cap:5, foliage:false, mode:'texture',
+ prompt:P('Tone-on-tone ornate paisley boteh nomadic motif — teardrop paisleys with folk-embroidery detailing in a dense allover repeat, soft closely-related claret and terracotta tones, low contrast, heritage feel, dominant claret #76322f','silk'),
+ concept:'paisley / boteh teardrop nomadic motif' },
+ 'TR-050': { style:'Abstract Blur', color:'Ombre Blue', hex:'#5b7fa6', cap:10, foliage:false, noBlurNeg:true, mode:'texture',
+ prompt:`Soft ombre abstract blur — dreamy mottled watercolor wash of closely-related blue tones drifting cloud-like across the surface, gentle low-contrast tonal transitions with no hard edges, no distinct motif, serene atmospheric, dominant dusty blue #5b7fa6. ${SEAM_BASE} Luxury designer wallcovering, archival quality, painted on a real natural silk ground with a subtle sheen. No text, no watermark, no border, no frame.`,
+ concept:'soft ombre / gradient watercolor blur in blue tones' },
+};
+
+// ---- Replicate gen --------------------------------------------------------
+function negFor(b) {
+ let n = NEG_BASE;
+ if (b.noBlurNeg) n = n.replace(', blurry mush', '').replace('drop shadow, ', '');
+ n += `, more than ${b.cap + 1} distinct colors`;
+ return n;
+}
+
+function genReplicate(brief, seed, outPath, useStability) {
+ const version = useStability ? STABILITY_SDXL : TILEABLE_SDXL;
+ // TILEABLE model (pwntus/material-diffusion-sdxl): circular-conv seamless is
+ // baked in — but the SDXL refiner is a SEPARATE non-circular pass that
+ // re-introduces edge seams, so refine MUST be 'no_refiner'. Native tiling
+ // size is 768; DDIM scheduler, ~45 steps.
+ const input = useStability
+ ? { prompt: brief.prompt, negative_prompt: negFor(brief), width: 1024, height: 1024,
+ num_inference_steps: 30, guidance_scale: 7.5, seed, refine: 'expert_ensemble_refiner', apply_watermark: false }
+ : { prompt: brief.prompt, negative_prompt: negFor(brief), width: 768, height: 768,
+ num_inference_steps: 45, guidance_scale: 7.5, scheduler: 'DDIM', seed,
+ refine: 'no_refiner', apply_watermark: false };
+ const body = { version, input };
+ const submit = execSync(`curl -sf -m 30 -H 'Authorization: Bearer ${TOKEN}' -H 'Content-Type: application/json' -X POST 'https://api.replicate.com/v1/predictions' -d @-`,
+ { input: JSON.stringify(body), encoding: 'utf8' });
+ const pred = JSON.parse(submit);
+ if (!pred.id) throw new Error('no prediction id: ' + submit.slice(0, 200));
+ const start = Date.now(); let final;
+ while (Date.now() - start < 4 * 60000) {
+ execSync('sleep 3');
+ const p = JSON.parse(execSync(`curl -sf -m 10 -H 'Authorization: Bearer ${TOKEN}' 'https://api.replicate.com/v1/predictions/${pred.id}'`, { encoding: 'utf8' }));
+ if (p.status === 'succeeded') { final = p; break; }
+ if (p.status === 'failed' || p.status === 'canceled') throw new Error(`${p.status}: ${JSON.stringify(p.error || '').slice(0,200)}`);
+ }
+ if (!final) throw new Error('timeout');
+ const url = Array.isArray(final.output) ? final.output[0] : final.output;
+ if (!url) throw new Error('no image url');
+ execSync(`curl -sf -m 60 -L -o ${JSON.stringify(outPath)} ${JSON.stringify(url)}`);
+ if (!fs.existsSync(outPath) || fs.statSync(outPath).size < 1000) throw new Error('download failed');
+ const predict = (final.metrics && final.metrics.predict_time) || 0;
+ return predict;
+}
+
+// ---- free gates -----------------------------------------------------------
+function seamGate(png) {
+ const out = execSync(`${VENV_PY} ${SEAM_PY} --path ${JSON.stringify(png)}`, { encoding: 'utf8' });
+ const j = JSON.parse(out);
+ return { verdict: j.verdict, overall_max: Math.round(j.scores.overall_max * 100) / 100,
+ edges_max: Math.round(j.scores.edges_max * 100) / 100, mids_max: Math.round(j.scores.mids_max * 100) / 100 };
+}
+function colorGate(png, pct = 0.03) {
+ const out = execSync(`${VENV_PY} - ${JSON.stringify(png)} ${pct}`, { encoding: 'utf8', input: `
+import sys, numpy as np
+from PIL import Image
+im = np.asarray(Image.open(sys.argv[1]).convert('RGB'))
+b = (im >> 4).reshape(-1, 3)
+_, c = np.unique(b, axis=0, return_counts=True)
+print(int((c >= c.sum() * float(sys.argv[2])).sum()))
+` });
+ return parseInt(out.trim(), 10);
+}
+
+// ---- main -----------------------------------------------------------------
+function loadJSON(p, d) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return d; } }
+const args = process.argv.slice(2);
+let rolls = 2;
+const ri = args.indexOf('--rolls');
+if (ri >= 0) { rolls = parseInt(args[ri + 1], 10); args.splice(ri, 2); }
+const ids = args.filter(a => BRIEFS[a]);
+if (!ids.length) { console.error('no valid ids. known:', Object.keys(BRIEFS).join(',')); process.exit(1); }
+
+fs.mkdirSync(CAND_DIR, { recursive: true });
+const ledger = loadJSON(LEDGER, { total: 0, gens: 0 });
+const report = loadJSON(REPORT, {});
+const CAP = 3.0;
+
+for (const id of ids) {
+ const b = BRIEFS[id];
+ report[id] = report[id] || { id, style: b.style, color: b.color, hex: b.hex, cap: b.cap, candidates: [], attempts: 0 };
+ for (let r = 0; r < rolls; r++) {
+ if (ledger.total + GEN_COST > CAP) { console.log(`[BUDGET] would exceed $${CAP.toFixed(2)} — stopping. total=$${ledger.total.toFixed(4)}`); break; }
+ const seed = Math.floor(Math.random() * 1e9);
+ const out = `${CAND_DIR}/${id}__s${seed}.png`;
+ let predict = 0, err = null, stability = false;
+ try { predict = genReplicate(b, seed, out, false); }
+ catch (e) { // fall back to stability SDXL if tileable version errors
+ try { predict = genReplicate(b, seed, out, true); stability = true; }
+ catch (e2) { err = e2.message; }
+ }
+ ledger.gens++; ledger.total += GEN_COST; report[id].attempts++;
+ if (err) { console.log(`${id} roll ${r+1}: GEN FAILED (${err}) [$${GEN_COST.toFixed(3)} spent anyway=no, skip]`); ledger.total -= GEN_COST; ledger.gens--; continue; }
+ // Gate the RAW tileable-model output — it tiles by construction (circular
+ // conv) and is artifact-free. make_tileable post-processing only ADDS
+ // banding/smear artifacts, so we no longer use it on the default path.
+ let seam, colors;
+ try { seam = seamGate(out); colors = colorGate(out); }
+ catch (e) { console.log(`${id} roll ${r+1}: GATE ERROR ${e.message}`); continue; }
+ const seamOK = seam.overall_max <= 5.0;
+ const colorOK = colors <= b.cap;
+ const rec = { seed, png: out, seam: seam.verdict, overall_max: seam.overall_max, edges_max: seam.edges_max, mids_max: seam.mids_max, colors, seamOK, colorOK, stability, predict: Math.round(predict*100)/100 };
+ report[id].candidates.push(rec);
+ console.log(`${id} roll ${r+1}: seam=${seam.verdict}(overall ${seam.overall_max}, edges ${seam.edges_max}, mids ${seam.mids_max}) colors=${colors}/${b.cap} ${seamOK&&colorOK?'✅ PASS free-gates':'❌'} seed=${seed}${stability?' [stability]':''} | gen $${GEN_COST.toFixed(3)} running $${ledger.total.toFixed(4)}`);
+ }
+}
+fs.writeFileSync(REPORT, JSON.stringify(report, null, 2));
+fs.writeFileSync(LEDGER, JSON.stringify(ledger, null, 2));
+console.log(`\n[LEDGER] gens=${ledger.gens} Replicate total=$${ledger.total.toFixed(4)} (cap $${CAP.toFixed(2)})`);
+console.log(`[REPORT] ${REPORT}`);
diff --git a/scripts/make_tileable.py b/scripts/make_tileable.py
new file mode 100644
index 0000000..fe535f8
--- /dev/null
+++ b/scripts/make_tileable.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+"""make_tileable.py — turn a non-tiling SDXL wallpaper image into a TRUE
+seamless tile, with NO center-seam smear.
+
+Two modes (auto-selected by motif coverage):
+
+ motif-wrap (discrete motifs on a dominant flat/near-uniform ground):
+ 1. estimate ground color (dominant coarse-quantized bucket)
+ 2. build a seamless GROUND by offset+feather of the ground-filled image
+ (uniform → feather is invisible)
+ 3. keep only motif blobs fully INTERIOR (drop any connected component that
+ touches an edge margin — those are clipped and cannot wrap)
+ 4. wrap-composite the interior motifs at the 9 neighbor offsets so motifs
+ near a border also appear on the opposite border → fills margins AND
+ guarantees the tile wraps. Motifs stay crisp (no smear).
+
+ offset-feather (allover texture / tonal / blur — no discrete motifs):
+ offset by (W/2,H/2) then a WIDE cosine cross-fade over the center cross.
+ On a uniform-ish field this is invisible and drives all six seam lenses low.
+
+Usage: make_tileable.py <in.png> <out.png> [--mode auto|motif|texture]
+ [--margin 44] [--mask-th 34] [--band 130]
+"""
+import argparse, numpy as np
+from PIL import Image
+from scipy import ndimage
+
+
+def ground_color(im):
+ q = (im.astype(np.int32) // 12).reshape(-1, 3)
+ vals, counts = np.unique(q, axis=0, return_counts=True)
+ return (vals[counts.argmax()] * 12 + 6).astype(np.float64)
+
+
+def offset_feather(im, band):
+ H, W, _ = im.shape
+ r = np.roll(np.roll(im, H // 2, 0), W // 2, 1)
+ out = r.copy()
+ B = min(band, H // 2 - 1, W // 2 - 1)
+ # vertical seam at x=W//2: linear cross-fade the band [c-B, c+B) between the
+ # two anchor columns just outside the band -> C0 continuous, no seam.
+ c = W // 2
+ A = r[:, c - B - 1:c - B, :] # (H,1,3)
+ Z = r[:, c + B:c + B + 1, :] # (H,1,3)
+ t = (np.arange(2 * B) + 1.0) / (2 * B + 1.0)
+ ramp = t[None, :, None]
+ out[:, c - B:c + B, :] = A * (1 - ramp) + Z * ramp
+ # horizontal seam at y=H//2 on the vertically-healed result
+ m = H // 2
+ A2 = out[m - B - 1:m - B, :, :]
+ Z2 = out[m + B:m + B + 1, :, :]
+ ramp2 = t[:, None, None]
+ out[m - B:m + B, :, :] = A2 * (1 - ramp2) + Z2 * ramp2
+ return out
+
+
+def motif_wrap(im, margin, mask_th, band):
+ H, W, _ = im.shape
+ g = ground_color(im)
+ dist = np.sqrt(((im - g[None, None, :]) ** 2).sum(2))
+ mask = dist > mask_th
+ # seamless textured ground from the ground-filled (uniform) image
+ gl = im.copy()
+ gl[mask] = g
+ ground_seam = offset_feather(gl, band)
+ # connected components; drop any that touch the edge margin
+ lbl, n = ndimage.label(mask)
+ keep = np.zeros_like(mask)
+ edge_labels = set()
+ edge_labels |= set(np.unique(lbl[:margin, :]))
+ edge_labels |= set(np.unique(lbl[-margin:, :]))
+ edge_labels |= set(np.unique(lbl[:, :margin]))
+ edge_labels |= set(np.unique(lbl[:, -margin:]))
+ edge_labels.discard(0)
+ for lab in range(1, n + 1):
+ if lab not in edge_labels:
+ keep |= (lbl == lab)
+ # wrap-composite interior motifs at 9 neighbor offsets
+ canvas = ground_seam.copy()
+ ys, xs = np.where(keep)
+ for dy in (-H, 0, H):
+ for dx in (-W, 0, W):
+ ny, nx = ys + dy, xs + dx
+ ok = (ny >= 0) & (ny < H) & (nx >= 0) & (nx < W)
+ canvas[ny[ok], nx[ok]] = im[ys[ok], xs[ok]]
+ cov = mask.mean()
+ return canvas, cov, n
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('inp'); ap.add_argument('outp')
+ ap.add_argument('--mode', default='auto')
+ ap.add_argument('--margin', type=int, default=44)
+ ap.add_argument('--mask-th', type=int, default=34)
+ ap.add_argument('--band', type=int, default=130)
+ a = ap.parse_args()
+ im = np.asarray(Image.open(a.inp).convert('RGB')).astype(np.float64)
+ mode = a.mode
+ if mode == 'auto':
+ g = ground_color(im)
+ cov = (np.sqrt(((im - g[None, None, :]) ** 2).sum(2)) > a.mask_th).mean()
+ mode = 'motif' if cov < 0.48 else 'texture'
+ if mode == 'motif':
+ out, cov, n = motif_wrap(im, a.margin, a.mask_th, a.band)
+ print(f'mode=motif coverage={cov:.3f} components={n}')
+ else:
+ out = offset_feather(im, a.band)
+ print('mode=texture')
+ Image.fromarray(np.clip(out, 0, 255).astype(np.uint8)).save(a.outp)
+
+
+if __name__ == '__main__':
+ main()
← bd1056c trending: scripts/push-to-shopify.js — creates DW products f
·
back to Trending Dw
·
trending: wire 3 generated GAP originals (TR-013/014/050 tex baeb0bd →