Rubyで画像生成メモ

数百枚のVGA程度の解像度の画像にそれぞれ数十のアルファチャンネルあり画像をcompositeして書き出す処理なら、RMagickよりjavax.imageioをJRubyから使うのがいいんじゃないの

  • javax.imageio、JAIともにJavaで書いてもJRubyから使っても速度差は無かった(とりあえず画像の読み込み、フォーマット変換、書き出し、width heightなどのパラメータ取得は確認)
  • Java1.5→1.6にするとjavax.imageioの画像読み込み/書き出し速度1.3〜1.5倍になる
  • Java1.6じゃないとJAIで画像を書き出せない
  • Java1.6じゃないとimageioでpng形式で画像を書き出せない(getTypeがエラーになる)
  • javax.imageioとJAIでは縦横1000ピクセルなどの大きい画像の時は読み書き速度はJAIの方が速いが、320x240程度x数百枚なら変わらない
    • でもImageMagick(RMagick)の方がさらに2〜3倍速い


アルファ値ありの画像のcompositeはimageioめちゃくちゃ速い。ImageMagick遅い。

  • 320x240、250枚の画像に50個の40x40の画像をランダムにcompositeした
    • ImageMagick(Ruby1.8.7+RMagick2.13.1+ImageMagick6.5.2)→25秒前後
    • imageio(JRuby1.5.1+Java1.6+javax.imageio)→6〜7秒
  • 250枚に10個ずつに減らした
    • ImageMagick→7〜8秒
    • imageio→4秒
  • 大量にcompositeするならimageioが速い
  • JAIでcompositeの仕方がよくわからない


JRuby+JAIは使いにくい

  • ParameterBlockにjava.lang.Float値を入れる必要があるが、RubyでNumber.to_fするとDoubleになる。明示的にjava.lang.Float.parseFloat("0.3");とかして直に値を渡してもJRubyがjava.lang.Doubleとして渡してしまう。
    • JavaでParameterBlockを返すコード書いて先にjavacして.classを作り、JRubyスクリプトからimportすれば変換が起きないのでなんとかなる
    • つまりJAI使おうとすると全部Rubyで書くというわけにはいかなくなる

JRubyからJAI.create("filestore" すると

java.lang.IllegalArgumentException: operation "FileStore" requires 1 source object(s).

というエラーがでる。
これも、JAI.creaet "filestore"をするJavaプログラムを先に作ってclassにしてimportして使えば回避できる


こうやる
ImageCopyJai.java

import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
import com.sun.media.jai.codec.FileSeekableStream;

public class ImageCopyJai{

    public static void main(String args[]){
        if(args.length < 2){
            System.out.println("ImageCopyJai input.jpg output.bmp");
            System.exit(1);
        }

        if(ImageCopyJai.copy(args[0], args[1])){
            System.out.println(args[1]);
        }
        else System.out.println("error");
    }

    public static boolean copy(String name_in, String name_out) {
        try{
            RenderedOp img = JAI.create("fileload", name_in);
            System.out.println(name_in+" => "+img.getWidth()+", "+img.getHeight());
            JAI.create("filestore", img, name_out, "BMP", null);
        }
        catch(Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

JRubyから使う
image_copy.rb

#!/usr/bin/env jruby
# -*- coding: utf-8 -*-

require 'java'
import 'ImageCopyJai'
import 'javax.media.jai.Interpolation'
import 'javax.media.jai.JAI'
import 'javax.media.jai.RenderedOp'
import 'com.sun.media.jai.codec.FileSeekableStream'
import 'java.awt.image.renderable.ParameterBlock'

if ARGV.size < 2
  puts 'jruby iamge_copy.rb /dir/to/in/ /dir/to/out/'
  exit 1
end

indir = ARGV.shift
outdir = ARGV.shift
indir += '/' if indir =~ /.+[^\/]$/
outdir += '/' if outdir =~ /.+[^\/]$/
Dir.mkdir(outdir) if !File.exists?(outdir)

files = Dir.glob("#{indir}*")
puts files.size

start = Time.now
files.each{|name|
  begin
    out_name = name.scan(/\/([^\/]+)\..+$/).first
    ImageCopyJai.copy(name, "#{outdir}#{out_name}.bmp")
  rescue => e
    puts "#{name} => error"
    puts e
  end
}

puts Time.now-start