您的位置:首页 > 编程语言 > Ruby

剪裁图片【Ruby on Rails】

2013-04-28 16:17 411 查看
这一集我们来用Paperclip和jQuery叫做Jcrop的插件在网页上裁剪图片。假设我们有一个Rails应用,比如一个论坛,允许用用户名注册,并且还可以用头像图片来展示自己。



用户选择上传的图片可以是任何大小,所以我们的应用将会调整大小并裁剪到100像素的正方形之内。



这个应用用Paperclip来自动裁剪和调整图片的大小,但是我们想让用户自己可以控制怎么去裁剪图片。我们可以用一些JavaScript来实现,我们这一集就要讲怎么来做。

如果你不熟悉Paperclip,在134集中涵盖了Paperclip的内容,你想理解怎么用Paperclip来添加附件到你的Model中,那一集是值得看一下的,并且我们这一集也是建立在那一集之上的。

有一些JavaScript图片裁剪的库可以用;我们将要用一个jQuery的插件,叫做Jcrop的库。如果你在使用Prototype和Scriptaculous来代替jQuery,JavaScript
Image Cropper UI library提供类似的功能。


创建裁剪页面

在我们的应用中有一个User Model用了Paperclip。

ruby

class User < ActiveRecord::Base
has_attached_file :avatar, :styles  => { :small => "100x100#" }
end


avatar属性有一个style将会把图片裁剪成100×100。我们要生成另外一个大一点的图片,显示给用户让他裁剪他的头像。我们可以给用户显示原始图片,但是原始图片可以是任意大小或长宽比,并且打乱了我们的程序布局。比较好的方式是添加另一个确定大小的样式的图片让用户来裁剪。

ruby

has_attached_file :avatar, :styles  => { :small => "100x100#", :large => "500x500>" }


这个新的large样式将把图片调整到500×500像素之内,但我们在大小后面用了”
>
”,这比用”
#
”好,Paperclip将维持这个长宽比,所以图片不会被裁剪。

我们的目的是修改我们的应用,让用户注册并上传图片或修改头像的时候,他们直接去一个可以裁剪他们上传图片的页面。要实现这些我们需要修改
UsersController
中的
create
update
 action,并且新建一个叫
crop
的新action。

我们将首先修改
create
 action。如果没有用户没有提供头像,将直接重定向到以前的操作页面,如果有图片上传了,我们将渲染
crop
 action。

ruby

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


同样的,当用户更新后如果有新图片上传我们要显示
crop
视图。

ruby

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


我们在
/app/views/users/crop.html.erb
创建new视图文件。这个文件将需要包含Jcrop用到的css和JavaScript文件,就和包含jQuery库一样。在我们的应用布局文件里头部有一个
yield
段,这个允许我们从视图文件向页面的
head
段插入内容。我们已经包含了jQuery库,所以不用担心这点。

ruby

<head>
<title><%= h(yield(:title) || "Untitled") %></title>
<%= stylesheet_link_tag 'application' %>
<%= javascript_include_tag 'jquery.1.3.2.min' %>
<%= yield(:head) %>
</head>


我们要给新页面一个标题,然后新建一个
content_for
块来放我们模板里要传到head段的代码。这将由一个Jcrop的样式文件和JavaScript文件的链接构成,文件我们下载下来放到我们应用的适当位置,还有一些内联的JavaScript也要放在里面,这些JavaScript将是id为cropbox的元素拥有Jcrop的功能。

最后我们在页面的body中渲染图片本身,把它
id
设为
cropbox
,这样我们写的jQuery代码就可以用在图片上了。

ruby

<% 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" %>


如果现在我们编辑一个用户,并且上传一张新的图片,我们将被重定向到哪个新的
crop
 action,在那我们将看见那个大的500×500版本的图片有Jcrop的功能。当光标在图片上面的时候将变成十字线的样子,我们拖动覆盖图片的一部分,我们就选择了这部分。



现在我们能选择图片的一部分了,但是我们还有办法去裁剪它。要想裁剪我们还要给页面添加一个表单,表单包含裁剪图片高度和宽度的字段,除此之外还有相对于左上角的x,y坐标值,和和一个”crop”按钮。

头像是作为User的一个属性,所以这个新表单将会修改用户的信息。我们需要在
User
模型中新建四个新属相来放裁剪区域的坐标。

ruby

class User < ActiveRecord::Base
has_attached_file :avatar, :styles  => { :small => "100x100#", :large => "500x500>" }
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
end


现在我们已经添加了新属性,我们可以创建表单了。我们通常使用隐藏字段来存裁剪区域的坐标,但是在开发我们的页面时候用文本框,是为了我们能看见那些值被存住了。表单的代码就直接放在crop视图的图片下面。

ruby

<% 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 %>


我们已经给表单中每个文本框一个
id
,这样我们可以在JavaScript中更新这些字段的值。要在裁剪区域改变的时候去更新这些字段,我们要在Jcrop调用的时候添加一些参数。

javascript

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


Jcrop有两个主要的事件回调,
onChange
onSelect
,当选择改变或移动的时候被调用。我们指定一个回调函数,当这些事件发生时都会被调用。我们也定义另外两个参数:
setSelect
,用来定义最初的的裁剪矩形,和
aspectRatio
,用来定义裁剪矩形的长宽比,我们设为1,所以只有正方形可以被选择。

我们还需要写
update_crop
回调函数。这是传递坐标对象的,从中能提取出
x
y
width
height
的值,并把它们传到适当的表单字段里。这个函数直接跟在上面函数的下面。

javascript

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


如果现在我们刷新这个页面,我们将会看到表单的字段都有表示但前裁剪区域的值。我们移动并调整裁剪区域的大小,这些文本框中的值将会改变。



当我们点击”crop”按钮的时候,
User
模型被更新了。我们在裁剪后需要告诉Paperclip重新处理附件。我们可以用
after_update
过滤器来实现,如果图片裁剪过过滤器将重新处理图片。

ruby

class User < ActiveRecord::Base
has_attached_file :avatar, :styles  => { :small => "100x100#", :large => "500x500>" }
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
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
end


如果四个裁剪参数从表单传递过来,
after_update
过滤器将被调用。如果有四个参数有值,Paperclip的
reprocess!
方法将被调用。

我们还需要告诉Paperclip怎么去用表单传来的坐标重新处理图片。我们要写一个自定义的Paperclip processor,在那之前我们要在
User
模型里指定processor。

ruby

has_attached_file :avatar, :styles  => { :small => "100x100#", :large => "500x500>" }, :processors => [:cropper]


习惯上,我们把自定义的processor放在/lib下面的
paperclip_processors
目录里面。我们要创建那个目录,并在里面新建一个
cropper.rb
文件,里面包含这些代码:

ruby

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


自定义的Paperclip processor放在
Paperclip
模块里面。我们的processor继承
Paperclip
默认的processor,
Thumbnail
 processor。
Thumbnail
有一个
transformation_command
方法,我们要覆盖这个方法,因为我们要加上自己的命令参数。在我们的
transformation_command
方法里,我们用正则表达式来检查存在的裁剪命令,并且用我们自己基于
User
模型中属相的命令来替代。


选择图片中正确的部分

现在已经写完我们的processor,我们可以试试裁剪我们的图片了,看看运行起来是不是和预期的一样。我们选择上传图片中的车子并按下”crop”按钮。



结果基本正确,图片被裁剪了,但是不是在正确的位置。



这个没有正确是因为用户看见的图片是
large
样式的,这个图片已经调整过大小了,裁剪坐标是给予这个图片的。Paperclip裁剪的图片是原始上传的图片,所以我们需要在这两个不同图片大小中补充,这样才能使图片正确裁剪。

我们可以用Paperclip的
Geometry.from_file
方法来得到原始图片的尺寸。在我们的
User
模型里,我们要写一个新的方法,在里面得到图片的尺寸,并存在一个示例变量里面。

ruby

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


crop
视图里面我们能修改当裁剪选择改变时设置表单值的JavaScript,这样就考虑到原始图片的尺寸了。我们计算原始图片宽度和裁剪过的图片的宽度的比例,并用这个比例把所有值计算出来。

javascript

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


如果现在我们选择了车的前面并裁剪,图片的正确区域就被裁剪了。




添加预览图片

这一集的最后一件事就是我们要在裁剪页面添加一个预览图片,这样用户就可以确切的看到他们的头像是不是和点击crop按钮之前一样。

回到
crop
视图里面,我们要在大图片和表单之间添加一个预览段落。在段落里还是显示大图但是用一个div来限定100×100像素大小,并且隐藏掉多出来的部分。现在显示坐标的文本框可以用隐藏的字段来替代了。

ruby

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

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

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


我们给预览图片一个
id
preview
,这样我们可以在JavaScript里引用它。我们要修改回调函数,这样裁剪区域改变的时候就可以更新预览图片。

javascript

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.floor(coords.x * ratio));
$('#crop_y').val(Math.floor(coords.y * ratio));
$('#crop_w').val(Math.floor(coords.w * ratio));
$('#crop_h').val(Math.floor(coords.h * ratio));
}


裁剪区域一改变,预览图片就会跟着更新,也就是说,裁剪的正方形拖动或改变大小的时候用户就可以实时地看到他们的头像是什么样子。



这一集就到这了。如果你想进一步了解这些东西,你可以看一看jschwindt github上的rjcrop项目,它是一个Rails应用的例子,和我们这看到的差不多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息