ssboost commited on
Commit
2c51ce3
ยท
verified ยท
1 Parent(s): 43fb7dd

Upload 8 files

Browse files
Files changed (9) hide show
  1. .gitattributes +1 -0
  2. 1.png +0 -0
  3. 2.png +3 -0
  4. 3.png +0 -0
  5. 4.png +0 -0
  6. filter.js +135 -0
  7. index.html +121 -19
  8. script.js +1120 -0
  9. style.css +290 -17
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ 2.png filter=lfs diff=lfs merge=lfs -text
1.png ADDED
2.png ADDED

Git LFS Details

  • SHA256: 9e14516b7bd88d077bf66b441a553489bab8d9791b8b8e855588066d9f59f6fa
  • Pointer size: 131 Bytes
  • Size of remote file: 202 kB
3.png ADDED
4.png ADDED
filter.js ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * ์ด๋ฏธ์ง€ ํ•„ํ„ฐ ๊ธฐ๋Šฅ์„ ์œ„ํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ
3
+ * ์ด๋ฏธ์ง€ ํ•„ํ„ฐ ์ ์šฉ, ๊ด€๋ฆฌ์— ๊ด€๋ จ๋œ ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ ํฌํ•จ
4
+ */
5
+
6
+ // ํ•„ํ„ฐ ๊ธฐ๋ณธ๊ฐ’
7
+ const DEFAULT_FILTERS = {
8
+ temperature: 0,
9
+ brightness: 100,
10
+ contrast: 100,
11
+ saturation: 100
12
+ };
13
+
14
+ /**
15
+ * ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ์— ํ•„ํ„ฐ ์ ์šฉ
16
+ * @param {ImageData} imageData - ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ
17
+ * @param {Object} filters - ํ•„ํ„ฐ ์„ค์ • (temperature, brightness, contrast, saturation)
18
+ * @returns {ImageData} - ํ•„ํ„ฐ๊ฐ€ ์ ์šฉ๋œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ
19
+ */
20
+ function applyFilters(imageData, filters) {
21
+ const {temperature, brightness, contrast, saturation} = filters;
22
+
23
+ const data = imageData.data;
24
+
25
+ // ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ์˜ ๊ฐ ํ”ฝ์…€์— ํ•„ํ„ฐ ์ ์šฉ
26
+ for (let i = 0; i < data.length; i += 4) {
27
+ // RGB ๊ฐ’ ์ถ”์ถœ
28
+ let r = data[i];
29
+ let g = data[i + 1];
30
+ let b = data[i + 2];
31
+
32
+ // ์ƒ‰์˜จ๋„ ์ ์šฉ (๋นจ๊ฐ„์ƒ‰/ํŒŒ๋ž€์ƒ‰ ์กฐ์ •)
33
+ if (temperature !== 0) {
34
+ const tempValue = temperature / 5; // ํšจ๊ณผ ๊ฐ•๋„ ์กฐ์ •
35
+ if (tempValue > 0) {
36
+ // ๋”ฐ๋œปํ•œ ์ƒ‰์กฐ (๋” ๋นจ๊ฐ›๊ฒŒ)
37
+ r = Math.min(255, r + tempValue);
38
+ b = Math.max(0, b - tempValue / 2);
39
+ } else {
40
+ // ์ฐจ๊ฐ€์šด ์ƒ‰์กฐ (๋” ํŒŒ๋ž—๊ฒŒ)
41
+ r = Math.max(0, r + tempValue);
42
+ b = Math.min(255, b - tempValue * 1.5);
43
+ }
44
+ }
45
+
46
+ // ๋ฐ๊ธฐ ์ ์šฉ (๋ชจ๋“  ์ƒ‰์ƒ ์ฑ„๋„์— ๊ท ์ผํ•˜๊ฒŒ ์ ์šฉ)
47
+ const brightnessMultiplier = brightness / 100;
48
+ r = Math.min(255, r * brightnessMultiplier);
49
+ g = Math.min(255, g * brightnessMultiplier);
50
+ b = Math.min(255, b * brightnessMultiplier);
51
+
52
+ // ๋Œ€๋น„ ์ ์šฉ
53
+ const contrastFactor = (contrast / 100);
54
+ r = Math.min(255, Math.max(0, (r - 128) * contrastFactor + 128));
55
+ g = Math.min(255, Math.max(0, (g - 128) * contrastFactor + 128));
56
+ b = Math.min(255, Math.max(0, (b - 128) * contrastFactor + 128));
57
+
58
+ // ์ฑ„๋„ ์ ์šฉ
59
+ const avg = (r + g + b) / 3;
60
+ const saturationMultiplier = saturation / 100;
61
+ r = Math.min(255, Math.max(0, avg + (r - avg) * saturationMultiplier));
62
+ g = Math.min(255, Math.max(0, avg + (g - avg) * saturationMultiplier));
63
+ b = Math.min(255, Math.max(0, avg + (b - avg) * saturationMultiplier));
64
+
65
+ // ๊ฐ’ ๋‹ค์‹œ ์ €์žฅ
66
+ data[i] = Math.round(r);
67
+ data[i + 1] = Math.round(g);
68
+ data[i + 2] = Math.round(b);
69
+ }
70
+
71
+ return imageData;
72
+ }
73
+
74
+ /**
75
+ * ์ด๋ฏธ์ง€์— ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜์—ฌ ์ƒˆ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฌ๊ธฐ
76
+ * @param {HTMLImageElement} image - ์›๋ณธ ์ด๋ฏธ์ง€
77
+ * @param {Object} filters - ํ•„ํ„ฐ ์„ค์ •
78
+ * @returns {HTMLCanvasElement} - ํ•„ํ„ฐ๊ฐ€ ์ ์šฉ๋œ ์ด๋ฏธ์ง€๊ฐ€ ๊ทธ๋ ค์ง„ ์บ”๋ฒ„์Šค
79
+ */
80
+ function createFilteredCanvas(image, filters) {
81
+ if (!image) return null;
82
+
83
+ const tempCanvas = document.createElement('canvas');
84
+ tempCanvas.width = image.width;
85
+ tempCanvas.height = image.height;
86
+ const tempCtx = tempCanvas.getContext('2d');
87
+
88
+ // ์›๋ณธ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ
89
+ tempCtx.drawImage(image, 0, 0);
90
+
91
+ // ํ•„ํ„ฐ ์ ์šฉ
92
+ const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
93
+ const filteredData = applyFilters(imageData, filters);
94
+ tempCtx.putImageData(filteredData, 0, 0);
95
+
96
+ return tempCanvas;
97
+ }
98
+
99
+ /**
100
+ * ์„ ํƒ๋œ ๋ ˆ์ด์–ด์—๋งŒ ํ•„ํ„ฐ ์ ์šฉ
101
+ * @param {Array} overlays - ๋ ˆ์ด์–ด ๋ฐฐ์—ด
102
+ * @param {number} activeOverlayIndex - ํ˜„์žฌ ํ™œ์„ฑ ๋ ˆ์ด์–ด ์ธ๋ฑ์Šค
103
+ * @param {Object} filterValues - ์ ์šฉํ•  ํ•„ํ„ฐ ๊ฐ’
104
+ */
105
+ function applyFilterToLayer(overlays, activeOverlayIndex, filterValues) {
106
+ if (activeOverlayIndex >= 0 && activeOverlayIndex < overlays.length) {
107
+ // ํ˜„์žฌ ์„ ํƒ๋œ ๋ ˆ์ด์–ด์—๋งŒ ํ•„ํ„ฐ ์ ์šฉ
108
+ overlays[activeOverlayIndex].filters = { ...filterValues };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * ํ•„ํ„ฐ ์ดˆ๊ธฐํ™”
114
+ * @param {Array} overlays - ๋ ˆ์ด์–ด ๋ฐฐ์—ด
115
+ * @param {number} activeOverlayIndex - ํ˜„์žฌ ํ™œ์„ฑ ๋ ˆ์ด์–ด ์ธ๋ฑ์Šค
116
+ * @returns {Object} - ์ดˆ๊ธฐํ™”๋œ ํ•„ํ„ฐ ๊ฐ’
117
+ */
118
+ function resetFilters(overlays, activeOverlayIndex) {
119
+ // ํ˜„์žฌ ๋ ˆ์ด์–ด์˜ ํ•„ํ„ฐ๋ฅผ ์ดˆ๊ธฐํ™”
120
+ if (activeOverlayIndex >= 0 && activeOverlayIndex < overlays.length) {
121
+ overlays[activeOverlayIndex].filters = { ...DEFAULT_FILTERS };
122
+ }
123
+
124
+ // ์ดˆ๊ธฐ ํ•„ํ„ฐ ๊ฐ’ ๋ฐ˜ํ™˜
125
+ return { ...DEFAULT_FILTERS };
126
+ }
127
+
128
+ // filter.js์˜ ํ•จ์ˆ˜๋ฅผ ์™ธ๋ถ€์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ „์—ญ์œผ๋กœ ๋…ธ์ถœ
129
+ window.ImageFilters = {
130
+ applyFilters,
131
+ createFilteredCanvas,
132
+ applyFilterToLayer,
133
+ resetFilters,
134
+ DEFAULT_FILTERS
135
+ };
index.html CHANGED
@@ -1,19 +1,121 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>์ด๋ฏธ์ง€ ๊ฒน์น˜๊ธฐ ํŽธ์ง‘๊ธฐ (์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€ ์ง€์›)</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <h1>์ด๋ฏธ์ง€ ๊ฒน์น˜๊ธฐ ํŽธ์ง‘๊ธฐ</h1>
12
+ <p>๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์œ„์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€๋ฅผ ๋ฐฐ์น˜ํ•˜๊ณ  ๊ฐ๊ฐ ์กฐ์ ˆํ•œ ํ›„, ์กฐ์ ˆ๋œ ์ด๋ฏธ์ง€๋“ค๋งŒ PNG๋กœ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.</p>
13
+
14
+ <div class="upload-container">
15
+ <div class="upload-box">
16
+ <span class="upload-label">1. ์›๋ณธ ์ด๋ฏธ์ง€ ์„ ํƒ</span>
17
+ <input type="file" id="background-input" class="file-input" accept="image/*">
18
+ <label for="background-input" class="upload-button">์›๋ณธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ</label>
19
+ </div>
20
+ <div class="upload-box">
21
+ <span class="upload-label">2. ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€ ์„ ํƒ (์ถ”์ถœ๋  PNG, ์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€๋Šฅ)</span>
22
+ <input type="file" id="overlay-input" class="file-input" accept="image/*">
23
+ <label for="overlay-input" class="upload-button">๊ฒน์น  ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ</label>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="control-panel">
28
+ <div class="control-group">
29
+ <h3>์œ„์น˜ ๋ฐ ํฌ๊ธฐ (์„ ํƒ๋œ ์ด๋ฏธ์ง€)</h3>
30
+ <div class="slider-container">
31
+ <label for="scale-slider">ํฌ๊ธฐ:</label>
32
+ <input type="range" id="scale-slider" min="10" max="500" value="100" disabled>
33
+ <span id="scale-value" class="value-display">100%</span>
34
+ </div>
35
+ <div class="slider-container">
36
+ <label for="rotation-slider">ํšŒ์ „:</label>
37
+ <input type="range" id="rotation-slider" min="0" max="360" value="0" disabled>
38
+ <span id="rotation-value" class="value-display">0ยฐ</span>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="control-group">
43
+ <h3>์˜ˆ์‹œ ์ด๋ฏธ์ง€ ์„ ํƒ</h3>
44
+ <div id="example-images-container" style="display: flex; justify-content: space-around; margin-top: 15px;">
45
+ <img src="1.png" alt="Example 1" class="example-img" data-filename="1.png" style="width: 160px; height: 160px; cursor: pointer; border: 1px solid #ccc;">
46
+ <img src="2.png" alt="Example 2" class="example-img" data-filename="2.png" style="width: 160px; height: 160px; cursor: pointer; border: 1px solid #ccc;">
47
+ <img src="3.png" alt="Example 3" class="example-img" data-filename="3.png" style="width: 160px; height: 160px; cursor: pointer; border: 1px solid #ccc;">
48
+ <img src="4.png" alt="Example 4" class="example-img" data-filename="4.png" style="width: 160px; height: 160px; cursor: pointer; border: 1px solid #ccc;">
49
+ </div>
50
+ </div>
51
+ <div class="control-group filter-panel">
52
+ </div>
53
+
54
+
55
+ <div class="control-group" id="layers-panel">
56
+ <h3>๋ ˆ์ด์–ด ๋ชฉ๋ก</h3>
57
+ <div class="layer-option">
58
+ </div>
59
+ <div id="layers-list" class="layers-list">
60
+ </div>
61
+ </div>
62
+
63
+ <div class="canvas-container">
64
+ <canvas id="canvas" width="800" height="600"></canvas>
65
+ </div>
66
+
67
+ <!-- ํ•„ํ„ฐ ์„ค์ • ์„น์…˜์„ ์ด๋ฏธ์ง€ ์•„๋ž˜๋กœ ์ด๋™ -->
68
+ <div class="filter-panel">
69
+ <h3>ํ•„ํ„ฐ ์„ค์ • (์„ ํƒ ๋ ˆ์ด์–ด์— ์ ์šฉ)</h3>
70
+
71
+ <div class="filter-sliders">
72
+ <div class="filter-slider-container">
73
+ <label for="temperature-slider">์ƒ‰์˜จ๋„:</label>
74
+ <input type="range" id="temperature-slider" min="-100" max="100" value="0" step="5" class="large-slider">
75
+ <span id="temperature-value" class="value-display">0</span>
76
+ </div>
77
+
78
+ <div class="filter-slider-container">
79
+ <label for="brightness-slider">๋ฐ๊ธฐ:</label>
80
+ <input type="range" id="brightness-slider" min="0" max="200" value="100" step="5" class="large-slider">
81
+ <span id="brightness-value" class="value-display">100%</span>
82
+ </div>
83
+
84
+ <div class="filter-slider-container">
85
+ <label for="contrast-slider">๋Œ€๋น„:</label>
86
+ <input type="range" id="contrast-slider" min="50" max="150" value="100" step="5" class="large-slider">
87
+ <span id="contrast-value" class="value-display">100%</span>
88
+ </div>
89
+
90
+ <div class="filter-slider-container">
91
+ <label for="saturation-slider">์ฑ„๋„:</label>
92
+ <input type="range" id="saturation-slider" min="0" max="200" value="100" step="5" class="large-slider">
93
+ <span id="saturation-value" class="value-display">100%</span>
94
+ </div>
95
+ </div>
96
+
97
+ <div class="filter-buttons">
98
+ <button id="reset-filter-btn" class="danger-btn">ํ•„ํ„ฐ ์ดˆ๊ธฐํ™”</button>
99
+ </div>
100
+ </div>
101
+
102
+ <div class="button-container">
103
+ <button id="generate-btn" class="primary-btn" disabled>์ด๋ฏธ์ง€ ํ•ฉ์น˜๊ธฐ</button>
104
+ <button id="reset-all-btn" class="danger-btn">์ฒ˜์Œ๋ถ€ํ„ฐ</button>
105
+ <button id="download-btn" class="info-btn" disabled>๋‹ค์šด๋กœ๋“œ</button>
106
+ </div>
107
+
108
+ <div class="preview-container" id="preview-container">
109
+ <h3>ํ•ฉ์„ฑ๋œ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ</h3>
110
+ <img id="preview-img" alt="ํ•ฉ์„ฑ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ">
111
+ </div>
112
+
113
+ </div>
114
+
115
+ </div>
116
+
117
+ <!-- ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ๋กœ๋“œ -->
118
+ <script src="filter.js"></script>
119
+ <script src="script.js"></script>
120
+ </body>
121
+ </html>
script.js ADDED
@@ -0,0 +1,1120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ ๋ฐ ํŽธ์ง‘ ๊ธฐ๋Šฅ์„ ์œ„ํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ
3
+ */
4
+
5
+ // ์š”์†Œ ๊ฐ€์ ธ์˜ค๊ธฐ
6
+ const backgroundInput = document.getElementById('background-input');
7
+ const overlayInput = document.getElementById('overlay-input');
8
+ const canvas = document.getElementById('canvas');
9
+ const ctx = canvas.getContext('2d');
10
+
11
+ const scaleSlider = document.getElementById('scale-slider');
12
+ const scaleValue = document.getElementById('scale-value');
13
+ const rotationSlider = document.getElementById('rotation-slider');
14
+ const rotationValue = document.getElementById('rotation-value');
15
+
16
+ // ํ•„ํ„ฐ ๊ด€๋ จ ์š”์†Œ
17
+ const temperatureSlider = document.getElementById('temperature-slider');
18
+ const temperatureValue = document.getElementById('temperature-value');
19
+ const brightnessSlider = document.getElementById('brightness-slider');
20
+ const brightnessValue = document.getElementById('brightness-value');
21
+ const contrastSlider = document.getElementById('contrast-slider');
22
+ const contrastValue = document.getElementById('contrast-value');
23
+ const saturationSlider = document.getElementById('saturation-slider');
24
+ const saturationValue = document.getElementById('saturation-value');
25
+ const resetFilterBtn = document.getElementById('reset-filter-btn');
26
+
27
+ const generateBtn = document.getElementById('generate-btn');
28
+ const resetAllBtn = document.getElementById('reset-all-btn');
29
+ const downloadBtn = document.getElementById('download-btn');
30
+
31
+ const previewContainer = document.getElementById('preview-container');
32
+ const previewImg = document.getElementById('preview-img');
33
+
34
+ const layersList = document.getElementById('layers-list');
35
+
36
+
37
+ // ์บ”๋ฒ„์Šค ๊ธฐ๋ณธ ํฌ๊ธฐ
38
+ const CANVAS_WIDTH = 800;
39
+ const CANVAS_HEIGHT = 600;
40
+ canvas.width = CANVAS_WIDTH;
41
+ canvas.height = CANVAS_HEIGHT;
42
+
43
+ // ์ „์—ญ ๋ณ€์ˆ˜
44
+ let backgroundImage = null;
45
+ let originalFormat = 'png'; // ๊ธฐ๋ณธ ํฌ๋งท์„ PNG๋กœ ์„ค์ •
46
+ let canvasAdjustedHeight = CANVAS_HEIGHT; // ์ด๋ฏธ์ง€ ๋น„์œจ์— ๋งž๊ฒŒ ์กฐ์ •๋œ ์บ”๋ฒ„์Šค ๋†’์ด
47
+
48
+ // ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ ์†์„ฑ
49
+ let bgDrawProps = { x: 0, y: 0, width: CANVAS_WIDTH, height: CANVAS_HEIGHT };
50
+
51
+ // ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์™€ ๊ทธ ์†์„ฑ์„ ์ €์žฅํ•  ๋ฐฐ์—ด
52
+ let overlays = [];
53
+ // ํ˜„์žฌ ์กฐ์ž‘ ์ค‘์ธ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ธ๋ฑ์Šค (-1์ด๋ฉด ์„ ํƒ๋˜์ง€ ์•Š์Œ)
54
+ let activeOverlayIndex = -1;
55
+
56
+ // ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์กฐ์ž‘ ๊ด€๋ จ ์ƒํƒœ
57
+ let isDragging = false;
58
+ let isRotating = false;
59
+ let isResizing = false;
60
+ let activeHandle = null;
61
+
62
+ // ์ด์ „ ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ
63
+ let lastX = 0;
64
+ let lastY = 0;
65
+
66
+ // ๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ์ž‘ ์‹œ ํ•ธ๋“ค๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ (๋น„๋ก€ ์Šค์ผ€์ผ๋ง์— ์‚ฌ์šฉ)
67
+ let resizeStartDistance = 0;
68
+
69
+ // ํ•ธ๋“ค ์„ค์ •
70
+ const handleSize = 15;
71
+ const rotateHandleDistance = 30;
72
+
73
+ // ํด๋ฆญ ๊ฐ์ง€ ์‹œ ์—ฌ์œ  ๊ณต๊ฐ„
74
+ const clickPadding = 5;
75
+
76
+ // ์บ”๋ฒ„์Šค ์ดˆ๊ธฐํ™” (ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ)
77
+ function initCanvas() {
78
+ ctx.fillStyle = '#ffffff';
79
+ ctx.fillRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
80
+ updateLayersList();
81
+ }
82
+
83
+ function cropTransparentPixels(imageElement, alphaThreshold = 0) {
84
+ const tempCanvas = document.createElement('canvas');
85
+ tempCanvas.width = imageElement.naturalWidth || imageElement.width;
86
+ tempCanvas.height = imageElement.naturalHeight || imageElement.height;
87
+ const tempCtx = tempCanvas.getContext('2d');
88
+ tempCtx.drawImage(imageElement, 0, 0);
89
+
90
+ let imageData;
91
+ try {
92
+ imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
93
+ } catch (e) {
94
+ console.error("Error getting ImageData (possibly due to CORS):", e);
95
+ // CORS ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ, ๋ณด์•ˆ ์ œ์•ฝ ์—†์ด ์ด๋ฏธ์ง€ ์ „์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์ฒด ๋กœ์ง (๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ํšจ๊ณผ๋Š” ์—†์„ ์ˆ˜ ์žˆ์Œ)
96
+ const fallbackCanvas = document.createElement('canvas');
97
+ fallbackCanvas.width = tempCanvas.width;
98
+ fallbackCanvas.height = tempCanvas.height;
99
+ const fallbackCtx = fallbackCanvas.getContext('2d');
100
+ fallbackCtx.drawImage(imageElement, 0, 0);
101
+ return fallbackCanvas;
102
+ }
103
+
104
+ const data = imageData.data;
105
+ let minX = tempCanvas.width, minY = tempCanvas.height, maxX = 0, maxY = 0;
106
+ let foundContent = false;
107
+
108
+ for (let y = 0; y < tempCanvas.height; y++) {
109
+ for (let x = 0; x < tempCanvas.width; x++) {
110
+ const alpha = data[(y * tempCanvas.width + x) * 4 + 3];
111
+ if (alpha > alphaThreshold) {
112
+ minX = Math.min(minX, x);
113
+ minY = Math.min(minY, y);
114
+ maxX = Math.max(maxX, x);
115
+ maxY = Math.max(maxY, y);
116
+ foundContent = true;
117
+ }
118
+ }
119
+ }
120
+
121
+ if (!foundContent) {
122
+ // ๋‚ด์šฉ์ด ์—†๋Š” ๊ฒฝ์šฐ (์™„์ „ํžˆ ํˆฌ๋ช…ํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜)
123
+ const emptyCanvas = document.createElement('canvas');
124
+ emptyCanvas.width = imageElement.naturalWidth || 1;
125
+ emptyCanvas.height = imageElement.naturalHeight || 1;
126
+ return emptyCanvas;
127
+ }
128
+
129
+ const cropWidth = maxX - minX + 1;
130
+ const cropHeight = maxY - minY + 1;
131
+
132
+ const croppedCanvas = document.createElement('canvas');
133
+ croppedCanvas.width = cropWidth;
134
+ croppedCanvas.height = cropHeight;
135
+ const croppedCtx = croppedCanvas.getContext('2d');
136
+
137
+ // ์ž˜๋ผ๋‚ธ ์˜์—ญ๋งŒ ์ƒˆ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
138
+ croppedCtx.drawImage(tempCanvas, minX, minY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
139
+
140
+ return croppedCanvas;
141
+ }
142
+
143
+ // ํŒŒ์ผ ๋˜๋Š” Blob ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๊ณ  ์บ”๋ฒ„์Šค์— ์ถ”๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜
144
+ function loadImage(fileOrBlob, type) {
145
+ if (!fileOrBlob) return;
146
+
147
+ let fileExtension = 'png'; // ๊ธฐ๋ณธ๊ฐ’
148
+ let isBlob = fileOrBlob instanceof Blob;
149
+ let fileName = 'unknown';
150
+
151
+ if (!isBlob && fileOrBlob.name) {
152
+ fileExtension = fileOrBlob.name.split('.').pop().toLowerCase();
153
+ fileName = fileOrBlob.name;
154
+ } else if (isBlob && fileOrBlob.type) {
155
+ fileExtension = fileOrBlob.type.split('/')[1] ? fileOrBlob.type.split('/')[1].toLowerCase() : 'png';
156
+ fileName = 'processed_image.' + fileExtension; // Blob์˜ ๊ฒฝ์šฐ ํŒŒ์ผ ์ด๋ฆ„ ์ž„์‹œ ์ง€์ •
157
+ }
158
+
159
+
160
+ if (type === 'background' && !isBlob && ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExtension)) {
161
+ originalFormat = fileExtension;
162
+ }
163
+
164
+ const reader = new FileReader();
165
+ reader.onload = function(event) {
166
+ const img = new Image();
167
+ img.onload = function() {
168
+ if (type === 'background') {
169
+ backgroundImage = img;
170
+ const aspectRatio = img.width / img.height;
171
+ bgDrawProps.width = CANVAS_WIDTH;
172
+ bgDrawProps.height = CANVAS_WIDTH / aspectRatio;
173
+ bgDrawProps.x = 0;
174
+ bgDrawProps.y = 0;
175
+ canvasAdjustedHeight = bgDrawProps.height;
176
+ canvas.height = canvasAdjustedHeight;
177
+ canvas.style.height = 'auto'; // ์บ”๋ฒ„์Šค wrapper๊ฐ€ ์•„๋‹Œ canvas ์ž์ฒด์˜ style
178
+ } else if (type === 'overlay') {
179
+ let imageToUse = img;
180
+
181
+ // PNG ๋˜๋Š” ํˆฌ๋ช…๋„๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€ ํ˜•์‹์˜ ๊ฒฝ์šฐ์—๋งŒ ํˆฌ๋ช… ํ”ฝ์…€ ์ž๋ฅด๊ธฐ ์‹œ๋„
182
+ // ์˜ˆ์‹œ ์ด๋ฏธ์ง€๋Š” ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๊ฐ€ ์™„๋ฃŒ๋œ ์ด๋ฏธ์ง€๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ฑฐ๋‚˜,
183
+ // ์—ฌ๊ธฐ์—์„œ cropTransparentPixels๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
184
+ // ์‹ค์ œ ๋ณต์žกํ•œ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ๋กœ์ง์€ ์ด ๋ถ€๋ถ„์ด ์•„๋‹Œ ๋ณ„๋„์˜ ํ•จ์ˆ˜์—์„œ ๊ตฌํ˜„๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
185
+ if (fileExtension === 'png' || fileExtension === 'webp' || fileExtension === 'gif' || (isBlob && fileOrBlob.type === 'image/png')) {
186
+ try {
187
+ // cropTransparentPixels๋Š” ํˆฌ๋ช… ์˜์—ญ์„ ์ž๋ฅด๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.
188
+ // ๋งŒ์•ฝ ์˜ˆ์‹œ ์ด๋ฏธ์ง€๊ฐ€ ์ด๋ฏธ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€๋ผ๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
189
+ // ์ผ๋ฐ˜ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ์ œ๊ฑฐํ•˜๋ ค๋ฉด ๋‹ค๋ฅธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
190
+ const croppedImageCanvas = cropTransparentPixels(img);
191
+ if (croppedImageCanvas.width > 0 && croppedImageCanvas.height > 0) {
192
+ imageToUse = croppedImageCanvas;
193
+ } else {
194
+ // ์ž˜๋ผ๋‚ผ ์˜์—ญ์ด ์—†์œผ๋ฉด ์›๋ณธ ์‚ฌ์šฉ ๋˜๋Š” ๋นˆ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
195
+ console.warn("No non-transparent pixels found, using original or empty.");
196
+ // imageToUse = img; // ์›๋ณธ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
197
+ const emptyCanvas = document.createElement('canvas');
198
+ emptyCanvas.width = img.naturalWidth || 1;
199
+ emptyCanvas.height = img.naturalHeight || 1;
200
+ imageToUse = emptyCanvas; // ๋˜๋Š” ๋นˆ ์บ”๋ฒ„์Šค ์‚ฌ์šฉ
201
+ }
202
+ } catch (e) {
203
+ console.error("Error during image cropping/processing, using original:", e);
204
+ imageToUse = img; // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์›๋ณธ ์‚ฌ์šฉ
205
+ }
206
+ }
207
+
208
+
209
+ const newOverlay = {
210
+ image: imageToUse,
211
+ // ์บ”๋ฒ„์Šค ์ค‘์•™์— ๋ฐฐ์น˜
212
+ x: CANVAS_WIDTH / 2,
213
+ y: canvasAdjustedHeight / 2,
214
+ scale: 1,
215
+ rotation: 0,
216
+ filters: { ...ImageFilters.DEFAULT_FILTERS }
217
+ };
218
+
219
+ // ์ด๋ฏธ์ง€๊ฐ€ ์บ”๋ฒ„์Šค๋ณด๋‹ค ํฌ๋ฉด ์ดˆ๊ธฐ ์Šค์ผ€์ผ ์กฐ์ •
220
+ const initialScaleRatio = Math.min(CANVAS_WIDTH / newOverlay.image.width, canvasAdjustedHeight / newOverlay.image.height);
221
+ if (initialScaleRatio < 1) {
222
+ newOverlay.scale = initialScaleRatio * 0.9; // ์•ฝ๊ฐ„ ๋” ์ž‘๊ฒŒ ์‹œ์ž‘
223
+ }
224
+
225
+ overlays.push(newOverlay);
226
+ activeOverlayIndex = overlays.length - 1; // ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ๋ ˆ์ด์–ด๋ฅผ ํ™œ์„ฑํ™”
227
+ updateControlPanel();
228
+ updateLayersList();
229
+ }
230
+ drawCanvas();
231
+ };
232
+ img.onerror = function() {
233
+ console.error("Error loading image from reader result for:", fileName);
234
+ alert("์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ํŒŒ์ผ์ด ์œ ํšจํ•œ์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.");
235
+ };
236
+ img.src = event.target.result;
237
+ };
238
+ reader.readAsDataURL(fileOrBlob); // File ๋˜๋Š” Blob์—์„œ Data URL ์ฝ๊ธฐ
239
+ }
240
+
241
+
242
+ function updateControlPanel() {
243
+ const isActive = activeOverlayIndex >= 0;
244
+ const activeOverlay = isActive ? overlays[activeOverlayIndex] : null;
245
+
246
+ // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์„ ํƒ๋˜์ง€ ์•Š์•˜์„ ๋•Œ ์ปจํŠธ๋กค ๋น„ํ™œ์„ฑํ™”
247
+ scaleSlider.disabled = !isActive;
248
+ rotationSlider.disabled = !isActive;
249
+ temperatureSlider.disabled = !isActive;
250
+ brightnessSlider.disabled = !isActive;
251
+ contrastSlider.disabled = !isActive;
252
+ saturationSlider.disabled = !isActive;
253
+ resetFilterBtn.disabled = !isActive;
254
+
255
+
256
+ if (isActive) {
257
+ scaleSlider.value = Math.round(activeOverlay.scale * 100);
258
+ scaleValue.textContent = scaleSlider.value + '%';
259
+ rotationSlider.value = Math.round(activeOverlay.rotation);
260
+ rotationValue.textContent = rotationSlider.value + 'ยฐ';
261
+
262
+ temperatureSlider.value = activeOverlay.filters.temperature;
263
+ temperatureValue.textContent = activeOverlay.filters.temperature;
264
+ brightnessSlider.value = activeOverlay.filters.brightness;
265
+ brightnessValue.textContent = activeOverlay.filters.brightness + '%';
266
+ contrastSlider.value = activeOverlay.filters.contrast;
267
+ contrastValue.textContent = activeOverlay.filters.contrast + '%';
268
+ saturationSlider.value = activeOverlay.filters.saturation;
269
+ saturationValue.textContent = activeOverlay.filters.saturation + '%';
270
+ } else {
271
+ // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์„ ํƒ๋˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํ‘œ์‹œ
272
+ scaleSlider.value = 100;
273
+ scaleValue.textContent = '100%';
274
+ rotationSlider.value = 0;
275
+ rotationValue.textContent = '0ยฐ';
276
+
277
+ temperatureSlider.value = 0;
278
+ temperatureValue.textContent = '0';
279
+ brightnessSlider.value = 100;
280
+ brightnessValue.textContent = '100%';
281
+ contrastSlider.value = 100;
282
+ contrastValue.textContent = '100%';
283
+ saturationSlider.value = 100;
284
+ saturationValue.textContent = '100%';
285
+ }
286
+
287
+ // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ์žˆ์„ ๋•Œ๋งŒ ์ƒ์„ฑ/๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
288
+ generateBtn.disabled = overlays.length === 0;
289
+ downloadBtn.disabled = overlays.length === 0;
290
+
291
+ // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์—†์œผ๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ ์ˆจ๊น€
292
+ if (overlays.length === 0) {
293
+ previewContainer.style.display = 'none';
294
+ }
295
+ }
296
+
297
+ function updateLayersList() {
298
+ if (!layersList) return; // layersList ์š”์†Œ๊ฐ€ ์—†์œผ๋ฉด ํ•จ์ˆ˜ ์ข…๋ฃŒ
299
+ layersList.innerHTML = ''; // ๋ชฉ๋ก ์ดˆ๊ธฐํ™”
300
+
301
+ if (overlays.length === 0) {
302
+ const emptyMsg = document.createElement('div');
303
+ emptyMsg.style.padding = '8px';
304
+ emptyMsg.style.textAlign = 'center';
305
+ emptyMsg.style.color = '#777';
306
+ emptyMsg.textContent = '๋ ˆ์ด์–ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค';
307
+ layersList.appendChild(emptyMsg);
308
+ return;
309
+ }
310
+
311
+ // ๋ ˆ์ด์–ด ๋ชฉ๋ก์„ ์—ญ์ˆœ์œผ๋กœ ํ‘œ์‹œ (๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด๊ฐ€ ๋ชฉ๋ก์˜ ์•„๋ž˜์— ์˜ค๋„๋ก)
312
+ for (let i = overlays.length - 1; i >= 0; i--) {
313
+ const layerItem = document.createElement('div');
314
+ layerItem.className = 'layer-item';
315
+ if (i === activeOverlayIndex) {
316
+ layerItem.classList.add('active'); // ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ‘œ์‹œ
317
+ }
318
+
319
+ const layerName = document.createElement('span');
320
+ layerName.className = 'layer-name';
321
+ layerName.textContent = `๋ ˆ์ด์–ด ${i + 1}`; // ๋ ˆ์ด์–ด ๋ฒˆํ˜ธ ํ‘œ์‹œ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
322
+
323
+ const layerControls = document.createElement('div');
324
+ layerControls.className = 'layer-controls';
325
+
326
+ // ์œ„๋กœ ์ด๋™ ๋ฒ„ํŠผ (๋งจ ์œ„ ๋ ˆ์ด์–ด ์ œ์™ธ)
327
+ if (i < overlays.length - 1) {
328
+ const upButton = document.createElement('button');
329
+ upButton.className = 'layer-button';
330
+ upButton.textContent = 'โ†‘';
331
+ upButton.title = '์œ„๋กœ ์ด๋™';
332
+ upButton.addEventListener('click', (e) => { e.stopPropagation(); moveLayerUp(i); }); // ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง ๋ฐฉ์ง€
333
+ layerControls.appendChild(upButton);
334
+ }
335
+
336
+ // ์•„๋ž˜๋กœ ์ด๋™ ๋ฒ„ํŠผ (๋งจ ์•„๋ž˜ ๋ ˆ์ด์–ด ์ œ์™ธ)
337
+ if (i > 0) {
338
+ const downButton = document.createElement('button');
339
+ downButton.className = 'layer-button';
340
+ downButton.textContent = 'โ†“';
341
+ downButton.title = '์•„๋ž˜๋กœ ์ด๋™';
342
+ downButton.addEventListener('click', (e) => { e.stopPropagation(); moveLayerDown(i); }); // ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง ๋ฐฉ์ง€
343
+ layerControls.appendChild(downButton);
344
+ }
345
+
346
+
347
+ // ๋ ˆ์ด์–ด ํ•ญ๋ชฉ ํด๋ฆญ ์‹œ ํ•ด๋‹น ๋ ˆ์ด์–ด ํ™œ์„ฑํ™”
348
+ layerItem.addEventListener('click', function() {
349
+ activeOverlayIndex = i;
350
+ updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์—…๋ฐ์ดํŠธ
351
+ updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์‹œ๊ฐ์  ์—…๋ฐ์ดํŠธ
352
+ drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ (ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ธ๋“ค ํ‘œ์‹œ)
353
+ });
354
+
355
+ layerItem.appendChild(layerName);
356
+ layerItem.appendChild(layerControls);
357
+
358
+ layersList.appendChild(layerItem);
359
+ }
360
+ }
361
+
362
+ // ๋ ˆ์ด์–ด ์œ„๋กœ ์ด๋™
363
+ function moveLayerUp(index) {
364
+ if (index < overlays.length - 1) {
365
+ // ํ˜„์žฌ ๋ ˆ์ด์–ด์™€ ๋ฐ”๋กœ ์œ„ ๋ ˆ์ด์–ด์˜ ์œ„์น˜๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
366
+ const temp = overlays[index];
367
+ overlays[index] = overlays[index + 1];
368
+ overlays[index + 1] = temp;
369
+
370
+ // ํ™œ์„ฑ ๋ ˆ์ด์–ด์˜ ์ธ๋ฑ์Šค๋„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
371
+ if (activeOverlayIndex === index) activeOverlayIndex = index + 1;
372
+ else if (activeOverlayIndex === index + 1) activeOverlayIndex = index;
373
+
374
+ updateLayersList(); // ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
375
+ drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
376
+ }
377
+ }
378
+
379
+ // ๋ ˆ์ด์–ด ์•„๋ž˜๋กœ ์ด๋™
380
+ function moveLayerDown(index) {
381
+ if (index > 0) {
382
+ // ํ˜„์žฌ ๋ ˆ์ด์–ด์™€ ๋ฐ”๋กœ ์•„๋ž˜ ๋ ˆ์ด์–ด์˜ ์œ„์น˜๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
383
+ const temp = overlays[index];
384
+ overlays[index] = overlays[index - 1];
385
+ overlays[index - 1] = temp;
386
+
387
+ // ํ™œ์„ฑ ๋ ˆ์ด์–ด์˜ ์ธ๋ฑ์Šค๋„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
388
+ if (activeOverlayIndex === index) activeOverlayIndex = index - 1;
389
+ else if (activeOverlayIndex === index - 1) activeOverlayIndex = index;
390
+
391
+
392
+ updateLayersList(); // ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
393
+ drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
394
+ }
395
+ }
396
+
397
+
398
+ function drawCanvas() {
399
+ // ์บ”๋ฒ„์Šค ์ „์ฒด ์ง€์šฐ๊ธฐ
400
+ ctx.clearRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
401
+
402
+ // ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ (์žˆ๋‹ค๋ฉด)
403
+ if (backgroundImage) {
404
+ ctx.drawImage(backgroundImage, 0, 0, backgroundImage.width, backgroundImage.height, bgDrawProps.x, bgDrawProps.y, bgDrawProps.width, bgDrawProps.height);
405
+ } else {
406
+ // ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ์ฑ„์šฐ๊ธฐ
407
+ ctx.fillStyle = '#ffffff';
408
+ ctx.fillRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
409
+ }
410
+
411
+ // ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๋“ค ๊ทธ๋ฆฌ๊ธฐ (๋ ˆ์ด์–ด ์ˆœ์„œ๋Œ€๋กœ)
412
+ overlays.forEach((overlay, index) => {
413
+ // ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.
414
+ if (!overlay.image || overlay.image.width === 0 || overlay.image.height === 0) return;
415
+
416
+ // ํ•„ํ„ฐ ์ ์šฉ๋œ ์ด๋ฏธ์ง€ ์บ”๋ฒ„์Šค ์ƒ์„ฑ (filter.js์˜ ํ•จ์ˆ˜ ์‚ฌ์šฉ)
417
+ const filteredCanvas = ImageFilters.createFilteredCanvas(overlay.image, overlay.filters);
418
+
419
+
420
+ ctx.save(); // ํ˜„์žฌ ์บ”๋ฒ„์Šค ์ƒํƒœ ์ €์žฅ
421
+
422
+ // ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ ๋ณ€ํ™˜ ์ ์šฉ
423
+ ctx.translate(overlay.x, overlay.y); // ์œ„์น˜ ์ด๋™
424
+ ctx.rotate(overlay.rotation * Math.PI / 180); // ํšŒ์ „ (๋ผ๋””์•ˆ์œผ๋กœ ๋ณ€ํ™˜)
425
+ ctx.scale(overlay.scale, overlay.scale); // ํฌ๊ธฐ ์กฐ์ •
426
+
427
+ // ์ด๋ฏธ์ง€๋ฅผ ์ค‘์‹ฌ ๊ธฐ์ค€์œผ๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. (translate์—์„œ ์ด๋™ํ–ˆ์œผ๋ฏ€๋กœ -width/2, -height/2)
428
+ ctx.drawImage(filteredCanvas, -overlay.image.width / 2, -overlay.image.height / 2);
429
+
430
+ ctx.restore(); // ์ด์ „ ์บ”๋ฒ„์Šค ์ƒํƒœ ๋ณต์› (๋ณ€ํ™˜ ์ƒํƒœ ์ดˆ๊ธฐํ™”)
431
+
432
+ // ํ˜„์žฌ ํ™œ์„ฑ ๋ ˆ์ด์–ด์—๋งŒ ๋ณ€ํ™˜ ํ•ธ๋“ค ๊ทธ๋ฆฌ๊ธฐ
433
+ if (index === activeOverlayIndex) {
434
+ drawTransformHandles(overlay);
435
+ }
436
+ });
437
+ }
438
+
439
+ // ๋ณ€ํ™˜ ํ•ธ๋“ค ๊ทธ๋ฆฌ๊ธฐ
440
+ function drawTransformHandles(activeOverlay) {
441
+ // ํ™œ์„ฑ ์˜ค๋ฒ„๋ ˆ์ด๋‚˜ ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ทธ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
442
+ if (!activeOverlay || !activeOverlay.image || activeOverlay.image.width === 0 || activeOverlay.image.height === 0) return;
443
+
444
+ const currentWidth = activeOverlay.image.width * activeOverlay.scale;
445
+ const currentHeight = activeOverlay.image.height * activeOverlay.scale;
446
+ const halfWidth = currentWidth / 2;
447
+ const halfHeight = currentHeight / 2;
448
+
449
+ const angle = activeOverlay.rotation * Math.PI / 180; // ํ˜„์žฌ ํšŒ์ „ ๊ฐ๋„ (๋ผ๋””์•ˆ)
450
+
451
+ // ์ฝ”๋„ˆ ํ•ธ๋“ค ์œ„์น˜ (ํšŒ์ „๋˜์ง€ ์•Š์€ ์ƒํƒœ ๊ธฐ์ค€)
452
+ const handles = {
453
+ 'tl': { x: -halfWidth, y: -halfHeight, cursor: 'nwse-resize' }, // Top-Left
454
+ 'tr': { x: halfWidth, y: -halfHeight, cursor: 'nesw-resize' }, // Top-Right
455
+ 'br': { x: halfWidth, y: halfHeight, cursor: 'nwse-resize' }, // Bottom-Right
456
+ 'bl': { x: -halfWidth, y: halfHeight, cursor: 'nesw-resize' } // Bottom-Left
457
+ };
458
+
459
+ // ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜ (์ด๋ฏธ์ง€ ์ƒ๋‹จ ์ค‘์•™์—์„œ ํšŒ์ „ ๊ฑฐ๋ฆฌ๋งŒํผ ๋–จ์–ด์ง„ ์œ„์น˜)
460
+ const rotateHandlePos = { x: 0, y: -halfHeight - rotateHandleDistance };
461
+
462
+
463
+ ctx.save(); // ํ˜„์žฌ ์บ”๋ฒ„์Šค ์ƒํƒœ ์ €์žฅ
464
+
465
+ // ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ ์œ„์น˜๋กœ ์ด๋™ ํ›„ ํšŒ์ „
466
+ ctx.translate(activeOverlay.x, activeOverlay.y);
467
+ ctx.rotate(angle);
468
+
469
+ // ์ฝ”๋„ˆ ํ•ธ๋“ค ๊ทธ๋ฆฌ๊ธฐ
470
+ const cornerHandleColor = '#e74c3c'; // ๋นจ๊ฐ„์ƒ‰
471
+ for (const [, handle] of Object.entries(handles)) {
472
+ ctx.beginPath();
473
+ // ํ•ธ๋“ค ์œ„์น˜์— ์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ (ํ•ธ๋“ค ํฌ๊ธฐ์˜ ์ ˆ๋ฐ˜๋งŒํผ ๋นผ์„œ ์ค‘์•™์— ์œ„์น˜)
474
+ ctx.rect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
475
+ ctx.fillStyle = cornerHandleColor;
476
+ ctx.fill();
477
+ }
478
+
479
+ // ํšŒ์ „ ํ•ธ๋“ค ์—ฐ๊ฒฐ ์„  ๊ทธ๋ฆฌ๊ธฐ
480
+ ctx.beginPath();
481
+ ctx.moveTo(0, -halfHeight); // ์ด๋ฏธ์ง€ ์ƒ๋‹จ ์ค‘์•™
482
+ ctx.lineTo(rotateHandlePos.x, rotateHandlePos.y); // ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜
483
+ ctx.strokeStyle = '#3498db'; // ํŒŒ๋ž€์ƒ‰
484
+ ctx.lineWidth = 2;
485
+ ctx.stroke();
486
+
487
+ // ํšŒ์ „ ํ•ธ๋“ค ์› ๊ทธ๋ฆฌ๊ธฐ
488
+ ctx.beginPath();
489
+ ctx.arc(rotateHandlePos.x, rotateHandlePos.y, handleSize / 2, 0, Math.PI * 2); // ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜์— ์› ๊ทธ๋ฆฌ๊ธฐ
490
+ ctx.fillStyle = '#3498db'; // ํŒŒ๋ž€์ƒ‰
491
+ ctx.fill();
492
+
493
+ ctx.restore(); // ์ด์ „ ์บ”๋ฒ„์Šค ์ƒํƒœ ๋ณต์›
494
+ }
495
+
496
+
497
+ // ์ฃผ์–ด์ง„ ์บ”๋ฒ„์Šค ์ขŒํ‘œ์—์„œ ์–ด๋–ค ํ•ธ๋“ค์ด ์žˆ๋Š”์ง€ ํ™•์ธ
498
+ function getHandleAtPosition(canvasX, canvasY, activeOverlay) {
499
+ // ํ™œ์„ฑ ์˜ค๋ฒ„๋ ˆ์ด๋‚˜ ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ํ•ธ๋“ค ์—†์Œ
500
+ if (!activeOverlay || !activeOverlay.image || activeOverlay.image.width === 0 || activeOverlay.image.height === 0) return null;
501
+
502
+ const currentWidth = activeOverlay.image.width * activeOverlay.scale;
503
+ const currentHeight = activeOverlay.image.height * activeOverlay.scale;
504
+ const halfWidth = currentWidth / 2;
505
+ const halfHeight = currentHeight / 2;
506
+
507
+ // ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ๋ฅผ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜๊ณ  ํšŒ์ „์„ ๋˜๋Œ๋ฆฝ๋‹ˆ๋‹ค.
508
+ const dx = canvasX - activeOverlay.x;
509
+ const dy = canvasY - activeOverlay.y;
510
+ const angle = -activeOverlay.rotation * Math.PI / 180; // ํšŒ์ „์„ ๋˜๋Œ๋ฆฌ๋Š” ๊ฐ๋„
511
+ const cos = Math.cos(angle);
512
+ const sin = Math.sin(angle);
513
+
514
+ // ํšŒ์ „๋œ ์ขŒํ‘œ
515
+ const rotatedX = dx * cos - dy * sin;
516
+ const rotatedY = dx * sin + dy * cos;
517
+
518
+
519
+ // ์ฝ”๋„ˆ ํ•ธ๋“ค ์œ„์น˜ (ํšŒ์ „๋˜์ง€ ์•Š์€ ์ƒํƒœ ๊ธฐ์ค€)
520
+ const handles = {
521
+ 'tl': { x: -halfWidth, y: -halfHeight, cursor: 'nwse-resize' },
522
+ 'tr': { x: halfWidth, y: -halfHeight, cursor: 'nesw-resize' },
523
+ 'br': { x: halfWidth, y: halfHeight, cursor: 'nwse-resize' },
524
+ 'bl': { x: -halfWidth, y: halfHeight, cursor: 'nesw-resize' }
525
+ };
526
+
527
+ // ๊ฐ ์ฝ”๋„ˆ ํ•ธ๋“ค ๊ทผ์ฒ˜์— ํด๋ฆญ/ํ„ฐ์น˜ํ–ˆ๋Š”์ง€ ํ™•์ธ
528
+ for (const [key, handle] of Object.entries(handles)) {
529
+ const dist = Math.sqrt(Math.pow(rotatedX - handle.x, 2) + Math.pow(rotatedY - handle.y, 2));
530
+ // ํด๋ฆญ ํŒจ๋”ฉ์„ ๋”ํ•˜์—ฌ ํด๋ฆญ/ํ„ฐ์น˜ ์˜์—ญ์„ ์•ฝ๊ฐ„ ๋„“๊ฒŒ ์žก์Šต๋‹ˆ๋‹ค.
531
+ if (dist <= handleSize / 2 + clickPadding) {
532
+ return { id: key, cursor: handle.cursor }; // ํ•ด๋‹น ํ•ธ๋“ค์˜ ID์™€ ์ปค์„œ ๋ชจ์–‘ ๋ฐ˜ํ™˜
533
+ }
534
+ }
535
+
536
+ // ํšŒ์ „ ํ•ธ๋“ค ์œ„์น˜ (ํšŒ์ „๋˜์ง€ ์•Š์€ ์ƒํƒœ ๊ธฐ์ค€)
537
+ const rotateHandlePos = { x: 0, y: -halfHeight - rotateHandleDistance };
538
+ const rotateDist = Math.sqrt(Math.pow(rotatedX - rotateHandlePos.x, 2) + Math.pow(rotatedY - rotateHandlePos.y, 2));
539
+
540
+ // ํšŒ์ „ ํ•ธ๋“ค ๊ทผ์ฒ˜์— ํด๋ฆญ/ํ„ฐ์น˜ํ–ˆ๋Š”์ง€ ํ™•์ธ
541
+ if (rotateDist <= handleSize / 2 + clickPadding) {
542
+ return { id: 'rotate', cursor: 'grab' }; // ํšŒ์ „ ํ•ธ๋“ค ID์™€ ์ปค์„œ ๋ชจ์–‘ ๋ฐ˜ํ™˜
543
+ }
544
+
545
+ return null; // ์–ด๋–ค ํ•ธ๋“ค๋„ ํด๋ฆญ/ํ„ฐ์น˜๋˜์ง€ ์•Š์Œ
546
+ }
547
+
548
+
549
+ // ์ฃผ์–ด์ง„ ์บ”๋ฒ„์Šค ์ขŒํ‘œ๊ฐ€ ํŠน์ • ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์˜์—ญ ์œ„์— ์žˆ๋Š”์ง€ ํ™•์ธ
550
+ function isOverOverlayImage(canvasX, canvasY, overlay) {
551
+ // ์˜ค๋ฒ„๋ ˆ์ด๋‚˜ ์ด๋ฏธ์ง€๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด false ๋ฐ˜ํ™˜
552
+ if (!overlay || !overlay.image || overlay.image.width === 0 || overlay.image.height === 0) return false;
553
+
554
+ const currentWidth = overlay.image.width * overlay.scale;
555
+ const currentHeight = overlay.image.height * overlay.scale;
556
+ const halfWidth = currentWidth / 2;
557
+ const halfHeight = currentHeight / 2;
558
+
559
+ // ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ๋ฅผ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€์˜ ์ค‘์‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜๊ณ  ํšŒ์ „์„ ๋˜๋Œ๋ฆฝ๋‹ˆ๋‹ค.
560
+ const dx = canvasX - overlay.x;
561
+ const dy = canvasY - overlay.y;
562
+ const angle = -overlay.rotation * Math.PI / 180; // ํšŒ์ „์„ ๋˜๋Œ๋ฆฌ๋Š” ๊ฐ๋„
563
+ const cos = Math.cos(angle);
564
+ const sin = Math.sin(angle);
565
+
566
+ // ํšŒ์ „๋œ ์ขŒํ‘œ
567
+ const rotatedX = dx * cos - dy * sin;
568
+ const rotatedY = dx * sin + dy * cos;
569
+
570
+ // ํšŒ์ „๋œ ์ขŒํ‘œ๊ฐ€ ์ด๋ฏธ์ง€ ๊ฒฝ๊ณ„ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธ (ํด๋ฆญ ํŒจ๋”ฉ ์ ์šฉ)
571
+ return (rotatedX >= -halfWidth - clickPadding && rotatedX <= halfWidth + clickPadding &&
572
+ rotatedY >= -halfHeight - clickPadding && rotatedY <= halfHeight + clickPadding);
573
+ }
574
+
575
+
576
+ // ๋งˆ์šฐ์Šค ์ปค์„œ ๋ชจ์–‘ ์—…๋ฐ์ดํŠธ
577
+ function updateCursor(canvasX, canvasY) {
578
+ // ๋“œ๋ž˜๊ทธ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์—๋Š” ์ปค์„œ ๋ชจ์–‘์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
579
+ if (isDragging || isRotating || isResizing) return;
580
+
581
+ let cursor = 'default'; // ๊ธฐ๋ณธ ์ปค์„œ ๋ชจ์–‘
582
+ let handled = false; // ํ•ธ๋“ค์ด๋‚˜ ์ด๋ฏธ์ง€ ์œ„์— ์žˆ๋Š”์ง€ ์—ฌ๋ถ€
583
+
584
+ // ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ธ๋“ค ์œ„์— ์žˆ๋Š”์ง€ ๋จผ์ € ํ™•์ธ
585
+ if (activeOverlayIndex >= 0) {
586
+ const activeOverlay = overlays[activeOverlayIndex];
587
+ const handle = getHandleAtPosition(canvasX, canvasY, activeOverlay);
588
+ if (handle) {
589
+ cursor = handle.cursor; // ํ•ธ๋“ค์— ๋งž๋Š” ์ปค์„œ ๋ชจ์–‘ ์„ค์ •
590
+ handled = true;
591
+ }
592
+ }
593
+
594
+ // ํ•ธ๋“ค ์œ„์— ์žˆ์ง€ ์•Š๋‹ค๋ฉด, ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์œ„์— ์žˆ๋Š”์ง€ ํ™•์ธ (๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด๋ถ€ํ„ฐ)
595
+ if (!handled) {
596
+ for (let i = overlays.length - 1; i >= 0; i--) {
597
+ if (isOverOverlayImage(canvasX, canvasY, overlays[i])) {
598
+ cursor = 'move'; // ์ด๋™ ์ปค์„œ ์„ค์ •
599
+ handled = true;
600
+ break; // ํ•˜๋‚˜๋ผ๋„ ๋ฐœ๊ฒฌํ•˜๋ฉด ์ค‘์ง€
601
+ }
602
+ }
603
+ }
604
+
605
+ canvas.style.cursor = cursor; // ์บ”๋ฒ„์Šค ์ปค์„œ ๋ชจ์–‘ ์ ์šฉ
606
+ }
607
+
608
+
609
+ // ํ•ฉ์„ฑ๋œ ์ตœ์ข… ์ด๋ฏธ์ง€ ์ƒ์„ฑ
610
+ function generateMergedImage() {
611
+ // ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
612
+ if (overlays.length === 0) {
613
+ alert('ํ•ฉ์„ฑํ•  ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.');
614
+ return null;
615
+ }
616
+
617
+ // ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ฆด ์ƒˆ ์บ”๋ฒ„์Šค ์ƒ์„ฑ
618
+ const resultCanvas = document.createElement('canvas');
619
+ resultCanvas.width = CANVAS_WIDTH;
620
+ resultCanvas.height = canvasAdjustedHeight;
621
+ const resultCtx = resultCanvas.getContext('2d');
622
+
623
+ // ์บ”๋ฒ„์Šค ์ดˆ๊ธฐํ™” (๋ฐฐ๊ฒฝ ์ฑ„์šฐ๊ธฐ)
624
+ resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);
625
+ if (backgroundImage) {
626
+ resultCtx.drawImage(backgroundImage, 0, 0, backgroundImage.width, backgroundImage.height, bgDrawProps.x, bgDrawProps.y, bgDrawProps.width, bgDrawProps.height);
627
+ } else {
628
+ resultCtx.fillStyle = '#ffffff';
629
+ resultCtx.fillRect(0, 0, CANVAS_WIDTH, canvasAdjustedHeight);
630
+ }
631
+
632
+ // ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๋“ค์„ ์ˆœ์„œ๋Œ€๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
633
+ overlays.forEach(overlay => {
634
+ if (!overlay.image || overlay.image.width === 0 || overlay.image.height === 0) return;
635
+
636
+ // ํ•„ํ„ฐ ์ ์šฉ๋œ ์ด๋ฏธ์ง€ ์บ”๋ฒ„์Šค ์ƒ์„ฑ
637
+ const filteredCanvas = ImageFilters.createFilteredCanvas(overlay.image, overlay.filters);
638
+
639
+ resultCtx.save(); // ํ˜„์žฌ ์ƒํƒœ ์ €์žฅ
640
+
641
+ // ์˜ค๋ฒ„๋ ˆ์ด ์œ„์น˜, ํšŒ์ „, ์Šค์ผ€์ผ ์ ์šฉ
642
+ resultCtx.translate(overlay.x, overlay.y);
643
+ resultCtx.rotate(overlay.rotation * Math.PI / 180);
644
+ resultCtx.scale(overlay.scale, overlay.scale);
645
+
646
+ // ์ด๋ฏธ์ง€๋ฅผ ์ค‘์‹ฌ ๊ธฐ์ค€์œผ๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
647
+ resultCtx.drawImage(filteredCanvas, -overlay.image.width / 2, -overlay.image.height / 2);
648
+
649
+ resultCtx.restore(); // ์ƒํƒœ ๋ณต์›
650
+ });
651
+
652
+ // ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ํ‘œ์‹œ
653
+ previewImg.src = resultCanvas.toDataURL(`image/${originalFormat === 'jpg' ? 'jpeg' : originalFormat}`);
654
+ previewContainer.style.display = 'flex'; // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ปจํ…Œ์ด๋„ˆ ๋ณด์ด๊ฒŒ ํ•จ
655
+
656
+ return resultCanvas; // ๊ฒฐ๊ณผ ์บ”๋ฒ„์Šค ๋ฐ˜ํ™˜
657
+ }
658
+
659
+ // ์„ ํƒ๋œ ๋ ˆ์ด์–ด ์‚ญ์ œ
660
+ function deleteSelectedLayer() {
661
+ if (activeOverlayIndex >= 0) {
662
+ // ํ•ด๋‹น ์ธ๋ฑ์Šค์˜ ๋ ˆ์ด์–ด ์‚ญ์ œ
663
+ overlays.splice(activeOverlayIndex, 1);
664
+ activeOverlayIndex = -1; // ํ™œ์„ฑ ๋ ˆ์ด์–ด ์—†์Œ์œผ๋กœ ์„ค์ •
665
+
666
+ updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์—…๋ฐ์ดํŠธ
667
+ updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
668
+ drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
669
+
670
+ // ๋ชจ๋“  ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์‚ญ์ œ๋˜๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ˆจ๊น€
671
+ if (overlays.length === 0) {
672
+ previewContainer.style.display = 'none';
673
+ }
674
+ // else {
675
+ // // ์‚ญ์ œ ํ›„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์—…๋ฐ์ดํŠธ (์„ ํƒ ์‚ฌํ•ญ)
676
+ // generateMergedImage();
677
+ // }
678
+ }
679
+ }
680
+
681
+ // ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ด๋ฒคํŠธ์˜ ํด๋ผ์ด์–ธํŠธ ์ขŒํ‘œ๋ฅผ ์บ”๋ฒ„์Šค ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
682
+ function getCanvasCoordinates(clientX, clientY) {
683
+ const rect = canvas.getBoundingClientRect(); // ์บ”๋ฒ„์Šค์˜ ํ™”๋ฉด์ƒ ์œ„์น˜์™€ ํฌ๊ธฐ
684
+ const scaleX = canvas.width / rect.width; // ๊ฐ€๋กœ ์Šค์ผ€์ผ ๋น„์œจ
685
+ const scaleY = canvas.height / rect.height; // ์„ธ๋กœ ์Šค์ผ€์ผ ๋น„์œจ
686
+
687
+ // ํด๋ผ์ด์–ธํŠธ ์ขŒํ‘œ๋ฅผ ์บ”๋ฒ„์Šค ๋‚ด๋ถ€ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
688
+ const x = (clientX - rect.left) * scaleX;
689
+ const y = (clientY - rect.top) * scaleY;
690
+
691
+ return { x, y };
692
+ }
693
+
694
+
695
+ // ํ˜„์žฌ ํ•„ํ„ฐ ๊ฐ’์„ ํ™œ์„ฑ ๋ ˆ์ด์–ด์— ์ ์šฉํ•˜๊ณ  ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
696
+ function applyCurrentFilters() {
697
+ if (activeOverlayIndex < 0) return; // ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์—†์œผ๋ฉด ์ ์šฉํ•˜์ง€ ์•Š์Œ
698
+
699
+ const filterValues = {
700
+ temperature: parseInt(temperatureSlider.value),
701
+ brightness: parseInt(brightnessSlider.value),
702
+ contrast: parseInt(contrastSlider.value),
703
+ saturation: parseInt(saturationSlider.value)
704
+ };
705
+
706
+ // filter.js์˜ applyFilterToLayer ํ•จ์ˆ˜ ํ˜ธ์ถœ
707
+ ImageFilters.applyFilterToLayer(overlays, activeOverlayIndex, filterValues);
708
+
709
+ drawCanvas(); // ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•˜์—ฌ ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๏ฟฝ๏ฟฝ๊ธฐ
710
+ }
711
+
712
+
713
+ // --- ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ---
714
+
715
+ // DOMContentLoaded ์ด๋ฒคํŠธ: ๋ฌธ์„œ ๋กœ๋“œ ํ›„ ์ดˆ๊ธฐํ™” ๋ฐ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
716
+ document.addEventListener('DOMContentLoaded', () => {
717
+
718
+ // ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
719
+ backgroundInput.addEventListener('change', function(e) {
720
+ if (e.target.files.length > 0) {
721
+ loadImage(e.target.files[0], 'background');
722
+ }
723
+ });
724
+
725
+ // ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
726
+ overlayInput.addEventListener('change', function(e) {
727
+ if (e.target.files.length > 0) {
728
+ loadImage(e.target.files[0], 'overlay');
729
+ this.value = ''; // ํŒŒ์ผ ์„ ํƒ ํ›„ input ๊ฐ’ ์ดˆ๊ธฐํ™” (๊ฐ™์€ ํŒŒ์ผ ๋‹ค์‹œ ์„ ํƒ ๊ฐ€๋Šฅํ•˜๊ฒŒ)
730
+ }
731
+ });
732
+
733
+ // ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
734
+ const exampleImagesContainer = document.getElementById('example-images-container'); // ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ ID
735
+ if (exampleImagesContainer) {
736
+ exampleImagesContainer.querySelectorAll('.example-img').forEach(img => {
737
+ img.addEventListener('click', function() {
738
+ const imageUrl = this.src;
739
+ const fileName = this.getAttribute('data-filename') || 'example_image.png';
740
+
741
+ // ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ๋กœ๋“œ
742
+ const exampleImgElement = new Image();
743
+ exampleImgElement.crossOrigin = 'anonymous'; // CORS ๋ฌธ์ œ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
744
+ exampleImgElement.onload = function() {
745
+ // TODO: ์—ฌ๊ธฐ์—์„œ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ๋กœ์ง์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
746
+ // ์ œ๊ณต๋œ cropTransparentPixels ํ•จ์ˆ˜๋Š” ํˆฌ๋ช… ์˜์—ญ์„ ์ž๋ฅด๋Š” ๋ฐ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
747
+ // ๋งŒ์•ฝ ์˜ˆ์‹œ ์ด๋ฏธ์ง€๊ฐ€ ์ด๋ฏธ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€๋ผ๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.
748
+ // ์ผ๋ฐ˜ ์ด๋ฏธ์ง€์˜ ๋ณต์žกํ•œ ๋ฐฐ๊ฒฝ์„ ์ œ๊ฑฐํ•˜๋ ค๋ฉด ๋ณ„๋„์˜ ๊ณ ๊ธ‰ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋กœ์ง์ด๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
749
+
750
+ let processedImageSource = exampleImgElement; // ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ์›๋ณธ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
751
+
752
+ try {
753
+ // ํˆฌ๋ช… ํ”ฝ์…€ ์ž๋ฅด๊ธฐ ์‹œ๋„ (๋ฐฐ๊ฒฝ ์ œ๊ฑฐ๋œ ์ด๋ฏธ์ง€์— ์œ ์šฉ)
754
+ const croppedCanvas = cropTransparentPixels(exampleImgElement);
755
+ if (croppedCanvas.width > 0 && croppedCanvas.height > 0) {
756
+ processedImageSource = croppedCanvas;
757
+ } else {
758
+ console.warn("cropTransparentPixels resulted in empty canvas, using original image.");
759
+ processedImageSource = exampleImgElement;
760
+ }
761
+ } catch (e) {
762
+ console.error("Error during cropTransparentPixels, using original image:", e);
763
+ processedImageSource = exampleImgElement;
764
+ }
765
+
766
+
767
+ // ์ฒ˜๋ฆฌ๋œ ์ด๋ฏธ์ง€๋ฅผ Blob์œผ๋กœ ๋ณ€ํ™˜
768
+ // cropTransparentPixels๊ฐ€ ์บ”๋ฒ„์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ toBlob ์‚ฌ์šฉ
769
+ const canvasToProcess = processedImageSource instanceof HTMLCanvasElement ? processedImageSource : null;
770
+
771
+ if (canvasToProcess) {
772
+ canvasToProcess.toBlob(function(blob) {
773
+ if (blob) {
774
+ // Blob์„ ์‚ฌ์šฉํ•˜์—ฌ loadImage ํ•จ์ˆ˜ ํ˜ธ์ถœ (loadImage ํ•จ์ˆ˜๊ฐ€ Blob์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ˆ˜์ •๋จ)
775
+ loadImage(blob, 'overlay');
776
+ } else {
777
+ console.error("Failed to create blob from processed canvas.");
778
+ alert("์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
779
+ }
780
+ }, 'image/png'); // ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ์‹œ ํˆฌ๋ช…๋„๋ฅผ ์œ„ํ•ด PNG ํฌ๋งท ๊ถŒ์žฅ
781
+ } else if (processedImageSource instanceof HTMLImageElement) {
782
+ // Canvas๊ฐ€ ์•„๋‹Œ Image ์š”์†Œ์ธ ๊ฒฝ์šฐ (e.g., cropTransparentPixels ์˜ค๋ฅ˜ ์‹œ)
783
+ // Data URL๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ Blob ์ƒ์„ฑ ํ›„ loadImage ํ˜ธ์ถœ
784
+ const tempCanvas = document.createElement('canvas');
785
+ tempCanvas.width = processedImageSource.width;
786
+ tempCanvas.height = processedImageSource.height;
787
+ const tempCtx = tempCanvas.getContext('2d');
788
+ tempCtx.drawImage(processedImageSource, 0, 0);
789
+ tempCanvas.toBlob(function(blob) {
790
+ if (blob) {
791
+ loadImage(blob, 'overlay');
792
+ } else {
793
+ console.error("Failed to create blob from fallback image.");
794
+ alert("์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
795
+ }
796
+ }, 'image/png');
797
+ } else {
798
+ console.error("Processed image source is neither Canvas nor Image.");
799
+ alert("์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
800
+ }
801
+
802
+ };
803
+ exampleImgElement.onerror = function() {
804
+ console.error("Error loading example image:", imageUrl);
805
+ alert("์˜ˆ์‹œ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
806
+ };
807
+ exampleImgElement.src = imageUrl; // ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹œ์ž‘
808
+ });
809
+ });
810
+ }
811
+
812
+
813
+ // Delete ํ‚ค ๋ˆŒ๋ €์„ ๋•Œ ์„ ํƒ๋œ ๋ ˆ์ด์–ด ์‚ญ์ œ
814
+ document.addEventListener('keydown', function(e) {
815
+ // Ctrl ๋˜๋Š” Cmd ํ‚ค์™€ ํ•จ๊ป˜ ๋ˆŒ๋ ธ์„ ๋•Œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ์กฐ๊ฑด ์ถ”๊ฐ€ (์‹ค์ˆ˜ ๋ฐฉ์ง€)
816
+ if (e.key === 'Delete' && activeOverlayIndex >= 0) {
817
+ e.preventDefault(); // ๊ธฐ๋ณธ ๋™์ž‘(๋’ค๋กœ๊ฐ€๊ธฐ ๋“ฑ) ๋ฐฉ์ง€
818
+ deleteSelectedLayer();
819
+ }
820
+ });
821
+
822
+ // ํฌ๊ธฐ ์กฐ์ ˆ ์Šฌ๋ผ์ด๋” ์ด๋ฒคํŠธ
823
+ scaleSlider.addEventListener('input', function() {
824
+ if (activeOverlayIndex >= 0) {
825
+ overlays[activeOverlayIndex].scale = parseInt(this.value) / 100;
826
+ scaleValue.textContent = this.value + '%';
827
+ drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
828
+ }
829
+ });
830
+
831
+ // ํšŒ์ „ ์Šฌ๋ผ์ด๋” ์ด๋ฒคํŠธ
832
+ rotationSlider.addEventListener('input', function() {
833
+ if (activeOverlayIndex >= 0) {
834
+ overlays[activeOverlayIndex].rotation = parseInt(this.value);
835
+ rotationValue.textContent = overlays[activeOverlayIndex].rotation + 'ยฐ';
836
+ drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
837
+ }
838
+ });
839
+
840
+ // ํ•„ํ„ฐ ์Šฌ๋ผ์ด๋” ์ด๋ฒคํŠธ
841
+ temperatureSlider.addEventListener('input', function() { temperatureValue.textContent = this.value; applyCurrentFilters(); });
842
+ brightnessSlider.addEventListener('input', function() { brightnessValue.textContent = this.value + '%'; applyCurrentFilters(); });
843
+ contrastSlider.addEventListener('input', function() { contrastValue.textContent = this.value + '%'; applyCurrentFilters(); });
844
+ saturationSlider.addEventListener('input', function() { saturationValue.textContent = this.value + '%'; applyCurrentFilters(); });
845
+
846
+ // ํ•„ํ„ฐ ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
847
+ resetFilterBtn.addEventListener('click', function() {
848
+ // filter.js์˜ resetFilters ํ•จ์ˆ˜ ํ˜ธ์ถœ
849
+ const defaultFilters = ImageFilters.resetFilters(overlays, activeOverlayIndex);
850
+ // ์Šฌ๋ผ์ด๋” ๊ฐ’๊ณผ ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
851
+ temperatureSlider.value = defaultFilters.temperature; temperatureValue.textContent = defaultFilters.temperature;
852
+ brightnessSlider.value = defaultFilters.brightness; brightnessValue.textContent = defaultFilters.brightness + '%';
853
+ contrastSlider.value = defaultFilters.contrast; contrastValue.textContent = defaultFilters.contrast + '%';
854
+ saturationSlider.value = defaultFilters.saturation; saturationValue.textContent = defaultFilters.saturation + '%';
855
+ drawCanvas(); // ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๋ฐ˜์˜
856
+ });
857
+
858
+
859
+ // ์บ”๋ฒ„์Šค ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
860
+ canvas.addEventListener('mousedown', function(e) {
861
+ // ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋ฒ„ํŠผ๋งŒ ์ฒ˜๋ฆฌ
862
+ if (e.button !== 0) return;
863
+
864
+ const { x, y } = getCanvasCoordinates(e.clientX, e.clientY);
865
+ const isAltPressed = e.altKey; // Alt ํ‚ค ๋ˆŒ๋ฆผ ์—ฌ๋ถ€ ํ™•์ธ
866
+
867
+ let clickedOverlayIndex = -1; // ํด๋ฆญ๋œ ์˜ค๋ฒ„๋ ˆ์ด ์ธ๋ฑ์Šค
868
+ let handle = null; // ํด๋ฆญ๋œ ํ•ธ๋“ค
869
+
870
+ // ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ๋จผ์ € ํ•ด๋‹น ๋ ˆ์ด์–ด์˜ ํ•ธ๋“ค ํด๋ฆญ ์—ฌ๋ถ€ ํ™•์ธ
871
+ if (activeOverlayIndex >= 0) {
872
+ handle = getHandleAtPosition(x, y, overlays[activeOverlayIndex]);
873
+ if (handle) {
874
+ clickedOverlayIndex = activeOverlayIndex; // ํ•ธ๋“ค์ด ํด๋ฆญ๋˜๋ฉด ํ•ด๋‹น ๋ ˆ์ด์–ด ์„ ํƒ
875
+ }
876
+ }
877
+
878
+ // ํ•ธ๋“ค์ด ํด๋ฆญ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์ž์ฒด ํด๋ฆญ ์—ฌ๋ถ€ ํ™•์ธ
879
+ if (!handle) {
880
+ let overlaysAtPosition = []; // ํด๋ฆญ๋œ ์œ„์น˜์— ์žˆ๋Š” ๋ชจ๋“  ์˜ค๋ฒ„๋ ˆ์ด ๋ชฉ๋ก
881
+ // ๋ชจ๋“  ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ํด๋ฆญ๋œ ์œ„์น˜์— ํ•ด๋‹นํ•˜๋Š” ์˜ค๋ฒ„๋ ˆ์ด ์ฐพ๊ธฐ
882
+ for (let i = 0; i < overlays.length; i++) {
883
+ if (isOverOverlayImage(x, y, overlays[i])) {
884
+ overlaysAtPosition.push(i);
885
+ }
886
+ }
887
+
888
+ if (overlaysAtPosition.length > 0) {
889
+ // ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ๊ฒน์ณ ์žˆ๋‹ค๋ฉด Alt ํ‚ค ๋ˆŒ๋ฆผ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌ
890
+ if (isAltPressed && overlaysAtPosition.length > 1) {
891
+ // Alt ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ๊ณ  ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๊ฒน์ณ ์žˆ๋‹ค๋ฉด, ํ˜„์žฌ ์„ ํƒ๋œ ๋ ˆ์ด์–ด ๋‹ค์Œ ์ˆœ์„œ์˜ ๋ ˆ์ด์–ด๋ฅผ ์„ ํƒ
892
+ if (activeOverlayIndex >= 0) {
893
+ const currentIndexInAtPosition = overlaysAtPosition.indexOf(activeOverlayIndex);
894
+ if (currentIndexInAtPosition !== -1) {
895
+ const nextIndexInAtPosition = (currentIndexInAtPosition + 1) % overlaysAtPosition.length;
896
+ clickedOverlayIndex = overlaysAtPosition[nextIndexInAtPosition];
897
+ } else {
898
+ // ํ˜„์žฌ ์„ ํƒ๋œ ๋ ˆ์ด์–ด๊ฐ€ ํด๋ฆญ๋œ ์œ„์น˜์— ์—†์œผ๋ฉด ๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด ์„ ํƒ (๊ธฐ๋ณธ ๋™์ž‘)
899
+ clickedOverlayIndex = overlaysAtPosition[overlaysAtPosition.length - 1];
900
+ }
901
+ } else {
902
+ // Alt ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ์ง€๋งŒ ํ˜„์žฌ ์„ ํƒ๋œ ๋ ˆ์ด์–ด๊ฐ€ ์—†์œผ๋ฉด ๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด ์„ ํƒ
903
+ clickedOverlayIndex = overlaysAtPosition[overlaysAtPosition.length - 1];
904
+ }
905
+ } else {
906
+ // Alt ํ‚ค๊ฐ€ ๋ˆŒ๋ฆฌ์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ํ•˜๋‚˜๋งŒ ๊ฒน์ณ ์žˆ๋‹ค๋ฉด, ๊ฐ€์žฅ ์œ„์— ์žˆ๋Š” ๋ ˆ์ด์–ด ์„ ํƒ
907
+ clickedOverlayIndex = overlaysAtPosition[overlaysAtPosition.length - 1];
908
+ }
909
+ }
910
+ }
911
+
912
+
913
+ // ํด๋ฆญ๋œ ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ํ™œ์„ฑ ๋ ˆ์ด์–ด๋กœ ์„ค์ •ํ•˜๊ณ  ์ƒํƒœ ์—…๋ฐ์ดํŠธ
914
+ if (clickedOverlayIndex >= 0) {
915
+ const activeOverlay = overlays[clickedOverlayIndex];
916
+ // ์ด๋ฏธ ํ™œ์„ฑ ๋ ˆ์ด์–ด์ธ ๊ฒฝ์šฐ๋Š” ์ œ์™ธ
917
+ if (activeOverlayIndex !== clickedOverlayIndex) {
918
+ activeOverlayIndex = clickedOverlayIndex;
919
+ updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์—…๋ฐ์ดํŠธ
920
+ updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
921
+ }
922
+
923
+ // ํด๋ฆญ๋œ ๊ฒƒ์ด ํ•ธ๋“ค์ด๋ฉด ํ•ด๋‹น ์ž‘์—… ์‹œ์ž‘
924
+ if (handle) {
925
+ if (handle.id === 'rotate') {
926
+ isRotating = true;
927
+ canvas.style.cursor = 'grabbing';
928
+ } else {
929
+ isResizing = true;
930
+ activeHandle = handle.id; // ํ™œ์„ฑ ํ•ธ๋“ค ID ์„ค์ •
931
+ // ๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ์ž‘ ์‹œ ๋งˆ์šฐ์Šค ์œ„์น˜์™€ ์˜ค๋ฒ„๋ ˆ์ด ์ค‘์‹ฌ ๊ฐ„์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ (๋น„๋ก€ ์Šค์ผ€์ผ๋ง์— ์‚ฌ์šฉ)
932
+ const dxMouse = x - activeOverlay.x;
933
+ const dyMouse = y - activeOverlay.y;
934
+ resizeStartDistance = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);
935
+ if (resizeStartDistance < 10) resizeStartDistance = 10; // ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ์ตœ์†Œ๊ฐ’ ์„ค์ •
936
+ }
937
+ } else {
938
+ // ํ•ธ๋“ค์ด ์•„๋‹Œ ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€ ์ž์ฒด๋ฅผ ํด๋ฆญํ•œ ๊ฒฝ์šฐ ๋“œ๋ž˜๊ทธ ์‹œ์ž‘
939
+ isDragging = true;
940
+ canvas.style.cursor = 'grabbing'; // ๋“œ๋ž˜๊ทธ ์ปค์„œ ๋ชจ์–‘
941
+ }
942
+ } else {
943
+ // ์˜ค๋ฒ„๋ ˆ์ด ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹Œ ๋นˆ ๊ณต๊ฐ„์„ ํด๋ฆญํ•œ ๊ฒฝ์šฐ ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ด์ œ
944
+ activeOverlayIndex = -1;
945
+ updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์ดˆ๊ธฐํ™”
946
+ updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ (ํ™œ์„ฑ ํ‘œ์‹œ ์ œ๊ฑฐ)
947
+ }
948
+
949
+ // ๋งˆ์ง€๋ง‰ ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ ์ €์žฅ
950
+ lastX = x;
951
+ lastY = y;
952
+
953
+ drawCanvas(); // ์บ”๋ฒ„์Šค ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ (ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ธ๋“ค ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ)
954
+ });
955
+
956
+ canvas.addEventListener('mousemove', function(e) {
957
+ const { x, y } = getCanvasCoordinates(e.clientX, e.clientY);
958
+
959
+ // ๋“œ๋ž˜๊ทธ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์ด ์•„๋‹ˆ๋ฉด ์ปค์„œ ๋ชจ์–‘ ์—…๋ฐ์ดํŠธ
960
+ if (!isDragging && !isRotating && !isResizing) {
961
+ updateCursor(x, y);
962
+ } else if (activeOverlayIndex >= 0) { // ๋“œ๋ž˜๊ทธ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์ด๊ณ  ํ™œ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด
963
+ const activeOverlay = overlays[activeOverlayIndex];
964
+ canvas.style.cursor = 'grabbing'; // ์ž‘์—… ์ค‘์—๋Š” ์žก๋Š” ์ปค์„œ ์œ ์ง€
965
+
966
+ if (isRotating) {
967
+ // ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํšŒ์ „ ๊ฐ๋„ ๊ณ„์‚ฐ
968
+ const dx = x - activeOverlay.x;
969
+ const dy = y - activeOverlay.y;
970
+ let angle = Math.atan2(dy, dx) * 180 / Math.PI + 90; // atan2 ๊ฒฐ๊ณผ๋Š” -180~180, +90ํ•˜์—ฌ 0~360์œผ๋กœ ๋ณ€ํ™˜
971
+ angle = (angle % 360 + 360) % 360; // ๊ฐ๋„๋ฅผ 0~360 ๋ฒ”์œ„๋กœ ์œ ์ง€
972
+ activeOverlay.rotation = angle;
973
+
974
+ // ํšŒ์ „ ์Šฌ๋ผ์ด๋” ๋ฐ ๊ฐ’ ์—…๋ฐ์ดํŠธ
975
+ rotationSlider.value = Math.round(activeOverlay.rotation);
976
+ rotationValue.textContent = Math.round(activeOverlay.rotation) + 'ยฐ';
977
+
978
+ } else if (isResizing && activeOverlay.image) {
979
+ // ๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘์ด๊ณ  ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด
980
+ const dxMouse = x - activeOverlay.x;
981
+ const dyMouse = y - activeOverlay.y;
982
+ const currentDistance = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);
983
+
984
+ if (resizeStartDistance > 0 && currentDistance > 0) {
985
+ // ๋งˆ์šฐ์Šค ์ด๋™ ๊ฑฐ๋ฆฌ์— ๋น„๋ก€ํ•˜์—ฌ ์Šค์ผ€๏ฟฝ๏ฟฝ ์กฐ์ •
986
+ let newScale = activeOverlay.scale * (currentDistance / resizeStartDistance);
987
+ activeOverlay.scale = Math.max(0.01, newScale); // ์Šค์ผ€์ผ ์ตœ์†Œ๊ฐ’ ์ œํ•œ
988
+ resizeStartDistance = currentDistance; // ๋‹ค์Œ ์ด๋™ ๊ณ„์‚ฐ์„ ์œ„ํ•ด ๊ฑฐ๋ฆฌ ์—…๋ฐ์ดํŠธ
989
+ }
990
+
991
+ // ์Šค์ผ€์ผ ์Šฌ๋ผ์ด๋” ๋ฐ ๊ฐ’ ์—…๋ฐ์ดํŠธ
992
+ const scalePercent = Math.round(activeOverlay.scale * 100);
993
+ scaleSlider.value = scalePercent;
994
+ scaleValue.textContent = scalePercent + '%';
995
+
996
+ } else if (isDragging && activeOverlay.image) {
997
+ // ๋“œ๋ž˜๊ทธ ์ค‘์ด๊ณ  ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด
998
+ const dx = x - lastX; // x์ถ• ์ด๋™ ๊ฑฐ๋ฆฌ
999
+ const dy = y - lastY; // y์ถ• ์ด๋™ ๊ฑฐ๋ฆฌ
1000
+
1001
+ activeOverlay.x += dx; // ์˜ค๋ฒ„๋ ˆ์ด ์œ„์น˜ ์—…๋ฐ์ดํŠธ
1002
+ activeOverlay.y += dy;
1003
+ }
1004
+
1005
+ // ์ž‘์—… ์ค‘์—๋Š” ์บ”๋ฒ„์Šค ๊ณ„์† ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
1006
+ if (isRotating || isResizing || isDragging) {
1007
+ drawCanvas();
1008
+ }
1009
+ }
1010
+
1011
+ // ๋งˆ์ง€๋ง‰ ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ขŒํ‘œ ์—…๋ฐ์ดํŠธ
1012
+ lastX = x;
1013
+ lastY = y;
1014
+ });
1015
+
1016
+ // ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ ๋–ผ๊ฑฐ๋‚˜ ์บ”๋ฒ„์Šค ๋ฐ–์œผ๋กœ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ ์ž‘์—… ์ข…๋ฃŒ
1017
+ window.addEventListener('mouseup', function(e) {
1018
+ isDragging = false;
1019
+ isRotating = false;
1020
+ isResizing = false;
1021
+ activeHandle = null; // ํ™œ์„ฑ ํ•ธ๋“ค ์—†์Œ
1022
+
1023
+ // ๋งˆ์šฐ์Šค ์ขŒํ‘œ๊ฐ€ ์œ ํšจํ•˜๋ฉด ์ปค์„œ ๋ชจ์–‘ ์—…๋ฐ์ดํŠธ
1024
+ if (e.clientX !== undefined && e.clientY !== undefined) {
1025
+ const { x, y } = getCanvasCoordinates(e.clientX, e.clientY);
1026
+ updateCursor(x, y);
1027
+ } else {
1028
+ // ์ขŒํ‘œ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ ์ปค์„œ๋กœ ์„ค์ •
1029
+ canvas.style.cursor = 'default';
1030
+ }
1031
+ });
1032
+
1033
+
1034
+ // ํ„ฐ์น˜ ์ด๋ฒคํŠธ (๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ๋ฅผ ์—๋ฎฌ๋ ˆ์ด์…˜ํ•˜์—ฌ ์ฒ˜๋ฆฌ)
1035
+ canvas.addEventListener('touchstart', function(e) {
1036
+ e.preventDefault(); // ๊ธฐ๋ณธ ํ„ฐ์น˜ ๋™์ž‘ ๋ฐฉ์ง€ (์Šคํฌ๋กค ๋“ฑ)
1037
+ if (e.touches.length > 0) {
1038
+ const touch = e.touches[0];
1039
+ // ์ฒซ ๋ฒˆ์งธ ํ„ฐ์น˜ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ mousedown ์ด๋ฒคํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐœ์ƒ
1040
+ const mouseEvent = new MouseEvent('mousedown', { clientX: touch.clientX, clientY: touch.clientY, buttons: 1 });
1041
+ canvas.dispatchEvent(mouseEvent);
1042
+ }
1043
+ }, { passive: false }); // passive: false๋กœ ์„ค์ •ํ•˜์—ฌ preventDefault()๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก ํ•จ
1044
+
1045
+ canvas.addEventListener('touchmove', function(e) {
1046
+ e.preventDefault(); // ๊ธฐ๋ณธ ํ„ฐ์น˜ ๋™์ž‘ ๋ฐฉ์ง€
1047
+ if (e.touches.length > 0) {
1048
+ const touch = e.touches[0];
1049
+ // ์ฒซ ๋ฒˆ์งธ ํ„ฐ์น˜ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ mousemove ์ด๋ฒคํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐœ์ƒ
1050
+ const mouseEvent = new MouseEvent('mousemove', { clientX: touch.clientX, clientY: touch.clientY, buttons: 1 });
1051
+ canvas.dispatchEvent(mouseEvent);
1052
+ }
1053
+ }, { passive: false });
1054
+
1055
+ canvas.addEventListener('touchend', function(e) {
1056
+ e.preventDefault(); // ๊ธฐ๋ณธ ํ„ฐ์น˜ ๋™์ž‘ ๋ฐฉ์ง€
1057
+ // mouseup ์ด๋ฒคํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐœ์ƒ (์ขŒํ‘œ ์ •๋ณด๋Š” ํ•„์š” ์—†์Œ)
1058
+ const mouseEvent = new MouseEvent('mouseup', {});
1059
+ window.dispatchEvent(mouseEvent); // window์— ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ mouseup ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก ํ•จ
1060
+ }, { passive: false });
1061
+
1062
+
1063
+ // ํ•ฉ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
1064
+ generateBtn.addEventListener('click', function() {
1065
+ generateMergedImage(); // ํ•ฉ์„ฑ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ํ‘œ์‹œ
1066
+ // ์ด ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ UI ๋ ˆ์ด์•„์›ƒ์„ ๋ณ€๊ฒฝํ•˜๋Š” ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์— ์—†์Šต๋‹ˆ๋‹ค.
1067
+ // ์˜ค์ง generateMergedImage() ํ•จ์ˆ˜๋งŒ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
1068
+ });
1069
+
1070
+ // ์ „์ฒด ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
1071
+ resetAllBtn.addEventListener('click', function() {
1072
+ backgroundImage = null; // ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ดˆ๊ธฐํ™”
1073
+ overlays = []; // ์˜ค๋ฒ„๋ ˆ์ด ๋ฐฐ์—ด ์ดˆ๊ธฐํ™”
1074
+ activeOverlayIndex = -1; // ํ™œ์„ฑ ๋ ˆ์ด์–ด ํ•ด์ œ
1075
+ backgroundInput.value = ''; // ํŒŒ์ผ input ๊ฐ’ ์ดˆ๊ธฐํ™”
1076
+ overlayInput.value = '';
1077
+
1078
+ updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์ดˆ๊ธฐํ™”
1079
+ updateLayersList(); // ๋ ˆ์ด์–ด ๋ชฉ๋ก ์ดˆ๊ธฐํ™”
1080
+
1081
+ // ํ•„ํ„ฐ ์ปจํŠธ๋กค ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์„ค์ •
1082
+ const defaultFilters = ImageFilters.resetFilters(overlays, activeOverlayIndex); // (์‚ฌ์‹ค์ƒ overlays๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฏ€๋กœ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜)
1083
+ temperatureSlider.value = defaultFilters.temperature; temperatureValue.textContent = defaultFilters.temperature;
1084
+ brightnessSlider.value = defaultFilters.brightness; brightnessValue.textContent = defaultFilters.brightness + '%';
1085
+ contrastSlider.value = defaultFilters.contrast; contrastValue.textContent = defaultFilters.contrast + '%';
1086
+ saturationSlider.value = defaultFilters.saturation; saturationValue.textContent = defaultFilters.saturation + '%';
1087
+
1088
+ // ๏ฟฝ๏ฟฝ๏ฟฝ๋ฒ„์Šค ํฌ๊ธฐ ๋ฐ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์žฌ์„ค์ •
1089
+ canvasAdjustedHeight = CANVAS_HEIGHT;
1090
+ canvas.height = canvasAdjustedHeight;
1091
+ initCanvas(); // ์บ”๋ฒ„์Šค ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ์œผ๋กœ ์ดˆ๊ธฐํ™”
1092
+
1093
+ previewContainer.style.display = 'none'; // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ ์ˆจ๊น€
1094
+ });
1095
+
1096
+ // ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
1097
+ downloadBtn.addEventListener('click', function() {
1098
+ const resultCanvas = generateMergedImage(); // ํ•ฉ์„ฑ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
1099
+ if (!resultCanvas) return; // ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด ๋‹ค์šด๋กœ๋“œํ•˜์ง€ ์•Š์Œ
1100
+
1101
+ const link = document.createElement('a'); // ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ์ž„์‹œ ๋งํฌ ์š”์†Œ ์ƒ์„ฑ
1102
+ // ํŒŒ์ผ ์ด๋ฆ„ ์ƒ์„ฑ (ํ˜„์žฌ ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜)
1103
+ const timestamp = new Date().toISOString().replace(/[-:.]/g, '').substring(0, 14);
1104
+ // ์ด๋ฏธ์ง€ ํฌ๋งท ๋ฐ ํ™•์žฅ์ž ๊ฒฐ์ • (์›๋ณธ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ํฌ๋งท ๋”ฐ๋ฆ„)
1105
+ const mimeType = originalFormat === 'jpg' ? 'image/jpeg' : `image/${originalFormat}`;
1106
+ const fileExt = originalFormat;
1107
+
1108
+ link.download = `merged_image_${timestamp}.${fileExt}`; // ๋‹ค์šด๋กœ๋“œ๋  ํŒŒ์ผ ์ด๋ฆ„ ์„ค์ •
1109
+ // ์บ”๋ฒ„์Šค ์ด๋ฏธ์ง€๋ฅผ Data URL๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋งํฌ์˜ href๋กœ ์„ค์ •
1110
+ link.href = resultCanvas.toDataURL(mimeType, 1.0); // 1.0์€ ํ™”์งˆ (PNG๋Š” ์˜ํ–ฅ ์—†์Œ, JPG๋Š” 0~1)
1111
+
1112
+ link.click(); // ๋งํฌ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ๋‹ค์šด๋กœ๋“œ ์‹คํ–‰
1113
+ });
1114
+
1115
+
1116
+ // ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ ํ˜ธ์ถœ
1117
+ initCanvas(); // ์บ”๋ฒ„์Šค ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์„ค์ •
1118
+ updateControlPanel(); // ์ปจํŠธ๋กค ํŒจ๋„ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์„ค์ • (๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™” ๋“ฑ)
1119
+
1120
+ }); // DOMContentLoaded ๋
style.css CHANGED
@@ -1,28 +1,301 @@
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
 
 
 
 
 
 
 
9
  }
10
 
11
  p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
 
 
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  body {
2
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
3
+ line-height: 1.6;
4
+ margin: 0;
5
+ padding: 0;
6
+ background-color: #f0f0f0;
7
+ color: #333;
8
  }
9
 
10
+ .container {
11
+ max-width: 1200px; /* ํŽ˜์ด์ง€ ์ „์ฒด ์ปจํ…Œ์ด๋„ˆ ์ตœ๋Œ€ ๋„ˆ๋น„ */
12
+ margin: 20px auto;
13
+ padding: 20px;
14
+ background-color: white;
15
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
16
+ border-radius: 8px;
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ h1, h2, h3 {
22
+ color: #2c3e50;
23
+ text-align: center;
24
  }
25
 
26
  p {
27
+ text-align: center;
28
+ margin-bottom: 20px;
29
+ }
30
+
31
+ .upload-container {
32
+ display: flex;
33
+ flex-wrap: wrap;
34
+ justify-content: space-around;
35
+ margin-bottom: 20px;
36
+ gap: 20px;
37
+ }
38
+
39
+ .upload-box {
40
+ flex: 1;
41
+ min-width: 300px;
42
+ border: 2px dashed #3498db;
43
+ border-radius: 5px;
44
+ padding: 20px;
45
+ text-align: center;
46
+ transition: background-color 0.3s;
47
+ }
48
+
49
+ .upload-box:hover {
50
+ background-color: #ecf0f1;
51
+ }
52
+
53
+ .upload-label {
54
+ display: block;
55
+ font-weight: bold;
56
+ margin-bottom: 10px;
57
+ color: #2980b9;
58
+ }
59
+
60
+ .file-input {
61
+ display: none;
62
+ }
63
+
64
+ .upload-button {
65
+ display: inline-block;
66
+ background-color: #3498db;
67
+ color: white;
68
+ padding: 10px 20px;
69
+ border-radius: 5px;
70
+ cursor: pointer;
71
+ transition: background-color 0.3s;
72
+ }
73
+
74
+ .upload-button:hover {
75
+ background-color: #2980b9;
76
+ }
77
+
78
+ /* ์ž‘์—… ์˜์—ญ ๋ž˜ํผ */
79
+ .workspace-wrapper {
80
+ display: flex; /* ์บ”๋ฒ„์Šค+์ปจํŠธ๋กค ์˜์—ญ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์˜์—ญ์„ ๊ฐ€๋กœ๋กœ ๋ฐฐ์น˜ */
81
+ flex-wrap: wrap; /* ํ™”๋ฉด์ด ์ž‘์„ ๊ฒฝ์šฐ ์ค„๋ฐ”๊ฟˆ ํ—ˆ์šฉ */
82
+ gap: 20px;
83
+ margin-bottom: 20px;
84
+ }
85
+
86
+ /* ์บ”๋ฒ„์Šค์™€ ์ปจํŠธ๋กค ํŒจ๋„๋“ค์„ ๋ฌถ๋Š” ์˜์—ญ */
87
+ .canvas-and-controls {
88
+ flex: 3; /* ๊ฐ€๋กœ ๋ฐฐ์น˜ ์‹œ ๋น„์œจ (์บ”๋ฒ„์Šค ์˜์—ญ์ด ๋” ๋„“๊ฒŒ) */
89
+ display: flex;
90
+ flex-direction: column; /* ์บ”๋ฒ„์Šค ์•„๋ž˜์— ์ปจํŠธ๋กค ํŒจ๋„ ๋ฐฐ์น˜ (ํ•„์š”์‹œ ๊ตฌ์กฐ ๋ณ€๊ฒฝ) */
91
+ min-width: 0; /* flex item์˜ ๋‚ด์šฉ์ด ๋„˜์น  ๋•Œ ์ˆ˜์ถ•ํ•˜๋„๋ก ํ•จ */
92
+ }
93
+
94
+ .canvas-container {
95
+ position: relative;
96
+ width: 100%; /* ๋ถ€๋ชจ ๋„ˆ๋น„์— ๋งž์ถค */
97
+ /* max-width: 800px; /* ์บ”๋ฒ„์Šค ์ตœ๋Œ€ ๋„ˆ๋น„๋Š” JS์—์„œ ์„ค์ •๋œ CANVAS_WIDTH์— ๋”ฐ๋ฆ„ */
98
+ border: 2px solid #333;
99
+ border-radius: 4px;
100
+ overflow: hidden; /* ์บ”๋ฒ„์Šค๊ฐ€ ๋„˜์น˜์ง€ ์•Š๋„๋ก */
101
+ margin-bottom: 20px; /* ์ปจํŠธ๋กค ํŒจ๋„๊ณผ์˜ ๊ฐ„๊ฒฉ */
102
+ }
103
+
104
+ #canvas {
105
+ display: block; /* ํ•˜๋‹จ ์—ฌ๋ฐฑ ์ œ๊ฑฐ */
106
+ width: 100%; /* canvas-container์— ๊ฝ‰ ์ฐจ๊ฒŒ */
107
+ height: auto; /* ๋น„์œจ ์œ ์ง€ */
108
+ background-color: #ffffff;
109
+ cursor: default;
110
+ }
111
+
112
+ /* ์ปจํŠธ๋กค ํŒจ๋„๋“ค์„ ๋ชจ์•„๋‘๋Š” ์‚ฌ์ด๋“œ๋ฐ” ์—ญํ•  (๋˜๋Š” ์บ”๋ฒ„์Šค ์•„๋ž˜ ์˜์—ญ) */
113
+ .control-panel-sidebar {
114
+ display: flex;
115
+ flex-direction: column;
116
+ gap: 15px;
117
+ /* flex: 1; /* canvas-and-controls ๋‚ด์—์„œ ๋น„์œจ (ํ•„์š”์‹œ) */
118
+ }
119
+
120
+
121
+ .control-group {
122
+ display: flex;
123
+ flex-direction: column;
124
+ /* align-items: center; ํ•ญ๋ชฉ๋“ค์ด ์ค‘์•™ ์ •๋ ฌ๋˜์ง€ ์•Š๋„๋ก ์ œ๊ฑฐ ๋˜๋Š” ์ˆ˜์ • */
125
+ border: 1px solid #ddd;
126
+ padding: 15px;
127
+ border-radius: 5px;
128
+ background-color: #f9f9f9;
129
+ /* min-width: 250px; /* ์ตœ์†Œ ๋„ˆ๋น„๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ์กฐ์ • */
130
+ }
131
+
132
+ .control-group h3 {
133
+ margin: 0 0 15px 0;
134
+ font-size: 1rem;
135
+ text-align: center;
136
+ width: 100%;
137
+ }
138
+
139
+ .slider-container {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: 10px;
143
+ margin: 8px 0;
144
+ width: 100%;
145
+ padding: 0 5px;
146
+ box-sizing: border-box;
147
+ }
148
+
149
+ .slider-container label {
150
+ font-weight: bold;
151
+ min-width: 50px; /* "ํšŒ์ „:" ๋“ฑ ๋ ˆ์ด๋ธ”์ด ์ž˜๋ฆฌ์ง€ ์•Š๋„๋ก ์ถฉ๋ถ„ํžˆ */
152
+ text-align: left;
153
+ flex-shrink: 0;
154
+ }
155
+
156
+ .slider-container input[type="range"] {
157
+ flex-grow: 1; /* ์Šฌ๋ผ์ด๋”๊ฐ€ ๋‚จ์€ ๊ณต๊ฐ„์„ ๋ชจ๋‘ ์ฐจ์ง€ */
158
+ width: auto; /* flex-grow๊ฐ€ ์ž‘๋™ํ•˜๋„๋ก auto๋กœ ์„ค์ • */
159
+ margin: 0 5px; /* ์ขŒ์šฐ ์•ฝ๊ฐ„์˜ ์—ฌ๋ฐฑ */
160
+ }
161
+
162
+ .slider-container .value-display {
163
+ min-width: 40px; /* ๊ฐ’ ํ‘œ์‹œ ์˜์—ญ ๋„ˆ๋น„ */
164
+ text-align: right; /* ๊ฐ’์„ ์˜ค๋ฅธ์ชฝ์— ํ‘œ์‹œ */
165
+ flex-shrink: 0;
166
+ }
167
+
168
+ /* ๋ ˆ์ด์–ด ๋ชฉ๋ก ์Šคํƒ€์ผ */
169
+ .layers-list {
170
+ width: 100%;
171
+ max-height: 150px;
172
+ overflow-y: auto;
173
+ border: 1px solid #ddd;
174
+ border-radius: 4px;
175
+ }
176
+
177
+ .layer-item {
178
+ padding: 8px;
179
+ border-bottom: 1px solid #eee;
180
+ cursor: pointer;
181
+ display: flex;
182
+ justify-content: space-between;
183
+ align-items: center;
184
+ }
185
+ .layer-item:last-child { border-bottom: none; }
186
+ .layer-item:hover { background-color: #f5f5f5; }
187
+ .layer-item.active { background-color: #e3f2fd; font-weight: bold; }
188
+ .layer-name { flex-grow: 1; padding-left: 5px; }
189
+ .layer-controls { display: flex; gap: 5px; }
190
+ .layer-button {
191
+ background-color: #f0f0f0; border: 1px solid #ddd; border-radius: 3px;
192
+ padding: 2px 5px; cursor: pointer; font-size: 11px; color: #555;
193
+ transition: all 0.2s ease;
194
+ }
195
+ .layer-button:hover { background-color: #e0e0e0; }
196
+
197
+
198
+ /* ํ•„ํ„ฐ ํŒจ๋„ ์Šคํƒ€์ผ */
199
+ .filter-panel {
200
+ /* width: 100%; /* ๋ถ€๋ชจ(.control-panel-sidebar)์— ์˜ํ•ด ๋„ˆ๋น„ ๊ฒฐ์ • */
201
+ padding: 15px;
202
+ border: 1px solid #ddd;
203
+ border-radius: 5px;
204
+ background-color: #f9f9f9;
205
+ }
206
+ .filter-panel h3 { margin: 0 0 15px 0; text-align: center; }
207
+ .filter-sliders { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; }
208
+ .filter-slider-container { display: flex; flex-direction: column; gap: 5px; width: 100%; padding: 0 5px; box-sizing: border-box; }
209
+ .filter-slider-container label { font-size: 14px; margin-bottom: 5px; text-align: left; }
210
+ .large-slider { width: 100%; height: 20px; /* ์Šฌ๋ผ์ด๋” ๋†’์ด ์กฐ์ • */ }
211
+ .filter-slider-container .value-display { text-align: right; font-weight: bold; }
212
+ .filter-buttons { display: flex; justify-content: center; margin-top: 15px; }
213
+ #reset-filter-btn { width: auto; padding: 8px 15px; flex-grow: 1; max-width: 200px; }
214
+
215
+
216
+ /* ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์„น์…˜ */
217
+ .preview-section {
218
+ flex: 2; /* ๊ฐ€๋กœ ๋ฐฐ์น˜ ์‹œ ๋น„์œจ (์บ”๋ฒ„์Šค ์˜์—ญ๋ณด๋‹ค ์ž‘๊ฒŒ) */
219
+ display: flex; /* ๋‚ด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ์ •๋ ฌ ์œ„ํ•จ */
220
+ flex-direction: column;
221
+ align-items: center; /* ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ค‘์•™์— */
222
+ min-width: 0; /* flex item์˜ ๋‚ด์šฉ์ด ๋„˜์น  ๋•Œ ์ˆ˜์ถ•ํ•˜๋„๋ก ํ•จ */
223
+ }
224
+
225
+ .preview-container {
226
+ width: 100%; /* preview-section ๋„ˆ๋น„์— ๋งž์ถค */
227
+ margin-top: 0; /* workspace-wrapper์—์„œ gap์œผ๋กœ ์ฒ˜๋ฆฌ */
228
+ display: none; /* ์ดˆ๊ธฐ์—๋Š” ์ˆจ๊น€ (JS๋กœ ์ œ์–ด) */
229
+ flex-direction: column;
230
+ align-items: center;
231
+ padding: 10px;
232
+ border: 1px dashed #ccc;
233
+ border-radius: 4px;
234
+ background-color: #f9f9f9;
235
  }
236
 
237
+ .preview-container h3 {
238
+ margin-bottom: 10px;
 
 
 
 
239
  }
240
 
241
+ #preview-img {
242
+ max-width: 100%;
243
+ max-height: 400px; /* ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ์ตœ๋Œ€ ๋†’์ด ์ œํ•œ */
244
+ border: 2px solid #333;
245
+ border-radius: 4px;
246
+ object-fit: contain; /* ์ด๋ฏธ์ง€ ๋น„์œจ ์œ ์ง€ */
247
  }
248
+
249
+ /* ํ•˜๋‹จ ๋ฒ„ํŠผ ์ปจํ…Œ์ด๋„ˆ */
250
+ .button-container {
251
+ display: flex;
252
+ justify-content: center;
253
+ gap: 15px;
254
+ margin-top: 20px; /* workspace-wrapper ์™€์˜ ๊ฐ„๊ฒฉ */
255
+ flex-wrap: wrap;
256
+ }
257
+
258
+ button {
259
+ padding: 10px 20px;
260
+ border: none;
261
+ border-radius: 5px;
262
+ cursor: pointer;
263
+ font-weight: bold;
264
+ transition: background-color 0.3s;
265
+ }
266
+ .primary-btn { background-color: #27ae60; color: white; }
267
+ .primary-btn:hover { background-color: #219653; }
268
+ .danger-btn { background-color: #e74c3c; color: white; }
269
+ .danger-btn:hover { background-color: #c0392b; }
270
+ .info-btn { background-color: #3498db; color: white; }
271
+ .info-btn:hover { background-color: #2980b9; }
272
+
273
+
274
+ /* ๋ชจ๋ฐ”์ผ ๋Œ€์‘ */
275
+ @media (max-width: 900px) { /* ์ข€ ๋” ๋„“์€ ํ™”๋ฉด์—์„œ๋ถ€ํ„ฐ ๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ ๊ณ ๋ ค */
276
+ .workspace-wrapper {
277
+ flex-direction: column; /* ์ž‘์—… ์˜์—ญ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์„ธ๋กœ๋กœ ๋ฐฐ์น˜ */
278
+ align-items: center; /* ์ค‘์•™ ์ •๋ ฌ */
279
+ }
280
+ .canvas-and-controls, .preview-section {
281
+ flex-basis: auto; /* flex ๋น„์œจ ์ดˆ๊ธฐํ™” */
282
+ width: 100%; /* ์ „์ฒด ๋„ˆ๋น„ ์‚ฌ์šฉ */
283
+ max-width: 600px; /* ๋ชจ๋ฐ”์ผ์—์„œ ๋„ˆ๋ฌด ๋„“์–ด์ง€์ง€ ์•Š๋„๋ก */
284
+ }
285
+ .control-panel-sidebar {
286
+ width: 100%; /* ์ „์ฒด ๋„ˆ๋น„ ์‚ฌ์šฉ */
287
+ }
288
+ .filter-sliders {
289
+ grid-template-columns: 1fr; /* ํ•„ํ„ฐ ์Šฌ๋ผ์ด๋” ํ•œ ์ค„๋กœ */
290
+ }
291
+ }
292
+
293
+ @media (max-width: 768px) {
294
+ .upload-container {
295
+ flex-direction: column;
296
+ }
297
+ .upload-box {
298
+ width: 100%;
299
+ margin-bottom: 10px;
300
+ }
301
+ }