プログ

’14修了無内定理系院卒の活動雑記||プー太郎ログ||プログラミング雑記

円形配置とjQueryの汎用化

日記が空いた時というのは大抵飲んだ日です,今回もそうでした.

そんなことは置いといて,ちょっと前に公開された
フリーフォント「MagicRing」
がおしゃれでそのうち使いたいなと思っていて,用途としてはやはり魔法陣かなと考えた時に,円形に文字を配置するのは中々にメンドイのでこれを画像ジェネレータを作ることで解決できないかと考えました.

進捗

f:id:triplog:20140525232136p:plain

円形配置

これまでは読み込んだ画像上に文字を配置するだけだったので扱ってこなかったのですが, canvas には座標変換する関数が用意されています.僕のイメージでは canvas はペイントに近いイメージがあったので,座標変換というと既に描いてあるものに適用するものだと思っており,文字を書く→回転→文字を書く→回転 の繰り返しでいけると考えていました.しかし実際は canvas に対する描き手の姿勢を変えるといった感じで,座標変換をしても canvas の内容は変化せず,一方で変換前後で同じ座標に対して文字を書くと別の場所に書き込まれます.また回転の中心は canvas の原点(0,0)となっています.これらは,向きの概念があまり無い記号なら大きな問題とはならないのですが,文字は向きが決まっているため相性が最悪に思われました,思われました.実際は canvas には座標を移動することで回転の中心を canvas の中心に持ってくることができ,またマイナスの座標も存在するため事なきを得ました.

function marugakiTest(text, i){
  //canvas の取得
  var canvas  = document.getElementById("srcImg");
  var context = canvas.getContext("2d");
  //原点をキャンバスの中心に移動
  context.setTransform(1,0,0,1,240,240);
  context.rotate(-45/180*Math.PI);
  //半径の設定
  var r = radius[i]; //グローバル変数[220,180,140]
  //フォントの設定
  context.font = ((radius[i]-40)/10)+"px 'MagicRing'";
  context.fillStyle = $("#color"+(i+1)).css("color");

  for(var j=0;j<4;j++){
    //90度に円形配置する関数
    marugaki90(context,text,r);
  }
}
function marugaki90(context, text, r){
  //始めに5度空ける
  context.rotate(5/180*Math.PI);
  for(var i=0;i<text.length;i++){
    //中心から半径分だけ上の位置に書き込む
    context.fillText(text[i], 0, -r);
    //2文字以上なら等間隔になるように回転,最大16文字想定
    if(i!==text.length-1)
      context.rotate(16/(text.length-1)*5/180*Math.PI);
    //1文字なら80度回転して終了
    else if(text.length===1)
      context.rotate(80/180*Math.PI);
  }
  //終わりに5度空ける
  context.rotate(5/180*Math.PI);
}

16文字以下のテキストを90度おきに4回配置して1周を書き込む関数です.実際には文字数に応じて色々分岐するようにしています.工夫したのは最初と最後の文字を配置してから残りの文字を等間隔に配置するようにしたことで,これにより単純な等間隔でないムラができて味のある感じになったと思います.また繰り返しの際にも隙間ができるので規則性を感じさせることができます.注意としては,canvas の座標変換状態は引き継がれるので,新しく処理を始める際には座標リセットの掛かる context.setTransform()をしっかり使うことです. あとChromeはサイト側の設定を無視して文字の最小サイズを10ptにするみたいなので,10pt以下の値がセットされるようなデザインにした時は他のブラウザと異なる挙動になりそうです.一応Chromeで使うことを想定しているので,これは仕様という認識にしておきます.

参考
rotate(angle)-Canvasリファレンス
setTransform(a, b, c, d, e, f)-Canvasリファレンス
Google Chromeには「最小フォントサイズ」という設定項目が存在する | CreativeStyle

jQueryの汎用化

前回の日記でjsが汎用化できないと言っていたのと似てるようでちょっと違う話で,今回はファイル内のjQueryを統一したい話.進捗の画像を見ると分かるのですが,今回はデザイン的な要素が大きいので文字色やサイズを変更できるようにしています.同じようなことをするので冗長にならないようにまとめておきたいけれど,一方で内容は微妙に異なる処理をイベント関数でどう実行したら良いのか.試行錯誤した結果以下の様に.

<style>
#color1,#color2,#color3 { background-color:#888888;color:white;}
 #plus1, #plus2, #plus3 { background-color: red;   color:white;}
#minus1,#minus2,#minus3 { background-color:blue;   color:white;}
#edit>span {-moz-user-select: none; -webkit-user-select: none; -ms-user-select: none;unselectable="on"}
#edit>input{width:246px;}
</style>
<!--中略-->
<div id="edit">
<!--中略-->
    <span id="color1"></span><input type="text" name="outer"  placeholder=<?php echo $serif1;?>><span id="plus1"></span><span id="minus1"></span><br>
    <span id="color2"></span><input type="text" name="middle" placeholder=<?php echo $serif2;?>><span id="plus2"></span><span id="minus2"></span><br>
    <span id="color3"></span><input type="text" name="inner"  placeholder=<?php echo $serif3;?>><span id="plus3"></span><span id="minus3"></span><br>
//■をクリックで色が変わる
$("#edit>span:contains('■')").click(function(){
  switch($(this).css('color')){
    case "rgb(255, 255, 255)":$(this).css('color','#00FFFF');break;
    //省略
  }
  renewText();
});
//+をクリックで円が大きく
$("#edit>span:contains('+')").click(function(){
  var num = $(this).attr("id").substring(4);
  var index = parseInt(num,10)-1;
  radius[index]+=10;
  renewText();
})
//-をクリックで円が小さく
$("#edit>span:contains('-')").click(function(){
  var num = $(this).attr("id").substring(5);
  var index = parseInt(num,10)-1;
  radius[index] = (radius[index]>50)?radius[index]-10:50;
  renewText();
})

まずそれぞれの文字に対しidを振り,文字毎に共通のワード+数字にしておきます.こうすることで先ほどのプログラムにも登場したように, $("color"+i) といった表現で要素を選べるようになります.そして this によって個別の動作を可能にしていますが, this だけでは当然 this 以外のものを操作できません.そこで, attr("id") から id を string で取得し, substring() で数字部分を取得,それを parseInt することで数値に直し配列の引数に利用しています.jQueryの動作について細かく分かっていないので,もしかすると $("#edit>span")でイベントを起動してから内部で分岐した方が高速化するかもしれません.span タグはダサい上に,半角スペースの影響があるから一行に書かないと思い通りにならないのが悩み.

参考
画面上の文字や画像を選択できないようにする at softelメモ
atoi() (C) in JavaScript - finbr
attr(name) - jQuery 日本語リファレンス

その他

今まで canvas の初期化に画像の読み込みを利用していましたが,今回は元となる画像がありません.当初は画面いっぱいの四角形を描画していたのですが, clearRect() というメソッド(?)に気づきまして,これを利用することで空にすることができ,PNGで保存することで透明背景で保存ができます.あくまで適当な文字列からそれっぽい簡易魔法陣を描くことを想定してるから実用する人は中々居ないだろうけど他の絵に重ねて使うことはできますね.本格的な魔法陣を描けるようにしようとすると,実装に手間が掛かりそうな上にとっつきにくさが上がってしまいそうなのでそれは気が向いたらってことにします.今後は以前も書いた OpenCVjs を作成した魔法陣に対して適用したり,魔法陣をアニメーションさせて gif で保存できるようにしたり,はたまた入力した文字列から適当な魔法効果(テキスト)を生成したりといったことができたらなと.あと慣れてきたら水玉コラジェネレーターでも作ったらウケるかと思ったら,先人が居た上に素敵な名前が付いていたので諦めることにしました.腕試し兼ねて実装は挑戦するかもしれないけどね.

参考
今更聞けないcanvasの基礎の基礎 | tech.kayac.com - KAYAC engineers' blog
フォトショップでCG講座 Atelier Unicorn 分室 魔方陣の描き方
javascriptで画像処理してみよう(1)〜OpenCVjs〜|画像処理だけで飯が食えるかっ!?
クリヌイター - オンライン水玉ジェネレーター