mustachio 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -5
- data/Gemfile.lock +51 -42
- data/README.md +2 -2
- data/VERSION +1 -1
- data/bookmarklet.js +2 -2
- data/lib/mustachio.rb +78 -81
- data/lib/mustachio/app.rb +8 -1
- data/{public → lib/mustachio/public}/favicon.ico +0 -0
- data/{public → lib/mustachio/public}/images/guy_hecker.jpeg +0 -0
- data/{public → lib/mustachio/public}/images/staches/colonel_mustard.png +0 -0
- data/{public → lib/mustachio/public}/images/staches/mustache_03.png +0 -0
- data/{public → lib/mustachio/public}/images/staches/mustache_03.svg +0 -0
- data/{public → lib/mustachio/public}/images/staches/mustache_07.png +0 -0
- data/lib/mustachio/shortcuts.rb +125 -0
- data/{views → lib/mustachio/views}/face_api_dev_challenge.haml +0 -0
- data/{views → lib/mustachio/views}/ga.haml +0 -0
- data/{views → lib/mustachio/views}/gallery.haml +0 -0
- data/lib/mustachio/views/index.haml +35 -0
- data/{views → lib/mustachio/views}/test.haml +0 -0
- data/mustachio.gemspec +34 -35
- data/spec/spec_helper.rb +30 -0
- data/spec/unit/analyser_spec.rb +23 -36
- data/spec/unit/job_spec.rb +13 -0
- data/spec/unit/magickly_spec.rb +33 -0
- metadata +144 -228
- data/public/images/dubya.jpeg +0 -0
- data/views/index.haml +0 -10
data/Gemfile
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
source 'http://rubygems.org'
|
2
2
|
|
3
|
-
gem 'sinatra', '~> 1.2
|
3
|
+
gem 'sinatra', '~> 1.2', :require => 'sinatra/base'
|
4
4
|
gem 'dragonfly', '~> 0.9.0'
|
5
|
-
gem 'magickly', '~> 1.
|
5
|
+
gem 'magickly', '~> 1.2'
|
6
|
+
|
6
7
|
gem 'addressable', '~> 2.2.4', :require => 'addressable/uri'
|
7
|
-
gem 'haml'
|
8
|
+
gem 'haml', '~> 3.0'
|
8
9
|
|
9
10
|
gem 'face', '0.0.4'
|
10
11
|
gem 'imagesize', '~> 0.1', :require => 'image_size'
|
11
12
|
|
12
13
|
group :development do
|
13
|
-
# rake 0.9.0 doesn't work with Jeweler 1.6.0
|
14
|
-
gem 'rake', '~> 0.8.7'
|
15
14
|
gem 'jeweler', '~> 1.6'
|
16
15
|
end
|
17
16
|
|
data/Gemfile.lock
CHANGED
@@ -1,59 +1,68 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (3.
|
4
|
+
activesupport (3.1.3)
|
5
|
+
multi_json (~> 1.0)
|
5
6
|
addressable (2.2.6)
|
6
7
|
archive-tar-minitar (0.5.2)
|
7
|
-
columnize (0.3.
|
8
|
-
crack (0.1
|
9
|
-
diff-lcs (1.1.
|
10
|
-
dragonfly (0.9.
|
8
|
+
columnize (0.3.6)
|
9
|
+
crack (0.3.1)
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
dragonfly (0.9.8)
|
11
12
|
rack
|
12
13
|
face (0.0.4)
|
13
14
|
json (>= 1.4.6)
|
14
15
|
rest-client (>= 1.6.1)
|
15
16
|
git (1.2.5)
|
16
|
-
haml (3.1.
|
17
|
-
httparty (0.
|
18
|
-
|
17
|
+
haml (3.1.4)
|
18
|
+
httparty (0.8.1)
|
19
|
+
multi_json
|
20
|
+
multi_xml
|
19
21
|
imagesize (0.1.1)
|
20
|
-
jeweler (1.6.
|
21
|
-
bundler (~> 1.0
|
22
|
+
jeweler (1.6.4)
|
23
|
+
bundler (~> 1.0)
|
22
24
|
git (>= 1.2.5)
|
23
25
|
rake
|
24
|
-
json (1.
|
25
|
-
linecache (0.
|
26
|
-
|
26
|
+
json (1.6.3)
|
27
|
+
linecache (0.46)
|
28
|
+
rbx-require-relative (> 0.0.4)
|
29
|
+
linecache19 (0.5.13)
|
27
30
|
ruby_core_source (>= 0.1.4)
|
28
|
-
magickly (1.1
|
31
|
+
magickly (1.2.1)
|
29
32
|
activesupport (>= 2.0.0)
|
30
33
|
addressable (~> 2.2)
|
31
|
-
dragonfly (~> 0.9.
|
34
|
+
dragonfly (~> 0.9.5)
|
32
35
|
haml (~> 3.0)
|
33
|
-
httparty (~> 0.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
httparty (~> 0.8.1)
|
37
|
+
json (~> 1.5)
|
38
|
+
sinatra (~> 1.2)
|
39
|
+
mime-types (1.17.2)
|
40
|
+
multi_json (1.0.4)
|
41
|
+
multi_xml (0.4.1)
|
42
|
+
newrelic_rpm (3.3.1)
|
43
|
+
rack (1.3.5)
|
44
|
+
rack-protection (1.1.4)
|
45
|
+
rack
|
46
|
+
rack-test (0.6.1)
|
39
47
|
rack (>= 1.0)
|
40
|
-
rake (0.
|
41
|
-
|
48
|
+
rake (0.9.2.2)
|
49
|
+
rbx-require-relative (0.0.5)
|
50
|
+
rest-client (1.6.7)
|
42
51
|
mime-types (>= 1.16)
|
43
|
-
rspec (2.
|
44
|
-
rspec-core (~> 2.
|
45
|
-
rspec-expectations (~> 2.
|
46
|
-
rspec-mocks (~> 2.
|
47
|
-
rspec-core (2.
|
48
|
-
rspec-expectations (2.
|
52
|
+
rspec (2.7.0)
|
53
|
+
rspec-core (~> 2.7.0)
|
54
|
+
rspec-expectations (~> 2.7.0)
|
55
|
+
rspec-mocks (~> 2.7.0)
|
56
|
+
rspec-core (2.7.1)
|
57
|
+
rspec-expectations (2.7.0)
|
49
58
|
diff-lcs (~> 1.1.2)
|
50
|
-
rspec-mocks (2.
|
59
|
+
rspec-mocks (2.7.0)
|
51
60
|
ruby-debug (0.10.4)
|
52
61
|
columnize (>= 0.1)
|
53
62
|
ruby-debug-base (~> 0.10.4.0)
|
54
63
|
ruby-debug-base (0.10.4)
|
55
64
|
linecache (>= 0.3)
|
56
|
-
ruby-debug-base19 (0.11.
|
65
|
+
ruby-debug-base19 (0.11.26)
|
57
66
|
columnize (>= 0.3.1)
|
58
67
|
linecache19 (>= 0.5.11)
|
59
68
|
ruby_core_source (>= 0.1.4)
|
@@ -63,13 +72,14 @@ GEM
|
|
63
72
|
ruby-debug-base19 (>= 0.11.19)
|
64
73
|
ruby_core_source (0.1.5)
|
65
74
|
archive-tar-minitar (>= 0.5.2)
|
66
|
-
sinatra (1.
|
67
|
-
rack (~> 1.1)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
75
|
+
sinatra (1.3.1)
|
76
|
+
rack (~> 1.3, >= 1.3.4)
|
77
|
+
rack-protection (~> 1.1, >= 1.1.2)
|
78
|
+
tilt (~> 1.3, >= 1.3.3)
|
79
|
+
tilt (1.3.3)
|
80
|
+
vcr (1.11.3)
|
81
|
+
webmock (1.7.8)
|
82
|
+
addressable (~> 2.2, > 2.2.5)
|
73
83
|
crack (>= 0.1.7)
|
74
84
|
|
75
85
|
PLATFORMS
|
@@ -79,16 +89,15 @@ DEPENDENCIES
|
|
79
89
|
addressable (~> 2.2.4)
|
80
90
|
dragonfly (~> 0.9.0)
|
81
91
|
face (= 0.0.4)
|
82
|
-
haml
|
92
|
+
haml (~> 3.0)
|
83
93
|
imagesize (~> 0.1)
|
84
94
|
jeweler (~> 1.6)
|
85
|
-
magickly (~> 1.
|
95
|
+
magickly (~> 1.2)
|
86
96
|
newrelic_rpm
|
87
97
|
rack-test
|
88
|
-
rake (~> 0.8.7)
|
89
98
|
rspec (~> 2.5)
|
90
99
|
ruby-debug
|
91
100
|
ruby-debug19
|
92
|
-
sinatra (~> 1.2
|
101
|
+
sinatra (~> 1.2)
|
93
102
|
vcr (~> 1.9)
|
94
103
|
webmock (~> 1.6)
|
data/README.md
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
![the queen](http://
|
1
|
+
![the queen](http://mustachify.me/?src=http://www.librarising.com/astrology/celebs/images2/QR/queenelizabethii.jpg)
|
2
2
|
|
3
|
-
http://
|
3
|
+
http://mustachify.me/?src=YOUR-IMAGE-URL
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/bookmarklet.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
// original
|
2
2
|
javascript:(function(){
|
3
|
-
var URL = 'http://
|
3
|
+
var URL = 'http://mustachify.me/magickly?mustachify=true&src=';
|
4
4
|
var images = document.getElementsByTagName('img');
|
5
5
|
var i;
|
6
6
|
for (i = 0; i < images.length; i += 1){
|
@@ -15,4 +15,4 @@ javascript:(function(){
|
|
15
15
|
})();
|
16
16
|
|
17
17
|
// JSMin'd
|
18
|
-
javascript:(function(){var URL='http://
|
18
|
+
javascript:(function(){var URL='http://mustachify.me/magickly?mustachify=true&src=';var images=document.getElementsByTagName('img');var i;for(i=0;i<images.length;i+=1){var image=images[i];var src=image.getAttribute('src');if(image.src[0]==='/'){image.setAttribute('src',URL+encodeURIComponent(window.location.origin+src));}else if(src.match(/^https?:\/\//)){image.setAttribute('src',URL+encodeURIComponent(src));}}})();
|
data/lib/mustachio.rb
CHANGED
@@ -1,89 +1,11 @@
|
|
1
1
|
require 'magickly'
|
2
2
|
require 'image_size'
|
3
|
-
require '
|
3
|
+
require File.join(File.dirname(__FILE__), 'mustachio', 'shortcuts')
|
4
4
|
|
5
|
-
Magickly.dragonfly.configure do |c|
|
6
|
-
c.log_commands = true
|
7
|
-
|
8
|
-
c.analyser.add :face_data do |temp_object|
|
9
|
-
Mustachio.face_client.faces_detect(:file => temp_object.file, :attributes => 'none')['photos'].first
|
10
|
-
end
|
11
|
-
|
12
|
-
c.analyser.add :face_data_as_px do |temp_object|
|
13
|
-
data = Mustachio.face_client.faces_detect(:file => temp_object.file, :attributes => 'none')['photos'].first # TODO use #face_data
|
14
|
-
|
15
|
-
new_tags = []
|
16
|
-
data['tags'].map do |face|
|
17
|
-
has_all_attrs = Mustachio::FACE_POS_ATTRS.all? do |pos_attr|
|
18
|
-
if face[pos_attr]
|
19
|
-
face[pos_attr]['x'] *= (data['width'] / 100.0)
|
20
|
-
face[pos_attr]['y'] *= (data['height'] / 100.0)
|
21
|
-
true
|
22
|
-
else
|
23
|
-
# face attribute missing
|
24
|
-
false
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
new_tags << face if has_all_attrs
|
29
|
-
end
|
30
|
-
|
31
|
-
data['tags'] = new_tags
|
32
|
-
data
|
33
|
-
end
|
34
|
-
|
35
|
-
c.job :mustachify do |stache_num_param|
|
36
|
-
photo_data = @job.face_data_as_px
|
37
|
-
width = photo_data['width']
|
38
|
-
|
39
|
-
commands = ['-virtual-pixel transparent']
|
40
|
-
photo_data['tags'].each do |face|
|
41
|
-
stache_num = case stache_num_param
|
42
|
-
when true
|
43
|
-
0
|
44
|
-
when 'true'
|
45
|
-
0
|
46
|
-
when 'rand'
|
47
|
-
rand(Mustachio.mustaches.size)
|
48
|
-
else
|
49
|
-
stache_num_param.to_i
|
50
|
-
end
|
51
|
-
|
52
|
-
mustache = Mustachio.mustaches[stache_num]
|
53
|
-
|
54
|
-
# perform transform such that the mustache is the height
|
55
|
-
# of the upper lip, and the bottom-center of the stache
|
56
|
-
# is mapped to the center of the mouth
|
57
|
-
rotation = Math.atan(
|
58
|
-
( face['mouth_right']['y'] - face['mouth_left']['y'] ).to_f /
|
59
|
-
( face['mouth_right']['x'] - face['mouth_left']['x'] ).to_f
|
60
|
-
) / Math::PI * 180.0
|
61
|
-
desired_height = Math.sqrt(
|
62
|
-
( face['nose']['x'] - face['mouth_center']['x'] ).to_f**2 +
|
63
|
-
( face['nose']['y'] - face['mouth_center']['y'] ).to_f**2
|
64
|
-
)
|
65
|
-
mouth_intersect = mustache['height'] - mustache['mouth_overlap']
|
66
|
-
scale = desired_height / mouth_intersect
|
67
|
-
|
68
|
-
srt_params = [
|
69
|
-
[ mustache['width'] / 2.0, mouth_intersect - mustache['vert_offset'] ].map{|e| e.to_i }.join(','), # bottom-center of stache
|
70
|
-
scale, # scale
|
71
|
-
rotation, # rotate
|
72
|
-
[ face['mouth_center']['x'], face['mouth_center']['y'] ].map{|e| e.to_i }.join(',') # middle of mouth
|
73
|
-
]
|
74
|
-
srt_params_str = srt_params.join(' ')
|
75
|
-
|
76
|
-
commands << "\\( #{mustache['file_path']} +distort SRT '#{srt_params_str}' \\)"
|
77
|
-
end
|
78
|
-
commands << "-flatten"
|
79
|
-
|
80
|
-
command_str = commands.join(' ')
|
81
|
-
process :convert, command_str
|
82
|
-
end
|
83
|
-
end
|
84
5
|
|
85
6
|
module Mustachio
|
86
7
|
FACE_POS_ATTRS = ['center', 'eye_left', 'eye_right', 'mouth_left', 'mouth_center', 'mouth_right', 'nose']
|
8
|
+
FACE_SPAN_SCALE = 2.0
|
87
9
|
|
88
10
|
class << self
|
89
11
|
def face_client
|
@@ -105,7 +27,7 @@ module Mustachio
|
|
105
27
|
stache['vert_offset'] ||= 0
|
106
28
|
stache['mouth_overlap'] ||= 0
|
107
29
|
|
108
|
-
stache['file_path'] = File.expand_path(File.join(File.dirname(__FILE__), '
|
30
|
+
stache['file_path'] = File.expand_path(File.join(File.dirname(__FILE__), 'mustachio', 'public', 'images', 'staches', stache['filename']))
|
109
31
|
unless stache['width'] && stache['height']
|
110
32
|
stache['width'], stache['height'] = ImageSize.new(File.new(stache['file_path'])).get_size
|
111
33
|
end
|
@@ -113,6 +35,81 @@ module Mustachio
|
|
113
35
|
end
|
114
36
|
@@mustaches = staches
|
115
37
|
end
|
38
|
+
|
39
|
+
|
40
|
+
def face_data(file_or_job)
|
41
|
+
file = case file_or_job
|
42
|
+
when Dragonfly::Job
|
43
|
+
file_or_job.temp_object
|
44
|
+
when Dragonfly::TempObject
|
45
|
+
file_or_job.file
|
46
|
+
when File
|
47
|
+
file_or_job
|
48
|
+
else
|
49
|
+
raise ArgumentError, "A #{file_or_job.class} is not a valid argument for #face_data. Please provide a File or a Dragonfly::Job."
|
50
|
+
end
|
51
|
+
|
52
|
+
face_data = Mustachio.face_client.faces_detect(:file => file, :attributes => 'none')
|
53
|
+
face_data['photos'].first
|
54
|
+
end
|
55
|
+
|
56
|
+
def face_data_as_px(file_or_job, width=nil, height=nil)
|
57
|
+
data = self.face_data(file_or_job)
|
58
|
+
width ||= data['width']
|
59
|
+
height ||= data['height']
|
60
|
+
|
61
|
+
new_tags = []
|
62
|
+
data['tags'].map do |face|
|
63
|
+
has_all_attrs = FACE_POS_ATTRS.all? do |pos_attr|
|
64
|
+
if face[pos_attr]
|
65
|
+
face[pos_attr]['x'] *= (width / 100.0)
|
66
|
+
face[pos_attr]['y'] *= (height / 100.0)
|
67
|
+
true
|
68
|
+
else # face attribute missing
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
new_tags << face if has_all_attrs
|
74
|
+
end
|
75
|
+
|
76
|
+
data['tags'] = new_tags
|
77
|
+
data
|
78
|
+
end
|
79
|
+
|
80
|
+
def face_span(file_or_job)
|
81
|
+
face_data = self.face_data_as_px(file_or_job)
|
82
|
+
faces = face_data['tags']
|
83
|
+
|
84
|
+
left_face, right_face = faces.minmax_by{|face| face['center']['x'] }
|
85
|
+
top_face, bottom_face = faces.minmax_by{|face| face['center']['y'] }
|
86
|
+
|
87
|
+
top = top_face['eye_left']['y']
|
88
|
+
bottom = bottom_face['mouth_center']['y']
|
89
|
+
right = right_face['eye_right']['x']
|
90
|
+
left = left_face['eye_left']['x']
|
91
|
+
width = right - left
|
92
|
+
height = bottom - top
|
93
|
+
|
94
|
+
# compute adjusted values for padding around face span
|
95
|
+
adj_width = width * FACE_SPAN_SCALE
|
96
|
+
adj_height = height * FACE_SPAN_SCALE
|
97
|
+
adj_top = top - ((adj_height - height) / 2.0)
|
98
|
+
adj_bottom = bottom + ((adj_height - height) / 2.0)
|
99
|
+
adj_right = right + ((adj_width - width) / 2.0)
|
100
|
+
adj_left = left - ((adj_width - width) / 2.0)
|
101
|
+
|
102
|
+
{
|
103
|
+
:top => adj_top,
|
104
|
+
:bottom => adj_bottom,
|
105
|
+
:right => adj_right,
|
106
|
+
:left => adj_left,
|
107
|
+
:width => adj_width,
|
108
|
+
:height => adj_height,
|
109
|
+
:center_x => (adj_left + adj_right) / 2,
|
110
|
+
:center_y => (adj_top + adj_bottom) / 2
|
111
|
+
}
|
112
|
+
end
|
116
113
|
end
|
117
114
|
|
118
115
|
|
data/lib/mustachio/app.rb
CHANGED
@@ -6,12 +6,19 @@ module Mustachio
|
|
6
6
|
DEMO_IMAGE = 'http://www.librarising.com/astrology/celebs/images2/QR/queenelizabethii.jpg'
|
7
7
|
|
8
8
|
set :static, true
|
9
|
-
set :public, 'public'
|
10
9
|
|
11
10
|
configure :production do
|
12
11
|
require 'newrelic_rpm' if ENV['NEW_RELIC_ID']
|
13
12
|
end
|
14
13
|
|
14
|
+
before do
|
15
|
+
app_host = ENV['MUSTACHIO_APP_DOMAIN']
|
16
|
+
if app_host && request.host != app_host
|
17
|
+
request_host_with_port = request.env['HTTP_HOST']
|
18
|
+
redirect request.url.sub(request_host_with_port, app_host), 301
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
15
22
|
|
16
23
|
get %r{^/(\d+|rand)?$} do |stache_num|
|
17
24
|
src = params[:src]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'face'
|
2
|
+
|
3
|
+
Magickly.dragonfly.configure do |c|
|
4
|
+
# c.log_commands = true
|
5
|
+
|
6
|
+
c.analyser.add :face_data do |temp_object|
|
7
|
+
Mustachio.face_data(temp_object)
|
8
|
+
end
|
9
|
+
|
10
|
+
c.analyser.add :face_data_as_px do |temp_object, width, height|
|
11
|
+
Mustachio.face_data_as_px(temp_object, width, height)
|
12
|
+
end
|
13
|
+
|
14
|
+
c.analyser.add :face_span do |temp_object|
|
15
|
+
Mustachio.face_span(temp_object)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
c.job :mustachify do |stache_num_param|
|
21
|
+
width = @job.width
|
22
|
+
height = @job.height
|
23
|
+
# resize to smaller than 900px, because Face.com downsizes the image to this anyway
|
24
|
+
# TODO move resize inside of Mustachio.face_data
|
25
|
+
photo_data = @job.thumb('900x900>').face_data_as_px(width, height)
|
26
|
+
|
27
|
+
commands = ['-virtual-pixel transparent']
|
28
|
+
photo_data['tags'].each do |face|
|
29
|
+
stache_num = case stache_num_param
|
30
|
+
when true
|
31
|
+
0
|
32
|
+
when 'true'
|
33
|
+
0
|
34
|
+
when 'rand'
|
35
|
+
rand(Mustachio.mustaches.size)
|
36
|
+
else
|
37
|
+
stache_num_param.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
mustache = Mustachio.mustaches[stache_num]
|
41
|
+
|
42
|
+
# perform transform such that the mustache is the height
|
43
|
+
# of the upper lip, and the bottom-center of the stache
|
44
|
+
# is mapped to the center of the mouth
|
45
|
+
rotation = Math.atan(
|
46
|
+
( face['mouth_right']['y'] - face['mouth_left']['y'] ).to_f /
|
47
|
+
( face['mouth_right']['x'] - face['mouth_left']['x'] ).to_f
|
48
|
+
) / Math::PI * 180.0
|
49
|
+
desired_height = Math.sqrt(
|
50
|
+
( face['nose']['x'] - face['mouth_center']['x'] ).to_f**2 +
|
51
|
+
( face['nose']['y'] - face['mouth_center']['y'] ).to_f**2
|
52
|
+
)
|
53
|
+
mouth_intersect = mustache['height'] - mustache['mouth_overlap']
|
54
|
+
scale = desired_height / mouth_intersect
|
55
|
+
|
56
|
+
srt_params = [
|
57
|
+
[ mustache['width'] / 2.0, mouth_intersect - mustache['vert_offset'] ].map{|e| e.to_i }.join(','), # bottom-center of stache
|
58
|
+
scale, # scale
|
59
|
+
rotation, # rotate
|
60
|
+
[ face['mouth_center']['x'], face['mouth_center']['y'] ].map{|e| e.to_i }.join(',') # middle of mouth
|
61
|
+
]
|
62
|
+
srt_params_str = srt_params.join(' ')
|
63
|
+
|
64
|
+
commands << "\\( #{mustache['file_path']} +distort SRT '#{srt_params_str}' \\)"
|
65
|
+
end
|
66
|
+
commands << "-flatten"
|
67
|
+
|
68
|
+
command_str = commands.join(' ')
|
69
|
+
process :convert, command_str
|
70
|
+
end
|
71
|
+
|
72
|
+
c.job :crop_to_faces do |geometry|
|
73
|
+
thumb_width, thumb_height = geometry.split('x')
|
74
|
+
# raise ArgumentError
|
75
|
+
thumb_width = thumb_width.to_f
|
76
|
+
thumb_height = thumb_height.to_f
|
77
|
+
|
78
|
+
span = Mustachio.face_span(@job)
|
79
|
+
puts span.inspect
|
80
|
+
scale_x = thumb_width / span[:width]
|
81
|
+
scale_y = thumb_height / span[:height]
|
82
|
+
|
83
|
+
# TODO
|
84
|
+
# if thumb larger than span
|
85
|
+
# center span and crop
|
86
|
+
# else
|
87
|
+
# resize image so span is smaller than thumb, then crop
|
88
|
+
|
89
|
+
# center the span in the dimension with the smaller scale
|
90
|
+
if scale_x < scale_y
|
91
|
+
orig_height = @job.height
|
92
|
+
# check if image is tall enough for this scaling
|
93
|
+
if orig_height * scale_x >= thumb_height
|
94
|
+
@scale = scale_x
|
95
|
+
@offset_x = span[:left] * @scale
|
96
|
+
else
|
97
|
+
# image is too short - increase scale to fit height
|
98
|
+
@scale = thumb_height / orig_height.to_f
|
99
|
+
orig_width = @job.width
|
100
|
+
@offset_x = span[:left] * @scale + ((@scale - scale_x) * orig_width / 2.0)
|
101
|
+
end
|
102
|
+
|
103
|
+
@offset_y = (span[:center_y] * @scale) - (thumb_height / 2)
|
104
|
+
else
|
105
|
+
orig_width = @job.width
|
106
|
+
# check if image is wide enough for this scaling
|
107
|
+
if orig_width * scale_y >= thumb_width
|
108
|
+
@scale = scale_y
|
109
|
+
@offset_y = span[:top] * @scale
|
110
|
+
else
|
111
|
+
# image is too narrow - increase scale to fit width
|
112
|
+
@scale = thumb_width / orig_width.to_f
|
113
|
+
orig_height = @job.height
|
114
|
+
@offset_y = span[:top] * @scale + ((@scale - scale_y) * orig_height / 2.0)
|
115
|
+
end
|
116
|
+
|
117
|
+
@offset_x = (span[:center_x] * @scale) - (thumb_width / 2)
|
118
|
+
end
|
119
|
+
|
120
|
+
# round up, to ensure the scaled image fills the thumb area
|
121
|
+
percentage = (@scale * 100).ceil
|
122
|
+
|
123
|
+
process :convert, "-resize #{percentage}% -extent #{geometry}+#{@offset_x.to_i}+#{@offset_y.to_i}"
|
124
|
+
end
|
125
|
+
end
|