なんとなく

誰得感満載な記事が多いかも。Mono関係とLinuxのサーバ関係、レビューとか。

LibOpenMetaverseとCSJ2KでSIMのMinimap画像を表示してみる

もくじ

  • はじめに
  • SLでの画像について
  • LibOpenMetaverseでのJPEG 2000の扱いについて
  • Mono for AndroidでのJPEG 2000の表示について
  • Mono for AndroidでのSIMMapの表示の実装について
  • 最後に

はじめに

セカンドライフの技術系の人が集まって、アドベントカレンダーに挑戦することになったということなので、当方も参加しています。セカンドライフ 技術系 Advent Calendar の記事です。
記事がなかなか集まらないようなので、当方としては2つ目を書いていきます。

今回は、前回割愛したSIMのMiniMapの画像の表示について見ていきたいと思います。
SLで扱われている画像形式についての説明とクロスプラットフォームのについての話、そしてLibOpenMetaverseを使用してSIMのMiniMapの画像データを取得し、表示してみます。

SLでの画像について

SLでの画像データは、
http://wiki.secondlife.com/wiki/Image_System#Definitions
で定義されているように、JPEG 2000という形式のものをサーバに保存しており、問い合わせを行うと取り寄せることができます。
Viewerから画像をアップデートする際にTGAなどでアップロードしますが、サーバサイドでJPEG 2000に変換され保存されています。

JPEG 2000について

まず、JPEG 2000とはというところを見てみたいと思います。
http://ja.wikipedia.org/wiki/JPEG_2000
によると
JPEG 2000は、静止画圧縮技術及び同技術を用いた画像フォーマットのことです。

なお、中程度の圧縮率では JPEG においても JPEG 2000 とそれほど遜色ない画質で圧縮できるが、高圧縮のときには画質の差は顕著になる。
実際、 JPEG では100分の1未満に圧縮した場合には、有り得ない色や模様になるなど酷い劣化が生じるが、JPEG 2000では確かに画質は劣化しているものの自然である。
また、JPEG 2000では可逆圧縮にも対応しているので、低圧縮の場合においては全く画像を劣化させることなく圧縮が可能である。

とあります。JPEGよりも圧縮率を高くできるし、ロスレス圧縮が可能という特性があるようです。

しかし、http://wiki.secondlife.com/wiki/Image_System#Format

JPEG-2000 is a highly efficient compression method, allowing for very small compressed file sizes,
which in turn helps to reduce Linden Lab's asset storage.
However, the cost to users of the Second Life client is that JPEG-2000 is much more processor intensive than other compression image methods,
and so is a major contributing factor for the slow "rezzing" of textures by the client,
even when these compressed textures exist in a local disk cache.

とあります。

サーバサイドの容量については、他のファイル形式よりも高圧縮できるので、ファイル容量はある程度小さくなり、その分サーバサイドの容量は他のファイル形式に比べると少なくすみます。
しかし、サーバからクライアントサイドにJPEG 2000の形式のデータを取得した後、表示するための形式に展開する際の計算時間というコストが他の形式に比べてかなりかかります。
設計当時は、JPEG 2000にするという判断を下したことについては、

  • 当時のストレージ容量や通信回線
  • JPEG 2000は次世代の画像ファイル形式と言われていて、主流になるであろうと想定されていた。(現状、残念ながらそうはなっていません。iOS5からブラウザでJPEG 2000に対応しています。次に主流になる画像のファイル形式は、JPEG2000、JPEG XR*1、WebP*2と現状三つ巴のようです。)

上記2点を考えると納得はできます。

また、現状では、ストレージ容量の増加を考えるとファイルの圧縮に対する計算時間というコストと容量が見合わないようですし、ファイルの展開に時間のコストがかかりすぎ*3、JPEG 2000であるところのメリットが享受できないと考えられます。ましてやスマートフォンなどのある程度なスペックのものが対象だと他のファイル形式と比べてしまうと利用にあたってコストは高いです。

現状で考えてしまうとメリットがあまりないJPEG 2000ですが、SLにおいては、クライアントサイドのデメリットを解消するために、とくに3Dのテクスチャについて、

  • Discard level
  • Mip Mapping

で対応しているようです。

http://wiki.secondlife.com/wiki/Image_System#Discard_Level_and_Mip_Mapping

Discard level
Discard level 0 represents the highest resolution version of a texture.
Discard level 1 represents a texture half the resolution of discard level 0 in each dimension, so one quarter of the pixel area and one quarter of the required texture memory.
It is primarily a function of total pixel area, e.g. if a 256x256 texture is covering a 128x128 portion of the screen, the desired discard level is 1.
Mip Mapping is a technique where multiple resolutions of a texture are stored in order to optimize rendering.
To support mipmapping, when discard level 0 is loaded, discard levels 1-N are also loaded (where N represents the smallest useful image). e.g. When a 256x128 texture is loaded, the following textures are also generated and loaded: 128x64, 64x32, 32x16, 16x8, 8x4.
This incurs a 33% increase in the amount of texture memory consumed, but makes a significant improvement in performance and visual quality.

見た目はなんとなくで雰囲気で分かる程度でクライアントサイドのコストを抑えたものが実現できるようです。Android用のViewerのLumiyaでも使用されているようです。*4

現状ではデメリットの多いJPEG 2000ですが、SLにおいてデータがサーバにJPEG 2000で格納されている事実は変わりなく、Viewer側で画像を表示するためにはJPEG 2000形式からの変換が必要です。

LibOpenMetaverseでのJPEG 2000の扱いについて

LibOpenMetaverseにおいてJPEG 2000については、openjpegプロジェクト*5のライブラリを使用し、変換しています。
しかし、openjpegプロジェクトのライブラリは、ソース内に

[System.Security.SuppressUnmanagedCodeSecurity]

とあるようにx86(32bit,64bit)用のunmanaged*6なライブラリです。
つまり、LibOpenMetaverseで、画像を描画したい場合、対象のCPU(x86系)以外はサポートしていません。個人的にはC#なのだからunmanagedなライブラリは余り使うべきではないと考えるのですが、母集団が少ないということと計算速度(最適化、初期起動)という点を考えると妥当な選択だと思います。
なお、後述するJPEG 2000を変換し、bitmapにして表示する実装においては、LibOpenMetaverseを使用していません。実装環境が特殊(ARMのCPUを使っている)なため、LibOpenMetaverseで実装しているものでは使用できないためです。もちろん、x86なWindows,MacそしてLinuxでは使用することができます。

クロスプラットフォームでのライブラリの利用について

ちょっと今回の本筋とはずれ、余談になりますが、クロスプラットフォーム*7でのライブラリの利用について触れていきたいと思います。

PCとして使われているもので、現状ほぼほぼWindowsもMacもx86系のCPU上で動くものなので、それ以外の環境であえて使うということがあまりありません。
ただし、LinuxAndroidで実現しようとするとx86以外のCPUであるPowerPCやARM,MIPSなど他のCPUも顔を出してきます。
また、LibOpenMetaverseは「C#なんだからWindowsだけなんでしょ」というのは、全く無くて、ソリューションとしてはmonoを用いることによって実現することができます。ただ上述したように画像に関しては、unmanagedな外部ライブラリを呼びだしている関係で、x86系のCPUに依存することになります。

以下は、CPUがPowerPClinuxでLibOpenMetaverseを使用して動く、PC版のdvgtuViewerを実行したところです。本来SIMのMINMapを表示するところのデータが表示できていません。(ただ、これは前述したLibOpenMetaverseのJPEG 2000の扱いの例ではなく、あくまでも異なるCPUでも動くという例です。)

同じ実行ファイルで、Linuxでも動きますし、Windowsでも、そしてMacでも。。。下に表示する画像はCPUはx86系です。

ライブラリを作成する際には、各々のネイティブで最適化できるライブラリか、クロスプラットフォームを考慮する場合は、とことんmanagedになるしかないのではないかと思います。LibOpenMetaverseで唯一残念なところが画像表示周りのところです。

Mono for Android でのJPEG 2000の表示について

Mono for Android でのJPEG 2000の表示についてです。
Androidでは標準でJPEG 2000をサポートしていませんから、標準で表示できる形式に変換する必要があります。JPEG 2000からAndroid表示できるbitmapに変換するのが妥当です。
また、AndroidのサポートしているCPUはARM,x86,MIPSですので、考慮が必要なこともあります。
LibOpenMetaverseでは、x86しかサポートしていませんので、AndroidのCPUの大半を占めるARMでどう実装するかが問題となってきます。

では、Mono for Android でどう実現したかというと、選択肢は以下に示す3点がありました。

  1. openjpegをコンパイルしてJNIとして認識して使用する方法
  2. JJ2000*8をbindingして使用する方法
  3. CSJ2K*9を変更する方法

1.については、
LibOpenMetaverseと同様の考えで、CPUごとにライブラリが必要となります。AndroidはARM,x86,MIPSをサポートしているようです。
そのため、openjpegAndroidのNDKでクロスコンパイルして、CPUごとに準備する必要があります。上述しましたが、せっかくmanagedなC#なのに、unmanagedな実装をすることでなんか矛盾しているような気がします。ただ、速度についてはそれなりの物が得られると思います。*10そういう理由で実装しませんでした。

2.については、
はじめは、2.で実現しようと考えていました。
JJ2000とはjavaでJPEG 2000を表示させるためのライブラリで、3.にでてくるCSJ2Kの元ネタ*11です。
Mono for Androidでは、jar*12C#のライブラリとして使うことのできる(bindingできる)仕組みがあります。
しかし、実際にJJ2000をAndroidで動くようにした後、作成されたjarファイルをMono for Androidを使用し、bindingしたところ、不可解なエラーが出て、解決に時間がかかりそうで、簡単に解決できそうになかったのであきらめました。

3.については、
エンディアン*13の面での心配があります。
C#エンディアンが固定されているわけではなく、各々のCPUのエンディアンを見ているようです。そのため、リトルとビッグを判定し、実装する必要があります。ただ、Mono for Androidの構造*14から言うと、VMが吸収してくれるはずなのですが、ちょっとここらへんは勉強が足りません。。。

2.の経緯もあり、3で実装することにしました。
CSJ2Kのソースはそのままではうごかなかったため、一部修正して動くようにして使っています。
修正した点は2点ほどあって

  • System.Drawing以下がMono標準とMono for Androidとの差異部分の修正
  • System.IO以下でMonoでは実装されているが、Mono for Androidでは実装されていないものの修正

です。

CSJ2Kの実装はリトルエンディアンにしか対応していないため、AndroidMIPSでビッグエンディアン可能なFroyoでは、CSJ2Kでは表示できなくなります(たぶんです。検証してないです。もしかしてVMが吸収してくれるのだろうか?)。
しかし、AndroidでビッグエンディアンをサポートしているのはFroyo(Android2.2)だけのようなので、考慮しなくてもいいということもできますが、母集団の少ないCPUを見捨てるということはあまりしたくなかったのですが、調査できていないのは残念です。今後調査したいですが、対象の機器?を入手できるのでしょうか?エミュレータでいけるの?というところです。

ちなみに上に表示したPowerPCLinuxで表示できなかったSIMMapの画像については、CSJ2Kを使って描画しています。
PowerPCはバイエンディアンで、Linuxの標準ではビッグエンディアン動くようです。CSJ2Kがリトルエンディアン向けにしか書かれていないため表示できません。

CSJ2Kは、ビッグエンディアン対応とbitmapの書き出しの高速化を図りたいです。

Mono for AndroidでのSIMMapの画像の表示の実装について

では、実際にLibOpenMetaverseでデータを取得し、CSJ2Kでbitmapに変換して、表示させてみます。

画像データの取得方法は以下です。

  • Openmetaverse.TextureDownloadCallbackというコールバックがあるので、画像データを取得するためのdelegate型のコールバックを用意します。
  • 現在いるSIMのグリッド情報を得て、グリッド情報にあるSIMのMAPIDをサーバ(グリッド)にリクエストします。
  • 取得したデータをCSJ2Kを使用してJPEG 2000からbitmapに変換します。

実際にソースでは、

//入れ物の準備
private TextureDownloadCallback TextureDownloadCallback;
private Bitmap pSimMap;

delegate型のコールバックを用意して、そこに渡してあげれば、別スレッドで取得してくれます。

//Openmetaverse.TextureDownloadCallbackに渡す
TextureDownloadCallback += new TextureDownloadCallback(SimTextureDownloadCallback);

現在いるSIMの情報を得て、SIMのイメージについてサーバにリクエストします。
LSLでも同じような感じなのをどこかで見たような気がします。

GridRegion gRegion;
//現在のSIMを得る
string SimName = Client.Network.CurrentSim.Name;
//リージョンの情報を得る
Client.Grid.GetGridRegion(SimName, GridLayerType.Objects , out gRegion);
//SIMのイメージをリクエストする
Client.Assets.RequestImage(gRegion.MapImageID,ImageType.Baked,SimTextureDownloadCallback);

得られるデータはJPEG 2000なので、bitmapに変換して格納します。

void SimTextureDownloadCallback(TextureRequestState state, AssetTexture assetTexture)
{
	if (state != TextureRequestState.NotFound || state != TextureRequestState.Timeout)
	{
		//JPEG 2000からbitmapに変換する。動くようにしたCSJ2Kを使う
		pSimMap = CSJ2K.J2kImage.FromBytes(assetTexture.AssetData);
	}	
}

前回のSIMにいるアバターの位置の描画と合わせると
以下のようになります。

SIMのMapだけではなく、Profileの画像も同様にして、取得することができます。

左のリストはフレンドリストにしようと思ったのですが、SIMからFull Nameを取得できなかったので、UUIDになってます。
画像はTakeshich Residentさんです。

公式Viewerではこんな感じになります。

最後に

JPEG 2000からクロスプラットフォームの話まで、かなり深いところまでの話になりました。
JPEG 2000を見ていくとそれを採用したSLの設計は、スマートフォンやタブレット向きではないなとつくづく感じました。
そもそも設計当時は、発表されていませんし。ただ、デメリットを補うためのある程度の考慮がされていて、それには感心しました。
時を経れば、ハードのスペックが上がるのは当たり前ですので、数年後はこのような話も話題にならないのかもしれません。

また、LibOpenMetaverseのJPEG 2000の変換部分において、クロスプラットフォームなライブラリのあり方についても考えさせられました。

この記事で

  • どうしてSLはJPEG 2000を採用しているんだろうという点
  • 他のファイル形式に比べるとJPEG 2000は展開する際に計算時間がかかるという点
  • そしてViewerサイドの実装する際の苦労^^;

について知っていただければ幸いです。

*1:http://ja.wikipedia.org/wiki/JPEG_XR

*2:http://ja.wikipedia.org/wiki/WebP

*3:http://www.slideshare.net/FukushimaNorishige/webp 17,18ページ参照されるとJPEG 2000の計算時間のコストを視覚的に理解できると思います。

*4:http://www.lumiyaviewer.com/index.php/features/3d-view

*5:http://code.google.com/p/openjpeg/

*6:http://e-words.jp/w/E382A2E383B3E3839EE3838DE383BCE382B8E38389E382B3E383BCE38389.html

*7:http://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%82%B9%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0

*8:http://code.google.com/p/jj2000/

*9:http://csj2k.codeplex.com/

*10:そのうち調べたい

*11:CSJ2KはJJ2000からの移植です

*12:http://ja.wikipedia.org/wiki/Java_Archive

*13:http://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%B3%E3%83%87%E3%82%A3%E3%82%A2%E3%83%B3

*14:http://docs.xamarin.com/Android/Guides/Advanced_Topics/Architecture