2015年2月15日日曜日

ブラウザとNode.jsで動作するライブラリの書いたときのやり方

動機

gifken というJavaScriptでGIF画像を作ったり、GIFアニメを分割や逆再生するようなライブラリをちまちま書いています。
もともとブラウザで動作させることしか考えていなかったのですが、Node.jsでも動かせそうだったので、対応させたときのやり方を書きます。
(ただNode.js環境でGIF画像など扱いたい場合は http://aheckmann.github.io/gm/ などでGraphicsMagickやImageMagick使った方が素直だし速度面などを考えても良いです)

導入

もっと良い方法がありそうな気もします……

もともとのものが1つの大きなJavaScriptだったので、ブラウザ依存の処理と、共通で利用できる処理をまず分けました。
Node.jsにはモジュールの仕組みが既にあり、ブラウザでその処理を動くようにするソフトウェアが既にあるのでそれを利用するのが楽でしょう。(Browserify, Webpackなど)
私は Browserify を利用しました。

Browserify の導入自体は楽だったのですが、Browserifyはrequireなどの依存関係を解決してJavaScriptファイルを書き出してくれますが、Browserifyで生成した関数に閉じた状態で出力されるので、scriptタグで読み込むだけで使えるようになる、という状態にはなりません。
Browserify のオプションを見ても特にそれらしいものは見つかりませんでした。
よって、以下のようにしました。

windowオブジェクトに追加してしまいます。
WebWorkerではwindowがglobal objectではないので、importScriptsする前に var window = self; とでもしてください……

まとめ


Browserifyの導入とglobal objectへの追加で何とかなった。

2014年4月10日木曜日

Vert.xでScalaのサンプルをとりあえず動かす

Vert.x が最近気になっているので、サンプルを動かすところまでやってみたいと思います。
Javaのサンプルでもいいのですが、多言語対応がウリの1つで、Scalaでも書けるということなので、せっかくだからScalaを使ってみたいと思います。

使用する Vert.x のバージョンは現在最新の 2.1RC3 を使います。

Vert.x のインストール


公式サイトの手順を参考に進めます。

2.1RC3のダウンロード後、解凍する。
$ tar -zxf ~/Downloads/vert.x-2.1RC3.tar.gz
$ ~/vert.x-2.1RC3/bin/vertx version
2.1RC3 (built 2014-04-02 10:07:26)

Scala モジュールのインストール


2.1RC3の時点では、 Scala は標準では使えず、別途言語モジュールとして提供されているようです。
詳しくは知らないですが、 Vert.x Module Registry
io.vertx~lang-scala~1.0.0
が登録されているので、どうやらこれのようです。

公式のモジュールマニュアルを参考にインストールしてみます。

$ ~/vert.x-2.1RC3/bin/vertx install io.vertx~lang-scala~1.0.0
Attempting to install module io.vertx~lang-scala~1.0.0 
Downloading io.vertx~lang-scala~1.0.0. Please wait... 
Downloading 100%
Module io.vertx~lang-scala~1.0.0 successfully installed 
Succeeded in installing module

どうやらインストールできたっぽい。

サンプルの実行


Vert.x ExamplesとしてサンプルがGitHubに公開されているので、 Scala の Route Match を動かしてみたいです。
$ git clone https://github.com/vert-x/vertx-examples.git
$ cd vertx-examples/src/raw/scala/
$ ~/vert.x-2.1RC3/bin/vertx run route_match/RouteMatchExample.scala
Failed in deploying verticle 
java.lang.ClassNotFoundException: route_match/RouteMatchExample.scala
 at org.vertx.java.platform.impl.ModuleClassLoader.loadFromModule(ModuleClassLoader.java:127)
 at org.vertx.java.platform.impl.ModuleClassLoader.loadClass(ModuleClassLoader.java:108)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 at org.vertx.java.platform.impl.java.JavaVerticleFactory.createVerticle(JavaVerticleFactory.java:55)
 at org.vertx.java.platform.impl.DefaultPlatformManager$21.run(DefaultPlatformManager.java:1723)
 at org.vertx.java.core.impl.DefaultContext$3.run(DefaultContext.java:175)
 at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:370)
 at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:353)
 at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116)
 at java.lang.Thread.run(Thread.java:724)

Failed in deploying verticle 
java.lang.ClassNotFoundException: route_match/RouteMatchExample.scala
 at org.vertx.java.platform.impl.ModuleClassLoader.loadFromModule(ModuleClassLoader.java:127)
 at org.vertx.java.platform.impl.ModuleClassLoader.loadClass(ModuleClassLoader.java:108)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 at org.vertx.java.platform.impl.java.JavaVerticleFactory.createVerticle(JavaVerticleFactory.java:55)
 at org.vertx.java.platform.impl.DefaultPlatformManager$21.run(DefaultPlatformManager.java:1723)
 at org.vertx.java.core.impl.DefaultContext$3.run(DefaultContext.java:175)
 at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:370)
 at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:353)
 at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116)
 at java.lang.Thread.run(Thread.java:724)
module のインストールがうまくいってない気がしてくるエラーです。
http://vertx.io/mods_manual.html をもう少し丁寧に読んでみると、 lang-impl に書いてあることが参考になりそうでした。
~/vert.x-2.1RC3/conf/langs.properties を覗くと
groovy=io.vertx~lang-groovy~2.0.0-final:org.vertx.groovy.platform.impl.GroovyVerticleFactory
のような記述があり、これにならって Scala の設定も書けば動きそうな気がしてきます。

# ~/vert.x-2.1RC3/conf/langs.properties に以下を追加
scala=io.vertx~lang-scala~1.0.0:org.vertx.scala.platform.impl.ScalaVerticleFactory
.scala=scala

再度サンプルを実行。
$ ~/vert.x-2.1RC3/bin/vertx run route_match/RouteMatchExample.scala
Compiling route_match/RouteMatchExample.scala as Scala script 
Starting route_match/RouteMatchExample.scala 
Succeeded in deploying verticle
うまく実行されたので、 localhost:8080 を開いて動作を確認するところまでできました。


とりあえず動かすとこまでできたので、次はパフォーマンスの検証などしてみたいです。

2014年3月2日日曜日

画像に文字を上書きできるようなものを作ってみた

WEBにある画像の文字だけ書き換えたいなーってときに、一度保存してからペイントで編集して〜っていう作業が面倒だと思ったので、それっぽいものを作ってみた。


実は1年くらい前から公開してたのですが、放置してました。
デザインももっとマシにしようとは思いつつも手をつけてないので察してください。

どういうことができるかというと、

URLのパラメータをぽちぽち変更するのは大変だったので、JavaScriptで簡単なURLメーカーもつくりました。

機能しょぼいですが、クソ文字コラメーカーぐらいにはなるのかなーという気はします。
Twitter Cardsにも対応させた気がするので、Twitterでの画像展開もされるかも。

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;
+}());