バナー作成をProcessingで完結したい

[Processing Advent Calender 2020](https://adventar.org/calendars/4953) 12日目です！

私が所属する[クリエイティブコーディングサークル「ARTiS」](https://twitter.com/ARTiS_CCC/)は，不定期で開催した8回のワークショップごとに，テーマに合わせたバナーをProcessingだけで作っています．

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">ARTiSのWorkshop#1で出せなかったやつを供養<a href="https://twitter.com/hashtag/ARTiS_activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_activity</a> <a href="https://t.co/BU3NiWegoR">pic.twitter.com/BU3NiWegoR</a></p>&mdash; ohayota (@ohayoooota) <a href="https://twitter.com/ohayoooota/status/1257601456540016640?ref_src=twsrc%5Etfw">May 5, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">今日は2回目のワークショップを開催しました！<br>「Noise」をテーマにして、オリジナル作品をつくってみました。<br>部員たちの作品を、ぜひこのアカウントのRTからご覧ください。<br><br>次回は「Object」をテーマに、5/6の開催を予定しています。<a href="https://twitter.com/hashtag/ARTiS_activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_activity</a> <a href="https://t.co/w54jtmT9Oj">pic.twitter.com/w54jtmT9Oj</a></p>&mdash; ARTiS (@ARTiS_CCC) <a href="https://twitter.com/ARTiS_CCC/status/1256959364054650880?ref_src=twsrc%5Etfw">May 3, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">明日13:00-14:30の時間帯で、Workshop#3 “Object” を開催します！<a href="https://twitter.com/hashtag/ARTiS_activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_activity</a> <a href="https://t.co/CHmnxSFofl">pic.twitter.com/CHmnxSFofl</a></p>&mdash; ARTiS (@ARTiS_CCC) <a href="https://twitter.com/ARTiS_CCC/status/1257600174269972480?ref_src=twsrc%5Etfw">May 5, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">Workshop#4 “Image” を開催しました！<br>テンプレートプログラムを使いながら、画像の読み込みと加工、PGraphicsの使い方について学習しました。<br><br>次回Workshop#5 “Renderer” は、来月開催の予定です。<a href="https://twitter.com/hashtag/ARTiS_activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_activity</a> <a href="https://twitter.com/hashtag/ARTiS_workshop?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_workshop</a> <a href="https://t.co/Gu6HEjrZWz">pic.twitter.com/Gu6HEjrZWz</a></p>&mdash; ARTiS (@ARTiS_CCC) <a href="https://twitter.com/ARTiS_CCC/status/1264435278753587201?ref_src=twsrc%5Etfw">May 24, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">本日13時、Workshop#5 “Renderer”を開催します！<a href="https://twitter.com/hashtag/ARTiS_activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_activity</a> <a href="https://t.co/mvqcKzT7En">pic.twitter.com/mvqcKzT7En</a></p>&mdash; ARTiS (@ARTiS_CCC) <a href="https://twitter.com/ARTiS_CCC/status/1269467956204851200?ref_src=twsrc%5Etfw">June 7, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">本日、Workshop#6 “Color”を開催しました！<br>colorModeやblendMode、配色について学びました。<a href="https://twitter.com/hashtag/ARTiS_activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_activity</a> <a href="https://t.co/Db28YSFUuI">pic.twitter.com/Db28YSFUuI</a></p>&mdash; ARTiS (@ARTiS_CCC) <a href="https://twitter.com/ARTiS_CCC/status/1272072528702275584?ref_src=twsrc%5Etfw">June 14, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">明日27(日)の13時より、Workshop#7 “Recursive” を開催します！<br>場所はProcessing Community JapanのDiscord、進行は <a href="https://twitter.com/godpotbot?ref_src=twsrc%5Etfw">@godpotbot</a> です。<a href="https://twitter.com/hashtag/ARTiS_Activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_Activity</a> <a href="https://twitter.com/hashtag/ARTiS_Workshop?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_Workshop</a> <a href="https://t.co/CWCP5l6hLD">pic.twitter.com/CWCP5l6hLD</a></p>&mdash; ARTiS (@ARTiS_CCC) <a href="https://twitter.com/ARTiS_CCC/status/1309729625262571521?ref_src=twsrc%5Etfw">September 26, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">本日25(日)の12時半より、Workshop#8 “Attractor” を開催します！<br>場所はProcessing Community JapanのDiscord、進行は <a href="https://twitter.com/ohayoooota?ref_src=twsrc%5Etfw">@ohayoooota</a> です。<a href="https://twitter.com/hashtag/ARTiS_Activity?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_Activity</a> <a href="https://twitter.com/hashtag/ARTiS_Workshop?src=hash&amp;ref_src=twsrc%5Etfw">#ARTiS_Workshop</a> <a href="https://t.co/opWmYMpLBC">pic.twitter.com/opWmYMpLBC</a></p>&mdash; ARTiS (@ARTiS_CCC) <a href="https://twitter.com/ARTiS_CCC/status/1320191851492642817?ref_src=twsrc%5Etfw">October 25, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

開催するイベントのバナーを作るとき，どんなツールを使いますか？
グラフィック編集ツールで作る方が多いのではないでしょうか．

クリエイティブコーディングに関わるイベントであれば，Processing/p5.jsなどで描いたグラフィックをもとに，バナーを作ると思います．
作品を一度Processing/p5.jsで作ったものを他のソフトにインポートし，情報を付加し，エクスポートする...少し手数が増えますね．
それなら，コードを実行するだけで，バナーを完成できるようにしてしまいましょう．

本記事では，Workshop#2 "Noise"のバナーを例に，**Processing**で実現します（p5.js版に翻訳できなかった）．

# 実装

以下2つを作成します．

1. バナー背景に使いたい作品
1. イベント名，開催日時，ロゴなどを描画するレイヤー（TypeLayer）

最終的には，1.の作品が文字の邪魔をしないように，filterでぼかしを入れます．
TypeLayerは，Processingの[PGraphics](https://processing.org/reference/PGraphics.html)を利用して実装します．
1.の前面に2.のTypeLayerを描画して，最後に画像として出力し完成です．

## バナー背景に使いたい作品を描画

スライドの表紙にも使うことを考えて，縦横比を**縦：横＝9：16**にします．
例では`size(1280, 720)`とします．

まずは，背景にするグラフィックを作成してみます．
7色の折れ線グラフみたいなものを，ノイズを使って適当に作ります．
↓こんなやつです

![BannerExample1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/f0bbea6c-284c-d926-318c-75be294eec7a.png)

``` java:Banner.pde
float offset = 0.0;
float distance = 0.01;

void setup() {
  size(1280, 720);
  background(0);
  colorMode(HSB, 360, 100, 100, 100);
}

void draw() {
  noiseSeed(0);
  drawNoise(color(0, 100, 100));
  noiseSeed(1);
  drawNoise(color(30, 100, 100));
  noiseSeed(2);
  drawNoise(color(60, 100, 100));
  noiseSeed(3);
  drawNoise(color(90, 100, 100));
  noiseSeed(4);
  drawNoise(color(120, 100, 100));
  noiseSeed(5);
  drawNoise(color(210, 100, 100));
  noiseSeed(6);
  drawNoise(color(270, 100, 100));
  offset += distance;

  // 終了条件
  if (width <= (offset+distance)*100) {
    noLoop();
  }
}

void drawNoise(color c) {
  stroke(c, 50);
  strokeWeight(5);
  float x = offset*100;
  float x_next = (offset+distance)*100;
  float y = map(noise(offset), 0, 1, 0, height);
  float y_next = map(noise(offset+distance), 0, 1, 0, height);
  line(x, y, x_next, y_next);
}
```

## イベント名，開催日時，ロゴなどを描画するレイヤー

作品の上に文字情報などを表示するため，TypeLayerクラスを作ってみます．
文字に使うフォントは"Avenir-Black"を選択しました．
サークルのバナーなので，サークルのロゴ（svg形式）を`loadShape`で読み込んでいます．
（再現するときは，適宜置換・削除してください）

``` java:TypeLayer.pde
class TypeLayer {
  
  PGraphics pg;
  PShape logo;
  
  TypeLayer() {
    this.pg = createGraphics(width, height);
    this.logo = pg.loadShape("ArtisLogo_v1.1.svg");
    generate();
  }
  
  void generate() {
    pg.beginDraw();
    pg.background(0, 30);
    pg.colorMode(HSB, 360, 100, 100, 100);
    pg.textAlign(LEFT, CENTER);
    pg.textFont(createFont("Avenir-Black", 80));
    pg.fill(0, 0, 100);
    pg.text("BannerExample", 100, height/2-45);
    pg.textSize(40);
    pg.text("2020.12.12(Sun)" + " " + "12:30-14:00", 100, height/2+45);
    pg.shape(logo, width-logo.width/3-54, height-logo.height/3-20, logo.width/3, logo.height/3);
    pg.endDraw();
  }
  
  void draw() {
    image(pg, 0, 0);
  }
  
}
```

TypeLayerクラスのインスタンスを生成することで，文字やロゴが描画されたレイヤーが完成します．
TypeLayerクラスインスタンスの生成は`setup()`内で行い，折れ線グラフみらいなグラフィックができた直後に，インスタンスの`draw()`を呼び出し描画します．

``` java:Banner.pde
float offset = 0.0;
float distance = 0.01;

// ここから追加
TypeLayer typeLayer;
// ここまで追加

void setup() {
  size(1280, 720);
  background(0);
  colorMode(HSB, 360, 100, 100, 100);

  // ここから追加
  typeLayer = new TypeLayer();
  // ここまで追加
}
```

``` java:Banner.pde
void draw() {
  noiseSeed(0);
  drawNoise(color(0, 100, 100));
  noiseSeed(1);
  drawNoise(color(30, 100, 100));
  noiseSeed(2);
  drawNoise(color(60, 100, 100));
  noiseSeed(3);
  drawNoise(color(90, 100, 100));
  noiseSeed(4);
  drawNoise(color(120, 100, 100));
  noiseSeed(5);
  drawNoise(color(210, 100, 100));
  noiseSeed(6);
  drawNoise(color(270, 100, 100));
  offset += distance;

  if (width <= (offset+distance)*100) {

    // ここから追加
    typeLayer.draw();
    // ここまで追加

    noLoop();
  }
}
```

実行してみると，下図のように文字とロゴが表示されます．

![BannerExample1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/0bc71015-d676-3776-7443-cfacb236685e.png)


## 見やすさのための調整

いまの状態だと背景の主張がうるさいので，少しだけぼかしを入れてみます．
そして，この状態を`saveFrame`で書き出してみます．

``` java:Banner.pde
void draw() {
  noiseSeed(0);
  drawNoise(color(0, 100, 100));
  noiseSeed(1);
  drawNoise(color(30, 100, 100));
  noiseSeed(2);
  drawNoise(color(60, 100, 100));
  noiseSeed(3);
  drawNoise(color(90, 100, 100));
  noiseSeed(4);
  drawNoise(color(120, 100, 100));
  noiseSeed(5);
  drawNoise(color(210, 100, 100));
  noiseSeed(6);
  drawNoise(color(270, 100, 100));
  offset += distance;

  // 終了条件
  if (width <= (offset+distance)*100) {

    // ここから追加
    filter(BLUR, 2);
    // ここまで追加

    typeLayer.draw();

    // ここから削除
    noLoop();
    // ここまで削除

    // ここから追加
    saveFrame("Banner.png");
    exit();
    // ここまで追加
  }
}
```

![BannerExample1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/a958af67-3775-b9c1-ab83-220681b415de.png)

背景にぼかしをいれるとこうなります．見やすくなりましたね．
これで，コードだけでバナー作成を完結できました．

# 掲載したコードのまとめ

``` java:Banner.pde
TypeLayer typeLayer;

float offset = 0.0;
float distance = 0.01;

void setup() {
  size(1280, 720);
  background(0);
  colorMode(HSB, 360, 100, 100, 100);
  
  typeLayer = new TypeLayer();
}

void draw() {
  noiseSeed(0);
  drawNoise(color(0, 100, 100));
  noiseSeed(1);
  drawNoise(color(30, 100, 100));
  noiseSeed(2);
  drawNoise(color(60, 100, 100));
  noiseSeed(3);
  drawNoise(color(90, 100, 100));
  noiseSeed(4);
  drawNoise(color(120, 100, 100));
  noiseSeed(5);
  drawNoise(color(210, 100, 100));
  noiseSeed(6);
  drawNoise(color(270, 100, 100));
  offset += distance;
  
  // 終了条件
  if (width <= (offset+distance)*100) {
    filter(BLUR, 2);
    typeLayer.draw();
    saveFrame("Banner.png");
    exit();
  }
}

void drawNoise(color c) {
  stroke(c, 50);
  strokeWeight(5);
  float x = offset*100;
  float x_next = (offset+distance)*100;
  float y = map(noise(offset), 0, 1, 0, height);
  float y_next = map(noise(offset+distance), 0, 1, 0, height);
  line(x, y, x_next, y_next);
}
```

``` java:TypeLayer.pde
class TypeLayer {
  
  PGraphics pg;
  PShape logo;
  
  TypeLayer() {
    this.pg = createGraphics(width, height);
    this.logo = pg.loadShape("ArtisLogo_v1.1.svg");
    generate();
  }
  
  void generate() {
    pg.beginDraw();
    pg.background(0, 30);
    pg.colorMode(HSB, 360, 100, 100, 100);
    pg.textAlign(LEFT, CENTER);
    pg.textFont(createFont("Avenir-Black", 80));
    pg.fill(0, 0, 100);
    pg.text("BannerExample", 100, height/2-45);
    pg.textSize(40);
    pg.text("2020.12.12(Sun)" + " " + "12:30-14:00", 100, height/2+45);
    pg.shape(logo, width-logo.width/3-54, height-logo.height/3-20, logo.width/3, logo.height/3);
    pg.endDraw();
  }
  
  void draw() {
    image(pg, 0, 0);
  }
  
}
```