[Astro] #119 JP2 (JPEG 2000) Image Converter — WASM + Canvas + JSZipによるブラウザ完結型画像変換・ZIP一括出力
概要
一部のアーカイブ資料や高精度画像フォーマットとして利用されるJPEG 2000(.jp2)ファイルを、サーバーにアップロードすることなくブラウザローカル環境のみで標準的な画像フォーマット(PNG / JPEG)に変換・プレビュー表示するWEBツールを構築しました。
デコード処理にはOpenJPEGのWASMビルドを使用し、解像度やアスペクト比を維持したままCanvas上に描画。複数ファイルの一括ドラッグ&ドロップおよびJSZipを使用した一括ZIPエクスポートに対応しています。
JP2 Image Converter
JPEG 2000 (.jp2) ファイルをブラウザ内で変換。DPR対応高精細プレビュー、メモリ効率の良いBlob生成、バッチZIPダウンロード対応。
lain-lab.comスクリーンショット
動画(GIF)
Sample data
sampleは以下のサイトからお借りしています。
Sample JP2 Files Download - Get Examples Instantly
開発者・テスター向けサンプルファイル提供サイト。開発・検証用の多様な解像度のJPEG 2000 (.jp2) サンプルファイルを配布。
filesamples.com今回の実装内容
| 機能・モジュール | 概要 |
|---|---|
| JP2デコード | OpenJPEG WASM による JPEG 2000 バイナリの高速デコード |
| Canvasプレビュー | window.devicePixelRatio (DPR) を考慮した高解像度(Retina/4K)クリア描画 |
| メモリ最適化エクスポート | canvas.toBlob() と URL.createObjectURL を使った低メモリフットプリントなファイル出力 |
| 一括ZIPダウンロード | JSZip を統合し、複数変換済み画像を converted_jp2_images.zip としてまとめて圧縮出力 |
| ドラッグ&ドロップUI | 複数 .jp2 ファイルの一括追加と個別・全体進捗インジケーター |
背景 — JPEG 2000のブラウザ非対応問題と解決策
JPEG 2000(.jp2)は高圧縮率かつ可逆・不可逆圧縮をサポートする優秀な画像フォーマットであるものの、モダンブラウザ(Chrome/Firefox/Edgeなど)で標準表示がサポートされていない。そのため、旧アーカイブ画像や地図データなどのJP2ファイルを閲覧・利用するには専用ソフトウェアやオンライン変換サービスに依存する必要がありました。
しかし、セキュリティやプライバシーの観点から外部サーバーに画像をアップロードしたくないケースも多い。本ツールでは、WebAssembly (WASM) を用いてブラウザ内部でOpenJPEGのC言語デコーダーを動作させることで、完全にローカル完結な変換処理を実現しています。
.jp2 ファイル (ドラッグ&ドロップ)
→ OpenJPEG (WASM デコーダー)
→ RGBA ピクセル配列 (ImageData)
→ Canvas API (DPRスケーリング & プレビュー表示)
→ canvas.toBlob() (メモリ最適化)
→ JSZip (一括ZIPバッチダウンロード)
技術実装の詳細
1. WASMデコードとCanvasピクセル復元
.jp2 ファイルを ArrayBuffer として読み込み、OpenJPEG WASM モジュールへ渡して生のRGBAピクセル配列を取り出す。取得したピクセルデータから ImageData を作成し、キャンバスに描画。
// JP2バイナリのデコード
const arrayBuffer = await file.arrayBuffer();
const decompressed = openjpegWasm.decode(new Uint8Array(arrayBuffer));
// ImageDataの生成と描画
const imageData = new ImageData(
new UintClampedArray(decompressed.data),
decompressed.width,
decompressed.height
);
ctx.putImageData(imageData, 0, 0);
2. 高解像度ディスプレイ(DPR)対応
Retinaディスプレイや4Kモニター等の高DPI環境でプレビュー画像がボヤけるのを防ぐため、window.devicePixelRatio を取得してキャンバスの内部解像度とCSS表示サイズを分離制御。
function renderPreview(canvas: HTMLCanvasElement, imgData: ImageData) {
const dpr = window.devicePixelRatio || 1;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// 論理サイズ(CSS表示サイズ)の設定
canvas.style.width = `${imgData.width}px`;
canvas.style.height = `${imgData.height}px`;
// 物理解像度(DPR考慮)の設定
canvas.width = imgData.width * dpr;
canvas.height = imgData.height * dpr;
// コンテキストのスケール調整
ctx.scale(dpr, dpr);
// スケール適用後に一時キャンバス等から描画
ctx.drawImage(offscreenCanvas, 0, 0);
}
3. toBlob を活用したメモリ最適化
巨大な解像度の画像をエクスポートする際、従来の canvas.toDataURL()(Base64文字列化)を使用すると文字列変換に伴うメモリ空間の急速な圧迫(元のデータ量の約1.33倍+文字列メモリ割り当て)が発生し、ブラウザがクラッシュするリスクがありました。
これを回避するため、非同期 canvas.toBlob() を用いて Blob オブジェクトを直接生成し、URL.createObjectURL 経由でエクスポートを行う設計に変更しました。
async function exportImage(canvas: HTMLCanvasElement, mimeType = 'image/png'): Promise<Blob> {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error('Canvas to Blob conversion failed'));
}
}, mimeType, 0.92);
});
}
// 単体ダウンロード時の実行例
const blob = await exportImage(canvas, 'image/png');
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = `${fileName}.png`;
a.click();
URL.revokeObjectURL(downloadUrl); // メモリ解放
4. JSZip によるバッチ ZIP パッキング
複数の .jp2 ファイルがドロップされた場合、非同期で順次デコード・変換を行い、得られた Blob を JSZip インスタンスに追加。
全ファイルの変換完了後、ZIPファイルをストリーム生成して一括ダウンロード。
import JSZip from 'jszip';
async function handleBatchDownload(convertedFiles: { name: string; blob: Blob }[]) {
const zip = new JSZip();
// 各ファイルをZIP構造へ追加
convertedFiles.forEach(({ name, blob }) => {
zip.file(name, blob);
});
// ZIPアーカイブの生成
const zipContent = await zip.generateAsync({ type: 'blob' });
// ダウンロード実行
const zipUrl = URL.createObjectURL(zipContent);
const link = document.createElement('a');
link.href = zipUrl;
link.download = 'converted_jp2_images.zip';
link.click();
URL.revokeObjectURL(zipUrl);
}
技術スタック
- Astro — ページ・コンポーネント構造
- OpenJPEG (WASM) — C言語ベースの高速 JPEG 2000 デコーダー
- Canvas API — ピクセル復元、DPR制御、画像フォーマットエンコード(PNG / JPEG)
- JSZip — 変換後の複数画像をクライアントサイドでZIP圧縮
更新履歴
- [v1.0]: 公開バージョン。Retina/DPR 描画対応、
toBlobによるメモリ最適化、JSZip 統合による一括 ZIP バッチダウンロード(converted_jp2_images.zip)に対応。 - [v0.1]: 初期実装。OpenJPEG WASM による
.jp2デコード、単一キャンバス描画および画像出力。