mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
Implement crop mode for cropping selected layer
This commit is contained in:
@@ -372,8 +372,24 @@ export class CanvasLayers {
|
||||
// Create a temporary canvas for the masked layer
|
||||
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(layer.width, layer.height);
|
||||
if (tempCtx) {
|
||||
// Draw the original image
|
||||
tempCtx.drawImage(layer.image, 0, 0, layer.width, layer.height);
|
||||
// This logic is now unified to handle both cropped and non-cropped images correctly.
|
||||
const s = layer.cropBounds || { x: 0, y: 0, width: layer.originalWidth, height: layer.originalHeight };
|
||||
if (!layer.originalWidth || !layer.originalHeight) {
|
||||
tempCtx.drawImage(layer.image, 0, 0, layer.width, layer.height);
|
||||
}
|
||||
else {
|
||||
const layerScaleX = layer.width / layer.originalWidth;
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
const dWidth = s.width * layerScaleX;
|
||||
const dHeight = s.height * layerScaleY;
|
||||
// The destination is the top-left of the temp canvas, plus the scaled offset of the crop area.
|
||||
const dX = s.x * layerScaleX;
|
||||
const dY = s.y * layerScaleY;
|
||||
// We draw into a temp canvas of size layer.width x layer.height.
|
||||
// The destination rect must be positioned correctly within this temp canvas.
|
||||
// The dX/dY here are offsets from the top-left of the transform frame.
|
||||
tempCtx.drawImage(layer.image, s.x, s.y, s.width, s.height, dX, dY, dWidth, dHeight);
|
||||
}
|
||||
// Apply the distance field mask using destination-in for transparency effect
|
||||
tempCtx.globalCompositeOperation = 'destination-in';
|
||||
tempCtx.drawImage(maskCanvas, 0, 0, layer.width, layer.height);
|
||||
@@ -384,26 +400,44 @@ export class CanvasLayers {
|
||||
}
|
||||
else {
|
||||
// Fallback to normal drawing
|
||||
ctx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
ctx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
this._drawLayerImage(ctx, layer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Fallback to normal drawing
|
||||
ctx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
ctx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
this._drawLayerImage(ctx, layer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Normal drawing without blend area effect
|
||||
ctx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
ctx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
this._drawLayerImage(ctx, layer);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
_drawLayerImage(ctx, layer) {
|
||||
ctx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
// Use cropBounds if they exist, otherwise use the full image dimensions as the source
|
||||
const s = layer.cropBounds || { x: 0, y: 0, width: layer.originalWidth, height: layer.originalHeight };
|
||||
if (!layer.originalWidth || !layer.originalHeight) {
|
||||
// Fallback for older layers without original dimensions or if data is missing
|
||||
ctx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
return;
|
||||
}
|
||||
// Calculate the on-screen scale of the layer's transform frame
|
||||
const layerScaleX = layer.width / layer.originalWidth;
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
// Calculate the on-screen size of the cropped portion
|
||||
const dWidth = s.width * layerScaleX;
|
||||
const dHeight = s.height * layerScaleY;
|
||||
// Calculate the on-screen position of the top-left of the cropped portion.
|
||||
// This is relative to the layer's center (the context's 0,0).
|
||||
const dX = (-layer.width / 2) + (s.x * layerScaleX);
|
||||
const dY = (-layer.height / 2) + (s.y * layerScaleY);
|
||||
ctx.drawImage(layer.image, s.x, s.y, s.width, s.height, // source rect (from original image)
|
||||
dX, dY, dWidth, dHeight // destination rect (scaled and positioned within the transform frame)
|
||||
);
|
||||
}
|
||||
getDistanceFieldMaskSync(image, blendArea) {
|
||||
// Check cache first
|
||||
let imageCache = this.distanceFieldCache.get(image);
|
||||
@@ -527,30 +561,47 @@ export class CanvasLayers {
|
||||
this.canvas.saveState();
|
||||
}
|
||||
getHandles(layer) {
|
||||
const centerX = layer.x + layer.width / 2;
|
||||
const centerY = layer.y + layer.height / 2;
|
||||
const layerCenterX = layer.x + layer.width / 2;
|
||||
const layerCenterY = layer.y + layer.height / 2;
|
||||
const rad = layer.rotation * Math.PI / 180;
|
||||
const cos = Math.cos(rad);
|
||||
const sin = Math.sin(rad);
|
||||
const halfW = layer.width / 2;
|
||||
const halfH = layer.height / 2;
|
||||
let handleCenterX, handleCenterY, halfW, halfH;
|
||||
if (layer.cropMode && layer.cropBounds && layer.originalWidth) {
|
||||
// CROP MODE: Handles are relative to the cropped area
|
||||
const layerScaleX = layer.width / layer.originalWidth;
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
const cropRectW = layer.cropBounds.width * layerScaleX;
|
||||
const cropRectH = layer.cropBounds.height * layerScaleY;
|
||||
// Center of the CROP rectangle in the layer's local, un-rotated space
|
||||
const cropCenterX_local = (-layer.width / 2) + ((layer.cropBounds.x + layer.cropBounds.width / 2) * layerScaleX);
|
||||
const cropCenterY_local = (-layer.height / 2) + ((layer.cropBounds.y + layer.cropBounds.height / 2) * layerScaleY);
|
||||
// Rotate this local center to find the world-space center of the crop rect
|
||||
handleCenterX = layerCenterX + (cropCenterX_local * cos - cropCenterY_local * sin);
|
||||
handleCenterY = layerCenterY + (cropCenterX_local * sin + cropCenterY_local * cos);
|
||||
halfW = cropRectW / 2;
|
||||
halfH = cropRectH / 2;
|
||||
}
|
||||
else {
|
||||
// TRANSFORM MODE: Handles are relative to the full layer transform frame
|
||||
handleCenterX = layerCenterX;
|
||||
handleCenterY = layerCenterY;
|
||||
halfW = layer.width / 2;
|
||||
halfH = layer.height / 2;
|
||||
}
|
||||
const localHandles = {
|
||||
'n': { x: 0, y: -halfH },
|
||||
'ne': { x: halfW, y: -halfH },
|
||||
'e': { x: halfW, y: 0 },
|
||||
'se': { x: halfW, y: halfH },
|
||||
's': { x: 0, y: halfH },
|
||||
'sw': { x: -halfW, y: halfH },
|
||||
'w': { x: -halfW, y: 0 },
|
||||
'nw': { x: -halfW, y: -halfH },
|
||||
'n': { x: 0, y: -halfH }, 'ne': { x: halfW, y: -halfH },
|
||||
'e': { x: halfW, y: 0 }, 'se': { x: halfW, y: halfH },
|
||||
's': { x: 0, y: halfH }, 'sw': { x: -halfW, y: halfH },
|
||||
'w': { x: -halfW, y: 0 }, 'nw': { x: -halfW, y: -halfH },
|
||||
'rot': { x: 0, y: -halfH - 20 / this.canvas.viewport.zoom }
|
||||
};
|
||||
const worldHandles = {};
|
||||
for (const key in localHandles) {
|
||||
const p = localHandles[key];
|
||||
worldHandles[key] = {
|
||||
x: centerX + (p.x * cos - p.y * sin),
|
||||
y: centerY + (p.x * sin + p.y * cos)
|
||||
x: handleCenterX + (p.x * cos - p.y * sin),
|
||||
y: handleCenterY + (p.x * sin + p.y * cos)
|
||||
};
|
||||
}
|
||||
return worldHandles;
|
||||
|
||||
Reference in New Issue
Block a user