Flutterでも使われているGoogleが開発している2DグラフィックライブラリのSkiaを試してみる

## Skiaとは?

[公式ページ](https://skia.org/)

> Skia（スキア）とは、Google が開発している、C++ で書かれたクロスプラットフォームかつ
オープンソースの2次元コンピュータグラフィックスライブラリ

[Wikipedia](https://ja.wikipedia.org/wiki/Skia)より

[Flutterでも使用](https://skia.org/dev/flutter)されている！！。

###  :computer:環境構築
****
他の言語同様、メジャーなライブラリそうなので、[Conan](https://conan.io/) (パッケージマネージャー)を使って
Skiaを導入できるものかと思ってましたが、
`conan search "skia" --remote=conan-center` で検索しても見つからず... (探し方が悪い?)

色々調査したのですが、地道に1から環境を構築した方が早そうなので環境構築からやっていきたいと思います。
ベースとなる環境は[こちら](https://qiita.com/Slowhand0309/items/6faa197572ba277e7cd2)の環境を流用して構築していきます。
まずはDockerfileを以下で用意します。

```dockerfile
FROM silkeh/clang:latest

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

RUN apt-get update && \
    apt-get install -y \
      git \
      python \
      curl \
      build-essential \
      libfontconfig-dev \
      libgl1-mesa-dev \
      libglu1-mesa-dev \
      libxi-dev \
      vim \
      cmake \
      python-pip \
      --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

RUN pip install --upgrade pip setuptools && pip install conan

RUN cd /opt \
 && git clone 'https://chromium.googlesource.com/chromium/tools/depot_tools.git' \
 && git clone 'https://skia.googlesource.com/skia.git'

ENV PATH="/opt/depot_tools:${PATH}"

RUN cd /opt/skia \
  && python tools/git-sync-deps \
  && gn gen out/Static --args='is_debug=false is_official_build=true skia_use_system_expat=false skia_use_system_icu=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=false skia_use_system_harfbuzz=false target_cpu="x64" extra_cflags_cc=["-frtti"]' \
  && ninja -C out/Static

ADD . /usr/src/app

CMD clang++
```
ベースにした環境のDockerfileからの差分としては、`skia` 本体やビルド時に使用する `depot_tools` をcloneする為の
`git` や `curl`, ビルドで使用する `libxxx` を追加しています。
実際にビルドしている箇所は

```dockerfile
RUN cd /opt/skia \
  && python tools/git-sync-deps \
  && gn gen out/Static --args='is_debug=false is_official_build=true skia_use_system_expat=false skia_use_system_icu=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=false skia_use_system_harfbuzz=false target_cpu="x64" extra_cflags_cc=["-frtti"]' \
  && ninja -C out/Static
```
こちらで依存関係のモジュール等をcloneしビルド設定 + ビルド実行を行っています。

- PythonのVersion

    ```sh
    $ python --version
    Python 2.7.16
    ```

`docker build` して `/opt/skia/out/Static` に `libskia.a` ができていれば成功です :sparkles: 

### :pencil: 実装
****
`skia` はbackend(描画対象)として以下が選択できます。
- [Raster](https://skia.org/user/api/skcanvas_creation#raster) - CPU-only.
- [GPU](https://skia.org/user/api/skcanvas_creation#gpu) - Skia's GPU-accelerated backend.
- [SkPDF](https://skia.org/user/api/skcanvas_creation#skpdf) - PDF document creation.
- [SkPicture](https://skia.org/user/api/skcanvas_creation#skpicture) - Skia's display list format.
- [NullCanvas](https://skia.org/user/api/skcanvas_creation#nullcanvas) - Useful for testing only.
- [SkXPS](https://skia.org/user/api/skcanvas_creation#skxps) - Experimental XPS backend.
- [SkSVG](https://skia.org/user/api/skcanvas_creation#sksvg) - Experimental SVG backend.

描画対象を選択できるのは便利ですね :sparkles: 
まずはCPUのみで描画する `Raster` を使用するサンプルを試してみたいと思います。

#### Raster- 簡単なサンプル

```c++
#include <string>
#include <fstream>
#include <iostream>
#include "spdlog/spdlog.h"

#include "SkCanvas.h"
#include "SkData.h"
#include "SkEncodedImageFormat.h"
#include "SkImage.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkSurface.h"

// https://skia.org/user/api/skcanvas_overview こちらのサンプルを少し修正
void draw(SkCanvas* canvas) {
  const SkScalar scale = 256.0f;
  const SkScalar R = 0.45f * scale;
  const SkScalar TAU = 6.2831853f;
  SkPath path;
  path.moveTo(R, 0.0f);
  for (int i = 1; i < 7; ++i) {
      SkScalar theta = 3 * i * TAU / 7;
      path.lineTo(R * cos(theta), R * sin(theta));
  }
  path.close();
  SkPaint p;
  p.setAntiAlias(true);
  p.setStyle(SkPaint::kFill_Style);
  p.setColor(SK_ColorBLUE);
  canvas->clear(SK_ColorWHITE);
  canvas->translate(0.5f * scale, 0.5f * scale);
  canvas->drawPath(path, p);
}

int main() {
  spdlog::info("start skia sample");

  // 描画対象キャンバスの準備
  sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(640, 480);
  SkCanvas *canvas = surface->getCanvas();
  canvas->clear(SK_ColorWHITE);

  // 描画
  draw(canvas);

  // 画像の保存
  sk_sp<SkImage> image(surface->makeImageSnapshot());
  sk_sp<SkData> data(image->encodeToData(SkEncodedImageFormat::kPNG, 100));

  std::ofstream ofs("sample.png", std::ios::binary);
  ofs.write(reinterpret_cast<const char*>(data->data()), data->size());
  ofs.close();

  spdlog::info("end skia sample");
  return 0;
}

```

上記のコードをビルドする為に、`CMakeLists.txt` を以下の内容で作成します。

```cmake
cmake_minimum_required(VERSION 3.13.4)
project(SkiaSample)
find_package (Threads)

include_directories(
  /opt/skia
  /opt/skia/include/core
  /opt/skia/include/config
  /opt/skia/include/utils
  /opt/skia/include/gpu
  /opt/skia/include/docs
)

add_definitions("-std=c++14")
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

link_directories(
  /opt/skia/out/Static
)

add_executable(sample sample.cpp)
target_link_libraries(sample
  skia
  z
  dl
  fontconfig
  freetype
  GL
  GLU
  ${CMAKE_THREAD_LIBS_INIT}
  ${CONAN_LIBS}
)
```
ついでにビルド用のシェルも作成します。
ビルド用のシェルでは `CC`, `CXX` をClangを使う様に設定し、
conanのinstallも行っています。 

```sh
#!/usr/bin/env bash

export CC=/usr/local/bin/clang
export CXX=/usr/local/bin/clang++

if [ -e build ]; then
  rm -rf build
  mkdir build
else
  mkdir build
fi

cd build
conan install .. --build=fmt --build=spdlog -pr=../clang_profile
cmake ..
make
```
いざビルドして、成功すると `./build/bin/sample` が作成されているので

```sh
$ ./build/bin/sample
```
実行して、以下の様な画像の `sample.png` が作成されれば成功です。
<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/cc7bbac4-590e-0999-96e6-3e38060a20ad.png">

#### SkPDF- 簡単なサンプル
次に PDFを描画対象とする様に修正してみたいと思います。
とはいえ、`sample.cpp` を以下に修正してビルド + 実行すれば↑と同じ画像のpdfが作成される様になります :sparkles: 

```c++
...
// 以下のincludeを追加
#include "SkPDFDocument.h"
#include "SkStream.h"

// main関数を以下に修正
int main() {
  spdlog::info("start skia sample");

  // 描画対象キャンバスの準備
  SkFILEWStream pdfStream("sample.pdf");
  auto pdfDoc = SkPDF::MakeDocument(&pdfStream);
  SkCanvas* pdfCanvas = pdfDoc->beginPage(SkIntToScalar(640),
                                          SkIntToScalar(480));
  // 描画
  draw(pdfCanvas);

  pdfDoc->close();
  spdlog::info("end skia sample");
  return 0;
}
```
----
### その他
skiaには他にも面白そうなものがあるので、時間があれば試してみたい :sparkles: 
-  Lottie Animation Playerの[Skottie](https://skia.org/user/modules/skottie)
- Skia + WebAssembly - [CanvasKit](https://skia.org/user/modules/canvaskit)
- Geometry in the Browser - [PathKit](https://skia.org/user/modules/pathkit)
- [SkSL](https://github.com/google/skia/blob/master/src/sksl/README) ("Skia Shading Language")

###  :bomb: バッドノウハウ
****
- skiaビルド中に  `SkShaper_harfbuzz.cpp:34:10: fatal error: hb.h: No such file or directory` が発生する!
以下のPRを元に `--args` のパラメータを変更
[Ninja build failing on macOS Catalina · Issue #2293 · aseprite/aseprite](https://github.com/aseprite/aseprite/issues/2293)
[Update macOS command line (match it to the build.yml one) · aseprite/skia@3043aa8](https://github.com/aseprite/skia/commit/3043aa83d14f2251f22c068816ceb6759ea3fb61)

- conanも一緒に使用した際に `Incorrect 'clang', is not the one detected by CMake: 'GNU'` が発生する
    - `C` と `CXX` の環境変数をClangのパスで設定する

        ```sh
        $ export CC=/usr/local/bin/clang
        $ export CXX=/usr/local/bin/clang++
        ```

### :sparkles: 感想
****
- `Skia` が思ってた以上にできる事が多く、時間があれば色々試してみたいと思いました。
- Flutter engineでどの様な使われ方をしているかも調べてみよう...

### :link: 参考になったURL
****
- [Google 開発の 2D グラフィックライブラリ Skia の紹介とはじめかた - 絵文字ジェネレーター 開発者ブログ](https://blog.emoji-gen.ninja/posts/20190204/skia.html)
- [CMakeを使ってみた（2）もう少しまともなプロジェクト - wagavulin's blog](https://www.wagavulin.jp/entry/2011/11/27/222642)
- [cmake チートシート - Qoosky](https://www.qoosky.io/techs/814fda555d)