0

glitchtweet.com作った

2ヶ月ぐらい前に作った。blog書くのめんどくさくて放置してた。

http://glitchtweet.com


俺がやっているような素敵な装飾がついたツイートがだれでもできるwebサービスです。しかも気に入ったのがでるまで何度でもやりなおせる。
iPhoneやAndroidから使うことを想定している。



ライブラリ以外のコードを数えたら、286行しかなかった。
その他は以前作ったテキストに文字装飾を行うglitchtext.jsが500行ぐらい。sinatra、haml、jqueryのおかげでシンプルに書けた。

ソースはここにある
shokai/glitchtweet-web-app – GitHub



以下細かいことなどを書く

■ファイルサイズ
でかい。glitchtext.jsが辞書が巨大すぎて300kb以上ある。けどしょうがないし、まあキャッシュ効くからいいか


■サーバー
さくらVPSのdev.shokai.orgに、virtualhostでglitchtweet.com割り当てて使ってる。


■開発環境
まずglitchtext.jsはv8とRakeで単体でテストをしている。前に書いた
glitchtweet.comのwebアプリ自体はローカルでwebrickで起動して、chromeの開発パネルでjsなどのデバッグをした。あとはiPhoneとAndroidのsafariのブラウザで開いて、見た目を調整した。


■なるべく画面遷移しない
jquery mobileやjQTouchを使うとネイティブアプリ風に外見で、横にスライドして表示を切り替えるwebアプリを作れるけど
面倒だったので使わなかった。visibleをon/offしてtweetボタンを表示したりしなかったり等している。
なのでviewのテンプレートは1つしかない。


■twitterログイン
生成したglitchテキストはoAuthでログインしてtweetされるが、ユーザ情報管理にDBは使っていない。
cookieにtwitterのoauth tokenとsecretを保存している。
最初の画面でログイン済みかどうかは、haml template上でtokenとsecretがあるかどうかだけで表示を分岐しているので、実際glitchtweet.com側では誰がどんなtweetをしているかは把握していない(しようと思えばできる)


■cookieでsession
sinatraでcookieベースのsessionを使うようにしている。
Rack::Session::Cookieを使う – 橋本詳解に書いた。
glitchtweet.comでは2週間cookieで保存するようにしている。
use Rack::Sessioin::Cookieしたらsinatraのsession関数がcookieを使うようになってくれた。
(これで大丈夫ですよね?)


■ホーム画面アイコン

<link href='http://glitchtweet.com/img/icon.png' rel='apple-touch-icon' />
これをheadに書いておくと、iPhoneのホーム画面にブックマーク保存するとアイコンが付く。
Android2.3でもホーム画面にブックマークショートカットを作ったらアイコンが出るようになった。しかも角丸化される。apple-touch-iconって名前なのに処理してくれるAndroidえらい。


■テスト環境にwebrick使うようにした
開発中にSinatra1.2.1が出たのでupdateしたら、thinを使うとhttpリクエスト送った瞬間に問答無用で強制終了するようになった。
webrick使うようにした。
Sinatra1.2.1とthin1.2.10を同時に使うと死ぬ – 橋本詳解


■viewport
@hitoriblogさんに教えてもらった。
適当なhtml/cssを書いていてもiPhoneではそれなりに正しいサイズで表示されるんだけど、Androidではテキストエリアをクリックした瞬間にものすごいズームをされてしまう。
これはviewportをheadに指定しておくとなんとかなる。Androidはデバイスがたくさんあるので、画面サイズが微妙に違うのでwebサービス作ってる人は大変そう。
<meta content='width=device-width, user-scalable=no' name='viewport' /> 
このへんも参考になる。


■shakeイベント
ネイティブアプリではshakeのイベント、つまりiPhone本体を振ったイベントを取得して、undoに使われている。
全く、操作と実行内容の関連付けが最も意味不明な機能だと思う。

でもiOS4.2からsafariでも加速度センサーが使えるようになったので、せっかくだからshakeイベントを取れるjsライブラリを作った。
shokai/js-iphone-shake-event – GitHub

glitchtweet.comでも使っている。tweet後に、もう一度同じソースからglitchしたい時にshakeすると消えた文字が復元される。

これについてはあとで書く。


■iphone-js-console
shokai/iphone-js-console – GitHub
iphone-shake-event.jsを作っている時に、iPhone実機でのデバッグをする為に作った。
パソコンのterminal上でjsを書くと、iPhone上で実行されたり、パソコン上でiPhoneのブラウザ上のjsの値を読み出したりできて便利。

これについてもあとで書く。

0

glitchtext.js作った

作った。https://github.com/shokai/js-glitchtext

ここで試せる → glitchtext.js sample
jsのサイズが圧縮しても385Kbもあるんだけど、9割以上がインターネットをクロールして取得した装飾用顔文字になってしまった。必要ない人は後述するビルド方法に基づいてsrc/plugins/face.jsを除外してビルドするともっと小さくなるはず。


元々は、去年の夏にRubyで作ったglitchtweetというtwitterクライアントがあって、それの機能をJavaScriptに移植したもの。

glitchtweetについてはずっと使ってるんだけどblogに書くの忘れてた。ふつうの発言を入れると適当に装飾・変形させてtweetするもので、20個ぐらいのプラグインがランダムに作用して壊れた文字列を作るというもの。
後輩のぽわわがTogetter – 「奇跡のglitchtweet開幕だ」にまとめてた。


■glitchtext.jsの使い方
まずTinySegmenterとglitchtext.jsを読み込んで

<script src="tiny_segmenter-0.1.js" type="text/javascript" />
<script src="glitchtext.js" type="text/javascript" />


javascriptでGlitchTextオブジェクトを作って、randomすると適当にランダムに変形される。
2〜3回randomを繰り返すとより強力に変形する。
var g = new GlitchText();
var source = '今日はかずすけ楽しかったねー また遊ぼうねー 今度はお弁当持って行こう';
var result = g.random(source);
もしくはglitch_を先頭に持った関数で任意の変形ができる。関数の一覧はmethods()関数で取得できる。

TinySegmenterが無くても動くが、いくつかのプラグインが有効化されない。



工夫したことについて書いておく

■プラグイン
今のところ、22のプラグインが入っている。
こんな感じ
glitch_addspace : こ こ に ソ ー ス 置 い た   h t t p s : / / g i t h u b . c o m / s h o k a i / j s - g l i t c h t e x t
glitch_atai : \ここにソース置いた https://github.com/shokai/js-glitchtext/
glitch_cmabridge : ここにーソスい置た htpts://github.com/shokai/js-giltchtext
glitch_double : ここにソース置いた https://github.com/shokai/js-glitchtext ここにソース置いた https://github.com/shokai/js-glitchtext
glitch_doubleChar : ここここににソソーースス置置いいたた hhttttppss::////ggiitthhuubb..ccoomm//sshhookkaaii//jjss--gglliittcchhtteexxtt
glitch_dullness : ごごにゾーズ置いだ https://github.com/shokai/js-glitchtext
glitch_face : ここにソース置いた https://github.com/shokai/js-glitchtext ォヵェリ☆彡..._〆(`・∀・´o)
glitch_fill140 : ここにソース置いた https://github.com/shokai/js-glitchtextここにソース置いた https://github.com/shokai/js-glitchtextここにソース置いた https://github.com/shokai/js-glitchtext
glitch_hiragana : ここにそーす置いた https://github.com/shokai/js-glitchtext
glitch_hirakata : ココニそーす置イタ https://github.com/shokai/js-glitchtext
glitch_ignoreSearch : こ/こ/に/ソ/ー/ス/置/い/た/ /h/t/t/p/s/://///g/i/t/h/u/b/./c/o/m///s/h/o/k/a/i///j/s/-/g/l/i/t/c/h/t/e/x/t
glitch_insertWave : こ〜こに〜ソー〜ス置〜いた h〜ttp〜s://g〜ithu〜b〜.com/shok〜a〜i/j〜s-g〜litch〜text
glitch_kaibu : ここにソース置いた https://github.com/shokai/js-glitchtextxethctilg-sj/iakohs/moc.buhtig//:sptth たい置スーソにここ
glitch_katakana : ココニソース置イタ https://github.com/shokai/js-glitchtext
glitch_kirakira : .'。:。'★。.:゜。*:*゜*'ここにソース置いた https://github.com/shokai/js-glitchtext'*゜*:*。゜:.。★'。:。'.
glitch_linePrefix : ────ここにソース置いた https://github.com/shokai/js-glitchtext
glitch_reverse : txethctilg-sj/iakohs/moc.buhtig//:sptth たい置スーソにここ
glitch_sekine : ここにショーシュ置いた https://github.com/shokai/js-glitchtext
glitch_speak : (ここにソース置いた https://github.com/shokai/js-glitchtext)
glitch_updown : ここにソー↓ス↑置いた ↓h↑t↓tps://g↑i↓t↑hub.com/shoka↓i↑/↓j↑s-gli↓tch↑t↓e↑xt
glitch_vertical_reverse : ここにソース置いた ɥʇʇds://bıʇɥnb.ɔoɯ/sɥoʞɐı/ظs-b1ıʇɔɥʇǝxʇ



■Rakeでビルドする
RakeはRubyで書けるMakefile。

今回は文字列を操作する関数をたくさん作りたかったが、1つのjsファイルに追記していくとメンテナンスがしづらい。
動作テストまでは関数群はpluginsディレクトリに置いてあって、リリース時にRakeで一つのglitchtext.jsに合体させる様にした。

今までJSのオブジェクト指向的な機能はあまり使ったことがなかった。
忘れないうちにメモしておく
  • 先にクラスを定義しておけば後からprototypeで関数を追加できる
  • for(i in object)でオブジェクトのメソッドの名前のリストを取れる
  • メソッド名がわかれば、object[メソッド名]()で関数を実行できる
  • メソッドとプロパティの区別があまり無いので高階関数が簡単に作れる
  • if(typeof hoge == ‘function’)でプロパティかメソッドか判別できる
このへん今回重要だった。



■TinySegmenterでcmabridge
cmabridgeというプラグインにTinySegmenterを使った。TinySegmenterはJavaScriptで実装されたわかち書きエンジンで、つまり日本語を単語レベルに分割してくれる。

cmabridgeはcambridge大学で発見されたという人間の認知の穴を突いた手法。単語中の隣接する1文字が入れ替わっていてもなぜか文章として読めてしまうというもの。
こんなの↓
日今はかずけすし楽かったーね たま遊ぼうーね 度今はお弁持当ってこ行う
tinysegmenterでは品詞は分類できないが、この用途には十分だった。
あとArray.prototype.mapにRubyのArray.mapを追加したので、メソッドチェーンでどんどん文字列操作できて書いてて楽しい。



■デバッグ
chromeのjsエンジンのv8を使った。Macでは
brew install v8
でインストールできる。v8はchromeの開発パネルみたいにどのファイルの何行目でエラーが起こったかを確かめられるので良い。

glitchtext.jsは、文字列を入れたら文字列を返す関数をたくさん作る必要があったので、ブラウザでデバッグするよりもterminalからたくさん文字を入れてまとめてテストできるようにした方が効率的だったので使ってみた。
rake test
すると、プラグインディレクトリとテストコードのディレクトリを総ざらいしてこんな風に実行する
v8 /Users/sho/src/js/glitchtext/libs/tiny_segmenter-0.1.js /Users/sho/src/js/glitchtext/src/glitchtext.js /Users/sho/src/js/glitchtext/src/plugins/addspace.js /Users/sho/src/js/glitchtext/src/plugins/atai.js /Users/sho/src/js/glitchtext/src/plugins/cmabridge.js /Users/sho/src/js/glitchtext/src/plugins/double.js /Users/sho/src/js/glitchtext/src/plugins/double_char.js /Users/sho/src/js/glitchtext/src/plugins/dullness.js /Users/sho/src/js/glitchtext/src/plugins/face.js /Users/sho/src/js/glitchtext/src/plugins/fill140.js /Users/sho/src/js/glitchtext/src/plugins/hiragana.js /Users/sho/src/js/glitchtext/src/plugins/hirakata.js /Users/sho/src/js/glitchtext/src/plugins/ignore_search.js /Users/sho/src/js/glitchtext/src/plugins/insert_wave.js /Users/sho/src/js/glitchtext/src/plugins/kaibun.js /Users/sho/src/js/glitchtext/src/plugins/katakana.js /Users/sho/src/js/glitchtext/src/plugins/kirakira.js /Users/sho/src/js/glitchtext/src/plugins/line_prefix.js /Users/sho/src/js/glitchtext/src/plugins/reverse.js /Users/sho/src/js/glitchtext/src/plugins/sekine.js /Users/sho/src/js/glitchtext/src/plugins/speak.js /Users/sho/src/js/glitchtext/src/plugins/updown.js /Users/sho/src/js/glitchtext/src/plugins/util.js /Users/sho/src/js/glitchtext/src/plugins/vertical_reverse.js /Users/sho/src/js/glitchtext/test/test.js

v8は、引数に複数のjsファイルを指定できて、先に指定した物から読み込まれる。先にGlitchTextクラスの定義を読み込んで、続けてプラグインを読み込んでGlitchTextクラスをprototype拡張し、最後にテストコードを実行している。

こうすると、どのプラグインの何行目でエラーが起きたかがちゃんとv8が教えてくれる。複数のプラグインを試行錯誤しながら作っている場合でも、できた物からgitにcommitできるし、別のパソコンで他のプラグインが欠けている状態からでもテストを実行できる。


■ビルド
v8に渡したのと同じく、glitchtext本体→プラグインの順にjsファイルを連結させれば単体の.jsファイルにできる。
jsminを使うとjsファイルを圧縮できた。
Macではhomebrewでインストールできる。
brew install jsmin
jsmin < input.js > output.js

0

JRubyでglitch iconを作る(2)

プラグイン機構を採用し、ランダムにいろいろ作れるようにしてみた。
普通はjpegとかのバイナリを直接いじるみたいだけどよく分からないのでJRubyでjavax.imageioを使ってやっている。

ランダムに96個作ってみて8×12に敷き詰めてみたのがこれ。
で、定期的にランダムに作ってtwitterのアイコンとしてアップロードしてる
montage96
敷き詰めるのはImageMagickと一緒にインストールされるmontageコマンドでできる montageコマンド – 橋本詳解

ソースコードは全てgithubに置いた


■仕組み
単機能モジュールをランダムに連結する事である程度ランダムな画像を生成できる。
javax.imageio.BufferedImageのインスタンスを受け取り、少し加工して返すというJRubyのmoduleをプラグインとし、それらをGlitchというclassがランダムに呼び出す。


今のところプラグインは23種類ある。単体だとそれほど派手にはならない。
glitchicon montage



■プラグイン
http://github.com/shokai/glitchicon/tree/master/plugins/にある。
pluginが動くルールはこれ。

  • pluginsディレクトリの中に置いておく
  • ファイル名末尾が.rbである
  • JRubyのmoduleである
  • ファイル名の先頭1文字を大文字にしたmodule名である
  • BufferedImageを受け取って、同じサイズのBufferedImageを返す glitch(BufferedImage) というstatic methodを持つ
  • 受け取ったBufferedImageに対しては破壊的に処理しても、そうでなくてもいい

プラグインはBufferedImageの処理に集中できるように、他の事はglitch.rbがやる。


どのプラグインも簡単にできている。30行ぐらい。

drumroll_verticalプラグインの場合(画像では一番左、下から2番目)
#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'


module Drumroll_vertical

  def Drumroll_vertical.glitch(img)
    img_result = BufferedImage.new(img.width, img.height, img.type)
    roll = 0
    for x in 0...img.width do
      roll = rand(img.height) if rand > 0.95
      roll = 0 if rand > 0.95
      for y in 0...img.height do
        pix = img.get_rgb(x, y)
        y2 = y+roll
        y2 -= img.height if y2 > img.height-1
        img_result.set_rgb(x, y2, pix)
      end
    end
    return img_result
  end

end
ランダムに縦に区切って、ランダムにスロットのドラムみたいにずらす。


色を反転させるcolor_reverseプラグイン
#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'


module Color_reverse

  def Color_reverse.glitch(img)
    for y in 0...img.height do
      for x in 0...img.width do
        pix = img.get_rgb(x, y)
        r = pix >> 16 & 0xFF
        g = pix >> 8 & 0xFF
        b = pix & 0xFF
        r = 256-r
        g = 256-g
        b = 256-b
        pix = ((r << 16)&0xFF0000 | (g << 8)&0xFF00 | b)
        img.set_rgb(x,y, pix)
      end
    end
    return img
  end

end


量子化した後に輪郭抽出するquantize_contour
#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'


module Quantize_contour

  def Quantize_contour.glitch(img)

    for y in 0...img.height do
      for x in 0...img.width do
        pix = img.get_rgb(x, y)
        r = pix >> 16 & 0xFF
        g = pix >> 8 & 0xFF
        b = pix & 0xFF
        gray = (r+g+b)/3
        quant = gray & 0xC0
        pix = ((quant << 16)&0xFF0000 | (quant << 8)&0xFF00 | quant)
        img.set_rgb(x, y, pix)
      end
    end

    img_result = BufferedImage.new(img.width, img.height, img.type)
    for y in 1...img.height-1 do
      for x in 1...img.width-1 do
        pix = img.get_rgb(x, y)
        around = (img.get_rgb(x-1,y)+img.get_rgb(x+1,y)+img.get_rgb(x,y-1)+img.get_rgb(x,y+1))/4
        if around < pix
          pix = 0
        else
          pix = 0xFFFFFF
        end
        img_result.set_rgb(x, y, pix)
      end
    end
    return img_result
  end

end


そんなかんじ。画像をいじるけど、元の画像を知っている人なら元画像を思い浮かべられる程度にglitchしたい。

0

JRubyでglitch iconを作る

こういうのをtwitter iconにしたかった


Twitter のアイコンの目を光らせるライフハック – 地獄の猫日記を参考にアニメgifを作った。末尾も3C2Cにした。

でもちょうど今日からtwitterがGIFアニメアイコンのチェックを厳しくしたらしく、最初の1フレームだけの静止アイコンになってしまう。
既にアップされている他の人のアイコンを見ると、3C00002Cとかになってるのもあったけど色々ためしたけどやっぱりアップロードすると静止画像になる。


画像を生成するスクリプト
jrubyでjavax.imageioを使うと簡単。
最後のあたりのコメントアウトしているconvertかffmpegどちらでも、アニメーションgifを作れる。
glitchicon.rb

#!/usr/bin/env jruby
require 'java'
import 'java.lang.System'
import 'javax.imageio.ImageIO'
import 'java.awt.image.BufferedImage'

if ARGV.size < 2
  STDERR.puts 'require : input file and output dir'
  STDERR.puts 'jruby glitchicon.rb /path/to/img.jpg /path/to/out/'
  exit 1
end

img_in = ImageIO.read(java.io.File.new(ARGV.shift))
out_dir = ARGV.shift
out_dir += '/' unless out_dir =~ /\/$/

puts "#{img_in.width}x#{img_in.height}"

for i in 1..10
  img = BufferedImage.new(img_in.width, img_in.height, img_in.type);
  img.graphics.drawImage(img_in, 0, 0, nil)
  shifts = [0,0,0]
  for y in 0...img.height do
    if rand > 0.85
      shifts = shifts.map{|j|
        j = rand(255)-128
      }
    end
    for x in 0...img.width do
      pix = img.get_rgb(x, y)
      r = pix >> 16 & 0xFF
      g = pix >> 8 & 0xFF
      b = pix & 0xFF
      r += shifts[0]
      g += shifts[1]
      b += shifts[2]
      r = 255 if r > 255
      g = 255 if g > 255
      b = 255 if b > 255
      r = 0 if r < 0
      g = 0 if g < 0
      b = 0 if b < 0
      pix = ((r << 16)&0xFF0000 | (g << 8)&0xFF00 | b)
      "#{r},#{g},#{b}"
      img.set_rgb(x,y, pix)
    end
  end
  out_name = "#{out_dir}#{i}.bmp"
  ImageIO.write(img, 'bmp', java.io.File.new(out_name))
  puts out_name
end

puts `convert #{out_dir}*.bmp #{out_dir}out.gif`
#puts `ffmpeg -y -i #{out_dir}%d.bmp -s 100x100 -pix_fmt rgb24 -loop_output 0 -sameq #{out_dir}out.gif`

open("#{out_dir}out.gif"){|gif|
  bytes = gif.read.unpack('H*')
  bytes = [bytes.first.gsub!(/3b$/,'3c2c')]
  open("#{out_dir}glitchicon.gif",'w+'){|out|
    out.write(bytes.pack('H*'))
  }
}