2013年10月6日日曜日

JavaScriptでGIFアニメの分解と逆再生やってみたかった

ネット上に転がっているGIFアニメ、とても好きなのですが、フレーム早すぎてなにが写っているかわからないものもあったりでフレーム毎に分割したいと思うことが何度かありました。
ImageMagickとかで分割とかはとても楽にできますが、貧弱なサーバーだとちょっとアクセス来ただけで残念なことになってしまうので、クライアント側でできないのかなってことでJavaScriptで実行してみることにした。

一応それなりに動くところまでできたので公開します。
現在Chrome拡張機能だけです。Firefoxはそのうち……

ソース&説明: https://github.com/aaharu/gifken/tree/master/sample/chromeextension
ダウンロード: https://www.dropbox.com/s/gkrl4scag4hf2tt/chromeextension.crx

実際使うとどんな感じになるのか?
こんな感じになります。

GifgigのとあるGIFアニメ。うまくできた&わかりやすい画像選んでみました。
岐阜gazo - まどマギ


右クリックで「GIFアニメ分解」を選択


新しいタブで結果画像が表示される


ArrayBufferでバイナリ解析できるので、それで実装しています。バイナリ解析行っているところはTypeScriptで実装してみました。
不完全なので、うまくいかない画像あったら教えてください。

2013年5月11日土曜日

EaselJS (CreateJS) を拡張してTextで縦書きできるようにしてみた

最近EaselJSをさわりはじめたんですが、AS3を少しやっていたのでAPIがにていてわかりやすいです。
そんなに複雑なもの作ろうとしているわけじゃないので、別に素のJSをつかってかいても良かったんですが、、、

EaselJSのTextですが、例によって縦書きはできないようで、縦書きをたまに使用する日本人にはかゆいところに手が届かない感じです。
EaselJSはMITライセンスのOSSだったので、勉強がてら拡張して縦書きもできるTextExを作ってみました。

TextEx.js

変更点はdirectionプロパティを追加して、directionが"vertical"のときforで一文字ずつ縦に描画するようにしただけです。
--- Text.js 2013-05-11 19:36:31.000000000 +0900
+++ TextEx.js 2013-05-11 20:57:56.000000000 +0900
@@ -41,7 +41,7 @@
  * multiple font styles, you will need to create multiple text instances, and position them manually.
  *
  * <h4>Example</h4>
- *      var text = new createjs.Text("Hello World", "20px Arial", "#ff7700");
+ *      var text = new createjs.TextEx("Hello World", "20px Arial", "#ff7700");
  *      text.x = 100;
  *      text.textBaseline = "alphabetic";
  *
@@ -59,17 +59,17 @@
  * @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex.
  * "#F00", "red", or "#FF0000").
  **/
-var Text = function(text, font, color) {
+var TextEx = function(text, font, color) {
   this.initialize(text, font, color);
 }
-var p = Text.prototype = new createjs.DisplayObject();
+var p = TextEx.prototype = new createjs.DisplayObject();
 
  /**
   * @property _workingContext
   * @type CanvasRenderingContext2D
   * @private
   **/
- Text._workingContext = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")).getContext("2d");
+ TextEx._workingContext = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")).getContext("2d");
 
 // public properties:
  /**
@@ -142,6 +142,13 @@
   **/
  p.lineWidth = null;
  
+ /**
+  * Indicates the text direction. Any of "horizontal" or "vertical". Default is "horizontal".
+  * @property direction
+  * @type String
+  */
+ p.direction = "horizontal";
+ 
 // constructor:
  /**
   * @property DisplayObject_initialize
@@ -182,7 +189,7 @@
  p.DisplayObject_draw = p.draw;
  
  /**
-  * Draws the Text into the specified context ignoring it's visible, alpha, shadow, and transform.
+  * Draws the TextEx into the specified context ignoring it's visible, alpha, shadow, and transform.
   * Returns true if the draw was handled (useful for overriding functionality).
   * NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
   * @method draw
@@ -226,7 +233,7 @@
 
  /**
   * Returns the approximate height of multi-line text by multiplying the number of lines against either the
-  * <code>lineHeight</code> (if specified) or {{#crossLink "Text/getMeasuredLineHeight"}}{{/crossLink}}. Note that
+  * <code>lineHeight</code> (if specified) or {{#crossLink "TextEx/getMeasuredLineHeight"}}{{/crossLink}}. Note that
   * this operation requires the text flowing logic to run, which has an associated CPU cost.
   * @method getMeasuredHeight
   * @return {Number} The approximate height of the drawn multi-line text.
@@ -236,12 +243,12 @@
  }
  
  /**
-  * Returns a clone of the Text instance.
+  * Returns a clone of the TextEx instance.
   * @method clone
-  * @return {Text} a clone of the Text instance.
+  * @return {TextEx} a clone of the TextEx instance.
   **/
  p.clone = function() {
-  var o = new Text(this.text, this.font, this.color);
+  var o = new TextEx(this.text, this.font, this.color);
   this.cloneProps(o);
   return o;
  }
@@ -252,7 +259,7 @@
   * @return {String} a string representation of the instance.
   **/
  p.toString = function() {
-  return "[Text (text="+  (this.text.length > 20 ? this.text.substr(0, 17)+"..." : this.text) +")]";
+  return "[TextEx (text="+  (this.text.length > 20 ? this.text.substr(0, 17)+"..." : this.text) +")]";
  }
  
 // private methods:
@@ -266,7 +273,7 @@
 
  /** 
   * @method cloneProps
-  * @param {Text} o
+  * @param {TextEx} o
   * @protected 
   **/
  p.cloneProps = function(o) {
@@ -284,7 +291,7 @@
   * @protected 
   **/
  p._getWorkingContext = function() {
-  var ctx = Text._workingContext;
+  var ctx = TextEx._workingContext;
   ctx.font = this.font;
   ctx.textAlign = this.textAlign||"start";
   ctx.textBaseline = this.textBaseline||"alphabetic";
@@ -333,16 +340,28 @@
  /** 
   * @method _drawTextLine
   * @param {CanvasRenderingContext2D} ctx
-  * @param {Text} text
+  * @param {String} text
   * @param {Number} y
   * @protected 
   **/
  p._drawTextLine = function(ctx, text, y) {
   // Chrome 17 will fail to draw the text if the last param is included but null, so we feed it a large value instead:
+  if (this.direction === "vertical") {
+   var lineHeight = this.lineHeight||this.getMeasuredLineHeight();
+   if (this.outline) {
+    for (var i=0, l=text.length; i<l; ++i) {
+     ctx.strokeText(text.charAt(i), -y - lineHeight, i*lineHeight, this.maxWidth||0xFFFF);
+    }
+   } else {
+    for (var i=0, l=text.length; i<l; ++i) {
+     ctx.fillText(text.charAt(i), -y - lineHeight, i*lineHeight, this.maxWidth||0xFFFF);
+    }
+   }
+  } else {
    if (this.outline) { ctx.strokeText(text, 0, y, this.maxWidth||0xFFFF); }
    else { ctx.fillText(text, 0, y, this.maxWidth||0xFFFF); }
-  
+  }
  }
 
-createjs.Text = Text;
-}());
\ No newline at end of file
+createjs.TextEx = TextEx;
+}());

2012年1月22日日曜日

きんつば

今日は上野・浅草あたりをぶらぶらしてきたんですが、お店で「きんつば」を見つけたので買ってみました。


この「きんつば」というお菓子、Wikipediaにも書かれてますが、私の出身のほう(福島県会津)では、今川焼き(大判焼き)のことをきんつばと呼んでたりします。
http://ja.wikipedia.org/wiki/%E3%81%8D%E3%82%93%E3%81%A4%E3%81%B0

各地で呼び方が異なるならまだしも、その名前の別のお菓子があるとなると非常に紛らわしいです。罠です。陰謀です。

地元の人は今川焼きじゃないきんつばのこと知らない人が多いんじゃないかと思います。私も知ったのは比較的最近(大学生の時)で、友人に話しても「え?マジで?」みたいな反応です。
会津でも場所によると思いますが、喜多方、若松あたりはきんつばって呼んでる?と思うので、ほぼ会津全域そうなんじゃないかなー。
中通りになるとどうだかしらないですけど。

2012年1月8日日曜日

Flashで3Dなお絵描き

大学の時にブラウザ上で3Dモデルに絵を描くアプリケーションを作りました。

長い間ブログを放置していたので、それの紹介でもしたいと思います。

まずブラウザで3Dを扱うというのはあまりない話です。重いですし。
下のリンクでは描いたテクスチャを3Dモデルに貼り付けるようなことをやっていました。
http://labs.logosware.com/archives/21

なんやかんやあってFlashで作るのがいいだろうって話になって、そこからは自分がいろいろ試して作っていくことになりました。
Flashもそんな詳しいわけではなかったし、3Dは全く分からないというぐらいだったので開発には苦労しました。
ブログの過去記事などを見ると試行錯誤の跡が少し見られると思います。

まず3Dモデルをどうするかという話でしたが、開発メンバーが有料のモデリングソフトもってない、Flashの3Dライブラリでサポートされてるといった点などを考えた結果、Collada(.xml)形式にしました。

で、3Dライブラリは当時(今でも?)一番メジャーなPapervision3Dにしようと考えていました。

http://blog.r3c7.net/?p=333
上の記事を参考にサンプルを作っていきました。3Dモデルの作成や、マウスイベントなど苦労しましたがなんとかそれっぽいものはできました。
じゃあ次はシェーダーを試そうと思った時に問題が起きました。
http://aaharu.blogspot.com/2009/10/pv3duv.html
UVマッピングを使用したモデルにシェーダーを適用すると黒い線が出てきました。
これの解決方法は結局わからず、Papervision3D以外のライブラリではどうなのか試してみることにしました。

で、試したのがAway3Dです。
まあFlashの3Dライブラリといってもメジャーなのは、Papervision3DとAway3DとAlternativa3Dぐらいでしたし、Papervision3Dから派生したものだと聞いて今までのが移植しやすいかなーと。

それで試した結果、Away3Dは問題なく動作することがわかり、3DライブラリはAway3Dを使用することに決めました。
Away3Dにして良かった点はいくつかありました。
・上の黒い線の問題が解決したこと
・3Dマウスイベントが豊富なこと
・シェーダーなども豊富で、きれいであったこと
絵を描くということを考えると3Dのオブジェクトのマウスイベントがあるのはとてもよかったです。
悪いと思った点もいくらかあります。
・重い
・そんなに移植しやすいわけではなかった

とそんな感じでぐだぐだと作りました。
せっかくなので、FlashDevelopで書き直した、一部機能削除版をソースコードごと公開します。(開発時はFlashBuilderを使いました。学生だったので無料ライセンスで)
今見るといろいろと直したくなりますがそこらへんは許してやってください。


Flexアプリ http://www.aaharu.com/data/UruLabCreater.swf
ソースコード(Flex) http://www.aaharu.com/data/srcview_urulabcreater/index.html
ソースコード(ライブラリ)http://www.aaharu.com/data/srcview_urulab/index.html

ソースコード書き出し後に修正行ってるのでソースコード最新じゃないですけどだいたいあってます。

2011年8月10日水曜日

GAE/Pのtemplateのextendsでちょっとはまった

Google App Engine (Python) のdjangoのテンプレートを使っていてちょっと躓いたことがあった。

私は、 http://www.aaharu.com/ というサイトを持っていますが、これはGAEで作っていて、DNSもGoogle Appsで設定しています。
お金がかかっているのはドメインだけ。

以前まではテンプレートを使わないでHTMLを表示していたわけですが、更新のたびにすべてのHTMLを変更するのが面倒になったので、テンプレートを使ってみることにしました。
GAEには標準でDjangoのテンプレートエンジンが入っていて、テンプレート継承の仕組みがあります。
http://djangoproject.jp/doc/ja/1.0/topics/templates.html
http://d.hatena.ne.jp/griefworker/20091028/gae_inherit_template

それで、テンプレート継承を使ってみたわけなのですが、HTMLの表示がくずれる現象が起きました。
HTMLのソースを表示させても問題は見当たらない。そしてなぜかOperaだけは正しく表示してくれる。

Chromeの『要素を検証』で確認してみると、どうやらbase.htmlの内容の前に空文字が挿入されているようだったので原因をいろいろ調べると、原因はUTF-8のBOMだった。
base.htmlがBOMありのUTF-8だったため、先頭の1文字を変な風に解釈していたようです。

というわけで、PythonでUTF-8扱うときはBOMは消そうと思いました。