gomobile で背景画像をアスペクト比を保ちつつ画面サイズにフィットさせる


この記事は [ACCESS Advent Calendar](http://qiita.com/advent-calendar/2015/access) 18日目の記事です。
ACCESS の @pankona です。

この記事は、gomobile でゲーム的なものを作ってみようと思い、背景画像を表示してみたのはいいものの、いろんな端末で表示してみたら見た目がことごとく違っていて悲しい気持ちになった人（私）が、どんな端末でもとりあえず見た目を保つために頑張った戦いの記録です。

# gomobile での画面サイズの扱われ方

メインループへのイベント `size.Event` として、
アプリ起動時、画面サイズ変更時などに通知されるようになっている。
以下の構造体が渡されてくる。

[size - GoDoc](https://godoc.org/golang.org/x/mobile/event/size) 

```go
type Event struct {
    // WidthPx and HeightPx are the window's dimensions in pixels.
    WidthPx, HeightPx int

    // WidthPt and HeightPt are the window's dimensions in points (1/72 of an
    // inch).
    WidthPt, HeightPt geom.Pt

    // PixelsPerPt is the window's physical resolution. It is the number of
    // pixels in a single geom.Pt, from the golang.org/x/mobile/geom package.
    //
    // There are a wide variety of pixel densities in existing phones and
    // tablets, so apps should be written to expect various non-integer
    // PixelsPerPt values. In general, work in geom.Pt.
    PixelsPerPt float32

    // Orientation is the orientation of the device screen.
    Orientation Orientation
}
```
これらは、
* `xxPx` と `xxPt` → Pixel 単位の情報と Pt（ポイント）単位の情報が格納されている。
* `PixelsPerPt` → Pixel/Pt の値も入っている
* `Orientation` → ついでに画面の向きも入っている

ポイントってなんぞや、思う方（私）もあるかもしれない。
そういう方にオススメのサイトは[こちら](https://ja.wikipedia.org/wiki/ポイント)だ。ウィキペディアだ、すまない。

背景画像のように画面いっぱいに画像を表示したいような場合は、
これらの情報を元に画像を引き延ばすようなアフィン変換が必要になる。

## ちなみに具体的には

size 構造体は以下のような値が格納されて渡されてくる。

### Vaio Pro 13 (Arch Linux) の場合 

```go
{
    400, // WidthPx
    400, // HeightPx
    400.00pt, // WidthPt
    400.00pt, // HeightPt
    1, // PixelsPerPt
    0  // Orientation
}
```

### Nexus 9 の場合

* 縦に向けているとき

```go
{
    1536, // WidthPx
    1952, // HeightPx
    345.60pt, // WidthPt
    439.20pt, // HeightPt
    4.4444447, // PixelsPerPt
    1 // Orientation
}
```

* 横に向けているとき

```go
{
    2048, // WidthPx
    1440, // HeightPx
    460.80pt, // WidthPt
    324.00pt, // Heightpt
    4.4444447, // PixelsPerPt
    2 // Orientation
}
```

と、こんな感じでデバイスによって値が色々変わるのである。

# 何も考えずに画像を画面いっぱいに表示させる場合

今回も Gopher くん。以下の画像を引き伸ばしてみる。
この画像はの大きさは 140x90 px。

![screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/36552/2e326f35-e460-6be7-7d87-35c8223f6c51.png)

さて、`size` 構造体には Px やら Pt やら入っているのは分かったが、
画面いっぱいに画像を拡大するにはどの値を使えばいいのか？
色々試してみたところ、結論から言えば `WidthPt` と `HeightPt` の値を使えば良いようだ。
以下のようにアフィン変換する。

```go
    // initialize affine variable
    affine = &f32.Affine{
                 {1, 0, 0}, 
                 {0, 1, 0}, 
             }

    // scale to fit screen
    affine.Scale(affine, sz.WidthPt, sz.HeihgtPt)

    // apply affine transformation
    eng.SetTransform(n, *affine)
```

この場合、元画像のアスペクト比を無視して画面サイズに引き延ばす形になる。
Nexus 9 を縦向きにした場合、↓ こんなふうになる。

<img width="400" alt="Screenshot_2015-12-17-23-22-18.png" src="https://qiita-image-store.s3.amazonaws.com/0/36552/a3c5b0eb-097a-4b6e-b6a8-4a2bd18c092c.png">

# 画像のアスペクト比を保ったままできるだけ拡大させる場合

元画像のサイズは 140x90px であるので、その比を保つように && 画面にフィットするように、という形で拡大する。
まずは、元画像のサイズと画面サイズから、拡大率を求める。

```go
var scaleFactor float32    // 拡大率
var sprite_w float32 = 140 // 画像の横幅 (pixel)
var sprite_h float32 = 90  // 画像の縦幅 (pixel)
// 下記関数内の sz は size 構造体。
func calcScaleFactor() {
    // 画像の縦横比と画面の縦横比を比較結果で、拡大率の計算の方法がちょっと変わる。
    if sprite_h/float32(sz.HeightPt) > sprite_w/float32(sz.WidthPt) {
        scaleFactor = float32(sz.HeightPt) / sprite_h
    } else {
        scaleFactor = float32(sz.WidthPt) / sprite_w
    }   
}
```

計算した拡大率で画像を拡大する。

```go
    // initialize affine variable
    affine = &f32.Affine{
                 {1, 0, 0}, 
                 {0, 1, 0}, 
             }

    // scale to fit screen
    affine.Scale(affine, sprite_w*scaleFactor, sprite_h*scaleFactor) // ← ここで拡大率を使う

    // apply affine transformation
    eng.SetTransform(n, *affine)
```

上のアフィン変換をしたときに、Nexus 9 で表示すると ↓ の画像のようになる。

（ちょっとわかりにくくなってしまったが）画像の下に白い余白が入って、
画像のアスペクト比を保ちつつ、画面いっぱいに拡大されるようになった。

* Nexus 9 を縦向きにしたとき

<img width="400" alt="Screenshot_2015-12-18-00-01-32.png" src="https://qiita-image-store.s3.amazonaws.com/0/36552/cf2e8c9d-7f95-a48b-9a28-bd50734e483d.png">


* Nexus 9 を横向きにしたとき

<img width="400" alt="Screenshot_2015-12-18-00-01-32.png" src="https://qiita-image-store.s3.amazonaws.com/0/36552/c5caec04-b1cd-0f68-00ba-268a2a90c762.png">

これでどこへ出しても歪みない背景画像を表示できるぞ！

# おわりに

ということで、gomobile での画像サイズ調整方法の例を書いてみました。
長々としてしまいましたが、ここまで見てくれた方、ありがとうございます。
これを見たどなたかの、size 構造体の使い方のヒントにでもなれば幸いです。

[ACCESS Advent Calendar](http://qiita.com/advent-calendar/2015/access)、明日の当番は @asukamirai さんです。ご期待あれ！
それではさようなら。 :wq!