avatar 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/Manifest.txt +32 -6
- data/lib/avatar.rb +1 -1
- data/lib/avatar/source/gravatar_source.rb +7 -5
- data/lib/avatar/source/paperclip_source.rb +52 -0
- data/lib/avatar/source/wrapper.rb +6 -0
- data/lib/avatar/source/wrapper/abstract_source_wrapper.rb +33 -0
- data/lib/avatar/source/wrapper/rails_asset_source_wrapper.rb +36 -0
- data/lib/avatar/source/wrapper/string_substitution_source_wrapper.rb +55 -0
- data/lib/avatar/version.rb +1 -1
- data/test/lib/paperclip/README +32 -0
- data/test/lib/paperclip/Rakefile +41 -0
- data/test/lib/paperclip/generators/paperclip/USAGE +5 -0
- data/test/lib/paperclip/generators/paperclip/paperclip_generator.rb +27 -0
- data/test/lib/paperclip/generators/paperclip/templates/paperclip_migration.rb +17 -0
- data/test/lib/paperclip/init.rb +3 -0
- data/test/lib/paperclip/lib/paperclip.rb +197 -0
- data/test/lib/paperclip/lib/paperclip/attachment.rb +230 -0
- data/test/lib/paperclip/lib/paperclip/geometry.rb +109 -0
- data/test/lib/paperclip/lib/paperclip/iostream.rb +43 -0
- data/test/lib/paperclip/lib/paperclip/thumbnail.rb +80 -0
- data/test/lib/paperclip/lib/paperclip/upfile.rb +32 -0
- data/test/lib/paperclip/tasks/paperclip_tasks.rake +38 -0
- data/test/lib/paperclip/test/database.yml +5 -0
- data/test/lib/paperclip/test/fixtures/12k.png +0 -0
- data/test/lib/paperclip/test/fixtures/5k.png +0 -0
- data/test/lib/paperclip/test/fixtures/bad.png +1 -0
- data/test/lib/paperclip/test/helper.rb +38 -0
- data/test/lib/paperclip/test/test_attachment.rb +99 -0
- data/test/lib/paperclip/test/test_geometry.rb +142 -0
- data/test/lib/paperclip/test/test_integration.rb +76 -0
- data/test/lib/paperclip/test/test_iostream.rb +60 -0
- data/test/lib/paperclip/test/test_paperclip.rb +83 -0
- data/test/lib/paperclip/test/test_thumbnail.rb +76 -0
- data/test/lib/schema.rb +14 -1
- data/test/test_file_column_source.rb +8 -6
- data/test/test_gravatar_source.rb +5 -0
- data/test/test_helper.rb +7 -2
- data/test/test_paperclip_source.rb +52 -0
- data/test/test_rails_asset_source_wrapper.rb +37 -0
- data/test/test_string_substitution_source_wrapper.rb +25 -0
- data/website/index.html +1 -1
- metadata +43 -11
- data/lib/avatar/source/rails_asset_source.rb +0 -64
- data/lib/avatar/source/string_substitution_source.rb +0 -45
- data/lib/avatar/string_substitution.rb +0 -28
- data/test/test_rails_asset_source.rb +0 -50
- data/test/test_string_substitution.rb +0 -28
- data/test/test_string_substitution_source.rb +0 -22
@@ -0,0 +1,197 @@
|
|
1
|
+
# Paperclip allows file attachments that are stored in the filesystem. All graphical
|
2
|
+
# transformations are done using the Graphics/ImageMagick command line utilities and
|
3
|
+
# are stored in Tempfiles until the record is saved. Paperclip does not require a
|
4
|
+
# separate model for storing the attachment's information, instead adding a few simple
|
5
|
+
# columns to your table.
|
6
|
+
#
|
7
|
+
# Author:: Jon Yurek
|
8
|
+
# Copyright:: Copyright (c) 2008 thoughtbot, inc.
|
9
|
+
# License:: Distrbutes under the same terms as Ruby
|
10
|
+
#
|
11
|
+
# Paperclip defines an attachment as any file, though it makes special considerations
|
12
|
+
# for image files. You can declare that a model has an attached file with the
|
13
|
+
# +has_attached_file+ method:
|
14
|
+
#
|
15
|
+
# class User < ActiveRecord::Base
|
16
|
+
# has_attached_file :avatar, :styles => { :thumb => "100x100" }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# user = User.new
|
20
|
+
# user.avatar = params[:user][:avatar]
|
21
|
+
# user.avatar.url
|
22
|
+
# # => "/users/avatars/4/original_me.jpg"
|
23
|
+
# user.avatar.url(:thumb)
|
24
|
+
# # => "/users/avatars/4/thumb_me.jpg"
|
25
|
+
#
|
26
|
+
# See the +has_attached_file+ documentation for more details.
|
27
|
+
|
28
|
+
require 'tempfile'
|
29
|
+
require 'paperclip/upfile'
|
30
|
+
require 'paperclip/iostream'
|
31
|
+
require 'paperclip/geometry'
|
32
|
+
require 'paperclip/thumbnail'
|
33
|
+
require 'paperclip/attachment'
|
34
|
+
|
35
|
+
# The base module that gets included in ActiveRecord::Base.
|
36
|
+
module Paperclip
|
37
|
+
class << self
|
38
|
+
# Provides configurability to Paperclip. There are a number of options available, such as:
|
39
|
+
# * whiny_thumbnails: Will raise an error if Paperclip cannot process thumbnails of
|
40
|
+
# an uploaded image. Defaults to true.
|
41
|
+
# * image_magick_path: Defines the path at which to find the +convert+ and +identify+
|
42
|
+
# programs if they are not visible to Rails the system's search path. Defaults to
|
43
|
+
# nil, which uses the first executable found in the search path.
|
44
|
+
def options
|
45
|
+
@options ||= {
|
46
|
+
:whiny_thumbnails => true,
|
47
|
+
:image_magick_path => nil
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def path_for_command command #:nodoc:
|
52
|
+
path = [options[:image_magick_path], command].compact
|
53
|
+
File.join(*path)
|
54
|
+
end
|
55
|
+
|
56
|
+
def included base #:nodoc:
|
57
|
+
base.extend ClassMethods
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class PaperclipError < StandardError #:nodoc:
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
attr_reader :attachment_definitions
|
66
|
+
|
67
|
+
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
|
68
|
+
# is typically a file stored somewhere on the filesystem and has been uploaded by a user.
|
69
|
+
# The attribute returns a Paperclip::Attachment object which handles the management of
|
70
|
+
# that file. The intent is to make the attachment as much like a normal attribute. The
|
71
|
+
# thumbnails will be created when the new file is assigned, but they will *not* be saved
|
72
|
+
# until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
|
73
|
+
# called on it, the attachment will *not* be deleted until +save+ is called. See the
|
74
|
+
# Paperclip::Attachment documentation for more specifics. There are a number of options
|
75
|
+
# you can set to change the behavior of a Paperclip attachment:
|
76
|
+
# * +url+: The full URL of where the attachment is publically accessible. This can just
|
77
|
+
# as easily point to a directory served directly through Apache as it can to an action
|
78
|
+
# that can control permissions. You can specify the full domain and path, but usually
|
79
|
+
# just an absolute path is sufficient. The leading slash must be included manually for
|
80
|
+
# absolute paths. The default value is "/:class/:attachment/:id/:style_:filename". See
|
81
|
+
# Paperclip::Attachment#interpolate for more information on variable interpolaton.
|
82
|
+
# :url => "/:attachment/:id/:style_:basename:extension"
|
83
|
+
# :url => "http://some.other.host/stuff/:class/:id_:extension"
|
84
|
+
# * +default_url+: The URL that will be returned if there is no attachment assigned.
|
85
|
+
# This field is interpolated just as the url is. The default value is
|
86
|
+
# "/:class/:attachment/missing_:style.png"
|
87
|
+
# has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
|
88
|
+
# User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
|
89
|
+
# * +styles+: A hash of thumbnail styles and their geometries. You can find more about
|
90
|
+
# geometry strings at the ImageMagick website
|
91
|
+
# (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip
|
92
|
+
# also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
|
93
|
+
# inside the dimensions and then crop the rest off (weighted at the center). The
|
94
|
+
# default value is to generate no thumbnails.
|
95
|
+
# * +default_style+: The thumbnail style that will be used by default URLs.
|
96
|
+
# Defaults to +original+.
|
97
|
+
# has_attached_file :avatar, :styles => { :normal => "100x100#" },
|
98
|
+
# :default_style => :normal
|
99
|
+
# user.avatar.url # => "/avatars/23/normal_me.png"
|
100
|
+
# * +path+: The location of the repository of attachments on disk. This can be coordinated
|
101
|
+
# with the value of the +url+ option to allow files to be saved into a place where Apache
|
102
|
+
# can serve them without hitting your app. Defaults to
|
103
|
+
# ":rails_root/public/:class/:attachment/:id/:style_:filename".
|
104
|
+
# By default this places the files in the app's public directory which can be served
|
105
|
+
# directly. If you are using capistrano for deployment, a good idea would be to
|
106
|
+
# make a symlink to the capistrano-created system directory from inside your app's
|
107
|
+
# public directory.
|
108
|
+
# See Paperclip::Attachment#interpolate for more information on variable interpolaton.
|
109
|
+
# :path => "/var/app/attachments/:class/:id/:style/:filename"
|
110
|
+
# * +whiny_thumbnails+: Will raise an error if Paperclip cannot process thumbnails of an
|
111
|
+
# uploaded image. This will ovrride the global setting for this attachment.
|
112
|
+
# Defaults to true.
|
113
|
+
def has_attached_file name, options = {}
|
114
|
+
include InstanceMethods
|
115
|
+
|
116
|
+
@attachment_definitions ||= {}
|
117
|
+
@attachment_definitions[name] = {:validations => []}.merge(options)
|
118
|
+
|
119
|
+
after_save :save_attached_files
|
120
|
+
before_destroy :destroy_attached_files
|
121
|
+
|
122
|
+
define_method name do |*args|
|
123
|
+
a = attachment_for(name)
|
124
|
+
(args.length > 0) ? a.to_s(args.first) : a
|
125
|
+
end
|
126
|
+
|
127
|
+
define_method "#{name}=" do |file|
|
128
|
+
attachment_for(name).assign(file)
|
129
|
+
end
|
130
|
+
|
131
|
+
define_method "#{name}?" do
|
132
|
+
! attachment_for(name).file.nil?
|
133
|
+
end
|
134
|
+
|
135
|
+
validates_each(name) do |record, attr, value|
|
136
|
+
value.send(:flush_errors)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Places ActiveRecord-style validations on the size of the file assigned. The
|
141
|
+
# possible options are:
|
142
|
+
# * +in+: a Range of bytes (i.e. +1..1.megabyte+),
|
143
|
+
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
144
|
+
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
145
|
+
def validates_attachment_size name, options = {}
|
146
|
+
@attachment_definitions[name][:validations] << lambda do |attachment, instance|
|
147
|
+
unless options[:greater_than].nil?
|
148
|
+
options[:in] = (options[:greater_than]..(1/0)) # 1/0 => Infinity
|
149
|
+
end
|
150
|
+
unless options[:less_than].nil?
|
151
|
+
options[:in] = (0..options[:less_than])
|
152
|
+
end
|
153
|
+
unless options[:in].include? instance[:"#{name}_file_size"].to_i
|
154
|
+
"file size is not between #{options[:in].first} and #{options[:in].last} bytes."
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Places ActiveRecord-style validations on the presence of a file.
|
160
|
+
def validates_attachment_presence name
|
161
|
+
@attachment_definitions[name][:validations] << lambda do |attachment, instance|
|
162
|
+
if attachment.file.nil? || !File.exist?(attachment.file.path)
|
163
|
+
"must be set."
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
module InstanceMethods #:nodoc:
|
171
|
+
def attachment_for name
|
172
|
+
@attachments ||= {}
|
173
|
+
@attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
|
174
|
+
end
|
175
|
+
|
176
|
+
def each_attachment
|
177
|
+
self.class.attachment_definitions.each do |name, definition|
|
178
|
+
yield(name, attachment_for(name))
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def save_attached_files
|
183
|
+
each_attachment do |name, attachment|
|
184
|
+
attachment.send(:flush_writes)
|
185
|
+
attachment.send(:flush_deletes)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def destroy_attached_files
|
190
|
+
each_attachment do |name, attachment|
|
191
|
+
attachment.send(:queue_existing_for_delete)
|
192
|
+
attachment.send(:flush_deletes)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
module Paperclip
|
2
|
+
# The Attachment class manages the files for a given attachment. It saves when the model saves,
|
3
|
+
# deletes when the model is destroyed, and processes the file upon assignment.
|
4
|
+
class Attachment
|
5
|
+
|
6
|
+
attr_reader :name, :instance, :file, :styles, :default_style
|
7
|
+
|
8
|
+
# Creates an Attachment object. +name+ is the name of the attachment, +instance+ is the
|
9
|
+
# ActiveRecord object instance it's attached to, and +options+ is the same as the hash
|
10
|
+
# passed to +has_attached_file+.
|
11
|
+
def initialize name, instance, options
|
12
|
+
@name = name
|
13
|
+
@instance = instance
|
14
|
+
@url = options[:url] ||
|
15
|
+
"/:attachment/:id/:style/:basename.:extension"
|
16
|
+
@path = options[:path] ||
|
17
|
+
":rails_root/public/:attachment/:id/:style/:basename.:extension"
|
18
|
+
@styles = options[:styles] || {}
|
19
|
+
@default_url = options[:default_url] || "/:attachment/:style/missing.png"
|
20
|
+
@validations = options[:validations] || []
|
21
|
+
@default_style = options[:default_style] || :original
|
22
|
+
@queued_for_delete = []
|
23
|
+
@processed_files = {}
|
24
|
+
@errors = []
|
25
|
+
@validation_errors = nil
|
26
|
+
@dirty = false
|
27
|
+
|
28
|
+
normalize_style_definition
|
29
|
+
|
30
|
+
@file = File.new(path) if original_filename && File.exists?(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
# What gets called when you call instance.attachment = File. It clears errors,
|
34
|
+
# assigns attributes, processes the file, and runs validations. It also queues up
|
35
|
+
# the previous file for deletion, to be flushed away on #save of its host.
|
36
|
+
def assign uploaded_file
|
37
|
+
queue_existing_for_delete
|
38
|
+
@errors = []
|
39
|
+
@validation_errors = nil
|
40
|
+
|
41
|
+
return nil unless valid_file?(uploaded_file)
|
42
|
+
|
43
|
+
@file = uploaded_file.to_tempfile
|
44
|
+
@instance[:"#{@name}_file_name"] = uploaded_file.original_filename
|
45
|
+
@instance[:"#{@name}_content_type"] = uploaded_file.content_type
|
46
|
+
@instance[:"#{@name}_file_size"] = uploaded_file.size
|
47
|
+
|
48
|
+
@dirty = true
|
49
|
+
|
50
|
+
post_process
|
51
|
+
ensure
|
52
|
+
validate
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the public URL of the attachment, with a given style. Note that this
|
56
|
+
# does not necessarily need to point to a file that your web server can access
|
57
|
+
# and can point to an action in your app, if you need fine grained security.
|
58
|
+
# This is not recommended if you don't need the security, however, for
|
59
|
+
# performance reasons.
|
60
|
+
def url style = nil
|
61
|
+
@file ? interpolate(@url, style) : interpolate(@default_url, style)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Alias to +url+
|
65
|
+
def to_s style = nil
|
66
|
+
url(style)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns true if there are any errors on this attachment.
|
70
|
+
def valid?
|
71
|
+
errors.length == 0
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns an array containing the errors on this attachment.
|
75
|
+
def errors
|
76
|
+
@errors.compact.uniq
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns true if there are changes that need to be saved.
|
80
|
+
def dirty?
|
81
|
+
@dirty
|
82
|
+
end
|
83
|
+
|
84
|
+
# Saves the file, if there are no errors. If there are, it flushes them to
|
85
|
+
# the instance's errors and returns false, cancelling the save.
|
86
|
+
def save
|
87
|
+
if valid?
|
88
|
+
flush_deletes
|
89
|
+
flush_writes
|
90
|
+
true
|
91
|
+
else
|
92
|
+
flush_errors
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns an +IO+ representing the data of the file assigned to the given
|
98
|
+
# style. Useful for streaming with +send_file+.
|
99
|
+
def to_io style = nil
|
100
|
+
begin
|
101
|
+
style ||= @default_style
|
102
|
+
@processed_files[style] || File.new(path(style))
|
103
|
+
rescue Errno::ENOENT
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the name of the file as originally assigned, and as lives in the
|
109
|
+
# <attachment>_file_name attribute of the model.
|
110
|
+
def original_filename
|
111
|
+
instance[:"#{name}_file_name"]
|
112
|
+
end
|
113
|
+
|
114
|
+
# A hash of procs that are run during the interpolation of a path or url.
|
115
|
+
# A variable of the format :name will be replaced with the return value of
|
116
|
+
# the proc named ":name". Each lambda takes the attachment and the current
|
117
|
+
# style as arguments. This hash can be added to with your own proc if
|
118
|
+
# necessary.
|
119
|
+
def self.interpolations
|
120
|
+
@interpolations ||= {
|
121
|
+
:rails_root => lambda{|attachment,style| RAILS_ROOT },
|
122
|
+
:class => lambda{|attachment,style| attachment.instance.class.to_s.downcase.pluralize },
|
123
|
+
:basename => lambda do |attachment,style|
|
124
|
+
attachment.original_filename.gsub(/\.(.*?)$/, "")
|
125
|
+
end,
|
126
|
+
:extension => lambda do |attachment,style|
|
127
|
+
((style = attachment.styles[style]) && style.last) ||
|
128
|
+
File.extname(attachment.original_filename).gsub(/^\.+/, "")
|
129
|
+
end,
|
130
|
+
:id => lambda{|attachment,style| attachment.instance.id },
|
131
|
+
:partition_id => lambda do |attachment, style|
|
132
|
+
("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
|
133
|
+
end,
|
134
|
+
:attachment => lambda{|attachment,style| attachment.name.to_s.downcase.pluralize },
|
135
|
+
:style => lambda{|attachment,style| style || attachment.default_style },
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def valid_file? file #:nodoc:
|
142
|
+
file.respond_to?(:original_filename) && file.respond_to?(:content_type)
|
143
|
+
end
|
144
|
+
|
145
|
+
def validate #:nodoc:
|
146
|
+
unless @validation_errors
|
147
|
+
@validation_errors = @validations.collect do |v|
|
148
|
+
v.call(self, instance)
|
149
|
+
end.flatten.compact.uniq
|
150
|
+
@errors += @validation_errors
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def normalize_style_definition
|
155
|
+
@styles.each do |name, args|
|
156
|
+
dimensions, format = [args, nil].flatten[0..1]
|
157
|
+
format = nil if format == ""
|
158
|
+
@styles[name] = [dimensions, format]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def post_process #:nodoc:
|
163
|
+
return nil if @file.nil?
|
164
|
+
@styles.each do |name, args|
|
165
|
+
begin
|
166
|
+
dimensions, format = args
|
167
|
+
@processed_files[name] = Thumbnail.make(self.file,
|
168
|
+
dimensions,
|
169
|
+
format,
|
170
|
+
@whiny_thumbnails)
|
171
|
+
rescue Errno::ENOENT => e
|
172
|
+
@errors << "could not be processed because the file does not exist."
|
173
|
+
rescue PaperclipError => e
|
174
|
+
@errors << e.message
|
175
|
+
end
|
176
|
+
end
|
177
|
+
@processed_files[:original] = @file
|
178
|
+
end
|
179
|
+
|
180
|
+
def interpolate pattern, style = nil #:nodoc:
|
181
|
+
style ||= @default_style
|
182
|
+
pattern = pattern.dup
|
183
|
+
self.class.interpolations.each do |tag, l|
|
184
|
+
pattern.gsub!(/:#{tag}/) do |match|
|
185
|
+
l.call( self, style )
|
186
|
+
end
|
187
|
+
end
|
188
|
+
pattern.gsub(%r{/+}, "/")
|
189
|
+
end
|
190
|
+
|
191
|
+
def path style = nil #:nodoc:
|
192
|
+
interpolate(@path, style)
|
193
|
+
end
|
194
|
+
|
195
|
+
def queue_existing_for_delete #:nodoc:
|
196
|
+
@queued_for_delete += @processed_files.values
|
197
|
+
@file = nil
|
198
|
+
@processed_files = {}
|
199
|
+
@instance[:"#{@name}_file_name"] = nil
|
200
|
+
@instance[:"#{@name}_content_type"] = nil
|
201
|
+
@instance[:"#{@name}_file_size"] = nil
|
202
|
+
end
|
203
|
+
|
204
|
+
def flush_errors #:nodoc:
|
205
|
+
@errors.each do |error|
|
206
|
+
instance.errors.add(name, error)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def flush_writes #:nodoc:
|
211
|
+
@processed_files.each do |style, file|
|
212
|
+
FileUtils.mkdir_p( File.dirname(path(style)) )
|
213
|
+
@processed_files[style] = file.stream_to(path(style)) unless file.path == path(style)
|
214
|
+
end
|
215
|
+
@file = @processed_files[@default_style]
|
216
|
+
end
|
217
|
+
|
218
|
+
def flush_deletes #:nodoc:
|
219
|
+
@queued_for_delete.compact.each do |file|
|
220
|
+
begin
|
221
|
+
FileUtils.rm(file.path)
|
222
|
+
rescue Errno::ENOENT => e
|
223
|
+
# ignore them
|
224
|
+
end
|
225
|
+
end
|
226
|
+
@queued_for_delete = []
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Paperclip
|
2
|
+
|
3
|
+
# Defines the geometry of an image.
|
4
|
+
class Geometry
|
5
|
+
attr_accessor :height, :width, :modifier
|
6
|
+
|
7
|
+
# Gives a Geometry representing the given height and width
|
8
|
+
def initialize width = nil, height = nil, modifier = nil
|
9
|
+
height = nil if height == ""
|
10
|
+
width = nil if width == ""
|
11
|
+
@height = (height || width).to_f
|
12
|
+
@width = (width || height).to_f
|
13
|
+
@modifier = modifier
|
14
|
+
end
|
15
|
+
|
16
|
+
# Uses ImageMagick to determing the dimensions of a file, passed in as either a
|
17
|
+
# File or path.
|
18
|
+
def self.from_file file
|
19
|
+
file = file.path if file.respond_to? "path"
|
20
|
+
parse(`#{Paperclip.path_for_command('identify')} "#{file}"`) ||
|
21
|
+
raise(Errno::ENOENT, file)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Parses a "WxH" formatted string, where W is the width and H is the height.
|
25
|
+
def self.parse string
|
26
|
+
if match = (string && string.match(/\b(\d*)x(\d*)\b([\>\<\#\@\%^!])?/))
|
27
|
+
Geometry.new(*match[1,3])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# True if the dimensions represent a square
|
32
|
+
def square?
|
33
|
+
height == width
|
34
|
+
end
|
35
|
+
|
36
|
+
# True if the dimensions represent a horizontal rectangle
|
37
|
+
def horizontal?
|
38
|
+
height < width
|
39
|
+
end
|
40
|
+
|
41
|
+
# True if the dimensions represent a vertical rectangle
|
42
|
+
def vertical?
|
43
|
+
height > width
|
44
|
+
end
|
45
|
+
|
46
|
+
# The aspect ratio of the dimensions.
|
47
|
+
def aspect
|
48
|
+
width / height
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the larger of the two dimensions
|
52
|
+
def larger
|
53
|
+
[height, width].max
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the smaller of the two dimensions
|
57
|
+
def smaller
|
58
|
+
[height, width].min
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the width and height in a format suitable to be passed to Geometry.parse
|
62
|
+
def to_s
|
63
|
+
"%dx%d%s" % [width, height, modifier]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Same as to_s
|
67
|
+
def inspect
|
68
|
+
to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the scaling and cropping geometries (in string-based ImageMagick format)
|
72
|
+
# neccessary to transform this Geometry into the Geometry given. If crop is true,
|
73
|
+
# then it is assumed the destination Geometry will be the exact final resolution.
|
74
|
+
# In this case, the source Geometry is scaled so that an image containing the
|
75
|
+
# destination Geometry would be completely filled by the source image, and any
|
76
|
+
# overhanging image would be cropped. Useful for square thumbnail images. The cropping
|
77
|
+
# is weighted at the center of the Geometry.
|
78
|
+
def transformation_to dst, crop = false
|
79
|
+
ratio = Geometry.new( dst.width / self.width, dst.height / self.height )
|
80
|
+
|
81
|
+
if crop
|
82
|
+
scale_geometry, scale = scaling(dst, ratio)
|
83
|
+
crop_geometry = cropping(dst, ratio, scale)
|
84
|
+
else
|
85
|
+
scale_geometry = dst.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
[ scale_geometry, crop_geometry ]
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def scaling dst, ratio
|
94
|
+
if ratio.horizontal? || ratio.square?
|
95
|
+
[ "%dx" % dst.width, ratio.width ]
|
96
|
+
else
|
97
|
+
[ "x%d" % dst.height, ratio.height ]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def cropping dst, ratio, scale
|
102
|
+
if ratio.horizontal? || ratio.square?
|
103
|
+
"%dx%d+%d+%d" % [ dst.width, dst.height, 0, (self.height * scale - dst.height) / 2 ]
|
104
|
+
else
|
105
|
+
"%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|