✏️

Moddableで図形描画をしてみよう!(Poco, Outline)

2024/08/27に公開

本記事では、Moddableにて図形描画を行う方法を解説します。
入門として、「図形描画を扱うクラスを知る、2D座標空間の考えかたを知る」を目的としています!

やること

  • Pocoを用いた図形描画
  • Outlineを用いた図形描画
  • 図形を組み合わせてスタックチャンのお顔を描画

スタックチャンはししかわが開発、公開している、 手乗りサイズのスーパーカワイイコミュニケーションロボットです。 作品ページ:https://github.com/stack-chan/stack-chan

コード
もし進めるなかで詰まったなどあれば参照ください

modのコード
mod.js
import Poco from 'commodetto/Poco'
import { Outline } from 'commodetto/outline'

const poco = new Poco(screen)
const white = poco.makeColor(255, 255, 255)
const black = poco.makeColor(0, 0, 0)
const red = poco.makeColor(255, 0, 0)

export function onLaunch() {}

const EYE_X_OFFSET = 60
const EYE_Y_OFFSET = 20
const EYE_RADIUS = 10
const EYE_COLOR = black

function drawEyes(poco) {
  // 目のアウトラインを作成
  const path = new Outline.CanvasPath()
  path.arc(0, 0, EYE_RADIUS, 0, 2 * Math.PI)
  const outline = Outline.fill(path)

  // 原点を中心に移動
  poco.origin(poco.width / 2, poco.height / 2)
  // 右目
  poco.blendOutline(EYE_COLOR, 255, outline, poco.width / 2 + EYE_X_OFFSET, poco.height / 2 - EYE_Y_OFFSET)
  // 左目
  poco.blendOutline(EYE_COLOR, 255, outline, poco.width / 2 - EYE_X_OFFSET, poco.height / 2 - EYE_Y_OFFSET)
  // 原点を戻す
  poco.origin()
}

const MOUTH_Y_OFFSET = 20
const MOUTH_WIDTH = 90
const MOUTH_HEIGHT = 10
const MOUTH_COLOR = black

function drawMouth(poco) {
  poco.origin(poco.width / 2, poco.height / 2)
  poco.fillRectangle(
    MOUTH_COLOR,
    - MOUTH_WIDTH / 2,
    MOUTH_Y_OFFSET,
    MOUTH_WIDTH,
    MOUTH_HEIGHT
  )
  poco.origin()
}

export function onRobotCreated(robot) {
  robot.pause() // HACK: 顔と動きの更新を止めておく

  poco.begin()
  poco.fillRectangle(white, 0, 0, poco.width, poco.height)
  drawEyes(poco)
  drawMouth(poco)
  poco.end()
}

Poco

Moddableで図形を描画する方法の一つとして、Pocoがあります。
Pocoはざっくりいうと、長方形を描画したり、テキストを描画したり、JPEG画像を扱ったり出来ます。

ドキュメントではサンプル画像が豊富にあるので、眺めてみるとイメージつくかと思います。
https://github.com/Moddable-OpenSource/moddable-jp/blob/dev/translate-jp/documentation/commodetto/poco.md#poco

準備

本記事では、スタックチャンの開発環境を土台にmodとしてPocoを動かしています。

スタックチャン開発環境で動かす準備

まずはmodに必要なファイルを作成します

stack-chan/
└── firmware/
    └── mods/
        └── poco_study/
            ├── manifest.json
            └── mod.js
manifest.json
{
	"include": [
		"$(MODDABLE)/examples/manifest_mod.json"
	],
	"modules": {
		"*": "./mod"
	}
}
mod.js
import Poco from 'commodetto/Poco'

const poco = new Poco(screen)

export function onLaunch() {}

export function onRobotCreated(robot) {
  robot.pause()  // HACK: 顔と動きの更新を止めておく

  poco.begin()
  // ここに処理を記述していく
  poco.end()
}

modのデバッグは下記コマンドで実行しています。

$ cd firmware
$ npm run debug --target=mac/m5stack
$ npm run mod --target=mac/m5stack ./mods/poco_work/manifest.json

座標空間を把握する

まずは座標空間を知ることから始めます。

  • 左上が(0, 0)
  • x座標は、正の数であれば右方向、負の数であれば左方向
  • y座標は、正の数であれば下方向、負の数であれば上方向

また、Poco経由で画面の横幅、縦幅を取得することが出来ます。

trace(`width: ${poco.width}, height: ${poco.height}`)
// width: 320, height: 240 ※M5Stack

これを利用して、画面の中心座標は(poco.width/2, poco.height/2)で取得することが出来ます。

長方形の描画

長方形の描画にはfillRectangleを用います。
引数の仕様はfillRectangle(color, x, y, width, height)です。

mod.js
  poco.begin()
  // 画面全体を覆う白い長方形を描画
  poco.fillRectangle(white, 0, 0, poco.width, poco.height)
  // 黒い長方形を描画
  // (100, 100) から幅150, 高さ50の長方形を描画
  poco.fillRectangle(black, 100, 100, 150, 50)
  poco.end()

Outline

Moddableで図形を描画するもうひとつの方法として、Outlineがあります。
Outlineはざっくりいうと、パスで図形を作成する、Canvas 2Dっぽいことが出来ます

こちらもドキュメントではサンプル画像が豊富にあるので、眺めてみるとイメージつくかと思います。
https://github.com/moddable-OpenSource/moddable-jp/blob/dev/translate-jp/documentation/commodetto/outline/Outlines.md#アウトライン

円の描画

若干癖のある描画方法です。ざっくりいうと、Outlineで図形を定義して、Pocoで描画するイメージです。

  1. 円のパスを作成
  2. outlineを作成
  3. pocoを使用して描画
mod.js
import Poco from 'commodetto/Poco'
import { Outline } from 'commodetto/outline'

const poco = new Poco(screen)
const white = poco.makeColor(255, 255, 255)
const black = poco.makeColor(0, 0, 0)

export function onLaunch() {}

export function onRobotCreated(robot) {
  robot.pause() // HACK: 顔と動きの更新を止めておく

  poco.begin()
  // 画面全体を覆う白い長方形を描画
  poco.fillRectangle(white, 0, 0, poco.width, poco.height)
  // 中心に黒い円を描画
  // 1. パスを作成
  const path = new Outline.CanvasPath()
  path.arc(0, 0, 50, 0, 2 * Math.PI)
  // 2. outlineを作成
  const outline = Outline.stroke(path) // 塗りつぶし
  // const outline = Outline.stroke(path) // ストローク
  // 3. poco.blendOutlineで描画
  poco.blendOutline(black, 255, outline, poco.width / 2, poco.height / 2)
  poco.end()
}

ちなみにストロークを選択すると塗りつぶしなしで描画されます。

スタックチャンのお顔を描画してみる

ここまでで、長方形と円を描画できるようになりました。
それでは、図形を組み合わせて、スタックチャンのお顔を描画してみましょう。

左右の目を描画

下記の図のイメージで描画します。

個人的には画面中央を基準に考えると計算が楽になる印象です。
処理としては、poco.originを用いて原点を画面中央にずらしています。

画面中央を基準にそれぞれ、

  • 右目はx座標に60、y座標に-20移動した位置に描画します
  • 左目はx座標に-60、y座標に-20移動した位置に描画します
mod.js
const EYE_X_OFFSET = 60
const EYE_Y_OFFSET = 20
const EYE_RADIUS = 10
const EYE_COLOR = black

function drawEyes(poco) {
  // 目のアウトラインを作成
  const path = new Outline.CanvasPath()
  path.arc(0, 0, EYE_RADIUS, 0, 2 * Math.PI)
  const outline = Outline.fill(path)

  // 原点を中心に移動
  poco.origin(poco.width / 2, poco.height / 2)
  // 右目
  poco.blendOutline(EYE_COLOR, 255, outline, poco.width / 2 + EYE_X_OFFSET, poco.height / 2 - EYE_Y_OFFSET)
  // 左目
  poco.blendOutline(EYE_COLOR, 255, outline, poco.width / 2 - EYE_X_OFFSET, poco.height / 2 - EYE_Y_OFFSET)
  // 原点を戻す
  poco.origin()
}

この関数をonRobotCreatedで呼び出すと、スタックチャンの目が描画されます。

口を描画

下記の図のイメージで描画します。

画面中央を基準にx座標に口の長さ/2、y座標に20移動した位置に描画します。

mod.js
const MOUTH_Y_OFFSET = 20
const MOUTH_WIDTH = 90
const MOUTH_HEIGHT = 10
const MOUTH_COLOR = black

function drawMouth(poco) {
  poco.origin(poco.width / 2, poco.height / 2)
  poco.fillRectangle(
    MOUTH_COLOR,
    - MOUTH_WIDTH / 2,
    MOUTH_Y_OFFSET,
    MOUTH_WIDTH,
    MOUTH_HEIGHT
  )
  poco.origin()
}

この関数をonRobotCreatedで呼び出すと、スタックチャンの口が描画されます。

スコブル カワイイ!!

振り返り

Moddableでの図形描画、座標空間の考え方のイメージが少しでも掴めれば幸いです。

もっと色々知りたい方へ

Discussion