あけましておめでとうございます。2026年もよろしくお願いいたします。

今年のケーキです

さて、年末年始の休みを利用して自由研究をしていました。Android向けのJPEG2000デコーダーライブラリ「jp2k-decoder-android」を作成したので、技術的なアプローチと、開発プロセスをまとめたいと思います。

成果物はGitHubで公開しています。

なぜ今、JPEG2000なのか

JPEG2000は、その名の通りJPEGの次世代規格として登場した画像フォーマットです。従来のJPEGよりも高圧縮・高品質で、様々な付加機能を持っていますが、ライセンスの複雑さや計算コストの高さなどもあってか、Webなどの一般用途では残念ながらあまり普及していません。

正直なところ、私自身も、このフォーマットを意識して扱うようになったのはここ数年のことです。

JPEGJPEG2000
JPEG画像JPEG2000画像(多くのブラウザで表示できない)

Androidにおいても、フレームワークレベルではJPEG2000のデコードに対応していません。そのため、アプリでJPEG2000を表示するにはサードパーティ製のライブラリを導入する必要があります。

これまでは thalesGroup/JP2ForAndroid が有力な選択肢でしたが、メンテナンスがあまりされていないという課題がありました。私自身、これをフォークしてモダナイズしたものをMaven Centralで配信したりもしていましたが、根本的な課題を感じていました。

アプローチの転換:JNIからWASMへ

根本的な課題とは、JNI(Java Native Interface)経由でネイティブコードのOpenJPEGを扱うことへの不安です。

画像CODECは、その複雑さゆえに脆弱性の温床になりやすいという事実があります。OpenJPEGも例外ではなく、過去には深刻な脆弱性が発見された経緯もあり、採用にあたっては慎重な意見も少なくありません。

信頼された情報源からの画像だけをデコードするなら一定のリスクは低減できますが、インターネットから取得した任意の画像を扱うようなケースでは、やはり不安が残ります。JNI経由で実行されるネイティブコードはアプリと同じ権限で動作するため、万が一脆弱性を突かれると影響範囲が大きくなる可能性があります。

OpenJPEG on WASM という選択

そこで今回採用したのが、WASM(WebAssembly) を利用するアプローチです。

C言語で書かれたOpenJPEGをWASMにコンパイルし、それをAndroidアプリ内で実行します。WASMはホスト環境から隔離されたサンドボックス内で動作するため、JNIで直接ネイティブコードを実行するのと比較して、比較的安全にデコード処理を行えることが期待できます。

WASM Sandbox

実行環境としては、タイミングよく安定版(1.0.0)がリリースされていた Jetpack JavaScript Engine を採用しました。これにより、Androidアプリ内で標準的と言える仕組みを使ってWASMを動かすことが可能になりました。

なぜRust書き直しやJVM実装ではないのか

もちろん、理想を言えばRustのようなメモリ安全な言語で書き直すべきかもしれません。しかし、OpenJPEGのような巨大で複雑なライブラリを短中期的に移植するのは非現実的です。かといって、JVM上で動作するデコーダーをフルスクラッチで開発するのも、学習コストや実装コストが高すぎます。

検討中に入手したJPEG2000関連の書籍

「既存の資産(OpenJPEG)は使いたい。でもリスクは排除したい」

このジレンマに対し、今回は「脆弱性をゼロにする」のではなく、「脆弱性があっても脅威になりにくい構成にする」 というアプローチを取りました。隔離された環境で動かすことで、万が一クラッシュしたり攻撃を受けたりしても、アプリ本体やユーザーデータへ波及させない。そのための隔離環境として、WASMが最適という結論に至りました。

開発プロセス:AIとのペアプログラミング

今回の開発では、技術的な検証は生成AI(Gemini)、開発プロセスにおいてはエージェント(Jules)を全面的に活用するスタイルを実践しました。

GeminiとのPoC作成(0→1フェーズ)

まず、未知の領域である「OpenJPEGのビルドからWASMへのコンパイル」、そして「それをJetpack JS Engineで動作させて画像として表示する」までのPoC(概念実証)作成をGeminiと行いました。

どういう手順でビルドすればいいのか、JS Engine側でどう受け取ればいいのか。手探りの状態から動くものを作るまでの0→1のフェーズは、Geminiとの対話が非常に効果的でした。

WASMのビルド方法について調整

Julesによるライブラリ化(1→10フェーズ)

PoCで実現可能性が見えた後は、ライブラリとして仕上げる作業全般をJulesに任せました。

全体的に、以前試した時よりもJulesが格段に賢くなっていると感じました。前回はGemini 2.5ベース、今回はGemini 3ベースになり、顕著な差を感じました。

今回、コードを書く以外にも、Julesには次のような作業を任せました。

特にDesign Docの整備は、自分が今なにを作っているのか、コードがどういう状態にあるのか、そしてどのように変えたのかを把握するために有用です。Mermaid形式でフローチャートやシーケンス図なども書いてくれるので、Design Docと実装を併せて育てる感覚で開発ができました。

Design Doc

AIが得意なこと

特に、C言語とJavaScriptの間でデータをやり取りするブリッジ部分の実装はAIの独壇場でした。

WASM Sandbox

「何をしたいか」は明確だが、具体的な言語仕様やAPIがうろ覚えだったり、書くのが面倒な処理を依頼するには最適でした。

データ受け渡しをBase64に変更するリクエスト

弱点の補完

一方で、Julesが苦手な部分もありました。それはライブラリのバージョン管理です。Julesが提案してくるライブラリのバージョンは微妙に古いことが多いのです。

いちいち「最新版にして」とお願いしても時間がかかるので、ここでは割り切りが必要でした。ひとまずJulesが知っているバージョンで実装を進めてもらい、最新版への更新は既存の仕組み(Dependabot)に任せることにしました。AIの弱点を既存ツールで補完する、現実的なワークフローだと思います。

AIに任せられなかったこと・課題

ほぼ全ての工程をAIと進めましたが、結局自分で作業することになった部分もあります。

それはMaven Centralに公開するための nmcp プラグインの設定です。何度聞いてもJulesが古いバージョンの設定方法を提示してくるため、ここばかりは自分でドキュメントを読んで設定した方が早かったです。面白いことに、Gemini(チャット画面)の方は新しいバージョンの使い方を知っていたので、同じモデルベースでも参照している知識ベースに差があるのかもしれません。

WASMのパフォーマンスと最適化の実際

実際にWASMを使ってみた感想についても触れておきます。

正直なところ、JavaScriptエンジン上で動くということで速度については多少侮っていた部分がありました。しかし、WASM部分の処理自体は十分に高速でした。JVM上でJPEG2000のデコード処理を走らせる(純粋なJava実装など)ことと比較すれば、やはりネイティブに近いWASMの方が速度面で有利だと思われます。

ボトルネックは「通信量」

一方で、明確なボトルネックも存在しました。それは JavaScript(JS Engine)の呼び出し部分 です。

内部を詳しく確認したわけではありませんが、AndroidのJetpack JavaScript Engineのサンドボックスは、アプリとは別の隔離されたプロセス上のサービスとして動作している様子です。この場合、サービスとのやり取りにはプロセス間通信(IPC)のオーバーヘッドが発生します。また、大量の画像データをJS Engineに渡したり、デコード結果を受け取ったりする部分でも時間がかかります。

実際、JS Engineとの通信量がそのまま処理時間に直結していました。

データ受け渡しの最適化

当初、JavaScript Engineとやり取りするバイナリデータをJavaScriptの配列([1, 213, 35, -1, 4...] のような形式)として取り扱っていたのですが、これは非常に遅くなりました。

そこで、データをHexString(16進数文字列)として受け取るように変更し、最終的には Base64エンコードされた文字列 でやり取りするように改善しました。この変更を行うたびに目に見えて処理時間が短縮されました。

パースコストとしてはBase64がもっとも大きくなるはずですが、少なくとも私が実験した環境ではデータ量の方が処理時間への影響が大きいことがわかりました。

「計算そのものは速いが、データの出し入れにコストがかかる」という特性を理解して実装することが、WASM活用(特にJS Engine経由の場合)の肝と言えそうです。

まとめと今後の展望

年末年始の短い時間で、新しいアプローチのライブラリを公開まで持っていけたのは大きな成果でした。README.mdやDesign Doc、CI/CD設定など、重要だけれども自分ではあまりやりたくない作業をJulesに任せられたのは非常に快適でした。

今後の課題としては、取り扱えるJPEG2000ファイルのバリエーションを増やすことです。現在は、私がよく取り扱う特定のケースのファイルが表示できることだけを確認しています。

ただ、JPEG2000の規格は非常に多機能で、その仕様の全容はあまりに広大です。正直なところ、できることが多すぎて、実際に世の中にどれほどのバリエーションのファイルが存在するのか想像もつきません。

今後も、まずは自分の手の届く範囲から、様々な用途で安全に使えるように整備を進めていきたいと思います。