From 57bd1e14991ac7ed8025bc059f74d9eac169b938 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Mon, 21 Jul 2025 22:35:18 +0200 Subject: [PATCH] Add layer flipH/flipV properties and rendering support Refactors horizontal and vertical mirroring to toggle flipH/flipV properties on layers instead of modifying image data. Updates rendering logic in CanvasLayers and CanvasRenderer to apply horizontal/vertical flipping via canvas transforms. Adds flipH and flipV to Layer type and includes them in state signature calculation. --- js/CanvasLayers.js | 52 +++++++++------------------------ js/CanvasRenderer.js | 5 ++++ js/utils/CommonUtils.js | 4 ++- src/CanvasLayers.ts | 62 ++++++++++++---------------------------- src/CanvasRenderer.ts | 7 +++++ src/types.ts | 2 ++ src/utils/CommonUtils.ts | 4 ++- 7 files changed, 52 insertions(+), 84 deletions(-) diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index c3fd080..ecd7acd 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -306,52 +306,18 @@ export class CanvasLayers { async mirrorHorizontal() { if (this.canvas.canvasSelection.selectedLayers.length === 0) return; - const promises = this.canvas.canvasSelection.selectedLayers.map((layer) => { - return new Promise(resolve => { - const tempCanvas = document.createElement('canvas'); - const tempCtx = tempCanvas.getContext('2d'); - if (!tempCtx) - return; - tempCanvas.width = layer.image.width; - tempCanvas.height = layer.image.height; - tempCtx.translate(tempCanvas.width, 0); - tempCtx.scale(-1, 1); - tempCtx.drawImage(layer.image, 0, 0); - const newImage = new Image(); - newImage.onload = () => { - layer.image = newImage; - resolve(); - }; - newImage.src = tempCanvas.toDataURL(); - }); + this.canvas.canvasSelection.selectedLayers.forEach((layer) => { + layer.flipH = !layer.flipH; }); - await Promise.all(promises); this.canvas.render(); this.canvas.requestSaveState(); } async mirrorVertical() { if (this.canvas.canvasSelection.selectedLayers.length === 0) return; - const promises = this.canvas.canvasSelection.selectedLayers.map((layer) => { - return new Promise(resolve => { - const tempCanvas = document.createElement('canvas'); - const tempCtx = tempCanvas.getContext('2d'); - if (!tempCtx) - return; - tempCanvas.width = layer.image.width; - tempCanvas.height = layer.image.height; - tempCtx.translate(0, tempCanvas.height); - tempCtx.scale(1, -1); - tempCtx.drawImage(layer.image, 0, 0); - const newImage = new Image(); - newImage.onload = () => { - layer.image = newImage; - resolve(); - }; - newImage.src = tempCanvas.toDataURL(); - }); + this.canvas.canvasSelection.selectedLayers.forEach((layer) => { + layer.flipV = !layer.flipV; }); - await Promise.all(promises); this.canvas.render(); this.canvas.requestSaveState(); } @@ -759,6 +725,11 @@ export class CanvasLayers { const centerY = layer.y + layer.height / 2; tempCtx.translate(centerX, centerY); tempCtx.rotate(layer.rotation * Math.PI / 180); + const scaleH = layer.flipH ? -1 : 1; + const scaleV = layer.flipV ? -1 : 1; + if (layer.flipH || layer.flipV) { + tempCtx.scale(scaleH, scaleV); + } tempCtx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height); tempCtx.restore(); }); @@ -824,6 +795,11 @@ export class CanvasLayers { const centerY = layer.y + layer.height / 2; tempCtx.translate(centerX, centerY); tempCtx.rotate(layer.rotation * Math.PI / 180); + const scaleH = layer.flipH ? -1 : 1; + const scaleV = layer.flipV ? -1 : 1; + if (layer.flipH || layer.flipV) { + tempCtx.scale(scaleH, scaleV); + } tempCtx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height); tempCtx.restore(); }); diff --git a/js/CanvasRenderer.js b/js/CanvasRenderer.js index 71332cd..b5acdc7 100644 --- a/js/CanvasRenderer.js +++ b/js/CanvasRenderer.js @@ -58,6 +58,11 @@ export class CanvasRenderer { const centerY = layer.y + layer.height / 2; ctx.translate(centerX, centerY); ctx.rotate(layer.rotation * Math.PI / 180); + const scaleH = layer.flipH ? -1 : 1; + const scaleV = layer.flipV ? -1 : 1; + if (layer.flipH || layer.flipV) { + ctx.scale(scaleH, scaleV); + } ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height); diff --git a/js/utils/CommonUtils.js b/js/utils/CommonUtils.js index af632b1..52e99bf 100644 --- a/js/utils/CommonUtils.js +++ b/js/utils/CommonUtils.js @@ -111,7 +111,9 @@ export function getStateSignature(layers) { rotation: Math.round((layer.rotation || 0) * 100) / 100, zIndex: layer.zIndex, blendMode: layer.blendMode || 'normal', - opacity: layer.opacity !== undefined ? Math.round(layer.opacity * 100) / 100 : 1 + opacity: layer.opacity !== undefined ? Math.round(layer.opacity * 100) / 100 : 1, + flipH: !!layer.flipH, + flipV: !!layer.flipV }; if (layer.imageId) { sig.imageId = layer.imageId; diff --git a/src/CanvasLayers.ts b/src/CanvasLayers.ts index 873d163..97a7ea6 100644 --- a/src/CanvasLayers.ts +++ b/src/CanvasLayers.ts @@ -351,58 +351,18 @@ export class CanvasLayers { async mirrorHorizontal(): Promise { if (this.canvas.canvasSelection.selectedLayers.length === 0) return; - - const promises = this.canvas.canvasSelection.selectedLayers.map((layer: Layer) => { - return new Promise(resolve => { - const tempCanvas = document.createElement('canvas'); - const tempCtx = tempCanvas.getContext('2d'); - if (!tempCtx) return; - tempCanvas.width = layer.image.width; - tempCanvas.height = layer.image.height; - - tempCtx.translate(tempCanvas.width, 0); - tempCtx.scale(-1, 1); - tempCtx.drawImage(layer.image, 0, 0); - - const newImage = new Image(); - newImage.onload = () => { - layer.image = newImage; - resolve(); - }; - newImage.src = tempCanvas.toDataURL(); - }); + this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => { + layer.flipH = !layer.flipH; }); - - await Promise.all(promises); this.canvas.render(); this.canvas.requestSaveState(); } async mirrorVertical(): Promise { if (this.canvas.canvasSelection.selectedLayers.length === 0) return; - - const promises = this.canvas.canvasSelection.selectedLayers.map((layer: Layer) => { - return new Promise(resolve => { - const tempCanvas = document.createElement('canvas'); - const tempCtx = tempCanvas.getContext('2d'); - if (!tempCtx) return; - tempCanvas.width = layer.image.width; - tempCanvas.height = layer.image.height; - - tempCtx.translate(0, tempCanvas.height); - tempCtx.scale(1, -1); - tempCtx.drawImage(layer.image, 0, 0); - - const newImage = new Image(); - newImage.onload = () => { - layer.image = newImage; - resolve(); - }; - newImage.src = tempCanvas.toDataURL(); - }); + this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => { + layer.flipV = !layer.flipV; }); - - await Promise.all(promises); this.canvas.render(); this.canvas.requestSaveState(); } @@ -896,6 +856,13 @@ export class CanvasLayers { const centerY = layer.y + layer.height / 2; tempCtx.translate(centerX, centerY); tempCtx.rotate(layer.rotation * Math.PI / 180); + + const scaleH = layer.flipH ? -1 : 1; + const scaleV = layer.flipV ? -1 : 1; + if (layer.flipH || layer.flipV) { + tempCtx.scale(scaleH, scaleV); + } + tempCtx.drawImage( layer.image, -layer.width / 2, -layer.height / 2, @@ -979,6 +946,13 @@ export class CanvasLayers { const centerY = layer.y + layer.height / 2; tempCtx.translate(centerX, centerY); tempCtx.rotate(layer.rotation * Math.PI / 180); + + const scaleH = layer.flipH ? -1 : 1; + const scaleV = layer.flipV ? -1 : 1; + if (layer.flipH || layer.flipV) { + tempCtx.scale(scaleH, scaleV); + } + tempCtx.drawImage( layer.image, -layer.width / 2, -layer.height / 2, diff --git a/src/CanvasRenderer.ts b/src/CanvasRenderer.ts index 0d78cf2..c14fc4a 100644 --- a/src/CanvasRenderer.ts +++ b/src/CanvasRenderer.ts @@ -71,6 +71,13 @@ export class CanvasRenderer { const centerY = layer.y + layer.height / 2; ctx.translate(centerX, centerY); ctx.rotate(layer.rotation * Math.PI / 180); + + const scaleH = layer.flipH ? -1 : 1; + const scaleV = layer.flipV ? -1 : 1; + if (layer.flipH || layer.flipV) { + ctx.scale(scaleH, scaleV); + } + ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; ctx.drawImage( diff --git a/src/types.ts b/src/types.ts index 8561258..3cb8a90 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,6 +17,8 @@ export interface Layer { blendMode: string; opacity: number; mask?: Float32Array; + flipH?: boolean; + flipV?: boolean; } export interface ComfyNode { diff --git a/src/utils/CommonUtils.ts b/src/utils/CommonUtils.ts index 8d2cff8..dd551aa 100644 --- a/src/utils/CommonUtils.ts +++ b/src/utils/CommonUtils.ts @@ -136,7 +136,9 @@ export function getStateSignature(layers: Layer[]): string { rotation: Math.round((layer.rotation || 0) * 100) / 100, zIndex: layer.zIndex, blendMode: layer.blendMode || 'normal', - opacity: layer.opacity !== undefined ? Math.round(layer.opacity * 100) / 100 : 1 + opacity: layer.opacity !== undefined ? Math.round(layer.opacity * 100) / 100 : 1, + flipH: !!layer.flipH, + flipV: !!layer.flipV }; if (layer.imageId) {