soiz1 commited on
Commit
42ec444
·
verified ·
1 Parent(s): d01a8a4

Update script.js

Browse files
Files changed (1) hide show
  1. script.js +327 -113
script.js CHANGED
@@ -1,111 +1,104 @@
1
  document.addEventListener('DOMContentLoaded', function() {
2
  // 要素の取得
3
  const imageUpload = document.getElementById('image-upload');
4
- const canvas = document.getElementById('image-canvas');
5
- const ctx = canvas.getContext('2d');
6
-
7
- // スライダー要素の取得
8
  const brightnessSlider = document.getElementById('brightness');
9
  const contrastSlider = document.getElementById('contrast');
10
  const saturationSlider = document.getElementById('saturation');
11
  const shadowsSlider = document.getElementById('shadows');
12
  const highlightsSlider = document.getElementById('highlights');
13
- const hueSlider = document.getElementById('hue');
14
- const hueSaturationSlider = document.getElementById('hue-saturation');
15
- const lightnessSlider = document.getElementById('lightness');
16
- const redCurveSlider = document.getElementById('red-curve');
17
- const greenCurveSlider = document.getElementById('green-curve');
18
- const blueCurveSlider = document.getElementById('blue-curve');
19
-
20
- // 値表示要素の取得
21
  const brightnessValue = document.getElementById('brightness-value');
22
  const contrastValue = document.getElementById('contrast-value');
23
  const saturationValue = document.getElementById('saturation-value');
24
  const shadowsValue = document.getElementById('shadows-value');
25
  const highlightsValue = document.getElementById('highlights-value');
26
- const hueValue = document.getElementById('hue-value');
27
- const hueSaturationValue = document.getElementById('hue-saturation-value');
28
- const lightnessValue = document.getElementById('lightness-value');
29
- const redCurveValue = document.getElementById('red-curve-value');
30
- const greenCurveValue = document.getElementById('green-curve-value');
31
- const blueCurveValue = document.getElementById('blue-curve-value');
32
-
33
- // ボタン要素の取得
34
  const resetBtn = document.getElementById('reset-btn');
35
  const downloadBtn = document.getElementById('download-btn');
36
 
 
 
 
 
 
 
 
 
 
 
37
  let camanInstance = null;
38
  let originalImageData = null;
39
 
40
  // 画像アップロードの処理
41
  imageUpload.addEventListener('change', function(e) {
42
- if (e.target.files && e.target.files[0]) {
43
- const reader = new FileReader();
44
-
45
- reader.onload = function(event) {
46
- const img = new Image();
47
- img.onload = function() {
48
- // キャンバスのサイズを画像に合わせて調整
49
- const maxWidth = 800;
50
- const maxHeight = 600;
51
- let width = img.width;
52
- let height = img.height;
53
-
54
- if (width > maxWidth) {
55
- height = (maxWidth / width) * height;
56
- width = maxWidth;
57
- }
58
-
59
- if (height > maxHeight) {
60
- width = (maxHeight / height) * width;
61
- height = maxHeight;
62
- }
63
-
64
- canvas.width = width;
65
- canvas.height = height;
66
-
67
- // 画像をキャンバスに描画
68
- ctx.drawImage(img, 0, 0, width, height);
 
 
 
 
 
69
 
70
- // CamanJSのインスタンスを作成
71
- Caman(canvas, function() {
72
- camanInstance = this;
73
- originalImageData = this.toBase64();
74
-
75
- // スライダーをリセット
76
- resetSliders();
77
- });
78
- };
79
 
80
- img.src = event.target.result;
 
81
  };
82
-
83
- reader.readAsDataURL(e.target.files[0]);
84
- }
85
  });
86
 
87
- // スライダー変更時の処理
88
- function setupSlider(slider, valueElement, filterName, paramName) {
89
  slider.addEventListener('input', function() {
90
- if (!camanInstance) return;
91
-
92
  valueElement.textContent = this.value;
93
- applyFilters();
 
 
 
94
  });
95
  }
96
 
97
- // 各スライダーにイベントリスナーを設定
98
- setupSlider(brightnessSlider, brightnessValue, 'brightness', 'brightness');
99
- setupSlider(contrastSlider, contrastValue, 'contrast', 'contrast');
100
- setupSlider(saturationSlider, saturationValue, 'saturation', 'saturation');
101
- setupSlider(shadowsSlider, shadowsValue, 'exposure', 'shadows');
102
- setupSlider(highlightsSlider, highlightsValue, 'exposure', 'highlights');
103
- setupSlider(hueSlider, hueValue, 'hue', 'hue');
104
- setupSlider(hueSaturationSlider, hueSaturationValue, 'saturation', 'hueSaturation');
105
- setupSlider(lightnessSlider, lightnessValue, 'vibrance', 'lightness');
106
- setupSlider(redCurveSlider, redCurveValue, 'channels', 'red');
107
- setupSlider(greenCurveSlider, greenCurveValue, 'channels', 'green');
108
- setupSlider(blueCurveSlider, blueCurveValue, 'channels', 'blue');
109
 
110
  // フィルターを適用する関数
111
  function applyFilters() {
@@ -113,67 +106,288 @@ document.addEventListener('DOMContentLoaded', function() {
113
 
114
  camanInstance.revert(false);
115
 
116
- // 各フィルターを適用
117
  camanInstance.brightness(parseInt(brightnessSlider.value));
118
  camanInstance.contrast(parseInt(contrastSlider.value));
119
  camanInstance.saturation(parseInt(saturationSlider.value));
120
- camanInstance.exposure({
121
- shadows: parseInt(shadowsSlider.value),
122
- highlights: parseInt(highlightsSlider.value)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  });
124
- camanInstance.hue(parseInt(hueSlider.value));
125
- camanInstance.vibrance(parseInt(lightnessSlider.value));
126
- camanInstance.channels({
127
- red: parseInt(redCurveSlider.value) / 100,
128
- green: parseInt(greenCurveSlider.value) / 100,
129
- blue: parseInt(blueCurveSlider.value) / 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  });
131
 
132
- camanInstance.render();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  }
134
 
135
- // スライダーをリセットする関数
136
- function resetSliders() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  brightnessSlider.value = 0;
138
  contrastSlider.value = 0;
139
  saturationSlider.value = 0;
140
  shadowsSlider.value = 0;
141
  highlightsSlider.value = 0;
142
- hueSlider.value = 0;
143
- hueSaturationSlider.value = 0;
144
- lightnessSlider.value = 0;
145
- redCurveSlider.value = 0;
146
- greenCurveSlider.value = 0;
147
- blueCurveSlider.value = 0;
148
 
149
  brightnessValue.textContent = '0';
150
  contrastValue.textContent = '0';
151
  saturationValue.textContent = '0';
152
  shadowsValue.textContent = '0';
153
  highlightsValue.textContent = '0';
154
- hueValue.textContent = '0';
155
- hueSaturationValue.textContent = '0';
156
- lightnessValue.textContent = '0';
157
- redCurveValue.textContent = '0';
158
- greenCurveValue.textContent = '0';
159
- blueCurveValue.textContent = '0';
160
-
161
- if (camanInstance) {
162
- camanInstance.revert(false);
163
- camanInstance.render();
164
- }
165
- }
166
-
167
- // リセットボタンの処理
168
- resetBtn.addEventListener('click', resetSliders);
 
 
 
 
 
 
 
 
 
169
 
170
- // ダウンロードボタンの処理
171
  downloadBtn.addEventListener('click', function() {
172
  if (!camanInstance) return;
173
 
174
  const link = document.createElement('a');
175
  link.download = 'edited-image.png';
176
- link.href = canvas.toDataURL('image/png');
177
  link.click();
178
  });
179
  });
 
1
  document.addEventListener('DOMContentLoaded', function() {
2
  // 要素の取得
3
  const imageUpload = document.getElementById('image-upload');
4
+ const imageCanvas = document.getElementById('image-canvas');
 
 
 
5
  const brightnessSlider = document.getElementById('brightness');
6
  const contrastSlider = document.getElementById('contrast');
7
  const saturationSlider = document.getElementById('saturation');
8
  const shadowsSlider = document.getElementById('shadows');
9
  const highlightsSlider = document.getElementById('highlights');
10
+ const hueRedSlider = document.getElementById('hue-red');
11
+ const hueGreenSlider = document.getElementById('hue-green');
12
+ const hueBlueSlider = document.getElementById('hue-blue');
 
 
 
 
 
13
  const brightnessValue = document.getElementById('brightness-value');
14
  const contrastValue = document.getElementById('contrast-value');
15
  const saturationValue = document.getElementById('saturation-value');
16
  const shadowsValue = document.getElementById('shadows-value');
17
  const highlightsValue = document.getElementById('highlights-value');
 
 
 
 
 
 
 
 
18
  const resetBtn = document.getElementById('reset-btn');
19
  const downloadBtn = document.getElementById('download-btn');
20
 
21
+ // RGBカーブ用のキャンバス
22
+ const redCurveCanvas = document.getElementById('red-curve');
23
+ const greenCurveCanvas = document.getElementById('green-curve');
24
+ const blueCurveCanvas = document.getElementById('blue-curve');
25
+
26
+ // カーブ制御用の変数
27
+ let redCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
28
+ let greenCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
29
+ let blueCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
30
+
31
  let camanInstance = null;
32
  let originalImageData = null;
33
 
34
  // 画像アップロードの処理
35
  imageUpload.addEventListener('change', function(e) {
36
+ const file = e.target.files[0];
37
+ if (!file) return;
38
+
39
+ const reader = new FileReader();
40
+ reader.onload = function(event) {
41
+ const img = new Image();
42
+ img.onload = function() {
43
+ // キャンバスのサイズを画像に合わせる
44
+ const maxWidth = 800;
45
+ const maxHeight = 600;
46
+ let width = img.width;
47
+ let height = img.height;
48
+
49
+ if (width > maxWidth) {
50
+ height = (maxWidth / width) * height;
51
+ width = maxWidth;
52
+ }
53
+
54
+ if (height > maxHeight) {
55
+ width = (maxHeight / height) * width;
56
+ height = maxHeight;
57
+ }
58
+
59
+ imageCanvas.width = width;
60
+ imageCanvas.height = height;
61
+
62
+ // CamanJSで画像を読み込���
63
+ Caman(imageCanvas, function() {
64
+ this.revert(false);
65
+ this.render();
66
+ camanInstance = this;
67
+ originalImageData = this.canvas.toDataURL();
68
 
69
+ // RGBカーブを初期化
70
+ initCurves();
71
+ });
 
 
 
 
 
 
72
 
73
+ const ctx = imageCanvas.getContext('2d');
74
+ ctx.drawImage(img, 0, 0, width, height);
75
  };
76
+ img.src = event.target.result;
77
+ };
78
+ reader.readAsDataURL(file);
79
  });
80
 
81
+ // スライダーイベントの設定
82
+ function setupSlider(slider, valueElement, filterName, callback) {
83
  slider.addEventListener('input', function() {
 
 
84
  valueElement.textContent = this.value;
85
+ if (camanInstance) {
86
+ applyFilters();
87
+ if (callback) callback();
88
+ }
89
  });
90
  }
91
 
92
+ setupSlider(brightnessSlider, brightnessValue, 'brightness');
93
+ setupSlider(contrastSlider, contrastValue, 'contrast');
94
+ setupSlider(saturationSlider, saturationValue, 'saturation');
95
+ setupSlider(shadowsSlider, shadowsValue, 'shadows');
96
+ setupSlider(highlightsSlider, highlightsValue, 'highlights');
97
+
98
+ // HSLスライダーの設定
99
+ hueRedSlider.addEventListener('input', applyFilters);
100
+ hueGreenSlider.addEventListener('input', applyFilters);
101
+ hueBlueSlider.addEventListener('input', applyFilters);
 
 
102
 
103
  // フィルターを適用する関数
104
  function applyFilters() {
 
106
 
107
  camanInstance.revert(false);
108
 
109
+ // 基本調整
110
  camanInstance.brightness(parseInt(brightnessSlider.value));
111
  camanInstance.contrast(parseInt(contrastSlider.value));
112
  camanInstance.saturation(parseInt(saturationSlider.value));
113
+
114
+ // トーン調整
115
+ camanInstance.exposure(parseInt(shadowsSlider.value) / 10);
116
+ camanInstance.exposure(parseInt(highlightsSlider.value) / 10);
117
+
118
+ // HSL調整
119
+ camanInstance.hue(parseInt(hueRedSlider.value) / 2);
120
+ camanInstance.hue(parseInt(hueGreenSlider.value) / 2);
121
+ camanInstance.hue(parseInt(hueBlueSlider.value) / 2);
122
+
123
+ // RGBカーブを適用
124
+ applyRgbCurves();
125
+
126
+ camanInstance.render();
127
+ }
128
+
129
+ // RGBカーブを初期化
130
+ function initCurves() {
131
+ drawCurve(redCurveCanvas, redCurvePoints, 'red');
132
+ drawCurve(greenCurveCanvas, greenCurvePoints, 'green');
133
+ drawCurve(blueCurveCanvas, blueCurvePoints, 'blue');
134
+
135
+ setupCurveInteraction(redCurveCanvas, redCurvePoints, 'red');
136
+ setupCurveInteraction(greenCurveCanvas, greenCurvePoints, 'green');
137
+ setupCurveInteraction(blueCurveCanvas, blueCurvePoints, 'blue');
138
+ }
139
+
140
+ // カーブを描画
141
+ function drawCurve(canvas, points, color) {
142
+ const ctx = canvas.getContext('2d');
143
+ const width = canvas.width;
144
+ const height = canvas.height;
145
+
146
+ // クリア
147
+ ctx.clearRect(0, 0, width, height);
148
+
149
+ // グリッドを描画
150
+ ctx.strokeStyle = '#eee';
151
+ ctx.lineWidth = 1;
152
+
153
+ // 水平線
154
+ for (let y = 0; y <= height; y += height / 4) {
155
+ ctx.beginPath();
156
+ ctx.moveTo(0, y);
157
+ ctx.lineTo(width, y);
158
+ ctx.stroke();
159
+ }
160
+
161
+ // 垂直線
162
+ for (let x = 0; x <= width; x += width / 4) {
163
+ ctx.beginPath();
164
+ ctx.moveTo(x, 0);
165
+ ctx.lineTo(x, height);
166
+ ctx.stroke();
167
+ }
168
+
169
+ // 対角線
170
+ ctx.strokeStyle = '#ccc';
171
+ ctx.beginPath();
172
+ ctx.moveTo(0, height);
173
+ ctx.lineTo(width, 0);
174
+ ctx.stroke();
175
+
176
+ // カーブを描画
177
+ ctx.strokeStyle = color;
178
+ ctx.lineWidth = 2;
179
+ ctx.beginPath();
180
+
181
+ // 点をソート
182
+ points.sort((a, b) => a.x - b.x);
183
+
184
+ // 最初の点
185
+ const firstPoint = points[0];
186
+ ctx.moveTo(firstPoint.x / 255 * width, (255 - firstPoint.y) / 255 * height);
187
+
188
+ // 中間の点
189
+ for (let i = 1; i < points.length; i++) {
190
+ const point = points[i];
191
+ ctx.lineTo(point.x / 255 * width, (255 - point.y) / 255 * height);
192
+ }
193
+
194
+ ctx.stroke();
195
+
196
+ // 制御点を描画
197
+ ctx.fillStyle = color;
198
+ points.forEach(point => {
199
+ const x = point.x / 255 * width;
200
+ const y = (255 - point.y) / 255 * height;
201
+ ctx.beginPath();
202
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
203
+ ctx.fill();
204
+ });
205
+ }
206
+
207
+ // カーブのインタラクションを設定
208
+ function setupCurveInteraction(canvas, points, color) {
209
+ let isDragging = false;
210
+ let draggedPoint = null;
211
+
212
+ canvas.addEventListener('mousedown', function(e) {
213
+ const rect = canvas.getBoundingClientRect();
214
+ const x = (e.clientX - rect.left) / rect.width * 255;
215
+ const y = 255 - (e.clientY - rect.top) / rect.height * 255;
216
+
217
+ // 既存の点をチェック
218
+ for (let i = 0; i < points.length; i++) {
219
+ const point = points[i];
220
+ const distance = Math.sqrt(Math.pow(point.x - x, 2) + Math.pow(point.y - y, 2));
221
+
222
+ if (distance < 15) { // ヒット半径
223
+ isDragging = true;
224
+ draggedPoint = point;
225
+ return;
226
+ }
227
+ }
228
+
229
+ // 新しい点を追加
230
+ if (x > 0 && x < 255 && y > 0 && y < 255) {
231
+ points.push({x, y});
232
+ points.sort((a, b) => a.x - b.x);
233
+ isDragging = true;
234
+ draggedPoint = {x, y};
235
+ }
236
+
237
+ drawCurve(canvas, points, color);
238
+ applyFilters();
239
  });
240
+
241
+ canvas.addEventListener('mousemove', function(e) {
242
+ if (!isDragging || !draggedPoint) return;
243
+
244
+ const rect = canvas.getBoundingClientRect();
245
+ let x = (e.clientX - rect.left) / rect.width * 255;
246
+ let y = 255 - (e.clientY - rect.top) / rect.height * 255;
247
+
248
+ // 境界チェック
249
+ x = Math.max(0, Math.min(255, x));
250
+ y = Math.max(0, Math.min(255, y));
251
+
252
+ // 最初と最後の点はx座標を固定
253
+ if (points.indexOf(draggedPoint) === 0) {
254
+ x = 0;
255
+ } else if (points.indexOf(draggedPoint) === points.length - 1) {
256
+ x = 255;
257
+ }
258
+
259
+ draggedPoint.x = x;
260
+ draggedPoint.y = y;
261
+
262
+ drawCurve(canvas, points, color);
263
+ applyFilters();
264
  });
265
 
266
+ canvas.addEventListener('mouseup', function() {
267
+ isDragging = false;
268
+ draggedPoint = null;
269
+ });
270
+
271
+ canvas.addEventListener('mouseleave', function() {
272
+ isDragging = false;
273
+ draggedPoint = null;
274
+ });
275
+
276
+ // ポイントを削除するダブルクリック
277
+ canvas.addEventListener('dblclick', function(e) {
278
+ if (points.length <= 2) return; // 最低2点は必要
279
+
280
+ const rect = canvas.getBoundingClientRect();
281
+ const x = (e.clientX - rect.left) / rect.width * 255;
282
+ const y = 255 - (e.clientY - rect.top) / rect.height * 255;
283
+
284
+ for (let i = 1; i < points.length - 1; i++) { // 最初と最後は削除しない
285
+ const point = points[i];
286
+ const distance = Math.sqrt(Math.pow(point.x - x, 2) + Math.pow(point.y - y, 2));
287
+
288
+ if (distance < 15) {
289
+ points.splice(i, 1);
290
+ drawCurve(canvas, points, color);
291
+ applyFilters();
292
+ return;
293
+ }
294
+ }
295
+ });
296
  }
297
 
298
+ // RGBカーブを適用
299
+ function applyRgbCurves() {
300
+ if (!camanInstance) return;
301
+
302
+ // カーブデータを準備
303
+ const redCurve = createCurveData(redCurvePoints);
304
+ const greenCurve = createCurveData(greenCurvePoints);
305
+ const blueCurve = createCurveData(blueCurvePoints);
306
+
307
+ // カーブを適用
308
+ camanInstance.process("rgbCurve", function(rgba) {
309
+ rgba.r = redCurve[rgba.r];
310
+ rgba.g = greenCurve[rgba.g];
311
+ rgba.b = blueCurve[rgba.b];
312
+ return rgba;
313
+ });
314
+ }
315
+
316
+ // カーブデータを作成
317
+ function createCurveData(points) {
318
+ points.sort((a, b) => a.x - b.x);
319
+ const curve = new Array(256);
320
+
321
+ for (let i = 0; i < points.length - 1; i++) {
322
+ const start = points[i];
323
+ const end = points[i + 1];
324
+
325
+ const x1 = Math.round(start.x);
326
+ const y1 = Math.round(start.y);
327
+ const x2 = Math.round(end.x);
328
+ const y2 = Math.round(end.y);
329
+
330
+ // 線形補間
331
+ for (let x = x1; x <= x2; x++) {
332
+ const t = (x - x1) / (x2 - x1);
333
+ curve[x] = Math.round(y1 + t * (y2 - y1));
334
+ }
335
+ }
336
+
337
+ return curve;
338
+ }
339
+
340
+ // リセットボタン
341
+ resetBtn.addEventListener('click', function() {
342
+ if (!camanInstance) return;
343
+
344
+ // スライダーをリセット
345
  brightnessSlider.value = 0;
346
  contrastSlider.value = 0;
347
  saturationSlider.value = 0;
348
  shadowsSlider.value = 0;
349
  highlightsSlider.value = 0;
350
+ hueRedSlider.value = 0;
351
+ hueGreenSlider.value = 0;
352
+ hueBlueSlider.value = 0;
 
 
 
353
 
354
  brightnessValue.textContent = '0';
355
  contrastValue.textContent = '0';
356
  saturationValue.textContent = '0';
357
  shadowsValue.textContent = '0';
358
  highlightsValue.textContent = '0';
359
+
360
+ // カーブをリセット
361
+ redCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
362
+ greenCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
363
+ blueCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
364
+
365
+ drawCurve(redCurveCanvas, redCurvePoints, 'red');
366
+ drawCurve(greenCurveCanvas, greenCurvePoints, 'green');
367
+ drawCurve(blueCurveCanvas, blueCurvePoints, 'blue');
368
+
369
+ // 画像をリセット
370
+ const img = new Image();
371
+ img.onload = function() {
372
+ const ctx = imageCanvas.getContext('2d');
373
+ ctx.drawImage(img, 0, 0, imageCanvas.width, imageCanvas.height);
374
+
375
+ Caman(imageCanvas, function() {
376
+ this.revert(false);
377
+ this.render();
378
+ camanInstance = this;
379
+ });
380
+ };
381
+ img.src = originalImageData;
382
+ });
383
 
384
+ // ダウンロードボタン
385
  downloadBtn.addEventListener('click', function() {
386
  if (!camanInstance) return;
387
 
388
  const link = document.createElement('a');
389
  link.download = 'edited-image.png';
390
+ link.href = imageCanvas.toDataURL('image/png');
391
  link.click();
392
  });
393
  });