glossではじめるグラフィック描画 :: Haskell入門の次に読む記事

[gloss](http://gloss.ouroborus.net/)はとても簡単にグラフィックを描画できるHaskellのライブラリです。この記事はgloss入門ということで関数・型・型クラスなどのHaskellの基本的な文法は理解した人向けにglossの使い方を丁寧に書いていきたいと思います。

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/a934b91e-d2ee-7025-5da4-d9004b114cd4.png">

環境構築
--------
プロジェクトの管理はstackを使って進めていきます。stackを知らない/まだ使っていない人は以下の記事を参考にstackを使える環境を構築してください。

* [Stackを使ったHaskellのインストール](https://qiita.com/KNaito/items/98debf322e08c706fc90)
* [Stackでやる最速Haskell Hello world! (GHCのインストール付き！)](https://qiita.com/igrep/items/da1d8df6d40eb001a561)
* [Haskell Stack とは何をするツールなのか](https://qiita.com/usamik26/items/672ed3c4451402bfc275)

`stack`コマンドが使えるようになったら早速プロジェクトを作りましょう。

```shell
$ stack new gloss-tutorial
```

以下のようなファイルが生成されるかと思います（ちなみに使用しているstackのバージョンは1.7.1です）。

```shell
$ tree gloss-tutorial
gloss-tutorial
├── ChangeLog.md
├── LICENSE
├── README.md
├── Setup.hs
├── app
│   └── Main.hs
├── gloss-tutorial.cabal
├── package.yaml
├── src
│   └── Lib.hs
├── stack.yaml
└── test
    └── Spec.hs
```

glossを使うためにライブラリの依存関係を指定します。`package.yaml`を開いてexecutablesの項目に以下のようにglossへの依存を記述してください。

```diff:package.yaml
 executables:
   gloss-tutorial-exe:
     main:                Main.hs
     source-dirs:         app
     ghc-options:
     - -threaded
     - -rtsopts
     - -with-rtsopts=-N
     dependencies:
+    - gloss
     - gloss-tutorial
```

以上でglossを使ったプログラミングを始める準備が整いました！

Hello World
------------
まずは何も考えずに定番の"Hello World"と画面に出力するプログラムを書いてみましょう。`app/Main.hs`を以下のように編集します。

```hs:app/Main.hs
module Main where

import Graphics.Gloss

window :: Display
window = InWindow "Hello World" (640, 480) (100, 100)

main :: IO ()
main = display window white (text "Hello world")
```

保存したら以下のようにコマンドを打って実行してみましょう。

```shell
$ stack build
$ stack exec gloss-tutorial-exe
```

初回のビルドだとglossを依存関係を含めてインストールするのに時間がかかるかもしれません。うまく実行できれば以下のような画面が表示されるはずです。

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/a9ee3ed8-a67c-7db1-8bfd-3375c2558787.png">

"World"がどこかに行ってしまいましたね？
文字が大きすぎることが原因のようで画面からはみ出してしまってるみたいです。文字の大きさを小さくしてついでに画面の中央に文字が表示されるように移動させてみましょう。`app/Main.hs`のmain関数を以下のように編集します。

```hs:app/Main.hs
main :: IO ()
main = display window white
  (translate (-150) (-10) . scale 0.5 0.5 $ text "Hello world")
```

ビルドして実行してみましょう。

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/6d3da274-c66d-8246-abcc-99f856879ff3.png">

ちゃんと文字が真ん中に表示されましたね！

glossの基本
-----------
それでは順を追ってglossの使い方を見ていきましょう。glossを使うときはまず[Graphics.Gloss](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss.html)をimportするのが基本です。この中でまず紹介したいのはプログラム全体が何を行うかを決める`display`, `animate`, `simulate`, `play`という４つの関数です。以下ではこれらの関数についてそれぞれ説明していきます。

### display
Hello Worldでも使用した[`display`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss.html#v:display)関数は単純なグラフィックを描画するために使われます。

```hs
display :: Display -- ^ 描画モード
        -> Color   -- ^ 背景色
        -> Picture -- ^ 描画するグラフィック
        -> IO ()
```

型を見ればすぐわかるように、描画モード・背景色・描画するグラフィックの３つを指定すればウィンドウを生成し、そのとおりにグラフィックを描画してくれます。

```hs
data Display = InWindow String (Int, Int) (Int, Int)
             | FullScreen
```

`Display`型は２つの値から成ります。`InWindow`はウィンドウのタイトル・ウィンドウのサイズ・ウィンドウのポジションを指定して設定したとおりのウィンドウを表す値となります。`FullScreen`は文字通りフルスクリーンモードです。

`Color`型は文字通り色を表す方です。[`makeColor`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Color.html#v:makeColor)という関数を使えばRGBAを指定して色を作ることができます。

```hs
makeColor :: Float -- ^ Red
          -> Float -- ^ Green
          -> Float -- ^ Blue
          -> Float -- ^ Alpha
          -> Color
```

また`black`, `whit`, `red`, `blue`, `green` などといった色を表す関数もデフォルトで用意されているので便利に使えます。`Color`に関する型は[Graphics.Gloss.Data.Color](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Color.html)にまとまっているので一度目を通しておくと良いでしょう。

試しに背景色をシアンにしたウィンドウを作ってみましょう。

```hs:app/Main.hs
main :: IO ()
main = display window cyan blank
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/0eb0e8ac-d3f1-4eba-89db-763354d476de.png">

ちゃんと背景色が変わりましたね！

`Picture`については後の章で詳しく説明していきます。

### animate
[`animate`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss.html#v:animate)関数は文字通りアニメーションを描画するための関数です。

```hs
animate :: Display            -- ^ 描画モード
        -> Color              -- ^ 背景色
        -> (Float -> Picture) -- ^ 次のフレームのグラフィックを生成する関数
                              -- ^ プログラムが開始してから経過した時間が秒単位で引数として与えられる。
        -> IO ()
```

さっきの`display`関数と比較すると3番目の引数が関数になってることがわかります。この関数にはプログラムが開始してから経過した時間が与えられるので、その時間で表示するグラフィックを返すことでアニメーションを生成することが出来るというわけです。

例として円が時間とともに大きくなっていくようなプログラムを書いてみましょう。

```hs:app/Main.hs
main :: IO ()
main = animate window white (\t -> circle (5 * t))
```

ビルドして実行してみましょう。

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/c111f171-f94b-081e-7cdd-ddc582bb63ed.gif">

じわっと円が大きくなっていくアニメーションが作れましたね！

`animate`を使って[マンデルブロ集合を描画した例](http://inf.ufrgs.br/~morprates/code/mandelbrot/)もあって面白いですよ。

### simulate
[`simulate`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss.html#v:simulate)関数は状態を管理することが可能なシミュレーションを行うための関数です。

```hs
simulate :: Display            -- ^ 描画モード
         -> Color              -- ^ 背景色
         -> Int                -- ^ 1秒あたりのステップ数
         -> model              -- ^ 初期状態
         -> (model -> Picture) -- ^ 状態を描画する関数
         -> (ViewPort -> Float -> model -> model) -- ^ 現在の状態を次の状態に発展させる関数
                                                  -- ^ 引数としてビューポートと前のステップから経過した時間が与えられる
         -> IO ()
```

* 状態
* 状態を描画する関数
* 現在の状態を次の状態に発展させる関数

の3つを用意するのがポイントです。

例としてよくあるスクリーンセーバーを作ってみましょう。

```hs:app/Main.hs
module Main where

import Graphics.Gloss
import Graphics.Gloss.Data.ViewPort

-------------------
-- Display の設定
-------------------

windowWidth, windowHeight :: Num a => a
windowWidth  = 640
windowHeight = 480

window :: Display
window = InWindow "Hello World" (windowWidth, windowHeight) (100, 100)

--------------------------
-- シミュレーションの実装
--------------------------

boxWidth, boxHeight :: Float
boxWidth  = 50
boxHeight = 50

data BoxState = BoxState
  { _x  :: Float -- x 座標の位置
  , _y  :: Float -- y 座標の位置
  , _vx :: Float -- x 方向の速度
  , _vy :: Float -- y 方向の速度
  }

initialBox :: BoxState
initialBox = BoxState 0 0 150 150

drawBox :: BoxState -> Picture
drawBox box = translate (_x box) (_y box) $ rectangleSolid boxWidth boxHeight

nextBox :: ViewPort -> Float -> BoxState -> BoxState
nextBox vp dt box =
  let -- 速度を考慮した次のステップでの位置を計算
      x  = _x box + _vx box * dt
      y  = _y box + _vy box * dt

      -- 壁との当たり判定
      isOverTop    = y >  (windowHeight - boxHeight) / 2
      isOverBottom = y < -(windowHeight - boxHeight) / 2
      isOverRight  = x >  (windowWidth - boxWidth) / 2
      isOverLeft   = x < -(windowWidth - boxWidth) / 2

      -- 壁と衝突していれば速度を反転させる
      vx = if isOverRight || isOverLeft   then (-_vx box) else (_vx box)
      vy = if isOverTop   || isOverBottom then (-_vy box) else (_vy box)

   in BoxState x y vx vy

-------------
-- main 関数
-------------

main :: IO ()
main = simulate window white 24 initialBox drawBox nextBox
```

ビルドして実行してみましょう。

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/a9097f5a-3362-20ae-2ec8-4ffc86a438e4.gif">

よくあるスクリーンセーバーが作れましたね！

細かいテクニックですが

```hs
windowWidth, windowHeight :: Num a => a
```

このように型を指定しておくことで`windowWidth`,`windowHeight`をIntとしてもFloatとしても使えたりするので便利です。

`simulate`関数を使えば運動方程式を解く物理シミュレーションだったりライフゲームなんかも作ることができます。

例の中では使いませんでしたが`ViewPort`について説明しておきましょう。実はこれまでに紹介した`display`, `animate`, `simulate`の3つの関数にはデフォルトで以下の機能が備わっています。

* 終了
    * Escで終了する
* 画面の移動
    * 方向キーで移動する
    * 左クリック＆ドラッグで移動する
* ズーム
    * page up/downキーでズームイン/アウト
    * Control+左クリック&ドラッグでズームイン/アウト
    * 右クリック&ドラッグでズームイン/アウト
    * マウスホイールでズームイン/アウト
* 回転
    * home/endキーで回転
    * Alt+左クリック＆ドラッグで回転
* ビューポートのリセット
    * `r`キーを押してリセット

多機能ですね！`ViewPort`にはユーザーによって操作された現在のビューポートの情報が入っています。

```hs
data ViewPort = ViewPort
  { viewPortTranslate :: !(Float, Float) -- 移動した座標
  , viewPortRotate    :: !Float          -- 回転した角度
  , viewPortScale     :: !Float          -- ズームしたスケール
  }
```

### play
[`play`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss.html#v:play)関数はシミュレーションに加えて、マウスやキーボードのイベントを取得することが出来るためユーザーとのインタラクティブなアクションを実装することができる関数です。

```hs
play    :: Display                   -- ^ 描画モード
        -> Color                     -- ^ 背景色
        -> Int                       -- ^ 1秒あたりのステップ数
        -> world                     -- ^ 初期状態
        -> (world -> Picture)        -- ^ 状態を描画する関数
        -> (Event -> world -> world) -- ^ イベントを処理して状態を発展させる関数
        -> (Float -> world -> world) -- ^ 現在の状態を次の状態に発展させる関数
                                     -- ^ 引数として前のステップから経過した時間が与えられる
        -> IO ()
```

`simulate`関数との違いはViewPortが引数に与えられなくなったところとイベントを処理して状態を発展させる関数が増えたところだと思います。`Event`は全てのイベントを表す型です。

```hs
data Event = EventKey    Key KeyState Modifiers (Float, Float) -- ^ キーボードの押下とマウスのクリックイベント
           | EventMotion (Float, Float)                        -- ^ マウスの移動イベント
           | EventResize (Int, Int)                            -- ^ ウィンドウのリサイズイベント
           deriving (Eq, Show)
```

１つずつ説明していきましょう。`EventKey`はキーボードの押下とマウスのクリックを表すイベントで、４つの値を保持しています。

|型|説明|
|:----:|:----:|
|`Key`|押下されたキーもしくはマウスのクリックの種類|
|`KeyState`|押されたのか、離されたのか|
|`Modifiers`|Shift, Ctrl, Altキーの状態|
|`(Float, Float)`|マウスカーソルの位置|

`Key` は以下のような型になっています。

```hs
data Key = Char        Char        -- 文字キー
         | SpecialKey  SpecialKey  -- 特殊キー
         | MouseButton MouseButton -- マウスクリック
         deriving (Show, Eq, Ord)
```

abcなど通常も文字キーが押された場合は`Char`に押された文字が保持された状態で渡されます。`SpecialKey`はスペースや十字キーやファンクションキーなどです。非常に種類が多いのでリンク先のリストを見てもらえると理解しやすいかと思います（[`SpecialKey`の定義](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Interface-IO-Interact.html#t:SpecialKey)）。`MouseButton`は文字通りクリックされたマウスのボタンの種類で以下のような型になっています。

```hs
data MouseButton = LeftButton           -- ^ 左クリック
                 | MiddleButton         -- ^ ホイールクリック
                 | RightButton          -- ^ 右クリック
                 | WheelUp              -- ^ ホイール上回転
                 | WheelDown            -- ^ ホイール下回転
                 | AdditionalButton Int -- ^ その他のボタン
                 deriving (Show, Eq, Ord)
```

`KeyState`は単純で以下のような型です。

```hs
data KeyState = Down -- ^ 押された
              | Up   -- ^ 離された
              deriving (Show, Eq, Ord)
```

`Modifiers`にはShift, Ctrl, Altの状態がレコードとして保持されています。

```hs
data Modifiers = Modifiers
  { shift :: KeyState -- ^ Shiftキーの状態
  , ctrl  :: KeyState -- ^ Ctrlキーの状態
  , alt   :: KeyState -- ^ Altキーの状態
  } deriving (Show, Eq, Ord)
```

2つ目のイベントの値である`EventMotion`はウィンドウ上でマウスを移動させたときのウィンドウの座標でのマウスカーソルの位置を保持しています。

3つ目のイベントの値である`EventResize`はウィンドウの大きさが変更された時に変更後のウィンドウのサイズを保持しています。

例として方向キーで移動する四角形を作ってみましょう。

```hs:app/Main.hs
module Main where

import Graphics.Gloss
import Graphics.Gloss.Interface.IO.Game

-------------------
-- Display の設定
-------------------

windowWidth, windowHeight :: Num a => a
windowWidth = 640
windowHeight = 480

window :: Display
window = InWindow "Hello World" (windowWidth, windowHeight) (100, 100)

--------------------------
-- シミュレーションの実装
--------------------------

boxWidth, boxHeight :: Float
boxWidth  = 50
boxHeight = 50

data BoxState = BoxState
  { _x  :: Float -- x 座標の位置
  , _y  :: Float -- y 座標の位置
  , _vx :: Float -- x 方向の速度
  , _vy :: Float -- y 方向の速度
  }


initialBox :: BoxState
initialBox = BoxState 0 0 0 0

drawBox :: BoxState -> Picture
drawBox box = translate (_x box) (_y box) $ rectangleSolid boxWidth boxHeight

-- | イベントを処理する関数。EventKey以外のイベントは無視する
updateBox :: Event -> BoxState -> BoxState
updateBox (EventKey key ks _ _) box = updateBoxWithKey key ks box
updateBox (EventMotion _)       box = box
updateBox (EventResize _)       box = box

-- | 上下左右の速度を与える関数
up, down, right, left :: BoxState -> BoxState
up    box = box { _vy = _vy box + 100 }
down  box = box { _vy = _vy box - 100 }
right box = box { _vx = _vx box + 100 }
left  box = box { _vx = _vx box - 100 }

-- | 方向キーとWASDキーに対応して四角形を移動させる
updateBoxWithKey :: Key -> KeyState -> BoxState -> BoxState
updateBoxWithKey (SpecialKey KeyUp)    ks = if ks == Down then up    else down
updateBoxWithKey (SpecialKey KeyDown)  ks = if ks == Down then down  else up
updateBoxWithKey (SpecialKey KeyRight) ks = if ks == Down then right else left
updateBoxWithKey (SpecialKey KeyLeft)  ks = if ks == Down then left  else right
updateBoxWithKey (Char 'w')            ks = if ks == Down then up    else down
updateBoxWithKey (Char 's')            ks = if ks == Down then down  else up
updateBoxWithKey (Char 'd')            ks = if ks == Down then right else left
updateBoxWithKey (Char 'a')            ks = if ks == Down then left  else right
updateBoxWithKey _ _ = id

nextBox :: Float -> BoxState -> BoxState
nextBox dt box =
  let -- 速度を考慮した次のステップでの位置を計算
      x  = _x box + _vx box * dt
      y  = _y box + _vy box * dt

   in box { _x = x, _y = y }

-------------
-- main 関数
-------------

main :: IO ()
main = play window white 24 initialBox drawBox updateBox nextBox
```

ビルドして実行してみましょう。

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/c2d3f3b7-2f66-6e8d-f9cc-9a5a04cbe020.gif">

うまく動いていますね！

play関数の中で動的にファイルを読み込んだり外部に通信したりしたくなることがあるかもしれません。その時は[`Graphics.Gloss.Interface.IO.Game`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Interface-IO-Game.html)で公開されている[`playIO`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Interface-IO-Game.html#v:playIO)関数が使えます。

```hs
playIO  :: Display
        -> Color
        -> Int
        -> world
        -> (world -> IO Picture)
        -> (Event -> world -> IO world)
        -> (Float -> world -> IO world)
        -> IO ()
```

play関数とほとんど同じ型ですが、描画・イベントハンドリング・状態を発展させる関数それぞれの返り値がIOに包まれているためこれらの関数の中で副作用を起こすことができます。

Picture
--------
[`Picture`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Picture.html#t:Picture)はglossでのグラフィック描画において一番大事な概念です。

```hs
data Picture =
             -- | プリミティブ系
               Blank                                 -- ^ なにもない空白のPicture
             | Polygon       Path                    -- ^ 内部が塗りつぶされた多角形
             | Line          Path                    -- ^ パスに沿った線
             | Circle        Float                   -- ^ 与えられた半径の円（内部は塗りつぶされていない）
             | ThickCircle   Float Float             -- ^ 厚みのある円
                                                     -- ^ thicknessが0となるときはCircleに一致する
             | Arc           Float Float Float       -- ^ 与えられた２つの角度の間の円弧
             | ThickArc      Float Float Float Float -- ^ 与えられた２つの角度の間の厚みのある円弧
                                                     -- ^ thicknessが0となるときはArcに一致する
             | Text          String                  -- ^ ベクターフォントで描画されるテキスト
             | Bitmap        Int     Int     BitmapData Bool -- ^ ビットマップデータ

             -- | 変換系
             | Color         Color           Picture -- ^ 特定の色で塗られた Picture
             | Translate     Float Float     Picture -- ^ x, y軸方向に移動したPicture
             | Rotate        Float           Picture -- ^ 回転したPicture
             | Scale         Float   Float   Picture -- ^ x, y方向にスケールしたPicture

             -- | 複合系
             | Pictures      [Picture]               -- ^ いくつかのPictureをまとめたもの
             deriving (Show, Eq, Data, Typeable)
```

それぞれの値構築子に対応した小文字の関数も用意されており、通常はそちらを使ってPictureを組み立てていきます。それぞれどのような図形が描画できるのか、１つずつ見ていきましょう。

### Blank
`Blank`はなにもないPictureです。実際に表示してみましょう。

```hs:app/Main.hs
import Graphics.Gloss

window :: Display
window = InWindow "Hello World" (640, 480) (100, 100)

picture :: Picture
picture = blank

main :: IO ()
main = display window white picture
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/e5a3b001-82eb-d4f3-bac4-5855941a1480.png">

本当に何も表示されず、背景色の白が見えているだけですね。Blankがなんのために存在するかというと`Picture`は`Monoid`のインスタンスになっており、例えばfold関数で複数のPictureを足し合わせたりする時に単位元の役割を果たしてくれるのがBlankとなります。

Blankの例で使った `app/Main.hs` のコードはpicture関数を差し替えながら今後も使いまわして行くことにしましょう。

### Polygon
Polygonは多角形を描画するためのPictureです。保持している`Path`は

```hs
type Path = [Point]
type Point = (Float, Float)
```

と定義されていて(x, y)平面の点のリストになっています。このリストで指定された点に沿って多角形が描画されるというわけです。実際に表示してみましょう。

```hs:app/Main.hs
picture = polygon [(0, 150), (150, 50), (100, -100), (-100, -100), (-150, 50)]
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/c11ee605-3818-e282-8a61-731ad080e0b0.png">

簡単に五角形を書くことができました！

特に四角形はよく使う図形ですが、毎回Pathを指定するのは手間です。glossでは[`rectangleSolid `](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Picture.html#v:rectangleSolid)という幅と高さを指定して四角形のPictureを作る関数が用意されています。

```hs
picture = rectangleSolid 200 150
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/5e3ff6a8-63f2-936a-3d00-c567b323fde5.png">

簡単に四角形が書けましたね！

### Line
Lineは線分を描画するためのPictureです。さっそく表示してみましょう。

```hs:app/Main.hs
picture = line [(0, 150), (150, 50), (100, -100), (-100, -100), (-150, 50)]
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/33844d70-3ec0-7d30-ae8c-d963a9bf5e0e.png">

わかりやすいようにPolygonの時と同じパラメータで描画してみました。Polygonと違う点は

* 中が塗りつぶされていない
* 終点として始点を指定しないと線が閉じない

といったところでしょうか。2点目に関しては明示的に閉じなくても閉じた線分を書いてくれる [`lineLoop`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Picture.html#v:lineLoop)という関数も用意されています。

Lineに関しても四角形を簡単に書く関数が用意されています。

```hs
picture = rectangleWire 200 150
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/4fd8eb02-15a1-79a2-c8ce-064211cdd73e.png">

### Circle
Circleは与えられた半径の円を描画するPictureです。

```hs:app/Main.hs
picture = circle 100
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/471d48c2-230b-efd0-a885-8259295cdba3.png">

見事な円が書けました！中身が塗りつぶされないということもわかると思います。

中身が塗りつぶされた円を書くためには[`circleSolid`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Picture.html#v:circleSolid)という関数を使います。

```hs
picture = circleSolid 100
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/939e811c-930b-af9b-e945-b9186dbfa882.png">

### ThickCircle
ThickCircleは厚みのある円を描画するためのPictureです。

```hs
picture = thickCircle 100 10
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/497e2834-c968-f4ee-92f9-9247a43c0a63.png">

半径100, 厚み10の円を書いているため、一番外側の円の半径は厚みの半分がプラスされた105となっています。

### Arc
Arcは円弧を描画するためのPictureです。

```hs
picture = arc 30 150 100
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/e68d4369-902e-04cd-29db-4602f334456e.png">

arc関数には開始の角度・終了の角度・半径という順番で引数を与えます。角度は度数法で指定します（つまり一周が360となります）。

円弧だけでなく半径も描画して扇形を作るには[`sectorWire`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Picture.html#v:sectorWire)関数を使います。

```hs
picture = sectorWire 30 150 100
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/e4d90728-6125-31d0-6e50-6a2ed55f6883.png">

更に塗りつぶした扇形を描画するには[`arcSolid`](http://hackage.haskell.org/package/gloss-1.12.0.0/docs/Graphics-Gloss-Data-Picture.html#v:arcSolid)関数を使います。

```hs
picture = arcSolid 30 150 100
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/ea5197fc-f515-3d77-c458-61e583cd99cb.png">

### ThickArc
ThickArcは厚みのある円弧を描画するためのPictureです。

```hs
picture = thickArc 30 150 100 10
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/f1e49f52-53d7-37d9-662c-a4c5c7a2c5e2.png">

thickArc関数には開始の角度・終了の角度・半径・厚みという順番で引数を与えます。角度は度数法で指定します。

### Text
Textは文字を描画するためのPictureです。Hello Worldの際にも使用しましたね。

```hs
picture = text "gloss"
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/3ef72094-9d4a-daa0-c7dc-037e869ac1c5.png">

しかしTextでは日本語が表示できないので注意が必要です。もし日本語を表示したいのであれば画像ファイルとして書き出したものを後述する方法で読み込んで表示するのが良いでしょう。


### Bitmap
Bitmapは画像データを表示するためのPictureですが直接使うことはあまりないでしょう。いわずもがな画像データはBitmapだけでなくjpgやpngなど沢山の種類があります。Haskellで画像を扱うライブラリとしては[JuicyPixels](http://hackage.haskell.org/package/JuicyPixels)が有名ですが、JuicyPixelsの機能を利用して色んな種類の画像ファイルをglossのPictureとして読み込んでくれる[gloss-juicy](http://hackage.haskell.org/package/gloss-juicy)というライブラリを使って画像を表示してみましょう。

gloss-juicyは残念ながらStackageに取り込まれていないので`stack.yaml`を編集して依存するバージョンを指定します。

```diff:stack.yaml
 # Dependency packages to be pulled from upstream that are not in the resolver
 # using the same syntax as the packages field.
 # (e.g., acme-missiles-0.3)
-# extra-deps: []
+extra-deps:
+- gloss-juicy-0.2.2
```

次に`package.yaml`を編集して依存関係を指定します。

```diff:package.yaml
     - -rtsopts
     - -with-rtsopts=-N
     dependencies:
     - gloss
+    - gloss-juicy
     - gloss-tutorial
```

表示する画像`image.png`を準備して表示してみましょう。

```hs
import Graphics.Gloss
import Graphics.Gloss.Juicy

window :: Display
window = InWindow "Hello World" (640, 480) (100, 100)

main :: IO ()
main = do
  Just img <- loadJuicy "image.png"
  display window white (scale 0.5 0.5 img)
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/2bc711a5-2143-4fea-0c81-87ecefd22c4f.png">

画像はそのままだと少し大きかったので後述するscaleを使って半分のサイズにしています。画像ファイルは[いらすとや](https://www.irasutoya.com/)さんで配布されているものを使用させていただきました。

重要なのは[`loadJuicy`](http://hackage.haskell.org/package/gloss-juicy-0.2.2/docs/Graphics-Gloss-Juicy.html#v:loadJuicy)という関数で画像ファイルのパスを渡せばglossのPictureとして読み込んでくれるというものです。

```hs
loadJuicy :: FilePath -> IO (Maybe Picture)
```

内部では読み込んだ画像をBitmapに変換してPictureにしています。

### Color
Colorは元のPictureの色を変更させたPictureを作ります。

```hs
picture = color red $ rectangleSolid 200 150
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/65564b72-d53a-474e-bdd4-19d85c864180.png">

### Translate
Translateは元のPictureをx, y軸方向に平行移動させたPictureを作ります。

```hs
picture = translate 50 100 $ rectangleSolid 200 150
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/58d9556e-85f6-3abe-0ac5-09caaee5d22f.png">

glossの座標系は画面の中心が原点となっており、y軸は上方向が正、x軸は右方向が正となっています。

### Rotate
Rotateは元のPictureを回転させたPictureを作ります。

```hs
picture = rotate 30 $ rectangleSolid 200 150
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/06ed64f8-6eb4-5ec3-9b6f-21a9b49c9d5f.png">

### Scale
Scaleは元のPictureの拡大縮小を行ったPictureを作ります。

```hs
picture = scale 2 0.5 $ polygon [(0, 150), (150, 50), (100, -100), (-100, -100), (-150, 50)]
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/d1503187-362d-6cab-d7e4-87b37b2f415a.png">

Polygonのところで書いた五角形を横に伸ばし縦に潰してみました。

### Pictures
Picturesは複数のPictureを組み合わせて1つのPictureを作ります。これを使うことで画面上にたくさんのPictureを並べることができます。

```hs
picture = pictures
  [ translate   100    100  $ color red    $ circleSolid 50
  , translate   100  (-100) $ color blue   $ rectangleSolid 60 80
  , translate (-100) (-100) $ color green  $ arcSolid 30 150 100
  , translate (-100)   100  $ color orange $ rectangleWire 100 100
  ]
```

<img width=480 src="https://qiita-image-store.s3.amazonaws.com/0/17134/2b629ff1-90cb-f34f-15d8-d5997fe68b16.png">

まとめ
-----
以上2Dグラフィック描画ライブラリglossの基本的な使い方を見てきました。何か作りたいもののアイデアは湧いてきたでしょうか？単純なシミュレーションであればglossを使えば本当に一瞬で作ることができます。僕自身、二重振り子のシミュレーションをglossを使って作ったことがあります。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Haskellで二重振り子のシミュレーションを書いた。実はこの振り子50本あって初期値を1兆分の1ぐらいずらしてあるので途中からめちゃくちゃ分岐する <a href="https://t.co/3dh4Metcpj">pic.twitter.com/3dh4Metcpj</a></p>&mdash; lotz△ (@lotz84_) <a href="https://twitter.com/lotz84_/status/866661941279834112?ref_src=twsrc%5Etfw">2017年5月22日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

（ソースコードは[こちら](https://github.com/lotz84/double-pendulum-simulation)）


みなさんもglossを使ってアイデアを実現してHaskellプログラミングを楽しんでください(^^)/