Swift Package Managerで画像を作成する

先日書いた[XCTestのUIテストの結果をイイ感じで見たい](https://qiita.com/fromkk/items/38d8d4139053ae33773f)という記事では`XCTest`で撮影したスクリーンショットをHTMLにまとて表示する様にしてみました。
ただ、HTMLではチームのメンバーにそのまま共有するのは大変だったので、画像かPDF化してみたいなと考えていました。
今回はSwift Package Manager上で画像を作成する事で、生成した画像を手軽にチームメンバーに共有出来る様にしてみました。

---

## 本題

普段iOSやmacOSのアプリを書いていると画像生成はCore Graphic等を利用して簡単に画像を作成する事が出来ますね。ただ、Swift Package ManagerなどのPure Swiftな環境では簡単な画像すら作成する環境が整っていません。簡単な画像合成プログラムをPure Swiftな環境で書いてみたので共有したいと思います。

---

## 前提

- macOS High Sierra
- Xcode 9.3

---

## 検討したツール

- [Imagemagick](https://www.imagemagick.org/)
- [GD Graphics Library](https://libgd.github.io/)

今回検討したツールは`Imagemagick`と`GD Graphics Library`です。最初に使おうと思ったのが`Imagemagick`です。サーバーで画像生成するには最近では定番の画像作成ツールですね。但し、今回は利用しませんでした。 理由としては誰でもインストール出来るCLIツールを作りたかったのですが、インストールだけでかなりの時間を取られてしまった為です。

---

## 今回は `GD` を利用

次に検討したのがGDと呼ばれるPHPではお馴染みの昔からある画像生成ライブラリです。こちらはインストールが比較的さくっと出来たので今回はこちらを利用する事にしました。

---

## 何故AppKit使わないのか

GDならLinuxからでも利用出来る為今回はAppKitを利用しませんでした。将来的に利用シーンを広げる事を視野に入れるとLinuxで利用出来る事は価値があるかなと思います。

---

## GDのインストール

```
brew install gd
```

最初にGDをインストールします。GDはHomebrewでインストール可能です。

---

## SwiftからC言語のライブラリを利用する

これで画像を生成する準備ができました。次にSwiftからC言語のライブラリを利用出来る様にしましょう。

---

### `module.modulemap` を作成

```bash
mkdir ./GD && cd $_
touch module.modulemap
```

今回作成するプロジェクトと並列の階層にGDという名前のディレクトリを作成して、`module.modulemap`ファイルを作成します。

---

### `module.modulemap` 内容

```
module GD [system] {
	header "/usr/local/include/gd.h"
	header "/usr/local/include/gdfontt.h"
	header "/usr/local/include/gdfonts.h"
	header "/usr/local/include/gdfontl.h"
	header "/usr/local/include/gdfontg.h"
	header "/usr/local/include/gdfontmb.h"
	link "gd"
	export *
}
```

module.mobulemapの内容はこんな感じです。名前の定義とヘッダーファイルへのパスを示します。

---

## 同じ階層に `Package.swift` を作成

```swift
import PackageDescription

let package = Package(
    name: "GD",
    pkgConfig: "gdlib",
    providers: [
        .brew(["gd"])
    ]
)
```

`module.modulemap`ファイルと同じ階層に`Package.swift`ファイルを作成します。Homebrew経由のGDを利用する様な記述がある事が分かります。

---

## Gitでタグ付け

```bash
git init .
git add .
git commit -m "Initial commit"
git tag 0.0.1 -m "Initial commit"
```

これでGitをコミットしてタグ付けしておきます。プロジェクト側からはこのタグを参照する様です。

---

### プロジェクトを作成

```swift
cd ../ && mkdir ./MyProject && cd $_
swift package init --type executable
```

ようやくプロジェクトの作成です。GDと同階層にMyProjectという名前のディレクトリを作成して、SPMを実行可能形式で初期化しておきます。

---

### `Package.swift` を編集

```swift
import PackageDescription

let package = Package(
    name: "MyProject",
    dependencies: [
        .package(url: "../GD", from: "0.0.1"),
    ],
    targets: [
        .target(
            name: "MyProject",
            dependencies: []),
    ]
)
```

Package.swiftファイルのdependenciesにGDをリンクします。

---

### 一度ビルドする

```bash
swift build
```

↓

```bash
Fetching /PATH/TO/GD
Cloning /PATH/TO/GD
Resolving /PATH/TO/GD at 0.0.1
Compile Swift Module 'MyProject' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/MyProject
```

`GD` がフェッチされてちゃんとビルドされている事を確認します。

---

### Xcodeprojを作成

```bash
swift package generate-xcodeproj
```

ここでXcodeプロジェクトを作成して、ガシガシコードが書ける様になりました。

---

### フォルダ構成

```
tree .
.
├── GD
│   ├── Package.swift
│   └── module.modulemap
└── MyProject
    ├── MyProject.xcodeproj
    │   ├── project.pbxproj
    │   ├── project.xcworkspace
    │   │   ├── contents.xcworkspacedata
    │   │   ├── xcshareddata
    │   │   │   └── IDEWorkspaceChecks.plist
    │   │   └── xcuserdata
    │   │       └── fromkk.xcuserdatad
    │   │           └── UserInterfaceState.xcuserstate
    │   ├── xcshareddata
    │   │   └── xcschemes
    │   │       ├── MyProject-Package.xcscheme
    │   │       └── xcschememanagement.plist
    │   └── xcuserdata
    │       └── fromkk.xcuserdatad
    │           └── xcschemes
    │               └── xcschememanagement.plist
    ├── Package.resolved
    ├── Package.swift
    ├── README.md
    ├── Sources
    │   └── MyProject
    │       └── main.swift
    └── Tests
```

現時点でのフォルダ構成はこの様な感じです。最初に作ったGDと並列でMyProjectを作成しました。

---

## GDの基礎

---

# 画像新規作成

```swift
import GD

let width: Int32 = 640
let height: Int32 = 480
let image = gdImageCreateTrueColor(width, height)
```

画像を新規作成するには`gdImageCreateXXXXX`を利用します。画像から読み込む事も可能です。

---

# 画像保存

```swift
import GD

let handler = fopen("path/to/image.png", "wb")
defer { fclose(handler) }
gdImagePng(image, handler)
```

画像を保存する時は`gdImageXXXX`を利用します。保存したい形式によっていくつか関数があるのでその時々で選んでください。

---

## 画像合成

```swift
import GD

let file = fopen("copy/image/path.png", "rb")
defer { fclose(file) }

let copyImage = gdImageCreateFromPng(file)
gdImageCopy(image, copyImage, dstX, dstY, srcX, srcY, width, height)
```

画像同士を合成するには別の画像を読み込んで、`gdImageCopy`関数で貼り付けます。

---

## 文字列を書き込む

```swift
import GD

let blackColor = gdImageColorAllocate(image, 0, 0, 0)
defer { gdImageColorDeallocate(image, blackColor) }

let text = UnsafeMutablePointer<UInt8>(mutating: "hogehoge")
gdImageString(image, gdFontGetSmall(), x, y, text, blackColor)
```

文字列を書き込む為には`gdImageString`関数にフォントサイズ、位置、テキスト、色を指定します。デフォルトのフォントではAscii以外は文字化けします。
※日本語が利用したい場合は別でフォントを指定する必要がありますが今回は割愛します。

---

## 作ってみたツール

# [https://github.com/fromkk/TestSummaries](https://github.com/fromkk/TestSummaries)

---

### インストール方法

```bash
brew install fromkk/TestSummaries/testsummaries
```

---

## 結果

![result.png](https://qiita-image-store.s3.amazonaws.com/0/2846/5359bf73-92c4-77c4-d427-5fd52b269314.png)


---

# まとめ

XCTestで撮ったスクショを画像にまとめてチームに共有出来る様にしてみました。Homebrewから誰でもインストール出来ますので手軽に試せるかと思います。まだまだ過去の資産を活用する事で便利なツールは作れるなと思うのでこれを機にCLIでツールの作成に挑戦してみてはいかがでしょうか？

---

## 参考

- [
Swift Package Manager経由でSwiftからCのライブラリを利用する](https://qiita.com/shingt/items/08c726f968dd8508b1e2)
- [GD Graphics Library](https://libgd.github.io)