Advent Calendar だから Illustrator + JavaScript でお菓子を作ってみる

#はじめに
この記事は [FORK Advent Calendar 2019](https://qiita.com/advent-calendar/2019/fork) 19日目の記事です。

Advent Calendar といえばクリスマスに向けて1日ずつ数える習慣で、毎日1つお菓子を食べるなんて楽しみもありますよね！(以下 [wikipedia](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%89%E3%83%99%E3%83%B3%E3%83%88%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC) より)

> アドベントカレンダー (Advent calendar) は、クリスマスまでの期間に日数を数えるために使用されるカレンダーである。待降節の期間（イエス・キリストの降誕を待ち望む期間）に窓を毎日ひとつずつ開けていくカレンダーである。すべての窓を開け終わるとクリスマスを迎えたことになる。

ということで、大好きな [Adobe Illustrator](https://www.adobe.com/jp/products/illustrator.html) でお菓子を作ります！
ただしここはQiita、プログラムの記事を書かかねばなりません。Illustratorは譲れないので今回はJavaScriptで作ることにします。コードの質も甘めですがそこはご容赦ください。

## やりたいこと
- Adobe IllustratorにJavaScriptでお菓子の絵を描く

IllustratorでJavaScriptを実行する方法は、
Illustratorの `ファイル > スクリプト > その他のスクリプト` で読み込むか、
IllustratorのアートボードにJavaScriptのファイルをドラッグ&ドロップするかになります。その為、

- Adobe IllustratorのアートボードにJavaScriptのファイルをドラッグ&ドロップするとお菓子が出てくる

といった感じになります。

## 用意するもの

### 材料
- Adobe Illustrator
- JavaScript

本記事ではAdobe Illustrator CCを使用しております。

### レシピ
- [Adobe Illustrator CC 2017 Reference: JavaScript](http://adobe.com/content/dam/acom/en/devnet/illustrator/pdf/Illustrator_JavaScript_Scripting_Reference_2017.pdf)

英語というだけでアレルギーが出る方も多いかもしれませんが、Illustratorが使えれば割と読めます。

# 実装 
どんなお菓子を作ろうか考えた結果、クッキーを作ることにしました！

![cookie1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317963/a41e8a69-2d42-6f58-05a7-0bc6e624e142.png)

~~安っぽい~~定番ですね。


## 生地作り
まずは基礎となる生地を作ります。
Illustratorのアートボードに長方形ツールで正方形を描いてみましょう。

```javascript
var doc   = app.activeDocument;
var dough = doc.pathItems.rectangle(-5, 5, 90, 90);
```

### ドキュメントの指定
1行目の `app.activeDocument` について
`app` は対象のアプリケーションすなわちIllustrator、
`activeDocument` は使用中のドキュメントになります。

### 正方形の描画
2行目の `doc.pathItems.rectangle()` について
`doc.pathItems` は1行目のドキュメントでパスオブジェクトを取り扱おうとしています。
続いて `rectangle()` でパスオブジェクトの長方形ツールを使用すると書いています。

`rectangle()`  は `rectangle(上のY座標, 左のX座標, 横幅, 高さ)` のように数字を指定し、
ここではY座標-5、X座標5の位置に横幅90×高さ90の長方形を描画すると書いています。

![1. JavaScriptで描画した正方形](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317963/7df20812-b826-4017-9909-daf647f7ab7d.png)

これで正方形ができました！
※ 画像では黒枠の正方形ですが、実際の色は選択中の色に準じます。

## 色づけ
続いて正方形に色をつけて美味しそうにしていきましょう。

### RGBによる色指定
色を指定する場合、RGBで指定する方法とCMYKで指定する方法があります。
今回はRGBで色を指定し、`RGBColor()` を使います。

```javascript
var color = new RGBColor();
color.red   = 255;
color.green = 0;
color.blue  = 0;
```

このように `red` `green` `blue` の数値を指定すると `color` は `rgb(255, 0, 0)` となります。
なお、CMYKで色を指定する場合は `CMYKColor` で `cyan` `magenta` `yellow` `black` を指定します。

### 色の適応
好きな色を指定できたら、その色をつけてみましょう！
Illsutratorには塗りと線があります。
塗りはパスの内側の色を指し `fillColor`、線はパスの色を指し `strokeColor` で指定します。

```javascript
var doc = app.activeDocument;

var dough = doc.pathItems.rectangle(-5, 5, 90, 90);
dough.fillColor = getRGB(255, 225, 150);
dough.stroked   = false;

function getRGB(r, g, b) {
  var color = new RGBColor();
  color.red   = r;
  color.green = g;
  color.blue  = b;
  return color;
}
```

生地の塗りに色をつけたいので `dough.fillColor` に色を指定します！
ただ色を指定する度に先程のように4行書くのは手間になるので、ここでは `getRGB()` という関数にしています。`getRGB(255, 225, 150)` と書くと `rgb(255, 225, 150)` の色が指定できます。

逆に線には色をつけないことにします。
線を使わない場合は `dough.stroked = false;` と書きます。
塗りの場合は `dough.filled = false;` です。

![色付けした正方形](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317963/12643900-764a-c113-4161-6f8180335e9d.png)

ちょっとクッキーっぽくなりましたね！

## 模様づけ
このクッキーのメイン、チェック柄をつけましょう。
小さいチョコレート色の正方形を追加します。

### パスオブジェクト追加
`doc` に新しく `pathItems.rectangle()` を書くとパスオブジェクトの長方形を追加できます。

```javascript
var doc = app.activeDocument;

var dough = doc.pathItems.rectangle(-5, 5, 90, 90);
dough.fillColor = getRGB(255, 225, 150);
dough.stroked   = false;

var choco1 = doc.pathItems.rectangle(-5, 5, 45, 45);
choco1.fillColor = getRGB(150, 100, 50);
choco1.stroked   = false;

function getRGB(r, g, b) {
  var color = new RGBColor();
  color.red   = r;
  color.green = g;
  color.blue  = b;
  return color;
}
```

![小さい正方形を追加した正方形](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317963/051c7d26-8bf2-2ef3-949c-6da50a0b9ec6.png)

左上に正方形が追加されました。

### コピー
同じく右下にも正方形を追加するわけですが、今度はちょっと違う方法を試してみましょう！
左上の正方形をコピーして右下に移動させます。

```javascript
var doc = app.activeDocument;

var dough = doc.pathItems.rectangle(-5, 5, 90, 90);
dough.fillColor = getRGB(255, 225, 150);
dough.stroked   = false;

var choco1 = doc.pathItems.rectangle(-5, 5, 45, 45);
choco1.fillColor = getRGB(150, 100, 50);
choco1.stroked   = false;

var choco2 = choco1.duplicate();
choco2.position = Array(50, -50);

function getRGB(r, g, b) {
  var color = new RGBColor();
  color.red   = r;
  color.green = g;
  color.blue  = b;
  return color;
}
```

`choco1.duplicate()` と書くと左上の正方形 `choco1` をコピーできます！
`var choco2 = choco1.duplicate();` でコピーした正方形に `choco2` と名前をつけ、
`choco2.position = Array(50, -50);` で `choco2` の位置をX座標50、Y座標-50と指定します。

![左上の正方形を右下にコピーした正方形](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317963/101d7678-c12f-78a8-44ee-5bdb8f1077e6.png)

これはほぼほぼクッキーといっても良いのではないでしょうか。

## 角丸
最後の仕上げにクッキーの角を丸くしましょう！可愛くなります。
ここであのクリッピングマスクの登場です。

### グループ化
JavaScriptでクリッピングマスクを使用する場合は予めグループ化をする必要があります。
まず、先程までのクッキーをグループ化してみましょう。

```javascript
var doc = app.activeDocument;

var cookie = doc.groupItems.add();

var dough = cookie.pathItems.rectangle(-5, 5, 90, 90);
dough.fillColor = getRGB(255, 225, 150);
dough.stroked   = false;

var choco1 = cookie.pathItems.rectangle(-5, 5, 45, 45);
choco1.fillColor = getRGB(150, 100, 50);
choco1.stroked   = false;

var choco2 = choco1.duplicate();
choco2.position = Array(50, -50);

function getRGB(r, g, b) {
  var color = new RGBColor();
  color.red   = r;
  color.green = g;
  color.blue  = b;
  return color;
}
```

`doc.groupItems.add()` で新しいグループを作成することが可能です。
ここでは `cookie` というグループを作成し、今までの正方形を `cookie.pathItems.rectangle()` とすることで `cookie` というグループの中に正方形を作るように変えています。
Illustrator上で選択するとグループ化されていることを確認できます。

### 角丸長方形の枠を描画
マスクする為の角丸長方形を描画します。
こちらは今まで何度か出てきた `pathItems` の `roundedRectangle()` を使います。

```javascript
var doc = app.activeDocument;

var cookie = doc.groupItems.add();

var dough = cookie.pathItems.rectangle(-5, 5, 90, 90);
dough.fillColor = getRGB(255, 225, 150);
dough.stroked   = false;

var choco1 = cookie.pathItems.rectangle(-5, 5, 45, 45);
choco1.fillColor = getRGB(150, 100, 50);
choco1.stroked   = false;

var choco2 = choco1.duplicate();
choco2.position = Array(50, -50);

var outline = cookie.pathItems.roundedRectangle(-5, 5, 90, 90, 10, 10);

function getRGB(r, g, b) {
  var color = new RGBColor();
  color.red   = r;
  color.green = g;
  color.blue  = b;
  return color;
}
```

`cookie.pathItems.roundedRectangle(-5, 5, 90, 90, 10, 10)` でグループの中に角丸長方形が追加できます。
`rectangle()` の引数に続いて `角丸の水平(横)方向の半径` `角丸の垂直(縦)方向の半径` を指定します。
`roundedRectangle(上のY座標, 左のX座標, 横幅, 高さ, 角丸の水平方向の半径, 角丸の垂直方向の半径)` です。

### クリッピングマスク
最後の最後、クリッピングマスクで角丸長方形にします！
クリッピングマスクは簡単、グループの `clipped` を `true` にします。

```javascript
var doc = app.activeDocument;

var cookie = doc.groupItems.add();

var dough = cookie.pathItems.rectangle(-5, 5, 90, 90);
dough.fillColor = getRGB(255, 225, 150);
dough.stroked   = false;

var choco1 = cookie.pathItems.rectangle(-5, 5, 45, 45);
choco1.fillColor = getRGB(150, 100, 50);
choco1.stroked   = false;

var choco2 = choco1.duplicate();
choco2.position = Array(50, -50);

var outline = cookie.pathItems.roundedRectangle(-5, 5, 90, 90, 10, 10);
cookie.clipped = true;

function getRGB(r, g, b) {
  var color = new RGBColor();
  color.red   = r;
  color.green = g;
  color.blue  = b;
  return color;
}
```

これでチェック柄の生地が角丸になりました！

## 完成
とても美味しそうなクッキーができましたね！

![完成したクッキー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317963/e508b43b-ab0e-7f8f-4b63-1b78756cf8f2.png)

JavaScriptで絵を描くというと難しそうに聞こえるかもしれませんが、一つ一つの処理を追っていくと意外と簡単に描けると感じてもらえたのではないかと思います。

# おまけ
1つだけだと寂しいので追加で2種類用意してみました！

![Illustratorで制作したクッキー3種類](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317963/d188d1bd-a360-c6f0-46e4-91a9da76b885.png)
<details><summary>コードはこちら</summary><div>

```javascript:cookie.js
/**
 * const
 *
 */
const DOC = app.activeDocument;
const COLOR_DOUGH = getRGB(255, 225, 150);


if (app.documents.length > 0) {
  rand = Math.floor(Math.random() * 4) + 1;
  
  if (rand == 1) {
    bakeCookie1();
  } else if (rand == 2) {
    bakeCookie2();
  } else if (rand == 3) {
    bakeCookie3();
  }
}


/**
 * bakeCookie1
 *
 */
function bakeCookie1() {
  var cookie = DOC.groupItems.add();

  var dough = cookie.pathItems.rectangle(-5, 5, 90, 90);
  dough.fillColor = COLOR_DOUGH;
  dough.stroked   = false;

  var choco1 = cookie.pathItems.rectangle(-5, 5, 45, 45);
  choco1.fillColor = getRGB(150, 100, 50);
  choco1.stroked   = false;

  var choco2 = choco1.duplicate();
  choco2.position = Array(50, -50);

  var outline = cookie.pathItems.roundedRectangle(-5, 5, 90, 90, 10, 10);
  cookie.clipped = true;
}


/**
 * bakeCookie2
 *
 */
function bakeCookie2() {
  var cookie = DOC.groupItems.add();

  var point = 24;
  var dough = cookie.pathItems.star(50, -50, 50, 45, point);
  dough.fillColor = COLOR_DOUGH;
  dough.stroked   = false;

  for (i = 0; i < (point * 2); i++) {
    var anchorLeft  = (i == 0) ? dough.pathPoints[(point * 2) - 1].anchor : dough.pathPoints[i - 1].anchor;
    var anchorRight = (i == (point * 2) - 1) ? dough.pathPoints[0].anchor : dough.pathPoints[i + 1].anchor;
    dough.pathPoints[i].leftDirection  = anchorLeft;
    dough.pathPoints[i].rightDirection = anchorRight;
  }
  for (i = (point * 2 - 1); i >= 0; i--) {
    if (i % 2 == 0) dough.pathPoints[i].remove();
  }

  var dot = cookie.groupItems.add();
  var dotColor    = getRGB(200, 150, 100);
  var dotDiameter = 3;

  var dotOut = dot.pathItems.ellipse(-25, 25, 50, 50);
  dotOut.filled = false;
  dotOut.strokeColor  = dotColor;
  dotOut.strokeDashes = [0, (50 * 3.14 * 2 / point)];
  dotOut.strokeCap    = StrokeCap.ROUNDENDCAP;
  dotOut.strokeWidth  = dotDiameter;

  var dotIn = dot.pathItems.ellipse(-37.5, 37.5, 25, 25);
  dotIn.filled = false;
  dotIn.strokeColor  = dotColor;
  dotIn.strokeDashes = [0, (25 * 3.14 * 4 / point)];
  dotIn.strokeCap    = StrokeCap.ROUNDENDCAP;
  dotIn.strokeWidth  = dotDiameter;

  dot.opacity = 25.0;
}


/**
 * bakeCookie3
 *
 */
function bakeCookie3() {
  var cookie = DOC.groupItems.add();

  var dough = cookie.pathItems.ellipse(-0, 0, 100, 100);
  dough.fillColor = COLOR_DOUGH;
  dough.stroked   = false;

  var almond = cookie.groupItems.add();
  
  var body = almond.pathItems.ellipse(-35, 35, 30, 40);
  var ld = body.pathPoints[1].leftDirection;
  var rd = body.pathPoints[1].rightDirection;
  body.pathPoints[1].anchor = Array(50, -25);
  body.pathPoints[1].leftDirection  = Array(ld[0] + 5, body.pathPoints[1].anchor[1]);
  body.pathPoints[1].rightDirection = Array(rd[0] - 5, body.pathPoints[1].anchor[1]);
  body.fillColor = getRGB(200, 150, 100);
  body.stroked   = false;

  var lineColor = getRGB(175, 125, 100);
  var line1 = body.duplicate();
  line1.filled      = false;
  line1.strokeColor = lineColor;
  line1.width       = 20;
  line1.position    = Array(body.position[0] + 5, body.position[1]);

  var line2 = body.duplicate();
  line2.filled      = false;
  line2.strokeColor = lineColor;
  line2.width       = 10;
  line2.position    = Array(body.position[0] + 10, body.position[1]);  
}


/**
 * getRGB
 *
 */
function getRGB(r, g, b) {
  var color = new RGBColor();

  color.red   = r;
  color.green = g;
  color.blue  = b;

  return color;
}
```

</div></details>

このコードをIllusratorに持ってくるとランダムで1種類クッキーが出てきます。プログラムっぽい！
他2種類もどんなことをしているか簡単にご紹介します。

## ギザギザクッキー
ギザギザのクッキーです！
コードでいうと `bakeCookie2()` です。
最初のクッキーと違う点をピックアップすると

- スターツール
- 破線

このあたりが特徴的かと思います。

### スターツール
ギザギザな図形を作るのにスターツールを使います。

```javascript
var point = 24;
var dough = cookie.pathItems.star(50, -50, 50, 45, point);
```
スターツールは `pathItems.star()` を使います。
`中心のX座標` `中心のY座標` `外側の点の半径` `内側の点の半径` `点の数` を引数に指定します。

### 破線
真ん中の点々を描いていきます。
今回は点線の正円を大小2つ描いています。
Illustratorで点線を描く時は破線にして線端を丸型線端、線分を0にすると簡単にできちゃいます。

```javascript
var dot = cookie.groupItems.add();
var dotColor    = getRGB(200, 150, 100);
var dotDiameter = 3;

var dotOut = dot.pathItems.ellipse(-25, 25, 50, 50);
dotOut.filled = false;
dotOut.strokeColor  = dotColor;
dotOut.strokeDashes = [0, (50 * 3.14 * 2 / point)];
dotOut.strokeCap    = StrokeCap.ROUNDENDCAP;
dotOut.strokeWidth  = dotDiameter;

var dotIn = dot.pathItems.ellipse(-37.5, 37.5, 25, 25);
dotIn.filled = false;
dotIn.strokeColor  = dotColor;
dotIn.strokeDashes = [0, (25 * 3.14 * 4 / point)];
dotIn.strokeCap    = StrokeCap.ROUNDENDCAP;
dotIn.strokeWidth  = dotDiameter;
```

破線は `strokeDashes` を使います。 破線の線分と間隔は `[線分, 間隔]` と指定します。
線端は `strokeCap` を使い、丸型線端は `StrokeCap.ROUNDENDCAP`になります。
線幅は `strokeWidth` で指定できます。

## アーモンドクッキー
アーモンドをトッピングしてみました！
コードでいうと `bakeCookie3()` で、アーモンドは

- アンカーの移動
- アンカーのハンドル操作

このあたりが特徴的かと思います。

### アンカーの移動
アーモンドの形は楕円を描いた後に上のアンカーを移動して尖らせています。

```javascript
var body = almond.pathItems.ellipse(-35, 35, 30, 40);
~ 略 ~
body.pathPoints[1].anchor = Array(50, -25);
```

パスオブジェクトの `pathPoints` でアンカーを取り扱うことができます。
今回は楕円の上のアンカー `body.pathPoints[1]` の位置を `anchor` で指定します。
`body.pathPoints[1].anchor = Array(50, -25);` でX座標50、Y座標-25とすることで元の位置しています。

### アンカーのハンドル操作
アーモンドの上を尖らせる為にハンドルも操作して調整します。

```javascript
var body = almond.pathItems.ellipse(-35, 35, 30, 40);
var ld = body.pathPoints[1].leftDirection;
var rd = body.pathPoints[1].rightDirection;
body.pathPoints[1].anchor = Array(50, -25);
body.pathPoints[1].leftDirection  = Array(ld[0] + 5, body.pathPoints[1].anchor[1]);
body.pathPoints[1].rightDirection = Array(rd[0] - 5, body.pathPoints[1].anchor[1]);
body.pathPoints[1].anchor = Array(50, -25);
```

ハンドルは `leftDirection` で左側のハンドル、`rightDirection` で右側のハンドルの座標を指定できます。
`anchor` でアンカーの座標を移動してもハンドルの座標は変わらない為、ハンドルの長さを変えない場合も移動させる必要があります。

今回は移動前に `ld` `rd` に元々のハンドルの座標を代入しておき、移動後に改めてその値を使っています。
`ld[0] + 5` は移動前のハンドルのX座標を少しアンカー寄りに近づけた数値です。
`body.pathPoints[1].anchor[1]` は移動後のアンカーのY座標です。

このようにアンカー、ハンドルも自由に操作できるので好きなカーブを描くことも可能です！

# まとめ
普通にIllustratorで描いた方が圧倒的に速いです。が、JavaScriptで描くのも楽しかったです！
Illsutratorで作るものがあるけど今日はコード書きたいなって気分の時なんかにオススメします。


# 参考
[Illustrator Scripting](https://www.adobe.com/devnet/illustrator/scripting.html)
[Adobe Illustrator CC 2017 Reference: JavaScript](http://adobe.com/content/dam/acom/en/devnet/illustrator/pdf/Illustrator_JavaScript_Scripting_Reference_2017.pdf)

---
[:christmas_tree: FORK Advent Calendar 2019](/advent-calendar/2019/fork)
:arrow_left: 18日目 [今年の正月はNuxtとFirebaseで作ったカードゲームで甥っ子に尊敬されたい](/yoh_zzzz/items/377f5ddb922c17da73b3) @yoh_zzzz さん
:arrow_right: 20日目 [日本語で書く言語](/re_sai/items/bfb6e34a76dd2d3d21a9) @re_sai さん