高機能な画像ビューワーであるOpenSeadragon(以下、OSD)を使った小ネタ。
カスタムタイルソース
OSDでは、IIIFやDZIのように決められたフォーマット以外にも、生のタイル画像をそのまま扱えるパターンもある。
で、それをやるにはこんなコードを書く。
new OpenSeadragon.Viewer({ // ... tileSources: { height: 512 * 256, width: 512 * 256, tileSize: 256, getTileUrl: (level, x, y) => "http://s3.amazonaws.com/com.modestmaps.bluemarble/" + `${level - 8}-r${y}-c${x}.jpg`, }, });
ここまでは、ドキュメントにも書いてある通り。
この場合の実装的な挙動としては、内部的に`Image`オブジェクトを生成して、その`src`にこの`getTileUrl()`で得られるパスがそのまま使われるようになってる。
で、これの問題は、タイル画像に対して、静的なパス + 単なるGETでアクセスできない場合。
などなどの場合に、どうするか。
1. オプションで解決する
実は`Viewer`や`TileSources`を指定するときに、いくつか渡せるオプションがあって、それで挙動を変えて解決できるものもある。
- `crossOriginPolicy`: `Image`で読み込む場合の`crossorigin`属性
- `loadTilesWithAjax`: `true`なら、`XMLHttpRequest`のラッパーである`$.makeAjaxRequest()`でGETするようになる
- `ajaxHeaders`: その際のカスタムヘッダー
- `ajaxWithCredentials`: その際の`withCredentials`
単にヘッダがついてればいい場合は、こういうのでいい。
ほかにも、`tileSources`を文字列URLで指定した場合に、そのURLの`#`以降をPOSTで送信できる?風の、`splitHashDataForPost`なるオプションもあるらしい。
(というか、高機能すぎて何ができるのか未だに把握できないし、この場合はこれが使えないとかそういうのもわかってない。)
2. コードを上書きして解決する
オプションだけでは解決できない場合の奥の手。
たとえばS3とかにタイル画像を置いてて、SDKでしかそれを取得できない場合などに。
さっきから見ての通り、`getTileUrl()`のシグネチャは`() => string`ではあるが、`OpenSeadragon.TileSource`クラスの一部を差し替えることで、非同期にできる。
これを事前に実行しておけば、`getTileUrl()`のシグネチャを`() => Promise
`() => string`以外を返すようにした場合は、`hasTransparency(): boolean`がエラーにならないようにケアする必要があるらしい。
もしくは、`getTileUrl()`はそのまま同期で文字列を返しつつ、ロジック側でよしなに非同期することもできる。
const url = context.src; const ac = new AbortController(); mySdk.getObject(mySdk.toParams(url), ac.signal) .then((blob) => { image.src = URL.createObjectURL(blob); }) .catch((err) => finish(err.message)); // XXX: Shoud be `XMLHttpRequest` but `abort()` is only used dataStore.request = ac;
なんかのタイミングで`revokeObjectURL()`したい気持ちもあるけど、もともとのロジックでもやってなかったので仕方ない。
というわけで、ココのあたりをいじれば、だいたいのことは実現できそうである。ただし、裏技なので本体アップデートによりいつ動かなくなってもおかしくない。