Paste Image

This commit is contained in:
Dariusz L
2025-06-20 21:38:19 +02:00
parent bea71e19e2
commit beb89ed612

View File

@@ -168,8 +168,8 @@ async function createCanvasWidget(node, widget, app) {
img.onload = async () => { img.onload = async () => {
const scale = Math.min( const scale = Math.min(
canvas.width / img.width * 0.8, canvas.width / img.width,
canvas.height / img.height * 0.8 canvas.height / img.height
); );
const layer = { const layer = {
@@ -213,6 +213,72 @@ async function createCanvasWidget(node, widget, app) {
} }
} }
}), }),
$el("button.painter-button.primary", {
textContent: "Paste Image",
onclick: async () => {
try {
// Sprawdzenie, czy przeglądarka obsługuje API schowka
if (!navigator.clipboard || !navigator.clipboard.read) {
alert("Your browser does not support pasting from the clipboard.");
return;
}
// Poproś o dostęp do schowka i odczytaj jego zawartość
const clipboardItems = await navigator.clipboard.read();
let imageFound = false;
for (const item of clipboardItems) {
// Szukaj typu danych, który jest obrazem
const imageType = item.types.find(type => type.startsWith('image/'));
if (imageType) {
// Pobierz dane obrazu jako Blob
const blob = await item.getType(imageType);
// Ta część jest niemal identyczna jak w "Add Image"
const img = new Image();
img.onload = () => {
// Skaluj obraz, aby pasował do canvasu, zachowując proporcje
const scale = Math.min(
canvas.width / img.width,
canvas.height / img.height
);
const layer = {
image: img,
x: (canvas.width - img.width * scale) / 2,
y: (canvas.height - img.height * scale) / 2,
width: img.width * scale,
height: img.height * scale,
rotation: 0,
zIndex: canvas.layers.length
};
canvas.layers.push(layer);
canvas.updateSelection([layer]); // Zaznacz nową warstwę
canvas.render();
// Zwolnij zasób URL po załadowaniu obrazu
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(blob);
imageFound = true;
break; // Znaleziono obraz, przerwij pętlę
}
}
if (!imageFound) {
alert("No image found in the clipboard.");
}
} catch (err) {
console.error("Failed to paste image:", err);
alert("Could not paste image. Please ensure you have granted clipboard permissions or that there is an image in the clipboard.");
}
}
}),
$el("button.painter-button", { $el("button.painter-button", {
textContent: "Canvas Size", textContent: "Canvas Size",
onclick: () => { onclick: () => {
@@ -351,42 +417,32 @@ async function createCanvasWidget(node, widget, app) {
} }
}), }),
$el("button.painter-button.requires-selection", { $el("button.painter-button.requires-selection.matting-button", {
textContent: "Matting", textContent: "Matting",
onclick: async () => { onclick: async () => {
try {
if (!canvas.selectedLayer) {
throw new Error("Please select an image first");
}
const statusIndicator = MattingStatusIndicator.getInstance(controlPanel.querySelector('.controls')); const statusIndicator = MattingStatusIndicator.getInstance(controlPanel.querySelector('.controls'));
const updateStatus = (event) => {
const {status} = event.detail;
statusIndicator.setStatus(status);
};
api.addEventListener("matting_status", updateStatus);
try { try {
if (canvas.selectedLayers.length !== 1) {
throw new Error("Please select exactly one image layer for matting.");
}
const imageData = await canvas.getLayerImageData(canvas.selectedLayer); // Ustaw status na 'przetwarzanie' (żółty)
console.log("Sending image to server..."); statusIndicator.setStatus('processing');
const selectedLayer = canvas.selectedLayers[0];
const imageData = await canvas.getLayerImageData(selectedLayer);
console.log("Sending image to server for matting...");
const response = await fetch("/matting", { const response = await fetch("/matting", {
method: "POST", method: "POST",
headers: { headers: {"Content-Type": "application/json"},
"Content-Type": "application/json", body: JSON.stringify({image: imageData})
},
body: JSON.stringify({
image: imageData,
threshold: 0.5,
refinement: 1
})
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`Server error: ${response.status}`); throw new Error(`Server error: ${response.status} - ${response.statusText}`);
} }
const result = await response.json(); const result = await response.json();
@@ -394,64 +450,64 @@ async function createCanvasWidget(node, widget, app) {
const mattedImage = new Image(); const mattedImage = new Image();
mattedImage.onload = async () => { mattedImage.onload = async () => {
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = canvas.selectedLayer.width;
tempCanvas.height = canvas.selectedLayer.height;
tempCtx.drawImage(
mattedImage,
0, 0,
tempCanvas.width, tempCanvas.height
);
const newImage = new Image(); const newImage = new Image();
newImage.onload = async () => { newImage.onload = async () => {
const newLayer = { const newLayer = {
image: newImage, image: newImage,
x: canvas.selectedLayer.x, x: selectedLayer.x,
y: canvas.selectedLayer.y, y: selectedLayer.y,
width: canvas.selectedLayer.width, width: selectedLayer.width,
height: canvas.selectedLayer.height, height: selectedLayer.height,
rotation: canvas.selectedLayer.rotation, rotation: selectedLayer.rotation,
zIndex: canvas.layers.length + 1 zIndex: canvas.layers.length + 1
}; };
canvas.layers.push(newLayer); canvas.layers.push(newLayer);
canvas.selectedLayer = newLayer; canvas.updateSelection([newLayer]);
canvas.render(); canvas.render();
await canvas.saveToServer(widget.value); await canvas.saveToServer(widget.value);
app.graph.runStep(); app.graph.runStep();
// Ustaw status na 'ukończono' (zielony)
statusIndicator.setStatus('completed');
}; };
newImage.src = tempCanvas.toDataURL('image/png'); // Tworzymy obraz z przezroczystością z serwera
newImage.src = result.matted_image;
};
mattedImage.onerror = () => {
throw new Error("Failed to load the matted image from server response.");
}; };
mattedImage.src = result.matted_image; mattedImage.src = result.matted_image;
console.log("Matting result applied successfully");
} finally {
api.removeEventListener("matting_status", updateStatus);
}
} catch (error) { } catch (error) {
console.error("Matting error:", error); console.error("Matting error:", error);
alert(`Error during matting process: ${error.message}`); alert(`Error during matting process: ${error.message}`);
// Ustaw status na 'błąd' (czerwony)
statusIndicator.setStatus('error');
} }
} }
}) })
]) ])
]); ]);
const updateButtonStates = () => { const updateButtonStates = () => {
const hasSelection = canvas.selectedLayers.length > 0; const selectionCount = canvas.selectedLayers.length;
const buttonsToToggle = controlPanel.querySelectorAll('.requires-selection'); const hasSelection = selectionCount > 0;
buttonsToToggle.forEach(btn => {
// Ogólne przyciski wymagające przynajmniej jednego zaznaczenia
controlPanel.querySelectorAll('.requires-selection').forEach(btn => {
btn.disabled = !hasSelection; btn.disabled = !hasSelection;
}); });
// Specjalna logika dla przycisku "Matting", który wymaga DOKŁADNIE jednego zaznaczenia
const mattingBtn = controlPanel.querySelector('.matting-button');
if (mattingBtn) {
mattingBtn.disabled = selectionCount !== 1;
}
}; };
canvas.onSelectionChange = updateButtonStates; canvas.onSelectionChange = updateButtonStates;
@@ -519,10 +575,9 @@ async function createCanvasWidget(node, widget, app) {
const img = new Image(); const img = new Image();
img.onload = async () => { img.onload = async () => {
// Logika dodawania obrazu jest taka sama jak w przycisku "Add Image"
const scale = Math.min( const scale = Math.min(
canvas.width / img.width * 0.8, canvas.width / img.width,
canvas.height / img.height * 0.8 canvas.height / img.height
); );
const layer = { const layer = {
@@ -608,7 +663,6 @@ async function createCanvasWidget(node, widget, app) {
}; };
} }
class MattingStatusIndicator { class MattingStatusIndicator {
static instance = null; static instance = null;
@@ -616,32 +670,46 @@ class MattingStatusIndicator {
if (!MattingStatusIndicator.instance) { if (!MattingStatusIndicator.instance) {
MattingStatusIndicator.instance = new MattingStatusIndicator(container); MattingStatusIndicator.instance = new MattingStatusIndicator(container);
} }
if (container && !container.contains(MattingStatusIndicator.instance.indicator)) {
container.appendChild(MattingStatusIndicator.instance.indicator);
}
return MattingStatusIndicator.instance; return MattingStatusIndicator.instance;
} }
constructor(container) { constructor(container) {
// Lista możliwych statusów, aby łatwiej nimi zarządzać
this.statuses = ['processing', 'completed', 'error'];
this.indicator = document.createElement('div'); this.indicator = document.createElement('div');
// Ustawiamy bazową klasę, która będzie miała domyślny szary kolor
this.indicator.className = 'matting-indicator';
// Usunięto 'background-color' z stylów inline
this.indicator.style.cssText = ` this.indicator.style.cssText = `
width: 10px; width: 10px;
height: 10px; height: 10px;
border-radius: 50%; border-radius: 50%;
background-color: #808080;
margin-left: 10px; margin-left: 10px;
display: inline-block; display: inline-block;
transition: background-color 0.3s; transition: background-color 0.3s ease;
`; `;
const style = document.createElement('style'); const style = document.createElement('style');
style.textContent = ` style.textContent = `
.processing { /* Styl dla domyślnego stanu (szary) */
background-color: #2196F3; .matting-indicator {
background-color: #808080;
}
/* Style dla konkretnych statusów, które nadpiszą domyślny */
.matting-indicator.processing {
background-color: #FFC107; /* Żółty */
animation: blink 1s infinite; animation: blink 1s infinite;
} }
.completed { .matting-indicator.completed {
background-color: #4CAF50; background-color: #4CAF50; /* Zielony */
} }
.error { .matting-indicator.error {
background-color: #f44336; background-color: #f44336; /* Czerwony */
} }
@keyframes blink { @keyframes blink {
0% { opacity: 1; } 0% { opacity: 1; }
@@ -651,18 +719,26 @@ class MattingStatusIndicator {
`; `;
document.head.appendChild(style); document.head.appendChild(style);
if (container) {
container.appendChild(this.indicator); container.appendChild(this.indicator);
} }
}
setStatus(status) { setStatus(status) {
this.indicator.className = ''; // 1. Usuń wszystkie poprzednie klasy statusu, pozostawiając klasę bazową
if (status) { this.indicator.classList.remove(...this.statuses);
// 2. Dodaj nową klasę statusu, jeśli została podana
if (status && this.statuses.includes(status)) {
this.indicator.classList.add(status); this.indicator.classList.add(status);
} }
if (status === 'completed') {
// 3. Usuń statusy końcowe (sukces/błąd) po 3 sekundach,
// aby wskaźnik wrócił do domyślnego szarego koloru.
if (status === 'completed' || status === 'error') {
setTimeout(() => { setTimeout(() => {
this.indicator.classList.remove('completed'); this.indicator.classList.remove(status);
}, 2000); }, 3000);
} }
} }
} }