diff --git a/js/mask_painter_manager.js b/js/mask_painter_manager.js index f21ad9c..4a1a609 100644 --- a/js/mask_painter_manager.js +++ b/js/mask_painter_manager.js @@ -5,7 +5,8 @@ export class MaskPainterManager { this.lastPoint = { x: 0, y: 0 }; this.settings = { size: 30, - hardness: 80, // Le retour de la dureté ! + hardness: 80, + opacity: 100, // Le retour de la dureté ! mode: 'brush' }; @@ -19,90 +20,71 @@ export class MaskPainterManager { this.createSettingsToolbar(); } - createSettingsToolbar() { - if (this.toolbar) this.toolbar.remove(); +createSettingsToolbar() { + if (this.toolbar) this.toolbar.remove(); - const styleId = 'ls-mask-painter-style'; - if (!document.getElementById(styleId)) { - const style = document.createElement('style'); - style.id = styleId; - style.innerHTML = ` - .ls-mask-painter-toolbar .ls-tool-button { - border: 1px solid white; - background-color: transparent; - color: white; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.2s; - font-size: 18px; - width: 32px; - height: 32px; - padding: 2px; - } - .ls-mask-painter-toolbar .ls-tool-button:not(.active):hover { - background-color: rgba(255, 255, 255, 0.15); - } - .ls-mask-painter-toolbar .ls-tool-button.active { - background-color: rgba(100, 180, 255, 0.4); - border-color: #64b4ff; - } - `; - document.head.appendChild(style); - } - - this.toolbar = document.createElement("div"); - this.toolbar.className = 'ls-mask-painter-toolbar'; - Object.assign(this.toolbar.style, { - position: 'fixed', display: 'none', zIndex: '10002', - backgroundColor: 'rgba(40, 40, 40, 0.9)', border: '1px solid #555', - borderRadius: '8px', padding: '8px', alignItems: 'center', - gap: '6px', color: 'white', fontFamily: 'sans-serif' - }); - - // --- CORRECTION : On retire le curseur Opacity --- - this.toolbar.innerHTML = ` - - - ${this.settings.size} - - - - ${this.settings.hardness}% - - - - - - + const styleId = 'ls-mask-painter-style'; + if (!document.getElementById(styleId)) { + const style = document.createElement('style'); + style.id = styleId; + style.innerHTML = ` + .ls-mask-painter-toolbar .ls-tool-button { border: 1px solid white; background-color: transparent; color: white; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; font-size: 18px; width: 32px; height: 32px; padding: 2px; } + .ls-mask-painter-toolbar .ls-tool-button:not(.active):hover { background-color: rgba(255, 255, 255, 0.15); } + .ls-mask-painter-toolbar .ls-tool-button.active { background-color: rgba(100, 180, 255, 0.4); border-color: #64b4ff; } `; - document.body.appendChild(this.toolbar); - - this.toolbar.querySelector('[data-mode="brush"]').classList.add('active'); - - this.toolbar.addEventListener('input', (e) => { - const setting = e.target.dataset.setting; - if (setting) { - this.settings[setting] = parseInt(e.target.value, 10); - const suffix = (setting === 'hardness') ? '%' : ''; - this.toolbar.querySelector(`span[data-value="${setting}"]`).textContent = e.target.value + suffix; - } - }); - - this.toolbar.addEventListener('click', (e) => { - const target = e.target.closest('button'); - if (!target) return; - - if (target.dataset.mode) { - this.settings.mode = target.dataset.mode; - this.toolbar.querySelectorAll('.ls-tool-button').forEach(btn => btn.classList.remove('active')); - target.classList.add('active'); - } - - if (target.dataset.action === 'apply') this.finalizeDrawing(); - if (target.dataset.action === 'cancel') this.hide(); - }); + document.head.appendChild(style); } + this.toolbar = document.createElement("div"); + this.toolbar.className = 'ls-mask-painter-toolbar'; + Object.assign(this.toolbar.style, { + position: 'fixed', display: 'none', zIndex: '10002', + backgroundColor: 'rgba(40, 40, 40, 0.9)', border: '1px solid #555', + borderRadius: '8px', padding: '8px', alignItems: 'center', + gap: '8px', color: 'white', fontFamily: 'sans-serif' + }); + + this.toolbar.innerHTML = ` + + + ${this.settings.size} + + + ${this.settings.hardness}% + + + ${this.settings.opacity}% + + + + + `; + document.body.appendChild(this.toolbar); + + this.toolbar.querySelector('[data-mode="brush"]').classList.add('active'); + + this.toolbar.addEventListener('input', (e) => { + const setting = e.target.dataset.setting; + if (setting) { + this.settings[setting] = parseInt(e.target.value, 10); + const suffix = (setting === 'hardness' || setting === 'opacity') ? '%' : ''; + this.toolbar.querySelector(`span[data-value="${setting}"]`).textContent = e.target.value + suffix; + } + }); + + this.toolbar.addEventListener('click', (e) => { + const target = e.target.closest('button'); + if (!target) return; + if (target.dataset.mode) { + this.settings.mode = target.dataset.mode; + this.toolbar.querySelectorAll('.ls-tool-button').forEach(btn => btn.classList.remove('active')); + target.classList.add('active'); + } + if (target.dataset.action === 'apply') this.finalizeDrawing(); + if (target.dataset.action === 'cancel') this.hide(); + }); +} + async show() { this.node.toolbar.activeTool = 'mask_painter'; this.activeLayer = this.node.getActiveLayer(); @@ -192,37 +174,37 @@ async show() { this.positionToolbar(); } - drawStamp(point) { - const ctx = this.maskCtx; - const radius = this.settings.size / 2; - const gradient = ctx.createRadialGradient(point.x, point.y, 0, point.x, point.y, radius); - // La dureté (0-100) contrôle où le dégradé devient transparent - const hardnessStop = Math.max(0, Math.min(1, this.settings.hardness / 100)); - - // Pinceau (Révéler) -> Peint de l'opaque avec 'source-over' - if (this.settings.mode === 'brush') { - gradient.addColorStop(0, 'white'); - gradient.addColorStop(hardnessStop, 'white'); - gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); - ctx.globalCompositeOperation = 'source-over'; - ctx.fillStyle = gradient; - - // Gomme (Cacher) -> Efface vers le transparent avec 'destination-out' - } else { - gradient.addColorStop(0, 'black'); // La couleur n'importe pas - gradient.addColorStop(hardnessStop, 'black'); - gradient.addColorStop(1, 'rgba(0, 0, 0, 0)'); - ctx.globalCompositeOperation = 'destination-out'; - ctx.fillStyle = gradient; - } - - ctx.beginPath(); - ctx.arc(point.x, point.y, radius, 0, 2 * Math.PI); - ctx.fill(); - - ctx.globalCompositeOperation = 'source-over'; - } +drawStamp(point) { + const ctx = this.maskCtx; + const radius = this.settings.size / 2; + const gradient = ctx.createRadialGradient(point.x, point.y, 0, point.x, point.y, radius); + const hardnessStop = Math.max(0, Math.min(1, this.settings.hardness / 100)); + + ctx.save(); + ctx.globalAlpha = this.settings.opacity / 100; + + if (this.settings.mode === 'brush') { + gradient.addColorStop(0, 'white'); + gradient.addColorStop(hardnessStop, 'white'); + gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); + // On revient à 'lighter' + ctx.globalCompositeOperation = 'lighter'; + ctx.fillStyle = gradient; + } else { // La gomme reste correcte + gradient.addColorStop(0, 'black'); + gradient.addColorStop(hardnessStop, 'black'); + gradient.addColorStop(1, 'rgba(0, 0, 0, 0)'); + ctx.globalCompositeOperation = 'destination-out'; + ctx.fillStyle = gradient; + } + + ctx.beginPath(); + ctx.arc(point.x, point.y, radius, 0, 2 * Math.PI); + ctx.fill(); + + ctx.restore(); +} hide() { if (this.activeLayer) { const props = this.node.layer_properties[this.activeLayer.name]; @@ -343,45 +325,50 @@ async finalizeDrawing() { handleMouseEvent(e) { if (!this.activeLayer) return; - // On calcule la position et on vérifie si on est sur la barre d'outils + // La gestion de la barre d'outils reste la même const mainCanvasRect = this.node.previewCanvas.getBoundingClientRect(); const zoom = app.canvas.ds.scale || 1; const mouseX = (e.clientX - mainCanvasRect.left) / zoom; const mouseY = (e.clientY - mainCanvasRect.top) / zoom; const isOverToolbar = this.node.toolbar.isClickOnToolbar(mouseX, mouseY); - // CAS 1 : La souris est sur la barre d'outils principale if (isOverToolbar) { - this.liveOverlay.style.cursor = 'default'; // On montre le curseur normal - this.updateLivePreview(null); // On met à jour l'aperçu sans le curseur personnalisé - + this.liveOverlay.style.cursor = 'default'; + this.updateLivePreview(null); if (e.type === 'mousedown') { this.node.toolbar.handleClick(e, mouseX, mouseY); } - return; // On s'arrête ici + return; } - - // CAS 2 : La souris est sur la zone de dessin - this.liveOverlay.style.cursor = 'none'; // On cache le curseur normal pour dessiner le nôtre + + this.liveOverlay.style.cursor = 'none'; if (e.type === 'mousedown') { this.isDrawing = true; const coords = this.getOriginalCoords(e); this.lastPoint = coords; - this.drawStamp(coords); + this.drawStamp(coords); // On dessine le premier point } else if (e.type === 'mousemove') { if (this.isDrawing) { const coords = this.getOriginalCoords(e); - this.drawStroke(this.lastPoint, coords); - this.lastPoint = coords; + + // --- TA SOLUTION : L'ESPACEMENT --- + const dist = Math.hypot(coords.x - this.lastPoint.x, coords.y - this.lastPoint.y); + const spacing = this.settings.size / 2; // On peut ajuster cette valeur + + // On ne dessine un nouveau segment que si on a bougé suffisamment + if (dist >= spacing) { + this.drawStroke(this.lastPoint, coords); + this.lastPoint = coords; // On met à jour la position du dernier point DESSINÉ + } + // --- FIN DE LA SOLUTION --- } } else if (e.type === 'mouseup' || e.type === 'mouseleave') { this.isDrawing = false; } - // On met à jour l'aperçu et le curseur personnalisé this.updateLivePreview(e); }