ライブラリのアーキテクチャ概要と、構成する各コンポーネントについて説明します。
本ライブラリは、AndroidのJetpack JavaScriptEngineを利用して、WASM (WebAssembly) にコンパイルされたOpenJPEGライブラリをサンドボックス環境で実行します。これにより、ネイティブコードの実行に伴うセキュリティリスクを隔離し、安全にJPEG2000画像のデコードを行います。
graph TB
subgraph "Android Application"
App[User App]
end
subgraph "jp2k-decoder-android (Library)"
subgraph "Kotlin Layer"
Decoder["Jp2kDecoder / Async"]
SandboxMgr["Jp2kSandbox (Singleton)"]
end
subgraph "Infrastructure Layer"
JSSandbox["JavaScriptSandbox (Jetpack)"]
JSIsolate[JavaScriptIsolate]
end
subgraph "Runtime Layer (WASM)"
Wrapper["wrapper.c (WASM)"]
OpenJPEG["OpenJPEG (WASM)"]
end
end
App -->|Byte Array| Decoder
Decoder -->|Manage| JSSandbox
SandboxMgr -->|Provide| JSSandbox
JSSandbox -->|Create| JSIsolate
Decoder -->|Execute| JSIsolate
JSIsolate -->|Run| Wrapper
Wrapper -->|Link| OpenJPEG
本ライブラリは2つのデコーダ実装を提供します。どちらも内部的には同じWASMモジュールとサンドボックスを使用しますが、APIのスタイルが異なります。
- Jp2kDecoder: Kotlin Coroutinesネイティブの実装です。
suspend関数を使用して非同期処理を行い、Mutexによりスレッドセーフを保証します。Kotlinプロジェクトでの使用を推奨します。 - Jp2kDecoderAsync: コールバックベースの実装です。Javaプロジェクトや、独自のExecutor管理が必要な場合に使用します。
画像デコード時のデータフローは以下の通りです。
sequenceDiagram
participant App as User App
participant Kotlin as Jp2kDecoder (Kotlin)
participant Bridge as JS Bridge (Evaluate)
participant WASM as wrapper.c (WASM)
participant OpenJPEG as OpenJPEG
App->>Kotlin: decodeImage(ByteArray)
Kotlin->>Kotlin: Convert ByteArray to Base64 String
Kotlin->>Bridge: Call decodeJ2K(base64String)
rect rgb(240, 248, 255)
note right of Bridge: Inside JS/WASM Sandbox
Bridge->>Bridge: Parse Base64 to Bytes (Alloc WASM Mem) using SCRIPT_BYTES_BASE64_CONVERTER
Bridge->>WASM: Invoke C function (decodeToBmp)
WASM->>OpenJPEG: opj_decode
OpenJPEG-->>WASM: opj_image_t (Raw Pixel Data)
WASM->>WASM: Convert to BMP Format (Add Header)
WASM-->>Bridge: Return Pointer
Bridge->>Bridge: Read Mem & Convert BMP to Base64 String
Bridge-->>Kotlin: Return JSON { bmp: "Base64..." }
end
Kotlin->>Kotlin: Parse JSON & Base64 to ByteArray
Kotlin->>Kotlin: BitmapFactory.decodeByteArray()
Kotlin-->>App: Return Bitmap
Androidアプリから利用されるAPIを提供するレイヤーです。Jp2kDecoder (Coroutine対応) および Jp2kDecoderAsync (Callback対応) がこれに該当します。
- Jp2kDecoder / Jp2kDecoderAsync: ユーザー向けAPI。デコード要求を受け付け、バックグラウンドスレッドで処理を行います。
- Jp2kSandbox: シングルトンオブジェクトとして
JavaScriptSandboxの接続を管理します。アプリ全体で1つの接続を再利用することで、オーバーヘッドとリソース消費を最小限に抑えます。
- WASMのロードと初期化:
openjpeg_core.wasmをアセットから読み込み、バイナリをBase64文字列に変換してJavaScript環境に注入します。JS側でBase64文字列をバイナリ (Uint8Array) に復元してからWebAssembly.instantiateを実行します。 - データの受け渡し (Base64 Encoding):
- WASM環境とのデータ授受には、バイナリデータをBase64文字列にエンコードして渡す方式を採用しています。
- データ転送を効率化するため、JS側のユーティリティ関数 (
base64ToBytes,bytesToBase64) はSCRIPT_BYTES_BASE64_CONVERTER定数として分離・注入されます。
- 画像変換: 返却されたBMP形式のBase64文字列をバイト配列に変換し、
BitmapFactoryを使用してAndroidのBitmapオブジェクトを生成します。ColorFormat指定 (RGB565 / ARGB8888) に応じてBitmapFactory.Optionsを設定し、適切なフォーマットで Bitmap を生成します。
- ライフサイクル管理:
init(),release()によるJavaScriptIsolateのリソース管理を行います。release()実行時には Isolate をクローズし、処理を強制終了します。 - 設定管理:
Configクラスを通じて、最大ヒープサイズや最大ピクセル数などのパラメータを管理・適用します。
Jp2kDecoder と Jp2kDecoderAsync は共通の State 定義 (dev.keiji.jp2k.State) を使用して状態を管理します。
Uninitialized: 初期状態。init()の呼び出しのみ許可されます。Initializing: 初期化処理中。バックグラウンドでのWASMロードやIsolate作成を行っています。Initialized: 初期化完了。decodeImage()の呼び出しが可能な待機状態。Processing: デコードまたはメモリ使用量取得などの処理中。Releasing: 終了処理中。release()が実行中です。Released: 終了状態。release()が完了した後の状態。これ以上の操作は受け付けません。
stateDiagram-v2
[*] --> Uninitialized
Uninitialized --> Initializing : init() called
state Initializing {
[*] --> SetupSandbox
SetupSandbox --> LoadWasm
LoadWasm --> [*]
}
Initializing --> Initialized : init() success
Initializing --> Uninitialized : init() failed (Exception)
Initializing --> Releasing : release() called during init
Initialized --> Processing : decodeImage() / getMemoryUsage() / getSize() called
Processing --> Initialized : processing finished (Success/Error)
Processing --> Releasing : release() called
Initialized --> Releasing : release() called
Releasing --> Released
Released --> [*]
- init(context): Suspending関数。Isolateの初期化を行います。
Mutexによりシリアライズされており、並行呼び出しは安全に制御されます。デフォルトではDispatchers.Default(またはコンストラクタで指定されたDispatcher) 上で実行されます。 - decodeImage(bytes): Suspending関数。デコード処理を行います。
Dispatchers.Default(またはコンストラクタで指定されたDispatcher) 上で実行されます。 - getSize(bytes): Suspending関数。デコードを行わずに画像のサイズ(幅・高さ)を取得します。
- release(): 即座にIsolateをクローズし、状態を
Releasedに変更します。
各状態におけるメソッド呼び出し時の挙動は以下の通りです。synchronized ブロックにより排他制御されます。
| State \ Method | init() | decodeImage() | getSize() | getMemoryUsage() | release() |
|---|---|---|---|---|---|
| Uninitialized | 初期化開始 | Error | Error | Error | 終了処理 (State=Released) |
| Initializing | Error | Error | Error | Error | 終了処理 (State=Released) |
| Initialized | 成功 (何もしない) | デコード開始 | 取得開始 | 取得開始 | 終了処理 (State=Released) |
| Processing | Error | デコード開始 (キューイング) | 取得開始 (キューイング) | 取得開始 (キューイング) | 終了処理 (State=Released) |
| Released | Error | Error | Error | 成功 (何もしない) |
- Error:
IllegalStateException(またはそれに準ずるエラー) をコールバックに返却します。 - 初期化開始: バックグラウンドExecutorで初期化処理を開始します。
- デコード開始: バックグラウンドExecutorでデコード処理を開始します。
- キューイング: シングルスレッドExecutorにより、実行中の処理が完了した後、順次実行されます。
OpenJPEGライブラリをWASMから扱いやすくするためのラッパーコードです。C言語で記述され、EmscriptenによってWASMにコンパイルされます。
- インターフェース公開: JavaScriptから呼び出し可能な関数
decodeToBmpなどをエクスポートします。 - フォーマット判定: 入力データのシグネチャを確認し、JP2形式かJ2Kコードストリームかを内部で自動判定します(OpenJPEG API利用)。
- BMP変換: OpenJPEGによってデコードされた
opj_image_t構造体(各コンポーネントごとのデータ)を、指定されたcolor_formatに応じた BMPファイルフォーマットのバイト列に変換します。ARGB8888: 32bpp (BGRA) 標準BMP。RGB565: 16bpp (Bitfields) BMP。
- サイズ取得:
getSize関数を公開し、ヘッダー情報のみを解析して幅と高さを返します。 - メモリ管理: デコード結果のBMPデータを格納するバッファの確保(
malloc)を行います(JavaScript側でfreeされることを期待します)。
- 入力サイズ制限:
max_heap_size(Kotlin側から渡される設定値) と指定されたcolor_formatに基づき、最大入力データサイズを動的に計算します。ARGB8888:max_heap_size / 4RGB565:max_heap_size / 2- これを超える場合は
ERR_INPUT_DATA_SIZEを返します。
- ピクセル数制限: デコード結果の総ピクセル数(幅×高さ)が
max_pixels(Kotlin側から渡される設定値) を超える場合、ERR_PIXEL_DATA_SIZEを返し、処理を中断します。 - コンポーネント数: 画像が少なくとも3つのコンポーネント(RGB)を持っているか確認します。不足している場合はエラーとします。
JPEG 2000規格 (ISO/IEC 15444-1) に準拠したデコード処理を行うコアライブラリです。
- コードストリーム解析: JPEG 2000のパケット、タイル、プレシンクトなどの構造を解析します。
- エントロピーデコード: 圧縮データを解凍します。
- 逆ウェーブレット変換: 周波数領域のデータを空間領域(ピクセルデータ)に変換します。
- 色空間変換: 必要に応じて色空間の変換処理を行います。
- 規格準拠チェック: 入力データがJPEG 2000の規格に準拠しているか、破損していないかを内部的に検証します。不整合がある場合、デコード処理関数 (
opj_decode等) が失敗を返します。