swq2f / script.js
ssboost's picture
Update script.js
fb8140b verified
/**
* ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ ๋ฐ ํŽธ์ง‘ ๊ธฐ๋Šฅ์„ ์œ„ํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ
*/
// ์š”์†Œ ๊ฐ€์ ธ์˜ค๊ธฐ
const backgroundInput = document.getElementById('background-input');
const overlayInput = document.getElementById('overlay-input');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const scaleSlider = document.getElementById('scale-slider');
const scaleValue = document.getElementById('scale-value');
const rotationSlider = document.getElementById('rotation-slider');
const rotationValue = document.getElementById('rotation-value');
// ํ•„ํ„ฐ ๊ด€๋ จ ์š”์†Œ
const temperatureSlider = document.getElementById('temperature-slider');
const temperatureValue = document.getElementById('temperature-value');
const brightnessSlider = document.getElementById('brightness-slider');
const brightnessValue = document.getElementById('brightness-value');
const contrastSlider = document.getElementById('contrast-slider');
const contrastValue = document.getElementById('contrast-value');
const saturationSlider = document.getElementById('saturation-slider');
const saturationValue = document.getElementById('saturation-value');
const resetFilterBtn = document.getElementById('reset-filter-btn');
const generateBtn = document.getElementById('generate-btn');
const resetAllBtn = document.getElementById('reset-all-btn');
const downloadBtn = document.getElementById('download-btn');
const deleteLayerBtn = document.getElementById('delete-layer-btn'); // ์ถ”๊ฐ€
const previewContainer = document.getElementById('preview-container');
const previewImg = document.getElementById('preview-img');
const layersList = document.getElementById('layers-list');
// ์บ”๋ฒ„์Šค ๊ธฐ๋ณธ ํฌ๊ธฐ
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 600;
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
// ์ „์—ญ ๋ณ€์ˆ˜
let backgroundImage = null;
let originalFormat = 'png'; // ๊ธฐ๋ณธ ํฌ๋งท์„ PNG๋กœ ์„ค์ •
let canvasAdjustedHeight = CANVAS_HEIGHT; // ์ด๋ฏธ์ง€ ๋น„์œจ์— ๋งž๊ฒŒ ์กฐ์ •๋œ ์บ”๋ฒ„์Šค ๋†’์ด
// ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ ์†์„ฑ
let bgDrawProps = { x: 0, y: 0, width: CANVAS_WIDTH, height: CANVAS_HEIGHT };
// ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์™€ ๊ทธ ์†์„ฑ์„ ์ €์žฅํ•  ๋ฐฐ์—ด
let overlays = [];
// ํ˜„์žฌ ์กฐ์ž‘ ์ค‘์ธ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ธ๋ฑ์Šค (-1์ด๋ฉด ์„ ํƒ๋˜์ง€ ์•Š์Œ)
let activeOverlayIndex = -1;
// ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์กฐ์ž‘ ๊ด€๋ จ ์ƒํƒœ
let isDragging = false;
let isRotating = false;
let isResizing = false;
let activeHandle = null;
// ์ด์ „ ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ
let lastX = 0;
let lastY = 0;
// ๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ์ž‘ ์‹œ ํ•ธ๋“ค๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ (๋น„๋ก€ ์Šค์ผ€์ผ๋ง์— ์‚ฌ์šฉ)
let resizeStartDistance = 0;
// ํ•ธ๋“ค ์„ค์ •
const handleSize = 15;
const rotateHandleDistance = 30;
// ํด๋ฆญ ๊ฐ์ง€ ์‹œ ์—ฌ์œ  ๊ณต๊ฐ„
const clickPadding = 5;
// ์บ”๋ฒ„์Šค ์ดˆ๊ธฐํ™” (ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ)
function initCanvas() {
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
updateLayersList();
}
function cropTransparentPixels(imageElement, alphaThreshold = 0) {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = imageElement.naturalWidth || imageElement.width;
tempCanvas.height = imageElement.naturalHeight || imageElement.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(imageElement, 0, 0);
let imageData;
try {
imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
} catch (e) {
console.error("Error getting ImageData (possibly due to CORS):", e);
// CORS ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ, ๋ณด์•ˆ ์ œ์•ฝ ์—†์ด ์ด๋ฏธ์ง€ ์ „์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์ฒด ๋กœ์ง (๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ํšจ๊ณผ๋Š” ์—†์„ ์ˆ˜ ์žˆ์Œ)
const fallbackCanvas = document.createElement('canvas');
fallbackCanvas.width = tempCanvas.width;
fallbackCanvas.height = tempCanvas.height;
const fallbackCtx = fallbackCanvas.getContext('2d');
fallbackCtx.drawImage(imageElement, 0, 0);
return fallbackCanvas;
}
const data = imageData.data;
let minX = tempCanvas.width, minY = tempCanvas.height, maxX = 0, maxY = 0;
let foundContent = false;
for (let y = 0; y < tempCanvas.height; y++) {
for (let x = 0; x < tempCanvas.width; x++) {
const alpha = data[(y * tempCanvas.width + x) * 4 + 3];
if (alpha > alphaThreshold) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
foundContent = true;
}
}
}
if (!foundContent) {
// ๋‚ด์šฉ์ด ์—†๋Š” ๊ฒฝ์šฐ (์™„์ „ํžˆ ํˆฌ๋ช…ํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜)
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = imageElement.naturalWidth || 1;
emptyCanvas.height = imageElement.naturalHeight || 1;
return emptyCanvas;
}
const cropWidth = maxX - minX + 1;
const cropHeight = maxY - minY + 1;
const croppedCanvas = document.createElement('canvas');
croppedCanvas.width = cropWidth;
croppedCanvas.height = cropHeight;
const croppedCtx = croppedCanvas.getContext('2d');
// ์ž˜๋ผ๋‚ธ ์˜์—ญ๋งŒ ์ƒˆ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
croppedCtx.drawImage(tempCanvas, minX, minY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
return croppedCanvas;
}
// ํŒŒ์ผ ๋˜๋Š” Blob ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๊ณ  ์บ”๋ฒ„์Šค์— ์ถ”๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜
function loadImage(fileOrBlob, type) {
if (!fileOrBlob) return;
let fileExtension = 'png'; // ๊ธฐ๋ณธ๊ฐ’
let isBlob = fileOrBlob instanceof Blob;
let fileName = 'unknown';
if (!isBlob && fileOrBlob.name) {
fileExtension = fileOrBlob.name.split('.').pop().toLowerCase();
fileName = fileOrBlob.name;
} else if (isBlob && fileOrBlob.type) {
fileExtension = fileOrBlob.type.split('/')[1] ? fileOrBlob.type.split('/')[1].toLowerCase() : 'png';
fileName = 'processed_image.' + fileExtension; // Blob์˜ ๊ฒฝ์šฐ ํŒŒ์ผ ์ด๋ฆ„ ์ž„์‹œ ์ง€์ •
}
if (type === 'background' && !isBlob && ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExtension)) {
originalFormat = fileExtension;
}
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
if (type === 'background') {
backgroundImage = img;
const aspectRatio = img.width / img.height;
bgDrawProps.width = CANVAS_WIDTH;
bgDrawProps.height = CANVAS_WIDTH / aspectRatio;
bgDrawProps.x = 0;
bgDrawProps.y = 0;
canvasAdjustedHeight = bgDrawProps.height;
canvas.height = canvasAdjustedHeight;
canvas.style.height = 'auto'; // ์บ”๋ฒ„์Šค wrapper๊ฐ€ ์•„๋‹Œ canvas ์ž์ฒด์˜ style
} else if (type === 'overlay') {
let imageToUse = img;
// PNG ๋˜๋Š” ํˆฌ๋ช…๋„๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€ ํ˜•์‹์˜ ๊ฒฝ์šฐ์—๋งŒ ํˆฌ๋ช… ํ”ฝ์…€ ์ž๋ฅด๊ธฐ ์‹œ๋„
// ์˜ˆ์‹œ ์ด๋ฏธ์ง€๋Š” ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๊ฐ€ ์™„๋ฃŒ๋œ ์ด๋ฏธ์ง€๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ฑฐ๋‚˜,
// ์—ฌ๊ธฐ์—์„œ cropTransparentPixels๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
// ์‹ค์ œ ๋ณต์žกํ•œ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ๋กœ์ง์€ ์ด ๋ถ€๋ถ„์ด ์•„๋‹Œ ๋ณ„๋„์˜ ํ•จ์ˆ˜์—์„œ ๊ตฌํ˜„๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
if (fileExtension === 'png' || fileExtension === 'webp' || fileExtension === 'gif' || (isBlob && fileOrBlob.type === 'image/png')) {
try {
// cropTransparentPixels๋Š” ํˆฌ๋ช… ์˜์—ญ์„ ์ž๋ฅด๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.
// ๋งŒ์•ฝ ์˜ˆ์‹œ ์ด๋ฏธ์ง€๊ฐ€ ์ด๋ฏธ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€๋ผ๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// ์ผ๋ฐ˜ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ์ œ๊ฑฐํ•˜๋ ค๋ฉด ๋‹ค๋ฅธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
const croppedImageCanvas = cropTransparentPixels(img);
if (croppedImageCanvas.width > 0 && croppedImageCanvas.height > 0) {
imageToUse = croppedImageCanvas;
} else {
// ์ž˜๋ผ๋‚ผ ์˜์—ญ์ด ์—†์œผ๋ฉด ์›๋ณธ ์‚ฌ์šฉ ๋˜๋Š” ๋นˆ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
console.warn("No non-transparent pixels found, using original or empty.");
// imageToUse = img; // ์›๋ณธ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = img.naturalWidth || 1;
emptyCanvas.height = img.naturalHeight || 1;
imageToUse = emptyCanvas; // ๋˜๋Š” ๋นˆ ์บ”๋ฒ„์Šค ์‚ฌ์šฉ
}
} catch (e) {
console.error("Error during image cropping/processing, using original:", e);
imageToUse = img; // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์›๋ณธ ์‚ฌ์šฉ
}
}
const newOverlay = {
image: imageToUse,
// ์บ”๋ฒ„์Šค ์ค‘์•™์— ๋ฐฐ์น˜
x: CANVAS_WIDTH / 2,
y: canvasAdjustedHeight / 2,
scale: 1,
rotation: 0,
filters: { ...ImageFilters.DEFAULT_FILTERS }
};
// ์ด๋ฏธ์ง€๊ฐ€ ์บ”๋ฒ„์Šค๋ณด๋‹ค ํฌ๋ฉด ์ดˆ๊ธฐ ์Šค์ผ€์ผ ์กฐ์ •
const initialScaleRatio = Math.min(CANVAS_WIDTH / newOverlay.image.width, canvasAdjustedHeight / newOverlay.image.height);
if (initialScaleRatio < 1) {
newOverlay.scale = initialScaleRatio * 0.9; // ์•ฝ๊ฐ„ ๋” ์ž‘๊ฒŒ ์‹œ์ž‘
}
overlays.push(newOverlay);
activeOverlayIndex = overlays.length - 1; // ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ๋ ˆ์ด์–ด๋ฅผ ํ™œ์„ฑํ™”
updateControlPanel();
updateLayersList();
}
drawCanvas();
};
img.onerror = function() {
console.error("Error loading image from reader result for:", fileName);
alert("์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ํŒŒ์ผ์ด ์œ ํšจํ•œ์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.");
};
img.src = event.target.result;
};
reader.readAsDataURL(fileOrBlob); // File ๋˜๋Š” Blob์—์„œ Data URL ์ฝ๊ธฐ
}
function updateControlPanel() {
const isActive = activeOverlayIndex >= 0;
const activeOverlay = isActive ? overlays[activeOverlayIndex] : null;
// ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์„ ํƒ๋˜์ง€ ์•Š์•˜์„ ๋•Œ ์ปจํŠธ๋กค ๋น„ํ™œ์„ฑํ™”
scaleSlider.disabled = !isActive;
rotationSlider.disabled = !isActive;
temperatureSlider.disabled = !isActive;
brightnessSlider.disabled = !isActive;
contrastSlider.disabled = !isActive;
saturationSlider.disabled = !isActive;
resetFilterBtn.disabled = !isActive;
deleteLayerBtn.disabled = !isActive; // ์‚ญ์ œ ๋ฒ„ํŠผ๋„ ๋น„ํ™œ์„ฑํ™”
if (isActive) {
scaleSlider.value = Math.round(activeOverlay.scale * 100);
scaleValue.textContent = scaleSlider.value + '%';
rotationSlider.value = Math.round(activeOverlay.rotation);
rotationValue.textContent = rotationSlider.value + 'ยฐ';
temperatureSlider.value = activeOverlay.filters.temperature;
temperatureValue.textContent = activeOverlay.filters.temperature;
brightnessSlider.value = activeOverlay.filters.brightness;
brightnessValue.textContent = activeOverlay.filters.brightness + '%';
contrastSlider.value = activeOverlay.filters.contrast;
contrastValue.textContent = activeOverlay.filters.contrast + '%';
saturationSlider.value = activeOverlay.filters.saturation;
saturationValue.textContent = activeOverlay.filters.saturation + '%';
} else {
// ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์„ ํƒ๋˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํ‘œ์‹œ
scaleSlider.value = 100;
scaleValue.textContent = '100%';
rotationSlider.value = 0;
rotationValue.textContent = '0ยฐ';
temperatureSlider.value = 0;
temperatureValue.textContent = '0';
brightnessSlider.value = 100;
brightnessValue.textContent = '100%';
contrastSlider.value = 100;
contrastValue.textContent = '100%';
saturationSlider.value = 100;
saturationValue.textContent = '100%';
}
// ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ์žˆ์„ ๋•Œ๋งŒ ์ƒ์„ฑ/๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
generateBtn.disabled = overlays.length === 0;
downloadBtn.disabled = overlays.length === 0;
// ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์—†์œผ๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ ์ˆจ๊น€
if (overlays.length === 0) {
previewContainer.style.display = 'none';
}
}
function updateLayersList() {
if (!layersList) return; // layersList ์š”์†Œ๊ฐ€ ์—†์œผ๋ฉด ํ•จ์ˆ˜ ์ข…๋ฃŒ
layersList.innerHTML = ''; // ๋ชฉ๋ก ์ดˆ๊ธฐํ™”
// ์‚ญ์ œ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™” ์„ค์ •
if (deleteLayerBtn) {
deleteLayerBtn.disabled = activeOverlayIndex < 0;
}
if (overlays.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.style.padding = '8px';
emptyMsg.style.textAlign = 'center';
emptyMsg.style.color = '#777';
emptyMsg.textContent = '๋ ˆ์ด์–ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค';
layersList.appendChild(emptyMsg);
return;
}
// ๋ ˆ์ด์–ด ๋ชฉ๋ก์„ ์—ญ์ˆœ์œผ๋กœ ํ‘œ์‹œ (๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด๊ฐ€ ๋ชฉ๋ก์˜ ์•„๋ž˜์— ์˜ค๋„๋ก)
for (let i = overlays.length - 1; i >= 0; i--) {
const layerItem = document.createElement('div');
layerItem.className = 'layer-item';
if (i === activeOverlayIndex) {
layerItem.classList.add('active'); // ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ‘œ์‹œ
}
const layerName = document.createElement('span');
layerName.className = 'layer-name';
layerName.textContent = `๋ ˆ์ด์–ด ${i + 1}`; // ๋ ˆ์ด์–ด ๋ฒˆํ˜ธ ํ‘œ์‹œ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
const layerControls = document.createElement('div');
layerControls.className = 'layer-controls';
// ์œ„๋กœ ์ด๋™ ๋ฒ„ํŠผ (๋งจ ์œ„ ๋ ˆ์ด์–ด ์ œ์™ธ)
if (i < overlays.length - 1) {
const upButton = document.createElement('button');
upButton.className = 'layer-button';
upButton.textContent = 'โ†‘';
upButton.title = '์œ„๋กœ ์ด๋™';
upButton.addEventListener('click', (e) => { e.stopPropagation(); moveLayerUp(i); }); // ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง ๋ฐฉ์ง€
layerControls.appendChild(upButton);
}
// ์•„๋ž˜๋กœ ์ด๋™ ๋ฒ„ํŠผ (๋งจ ์•„๋ž˜ ๋ ˆ์ด์–ด ์ œ์™ธ)
if (i > 0) {
const downButton = document.createElement('button');
downButton.className = 'layer-button';
downButton.textContent = 'โ†“';
downButton.title = '์•„๋ž˜๋กœ ์ด๋™';
downButton.addEventListener('click', (e) => { e.stopPropagation(); moveLayerDown(i); }); // ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง ๋ฐฉ์ง€
layerControls.appendChild(downButton);
}
// ๋ ˆ์ด์–ด ์‚ญ์ œ ๋ฒ„ํŠผ ์ถ”๊ฐ€
const deleteButton = document.createElement('button');
deleteButton.className = 'delete-button';
deleteButton.textContent = 'ร—';
deleteButton.title = '๋ ˆ์ด์–ด ์‚ญ์ œ (๋‹จ์ถ•ํ‚ค: Del)';
deleteButton.addEventListener('click', (e) => {
e.stopPropagation(); // ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง ๋ฐฉ์ง€
// ํ˜„์žฌ ๋ ˆ์ด์–ด ์ธ๋ฑ์Šค๋ฅผ ์„ค์ •ํ•˜๊ณ  ์‚ญ์ œ
activeOverlayIndex = i;
deleteSelectedLayer();
});
layerControls.appendChild(deleteButton);
// ๋ ˆ์ด์–ด ํ•ญ๋ชฉ ํด๋ฆญ ์‹œ ํ•ด๋‹น ๋ ˆ์ด์–ด ํ™œ์„ฑํ™”
layerItem.addEventListener('click', function() {
activeOverlayIndex = i;
updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์—…๋ฐ์ดํŠธ
updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์‹œ๊ฐ์  ์—…๋ฐ์ดํŠธ
drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ (ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ธ๋“ค ํ‘œ์‹œ)
});
layerItem.appendChild(layerName);
layerItem.appendChild(layerControls);
layersList.appendChild(layerItem);
}
}
// ๋ ˆ์ด์–ด ์œ„๋กœ ์ด๋™
function moveLayerUp(index) {
if (index < overlays.length - 1) {
// ํ˜„์žฌ ๋ ˆ์ด์–ด์™€ ๋ฐ”๋กœ ์œ„ ๋ ˆ์ด์–ด์˜ ์œ„์น˜๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
const temp = overlays[index];
overlays[index] = overlays[index + 1];
overlays[index + 1] = temp;
// ํ™œ์„ฑ ๋ ˆ์ด์–ด์˜ ์ธ๋ฑ์Šค๋„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
if (activeOverlayIndex === index) activeOverlayIndex = index + 1;
else if (activeOverlayIndex === index + 1) activeOverlayIndex = index;
updateLayersList(); // ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
}
}
// ๋ ˆ์ด์–ด ์•„๋ž˜๋กœ ์ด๋™
function moveLayerDown(index) {
if (index > 0) {
// ํ˜„์žฌ ๋ ˆ์ด์–ด์™€ ๋ฐ”๋กœ ์•„๋ž˜ ๋ ˆ์ด์–ด์˜ ์œ„์น˜๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
const temp = overlays[index];
overlays[index] = overlays[index - 1];
overlays[index - 1] = temp;
// ํ™œ์„ฑ ๋ ˆ์ด์–ด์˜ ์ธ๋ฑ์Šค๋„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
if (activeOverlayIndex === index) activeOverlayIndex = index - 1;
else if (activeOverlayIndex === index - 1) activeOverlayIndex = index;
updateLayersList(); // ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
}
}
function drawCanvas() {
// ์บ”๋ฒ„์Šค ์ „์ฒด ์ง€์šฐ๊ธฐ
ctx.clearRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
// ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ (์žˆ๋‹ค๋ฉด)
if (backgroundImage) {
ctx.drawImage(backgroundImage, 0, 0, backgroundImage.width, backgroundImage.height, bgDrawProps.x, bgDrawProps.y, bgDrawProps.width, bgDrawProps.height);
} else {
// ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ์ฑ„์šฐ๊ธฐ
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
}
// ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๋“ค ๊ทธ๋ฆฌ๊ธฐ (๋ ˆ์ด์–ด ์ˆœ์„œ๋Œ€๋กœ)
overlays.forEach((overlay, index) => {
// ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.
if (!overlay.image || overlay.image.width === 0 || overlay.image.height === 0) return;
// ํ•„ํ„ฐ ์ ์šฉ๋œ ์ด๋ฏธ์ง€ ์บ”๋ฒ„์Šค ์ƒ์„ฑ (filter.js์˜ ํ•จ์ˆ˜ ์‚ฌ์šฉ)
const filteredCanvas = ImageFilters.createFilteredCanvas(overlay.image, overlay.filters);
ctx.save(); // ํ˜„์žฌ ์บ”๋ฒ„์Šค ์ƒํƒœ ์ €์žฅ
// ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ ๋ณ€ํ™˜ ์ ์šฉ
ctx.translate(overlay.x, overlay.y); // ์œ„์น˜ ์ด๋™
ctx.rotate(overlay.rotation * Math.PI / 180); // ํšŒ์ „ (๋ผ๋””์•ˆ์œผ๋กœ ๋ณ€ํ™˜)
ctx.scale(overlay.scale, overlay.scale); // ํฌ๊ธฐ ์กฐ์ •
// ์ด๋ฏธ์ง€๋ฅผ ์ค‘์‹ฌ ๊ธฐ์ค€์œผ๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. (translate์—์„œ ์ด๋™ํ–ˆ์œผ๋ฏ€๋กœ -width/2, -height/2)
ctx.drawImage(filteredCanvas, -overlay.image.width / 2, -overlay.image.height / 2);
ctx.restore(); // ์ด์ „ ์บ”๋ฒ„์Šค ์ƒํƒœ ๋ณต์› (๋ณ€ํ™˜ ์ƒํƒœ ์ดˆ๊ธฐํ™”)
// ํ˜„์žฌ ํ™œ์„ฑ ๋ ˆ์ด์–ด์—๋งŒ ๋ณ€ํ™˜ ํ•ธ๋“ค ๊ทธ๋ฆฌ๊ธฐ
if (index === activeOverlayIndex) {
drawTransformHandles(overlay);
}
});
}
// ๋ณ€ํ™˜ ํ•ธ๋“ค ๊ทธ๋ฆฌ๊ธฐ
function drawTransformHandles(activeOverlay) {
// ํ™œ์„ฑ ์˜ค๋ฒ„๋ ˆ์ด๋‚˜ ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ทธ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
if (!activeOverlay || !activeOverlay.image || activeOverlay.image.width === 0 || activeOverlay.image.height === 0) return;
const currentWidth = activeOverlay.image.width * activeOverlay.scale;
const currentHeight = activeOverlay.image.height * activeOverlay.scale;
const halfWidth = currentWidth / 2;
const halfHeight = currentHeight / 2;
const angle = activeOverlay.rotation * Math.PI / 180; // ํ˜„์žฌ ํšŒ์ „ ๊ฐ๋„ (๋ผ๋””์•ˆ)
// ์ฝ”๋„ˆ ํ•ธ๋“ค ์œ„์น˜ (ํšŒ์ „๋˜์ง€ ์•Š์€ ์ƒํƒœ ๊ธฐ์ค€)
const handles = {
'tl': { x: -halfWidth, y: -halfHeight, cursor: 'nwse-resize' }, // Top-Left
'tr': { x: halfWidth, y: -halfHeight, cursor: 'nesw-resize' }, // Top-Right
'br': { x: halfWidth, y: halfHeight, cursor: 'nwse-resize' }, // Bottom-Right
'bl': { x: -halfWidth, y: halfHeight, cursor: 'nesw-resize' } // Bottom-Left
};
// ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜ (์ด๋ฏธ์ง€ ์ƒ๋‹จ ์ค‘์•™์—์„œ ํšŒ์ „ ๊ฑฐ๋ฆฌ๋งŒํผ ๋–จ์–ด์ง„ ์œ„์น˜)
const rotateHandlePos = { x: 0, y: -halfHeight - rotateHandleDistance };
ctx.save(); // ํ˜„์žฌ ์บ”๋ฒ„์Šค ์ƒํƒœ ์ €์žฅ
// ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ ์œ„์น˜๋กœ ์ด๋™ ํ›„ ํšŒ์ „
ctx.translate(activeOverlay.x, activeOverlay.y);
ctx.rotate(angle);
// ์ฝ”๋„ˆ ํ•ธ๋“ค ๊ทธ๋ฆฌ๊ธฐ
const cornerHandleColor = '#e74c3c'; // ๋นจ๊ฐ„์ƒ‰
for (const [, handle] of Object.entries(handles)) {
ctx.beginPath();
// ํ•ธ๋“ค ์œ„์น˜์— ์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ (ํ•ธ๋“ค ํฌ๊ธฐ์˜ ์ ˆ๋ฐ˜๋งŒํผ ๋นผ์„œ ์ค‘์•™์— ์œ„์น˜)
ctx.rect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
ctx.fillStyle = cornerHandleColor;
ctx.fill();
}
// ํšŒ์ „ ํ•ธ๋“ค ์—ฐ๊ฒฐ ์„  ๊ทธ๋ฆฌ๊ธฐ
ctx.beginPath();
ctx.moveTo(0, -halfHeight); // ์ด๋ฏธ์ง€ ์ƒ๋‹จ ์ค‘์•™
ctx.lineTo(rotateHandlePos.x, rotateHandlePos.y); // ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜
ctx.strokeStyle = '#3498db'; // ํŒŒ๋ž€์ƒ‰
ctx.lineWidth = 2;
ctx.stroke();
// ํšŒ์ „ ํ•ธ๋“ค ์› ๊ทธ๋ฆฌ๊ธฐ
ctx.beginPath();
ctx.arc(rotateHandlePos.x, rotateHandlePos.y, handleSize / 2, 0, Math.PI * 2); // ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜์— ์› ๊ทธ๋ฆฌ๊ธฐ
ctx.fillStyle = '#3498db'; // ํŒŒ๋ž€์ƒ‰
ctx.fill();
ctx.restore(); // ์ด์ „ ์บ”๋ฒ„์Šค ์ƒํƒœ ๋ณต์›
}
// ์ฃผ์–ด์ง„ ์บ”๋ฒ„์Šค ์ขŒํ‘œ์—์„œ ์–ด๋–ค ํ•ธ๋“ค์ด ์žˆ๋Š”์ง€ ํ™•์ธ
function getHandleAtPosition(canvasX, canvasY, activeOverlay) {
// ํ™œ์„ฑ ์˜ค๋ฒ„๋ ˆ์ด๋‚˜ ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ํ•ธ๋“ค ์—†์Œ
if (!activeOverlay || !activeOverlay.image || activeOverlay.image.width === 0 || activeOverlay.image.height === 0) return null;
const currentWidth = activeOverlay.image.width * activeOverlay.scale;
const currentHeight = activeOverlay.image.height * activeOverlay.scale;
const halfWidth = currentWidth / 2;
const halfHeight = currentHeight / 2;
// ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ๋ฅผ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜๊ณ  ํšŒ์ „์„ ๋˜๋Œ๋ฆฝ๋‹ˆ๋‹ค.
const dx = canvasX - activeOverlay.x;
const dy = canvasY - activeOverlay.y;
const angle = -activeOverlay.rotation * Math.PI / 180; // ํšŒ์ „์„ ๋˜๋Œ๋ฆฌ๋Š” ๊ฐ๋„
const cos = Math.cos(angle);
const sin = Math.sin(angle);
// ํšŒ์ „๋œ ์ขŒํ‘œ
const rotatedX = dx * cos - dy * sin;
const rotatedY = dx * sin + dy * cos;
// ์ฝ”๋„ˆ ํ•ธ๋“ค ์œ„์น˜ (ํšŒ์ „๋˜์ง€ ์•Š์€ ์ƒํƒœ ๊ธฐ์ค€)
const handles = {
'tl': { x: -halfWidth, y: -halfHeight, cursor: 'nwse-resize' },
'tr': { x: halfWidth, y: -halfHeight, cursor: 'nesw-resize' },
'br': { x: halfWidth, y: halfHeight, cursor: 'nwse-resize' },
'bl': { x: -halfWidth, y: halfHeight, cursor: 'nesw-resize' }
};
// ๊ฐ ์ฝ”๋„ˆ ํ•ธ๋“ค ๊ทผ์ฒ˜์— ํด๋ฆญ/ํ„ฐ์น˜ํ–ˆ๋Š”์ง€ ํ™•์ธ
for (const [key, handle] of Object.entries(handles)) {
const dist = Math.sqrt(Math.pow(rotatedX - handle.x, 2) + Math.pow(rotatedY - handle.y, 2));
// ํด๋ฆญ ํŒจ๋”ฉ์„ ๋”ํ•˜์—ฌ ํด๋ฆญ/ํ„ฐ์น˜ ์˜์—ญ์„ ์•ฝ๊ฐ„ ๋„“๊ฒŒ ์žก์Šต๋‹ˆ๋‹ค.
if (dist <= handleSize / 2 + clickPadding) {
return { id: key, cursor: handle.cursor }; // ํ•ด๋‹น ํ•ธ๋“ค์˜ ID์™€ ์ปค์„œ ๋ชจ์–‘ ๋ฐ˜ํ™˜
}
}
// ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜ (ํšŒ์ „๋˜์ง€ ์•Š์€ ์ƒํƒœ ๊ธฐ์ค€)
const rotateHandlePos = { x: 0, y: -halfHeight - rotateHandleDistance };
const rotateDist = Math.sqrt(Math.pow(rotatedX - rotateHandlePos.x, 2) + Math.pow(rotatedY - rotateHandlePos.y, 2));
// ํšŒ์ „ ํ•ธ๋“ค ๊ทผ์ฒ˜์— ํด๋ฆญ/ํ„ฐ์น˜ํ–ˆ๋Š”์ง€ ํ™•์ธ
if (rotateDist <= handleSize / 2 + clickPadding) {
return { id: 'rotate', cursor: 'grab' }; // ํšŒ์ „ ํ•ธ๋“ค ID์™€ ์ปค์„œ ๋ชจ์–‘ ๋ฐ˜ํ™˜
}
return null; // ์–ด๋–ค ํ•ธ๋“ค๋„ ํด๋ฆญ/ํ„ฐ์น˜๋˜์ง€ ์•Š์Œ
}
// ์ฃผ์–ด์ง„ ์บ”๋ฒ„์Šค ์ขŒํ‘œ๊ฐ€ ํŠน์ • ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์˜์—ญ ์œ„์— ์žˆ๋Š”์ง€ ํ™•์ธ
function isOverOverlayImage(canvasX, canvasY, overlay) {
// ์˜ค๋ฒ„๋ ˆ์ด๋‚˜ ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด false ๋ฐ˜ํ™˜
if (!overlay || !overlay.image || overlay.image.width === 0 || overlay.image.height === 0) return false;
const currentWidth = overlay.image.width * overlay.scale;
const currentHeight = overlay.image.height * overlay.scale;
const halfWidth = currentWidth / 2;
const halfHeight = currentHeight / 2;
// ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ๋ฅผ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜๊ณ  ํšŒ์ „์„ ๋˜๋Œ๋ฆฝ๋‹ˆ๋‹ค.
const dx = canvasX - overlay.x;
const dy = canvasY - overlay.y;
const angle = -overlay.rotation * Math.PI / 180; // ํšŒ์ „์„ ๋˜๋Œ๋ฆฌ๋Š” ๊ฐ๋„
const cos = Math.cos(angle);
const sin = Math.sin(angle);
// ํšŒ์ „๋œ ์ขŒํ‘œ
const rotatedX = dx * cos - dy * sin;
const rotatedY = dx * sin + dy * cos;
// ํšŒ์ „๋œ ์ขŒํ‘œ๊ฐ€ ์ด๋ฏธ์ง€ ๊ฒฝ๊ณ„ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธ (ํด๋ฆญ ํŒจ๋”ฉ ์ ์šฉ)
return (rotatedX >= -halfWidth - clickPadding && rotatedX <= halfWidth + clickPadding &&
rotatedY >= -halfHeight - clickPadding && rotatedY <= halfHeight + clickPadding);
}
// ๋งˆ์šฐ์Šค ์ปค์„œ ๋ชจ์–‘ ์—…๋ฐ์ดํŠธ
function updateCursor(canvasX, canvasY) {
// ๋“œ๋ž˜๊ทธ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์—๋Š” ์ปค์„œ ๋ชจ์–‘์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
if (isDragging || isRotating || isResizing) return;
let cursor = 'default'; // ๊ธฐ๋ณธ ์ปค์„œ ๋ชจ์–‘
let handled = false; // ํ•ธ๋“ค์ด๋‚˜ ์ด๋ฏธ์ง€ ์œ„์— ์žˆ๋Š”์ง€ ์—ฌ๋ถ€
// ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ธ๋“ค ์œ„์— ์žˆ๋Š”์ง€ ๋จผ์ € ํ™•์ธ
if (activeOverlayIndex >= 0) {
const activeOverlay = overlays[activeOverlayIndex];
const handle = getHandleAtPosition(canvasX, canvasY, activeOverlay);
if (handle) {
cursor = handle.cursor; // ํ•ธ๋“ค์— ๋งž๋Š” ์ปค์„œ ๋ชจ์–‘ ์„ค์ •
handled = true;
}
}
// ํ•ธ๋“ค ์œ„์— ์žˆ์ง€ ์•Š๋‹ค๋ฉด, ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์œ„์— ์žˆ๋Š”์ง€ ํ™•์ธ (๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด๋ถ€ํ„ฐ)
if (!handled) {
for (let i = overlays.length - 1; i >= 0; i--) {
if (isOverOverlayImage(canvasX, canvasY, overlays[i])) {
cursor = 'move'; // ์ด๋™ ์ปค์„œ ์„ค์ •
handled = true;
break; // ํ•˜๋‚˜๋ผ๋„ ๋ฐœ๊ฒฌํ•˜๋ฉด ์ค‘์ง€
}
}
}
canvas.style.cursor = cursor; // ์บ”๋ฒ„์Šค ์ปค์„œ ๋ชจ์–‘ ์ ์šฉ
}
// ํ•ฉ์„ฑ๋œ ์ตœ์ข… ์ด๋ฏธ์ง€ ์ƒ์„ฑ
function generateMergedImage() {
// ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
if (overlays.length === 0) {
alert('ํ•ฉ์„ฑํ•  ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.');
return null;
}
// ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ฆด ์ƒˆ ์บ”๋ฒ„์Šค ์ƒ์„ฑ
const resultCanvas = document.createElement('canvas');
resultCanvas.width = CANVAS_WIDTH;
resultCanvas.height = canvasAdjustedHeight;
const resultCtx = resultCanvas.getContext('2d');
// ์บ”๋ฒ„์Šค ์ดˆ๊ธฐํ™” (๋ฐฐ๊ฒฝ ์ฑ„์šฐ๊ธฐ)
resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);
if (backgroundImage) {
resultCtx.drawImage(backgroundImage, 0, 0, backgroundImage.width, backgroundImage.height, bgDrawProps.x, bgDrawProps.y, bgDrawProps.width, bgDrawProps.height);
} else {
resultCtx.fillStyle = '#ffffff';
resultCtx.fillRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
}
// ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๋“ค์„ ์ˆœ์„œ๋Œ€๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
overlays.forEach(overlay => {
if (!overlay.image || overlay.image.width === 0 || overlay.image.height === 0) return;
// ํ•„ํ„ฐ ์ ์šฉ๋œ ์ด๋ฏธ์ง€ ์บ”๋ฒ„์Šค ์ƒ์„ฑ
const filteredCanvas = ImageFilters.createFilteredCanvas(overlay.image, overlay.filters);
resultCtx.save(); // ํ˜„์žฌ ์ƒํƒœ ์ €์žฅ
// ์˜ค๋ฒ„๋ ˆ์ด ์œ„์น˜, ํšŒ์ „, ์Šค์ผ€์ผ ์ ์šฉ
resultCtx.translate(overlay.x, overlay.y);
resultCtx.rotate(overlay.rotation * Math.PI / 180);
resultCtx.scale(overlay.scale, overlay.scale);
// ์ด๋ฏธ์ง€๋ฅผ ์ค‘์‹ฌ ๊ธฐ์ค€์œผ๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
resultCtx.drawImage(filteredCanvas, -overlay.image.width / 2, -overlay.image.height / 2);
resultCtx.restore(); // ์ƒํƒœ ๋ณต์›
});
// ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ํ‘œ์‹œ
previewImg.src = resultCanvas.toDataURL(`image/${originalFormat === 'jpg' ? 'jpeg' : originalFormat}`);
previewContainer.style.display = 'flex'; // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ปจํ…Œ์ด๋„ˆ ๋ณด์ด๊ฒŒ ํ•จ
return resultCanvas; // ๊ฒฐ๊ณผ ์บ”๋ฒ„์Šค ๋ฐ˜ํ™˜
}
// ์„ ํƒ๋œ ๋ ˆ์ด์–ด ์‚ญ์ œ
function deleteSelectedLayer() {
if (activeOverlayIndex >= 0) {
// ์‚ญ์ œ ํ™•์ธ (์„ ํƒ ์‚ฌํ•ญ)
if (confirm(`๋ ˆ์ด์–ด ${activeOverlayIndex + 1}์„(๋ฅผ) ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?`)) {
// ํ•ด๋‹น ์ธ๋ฑ์Šค์˜ ๋ ˆ์ด์–ด ์‚ญ์ œ
overlays.splice(activeOverlayIndex, 1);
activeOverlayIndex = -1; // ํ™œ์„ฑ ๋ ˆ์ด์–ด ์—†์Œ์œผ๋กœ ์„ค์ •
updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์—…๋ฐ์ดํŠธ
updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
// ๋ชจ๋“  ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์‚ญ์ œ๋˜๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ˆจ๊น€
if (overlays.length === 0) {
previewContainer.style.display = 'none';
}
}
}
}
// ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ด๋ฒคํŠธ์˜ ํด๋ผ์ด์–ธํŠธ ์ขŒํ‘œ๋ฅผ ์บ”๋ฒ„์Šค ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
function getCanvasCoordinates(clientX, clientY) {
const rect = canvas.getBoundingClientRect(); // ์บ”๋ฒ„์Šค์˜ ํ™”๋ฉด์ƒ ์œ„์น˜์™€ ํฌ๊ธฐ
const scaleX = canvas.width / rect.width; // ๊ฐ€๋กœ ์Šค์ผ€์ผ ๋น„์œจ
const scaleY = canvas.height / rect.height; // ์„ธ๋กœ ์Šค์ผ€์ผ ๋น„์œจ
// ํด๋ผ์ด์–ธํŠธ ์ขŒํ‘œ๋ฅผ ์บ”๋ฒ„์Šค ๋‚ด๋ถ€ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
const x = (clientX - rect.left) * scaleX;
const y = (clientY - rect.top) * scaleY;
return { x, y };
}
// ํ˜„์žฌ ํ•„ํ„ฐ ๊ฐ’์„ ํ™œ์„ฑ ๋ ˆ์ด์–ด์— ์ ์šฉํ•˜๊ณ  ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
function applyCurrentFilters() {
if (activeOverlayIndex < 0) return; // ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์—†์œผ๋ฉด ์ ์šฉํ•˜์ง€ ์•Š์Œ
const filterValues = {
temperature: parseInt(temperatureSlider.value),
brightness: parseInt(brightnessSlider.value),
contrast: parseInt(contrastSlider.value),
saturation: parseInt(saturationSlider.value)
};
// filter.js์˜ applyFilterToLayer ํ•จ์ˆ˜ ํ˜ธ์ถœ
ImageFilters.applyFilterToLayer(overlays, activeOverlayIndex, filterValues);
drawCanvas(); // ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•˜์—ฌ ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
}
// --- ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ---
// DOMContentLoaded ์ด๋ฒคํŠธ: ๋ฌธ์„œ ๋กœ๋“œ ํ›„ ์ดˆ๊ธฐํ™” ๋ฐ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
document.addEventListener('DOMContentLoaded', () => {
// ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
backgroundInput.addEventListener('change', function(e) {
if (e.target.files.length > 0) {
loadImage(e.target.files[0], 'background');
}
});
// ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
overlayInput.addEventListener('change', function(e) {
if (e.target.files.length > 0) {
loadImage(e.target.files[0], 'overlay');
this.value = ''; // ํŒŒ์ผ ์„ ํƒ ํ›„ input ๊ฐ’ ์ดˆ๊ธฐํ™” (๊ฐ™์€ ํŒŒ์ผ ๋‹ค์‹œ ์„ ํƒ ๊ฐ€๋Šฅํ•˜๊ฒŒ)
}
});
// ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
const exampleImagesContainer = document.getElementById('example-images-container'); // ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ ID
if (exampleImagesContainer) {
exampleImagesContainer.querySelectorAll('.example-img').forEach(img => {
img.addEventListener('click', function() {
const imageUrl = this.src;
const fileName = this.getAttribute('data-filename') || 'example_image.png';
// ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ๋กœ๋“œ
const exampleImgElement = new Image();
exampleImgElement.crossOrigin = 'anonymous'; // CORS ๋ฌธ์ œ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
exampleImgElement.onload = function() {
// TODO: ์—ฌ๊ธฐ์—์„œ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ๋กœ์ง์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
// ์ œ๊ณต๋œ cropTransparentPixels ํ•จ์ˆ˜๋Š” ํˆฌ๋ช… ์˜์—ญ์„ ์ž๋ฅด๋Š” ๋ฐ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// ๋งŒ์•ฝ ์˜ˆ์‹œ ์ด๋ฏธ์ง€๊ฐ€ ์ด๋ฏธ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€๋ผ๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.
// ์ผ๋ฐ˜ ์ด๋ฏธ์ง€์˜ ๋ณต์žกํ•œ ๋ฐฐ๊ฒฝ์„ ์ œ๊ฑฐํ•˜๋ ค๋ฉด ๋ณ„๋„์˜ ๊ณ ๊ธ‰ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋กœ์ง์ด๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
let processedImageSource = exampleImgElement; // ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ์›๋ณธ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
try {
// ํˆฌ๋ช… ํ”ฝ์…€ ์ž๋ฅด๊ธฐ ์‹œ๋„ (๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€์— ์œ ์šฉ)
const croppedCanvas = cropTransparentPixels(exampleImgElement);
if (croppedCanvas.width > 0 && croppedCanvas.height > 0) {
processedImageSource = croppedCanvas;
} else {
console.warn("cropTransparentPixels resulted in empty canvas, using original image.");
processedImageSource = exampleImgElement;
}
} catch (e) {
console.error("Error during cropTransparentPixels, using original image:", e);
processedImageSource = exampleImgElement;
}
// ์ฒ˜๋ฆฌ๋œ ์ด๋ฏธ์ง€๋ฅผ Blob์œผ๋กœ ๋ณ€ํ™˜
// cropTransparentPixels๊ฐ€ ์บ”๋ฒ„์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ toBlob ์‚ฌ์šฉ
const canvasToProcess = processedImageSource instanceof HTMLCanvasElement ? processedImageSource : null;
if (canvasToProcess) {
canvasToProcess.toBlob(function(blob) {
if (blob) {
// Blob์„ ์‚ฌ์šฉํ•˜์—ฌ loadImage ํ•จ์ˆ˜ ํ˜ธ์ถœ (loadImage ํ•จ์ˆ˜๊ฐ€ Blob์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ˆ˜์ •๋จ)
loadImage(blob, 'overlay');
} else {
console.error("Failed to create blob from processed canvas.");
alert("์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
}
}, 'image/png'); // ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ์‹œ ํˆฌ๋ช…๋„๋ฅผ ์œ„ํ•ด PNG ํฌ๋งท ๊ถŒ์žฅ
} else if (processedImageSource instanceof HTMLImageElement) {
// Canvas๊ฐ€ ์•„๋‹Œ Image ์š”์†Œ์ธ ๊ฒฝ์šฐ (e.g., cropTransparentPixels ์˜ค๋ฅ˜ ์‹œ)
// Data URL๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ Blob ์ƒ์„ฑ ํ›„ loadImage ํ˜ธ์ถœ
const tempCanvas = document.createElement('canvas');
tempCanvas.width = processedImageSource.width;
tempCanvas.height = processedImageSource.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(processedImageSource, 0, 0);
tempCanvas.toBlob(function(blob) {
if (blob) {
loadImage(blob, 'overlay');
} else {
console.error("Failed to create blob from fallback image.");
alert("์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
}
}, 'image/png');
} else {
console.error("Processed image source is neither Canvas nor Image.");
alert("์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
}
};
exampleImgElement.onerror = function() {
console.error("Error loading example image:", imageUrl);
alert("์˜ˆ์‹œ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
};
exampleImgElement.src = imageUrl; // ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹œ์ž‘
});
});
}
// Delete ํ‚ค ๋ˆŒ๋ €์„ ๋•Œ ์„ ํƒ๋œ ๋ ˆ์ด์–ด ์‚ญ์ œ
document.addEventListener('keydown', function(e) {
// Ctrl ๋˜๋Š” Cmd ํ‚ค์™€ ํ•จ๊ป˜ ๋ˆŒ๋ ธ์„ ๋•Œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ์กฐ๊ฑด ์ถ”๊ฐ€ (์‹ค์ˆ˜ ๋ฐฉ์ง€)
if (e.key === 'Delete' && activeOverlayIndex >= 0) {
e.preventDefault(); // ๊ธฐ๋ณธ ๋™์ž‘(๋’ค๋กœ๊ฐ€๊ธฐ ๋“ฑ) ๋ฐฉ์ง€
deleteSelectedLayer();
}
});
// ๋ ˆ์ด์–ด ์‚ญ์ œ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
deleteLayerBtn.addEventListener('click', function() {
if (activeOverlayIndex >= 0) {
deleteSelectedLayer();
}
});
// ํฌ๊ธฐ ์กฐ์ ˆ ์Šฌ๋ผ์ด๋” ์ด๋ฒคํŠธ
scaleSlider.addEventListener('input', function() {
if (activeOverlayIndex >= 0) {
overlays[activeOverlayIndex].scale = parseInt(this.value) / 100;
scaleValue.textContent = this.value + '%';
drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
}
});
// ํšŒ์ „ ์Šฌ๋ผ์ด๋” ์ด๋ฒคํŠธ
rotationSlider.addEventListener('input', function() {
if (activeOverlayIndex >= 0) {
overlays[activeOverlayIndex].rotation = parseInt(this.value);
rotationValue.textContent = overlays[activeOverlayIndex].rotation + 'ยฐ';
drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
}
});
// ํ•„ํ„ฐ ์Šฌ๋ผ์ด๋” ์ด๋ฒคํŠธ
temperatureSlider.addEventListener('input', function() { temperatureValue.textContent = this.value; applyCurrentFilters(); });
brightnessSlider.addEventListener('input', function() { brightnessValue.textContent = this.value + '%'; applyCurrentFilters(); });
contrastSlider.addEventListener('input', function() { contrastValue.textContent = this.value + '%'; applyCurrentFilters(); });
saturationSlider.addEventListener('input', function() { saturationValue.textContent = this.value + '%'; applyCurrentFilters(); });
// ํ•„ํ„ฐ ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
resetFilterBtn.addEventListener('click', function() {
// filter.js์˜ resetFilters ํ•จ์ˆ˜ ํ˜ธ์ถœ
const defaultFilters = ImageFilters.resetFilters(overlays, activeOverlayIndex);
// ์Šฌ๋ผ์ด๋” ๊ฐ’๊ณผ ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
temperatureSlider.value = defaultFilters.temperature; temperatureValue.textContent = defaultFilters.temperature;
brightnessSlider.value = defaultFilters.brightness; brightnessValue.textContent = defaultFilters.brightness + '%';
contrastSlider.value = defaultFilters.contrast; contrastValue.textContent = defaultFilters.contrast + '%';
saturationSlider.value = defaultFilters.saturation; saturationValue.textContent = defaultFilters.saturation + '%';
drawCanvas(); // ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๋ฐ˜์˜
});
// ์บ”๋ฒ„์Šค ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
canvas.addEventListener('mousedown', function(e) {
// ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋ฒ„ํŠผ๋งŒ ์ฒ˜๋ฆฌ
if (e.button !== 0) return;
const { x, y } = getCanvasCoordinates(e.clientX, e.clientY);
const isAltPressed = e.altKey; // Alt ํ‚ค ๋ˆŒ๋ฆผ ์—ฌ๋ถ€ ํ™•์ธ
let clickedOverlayIndex = -1; // ํด๋ฆญ๋œ ์˜ค๋ฒ„๋ ˆ์ด ์ธ๋ฑ์Šค
let handle = null; // ํด๋ฆญ๋œ ํ•ธ๋“ค
// ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ๋จผ์ € ํ•ด๋‹น ๋ ˆ์ด์–ด์˜ ํ•ธ๋“ค ํด๋ฆญ ์—ฌ๋ถ€ ํ™•์ธ
if (activeOverlayIndex >= 0) {
handle = getHandleAtPosition(x, y, overlays[activeOverlayIndex]);
if (handle) {
clickedOverlayIndex = activeOverlayIndex; // ํ•ธ๋“ค์ด ํด๋ฆญ๋˜๋ฉด ํ•ด๋‹น ๋ ˆ์ด์–ด ์„ ํƒ
}
}
// ํ•ธ๋“ค์ด ํด๋ฆญ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์ž์ฒด ํด๋ฆญ ์—ฌ๋ถ€ ํ™•์ธ
if (!handle) {
let overlaysAtPosition = []; // ํด๋ฆญ๋œ ์œ„์น˜์— ์žˆ๋Š” ๋ชจ๋“  ์˜ค๋ฒ„๋ ˆ์ด ๋ชฉ๋ก
// ๋ชจ๋“  ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ํด๋ฆญ๋œ ์œ„์น˜์— ํ•ด๋‹นํ•˜๋Š” ์˜ค๋ฒ„๋ ˆ์ด ์ฐพ๊ธฐ
for (let i = 0; i < overlays.length; i++) {
if (isOverOverlayImage(x, y, overlays[i])) {
overlaysAtPosition.push(i);
}
}
if (overlaysAtPosition.length > 0) {
// ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ๊ฒน์ณ ์žˆ๋‹ค๋ฉด Alt ํ‚ค ๋ˆŒ๋ฆผ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌ
if (isAltPressed && overlaysAtPosition.length > 1) {
// Alt ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ๊ณ  ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๊ฒน์ณ ์žˆ๋‹ค๋ฉด, ํ˜„์žฌ ์„ ํƒ๋œ ๋ ˆ์ด์–ด ๋‹ค์Œ ์ˆœ์„œ์˜ ๋ ˆ์ด์–ด๋ฅผ ์„ ํƒ
if (activeOverlayIndex >= 0) {
const currentIndexInAtPosition = overlaysAtPosition.indexOf(activeOverlayIndex);
if (currentIndexInAtPosition !== -1) {
const nextIndexInAtPosition = (currentIndexInAtPosition + 1) % overlaysAtPosition.length;
clickedOverlayIndex = overlaysAtPosition[nextIndexInAtPosition];
} else {
// ํ˜„์žฌ ์„ ํƒ๋œ ๋ ˆ์ด์–ด๊ฐ€ ํด๋ฆญ๋œ ์œ„์น˜์— ์—†์œผ๋ฉด ๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด ์„ ํƒ (๊ธฐ๋ณธ ๋™์ž‘)
clickedOverlayIndex = overlaysAtPosition[overlaysAtPosition.length - 1];
}
} else {
// Alt ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ์ง€๋งŒ ํ˜„์žฌ ์„ ํƒ๋œ ๋ ˆ์ด์–ด๊ฐ€ ์—†์œผ๋ฉด ๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด ์„ ํƒ
clickedOverlayIndex = overlaysAtPosition[overlaysAtPosition.length - 1];
}
} else {
// Alt ํ‚ค๊ฐ€ ๋ˆŒ๋ฆฌ์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ํ•˜๋‚˜๋งŒ ๊ฒน์ณ ์žˆ๋‹ค๋ฉด, ๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด ์„ ํƒ
clickedOverlayIndex = overlaysAtPosition[overlaysAtPosition.length - 1];
}
}
}
// ํด๋ฆญ๋œ ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ํ™œ์„ฑ ๋ ˆ์ด์–ด๋กœ ์„ค์ •ํ•˜๊ณ  ์ƒํƒœ ์—…๋ฐ์ดํŠธ
if (clickedOverlayIndex >= 0) {
const activeOverlay = overlays[clickedOverlayIndex];
// ์ด๋ฏธ ํ™œ์„ฑ ๋ ˆ์ด์–ด์ธ ๊ฒฝ์šฐ๋Š” ์ œ์™ธ
if (activeOverlayIndex !== clickedOverlayIndex) {
activeOverlayIndex = clickedOverlayIndex;
updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์—…๋ฐ์ดํŠธ
updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
}
// ํด๋ฆญ๋œ ๊ฒƒ์ด ํ•ธ๋“ค์ด๋ฉด ํ•ด๋‹น ์ž‘์—… ์‹œ์ž‘
if (handle) {
if (handle.id === 'rotate') {
isRotating = true;
canvas.style.cursor = 'grabbing';
} else {
isResizing = true;
activeHandle = handle.id; // ํ™œ์„ฑ ํ•ธ๋“ค ID ์„ค์ •
// ๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ์ž‘ ์‹œ ๋งˆ์šฐ์Šค ์œ„์น˜์™€ ์˜ค๋ฒ„๋ ˆ์ด ์ค‘์‹ฌ ๊ฐ„์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ (๋น„๋ก€ ์Šค์ผ€์ผ๋ง์— ์‚ฌ์šฉ)
const dxMouse = x - activeOverlay.x;
const dyMouse = y - activeOverlay.y;
resizeStartDistance = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);
if (resizeStartDistance < 10) resizeStartDistance = 10; // ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ์ตœ์†Œ๊ฐ’ ์„ค์ •
}
} else {
// ํ•ธ๋“ค์ด ์•„๋‹Œ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์ž์ฒด๋ฅผ ํด๋ฆญํ•œ ๊ฒฝ์šฐ ๋“œ๋ž˜๊ทธ ์‹œ์ž‘
isDragging = true;
canvas.style.cursor = 'grabbing'; // ๋“œ๋ž˜๊ทธ ์ปค์„œ ๋ชจ์–‘
}
} else {
// ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹Œ ๋นˆ ๊ณต๊ฐ„์„ ํด๋ฆญํ•œ ๊ฒฝ์šฐ ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ด์ œ
activeOverlayIndex = -1;
updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์ดˆ๊ธฐํ™”
updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ (ํ™œ์„ฑ ํ‘œ์‹œ ์ œ๊ฑฐ)
}
// ๋งˆ์ง€๋ง‰ ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ ์ €์žฅ
lastX = x;
lastY = y;
drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ (ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ธ๋“ค ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ)
});
canvas.addEventListener('mousemove', function(e) {
const { x, y } = getCanvasCoordinates(e.clientX, e.clientY);
// ๋“œ๋ž˜๊ทธ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์ด ์•„๋‹ˆ๋ฉด ์ปค์„œ ๋ชจ์–‘ ์—…๋ฐ์ดํŠธ
if (!isDragging && !isRotating && !isResizing) {
updateCursor(x, y);
} else if (activeOverlayIndex >= 0) { // ๋“œ๋ž˜๊ทธ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์ด๊ณ  ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด
const activeOverlay = overlays[activeOverlayIndex];
canvas.style.cursor = 'grabbing'; // ์ž‘์—… ์ค‘์—๋Š” ์žก๋Š” ์ปค์„œ ์œ ์ง€
if (isRotating) {
// ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํšŒ์ „ ๊ฐ๋„ ๊ณ„์‚ฐ
const dx = x - activeOverlay.x;
const dy = y - activeOverlay.y;
let angle = Math.atan2(dy, dx) * 180 / Math.PI + 90; // atan2 ๊ฒฐ๊ณผ๋Š” -180~180, +90ํ•˜์—ฌ 0~360์œผ๋กœ ๋ณ€ํ™˜
angle = (angle % 360 + 360) % 360; // ๊ฐ๋„๋ฅผ 0~360 ๋ฒ”์œ„๋กœ ์œ ์ง€
activeOverlay.rotation = angle;
// ํšŒ์ „ ์Šฌ๋ผ์ด๋” ๋ฐ ๊ฐ’ ์—…๋ฐ์ดํŠธ
rotationSlider.value = Math.round(activeOverlay.rotation);
rotationValue.textContent = Math.round(activeOverlay.rotation) + 'ยฐ';
} else if (isResizing && activeOverlay.image) {
// ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์ด๊ณ  ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด
const dxMouse = x - activeOverlay.x;
const dyMouse = y - activeOverlay.y;
const currentDistance = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);
if (resizeStartDistance > 0 && currentDistance > 0) {
// ๋งˆ์šฐ์Šค ์ด๋™ ๊ฑฐ๋ฆฌ์— ๋น„๋ก€ํ•˜์—ฌ ์Šค์ผ€์ผ ์กฐ์ •
let newScale = activeOverlay.scale * (currentDistance / resizeStartDistance);
activeOverlay.scale = Math.max(0.01, newScale); // ์Šค์ผ€์ผ ์ตœ์†Œ๊ฐ’ ์ œํ•œ
resizeStartDistance = currentDistance; // ๋‹ค์Œ ์ด๋™ ๊ณ„์‚ฐ์„ ์œ„ํ•ด ๊ฑฐ๋ฆฌ ์—…๋ฐ์ดํŠธ
}
// ์Šค์ผ€์ผ ์Šฌ๋ผ์ด๋” ๋ฐ ๊ฐ’ ์—…๋ฐ์ดํŠธ
const scalePercent = Math.round(activeOverlay.scale * 100);
scaleSlider.value = scalePercent;
scaleValue.textContent = scalePercent + '%';
} else if (isDragging && activeOverlay.image) {
// ๋“œ๋ž˜๊ทธ ์ค‘์ด๊ณ  ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด
const dx = x - lastX; // x์ถ• ์ด๋™ ๊ฑฐ๋ฆฌ
const dy = y - lastY; // y์ถ• ์ด๋™ ๊ฑฐ๋ฆฌ
activeOverlay.x += dx; // ์˜ค๋ฒ„๋ ˆ์ด ์œ„์น˜ ์—…๋ฐ์ดํŠธ
activeOverlay.y += dy;
}
// ์ž‘์—… ์ค‘์—๋Š” ์บ”๋ฒ„์Šค ๊ณ„์† ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
if (isRotating || isResizing || isDragging) {
drawCanvas();
}
}
// ๋งˆ์ง€๋ง‰ ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ ์—…๋ฐ์ดํŠธ
lastX = x;
lastY = y;
});
// ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ ๋–ผ๊ฑฐ๋‚˜ ์บ”๋ฒ„์Šค ๋ฐ–์œผ๋กœ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ ์ž‘์—… ์ข…๋ฃŒ
window.addEventListener('mouseup', function(e) {
isDragging = false;
isRotating = false;
isResizing = false;
activeHandle = null; // ํ™œ์„ฑ ํ•ธ๋“ค ์—†์Œ
// ๋งˆ์šฐ์Šค ์ขŒํ‘œ๊ฐ€ ์œ ํšจํ•˜๋ฉด ์ปค์„œ ๋ชจ์–‘ ์—…๋ฐ์ดํŠธ
if (e.clientX !== undefined && e.clientY !== undefined) {
const { x, y } = getCanvasCoordinates(e.clientX, e.clientY);
updateCursor(x, y);
} else {
// ์ขŒํ‘œ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ ์ปค์„œ๋กœ ์„ค์ •
canvas.style.cursor = 'default';
}
});
// ํ„ฐ์น˜ ์ด๋ฒคํŠธ (๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ๋ฅผ ์—๋ฎฌ๋ ˆ์ด์…˜ํ•˜์—ฌ ์ฒ˜๋ฆฌ)
canvas.addEventListener('touchstart', function(e) {
e.preventDefault(); // ๊ธฐ๋ณธ ํ„ฐ์น˜ ๋™์ž‘ ๋ฐฉ์ง€ (์Šคํฌ๋กค ๋“ฑ)
if (e.touches.length > 0) {
const touch = e.touches[0];
// ์ฒซ ๋ฒˆ์งธ ํ„ฐ์น˜ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ mousedown ์ด๋ฒคํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐœ์ƒ
const mouseEvent = new MouseEvent('mousedown', { clientX: touch.clientX, clientY: touch.clientY, buttons: 1 });
canvas.dispatchEvent(mouseEvent);
}
}, { passive: false }); // passive: false๋กœ ์„ค์ •ํ•˜์—ฌ preventDefault()๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก ํ•จ
canvas.addEventListener('touchmove', function(e) {
e.preventDefault(); // ๊ธฐ๋ณธ ํ„ฐ์น˜ ๋™์ž‘ ๋ฐฉ์ง€
if (e.touches.length > 0) {
const touch = e.touches[0];
// ์ฒซ ๋ฒˆ์งธ ํ„ฐ์น˜ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ mousemove ์ด๋ฒคํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐœ์ƒ
const mouseEvent = new MouseEvent('mousemove', { clientX: touch.clientX, clientY: touch.clientY, buttons: 1 });
canvas.dispatchEvent(mouseEvent);
}
}, { passive: false });
canvas.addEventListener('touchend', function(e) {
e.preventDefault(); // ๊ธฐ๋ณธ ํ„ฐ์น˜ ๋™์ž‘ ๋ฐฉ์ง€
// mouseup ์ด๋ฒคํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐœ์ƒ (์ขŒํ‘œ ์ •๋ณด๋Š” ํ•„์š” ์—†์Œ)
const mouseEvent = new MouseEvent('mouseup', {});
window.dispatchEvent(mouseEvent); // window์— ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ mouseup ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก ํ•จ
}, { passive: false });
// ํ•ฉ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
generateBtn.addEventListener('click', function() {
generateMergedImage(); // ํ•ฉ์„ฑ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ํ‘œ์‹œ
// ์ด ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ UI ๋ ˆ์ด์•„์›ƒ์„ ๋ณ€๊ฒฝํ•˜๋Š” ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์— ์—†์Šต๋‹ˆ๋‹ค.
// ์˜ค์ง generateMergedImage() ํ•จ์ˆ˜๋งŒ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
});
// ์ „์ฒด ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
resetAllBtn.addEventListener('click', function() {
backgroundImage = null; // ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ดˆ๊ธฐํ™”
overlays = []; // ์˜ค๋ฒ„๋ ˆ์ด ๋ฐฐ์—ด ์ดˆ๊ธฐํ™”
activeOverlayIndex = -1; // ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ด์ œ
backgroundInput.value = ''; // ํŒŒ์ผ input ๊ฐ’ ์ดˆ๊ธฐํ™”
overlayInput.value = '';
updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์ดˆ๊ธฐํ™”
updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์ดˆ๊ธฐํ™”
// ํ•„ํ„ฐ ์ปจํŠธ๋กค ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์„ค์ •
const defaultFilters = ImageFilters.resetFilters(overlays, activeOverlayIndex); // (์‚ฌ์‹ค์ƒ overlays๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฏ€๋กœ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜)
temperatureSlider.value = defaultFilters.temperature; temperatureValue.textContent = defaultFilters.temperature;
brightnessSlider.value = defaultFilters.brightness; brightnessValue.textContent = defaultFilters.brightness + '%';
contrastSlider.value = defaultFilters.contrast; contrastValue.textContent = defaultFilters.contrast + '%';
saturationSlider.value = defaultFilters.saturation; saturationValue.textContent = defaultFilters.saturation + '%';
// ์บ”๋ฒ„์Šค ํฌ๊ธฐ ๋ฐ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์žฌ์„ค์ •
canvasAdjustedHeight = CANVAS_HEIGHT;
canvas.height = canvasAdjustedHeight;
initCanvas(); // ์บ”๋ฒ„์Šค ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ์œผ๋กœ ์ดˆ๊ธฐํ™”
previewContainer.style.display = 'none'; // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ ์ˆจ๊น€
});
// ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
downloadBtn.addEventListener('click', function() {
const resultCanvas = generateMergedImage(); // ํ•ฉ์„ฑ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
if (!resultCanvas) return; // ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด ๋‹ค์šด๋กœ๋“œํ•˜์ง€ ์•Š์Œ
const link = document.createElement('a'); // ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ์ž„์‹œ ๋งํฌ ์š”์†Œ ์ƒ์„ฑ
// ํŒŒ์ผ ์ด๋ฆ„ ์ƒ์„ฑ (ํ˜„์žฌ ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜)
const timestamp = new Date().toISOString().replace(/[-:.]/g, '').substring(0, 14);
// ์ด๋ฏธ์ง€ ํฌ๋งท ๋ฐ ํ™•์žฅ์ž ๊ฒฐ์ • (์›๋ณธ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ํฌ๋งท ๋”ฐ๋ฆ„)
const mimeType = originalFormat === 'jpg' ? 'image/jpeg' : `image/${originalFormat}`;
const fileExt = originalFormat;
link.download = `merged_image_${timestamp}.${fileExt}`; // ๋‹ค์šด๋กœ๋“œ๋  ํŒŒ์ผ ์ด๋ฆ„ ์„ค์ •
// ์บ”๋ฒ„์Šค ์ด๋ฏธ์ง€๋ฅผ Data URL๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋งํฌ์˜ href๋กœ ์„ค์ •
link.href = resultCanvas.toDataURL(mimeType, 1.0); // 1.0์€ ํ™”์งˆ (PNG๋Š” ์˜ํ–ฅ ์—†์Œ, JPG๋Š” 0~1)
link.click(); // ๋งํฌ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ๋‹ค์šด๋กœ๋“œ ์‹คํ–‰
});
// ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ ํ˜ธ์ถœ
initCanvas(); // ์บ”๋ฒ„์Šค ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์„ค์ •
updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์„ค์ • (๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” ๋“ฑ)
}); // DOMContentLoaded ๋