0

gemだけで画像をリサイズできるImageResizeを作った

ImageResizeはImageMagickなどの外部プログラムに依存せずに、単体で画像をリサイズできる。ただしJavaの実行環境が必要。


SFCの「革新的ネットサービスの構築」という授業のTAをやっていて、昨日と今日niftyに行って開発合宿をしていた。
そこでいろいろあって、ImageMagickなしで画像のサムネイルを作る方法が無いのか模索していたら、ImageResizeというgemができた。

■インストール

gem install ImageResize


■使う
require 'rubygems'
require 'ImageResize'

# input, output, width, height
Image.resize('big.jpg', 'small.jpg', 40, 40)
これで40×40ピクセルに縮小される。
縦横のアスペクト比が1:1ではない画像の場合、アスペクト比を保ったまま長辺の方にあわせて縮小する。


■ソースコード
githubに置いた。
http://github.com/shokai/ImageResize-ruby


■実装
本体はJava。
標準実行環境にJPEGやGIFやBITMAPなど様々な画像フォーマットの読み書き機能を含んでいる環境というと、Javaと.NETとopenFrameworksしか思いつかなかった。
その中でいちばん色々なマシンにインストールされていそうで、配布が容易な物を選んだらJavaになった。


Javaでの画像の扱いは.NET並に簡単。java.awtとjavax.imageioの下に色々充実しているのでそれらを使えばいい。
ImageResize.java

import javax.imageio.*;
import java.io.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.regex.*;

class ImageResize{

    public ImageResize(){
    }

    public static void main(String args[]){
if(args.length < 4){
    System.out.println("ImageResize in.jpg out.jpg 320 320");
    System.exit(1);
}
ImageResize app = new ImageResize();
if(app.resize(args[0], args[1], Integer.parseInt(args[2]), Integer.parseInt(args[3]))){
    System.out.println(args[1]);
}
    }

    public boolean resize(String fname_in, String fname_out, int max_width, int max_height){
System.out.println(fname_in);
BufferedImage img = null;
try {
    img = ImageIO.read(new File(fname_in));
}
catch (Exception e) {
    e.printStackTrace();
    img = null;
}

int width, height;
if(img.getWidth() < img.getHeight()){
    height = max_height;
    width = img.getWidth() * max_height / img.getHeight();
}
else{
    width = max_width;
    height = img.getHeight() * max_width / img.getWidth();
}
System.out.println(img.getWidth() + "x" +img.getHeight() + " => " + width + "x" + height);

BufferedImage img_resized = new BufferedImage(width, height, img.getType());
AffineTransformOp ato = new AffineTransformOp(AffineTransform.getScaleInstance((double)width / img.getWidth(), 
       (double)height / img.getHeight()), 
      null);
ato.filter(img, img_resized);


String format = Pattern.compile("^.+\\.(.+)$").matcher(fname_out).replaceAll("$1");
boolean result = false;
try {
    result = ImageIO.write(img_resized, format, new File(fname_out));
}
catch (Exception e) {
    e.printStackTrace();
    result = false;
}
return result;
    }
    
}


■参考


今回は使わなかったが、Java Advanced Imaging (JAI) APIというのが標準実行環境には含まれていないけどSunが作っていて、かなり充実しているらしい。
Javaランタイムも速くなってきているらしいしScalaで画像処理とかしてみたい。

0

エディタ保存したらブラウザリロードするsinatra/auto-reloadを作った

エディタを保存したらブラウザを自動リロードするsinatraプラグインを作った。
これでサブモニタにchromeとJavaScriptコンソール置いておくと幸せになれる。
ソースはgithubに。

Google ChromeもしくはFirefox+Greasemonkeyで動作する。エディタには依存しない。
sinatraアプリをローカルではなくサーバーで実行していてもリロードできる。



■インストール

sudo gem install sinatra-auto-reload
rubygems.orgに置いたので、gemコマンドでインストールできる



■使う
sinatraアプリ内で読み込む
require 'sinatra/auto-reloader' if development?
if development? すると、shotgunか -e development オプションを付けて起動した時だけ有効になる。production環境には影響を与えない。



■ブラウザ拡張をインストール
localhost:4567 でアプリを起動しているとして、
http://localhost:4567/sinatra_auto_reload.user.js にgreasemonkeyスクリプトが生成される。
Firefox 3.5+Greasemonkey、もしくはChrome 5.0のuserscriptとしてインストールできる。


スクリーンショット
sinatra-auto-reload Firefox userscript


ホスト名ごとに違うuserscriptとしてインストールされるので、調べ物して同じブラウザで他のページを開いても大丈夫
sinatra-auto-reload Chrome userscript



■監視しないファイルを定義
sinatraアプリ内でauto_reload_ignoresという関数を定義しておく
def auto_reload_ignores
  [/db.*/, /config.yaml/, /log.*/, /pid.*/]
end
配列内に正規表現で書いたファイルは、更新されてもリロードしない。logとかpidに反応しても困るので。


ところでプラグインに変数を渡すのは、こういう方法でいいんですかね?もっとまともなやり方がある気がするんだけど。



■しくみ
sinatra/baseに新しくgetでアクセスできるページを追加し、 http://hostname:port/sinatra_auto_reload にファイルの最終更新日時が出るようにした。
これをuserscriptからsetIntervalで監視して、更新があればリロードする。



■参考
JavaScript部分はこれを参考にした
最速インターフェース研究会 :: Firefoxでの開発を高速化する自動リロードスクリプト



■オススメ
sinatra-reloader か shotgun と一緒に使うのをおすすめします
sinatraはサーバーを再起動しないとrubyのコードの変更を読み込んでくれないんだけど、こいつらを使うと毎回読み込み直してくれるようになる。


■今後
よく考えたらSub URIで動かしている場合に動かなそうだ。あとで修正する。

0

ArgsParserをrubygemsに登録した

車輪の再発明臭がヤバイが、コマンドラインの引数のパーサを作ったのでrubygemsに登録した。はじめてgem登録した。

gemでインストールできる。

gem update --system
gem install ArgsParser


newgemコマンドでgemを作ってrubygems.orgに登録し、一応rspecでtestも書いた
gemはrubyforge.orgで公開するのが普通だったけど、2年ぐらい前からgithubでgemが作れる様になったので新しいプロジェクトはみんなgithubに行って、2009年秋?頃にgithubでgemがビルドできなくなったからかrubygems.orgでやってねという事になったらしい。


なのでリポジトリはいつも使っているmercurialにしてbitbucket.orgで公開する事にした。
shokai / argsparser-ruby / overview — bitbucket.org


既にgemにはいくつかCのgetopt風なARGVのparserがあるけど、なんとなくインタフェースが好きじゃないので自分が欲しい物を作った。

  1. –help -debug等のオプションの有無を判別する
  2. -x 320 -y 240 等のパラメータ(key value)を取得する
  3. -hと-helpのように省略名称が関連づけられていれば同じ物として扱う
  4. -helpと—helpのようなハイフンの数の違いを無視して、全て :help でアクセスできる
  5. ruby math.rb add -a 10 -b 25 の”add”のような、第一引数を取り出す
  6. パラメータ名にコメント文を付ける(-helpで起動した時の引数の説明文に使う)
という機能に絞ってある。



parseすると、第一引数(String)とパラメータ(Hash)が返ってくる。第一引数がない(いきなりパラメータが来た)場合は、第一引数はnilになる。
example.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'ArgsParser'

# 必要なパラメータ名を登録する
parser = ArgsParser.parser
parser.bind(:help, :h, "show help") # name, shortname, comment for help
parser.bind(:frame, :f, "frame image (required)")
parser.bind(:message, :m, "message (required)")
parser.bind(:size, :s, "size (required)")
parser.comment(:min, "minimum size") # add comment for help
parser.comment(:max, "maximum size")
parser.comment(:debug, "debug mode")


# parseして、引数が足りてるか、help表示指定が無いかチェック
first, params = parser.parse(ARGV)

if parser.has_option(:help) or !parser.has_params([:frame, :message, :size])
  puts parser.help
  puts 'e.g.  ruby example.rb -f frame.png -m "hello world" -s 320x240 -debug'
  exit 1
end

if first
  puts 'first arg : ' + first # 第一引数
end

if parser.has_param(:size)
  puts 'size : ' + params[:size] # -sでも-sizeでも :size でアクセスできる
end

# 全ての引数を表示
p params


実行してみる
ruby example.rb hoge -f frame.png -m "hello world" -s 320x240 -debug
結果
first arg : hoge
size : 320x240
{:message=>"hello world", :size=>"320x240", :debug=>true, :frame=>"frame.png"}

0

モバイルSuicaの履歴をtwitterに流したかった

xtunnelのためにスクレイピングの勉強をしていて、Mechanize+hpricotからMechanize+nokogiriの組み合わせに乗り換えようと色々と使ってみている中でできた物のひとつ。
昔しゃお先生がやっていたのを俺もやりたくて3ヶ月ぐらい前に作ったけど、mobilesuica.comのおサイフケータイ使用履歴は1日一度早朝に更新される仕様に変更されたらしくボツになった。


結局idやcssなどの手がかりが無くて手動で要素を取り出す事になり、nokogiriはHTMLタグを除去するのにしか使わなかった
MobileSuica.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'nokogiri'
require 'mechanize'
require 'kconv'

module MobileSuica
  def MobileSuica.get(user,pass)
    agent = WWW::Mechanize.new
    agent.user_agent_alias = 'Windows IE 7'
    page = agent.get('http://www.mobilesuica.com/iq/ir/SuicaDisp.aspx?returnId=SFRCMMEPC03')
    login_form = page.forms_with(:name => 'form1').first
    login_form.fields_with(:name => 'MailAddress').first.value = user
    login_form.fields_with(:name => 'Password').first.value = pass
    page = login_form.click_button
    
    return page.body.toutf8.split(/<tr>/).delete_if{|tr|
      !(tr =~ /&yen;/m)
    }.map{|tr|
      tr.gsub(/\n/,"").split(/\r/)[0..5].map{|line| # 月日,種別,利用場所,種別,利用場所,残額
        Nokogiri(line).inner_text.chomp.strip.gsub(/[\t ]/,"")
      }
    }
  end
end


mobilesuicaのユーザ名、パスワードで履歴を2次元配列として取り出せる。
require 'MobileSuica'
MobileSuica.get("user", "pass")
月日、種別、利用場所、種別、利用場所、残額の順になる
01/30

川崎

横浜
5,110
01/30

相鉄横浜
窓出
川崎
5,320



履歴のうち最新の駅名をtwitterに投稿する。-dつけて起動するとdaemonになる
tweet-mobilesuica.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'webrick'
require 'twitter'
require 'MobileSuica'

def start(conf)
  loop do
    begin
      first = MobileSuica.get(conf["suica_user"], conf["suica_pass"]).first
    rescue
      first = nil
    end
    if first != nil
      last = first if !last
      if first != last # 1回前に取得した履歴と比較
        puts first
        puts '-'*10
        if first[1] == '入' && first[3] == '出' # 降車履歴の時
          message = first[4].chomp.strip+"なう (suica)"
          if !(message =~ /#{conf["ngwords"]}/)
            if conf["nopost"] != true
              httpAuth = Twitter::HTTPAuth.new(conf["twitter_user"], conf["twitter_pass"])
              tw = Twitter::Base.new(httpAuth)
              tw.update(message) # twitter post
            end
            puts message
          end
        end
        last = first
      end
    end
    sleep 60*60*1.5 # 1時間半待つ
  end
end

conf = YAML::load open File.dirname(__FILE__)+'/config.yaml'
if ARGV[0] == '-d'
  WEBrick::Daemon.start {
    start(conf)
  }
else
  start(conf)
end


設定ファイル。自宅の駅名などはngwordsに入れておく
config.yaml
# config.yaml
# mobilesuica.com user/pass
suica_user : 'username@docomo.ne.jp'
suica_pass : '12345678'

# twitter user/pass
twitter_user : 'shokai'
twitter_pass : 'password'

# postしない駅名を正規表現で
ngwords : "(東京|横浜)"

# for debug
#nopost : "true"

0

wavファイルの音量を調整する

音量の小さいwavファイルのボリュームを上げる。上げすぎて音割れしないようにする。

前に作ったWavFile.rbを使ったら簡単にできた


16ビットwavは+-32768、8ビットwavは+-128の範囲の配列で波形が表現されている。
ソースのwavの波形を配列に取り出して、その中で最大の値を取りだし、全体を何倍すれば+-32768の間になるかの倍率を計算して全部かけ算すれば音量を調整できる。

maximizeVolume.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# wavの音量を最大に調節する
require File.dirname(__FILE__) + '/WavFile'

if ARGV.size < 2
  puts 'ruby maximizeVolume.rb input.wav output.wav'
  exit 1
end

in_file = ARGV.shift
out_file = ARGV.shift

format, data = WavFile::read open(in_file)

puts format.to_s

bit = 's*' if format.bitPerSample == 16 # int16_t
bit = 'c*' if format.bitPerSample == 8 # signed char
wavs = data.data.unpack(bit)

puts "このwav中の最大音量: #{wavs.max}"

volume_ratio = 32768/wavs.max.to_f if format.bitPerSample == 16
volume_ratio = 128/wavs.max.to_f if format.bitPerSample == 8
puts "補正倍率: #{volume_ratio}"

wavs_fixed = wavs.map{|w|
  (w*volume_ratio).to_i
}
puts "補正されたwav中の最大音量: #{wavs_fixed.max}"

data.data = wavs_fixed.pack(bit)

open(out_file, "w"){|out|
  WavFile::write(out, format, [data])
}


使う
ruby maximizeVolume.rb input.wav out.wav

約24倍されてout.wavに保存された
フォーマットID: 1
チャンネル数: 1
サンプリングレート: 44100 (Hz)
byte per sec: 88200
bit per sample: 16
ブロックサイズ: 2
このwav中の最大音量: 1335
補正倍率: 24.5453183520599
補正されたwav中の最大音量: 32768