音の鳴るブログ

鳴らないこともある

古めかしいCGを描くためのライブラリ gretro です

gretro - JavaScript graphic library for retro CG

最近、昭和っぽい気持ちのCGを描きたい感じで20年くらい前の本を参考にしながら とか とか描いて遊んでいたのだけど、古い本を参考したら昭和っぽい絵が描けるというわけではなくて、Canvas に描くとどうしてもモダンな感じになってしまうという問題があった。

昔は4096色中の16色しか使えないとか座標は整数のみとか色々制約があって、それでも絵を描くためにタイルパターン(2色を交互に並べたりして中間の色を出す)とか駆使していたのだけど、今の Canvas だと16777216色、透明あり、小数の座標、アンチエイリアスも利くとか表現力が格段に高くてちょっと描いただけですぐモダンな感じになってしまう。大は小を兼ねるとか言ってもちろん Canvas でもピクセル単位に描画して昭和っぽい絵を描くことは可能なのだけど、そのための便利な方法がなさそうなので作った。

主な特徴

  • 全4096色中16色同時使用可能!!!!
  • 32種類のタイルパターン、そのうち16種類はカスタマイズ可能!!!!
  • プラグインシステムで便利描画関数を拡張できる!!!!

頑張って作ってみたは良いものの、これを使って絵を描くのは結構大変で、ひとつひとつ丁寧に試行錯誤する必要があって、飽き性な自分には使いこなせそうにない。READMEまでは頑張って書いたけど、ドキュメント は途中までしか書いていなくて興味がある人がいるなら続きを書こうかなという感じ。

Travis CI から Coveralls にカバレッジレポートを送る

よく忘れるのでメモ。

事前作業

gem install travis

手順

  1. GitHubリポジトリを作って first commit する
  2. Travis のアカウントページでリポジトリを enabled にする
  3. Coveralls でリポジトリを ADD REPO する
  4. Coveralls のリポジトリページの REPO_TOKEN をコピー
  5. travis encrypt COVERALLS_REPO_TOKEN=[REPO_TOKEN]
  6. 出力される secure: "....." をコピー
  7. .travis.yml に以下のように書く
env:
  global:
    - secure: "....."
before_install:
  - npm install -g coveralls
script:
  - npm test
after_success:
  - coveralls < ./coverage/lcov.info

前回、円を再帰的に描いたので、今回は線を再帰的に描いた。

fractal/line

例えば以下の左図のような 5辺で作られる図形(ジェネレータ)のそれぞれの辺をその相似で描画していくと右図のようになる。単にそれだけなんだけど再帰の階層を増やすだけでどんどん意味不明な感じになっていって面白いというやつです。再帰万歳!!みたいな。

f:id:mohayonao:20140805205158p:plain

左右対称、水平垂直線中心、斜め線の導入等、ジェネレータの形によってかなりイメージの違う絵が描画される。ディスプレイに描画された抽象画は美術館のもののような存在感はないけど、描画がアニメーションするのでライブペインティング的な面白さがあるように思える。

ジェネレータ編集画面等の UI は vue.js で書いたのだけど、あまり調べずに適当に書いてもそれなりに出来た。各頂点を選択したらパスが結ばれるという画面で SVG を使っているのだけど、自分で全部実装していたら途中で諦めていたと思う。いやいや凄い。くだらないウェブアプリを大量に作りたい気持ちになる。

http://the.mohayonao.com/fractal/line/#AGDGDBFBFMMMMG,71,41

f:id:mohayonao:20140805201526p:plain

http://the.mohayonao.com/fractal/line/#AGEGFEGIHBIJJGMG,88,11

f:id:mohayonao:20140805201531p:plain

円を再帰的に描画していって模様を作るプログラムを書いた。

fractal/circle

本屋で時間を潰していたときに何となく昭和のプログラムな気分になって、古いコンピュータグラフィックの本を探すと少しだけあったので購入してそれを参考にした。本のとおりに再帰的に描画関数を呼び出すのではなくて引数をスタックで管理してアニメーションで徐々に描くようにしたので、全部描くのに1日以上かかるような円でも描ける。どうってことない感じだけどけっこう奇麗なのが描ける。こういうプログラムはすごく頑張ってプログラムを描かなくても何か出来た気持ちになれるので、忙しいときの息抜きに最適でもっと息抜きしたいと思わせる。

http://the.mohayonao.com/fractal/circle/#67,80,10,93

f:id:mohayonao:20140802084550p:plain

http://the.mohayonao.com/fractal/circle/#95,6,40,55

f:id:mohayonao:20140802084324p:plain

昭和と平成ではディスプレイの解像度が違うので昭和っぽい描画プログラムでも今っぽく見える。昭和っぽさを出したいのなら、ピクセル単位の描画ライブラリを作った方が良さそう。

あと、購入しなかったけどエクセルとVBAで絵を描く本もあってなかなか業が深いと思った。

CによるフラクタルCG (Information & Computing)

CによるフラクタルCG (Information & Computing)

フラクタルで描く 魅惑的な画像の世界

フラクタルで描く 魅惑的な画像の世界

GIFアニメを静止画にするやつ

GIFアニメというのがあって、普通の動かない画像よりもジフギフ動くGIFアニメのほうが格好良いぞということで、みんな頑張ってGIFアニメを作っている。じゃあ、逆にGIFアニメを普通の動かない画像にしたら最高に格好悪いのではないかと思って試した。

GIFアニメをドラッグ&ドロップするとアニメーションの各フレームを合成して静止画をつくる。

GIFのデコードにはこのライブラリを使った。

作例

f:id:mohayonao:20140729211804g:plain

f:id:mohayonao:20140729211841p:plain

こんな感じとか。

f:id:mohayonao:20140729211926g:plain

f:id:mohayonao:20140729211941p:plain

こんな感じになる。

require されても exports しない

最近そういう書き方を思いついて実践している。

前提

  • 100-200行程度のファイルの集合
  • 10000行程度のスタンドアロンライブラリ
  • node.js でテストして、最終的には結合してブラウザで使う
  • 外部モジュールは使用しない

大雑把な説明

  • ブラウザで require するの難しい。。
  • require したくない。。
  • require 消したいけど、消しにくい。。
  • require で代入するから消しにくいのでは?
  • 大きい名前空間をつくって各モジュールが名前空間を拡張すれば良いのでは?
  • それで最後に依存関係に従って単純に貼り付けていけば良さそう

例えば color というモジュールはこういう感じで書く。 グローバル変数として名前空間があって、そこにモジュールを追加するだけで exports していない。

// color.js
(function(namespace) {

  namespace.color = {
    fromRGB: function(r, g, b) {
      return [ r, g, b ];
    }
  };

})(namespace); // namespace というのはグローバル変数

color モジュールの利用方法はこういう感じ。 require するけど変数には代入せずに、変わりに名前空間経由でモジュールを取得する。node.js では先にモジュールが読み込まれるので問題なく動くし、ブラウザ向けに結合するときは main.js より先に color.js を貼り付ければよい(その際 require は不要になるので行ごと消す)。要は require は依存関係の表明としてだけ使う。

// main.js
(function(namespace) {
  require("./color"); // color.js が事前に読み込まれればこの行は不要になる

  var color = namespace.color;

  var white = color.fromRGB(255, 255, 255);

  namespace.main = {
    run: function() {
      console.log(white);
    }
  };

})(namespace);

最終的にライブラリとして外部に提供する部分。名前空間のうち特定の部分だけを外に出す。

// exports.js
(function(namespace) {

  require("./main");

  if (typeof module !== "undefined") {
    global.module.exports = namespace.main;
  } else {
    global.myApp = namespace.main;
  }

})(namespace);

最終的にライブラリとしてパッケージするときに名前空間グローバル変数は消してしまう。

node.js の場合

global.namespace = {};

module.exports = require("./exports");

delete global.namespace;

ブラウザの場合

(function(global) {
  var namespace = {};

  // color.js を貼り付け
  // main.js を貼り付け
  // exports.js を貼り付け
  // (各スクリプトを貼り付けるときに require の行を削除する)

})(this);

注意点

JavaScript の var の位置

JavaScript のコードスタイルに var をどのように書くかというのがある。JavaScript の仕様では関数以外にスコープをつくる方法はなくて、しかも関数内で宣言された変数はいかなる場所で宣言されていたとしても、全部関数の先頭部分で宣言しているのと同じことになるし、ブロックはスコープに対してなんの意味ももたない。と言うことで、だいたい先頭部分にまとめましょうとか一つの var しか許さないことにしましょうとか言うのだけど、僕は最近使う場所で宣言するようにしている。例なので変なプログラムを書くけど、こんな感じ。

// リストの各要素からリストの平均値で引き算する
function deviations(list) {
  // ----- 合計を求める -----
  var sum = 0;
  for (var i = 0; i < list.length; i++) {
    sum += list[i];
  }

  // ----- 平均を求める -----
  var avg;
  if (i !== 0) {
    avg = sum / i;
  }

  // ----- 平均で引き算する -----
  var result = [];
  for (var i = 0; i < list.length; i++) {
    result[i] = list[i] - avg;
  }
  return result;
}

こういう気持ち悪いコードを書いている。気持ち悪いポイントは以下。

  • var i が 2回ある (jshint, eslint で怒られる)
  • avg の計算に i を使っている (eslint だと block-scoped-var で警告)
  • そもそも var が途中に出てくるとキモイ (美意識の問題)

このプログラムですべての変数を冒頭の var で宣言してしまうと、機械には怒られなくて、時間差で人間に怒られることになる(僕は他の人と作業していないので想像だけど)

何でこんなに気持ち悪いことになっているのかというと、コメントに書いてある通りで 合計の計算 → 平均の計算 → 引き算 と複数の処理が行われているからで、そのたびに var が出現していてそこから気持ち悪さが滲み出している。キモさの可視化ともいえる。

こういう気持ち悪いコードを書いてしまうと魂がダメになっていくし、後で人間に怒られるのは嫌なので早々に修正したくなる。そのときに var の位置がコードを分割する目安になるし、どうにもこうにも変な位置に var が残るのであればオブジェクトとして分割するとか設計に問題があるとか検討しやすくなる。もし最初に var の位置を先頭に持ってきたばかりに機械に怒られず後で人間に怒られて修正するはめになったときでも有効で、とりあえず var を使う位置に移動させると先の理由で修正しやすくなる。

ということで、var を使う場所に書いて気持ち悪いコードを書いている。

function sum(list) {
  return list.reduce(function(a, b) {
    return a + b;
  }, 0);
}

function average(list) {
  if (list.length === 0) {
    return 0;
  }
  return sum(list) / list.length;
}

function deviations(list) {
  var avg = average(list);
  
  return list.map(function(x) {
    return x - avg;
  });
}