Spaces:
Runtime error
Runtime error
<html lang="ja"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>RGBYカーブエディタ</title> | |
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>RGBYカーブエディタ</h1> | |
<form method="post" enctype="multipart/form-data" onsubmit="showLoading()"> | |
<div class="upload-section"> | |
<input type="file" id="fileInput" accept=".jpg,.jpeg,.png"> | |
<button id="uploadBtn">画像を読み込み</button> | |
<div id="loading" style="display:none;">処理中...</div> | |
</div> | |
<div class="curve-controls"> | |
<h2>2Dトーンカーブエディタ</h2> | |
<div class="curve-editor-container"> | |
<canvas id="curveCanvas" width="400" height="400"></canvas> | |
<div class="curve-presets"> | |
<button type="button" onclick="setLinearCurve()">リニア</button> | |
<button type="button" onclick="setContrastCurve()">コントラスト↑</button> | |
<button type="button" onclick="setInverseCurve()">反転</button> | |
</div> | |
</div> | |
<div class="channel-controls"> | |
<div class="channel"> | |
<h3>Redチャンネル</h3> | |
<div class="sliders"> | |
{% for i in range(1, 6) %} | |
<div class="slider-container"> | |
<label>ポイント{{ i }}</label> | |
<input type="range" min="0" max="255" value="{{ r_curve[i-1] }}" | |
name="r{{ i }}" id="r{{ i }}" class="slider" oninput="updateCurve()"> | |
<span class="slider-value">{{ r_curve[i-1] }}</span> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
<div class="channel"> | |
<h3>Greenチャンネル</h3> | |
<div class="sliders"> | |
{% for i in range(1, 6) %} | |
<div class="slider-container"> | |
<label>ポイント{{ i }}</label> | |
<input type="range" min="0" max="255" value="{{ g_curve[i-1] }}" | |
name="g{{ i }}" id="g{{ i }}" class="slider" oninput="updateCurve()"> | |
<span class="slider-value">{{ g_curve[i-1] }}</span> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
<div class="channel"> | |
<h3>Blueチャンネル</h3> | |
<div class="sliders"> | |
{% for i in range(1, 6) %} | |
<div class="slider-container"> | |
<label>ポイント{{ i }}</label> | |
<input type="range" min="0" max="255" value="{{ b_curve[i-1] }}" | |
name="b{{ i }}" id="b{{ i }}" class="slider" oninput="updateCurve()"> | |
<span class="slider-value">{{ b_curve[i-1] }}</span> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
<div class="channel"> | |
<h3>明るさ (Y)</h3> | |
<div class="sliders"> | |
{% for i in range(1, 6) %} | |
<div class="slider-container"> | |
<label>ポイント{{ i }}</label> | |
<input type="range" min="0" max="255" value="{{ y_curve[i-1] }}" | |
name="y{{ i }}" id="y{{ i }}" class="slider" oninput="updateCurve()"> | |
<span class="slider-value">{{ y_curve[i-1] }}</span> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
</div> | |
</div> | |
</form> | |
<div class="image-comparison"> | |
<div class="image-container"> | |
<h2>元画像</h2> | |
<img id="originalImage" src="" alt="Original Image" style="display:none;"> | |
<canvas id="originalCanvas"></canvas> | |
</div> | |
<div class="image-container"> | |
<h2>調整後画像</h2> | |
<canvas id="adjustedCanvas"></canvas> | |
</div> | |
</div> | |
</div> | |
<script src="{{ url_for('static', filename='js/curve_editor.js') }}"></script> | |
<script> | |
// グローバル変数 | |
let originalImageData = null; | |
// DOM要素を取得 | |
const fileInput = document.getElementById('fileInput'); | |
const uploadBtn = document.getElementById('uploadBtn'); | |
const originalImage = document.getElementById('originalImage'); | |
const originalCanvas = document.getElementById('originalCanvas'); | |
const adjustedCanvas = document.getElementById('adjustedCanvas'); | |
const loadingIndicator = document.getElementById('loading'); | |
// 画像アップロード処理 | |
uploadBtn.addEventListener('click', async () => { | |
const file = fileInput.files[0]; | |
if (!file) return; | |
try { | |
loadingIndicator.style.display = 'block'; | |
// 画像を読み込み | |
const reader = new FileReader(); | |
reader.onload = async (e) => { | |
originalImage.src = e.target.result; | |
originalImage.onload = async () => { | |
// Canvasに描画 | |
drawImageToCanvas(originalImage, originalCanvas); | |
// 画像データを保存 | |
originalImageData = await getImageData(originalCanvas); | |
// 初期処理を実行 | |
await processImage(); | |
loadingIndicator.style.display = 'none'; | |
}; | |
}; | |
reader.readAsDataURL(file); | |
} catch (error) { | |
console.error('Error:', error); | |
loadingIndicator.style.display = 'none'; | |
alert('画像の読み込みに失敗しました'); | |
} | |
}); | |
// 画像処理関数 | |
async function processImage() { | |
if (!originalImageData) return; | |
try { | |
loadingIndicator.style.display = 'block'; | |
// カーブパラメータを取得 | |
const params = { | |
image: originalImageData, | |
r_curve: [ | |
parseInt(document.getElementById('r1').value), | |
parseInt(document.getElementById('r2').value), | |
parseInt(document.getElementById('r3').value), | |
parseInt(document.getElementById('r4').value), | |
parseInt(document.getElementById('r5').value) | |
], | |
g_curve: [ | |
parseInt(document.getElementById('g1').value), | |
parseInt(document.getElementById('g2').value), | |
parseInt(document.getElementById('g3').value), | |
parseInt(document.getElementById('g4').value), | |
parseInt(document.getElementById('g5').value) | |
], | |
b_curve: [ | |
parseInt(document.getElementById('b1').value), | |
parseInt(document.getElementById('b2').value), | |
parseInt(document.getElementById('b3').value), | |
parseInt(document.getElementById('b4').value), | |
parseInt(document.getElementById('b5').value) | |
], | |
y_curve: [ | |
parseInt(document.getElementById('y1').value), | |
parseInt(document.getElementById('y2').value), | |
parseInt(document.getElementById('y3').value), | |
parseInt(document.getElementById('y4').value), | |
parseInt(document.getElementById('y5').value) | |
] | |
}; | |
// APIを呼び出し | |
const response = await fetch('/api/process_image', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(params) | |
}); | |
const result = await response.json(); | |
if (result.status === 'success') { | |
// 処理後の画像を表示 | |
const img = new Image(); | |
img.onload = () => { | |
drawImageToCanvas(img, adjustedCanvas); | |
loadingIndicator.style.display = 'none'; | |
}; | |
img.src = result.image; | |
} else { | |
throw new Error(result.message); | |
} | |
} catch (error) { | |
console.error('Error:', error); | |
loadingIndicator.style.display = 'none'; | |
alert('画像処理に失敗しました'); | |
} | |
} | |
// 画像をCanvasに描画 | |
function drawImageToCanvas(image, canvas) { | |
const ctx = canvas.getContext('2d'); | |
canvas.width = image.width; | |
canvas.height = image.height; | |
ctx.drawImage(image, 0, 0, canvas.width, canvas.height); | |
} | |
// Canvasから画像データを取得 | |
async function getImageData(canvas) { | |
return new Promise((resolve) => { | |
canvas.toBlob((blob) => { | |
const reader = new FileReader(); | |
reader.onload = () => resolve(reader.result); | |
reader.readAsDataURL(blob); | |
}, 'image/jpeg', 0.9); | |
}); | |
} | |
// スライダー変更時に画像処理を実行 | |
document.querySelectorAll('.slider').forEach(slider => { | |
slider.addEventListener('input', function() { | |
const valueSpan = this.parentElement.querySelector('.slider-value'); | |
valueSpan.textContent = this.value; | |
// デバウンス処理 (連続変更を防ぐ) | |
if (this.timeout) clearTimeout(this.timeout); | |
this.timeout = setTimeout(processImage, 300); | |
}); | |
}); | |
// 初期化 | |
document.addEventListener('DOMContentLoaded', function() { | |
// スライダーの値を表示 | |
document.querySelectorAll('.slider').forEach(slider => { | |
const valueSpan = slider.parentElement.querySelector('.slider-value'); | |
valueSpan.textContent = slider.value; | |
}); | |
// カーブエディタを初期化 | |
initCurveEditor(); | |
// カーブエディタの変更時に処理を実行 | |
window.updateCurve = function() { | |
processImage(); | |
}; | |
}); | |
</script> | |
</body> | |
</html> |