insano_image_resizer 0.5.7 → 0.7.1
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/README.md +21 -8
- data/lib/insano_image_resizer/processor.rb +73 -43
- metadata +17 -17
data/README.md
CHANGED
@@ -7,7 +7,7 @@ in the frame when possible. The resizer is built on top of the VIPS command-line
|
|
7
7
|
and is designed to be as fast as possible. In our tests, VIPS is faster than ImageMagick for any
|
8
8
|
image larger than ~300x300px, and is exponentially faster for very large images.
|
9
9
|
|
10
|
-

|
11
11
|
|
12
12
|
Output formats: The Insano image resizer will keep PNGs as PNG, but any other format is converted to JPEG
|
13
13
|
|
@@ -36,7 +36,7 @@ Example:
|
|
36
36
|
|
37
37
|
Input parameters:
|
38
38
|
|
39
|
-
The
|
39
|
+
The #process method is the main function of the Insano gem. Using different parameters,
|
40
40
|
you can produce a wide range of resized images. Each of the parameters is explained below.
|
41
41
|
|
42
42
|
The first argument is an input file path. Because the Insano image resizer uses the VIPS
|
@@ -50,6 +50,13 @@ the current width and height of the image. Note that the image resizer will
|
|
50
50
|
never distort an image: the output image will always fill the viewport you provide,
|
51
51
|
scaling up only if absolutely necessary.
|
52
52
|
|
53
|
+
Note that the output image may show more of the source image than you specify using the
|
54
|
+
viewport. The region is only meant to indicate what region you'd like to ensure makes
|
55
|
+
it into the output. For example, if you have a 200 x 200px image and request an output image of
|
56
|
+
100 x 100px, showing the 50px region around 150px x 150px, the output will contain more than
|
57
|
+
just that region, since filling a 100px square with a 50px region would require enlarging the
|
58
|
+
source image.
|
59
|
+
|
53
60
|
The third parameter is the point of interest that you'd like to keep centered if possible.
|
54
61
|
Imagine that an 4:3 image contains a person's face on the left side. When you create a
|
55
62
|
square thumbnail of the image, the persons face is half chopped off, because the processor
|
@@ -61,12 +68,18 @@ should be cropped off. However, specifying the optional :region parameter with a
|
|
61
68
|
less than 1, you can make the image resizer zoom in around the POI, cropping the image
|
62
69
|
so that an area of size (region * image size) around the POI is visible.
|
63
70
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
71
|
+
The last optional parameter determines quality. The #process method accepts either a hash or an
|
72
|
+
integer. If an integer is provided, then that number will be used when generating all JPEG files.
|
73
|
+
A hash of the following form may be used instead:
|
74
|
+
|
75
|
+
{ :min_area => { :area => 4000, :quality => 90 },
|
76
|
+
:max_area => { :area => 1000000, :quality => 60 }}
|
77
|
+
|
78
|
+
When a hash is used, images up to the minimum area use the min_area quality. Images at or above the
|
79
|
+
maximum area use the max_area quality. Images in between get a quality based on a sliding scale
|
80
|
+
between the min and max. This is the default behavior.
|
81
|
+
|
82
|
+
Insano accounts for EXIF rotation flags, producing properly oriented images across all devices.
|
70
83
|
|
71
84
|
Credits
|
72
85
|
=======
|
@@ -9,28 +9,54 @@ module InsanoImageResizer
|
|
9
9
|
include Loggable
|
10
10
|
include Cocaine
|
11
11
|
|
12
|
+
DEFAULT_QUALITY_LIMITS = { :min_area => { :area => 4000, :quality => 90 },
|
13
|
+
:max_area => { :area => 1000000, :quality => 60 }}
|
14
|
+
|
12
15
|
def initialize(options = {})
|
13
16
|
@vips_path = options[:vips_path] || 'vips'
|
14
17
|
@identify_path = options[:identify_path] || 'identify'
|
15
18
|
end
|
16
19
|
|
17
|
-
def process(input_path, viewport_size = {}, interest_point = {},
|
20
|
+
def process(input_path, viewport_size = {}, interest_point = {}, quality_limits=DEFAULT_QUALITY_LIMITS)
|
18
21
|
width, height, original_format, target_extension = fetch_image_properties(input_path)
|
19
22
|
|
23
|
+
exif_result = handle_exif_rotation(input_path, original_format)
|
24
|
+
width, height = [height, width] if exif_result == :swap_dimensions
|
25
|
+
|
20
26
|
output_tmp = Tempfile.new(['img', ".#{target_extension}"])
|
21
27
|
|
22
28
|
transform = calculate_transform(input_path, width, height, viewport_size, interest_point)
|
29
|
+
quality = target_jpg_quality(transform[:w], transform[:h], quality_limits) if target_extension == 'jpg'
|
23
30
|
run_transform(input_path, output_tmp.path, transform, original_format, target_extension, quality)
|
24
31
|
|
25
32
|
output_tmp.path
|
26
33
|
end
|
27
34
|
|
35
|
+
# limits is of the form:
|
36
|
+
# { :min_area => { :area => 4000, :quality => 90 },
|
37
|
+
# :max_area => { :area => 1000000, :quality => 60 }}
|
38
|
+
def target_jpg_quality(width, height, limits)
|
39
|
+
return limits.to_i unless limits.is_a?(Hash)
|
40
|
+
min_area = limits[:min_area][:area]
|
41
|
+
min_area_quality = limits[:min_area][:quality]
|
42
|
+
max_area = limits[:max_area][:area]
|
43
|
+
max_area_quality = limits[:max_area][:quality]
|
44
|
+
normalized_target_area = [width * height - min_area, 0].max
|
45
|
+
normalized_max_area = max_area - min_area
|
46
|
+
target_area_fraction = [normalized_target_area.to_f / normalized_max_area, 1].min
|
47
|
+
quality_span = min_area_quality - max_area_quality
|
48
|
+
quality_fraction = quality_span * target_area_fraction
|
49
|
+
min_area_quality - quality_fraction
|
50
|
+
end
|
51
|
+
|
28
52
|
private
|
29
53
|
|
30
54
|
def fetch_image_properties(input_path)
|
31
55
|
line = Cocaine::CommandLine.new(@identify_path, '-format "%w %h %m" :input')
|
32
|
-
|
33
|
-
|
56
|
+
width, height, original_format = line.run(:input => input_path).split(' ')
|
57
|
+
|
58
|
+
target_extension = (original_format == 'PNG' ? 'png' : 'jpg')
|
59
|
+
[width.to_i, height.to_i, original_format, target_extension]
|
34
60
|
end
|
35
61
|
|
36
62
|
def calculate_transform(input_path, width, height, viewport_size, interest_point)
|
@@ -157,8 +183,7 @@ module InsanoImageResizer
|
|
157
183
|
# but don't seem to be used.
|
158
184
|
# The last four params define a rect of the source image that is transformed.
|
159
185
|
|
160
|
-
quality_extension = ''
|
161
|
-
quality_extension = ":#{quality}" if output_extension == 'jpg'
|
186
|
+
quality_extension = quality ? ":#{quality}" : ''
|
162
187
|
|
163
188
|
if (transform[:scale] < 0.5)
|
164
189
|
# If we're shrinking the image by more than a factor of two, let's do a two-pass operation. The reason we do this
|
@@ -169,21 +194,18 @@ module InsanoImageResizer
|
|
169
194
|
|
170
195
|
# To ensure that we actually do both passes, don't let the im_shrink go all the way. This will result in terrible
|
171
196
|
# looking shrunken images, since im_shrink basically just cuts pixels out.
|
172
|
-
if
|
173
|
-
shrink_factor -= 1
|
174
|
-
end
|
197
|
+
shrink_factor -= 1 if shrink_factor == 1.0 / transform[:scale]
|
175
198
|
|
176
199
|
transform[:scale] *= shrink_factor
|
177
200
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
intermediate_path = input_path[0..-4]+"_shrunk." + output_extension
|
201
|
+
|
202
|
+
intermediate_path = "#{input_path}_shrunk.#{output_extension}"
|
203
|
+
intermediate_quality_extension = quality ? ":90" : ''
|
204
|
+
|
183
205
|
|
184
206
|
line = Cocaine::CommandLine.new(@vips_path, "im_shrink :input :intermediate_path :shrink_factor :shrink_factor")
|
185
207
|
line.run(:input => input_path,
|
186
|
-
:intermediate_path => "#{intermediate_path}#{
|
208
|
+
:intermediate_path => "#{intermediate_path}#{intermediate_quality_extension}",
|
187
209
|
:shrink_factor => shrink_factor.to_s)
|
188
210
|
|
189
211
|
|
@@ -209,40 +231,48 @@ module InsanoImageResizer
|
|
209
231
|
:h => transform[:h].to_s)
|
210
232
|
end
|
211
233
|
|
212
|
-
|
213
|
-
orientation = 0
|
214
|
-
orientation = EXIFR::JPEG.new(input_path).orientation.to_i if original_format == 'JPEG'
|
215
|
-
|
216
|
-
if orientation == 3 || orientation == 6 || orientation == 8
|
217
|
-
FileUtils.mv(output_path, intermediate_path)
|
218
|
-
o_transform = []
|
219
|
-
if orientation == 3
|
220
|
-
command = 'im_rot180'
|
221
|
-
elsif orientation == 6
|
222
|
-
command = 'im_rot90'
|
223
|
-
elsif orientation == 8
|
224
|
-
command = 'im_rot270'
|
225
|
-
else
|
226
|
-
command = nil
|
227
|
-
end
|
228
|
-
|
229
|
-
if command
|
230
|
-
line = Cocaine::CommandLine.new(@vips_path, ":command :intermediate_path :output")
|
231
|
-
line.run(:output => "#{output_path}#{quality_extension}",
|
232
|
-
:intermediate_path => intermediate_path,
|
233
|
-
:command => command)
|
234
|
-
end
|
234
|
+
FileUtils.rm(input_path)
|
235
235
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
236
|
+
|
237
|
+
output_path
|
238
|
+
end
|
239
|
+
|
240
|
+
def handle_exif_rotation(input_path, original_format)
|
241
|
+
return unless original_format == 'JPEG'
|
242
|
+
|
243
|
+
# find the EXIF values
|
244
|
+
orientation = EXIFR::JPEG.new(input_path).orientation.to_i
|
245
|
+
|
246
|
+
if orientation == 3
|
247
|
+
command = 'im_rot180'
|
248
|
+
elsif orientation == 6
|
249
|
+
command = 'im_rot90'
|
250
|
+
return_value = :swap_dimensions
|
251
|
+
elsif orientation == 8
|
252
|
+
command = 'im_rot270'
|
253
|
+
return_value = :swap_dimensions
|
254
|
+
else
|
255
|
+
return
|
240
256
|
end
|
241
257
|
|
242
|
-
|
258
|
+
intermediate_path = "#{input_path}_rotated.jpg"
|
259
|
+
intermediate_quality_extension = ':90'
|
243
260
|
|
244
|
-
|
261
|
+
line = Cocaine::CommandLine.new(@vips_path, ":command :input :intermediate_path")
|
262
|
+
line.run(:input => input_path,
|
263
|
+
:intermediate_path => "#{intermediate_path}#{intermediate_quality_extension}",
|
264
|
+
:command => command)
|
265
|
+
|
266
|
+
# mogrify strips the EXIF tags so that browsers that support EXIF don't rotate again after
|
267
|
+
# we have rotated
|
268
|
+
line = Cocaine::CommandLine.new('mogrify', "-strip :intermediate_path")
|
269
|
+
line.run(:intermediate_path => intermediate_path)
|
270
|
+
|
271
|
+
FileUtils.mv(intermediate_path, input_path)
|
272
|
+
|
273
|
+
return_value
|
245
274
|
end
|
275
|
+
|
246
276
|
end
|
247
277
|
end
|
248
278
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: insano_image_resizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cocaine
|
16
|
-
requirement: &
|
16
|
+
requirement: &2164743100 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2164743100
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2164742380 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2164742380
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: jeweler
|
38
|
-
requirement: &
|
38
|
+
requirement: &2164741660 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2164741660
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: pry
|
49
|
-
requirement: &
|
49
|
+
requirement: &2164740980 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2164740980
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: pry-nav
|
60
|
-
requirement: &
|
60
|
+
requirement: &2164740340 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *2164740340
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: pry-stack_explorer
|
71
|
-
requirement: &
|
71
|
+
requirement: &2164739640 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *2164739640
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: exifr
|
82
|
-
requirement: &
|
82
|
+
requirement: &2164737920 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *2164737920
|
91
91
|
description: ! "An image resizing gem that generates smaller versions of requested
|
92
92
|
\n images. Calls through to VIPS on the command line to perform
|
93
93
|
processing,\n and automatically handles cropping and scaling
|
@@ -121,7 +121,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
121
|
version: '0'
|
122
122
|
segments:
|
123
123
|
- 0
|
124
|
-
hash:
|
124
|
+
hash: 1771295257992981064
|
125
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
126
|
none: false
|
127
127
|
requirements:
|