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
Gallery
Click images to open full size in a new tab.
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');
}
});