5分で作るソーシャル画像処理サービス(目標)(準備中)

下準備

$ sudo apt-get install imagemagick libmagick++9-dev -y
$ sudo apt-get install graphicsmagick libgraphicsmagick1 libgraphicsmagick1-dev libgraphicsmagick++1-dev libgraphicsmagick++1 -y
$ sudo gem install rmagick cairo

確認

rmagickが入っていること。
中途半端に入っていると、Image#extentがundefinedだったりするので、irb上で確認する。

$ irb -rubygems -r RMagick
irb(main):001:0> Magick::ImageList.new("ruby.jpg").first.methods.grep /ext/
=> ["extend", "texture_floodfill", "extract_info", "extract_info=", "texture_flood_fill", "texture_fill_to_border", "extent", "channel_extrema"]
irb(main):002:0> Magick::ImageList.new("ruby.jpg").first.extent
ArgumentError: wrong number of arguments (expected 2 to 4, got 0)
from (irb):2:in `extent'
from (irb):2
irb(main):003:0> exit

ついでにネットワークの接続確認。

手順(これを追うのに5分目標)

アプリ作成と動作確認

$ rails filtr
$ cd filtr
$ script/plugin install git://github.com/nanki/purl.git
$ script/generate scaffold Image data:binary
$ rake db:migrate
$ script/server

http://localhost:3000/へアクセスして、railsアプリの起動確認。

ソース修正

images/show/[id]にアクセス時、画像を表示する。

# /app/views/images/show.html.erb
<p>
  <b>Data:</b>

<img src="<%= url_for :action => :view, :id => @image.id %>" width="120px" height="90px" /></a></td>
</p>

images/indexにアクセス時、画像の一覧をプレビュー月で表示する。

# app/views/images/index.html.erb
<table>
  <tr>
    <th>Data</th>
  </tr>

<% for image in @images %>
  <tr>
    <td><a href="<%= url_for :action => :view, :id => image.id %>">
	<img src="<%= url_for :action => :view, :id => image.id %>" width="120px" height="90px" /></a></td>
    <td><%= link_to 'Show', image %></td>
    <td><%= link_to 'Edit', edit_image_path(image) %></td>
    <td><%= link_to 'Destroy', image, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

images/newのフォームから、画像をアップロードできるようにする。(file_field使用)

# app/views/images/new.html.erb
<% form_for(@image, :html => {:multipart => true }) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :data %><br />
    <%= file_field :upload, :data %>
  </p>
  <p>
    <%= f.submit "Upload", :disable_with => "wait" %>
  </p>
<% end %>
  • ImagesController#createで、images/newの画像アップロードフォームから受け取った画像のバイナリをDBに入れる。
  • ImagesController#viewでリクエストされた画像のバイナリをDBから取り出して送る。(view側では、ImagesController#viewのパスをimageタグのsrcに指定する)
# /app/controllers/images_controller.rb
class ImagesController < ApplicationController
  def view
    @image = Image.find(params[:id])
    send_data(@image.data, :disposition => 'inline')
  end

  # ...

  # POST /images
  # POST /images.xml
  def create
    @image = Image.new(:data => params[:upload][:data].read)
    respond_to do |format|
      if @image.save
        flash[:notice] = 'Image was successfully created.'
        format.html { redirect_to(@image) }
        format.xml  { render :xml => @image, :status => :created, :location => @image }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @image.errors, :status => :unprocessable_entity }
      end
    end
  end
  # ...
end
  • ここで画像がアップロードできること、アップロードした画像を一覧表示できること、個別に表示できることを確認する。

purl/[command]でpurlコマンドを実行。

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.connect 'purl/:commands',
    :controller => 'purl', :action => 'filter', :commands => /.*/
  # ...

PurlController#filterで、purlコマンド(params[:command]で渡される)を実行。

# app/controllers/purl_controller.rb
class PurlController < ApplicationController
  caches_page :filter

  def filter
      result = Purl::StackMachine.new(
        params[:commands], Purl::Purl.new {|p| }).run	
      logger.debug result.inspect
      send_data(result[-2], :type => result[-1], :disposition => 'inline')
  end
end

フィルターを登録できるようにする。

$ script/genrate scaffold ImageFilter title:string command:string && db:migrate

  • ここでフィルターを登録してみる
  • 登録した画像とフィルターを選択して、画像を加工できるようにする。

$ script/generate scaffold FilteredImage image_id:integer image_filter_id:integer && db:migrate

  • filtered_image/newにおいて、セレクトボックスで画像とフィルターを選択できるようにする。
# app/views/filtered_image/new.html.erb
<h1>New filtered_image</h1>

<% form_for(@filtered_image) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :image_id %><br />
    <%= f.select(:image_id, Image.find(:all).collect {|p| [p.id, p.id]}, {:prompt => "加工したい画像を選んでね!"}) %>
  </p>
  <p>
    <%= f.label :image_filter_id %><br />
    <%= f.select(:image_filter_id, ImageFilter.find(:all).collect {|p| [p.title, p.id]}, {:prompt => "フィルターを選んでね!"}) %>
  </p>
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>

<%= link_to 'Back', filtered_images_path %>
  • /showにおいて、加工画像を表示できるようにする。
  • まず、画面の裏側で画像をつくって@img_srcに画像のパスを入れる
# app/views/filtered_image/show.html.erb
class FilteredImagesController < ApplicationController
  # ...
  def show
    @filtered_image = FilteredImage.find(params[:id])
    image_id = @filtered_image.image_id
    command  = ImageFilter.find(@filtered_image.image_filter_id).command
    @img_src = url_for :controller => 'purl', :action => 'filter',
      :commands => "#{@filtered_image.image_id}:load:#{command}"
   # ...
  end
  # ...
end
  • 画面側で、@img_srcの画像を表示する。
# app/views/filtered_image/show.html.erb
<p>
  <b>Image:</b>
  <%=h @filtered_image.image_id %>
</p>

<p>
  <b>Image filter:</b>
  <%=h @filtered_image.image_filter_id %>
</p>

<p>
<img src="<%= @img_src %>"/a>
</p>

<%= link_to 'Edit', edit_filtered_image_path(@filtered_image) %> |
<%= link_to 'Back', filtered_images_path %>

サンプルフィルタ

影付の丸枠を追加する

load
5:round:5:5:5:5:extend:5:dropshadow:to.png

影付の丸枠(光沢つき)

load
geom:ctx:1:.2:1:rgb:fill:xtc:swap:composite:60:60:resize:60:60:ctx:30:-65:90:circle:1:1:1:.4:rgba:fill:30:0:40:90:grad.concentric:0:0:0:0:0:rgbao:0:0:0:.9:rgbo:pattern:fill:xtc:composite:7:round:5:5:5:5:extend:7:dropshadow:to.png

ユーザが何をできるか

  • 画像の共有
  • フィルターの共有
  • 加工した画像とその作り方(元画像とフィルター)の共有

thx!

http://www.kanasansoft.com/weblab/2008/10/url_rails_plugin.html
http://d.hatena.ne.jp/mostlyfine/20081012/1223837676
http://blog.s21g.com/articles/161
http://doruby.kbmj.com/katsuo_on_rails/20080225/rails_
ファイルのアップロード - SEのタマゴの殻
PurlからloadするためのImageはこの方法で管理。
Rmagickインストールメモ - cuspos diary
# graphicsmagickが入ってないと、Magick::Image.extentが定義されないのかな・・・?
rubyでundefined method "extent"とか言われて超はまった*1

セレクトボックスの要素をDBから取得する - One By One - norizo333's Blog -
VMWareのNATサービスを手軽に再起動する方法 - Hello, world! - s21g
イーモバ接続後、Ubuntuを起動したままスタンバイして再接続する際、VMWare NATの再起動が必要だった。

発展

Rails2.1でhas_manyな関連を設定してscaffold出来たとしたら... - ザリガニが見ていた...。
素材画像、フィルターのリコメンドに使えるかも。

  • 素材画像 has many 加工画像
  • フィルター has many 加工画像

の関係をつくって、
加工に使われた回数の多い素材画像やフィルターを表示するというイメージ。

*1:extentは、画像のサイズを大きくする?基本的なメソッドらしく、定義されていないというのはおかしい。