Refactor output area bounds handling for custom shapes

Output area bounds are now positioned relative to the world, not always at (0,0), and are updated to match custom shape placement. Rendering and extension logic have been updated to respect the new bounds, and the mask tool now adjusts to the output area position. Also sets log level to DEBUG for development.
This commit is contained in:
Dariusz L
2025-07-26 21:20:18 +02:00
parent ca9e1890c4
commit f329a6ded5
7 changed files with 90 additions and 98 deletions

View File

@@ -73,7 +73,13 @@ export class Canvas {
this.outputAreaExtensionEnabled = false; this.outputAreaExtensionEnabled = false;
this.outputAreaExtensionPreview = null; this.outputAreaExtensionPreview = null;
this.originalCanvasSize = { width: this.width, height: this.height }; this.originalCanvasSize = { width: this.width, height: this.height };
this.outputAreaBounds = { x: 0, y: 0, width: this.width, height: this.height }; // Initialize outputAreaBounds centered in viewport, similar to how canvas resize/move work
this.outputAreaBounds = {
x: -(this.width / 4),
y: -(this.height / 4),
width: this.width,
height: this.height
};
this.maskTool = new MaskTool(this, { onStateChange: this.onStateChange }); this.maskTool = new MaskTool(this, { onStateChange: this.onStateChange });
this.shapeTool = new ShapeTool(this); this.shapeTool = new ShapeTool(this);
this.customShapeMenu = new CustomShapeMenu(this); this.customShapeMenu = new CustomShapeMenu(this);
@@ -323,36 +329,22 @@ export class Canvas {
}; };
const newWidth = Math.round(boundingBox.width); const newWidth = Math.round(boundingBox.width);
const newHeight = Math.round(boundingBox.height); const newHeight = Math.round(boundingBox.height);
const finalX = boundingBox.x; const newX = Math.round(boundingBox.x);
const finalY = boundingBox.y; const newY = Math.round(boundingBox.y);
// Store the original canvas size for extension calculations
this.originalCanvasSize = { width: newWidth, height: newHeight };
// Update canvas size but don't change outputAreaBounds yet
this.updateOutputAreaSize(newWidth, newHeight, false); this.updateOutputAreaSize(newWidth, newHeight, false);
this.layers.forEach((layer) => { // Set outputAreaBounds to where the custom shape was drawn in the world
layer.x -= finalX; // Similar to finalizeCanvasMove - just update outputAreaBounds position
layer.y -= finalY; this.outputAreaBounds = {
}); x: newX,
this.maskTool.updatePosition(-finalX, -finalY); y: newY,
// Update batch preview managers like in canvas resize/move operations width: newWidth,
if (this.pendingBatchContext) { height: newHeight
this.pendingBatchContext.outputArea.x -= finalX; };
this.pendingBatchContext.outputArea.y -= finalY; // Update mask canvas to ensure it covers the new output area position
// Also update the menu spawn position to keep it relative this.maskTool.updateMaskCanvasForOutputArea();
this.pendingBatchContext.spawnPosition.x -= finalX;
this.pendingBatchContext.spawnPosition.y -= finalY;
log.debug("Updated pending batch context during shape definition:", this.pendingBatchContext);
}
// Also move any active batch preview menus
if (this.batchPreviewManagers && this.batchPreviewManagers.length > 0) {
this.batchPreviewManagers.forEach((manager) => {
manager.worldX -= finalX;
manager.worldY -= finalY;
if (manager.generationArea) {
manager.generationArea.x -= finalX;
manager.generationArea.y -= finalY;
}
});
}
this.viewport.x -= finalX;
this.viewport.y -= finalY;
this.saveState(); this.saveState();
this.render(); this.render();
} }
@@ -399,17 +391,17 @@ export class Canvas {
lastExecutionStartTime = Date.now(); lastExecutionStartTime = Date.now();
// Store a snapshot of the context for the upcoming batch // Store a snapshot of the context for the upcoming batch
this.pendingBatchContext = { this.pendingBatchContext = {
// For the menu position // For the menu position - position relative to outputAreaBounds, not canvas center
spawnPosition: { spawnPosition: {
x: this.width / 2, x: this.outputAreaBounds.x + this.outputAreaBounds.width / 2,
y: this.height y: this.outputAreaBounds.y + this.outputAreaBounds.height
}, },
// For the image placement // For the image placement - use actual outputAreaBounds instead of hardcoded (0,0)
outputArea: { outputArea: {
x: 0, x: this.outputAreaBounds.x,
y: 0, y: this.outputAreaBounds.y,
width: this.width, width: this.outputAreaBounds.width,
height: this.height height: this.outputAreaBounds.height
} }
}; };
log.debug(`Execution started, pending batch context captured:`, this.pendingBatchContext); log.debug(`Execution started, pending batch context captured:`, this.pendingBatchContext);

View File

@@ -272,10 +272,12 @@ export class CanvasRenderer {
ctx.lineWidth = 2 / this.canvas.viewport.zoom; ctx.lineWidth = 2 / this.canvas.viewport.zoom;
ctx.setLineDash([]); ctx.setLineDash([]);
const shape = this.canvas.outputAreaShape; const shape = this.canvas.outputAreaShape;
const bounds = this.canvas.outputAreaBounds;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(shape.points[0].x, shape.points[0].y); // Render custom shape relative to outputAreaBounds, not (0,0)
ctx.moveTo(bounds.x + shape.points[0].x, bounds.y + shape.points[0].y);
for (let i = 1; i < shape.points.length; i++) { for (let i = 1; i < shape.points.length; i++) {
ctx.lineTo(shape.points[i].x, shape.points[i].y); ctx.lineTo(bounds.x + shape.points[i].x, bounds.y + shape.points[i].y);
} }
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();

View File

@@ -610,10 +610,12 @@ export class CustomShapeMenu {
} }
_updateCanvasSize() { _updateCanvasSize() {
if (!this.canvas.outputAreaExtensionEnabled) { if (!this.canvas.outputAreaExtensionEnabled) {
// Reset to original bounds when disabled // When extensions are disabled, preserve the current outputAreaBounds position
// Only update the size to match originalCanvasSize
const currentBounds = this.canvas.outputAreaBounds;
this.canvas.outputAreaBounds = { this.canvas.outputAreaBounds = {
x: 0, x: currentBounds.x, // ✅ Preserve current position
y: 0, y: currentBounds.y, // ✅ Preserve current position
width: this.canvas.originalCanvasSize.width, width: this.canvas.originalCanvasSize.width,
height: this.canvas.originalCanvasSize.height height: this.canvas.originalCanvasSize.height
}; };
@@ -623,10 +625,11 @@ export class CustomShapeMenu {
const ext = this.canvas.outputAreaExtensions; const ext = this.canvas.outputAreaExtensions;
const newWidth = this.canvas.originalCanvasSize.width + ext.left + ext.right; const newWidth = this.canvas.originalCanvasSize.width + ext.left + ext.right;
const newHeight = this.canvas.originalCanvasSize.height + ext.top + ext.bottom; const newHeight = this.canvas.originalCanvasSize.height + ext.top + ext.bottom;
// Aktualizuj outputAreaBounds - "okno" w świecie które zostanie wyrenderowane // When extensions are enabled, calculate new bounds relative to current position
const currentBounds = this.canvas.outputAreaBounds;
this.canvas.outputAreaBounds = { this.canvas.outputAreaBounds = {
x: -ext.left, // Może być ujemne - wycinamy fragment świata x: currentBounds.x - ext.left, // Adjust position by left extension
y: -ext.top, // Może być ujemne - wycinamy fragment świata y: currentBounds.y - ext.top, // Adjust position by top extension
width: newWidth, width: newWidth,
height: newHeight height: newHeight
}; };

View File

@@ -1,3 +1,3 @@
// Log level for development. // Log level for development.
// Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE' // Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'
export const LOG_LEVEL = 'NONE'; export const LOG_LEVEL = 'DEBUG';

View File

@@ -132,7 +132,13 @@ export class Canvas {
this.outputAreaExtensionEnabled = false; this.outputAreaExtensionEnabled = false;
this.outputAreaExtensionPreview = null; this.outputAreaExtensionPreview = null;
this.originalCanvasSize = { width: this.width, height: this.height }; this.originalCanvasSize = { width: this.width, height: this.height };
this.outputAreaBounds = { x: 0, y: 0, width: this.width, height: this.height }; // Initialize outputAreaBounds centered in viewport, similar to how canvas resize/move work
this.outputAreaBounds = {
x: -(this.width / 4),
y: -(this.height / 4),
width: this.width,
height: this.height
};
this.maskTool = new MaskTool(this, {onStateChange: this.onStateChange}); this.maskTool = new MaskTool(this, {onStateChange: this.onStateChange});
this.shapeTool = new ShapeTool(this); this.shapeTool = new ShapeTool(this);
this.customShapeMenu = new CustomShapeMenu(this); this.customShapeMenu = new CustomShapeMenu(this);
@@ -424,43 +430,26 @@ export class Canvas {
const newWidth = Math.round(boundingBox.width); const newWidth = Math.round(boundingBox.width);
const newHeight = Math.round(boundingBox.height); const newHeight = Math.round(boundingBox.height);
const finalX = boundingBox.x; const newX = Math.round(boundingBox.x);
const finalY = boundingBox.y; const newY = Math.round(boundingBox.y);
// Store the original canvas size for extension calculations
this.originalCanvasSize = { width: newWidth, height: newHeight };
// Update canvas size but don't change outputAreaBounds yet
this.updateOutputAreaSize(newWidth, newHeight, false); this.updateOutputAreaSize(newWidth, newHeight, false);
this.layers.forEach((layer: Layer) => { // Set outputAreaBounds to where the custom shape was drawn in the world
layer.x -= finalX; // Similar to finalizeCanvasMove - just update outputAreaBounds position
layer.y -= finalY; this.outputAreaBounds = {
}); x: newX,
y: newY,
width: newWidth,
height: newHeight
};
this.maskTool.updatePosition(-finalX, -finalY); // Update mask canvas to ensure it covers the new output area position
this.maskTool.updateMaskCanvasForOutputArea();
// Update batch preview managers like in canvas resize/move operations
if (this.pendingBatchContext) {
this.pendingBatchContext.outputArea.x -= finalX;
this.pendingBatchContext.outputArea.y -= finalY;
// Also update the menu spawn position to keep it relative
this.pendingBatchContext.spawnPosition.x -= finalX;
this.pendingBatchContext.spawnPosition.y -= finalY;
log.debug("Updated pending batch context during shape definition:", this.pendingBatchContext);
}
// Also move any active batch preview menus
if (this.batchPreviewManagers && this.batchPreviewManagers.length > 0) {
this.batchPreviewManagers.forEach((manager: any) => {
manager.worldX -= finalX;
manager.worldY -= finalY;
if (manager.generationArea) {
manager.generationArea.x -= finalX;
manager.generationArea.y -= finalY;
}
});
}
this.viewport.x -= finalX;
this.viewport.y -= finalY;
this.saveState(); this.saveState();
this.render(); this.render();
@@ -517,17 +506,17 @@ export class Canvas {
lastExecutionStartTime = Date.now(); lastExecutionStartTime = Date.now();
// Store a snapshot of the context for the upcoming batch // Store a snapshot of the context for the upcoming batch
this.pendingBatchContext = { this.pendingBatchContext = {
// For the menu position // For the menu position - position relative to outputAreaBounds, not canvas center
spawnPosition: { spawnPosition: {
x: this.width / 2, x: this.outputAreaBounds.x + this.outputAreaBounds.width / 2,
y: this.height y: this.outputAreaBounds.y + this.outputAreaBounds.height
}, },
// For the image placement // For the image placement - use actual outputAreaBounds instead of hardcoded (0,0)
outputArea: { outputArea: {
x: 0, x: this.outputAreaBounds.x,
y: 0, y: this.outputAreaBounds.y,
width: this.width, width: this.outputAreaBounds.width,
height: this.height height: this.outputAreaBounds.height
} }
}; };
log.debug(`Execution started, pending batch context captured:`, this.pendingBatchContext); log.debug(`Execution started, pending batch context captured:`, this.pendingBatchContext);

View File

@@ -321,10 +321,13 @@ export class CanvasRenderer {
ctx.lineWidth = 2 / this.canvas.viewport.zoom; ctx.lineWidth = 2 / this.canvas.viewport.zoom;
ctx.setLineDash([]); ctx.setLineDash([]);
const shape = this.canvas.outputAreaShape; const shape = this.canvas.outputAreaShape;
const bounds = this.canvas.outputAreaBounds;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(shape.points[0].x, shape.points[0].y); // Render custom shape relative to outputAreaBounds, not (0,0)
ctx.moveTo(bounds.x + shape.points[0].x, bounds.y + shape.points[0].y);
for (let i = 1; i < shape.points.length; i++) { for (let i = 1; i < shape.points.length; i++) {
ctx.lineTo(shape.points[i].x, shape.points[i].y); ctx.lineTo(bounds.x + shape.points[i].x, bounds.y + shape.points[i].y);
} }
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();

View File

@@ -727,12 +727,14 @@ export class CustomShapeMenu {
} }
} }
private _updateCanvasSize(): void { public _updateCanvasSize(): void {
if (!this.canvas.outputAreaExtensionEnabled) { if (!this.canvas.outputAreaExtensionEnabled) {
// Reset to original bounds when disabled // When extensions are disabled, preserve the current outputAreaBounds position
// Only update the size to match originalCanvasSize
const currentBounds = this.canvas.outputAreaBounds;
this.canvas.outputAreaBounds = { this.canvas.outputAreaBounds = {
x: 0, x: currentBounds.x, // ✅ Preserve current position
y: 0, y: currentBounds.y, // ✅ Preserve current position
width: this.canvas.originalCanvasSize.width, width: this.canvas.originalCanvasSize.width,
height: this.canvas.originalCanvasSize.height height: this.canvas.originalCanvasSize.height
}; };
@@ -748,10 +750,11 @@ export class CustomShapeMenu {
const newWidth = this.canvas.originalCanvasSize.width + ext.left + ext.right; const newWidth = this.canvas.originalCanvasSize.width + ext.left + ext.right;
const newHeight = this.canvas.originalCanvasSize.height + ext.top + ext.bottom; const newHeight = this.canvas.originalCanvasSize.height + ext.top + ext.bottom;
// Aktualizuj outputAreaBounds - "okno" w świecie które zostanie wyrenderowane // When extensions are enabled, calculate new bounds relative to current position
const currentBounds = this.canvas.outputAreaBounds;
this.canvas.outputAreaBounds = { this.canvas.outputAreaBounds = {
x: -ext.left, // Może być ujemne - wycinamy fragment świata x: currentBounds.x - ext.left, // Adjust position by left extension
y: -ext.top, // Może być ujemne - wycinamy fragment świata y: currentBounds.y - ext.top, // Adjust position by top extension
width: newWidth, width: newWidth,
height: newHeight height: newHeight
}; };