Archive for the ‘rails’ Category

[Rails 3]Cropping images use paperclip and jcrop

在railscast看到用paperclip跟Jcrop(一个jquery的一个插件)来实现图片裁剪功能,看起来很简单 就顺便用在现在做的项目里面了。 结果杯具的花了一天多的时间来弄。

在上一篇

[/rails]

paperclip works on windows 中记录了怎么给一个model加avatar

100×100# VS 100×100>

在model中我们加入了一下的代码

has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }

注意这里的在维度后面加了一个大于号,当时不是很理解。 google也没有找到答案,今天又仔细研究了一下, 100*100>的话呢,>是指按比例缩放的情况下最长边等于100. #就不是按比例缩放了, 如果是 100×100# 最后的图就是拉伸到100×100,不论之前的比例。

首先 在model里面把上面这句改成下面一句

has_attached_file :avatar, :style {:medium=>"150x150#", :large=>"500x500>"}

我们在页面上显示用户的头像是150*150的。
因为用户上传的图片的大小必然是各种各样的,所以我们这里先把用户给的图片变化成我们已知的大小, 这里的large style接下来就会用来被裁剪的图片。

Action

我们要实现的是, 当用户upload一个图片时转到一个叫crop的页面去进行裁剪。
所以需要修改controller里面的action,下面这段代码直接从railscast里面偷过来的。

def create
    @user = User.new(params[:user])
    if @user.save
      if params[:user][:avatar].blank?
        flash[:notice] = "Successfully created user."
        redirect_to @user
      else
        render :action => "crop"
      end
    else
      render :action => 'new'
    end
  end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      if params[:user][:avatar].blank?
        flash[:notice] = "Successfully updated user."
        redirect_to @user
      else
        render :action => "crop"
      end
    else
      render :action => 'edit'
    end
  end

Jcrop选区

然后我们要新加一个crop的view,就是在upload一个图片后,跳转到用来图片剪裁的页面。 在这个页面中呢, 我们就要用到Jcrop这个plugin了,下载地址
把JavaScript,stylesheet一股脑都丢进这个文件里面, 当然为了保证Javascript在head里面呢,我们可以用 yield跟content_for 来做,请同学们自行动手尝试。(也可以到这里围观Ryan是怎么做的)

加入下列代码 依然是偷过来的,我自己的使用haml写的

    <% title "Crop Avatar" %>
    <% content_for (:head) do %>
    <%= stylesheet_link_tag "jquery.Jcrop" %>
    <%= javascript_include_tag "jquery.Jcrop.min" %>
    <script type="text/javascript">
     $(function() {
        $('#cropbox').Jcrop();
      });
    </script>
   <% end %>

  <%= image_tag @user.avatar.url(:large), :id => "cropbox" %>

通知paperclip选区范围

显然这只实现了一个在图片中选中一个区域的步骤,我们需要告诉paperclip我们选择了哪个区域。
回到model里 加入下面的代码

attr_accessor:crop_x, :crop_y, :crop_w,:crop_h

什么是attr_accessor, 字面上来看呢 就是attribute accessors,如果你熟悉java或者c++, whatever else. 这个就相当于getter跟setter的合体。
这里的四个值呢 分别是 crop_x 左上角的x坐标, crop_y 左上角的y坐标, crop_w 所选框的width,同理crop_h 所选框的height.

怎么把这些值传递给paperclip让它知道呢,首先想到的是要弄一个表单,为了不让这些值显示给亲爱的用户,所以我们使用hidden input.

继续偷代码

 <% form_for @user do |form| %>
    <% for attribute in [:crop_x, :crop_y, :crop_w, :crop_h] %>
      <%= form.text_field attribute, :id => attribute %>
     <% end %>
     <p><%= form.submit "Crop" %></p>
  <% end %>

这时候我们在Jcrop的调用中也要加一些配置了

$(function() {
  $('#cropbox').Jcrop({
    onChange: update_crop,
    onSelect: update_crop,
    setSelect: [0, 0, 500, 500],
    aspectRatio: 1
  });
});

function update_crop(coords) {
   $('#crop_x').val(coords.x);
   $('#crop_y').val(coords.y);
   $('#crop_w').val(coords.w);
   $('#crop_h').val(coords.h);
   }

这里update_crop的方法呢就是用来更新选区。通过表单提交给model,这时候呢我们要通知model 我们需要来进行crop
在model中加入以下偷来的代码

 after_update :reprocess_avatar, :if => :cropping?

   def cropping?
     !crop_x.blank? && !crop_y.blank? && !crop_w.blank? && !crop_h.blank?
   end

   private
   def reprocess_avatar
     avatar.reprocess!
   end

上面这段代码是说什么呢, 通过crop_x, crop_y, crop_w,crop_h来判定是否进行croping,如果进行cropping的话就通知paperclip再reprocess一下。
然后就是要告诉paperclip该如何处理?

Reprocess

安装railscast上讲的,我们需要有在lib文件夹下,新建一个paperclip_processors的文件夹, 然后new以下新的文件cropper.rb 内容如下

module Paperclip
  class Cropper < Thumbnail
    def transformation_command
      if crop_command
        crop_command + super.sub(/ -crop \S+/, '')
      else
        super
      end
    end

    def crop_command
      target = @attachment.instance
      if target.cropping?
        " -crop '#{target.crop_w}x#{target.crop_h}+#{target.crop_x}+#{target.crop_y}'"
      end
    end
  end
end

这时候测试一下, 错误如下
uninitialized constant Paperclip::Cropper
检查一下,好像是因为lib文件夹下面的文件并没有自动载入, 一google才知道 rails3 取消了对lib文件的autoload. 既然不能自动我们就手动呗。到model文件里面加入下面两句

require 'lib/paperclip_processors/cropper.rb'
include Paperclip

再次运行,新的错误又来了,
undefined method `sub’ for ["-resize", "x170", "-crop", "170x170+9+0", "+repage"]:Array

看了railscast下面的comment,有人也有同样的问题,原因是新版本的paperclip改变了他处理命令的方式,这里有人给出了解决方式
在cropper文件里面分别替换成下面两行

crop_command + super.join(' ').sub(/ -crop \S+/, '').split(' ')
["-crop", "#{target.crop_w}x#{target.crop_h}+#{target.crop_x}+#{target.crop_y}"]

再运行下,理论上应该可以crop了。

校准

但是视乎裁剪的结果并不不准确,原因是我们用large style的图片来做选区,但其实是在original的图上裁剪的,所以我们需要在中间做一个转换。
我们需要知道original跟large的width跟height。在model里面再加入下面的代码

def avatar_geometry(style= :o riginal)
    @geometry ||= {}
    @geometry[style] ||= Paperclip::Geometry.from_file(avatar.path(style))
 end

把update_crop文件更新为下面这样

function update_crop(coords) {
 var ratio = <%= @user.avatar_geometry(:original).width %> / <%= @user.avatar_geometry(:large).width %>;
  $("#crop_x").val(Math.round(coords.x * ratio));
  $("#crop_y").val(Math.round(coords.y * ratio));
  $("#crop_w").val(Math.round(coords.w * ratio));
  $("#crop_h").val(Math.round(coords.h * ratio));
}
</script>

现在再一运行 一切正常啦。

add a preview

但是这样用户体验不够好啊,那再加入一个预览的吧
view文件里面

<h4>Preview</h4>
  <div style="width: 100px; height: 100px; overflow: hidden;">
  <%= image_tag @user.avatar.url(:large), :id => "preview" %>
</div>

javascrip里面继续更新一下update_crop文件

function update_crop(coords) {
	var rx = 100/coords.w;
	var ry = 100/coords.h;
	$('#preview').css({
		width: Math.round(rx * <%= @user.avatar_geometry(:large).width %>) + 'px',
		height: Math.round(ry * <%= @user.avatar_geometry(:large).height %>) + 'px',
		marginLeft: '-' + Math.round(rx * coords.x) + 'px',
		marginTop: '-' + Math.round(ry * coords.y) + 'px'
	});
  var ratio = <%= @user.avatar_geometry(:original).width %> / <%= @user.avatar_geometry(:large).width %>;
  $("#crop_x").val(Math.round(coords.x * ratio));
  $("#crop_y").val(Math.round(coords.y * ratio));
  $("#crop_w").val(Math.round(coords.w * ratio));
  $("#crop_h").val(Math.round(coords.h * ratio));
}

一切搞定。

悲催的我在写cropping?的时候, 少写了一个感叹号给crop_h.blank? debug了好久才找到。希望以后不要再犯这样的错误

n2149

[rails] paperclip works on windows

需要实现一个avatar的功能

谷哥说paperclip

先在railscast上看了下Ryan的介绍, 对于功能基本满意。

1. 安装

Rails3用bundle来管理gem的dependencies。

在Gemfile里面加上如下代码

gem 'paperclip' 

然后命令行 bundle check, paperclip需要安装 再bundle install, 再check一下 现在所有的dependencies 都满足了

2. migration

这里假设我是要给user这个model加一个avatar
按照github上讲的http://github.com/thoughtbot/paperclip, 在model里面加上 下面这句

 has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
rails g paperclip user avatar

这样生成了一个migration file,下面这段是自动生成滴

class AddAttachmentAvatarToUser< ActiveRecord::Migration
  def self.up
    add_column :users, :avatar_file_name, :string
    add_column :users, :avatar_content_type, :string
    add_column :users, :avatar_file_size, :integer
    add_column :users, :avatar_updated_at, :datetime
  end

  def self.down
    remove_column :users, :avatar_file_name
    remove_column :users, :avatar_content_type
    remove_column :users, :avatar_file_size
    remove_column :users, :avatar_updated_at
  end
end

好最后别忘了 rake db:migrate (我经常忘记 囧 ), 这样数据库就搞定啦。

3. controller

Controller 里面不需要做什么改动,因为这里的avatar跟其他的email之类的属性是一样的 所以update的时候都会update的, 就来看看view吧

4. views

首先要有一个form吧, 嗯在我们的form的partial里面加一句, 因为我这里用的是haml所以就没有”"

= form.file_field :avatar

还没有完 还要给form加一个html的option, :multipart => true。这样呢 就给form加了一个enctype=”multipart/form-data“, 完整的haml代码如下

= form_for(@user,:html=>{:multipart=>true}) do |f|
  .field
      =f.label :avatar
      =f.file_field :avatar
  .field
      =f.label :email
      = f.text_field :email
  .field
      =f.label :name
      = f.text_field :name
  .actions
    = f.submit

嗯 form有, 上传完了以后要show出来吧, 然后在show的里面加一句

=image_tag police.avatar.url(:medium)

5.ImageMagick

嗯 运行一下,理论上应该是不行的, 因为这里有个重要的东西我们没有安装。 ImageMagick. 这个才是我们今天讨论的重点, 在linux跟mac下这个不是大问题。但是windows总是让人头痛的。
下载地址:http://www.imagemagick.org/script/binary-releases.php#windows

选好适合自己的下载, 注意安装的时候加入环境变量,为了安全起见可以check一下是否成功加入了

这个时候你运行呢 很可能会得到一个 ”Error: [...] is not recognized by the ‘identify’ command“ 的错误。
为什么环境变量加入了还是不能识别呢, 在cmd里面运行下面的代码
1. 把model里面的styles去掉 测试一下, 如果成功上传 说明确实是paperclip跟ImageMagick之间的沟通出了问题。否则,请检查以上步骤是否正确完成了
2. 测试完以后,加上styles, 为什么要加上? 因为我们需要嘛,如果不需要这个不同大小的话这里就可以算成功了 请忽略后面的。 现在打开你的log,看看有[paperclip]的行是怎么说。copy那句话,我的是如下的

identify '-format' '%wx%h' 'C:/Users/danyi/AppData/Local/Temp/str
eam,4212,0.jpg[0]'  

在cmd中输入,报错
这个时候呢 根据报错发现需要”代替‘.输入下面的命令,再试一下

identify "-format" "%wx%h" "C:/Users/danyi/AppData/Local/Temp/str
eam,4212,0.jpg[0]"

视乎运行成功了, 嗯 问题也似乎找到了。如何解决呢,我在config/initializers里面加入了一个paperclip_patch.rb文件, 然后加入一下代码,overwirte原来的quote_command_options方法。

if RUBY_PLATFORM == 'i386-mingw32'
  module Paperclip
    def self.quote_command_options(*options)
      options.map do |option|
        option.split("\"").map{|m| "\"#{m}\"" }.join("\\\"")
      end
    end
  end
end

再测试。。搞定。。

6 最后

不在于这个问题是怎么解决的,而在于这个过程怎么去发现问题分析问题最后才是解决问题。

明天做image的 crop..

n723
Return top