const MAX_LONG_EDGE = 1600;
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
const infoEl = document.getElementById('info');
const dropzone = document.getElementById('dropzone');
let currentObjectURL = null; // 表示中の Blob URL を後で解放
fileInput.addEventListener('change', async (e) => {
const file = e.target.files?.[0];
if (file) await handleFile(file);
// 同じファイル選択でも change が発火するように value をクリア
e.target.value = '';
});
// ドラッグ&ドロップ対応
['dragenter','dragover'].forEach(type => {
dropzone.addEventListener(type, (ev) => {
ev.preventDefault(); ev.stopPropagation();
dropzone.classList.add('dragover');
});
});
['dragleave','drop'].forEach(type => {
dropzone.addEventListener(type, (ev) => {
ev.preventDefault(); ev.stopPropagation();
dropzone.classList.remove('dragover');
});
});
dropzone.addEventListener('drop', async (ev) => {
const file = ev.dataTransfer?.files?.[0];
if (file) await handleFile(file);
});
async function handleFile(file) {
if (!file.type.startsWith('image/')) {
alert('画像ファイルを選択してください。');
return;
}
try {
// 1) 画像の読み込み(createImageBitmap があれば高速)
const { width, height, drawToCanvas } = await loadImage(file);
// 2) 縮小スケール計算(長辺が MAX_LONG_EDGE を超える場合のみ縮小)
const longEdge = Math.max(width, height);
const scale = longEdge > MAX_LONG_EDGE ? (MAX_LONG_EDGE / longEdge) : 1;
const outW = Math.round(width * scale);
const outH = Math.round(height * scale);
// 3) 描画用キャンバス
const canvas = document.createElement('canvas');
canvas.width = outW;
canvas.height = outH;
const ctx = canvas.getContext('2d', { alpha: true });
// 高品質縮小(ブラウザ依存)
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
// 4) 実描画
await drawToCanvas(ctx, outW, outH);
// 5) Blob 化して
に表示(PNG か JPEG を選択)
const outType = file.type.includes('png') ? 'image/png' : 'image/jpeg';
const quality = outType === 'image/jpeg' ? 0.9 : 1.0;
const blob = await new Promise(resolve => canvas.toBlob(resolve, outType, quality));
if (!blob) throw new Error('toBlob に失敗しました');
// 既存の URL を解放
if (currentObjectURL) URL.revokeObjectURL(currentObjectURL);
const url = URL.createObjectURL(blob);
currentObjectURL = url;
preview.src = url;
// 情報表示
const kb = (blob.size / 1024).toFixed(1);
infoEl.textContent =
`元サイズ: ${width}×${height} → 出力: ${outW}×${outH}, ` +
`形式: ${outType}, 約 ${kb} KB`;
} catch (err) {
console.error(err);
alert('画像の処理に失敗しました。別の画像でお試しください。');
}
}
/**
* 画像を読み込み、任意のキャンバスに描画する関数を返す。
* createImageBitmap 利用時は ImageBitmap を、フォールバックは HTMLImageElement を利用。
*/
async function loadImage(file) {
if ('createImageBitmap' in window) {
const bitmap = await createImageBitmap(file); // 高速デコード
const width = bitmap.width;
const height = bitmap.height;
return {
width, height,
drawToCanvas: async (ctx, w, h) => {
ctx.drawImage(bitmap, 0, 0, w, h);
bitmap.close?.();
}
};
} else {
// フォールバック:
+ Object URL
const url = URL.createObjectURL(file);
try {
const img = await loadImageElement(url);
const width = img.naturalWidth || img.width;
const height = img.naturalHeight || img.height;
return {
width, height,
drawToCanvas: async (ctx, w, h) => {
ctx.drawImage(img, 0, 0, w, h);
}
};
} finally {
// 読み込み用 URL は不要になったら解放
URL.revokeObjectURL(url);
}
}
}
function loadImageElement(url) {
return new Promise((resolve, reject) => {
const img = new Image();
// CORS 回避が必要な外部 URL を使う場合は crossOrigin 設定が必要
// img.crossOrigin = 'anonymous';
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}