coryodaniel-milton 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.markdown +350 -0
- data/Rakefile +9 -0
- data/init.rb +1 -0
- data/lib/milton.rb +111 -0
- data/lib/milton/attachment.rb +202 -0
- data/lib/milton/core/file.rb +22 -0
- data/lib/milton/core/tempfile.rb +44 -0
- data/lib/milton/derivatives/derivative.rb +106 -0
- data/lib/milton/derivatives/thumbnail.rb +39 -0
- data/lib/milton/derivatives/thumbnail/crop_calculator.rb +64 -0
- data/lib/milton/derivatives/thumbnail/image.rb +49 -0
- data/lib/milton/storage/disk_file.rb +84 -0
- data/lib/milton/storage/s3_file.rb +103 -0
- data/lib/milton/storage/stored_file.rb +46 -0
- data/lib/milton/uploading.rb +82 -0
- data/test/fixtures/big-milton.jpg +0 -0
- data/test/fixtures/milton.jpg +0 -0
- data/test/fixtures/mini-milton.jpg +0 -0
- data/test/fixtures/unsanitary .milton.jpg +0 -0
- data/test/milton/attachment_test.rb +359 -0
- data/test/milton/milton_test.rb +21 -0
- data/test/milton/resizing_test.rb +70 -0
- data/test/s3_helper.rb +59 -0
- data/test/schema.rb +13 -0
- data/test/test_helper.rb +78 -0
- metadata +87 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'milton/derivatives/derivative'
|
|
3
|
+
|
|
4
|
+
module Milton
|
|
5
|
+
module Attachment
|
|
6
|
+
# Call is_attachment with your options in order to add attachment
|
|
7
|
+
# functionality to your ActiveRecord model.
|
|
8
|
+
#
|
|
9
|
+
# TODO: list options
|
|
10
|
+
def is_attachment(options={})
|
|
11
|
+
# Check to see that it hasn't already been extended so that subclasses
|
|
12
|
+
# can redefine is_attachment from their superclasses and overwrite
|
|
13
|
+
# options w/o losing the superclass options.
|
|
14
|
+
unless respond_to?(:has_attachment_methods)
|
|
15
|
+
extend Milton::Attachment::AttachmentMethods
|
|
16
|
+
class_inheritable_accessor :milton_options
|
|
17
|
+
end
|
|
18
|
+
has_attachment_methods(options)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module AttachmentMethods
|
|
22
|
+
def require_column(column, message)
|
|
23
|
+
begin
|
|
24
|
+
raise message unless column_names.include?(column)
|
|
25
|
+
rescue ActiveRecord::StatementInvalid => i
|
|
26
|
+
# table doesn't exist yet, i.e. hasn't been migrated in...
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def has_attachment_methods(options={})
|
|
31
|
+
require_column 'filename', "Milton requires a filename column on #{class_name} table"
|
|
32
|
+
|
|
33
|
+
# It's possible that this is being included from a class and a sub
|
|
34
|
+
# class of that class, in which case we need to merge the existing
|
|
35
|
+
# options up.
|
|
36
|
+
self.milton_options ||= {}
|
|
37
|
+
milton_options.merge!(options)
|
|
38
|
+
|
|
39
|
+
# Character used to seperate a filename from its derivative options, this
|
|
40
|
+
# character will be stripped from all incoming filenames and replaced by
|
|
41
|
+
# replacement
|
|
42
|
+
milton_options[:separator] ||= '.'
|
|
43
|
+
milton_options[:replacement] ||= '-'
|
|
44
|
+
milton_options[:tempfile_path] ||= File.join(Rails.root, "tmp", "milton")
|
|
45
|
+
milton_options[:storage] ||= :disk
|
|
46
|
+
milton_options[:storage_options] ||= {}
|
|
47
|
+
milton_options[:processors] ||= {}
|
|
48
|
+
milton_options[:uploading] ||= true
|
|
49
|
+
|
|
50
|
+
# Set to true to allow on-demand processing of derivatives. This can
|
|
51
|
+
# be rediculously slow because it requires that the existance of the
|
|
52
|
+
# derivative is checked each time it's requested -- throw in S3 and
|
|
53
|
+
# that becomes a huge lag. Reccommended only for prototyping.
|
|
54
|
+
milton_options[:postproccess] ||= false
|
|
55
|
+
|
|
56
|
+
# TODO: Write about recipes
|
|
57
|
+
# * They're refered to by name in #path
|
|
58
|
+
# * They're an order of derivations to make against this attachment
|
|
59
|
+
# * They run in the order defined
|
|
60
|
+
# * They are created and run when the AR model is created
|
|
61
|
+
# * They're necessary when +:postprocessing+ is turned off
|
|
62
|
+
milton_options[:recipes] ||= {}
|
|
63
|
+
milton_options[:recipes].each do |name, steps|
|
|
64
|
+
steps = [steps] unless steps.is_a?(Array)
|
|
65
|
+
steps.each do |step|
|
|
66
|
+
step.each { |processor, options| Milton.try_require "milton/derivatives/#{processor}", "No '#{processor}' processor found for Milton" }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# TODO: Write about storage options
|
|
71
|
+
# * Late binding (so right_aws is only req'd if you use S3)
|
|
72
|
+
Milton.try_require "milton/storage/#{milton_options[:storage]}_file", "No '#{milton_options[:storage]}' storage found for Milton"
|
|
73
|
+
|
|
74
|
+
# TODO: initialize these options in DiskFile
|
|
75
|
+
if milton_options[:storage] == :disk
|
|
76
|
+
# root of where the underlying files are stored (or will be stored)
|
|
77
|
+
# on the file system
|
|
78
|
+
milton_options[:storage_options][:root] ||= File.join(Rails.root, "public", table_name)
|
|
79
|
+
milton_options[:storage_options][:root] = File.expand_path(milton_options[:storage_options][:root])
|
|
80
|
+
# mode to set on stored files and created directories
|
|
81
|
+
milton_options[:storage_options][:chmod] ||= 0755
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
validates_presence_of :filename
|
|
85
|
+
|
|
86
|
+
after_destroy :destroy_attached_file
|
|
87
|
+
after_create :create_derivatives
|
|
88
|
+
|
|
89
|
+
include Milton::Attachment::InstanceMethods
|
|
90
|
+
|
|
91
|
+
if milton_options[:uploading]
|
|
92
|
+
require 'milton/uploading'
|
|
93
|
+
extend Milton::Uploading::ClassMethods
|
|
94
|
+
include Milton::Uploading::InstanceMethods
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# These get mixed in to your model when you use Milton
|
|
100
|
+
module InstanceMethods
|
|
101
|
+
# Sets the filename to the given filename (sanitizes the given filename
|
|
102
|
+
# as well)
|
|
103
|
+
#
|
|
104
|
+
# TODO: change the filename on the underlying file system on save so as
|
|
105
|
+
# not to orphan the file
|
|
106
|
+
def filename=(name)
|
|
107
|
+
write_attribute :filename, Storage::StoredFile.sanitize_filename(name, self.class.milton_options)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns the content_type of this attachment, tries to determine it if
|
|
111
|
+
# hasn't been determined yet or is not saved to the database
|
|
112
|
+
def content_type
|
|
113
|
+
return self[:content_type] unless self[:content_type].blank?
|
|
114
|
+
self.content_type = attached_file.mime_type
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Sets the content type to the given type
|
|
118
|
+
def content_type=(type)
|
|
119
|
+
write_attribute :content_type, type.to_s.strip
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Simple helper, same as path except returns the directory from
|
|
123
|
+
# .../public/ on, i.e. for showing images in your views.
|
|
124
|
+
#
|
|
125
|
+
# @asset.path => /var/www/site/public/assets/000/000/001/313/milton.jpg
|
|
126
|
+
# @asset.public_path => /assets/000/000/001/313/milton.jpg
|
|
127
|
+
#
|
|
128
|
+
# Can send a different base path than public if you want to give the
|
|
129
|
+
# path from that base on, useful if you change your root path to
|
|
130
|
+
# somewhere else.
|
|
131
|
+
def public_path(options={}, base='public')
|
|
132
|
+
path(options).gsub(/.*?\/#{base}/, '')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# The path to the file.
|
|
136
|
+
def path(options=nil)
|
|
137
|
+
return attached_file.path if options.nil?
|
|
138
|
+
process(options).path
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
protected
|
|
142
|
+
|
|
143
|
+
# Meant to be used as an after_create filter -- loops over all the
|
|
144
|
+
# recipes and processes them to create the derivatives.
|
|
145
|
+
def create_derivatives
|
|
146
|
+
milton_options[:recipes].each{ |name, recipe| process(name, true) } if milton_options[:recipes].any?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Process the given options to produce a final derivative. +options+
|
|
150
|
+
# takes a Hash of options to process or the name of a pre-defined
|
|
151
|
+
# recipe which will be looked up and processed.
|
|
152
|
+
#
|
|
153
|
+
# Pass +force = true+ to force processing regardless of if
|
|
154
|
+
# +:postprocessing+ is turned on or not.
|
|
155
|
+
#
|
|
156
|
+
# Returns the final Derivative of all processors in the recipe.
|
|
157
|
+
def process(options, force=false)
|
|
158
|
+
options = milton_options[:recipes][options] unless options.is_a?(Hash)
|
|
159
|
+
options = [options] unless options.is_a?(Array)
|
|
160
|
+
|
|
161
|
+
source = attached_file
|
|
162
|
+
options.each do |recipe|
|
|
163
|
+
recipe.each do |processor, opts|
|
|
164
|
+
source = Derivative.factory(processor, source, opts, self.class.milton_options).process_if(process? || force).file
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
source
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns true if derivaties of the attachment should be processed,
|
|
171
|
+
# returns false if no processing should be done when a derivative is
|
|
172
|
+
# requested.
|
|
173
|
+
#
|
|
174
|
+
# No processing also means the derivative won't be checked for
|
|
175
|
+
# existance (since that can be slow) so w/o postprocessing things will
|
|
176
|
+
# be much faster but #path will happily return the paths to Derivatives
|
|
177
|
+
# which don't exist.
|
|
178
|
+
#
|
|
179
|
+
# It is highly recommended that you turn +:postprocessing+ off for
|
|
180
|
+
# anything but prototyping, and instead use recipes and refer to them
|
|
181
|
+
# via #path. +:postprocessing+ relies on checking for existance which
|
|
182
|
+
# will kill any real application.
|
|
183
|
+
def process?
|
|
184
|
+
self.class.milton_options[:postprocessing]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# A reference to the attached file, this is probably what you want to
|
|
188
|
+
# overwrite to introduce a new behavior
|
|
189
|
+
#
|
|
190
|
+
# i.e.
|
|
191
|
+
# have attached_file return a ResizeableFile, or a TranscodableFile
|
|
192
|
+
def attached_file
|
|
193
|
+
@attached_file ||= Storage::StoredFile.adapter(self.class.milton_options[:storage]).new(filename, id, self.class.milton_options)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Clean the file from the filesystem
|
|
197
|
+
def destroy_attached_file
|
|
198
|
+
attached_file.destroy
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'mimetype_fu'
|
|
3
|
+
rescue MissingSourceFile
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module Milton
|
|
7
|
+
class File < ::File
|
|
8
|
+
class << self
|
|
9
|
+
def extension(filename)
|
|
10
|
+
extension = extname(filename)
|
|
11
|
+
extension.slice(1, extension.length-1)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# File respond_to?(:mime_type) is true if mimetype_fu is installed, so
|
|
15
|
+
# this way we always have File.mime_type? available but it favors
|
|
16
|
+
# mimetype_fu's implementation.
|
|
17
|
+
def mime_type?(file)
|
|
18
|
+
::File.respond_to?(:mime_type?) ? super(file.filename) : file.content_type
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'tempfile'
|
|
2
|
+
|
|
3
|
+
module Milton
|
|
4
|
+
# For lack of a better name, a MiltonTempfile adds some helpful
|
|
5
|
+
# functionality to Ruby's Tempfile
|
|
6
|
+
class Tempfile < ::Tempfile
|
|
7
|
+
class << self
|
|
8
|
+
def create(data_or_path, tempfile_path)
|
|
9
|
+
FileUtils.mkdir_p(tempfile_path) unless File.exists?(tempfile_path)
|
|
10
|
+
|
|
11
|
+
tempfile = new(basename, tempfile_path)
|
|
12
|
+
|
|
13
|
+
if data_or_path.is_a?(StringIO)
|
|
14
|
+
tempfile.binmode
|
|
15
|
+
tempfile.write data_or_path.read
|
|
16
|
+
tempfile.close
|
|
17
|
+
else
|
|
18
|
+
tempfile.close
|
|
19
|
+
FileUtils.cp((data_or_path.respond_to?(:path) ? data_or_path.path : data_or_path), tempfile.path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
tempfile
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def basename
|
|
26
|
+
"#{rand(Time.now.to_i)}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def filename(extension)
|
|
30
|
+
"#{basename}.#{extension}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def path(tempfile_path, extension)
|
|
34
|
+
File.join(tempfile_path, filename(extension))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Simple helper that returns a path to a tempfile with a uniquely
|
|
38
|
+
# generated basename and same extension as the given source.
|
|
39
|
+
def from(source)
|
|
40
|
+
filename(Milton::File.extension(source))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module Milton
|
|
2
|
+
# Represents a file created on the file system that is a derivative of the
|
|
3
|
+
# one referenced by the model, i.e. a thumbnail of an image, or a transcode
|
|
4
|
+
# of a video.
|
|
5
|
+
#
|
|
6
|
+
# Provides a container for options and a uniform API to dealing with
|
|
7
|
+
# passing options for the creation of derivatives.
|
|
8
|
+
#
|
|
9
|
+
# Files created as derivatives have their creation options appended into
|
|
10
|
+
# their filenames so it can be checked later if a file w/ the given
|
|
11
|
+
# options already exists (so as not to create it again).
|
|
12
|
+
#
|
|
13
|
+
class Derivative
|
|
14
|
+
class << self
|
|
15
|
+
# Given a string of attachment options, splits them out into a hash,
|
|
16
|
+
# useful for things that take options on the query string or from
|
|
17
|
+
# filenames
|
|
18
|
+
def options_from(string)
|
|
19
|
+
Hash[*(string.split('_').collect { |option|
|
|
20
|
+
key, value = option.split('=')
|
|
21
|
+
[ key.to_sym, value || true ] # nothing on RHS of = means it's a boolean true
|
|
22
|
+
}).flatten]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Given a filename (presumably with options embedded in it) parses out
|
|
26
|
+
# the options and returns them as a hash.
|
|
27
|
+
def extract_options_from(filename, options)
|
|
28
|
+
File.basename(filename, File.extname(filename))[(filename.rindex(options[:separator]) + 1)..-1]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Creates a new Derivative from the given filename by extracting the
|
|
32
|
+
# options.
|
|
33
|
+
def from_filename(filename)
|
|
34
|
+
Derivative.new(filename, options_from(extract_options_from(filename)))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def process(source, options={}, settings={})
|
|
38
|
+
returning(derivative = new(source, options, settings)) do
|
|
39
|
+
derivative.process unless derivative.exists?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def factory(type, source, options={}, settings={})
|
|
44
|
+
begin
|
|
45
|
+
klass = "Milton::#{type.to_s.classify}".constantize
|
|
46
|
+
rescue NameError
|
|
47
|
+
begin
|
|
48
|
+
require "milton/derivatives/#{type.to_s}"
|
|
49
|
+
rescue MissingSourceFile => e
|
|
50
|
+
raise MissingSourceFile.new("#{e.message} (milton: couldn't find the processor '#{type}' you were trying to load)", e.path)
|
|
51
|
+
end
|
|
52
|
+
klass = "Milton::#{type.to_s.classify}".constantize
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
klass.new(source, options, settings)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
attr_reader :options, :settings
|
|
60
|
+
|
|
61
|
+
# Instantiate a new Derivative:
|
|
62
|
+
# * +source+: a reference to the Storage::StoredFile this will be a Derivative of
|
|
63
|
+
# * +options+: options to generate the Derivative using
|
|
64
|
+
# * +settings+: settings about how to create Derivatives
|
|
65
|
+
def initialize(source, options={}, settings={})
|
|
66
|
+
@source = source
|
|
67
|
+
@options = options.is_a?(String) ? self.class.options_from(options) : options
|
|
68
|
+
@settings = settings
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The resulting filename of this Derivative with embedded options.
|
|
72
|
+
def filename
|
|
73
|
+
# ignore false booleans and don't output =true for true booleans,
|
|
74
|
+
# otherwise just k=v
|
|
75
|
+
append = options.reject{ |k, v| v.is_a?(FalseClass) }.collect { |k, v| v === true ? k.to_s : "#{k}=#{v}" }.sort.join('_')
|
|
76
|
+
extension = File.extname(@source.path)
|
|
77
|
+
File.basename(@source.path, extension) + (append.blank? ? '' : "#{settings[:separator]}#{append}") + extension
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The full path and filename to this Derivative.
|
|
81
|
+
def path
|
|
82
|
+
file.path
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns true if this Derivative has already been generated and stored.
|
|
86
|
+
def exists?
|
|
87
|
+
file.exists?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Overwrite this to provide your derivatives processing.
|
|
91
|
+
def process;end;
|
|
92
|
+
|
|
93
|
+
# Convenience method, only runs process if the given condition is true.
|
|
94
|
+
# Returns the derivative so it's chainable.
|
|
95
|
+
def process_if(condition)
|
|
96
|
+
process if condition && !exists?
|
|
97
|
+
return self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns the StoredFile which represents the Derivative (which is a copy
|
|
101
|
+
# of the source w/ a different filename).
|
|
102
|
+
def file
|
|
103
|
+
@file ||= @source.clone(filename)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'milton/derivatives/thumbnail/image'
|
|
2
|
+
require 'milton/derivatives/thumbnail/crop_calculator'
|
|
3
|
+
|
|
4
|
+
module Milton
|
|
5
|
+
class Thumbnail < Derivative
|
|
6
|
+
def process
|
|
7
|
+
raise "target size must be specified for resizing" unless options.has_key?(:size)
|
|
8
|
+
|
|
9
|
+
temp_dst = File.join(settings[:tempfile_path], Milton::Tempfile.from(@source.filename))
|
|
10
|
+
temp_src = File.join(settings[:tempfile_path], Milton::Tempfile.from(@source.filename))
|
|
11
|
+
|
|
12
|
+
@source.copy(temp_src)
|
|
13
|
+
image = Image.from_path(temp_src)
|
|
14
|
+
|
|
15
|
+
# TODO: this really only makes sense for processing recipes, reimplement
|
|
16
|
+
# once it's setup to build all derivatives then push to storage
|
|
17
|
+
#
|
|
18
|
+
# For speed, any derivatives less than 640-wide are made from a
|
|
19
|
+
# 640-wide version of the image (so you're not generating tiny
|
|
20
|
+
# thumbnails from an 8-megapixel upload)
|
|
21
|
+
# source = if image.width > 640 && Image.from_geometry(options[:size]).width < 640
|
|
22
|
+
# Thumbnail.process(@source, { :size => '640x' }, settings).file
|
|
23
|
+
# else
|
|
24
|
+
# @source
|
|
25
|
+
# end
|
|
26
|
+
|
|
27
|
+
if options[:crop]
|
|
28
|
+
crop = CropCalculator.new(image, Image.from_geometry(options[:size]))
|
|
29
|
+
size = crop.resizing_geometry
|
|
30
|
+
conversion_options = %Q(-gravity #{crop.gravity} -crop #{crop.cropping_geometry})
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Milton.syscall!(%Q{convert #{temp_src} -geometry #{size || options[:size]} #{conversion_options || ''} +repage "#{temp_dst}"})
|
|
34
|
+
|
|
35
|
+
# TODO: raise if the store fails
|
|
36
|
+
file.store(temp_dst)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Milton
|
|
2
|
+
class CropCalculator
|
|
3
|
+
attr_reader :original, :target
|
|
4
|
+
|
|
5
|
+
# Initializes a new CropCalculator with the two given Images.
|
|
6
|
+
#
|
|
7
|
+
# A CropCalculator is used to calculate the proper zoom/crop dimensions
|
|
8
|
+
# to be passed to ImageMagick's convert method in order to transform
|
|
9
|
+
# the original Image's dimensions into the target Image's dimensions
|
|
10
|
+
# with sensible zoom/cropping.
|
|
11
|
+
def initialize(original, target)
|
|
12
|
+
@original = original
|
|
13
|
+
@target = target
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the geometry string to send to ImageMagick's convert -resize
|
|
17
|
+
# argument -- that is, the dimensions that the original Image would
|
|
18
|
+
# need to be resized to in order to result in the given target Image's
|
|
19
|
+
# dimensions with cropping.
|
|
20
|
+
def resizing_geometry
|
|
21
|
+
case
|
|
22
|
+
when original.wider? then "#{resized_width}x#{target.height}"
|
|
23
|
+
when original.square? && target.wider? then "#{target.width}x#{resized_height}"
|
|
24
|
+
when original.square? && !target.wider? then "#{resized_width}x#{target.height}"
|
|
25
|
+
else "#{target.width}x#{resized_height}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The geometry string to send to ImageMagick's convert -crop argument.
|
|
30
|
+
def cropping_geometry
|
|
31
|
+
"#{target.width}x#{target.height}+0+0"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The gravity to use for cropping.
|
|
35
|
+
def gravity
|
|
36
|
+
original.wider? ? "center" : "north"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def resized_width
|
|
42
|
+
(target.height * original.width / original.height).to_i
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def resized_height
|
|
46
|
+
(target.width * original.height / original.width).to_i
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# TODO: this is the old-school cropping w/ coords, need to implement
|
|
50
|
+
# cropping w/ coords using the new system calls
|
|
51
|
+
# def crop_with_coordinates(img, x, y, size, options={})
|
|
52
|
+
# gravity = options[:gravity] || Magick::NorthGravity
|
|
53
|
+
# cropped_img = nil
|
|
54
|
+
# img = Magick::Image.read(img).first unless img.is_a?(Magick::Image)
|
|
55
|
+
# szx, szy = img.columns, img.rows
|
|
56
|
+
# sz = self.class.get_size_from_parameter(size)
|
|
57
|
+
# # logger.info "crop_with_coordinates: img.crop!(#{x}, #{y}, #{sz[0]}, #{sz[1]}, true)"
|
|
58
|
+
# # cropped_img = img.resize!(sz[0], sz[1]) # EEEEEK
|
|
59
|
+
# cropped_img = img.crop!(x, y, szx, szy, true)
|
|
60
|
+
# cropped_img.crop_resized!(sz[0], sz[1], gravity) # EEEEEK
|
|
61
|
+
# self.temp_path = write_to_temp_file(cropped_img.to_blob)
|
|
62
|
+
# end
|
|
63
|
+
end
|
|
64
|
+
end
|