mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-24 05:52:12 -03:00
Move Canvas
This commit is contained in:
203
js/Canvas.js
203
js/Canvas.js
@@ -10,7 +10,7 @@ export class Canvas {
|
|||||||
this.selectedLayer = null;
|
this.selectedLayer = null;
|
||||||
this.selectedLayers = [];
|
this.selectedLayers = [];
|
||||||
this.onSelectionChange = null;
|
this.onSelectionChange = null;
|
||||||
this.lastMousePosition = { x: 0, y: 0 };
|
this.lastMousePosition = {x: 0, y: 0};
|
||||||
|
|
||||||
this.viewport = {
|
this.viewport = {
|
||||||
x: -(this.width / 4),
|
x: -(this.width / 4),
|
||||||
@@ -31,7 +31,8 @@ export class Canvas {
|
|||||||
lastClickTime: 0,
|
lastClickTime: 0,
|
||||||
};
|
};
|
||||||
this.originalLayerPositions = new Map();
|
this.originalLayerPositions = new Map();
|
||||||
this.canvasResizeRect = null;
|
this.interaction.canvasResizeRect = null;
|
||||||
|
this.interaction.canvasMoveRect = null;
|
||||||
|
|
||||||
this.offscreenCanvas = document.createElement('canvas');
|
this.offscreenCanvas = document.createElement('canvas');
|
||||||
this.offscreenCtx = this.offscreenCanvas.getContext('2d', {
|
this.offscreenCtx = this.offscreenCanvas.getContext('2d', {
|
||||||
@@ -94,8 +95,12 @@ export class Canvas {
|
|||||||
document.addEventListener('keydown', this.handleKeyDown.bind(this));
|
document.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||||
document.addEventListener('keyup', this.handleKeyUp.bind(this));
|
document.addEventListener('keyup', this.handleKeyUp.bind(this));
|
||||||
|
|
||||||
this.canvas.addEventListener('mouseenter', () => { this.isMouseOver = true; });
|
this.canvas.addEventListener('mouseenter', () => {
|
||||||
this.canvas.addEventListener('mouseleave', () => { this.isMouseOver = false; });
|
this.isMouseOver = true;
|
||||||
|
});
|
||||||
|
this.canvas.addEventListener('mouseleave', () => {
|
||||||
|
this.isMouseOver = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelection(newSelection) {
|
updateSelection(newSelection) {
|
||||||
@@ -113,7 +118,8 @@ export class Canvas {
|
|||||||
this.interaction.mode = 'none';
|
this.interaction.mode = 'none';
|
||||||
this.interaction.resizeHandle = null;
|
this.interaction.resizeHandle = null;
|
||||||
this.originalLayerPositions.clear();
|
this.originalLayerPositions.clear();
|
||||||
this.canvasResizeRect = null;
|
this.interaction.canvasResizeRect = null;
|
||||||
|
this.interaction.canvasMoveRect = null;
|
||||||
this.interaction.hasClonedInDrag = false;
|
this.interaction.hasClonedInDrag = false;
|
||||||
this.canvas.style.cursor = 'default';
|
this.canvas.style.cursor = 'default';
|
||||||
}
|
}
|
||||||
@@ -124,6 +130,12 @@ export class Canvas {
|
|||||||
handleMouseDown(e) {
|
handleMouseDown(e) {
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
const worldCoords = this.getMouseWorldCoordinates(e);
|
const worldCoords = this.getMouseWorldCoordinates(e);
|
||||||
|
if (e.shiftKey && e.ctrlKey) {
|
||||||
|
this.startCanvasMove(worldCoords);
|
||||||
|
this.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentTime - this.interaction.lastClickTime < 300) {
|
if (currentTime - this.interaction.lastClickTime < 300) {
|
||||||
this.updateSelection([]);
|
this.updateSelection([]);
|
||||||
this.selectedLayer = null;
|
this.selectedLayer = null;
|
||||||
@@ -163,23 +175,17 @@ export class Canvas {
|
|||||||
*/
|
*/
|
||||||
async copySelectedLayers() {
|
async copySelectedLayers() {
|
||||||
if (this.selectedLayers.length === 0) return;
|
if (this.selectedLayers.length === 0) return;
|
||||||
|
this.internalClipboard = this.selectedLayers.map(layer => ({...layer}));
|
||||||
// 1. Kopiowanie do wewnętrznego schowka (bez zmian)
|
|
||||||
this.internalClipboard = this.selectedLayers.map(layer => ({ ...layer }));
|
|
||||||
console.log(`Copied ${this.internalClipboard.length} layer(s) to internal clipboard.`);
|
console.log(`Copied ${this.internalClipboard.length} layer(s) to internal clipboard.`);
|
||||||
|
|
||||||
// 2. Kopiowanie spłaszczonego obrazu do globalnego schowka
|
|
||||||
try {
|
try {
|
||||||
const blob = await this.getFlattenedSelectionAsBlob();
|
const blob = await this.getFlattenedSelectionAsBlob();
|
||||||
if (blob) {
|
if (blob) {
|
||||||
// Używamy Clipboard API do wstawienia obrazka
|
const item = new ClipboardItem({'image/png': blob});
|
||||||
const item = new ClipboardItem({ 'image/png': blob });
|
|
||||||
await navigator.clipboard.write([item]);
|
await navigator.clipboard.write([item]);
|
||||||
console.log("Flattened selection copied to the system clipboard.");
|
console.log("Flattened selection copied to the system clipboard.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to copy image to system clipboard:", error);
|
console.error("Failed to copy image to system clipboard:", error);
|
||||||
// Można tu dodać powiadomienie dla użytkownika, jeśli operacja się nie uda
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,35 +196,34 @@ export class Canvas {
|
|||||||
if (this.internalClipboard.length === 0) return;
|
if (this.internalClipboard.length === 0) return;
|
||||||
|
|
||||||
const newLayers = [];
|
const newLayers = [];
|
||||||
const pasteOffset = 20; // Przesunięcie wklejonych warstw
|
const pasteOffset = 20;
|
||||||
|
|
||||||
this.internalClipboard.forEach(clipboardLayer => {
|
this.internalClipboard.forEach(clipboardLayer => {
|
||||||
const newLayer = {
|
const newLayer = {
|
||||||
...clipboardLayer,
|
...clipboardLayer,
|
||||||
x: clipboardLayer.x + pasteOffset / this.viewport.zoom,
|
x: clipboardLayer.x + pasteOffset / this.viewport.zoom,
|
||||||
y: clipboardLayer.y + pasteOffset / this.viewport.zoom,
|
y: clipboardLayer.y + pasteOffset / this.viewport.zoom,
|
||||||
zIndex: this.layers.length // Upewnij się, że nowa warstwa jest na wierzchu
|
zIndex: this.layers.length
|
||||||
};
|
};
|
||||||
this.layers.push(newLayer);
|
this.layers.push(newLayer);
|
||||||
newLayers.push(newLayer);
|
newLayers.push(newLayer);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateSelection(newLayers); // Zaznacz nowo wklejone warstwy
|
this.updateSelection(newLayers);
|
||||||
this.render();
|
this.render();
|
||||||
console.log(`Pasted ${newLayers.length} layer(s).`);
|
console.log(`Pasted ${newLayers.length} layer(s).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inteligentnie obsługuje operację wklejania.
|
* Inteligentnie obsługuje operację wklejania.
|
||||||
* Najpierw próbuje wkleić obraz z globalnego schowka,
|
* Najpierw próbuje wkleić obraz z globalnego schowka,
|
||||||
* a jeśli to się nie uda, wkleja z wewnętrznego schowka.
|
* a jeśli to się nie uda, wkleja z wewnętrznego schowka.
|
||||||
*/
|
*/
|
||||||
async handlePaste() {
|
async handlePaste() {
|
||||||
try {
|
try {
|
||||||
// Sprawdź, czy przeglądarka obsługuje API schowka
|
|
||||||
if (!navigator.clipboard?.read) {
|
if (!navigator.clipboard?.read) {
|
||||||
console.log("Browser does not support clipboard read API. Falling back to internal paste.");
|
console.log("Browser does not support clipboard read API. Falling back to internal paste.");
|
||||||
this.pasteLayers(); // Fallback do wklejania wewnętrznego
|
this.pasteLayers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,40 +237,33 @@ export class Canvas {
|
|||||||
const blob = await item.getType(imageType);
|
const blob = await item.getType(imageType);
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
// Tworzenie nowej warstwy z obrazka ze schowka
|
|
||||||
const newLayer = {
|
const newLayer = {
|
||||||
image: img,
|
image: img,
|
||||||
// Wklej obrazek tak, aby jego środek był pod kursorem
|
|
||||||
x: this.lastMousePosition.x - img.width / 2,
|
x: this.lastMousePosition.x - img.width / 2,
|
||||||
y: this.lastMousePosition.y - img.height / 2,
|
y: this.lastMousePosition.y - img.height / 2,
|
||||||
width: img.width, // Oryginalna szerokość
|
width: img.width,
|
||||||
height: img.height, // Oryginalna wysokość
|
height: img.height,
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
zIndex: this.layers.length,
|
zIndex: this.layers.length,
|
||||||
blendMode: 'normal',
|
blendMode: 'normal',
|
||||||
opacity: 1
|
opacity: 1
|
||||||
};
|
};
|
||||||
this.layers.push(newLayer);
|
this.layers.push(newLayer);
|
||||||
this.updateSelection([newLayer]); // Zaznacz nową warstwę
|
this.updateSelection([newLayer]);
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
// Zwolnij zasoby, aby uniknąć wycieków pamięci
|
|
||||||
URL.revokeObjectURL(img.src);
|
URL.revokeObjectURL(img.src);
|
||||||
};
|
};
|
||||||
img.src = URL.createObjectURL(blob);
|
img.src = URL.createObjectURL(blob);
|
||||||
imagePasted = true;
|
imagePasted = true;
|
||||||
break; // Znaleziono i przetworzono obraz, przerwij pętlę
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jeśli żaden obraz nie został wklejony z globalnego schowka, użyj naszego wewnętrznego
|
|
||||||
if (!imagePasted) {
|
if (!imagePasted) {
|
||||||
this.pasteLayers();
|
this.pasteLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Paste operation failed, falling back to internal paste. Error:", err);
|
console.error("Paste operation failed, falling back to internal paste. Error:", err);
|
||||||
// Błąd (np. brak uprawnień) również powinien skutkować próbą wklejenia z wewnętrznego schowka
|
|
||||||
this.pasteLayers();
|
this.pasteLayers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,7 +273,7 @@ export class Canvas {
|
|||||||
*/
|
*/
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
const worldCoords = this.getMouseWorldCoordinates(e);
|
const worldCoords = this.getMouseWorldCoordinates(e);
|
||||||
this.lastMousePosition = worldCoords; // Zapisujemy ostatnią pozycję kursora
|
this.lastMousePosition = worldCoords;
|
||||||
|
|
||||||
switch (this.interaction.mode) {
|
switch (this.interaction.mode) {
|
||||||
case 'panning':
|
case 'panning':
|
||||||
@@ -293,6 +291,9 @@ export class Canvas {
|
|||||||
case 'resizingCanvas':
|
case 'resizingCanvas':
|
||||||
this.updateCanvasResize(worldCoords);
|
this.updateCanvasResize(worldCoords);
|
||||||
break;
|
break;
|
||||||
|
case 'movingCanvas':
|
||||||
|
this.updateCanvasMove(worldCoords);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
this.updateCursor(worldCoords);
|
this.updateCursor(worldCoords);
|
||||||
break;
|
break;
|
||||||
@@ -305,6 +306,8 @@ export class Canvas {
|
|||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
if (this.interaction.mode === 'resizingCanvas') {
|
if (this.interaction.mode === 'resizingCanvas') {
|
||||||
this.finalizeCanvasResize();
|
this.finalizeCanvasResize();
|
||||||
|
} else if (this.interaction.mode === 'movingCanvas') {
|
||||||
|
this.finalizeCanvasMove();
|
||||||
}
|
}
|
||||||
this.resetInteractionState();
|
this.resetInteractionState();
|
||||||
this.render();
|
this.render();
|
||||||
@@ -401,9 +404,7 @@ export class Canvas {
|
|||||||
* Metoda obsługująca wciśnięcie klawisza.
|
* Metoda obsługująca wciśnięcie klawisza.
|
||||||
*/
|
*/
|
||||||
handleKeyDown(e) {
|
handleKeyDown(e) {
|
||||||
// Przechwytywanie Ctrl+C i Ctrl+V tylko jeśli kursor jest nad płótnem
|
|
||||||
if (this.isMouseOver) {
|
if (this.isMouseOver) {
|
||||||
// Kopiowanie (Ctrl+C)
|
|
||||||
if (e.ctrlKey && e.key.toLowerCase() === 'c') {
|
if (e.ctrlKey && e.key.toLowerCase() === 'c') {
|
||||||
if (this.selectedLayers.length > 0) {
|
if (this.selectedLayers.length > 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -412,12 +413,10 @@ export class Canvas {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wklejanie (Ctrl+V)
|
|
||||||
if (e.ctrlKey && e.key.toLowerCase() === 'v') {
|
if (e.ctrlKey && e.key.toLowerCase() === 'v') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.handlePaste(); // Wywołujemy naszą nową, inteligentną funkcję
|
this.handlePaste();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,6 +471,7 @@ export class Canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metoda obsługująca puszczenie klawisza.
|
* Metoda obsługująca puszczenie klawisza.
|
||||||
*/
|
*/
|
||||||
@@ -554,7 +554,59 @@ export class Canvas {
|
|||||||
const startX = this.snapToGrid(worldCoords.x);
|
const startX = this.snapToGrid(worldCoords.x);
|
||||||
const startY = this.snapToGrid(worldCoords.y);
|
const startY = this.snapToGrid(worldCoords.y);
|
||||||
this.interaction.canvasResizeStart = {x: startX, y: startY};
|
this.interaction.canvasResizeStart = {x: startX, y: startY};
|
||||||
this.canvasResizeRect = {x: startX, y: startY, width: 0, height: 0};
|
this.interaction.canvasResizeRect = {x: startX, y: startY, width: 0, height: 0};
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
startCanvasMove(worldCoords) {
|
||||||
|
this.interaction.mode = 'movingCanvas';
|
||||||
|
this.interaction.dragStart = { ...worldCoords };
|
||||||
|
const initialX = this.snapToGrid(worldCoords.x - this.width / 2);
|
||||||
|
const initialY = this.snapToGrid(worldCoords.y - this.height / 2);
|
||||||
|
|
||||||
|
this.interaction.canvasMoveRect = {
|
||||||
|
x: initialX,
|
||||||
|
y: initialY,
|
||||||
|
width: this.width,
|
||||||
|
height: this.height
|
||||||
|
};
|
||||||
|
|
||||||
|
this.canvas.style.cursor = 'grabbing';
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualizuje pozycję "ducha" płótna podczas przesuwania.
|
||||||
|
*/
|
||||||
|
updateCanvasMove(worldCoords) {
|
||||||
|
if (!this.interaction.canvasMoveRect) return;
|
||||||
|
const dx = worldCoords.x - this.interaction.dragStart.x;
|
||||||
|
const dy = worldCoords.y - this.interaction.dragStart.y;
|
||||||
|
const initialRectX = this.snapToGrid(this.interaction.dragStart.x - this.width / 2);
|
||||||
|
const initialRectY = this.snapToGrid(this.interaction.dragStart.y - this.height / 2);
|
||||||
|
this.interaction.canvasMoveRect.x = this.snapToGrid(initialRectX + dx);
|
||||||
|
this.interaction.canvasMoveRect.y = this.snapToGrid(initialRectY + dy);
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kończy przesuwanie płótna i zatwierdza nową pozycję.
|
||||||
|
*/
|
||||||
|
finalizeCanvasMove() {
|
||||||
|
const moveRect = this.interaction.canvasMoveRect;
|
||||||
|
|
||||||
|
if (moveRect && (moveRect.x !== 0 || moveRect.y !== 0)) {
|
||||||
|
const finalX = moveRect.x;
|
||||||
|
const finalY = moveRect.y;
|
||||||
|
|
||||||
|
this.layers.forEach(layer => {
|
||||||
|
layer.x -= finalX;
|
||||||
|
layer.y -= finalY;
|
||||||
|
});
|
||||||
|
this.viewport.x -= finalX;
|
||||||
|
this.viewport.y -= finalY;
|
||||||
|
}
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,19 +753,19 @@ export class Canvas {
|
|||||||
const snappedMouseY = this.snapToGrid(worldCoords.y);
|
const snappedMouseY = this.snapToGrid(worldCoords.y);
|
||||||
const start = this.interaction.canvasResizeStart;
|
const start = this.interaction.canvasResizeStart;
|
||||||
|
|
||||||
this.canvasResizeRect.x = Math.min(snappedMouseX, start.x);
|
this.interaction.canvasResizeRect.x = Math.min(snappedMouseX, start.x);
|
||||||
this.canvasResizeRect.y = Math.min(snappedMouseY, start.y);
|
this.interaction.canvasResizeRect.y = Math.min(snappedMouseY, start.y);
|
||||||
this.canvasResizeRect.width = Math.abs(snappedMouseX - start.x);
|
this.interaction.canvasResizeRect.width = Math.abs(snappedMouseX - start.x);
|
||||||
this.canvasResizeRect.height = Math.abs(snappedMouseY - start.y);
|
this.interaction.canvasResizeRect.height = Math.abs(snappedMouseY - start.y);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
finalizeCanvasResize() {
|
finalizeCanvasResize() {
|
||||||
if (this.canvasResizeRect && this.canvasResizeRect.width > 1 && this.canvasResizeRect.height > 1) {
|
if (this.interaction.canvasResizeRect && this.interaction.canvasResizeRect.width > 1 && this.interaction.canvasResizeRect.height > 1) {
|
||||||
const newWidth = Math.round(this.canvasResizeRect.width);
|
const newWidth = Math.round(this.interaction.canvasResizeRect.width);
|
||||||
const newHeight = Math.round(this.canvasResizeRect.height);
|
const newHeight = Math.round(this.interaction.canvasResizeRect.height);
|
||||||
const rectX = this.canvasResizeRect.x;
|
const rectX = this.interaction.canvasResizeRect.x;
|
||||||
const rectY = this.canvasResizeRect.y;
|
const rectY = this.interaction.canvasResizeRect.y;
|
||||||
|
|
||||||
this.updateCanvasSize(newWidth, newHeight);
|
this.updateCanvasSize(newWidth, newHeight);
|
||||||
|
|
||||||
@@ -932,10 +984,10 @@ export class Canvas {
|
|||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
});
|
});
|
||||||
this.drawCanvasOutline(ctx);
|
|
||||||
|
|
||||||
if (this.interaction.mode === 'resizingCanvas' && this.canvasResizeRect) {
|
this.drawCanvasOutline(ctx);
|
||||||
const rect = this.canvasResizeRect;
|
if (this.interaction.mode === 'resizingCanvas' && this.interaction.canvasResizeRect) {
|
||||||
|
const rect = this.interaction.canvasResizeRect;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.strokeStyle = 'rgba(0, 255, 0, 0.8)';
|
ctx.strokeStyle = 'rgba(0, 255, 0, 0.8)';
|
||||||
ctx.lineWidth = 2 / this.viewport.zoom;
|
ctx.lineWidth = 2 / this.viewport.zoom;
|
||||||
@@ -950,26 +1002,51 @@ export class Canvas {
|
|||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
|
||||||
const screenX = (textWorldX - this.viewport.x) * this.viewport.zoom;
|
const screenX = (textWorldX - this.viewport.x) * this.viewport.zoom;
|
||||||
const screenY = (textWorldY - this.viewport.y) * this.viewport.zoom;
|
const screenY = (textWorldY - this.viewport.y) * this.viewport.zoom;
|
||||||
|
|
||||||
ctx.font = "14px sans-serif";
|
ctx.font = "14px sans-serif";
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
const textMetrics = ctx.measureText(text);
|
const textMetrics = ctx.measureText(text);
|
||||||
const bgWidth = textMetrics.width + 10;
|
const bgWidth = textMetrics.width + 10;
|
||||||
const bgHeight = 22;
|
const bgHeight = 22;
|
||||||
|
|
||||||
ctx.fillStyle = "rgba(0, 128, 0, 0.7)";
|
ctx.fillStyle = "rgba(0, 128, 0, 0.7)";
|
||||||
ctx.fillRect(screenX - bgWidth / 2, screenY - bgHeight / 2, bgWidth, bgHeight);
|
ctx.fillRect(screenX - bgWidth / 2, screenY - bgHeight / 2, bgWidth, bgHeight);
|
||||||
|
|
||||||
ctx.fillStyle = "white";
|
ctx.fillStyle = "white";
|
||||||
ctx.fillText(text, screenX, screenY);
|
ctx.fillText(text, screenX, screenY);
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.interaction.mode === 'movingCanvas' && this.interaction.canvasMoveRect) {
|
||||||
|
const rect = this.interaction.canvasMoveRect;
|
||||||
|
ctx.save();
|
||||||
|
ctx.strokeStyle = 'rgba(0, 150, 255, 0.8)';
|
||||||
|
ctx.lineWidth = 2 / this.viewport.zoom;
|
||||||
|
ctx.setLineDash([10 / this.viewport.zoom, 5 / this.viewport.zoom]);
|
||||||
|
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
const text = `(${Math.round(rect.x)}, ${Math.round(rect.y)})`;
|
||||||
|
const textWorldX = rect.x + rect.width / 2;
|
||||||
|
const textWorldY = rect.y - (20 / this.viewport.zoom);
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
const screenX = (textWorldX - this.viewport.x) * this.viewport.zoom;
|
||||||
|
const screenY = (textWorldY - this.viewport.y) * this.viewport.zoom;
|
||||||
|
ctx.font = "14px sans-serif";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
const textMetrics = ctx.measureText(text);
|
||||||
|
const bgWidth = textMetrics.width + 10;
|
||||||
|
const bgHeight = 22;
|
||||||
|
ctx.fillStyle = "rgba(0, 100, 170, 0.7)";
|
||||||
|
ctx.fillRect(screenX - bgWidth / 2, screenY - bgHeight / 2, bgWidth, bgHeight);
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
ctx.fillText(text, screenX, screenY);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.selectedLayer) {
|
if (this.selectedLayer) {
|
||||||
this.selectedLayers.forEach(layer => {
|
this.selectedLayers.forEach(layer => {
|
||||||
@@ -1361,7 +1438,7 @@ export class Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tworzy spłaszczony obraz z zaznaczonych warstw, przycięty do ich zawartości.
|
* Tworzy spłaszczony obraz z zaznaczonych warstw, przycięty do ich zawartości.
|
||||||
* @returns {Promise<Blob|null>} Obiekt Blob z obrazem PNG lub null, jeśli nic nie jest zaznaczone.
|
* @returns {Promise<Blob|null>} Obiekt Blob z obrazem PNG lub null, jeśli nic nie jest zaznaczone.
|
||||||
*/
|
*/
|
||||||
@@ -1372,8 +1449,6 @@ export class Canvas {
|
|||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||||
|
|
||||||
// 1. Oblicz bounding box dla wszystkich zaznaczonych i obróconych warstw
|
|
||||||
this.selectedLayers.forEach(layer => {
|
this.selectedLayers.forEach(layer => {
|
||||||
const centerX = layer.x + layer.width / 2;
|
const centerX = layer.x + layer.width / 2;
|
||||||
const centerY = layer.y + layer.height / 2;
|
const centerY = layer.y + layer.height / 2;
|
||||||
@@ -1385,10 +1460,10 @@ export class Canvas {
|
|||||||
const halfH = layer.height / 2;
|
const halfH = layer.height / 2;
|
||||||
|
|
||||||
const corners = [
|
const corners = [
|
||||||
{ x: -halfW, y: -halfH },
|
{x: -halfW, y: -halfH},
|
||||||
{ x: halfW, y: -halfH },
|
{x: halfW, y: -halfH},
|
||||||
{ x: halfW, y: halfH },
|
{x: halfW, y: halfH},
|
||||||
{ x: -halfW, y: halfH }
|
{x: -halfW, y: halfH}
|
||||||
];
|
];
|
||||||
|
|
||||||
corners.forEach(p => {
|
corners.forEach(p => {
|
||||||
@@ -1409,15 +1484,11 @@ export class Canvas {
|
|||||||
resolve(null);
|
resolve(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Stwórz tymczasowe płótno o wymiarach bounding boxa
|
|
||||||
const tempCanvas = document.createElement('canvas');
|
const tempCanvas = document.createElement('canvas');
|
||||||
tempCanvas.width = newWidth;
|
tempCanvas.width = newWidth;
|
||||||
tempCanvas.height = newHeight;
|
tempCanvas.height = newHeight;
|
||||||
const tempCtx = tempCanvas.getContext('2d');
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
|
||||||
// 3. Narysuj zaznaczone warstwy na nowym płótnie
|
|
||||||
// Przesuwamy cały układ współrzędnych, aby lewy górny róg bounding boxa był w (0,0)
|
|
||||||
tempCtx.translate(-minX, -minY);
|
tempCtx.translate(-minX, -minY);
|
||||||
|
|
||||||
const sortedSelection = [...this.selectedLayers].sort((a, b) => a.zIndex - b.zIndex);
|
const sortedSelection = [...this.selectedLayers].sort((a, b) => a.zIndex - b.zIndex);
|
||||||
@@ -1440,8 +1511,6 @@ export class Canvas {
|
|||||||
);
|
);
|
||||||
tempCtx.restore();
|
tempCtx.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. Konwertuj płótno na Blob
|
|
||||||
tempCanvas.toBlob((blob) => {
|
tempCanvas.toBlob((blob) => {
|
||||||
resolve(blob);
|
resolve(blob);
|
||||||
}, 'image/png');
|
}, 'image/png');
|
||||||
|
|||||||
Reference in New Issue
Block a user