insano_image_resizer 0.5.7 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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
- ![A brief overview of the Insano processing function](insano_image_resizer/raw/master/samples/explanation.png)
10
+ ![A brief overview of the Insano processing function](samples/explanation.png)
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 `process` method is the main function of the Insano gem. Using different parameters,
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
- Note that the output image may show more of the source image than you specify using the
65
- interest region. The region is only meant to indicate what region you'd like to ensure makes
66
- it into the output. For example, if you have a 200 x 200px image and request an output image of
67
- 100 x 100px, showing the 50px region around 150px x 150px, the output will contain more than
68
- just that region, since filling a 100px square with a 50px region would require enlarging the
69
- source image.
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 = {}, quality = 60)
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
- parts = line.run(:input => input_path).split(' ')
33
- [parts[0].to_i, parts[1].to_i, parts[2], parts[2] == 'PNG' ? 'png' : 'jpg']
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 (shrink_factor == 1.0 / transform[:scale])
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
- if (input_path[-4..-3] != ".")
179
- FileUtils.mv(input_path, input_path+"."+output_extension)
180
- input_path = input_path + "." + output_extension
181
- end
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}#{quality_extension}",
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
- # find the EXIF values
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
- FileUtils.rm(intermediate_path)
237
- line = Cocaine::CommandLine.new(mogrify, "-strip :output")
238
- line.run(:output => output_path,
239
- :intermediate_path => intermediate_path)
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
- FileUtils.rm(input_path)
258
+ intermediate_path = "#{input_path}_rotated.jpg"
259
+ intermediate_quality_extension = ':90'
243
260
 
244
- output_path
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.5.7
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-01 00:00:00.000000000 Z
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: &2161837320 !ruby/object:Gem::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: *2161837320
24
+ version_requirements: *2164743100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &2161836240 !ruby/object:Gem::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: *2161836240
35
+ version_requirements: *2164742380
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: jeweler
38
- requirement: &2161834680 !ruby/object:Gem::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: *2161834680
46
+ version_requirements: *2164741660
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: pry
49
- requirement: &2161833680 !ruby/object:Gem::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: *2161833680
57
+ version_requirements: *2164740980
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: pry-nav
60
- requirement: &2161832100 !ruby/object:Gem::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: *2161832100
68
+ version_requirements: *2164740340
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: pry-stack_explorer
71
- requirement: &2161830740 !ruby/object:Gem::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: *2161830740
79
+ version_requirements: *2164739640
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: exifr
82
- requirement: &2161845740 !ruby/object:Gem::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: *2161845740
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: -2229782920587942478
124
+ hash: 1771295257992981064
125
125
  required_rubygems_version: !ruby/object:Gem::Requirement
126
126
  none: false
127
127
  requirements: