Canvas2D RadialGradientの細分割による線をはみ出さない塗りの実験

この記事は [Web グラフィックス Advent Calendar 2022](https://qiita.com/advent-calendar/2022/webgraphics) の6日目の記事です。

# 自作お絵かきツールで線をはみ出さないでブラシ塗りがしたい

自作ツールでブラシ塗りがしたいということで、[前回](https://qiita.com/warotarock/items/50320c22fc5c9096ee26)は放射状グラデーション（RadialGradient）のパフォーマンスを検証しました。

PC上でイラストなどの絵を描くためのソフトウェアは一般的にペイント系とドロー系に分けられると思いますが、筆者が今作っているツールはドロー系です。つまり、データをビットマップで持つのではなく、ベクトルデータで持します。それにより、後から修正しやすいのが売りのつもりです。

しかしドロー系なので、色を塗ったときに線からはみ出した部分を消しゴムで消す、ということが技術的に難しかったりします。消しゴムデータが肥大化したり、重ね合わせの問題が生じたりするからです。

また、そもそも消しゴムで消す作業を無くしたいという要求があり、多くのペイント系ソフトはそのための機能があります。下の画像はCLIP STUDIOで「はみ出し防止」機能を有効にして塗ってみたものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/dd40211e-b477-8b58-c483-15664dca989d.png)

今回は、放射状グラデーションを使って線をはみ出さない塗りができるかどうか、アイデアを出して実装してみます。

# 細分割して線をはみ出す部分を削除するというアイデア

色々考えて最初に出たアイデアが、放射状グラデーションを細分化して線を越える部分を削除する方法です。

下の図の左のような放射状グラデーションを、右のような小さな放射状グラデーションで近似することができれば、部分的に削除をしても概形を保つことができるはずです。

そして、削除の対象となった部分をさらに分割することで、描画の回数は増えますが、線の形に限りなく近づけることができるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/cd858c87-3e60-c6e3-5773-d6d52790e0de.png)


# 実装してみた

小さなプログラムなのでCodePenにしました。
キャンバスをクリックすると境界線の位置を変更できます。
また、パラメータなどの下に細分割後の円の数（=RadialGradientの描画回数）が表示されます。

<p class="codepen" data-height="800" data-default-tab="result" data-slug-hash="abKXbKQ" data-user="warotarock" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/warotarock/pen/abKXbKQ">
  Canvas2D RadialGradientの細分割による線画をはみ出さない塗りの実験</a> by 柏崎ワロタロ (<a href="https://codepen.io/warotarock">@warotarock</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# 考察

結論を言ってしまうと、残念ながら実用的ではないと思います。

## ダメなポイントその１

見た目が美しくない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/cabdf81b-afe2-101d-cead-ad836705e83f.png)

試行錯誤でパラメータを調整しましたが、どうやっても、なんともいえないモヤモヤした感じになりますね。よく考えてみたら、放射状グラデーションで放射状グラデーションを近似することがそもそも難しいのだと思います。

電気信号の合成のような感じで、小さな形状を加算して大きな形状を近似する必要があります。きちんとやろうとすると、これが難しい。グラフィックスの場合はアルファブレンドなので単なる加算ではありませんし、さらに二次元でもあるので、自分の数学のレベルではそもそも可能なのかすらよくわからないです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/59ea9181-847b-0d87-8ebb-5a2a1601cfcc.png)

ちょっと話は違いますが、絵師の方が手作業で美しいグラデーションを描くことができるのはそれだけでも凄いことなんだろうなと思いました。

ちなみに、分割が多くなると、Chromeではなぜかモアレというかディザリングの跡みたいなものが現れます。FireFoxでは現れません。ハードウェアアクセラレーションを無効にしても変わりませんでした。おそらく薄い色をたくさん重ねると発生するのだろうと思いますが、ちょっと面白いですね。

なおChromeのバージョンは107です。

## ダメなポイントその２

重い

初期状態で円の数が1300くらいですが、かなり線をはみ出してしまいます。はみ出しかたは分割のお最小半径を小さくすると改善されるのですが、そうすると円の数は1万を超えてしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/cdd9bcc3-1d1f-0120-4add-7da646bfa2cf.png)

分割数が14のとき円の数は初期で22（外側に14、内側に7、中心に1）なのですが、細分割が1段階進むと最大で22の累乗で円の数が増えていくことになります。

ごく単純な図形を描くだけでもすごい描画回数になってしまうわけですね。そして、前回の実験で分かったのですが、RadialGradientの描画は描画面積が小さくとも一定の負荷が生じる傾向があるので、描画回数が多いと重たくなってしまいます。またデータ容量も非常に大きくなります。

# まとめ

- 放射状グラデーション（RadialGradient）の細分割で線をはみ出さない塗りの実験を行った
- あまり美しくなく、しかも重いので実用的ではないことが分かった
- グラデーションでグラデーションを近似することの数学的な難しさを知った

以上です。まだこの流れで続きがありますので、後日投稿する予定です。
今後もWebグラフィックス アドベントカレンダー2022をよろしくお願いいたします。
参加枠にはまだ若干の余裕がございます。参加お待ちしております。:sob: