image-editor / script.js
soiz1's picture
Update script.js
42ec444 verified
raw
history blame
14.2 kB
document.addEventListener('DOMContentLoaded', function() {
// 要素の取得
const imageUpload = document.getElementById('image-upload');
const imageCanvas = document.getElementById('image-canvas');
const brightnessSlider = document.getElementById('brightness');
const contrastSlider = document.getElementById('contrast');
const saturationSlider = document.getElementById('saturation');
const shadowsSlider = document.getElementById('shadows');
const highlightsSlider = document.getElementById('highlights');
const hueRedSlider = document.getElementById('hue-red');
const hueGreenSlider = document.getElementById('hue-green');
const hueBlueSlider = document.getElementById('hue-blue');
const brightnessValue = document.getElementById('brightness-value');
const contrastValue = document.getElementById('contrast-value');
const saturationValue = document.getElementById('saturation-value');
const shadowsValue = document.getElementById('shadows-value');
const highlightsValue = document.getElementById('highlights-value');
const resetBtn = document.getElementById('reset-btn');
const downloadBtn = document.getElementById('download-btn');
// RGBカーブ用のキャンバス
const redCurveCanvas = document.getElementById('red-curve');
const greenCurveCanvas = document.getElementById('green-curve');
const blueCurveCanvas = document.getElementById('blue-curve');
// カーブ制御用の変数
let redCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
let greenCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
let blueCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
let camanInstance = null;
let originalImageData = null;
// 画像アップロードの処理
imageUpload.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
// キャンバスのサイズを画像に合わせる
const maxWidth = 800;
const maxHeight = 600;
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
if (height > maxHeight) {
width = (maxHeight / height) * width;
height = maxHeight;
}
imageCanvas.width = width;
imageCanvas.height = height;
// CamanJSで画像を読み込む
Caman(imageCanvas, function() {
this.revert(false);
this.render();
camanInstance = this;
originalImageData = this.canvas.toDataURL();
// RGBカーブを初期化
initCurves();
});
const ctx = imageCanvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
// スライダーイベントの設定
function setupSlider(slider, valueElement, filterName, callback) {
slider.addEventListener('input', function() {
valueElement.textContent = this.value;
if (camanInstance) {
applyFilters();
if (callback) callback();
}
});
}
setupSlider(brightnessSlider, brightnessValue, 'brightness');
setupSlider(contrastSlider, contrastValue, 'contrast');
setupSlider(saturationSlider, saturationValue, 'saturation');
setupSlider(shadowsSlider, shadowsValue, 'shadows');
setupSlider(highlightsSlider, highlightsValue, 'highlights');
// HSLスライダーの設定
hueRedSlider.addEventListener('input', applyFilters);
hueGreenSlider.addEventListener('input', applyFilters);
hueBlueSlider.addEventListener('input', applyFilters);
// フィルターを適用する関数
function applyFilters() {
if (!camanInstance) return;
camanInstance.revert(false);
// 基本調整
camanInstance.brightness(parseInt(brightnessSlider.value));
camanInstance.contrast(parseInt(contrastSlider.value));
camanInstance.saturation(parseInt(saturationSlider.value));
// トーン調整
camanInstance.exposure(parseInt(shadowsSlider.value) / 10);
camanInstance.exposure(parseInt(highlightsSlider.value) / 10);
// HSL調整
camanInstance.hue(parseInt(hueRedSlider.value) / 2);
camanInstance.hue(parseInt(hueGreenSlider.value) / 2);
camanInstance.hue(parseInt(hueBlueSlider.value) / 2);
// RGBカーブを適用
applyRgbCurves();
camanInstance.render();
}
// RGBカーブを初期化
function initCurves() {
drawCurve(redCurveCanvas, redCurvePoints, 'red');
drawCurve(greenCurveCanvas, greenCurvePoints, 'green');
drawCurve(blueCurveCanvas, blueCurvePoints, 'blue');
setupCurveInteraction(redCurveCanvas, redCurvePoints, 'red');
setupCurveInteraction(greenCurveCanvas, greenCurvePoints, 'green');
setupCurveInteraction(blueCurveCanvas, blueCurvePoints, 'blue');
}
// カーブを描画
function drawCurve(canvas, points, color) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// クリア
ctx.clearRect(0, 0, width, height);
// グリッドを描画
ctx.strokeStyle = '#eee';
ctx.lineWidth = 1;
// 水平線
for (let y = 0; y <= height; y += height / 4) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
// 垂直線
for (let x = 0; x <= width; x += width / 4) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
}
// 対角線
ctx.strokeStyle = '#ccc';
ctx.beginPath();
ctx.moveTo(0, height);
ctx.lineTo(width, 0);
ctx.stroke();
// カーブを描画
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.beginPath();
// 点をソート
points.sort((a, b) => a.x - b.x);
// 最初の点
const firstPoint = points[0];
ctx.moveTo(firstPoint.x / 255 * width, (255 - firstPoint.y) / 255 * height);
// 中間の点
for (let i = 1; i < points.length; i++) {
const point = points[i];
ctx.lineTo(point.x / 255 * width, (255 - point.y) / 255 * height);
}
ctx.stroke();
// 制御点を描画
ctx.fillStyle = color;
points.forEach(point => {
const x = point.x / 255 * width;
const y = (255 - point.y) / 255 * height;
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fill();
});
}
// カーブのインタラクションを設定
function setupCurveInteraction(canvas, points, color) {
let isDragging = false;
let draggedPoint = null;
canvas.addEventListener('mousedown', function(e) {
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width * 255;
const y = 255 - (e.clientY - rect.top) / rect.height * 255;
// 既存の点をチェック
for (let i = 0; i < points.length; i++) {
const point = points[i];
const distance = Math.sqrt(Math.pow(point.x - x, 2) + Math.pow(point.y - y, 2));
if (distance < 15) { // ヒット半径
isDragging = true;
draggedPoint = point;
return;
}
}
// 新しい点を追加
if (x > 0 && x < 255 && y > 0 && y < 255) {
points.push({x, y});
points.sort((a, b) => a.x - b.x);
isDragging = true;
draggedPoint = {x, y};
}
drawCurve(canvas, points, color);
applyFilters();
});
canvas.addEventListener('mousemove', function(e) {
if (!isDragging || !draggedPoint) return;
const rect = canvas.getBoundingClientRect();
let x = (e.clientX - rect.left) / rect.width * 255;
let y = 255 - (e.clientY - rect.top) / rect.height * 255;
// 境界チェック
x = Math.max(0, Math.min(255, x));
y = Math.max(0, Math.min(255, y));
// 最初と最後の点はx座標を固定
if (points.indexOf(draggedPoint) === 0) {
x = 0;
} else if (points.indexOf(draggedPoint) === points.length - 1) {
x = 255;
}
draggedPoint.x = x;
draggedPoint.y = y;
drawCurve(canvas, points, color);
applyFilters();
});
canvas.addEventListener('mouseup', function() {
isDragging = false;
draggedPoint = null;
});
canvas.addEventListener('mouseleave', function() {
isDragging = false;
draggedPoint = null;
});
// ポイントを削除するダブルクリック
canvas.addEventListener('dblclick', function(e) {
if (points.length <= 2) return; // 最低2点は必要
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width * 255;
const y = 255 - (e.clientY - rect.top) / rect.height * 255;
for (let i = 1; i < points.length - 1; i++) { // 最初と最後は削除しない
const point = points[i];
const distance = Math.sqrt(Math.pow(point.x - x, 2) + Math.pow(point.y - y, 2));
if (distance < 15) {
points.splice(i, 1);
drawCurve(canvas, points, color);
applyFilters();
return;
}
}
});
}
// RGBカーブを適用
function applyRgbCurves() {
if (!camanInstance) return;
// カーブデータを準備
const redCurve = createCurveData(redCurvePoints);
const greenCurve = createCurveData(greenCurvePoints);
const blueCurve = createCurveData(blueCurvePoints);
// カーブを適用
camanInstance.process("rgbCurve", function(rgba) {
rgba.r = redCurve[rgba.r];
rgba.g = greenCurve[rgba.g];
rgba.b = blueCurve[rgba.b];
return rgba;
});
}
// カーブデータを作成
function createCurveData(points) {
points.sort((a, b) => a.x - b.x);
const curve = new Array(256);
for (let i = 0; i < points.length - 1; i++) {
const start = points[i];
const end = points[i + 1];
const x1 = Math.round(start.x);
const y1 = Math.round(start.y);
const x2 = Math.round(end.x);
const y2 = Math.round(end.y);
// 線形補間
for (let x = x1; x <= x2; x++) {
const t = (x - x1) / (x2 - x1);
curve[x] = Math.round(y1 + t * (y2 - y1));
}
}
return curve;
}
// リセットボタン
resetBtn.addEventListener('click', function() {
if (!camanInstance) return;
// スライダーをリセット
brightnessSlider.value = 0;
contrastSlider.value = 0;
saturationSlider.value = 0;
shadowsSlider.value = 0;
highlightsSlider.value = 0;
hueRedSlider.value = 0;
hueGreenSlider.value = 0;
hueBlueSlider.value = 0;
brightnessValue.textContent = '0';
contrastValue.textContent = '0';
saturationValue.textContent = '0';
shadowsValue.textContent = '0';
highlightsValue.textContent = '0';
// カーブをリセット
redCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
greenCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
blueCurvePoints = [{x: 0, y: 0}, {x: 255, y: 255}];
drawCurve(redCurveCanvas, redCurvePoints, 'red');
drawCurve(greenCurveCanvas, greenCurvePoints, 'green');
drawCurve(blueCurveCanvas, blueCurvePoints, 'blue');
// 画像をリセット
const img = new Image();
img.onload = function() {
const ctx = imageCanvas.getContext('2d');
ctx.drawImage(img, 0, 0, imageCanvas.width, imageCanvas.height);
Caman(imageCanvas, function() {
this.revert(false);
this.render();
camanInstance = this;
});
};
img.src = originalImageData;
});
// ダウンロードボタン
downloadBtn.addEventListener('click', function() {
if (!camanInstance) return;
const link = document.createElement('a');
link.download = 'edited-image.png';
link.href = imageCanvas.toDataURL('image/png');
link.click();
});
});