Suminagashi

Create beautiful flowing patterns with drops and vertical tines. Toggle modes with T key for precise marbling control.

Loading Suminagashi...

Ready to create
Toggle modes with T • Drops: palette colors • Random: click for a random color • Tine: carve patterns
CSS Size 1200x800
Render Size 1200x800
DPR 1.00
Quality Balanced
Mode Drops

How to Create Art

Two Interaction Modes

Drops Mode: Click to create new drops with random themed colors. Tine Mode: Click to carve elegant vertical deformations into existing patterns. Press T key to toggle between modes.

Vertical Tine Tool

In Tine mode, adjust Strength (0-400) for displacement intensity and Sharpness (1-256) for influence radius. Features smooth quintic falloff and diminishing returns to prevent over-carving.

Dynamic Color Palettes

Enjoy 15 carefully curated color palettes including Sunset, Ocean, Traditional Japanese, Turkish Ebru, and more artistic themes.

Watch the Magic Module.ccall('setNextDropColor', null, ['number','number','number','number'], [r,g,b,a]); }); if(i===0){ btn.style.outline='2px solid var(--accent)'; selectedColorBtn=btn; Module.ccall('setNextDropColor', null, ['number','number','number','number'], [r,g,b,a]); } paletteContainer.appendChild(btn); } } // Rebuild palette after runtime init (in case palette selected randomly) function afterRuntimeInit(){ buildPalette(); setMode('drops'); updateSliderLabels(); Module.ccall('setNextDropRadius', null, ['number'], [parseInt(radiusSlider.value)]); } Module.onRuntimeInitialized = (function(orig){ return function(){ if(orig) orig(); afterRuntimeInit(); };})(Module.onRuntimeInitialized); // ---------------- Tine Interaction (applyTineAt bridge) ---------------- (function initTineInput(){ const canvas = document.getElementById('canvas'); if(!canvas) return; let isDown = false; let lastX = -9999; const MIN_DELTA = 4; // only re-apply while dragging if moved this many px function invokeTine(clientX){ if(currentMode !== 1) return; if(!(Module && Module.ccall)) return; const rect = canvas.getBoundingClientRect(); // Scale to canvas backing store size (Emscripten sets width/height attributes) const canvasX = (clientX - rect.left) * (canvas.width / rect.width); const strength = parseFloat(strengthSlider.value) || 0; const sharpness = parseFloat(sharpnessSlider.value) || 1; Module.ccall('applyTineAt', null, ['number','number','number'], [canvasX, strength, sharpness]); } canvas.addEventListener('mousedown', e=>{ if(e.button!==0) return; isDown=true; lastX=-9999; invokeTine(e.clientX); }); window.addEventListener('mouseup', ()=>{ isDown=false; }); canvas.addEventListener('mousemove', e=>{ if(!isDown) return; const rect = canvas.getBoundingClientRect(); const x = (e.clientX - rect.left) * (canvas.width / rect.width); if(Math.abs(x-lastX) >= MIN_DELTA){ invokeTine(e.clientX); lastX = x; } }); canvas.addEventListener('mouseleave', ()=>{ isDown=false; }); })(); // ---------------- Keyboard Shortcut (T toggles mode) ---------------- window.addEventListener('keydown', (e)=>{ if(e.key === 't' || e.key === 'T'){ setMode(currentMode===0 ? 'tine' : 'drops'); } });