insano_image_resizer 0.3.3 → 0.3.4

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.
@@ -8,14 +8,14 @@ module InsanoImageResizer
8
8
  include Shell
9
9
  include Loggable
10
10
 
11
- def process(input_path, viewport_size = {}, interest_point = {})
11
+ def process(input_path, viewport_size = {}, interest_point = {}, quality = 60)
12
12
  input_properties = fetch_image_properties(input_path)
13
- input_has_alpha = (input_properties["bands"] == 4)
13
+ input_has_alpha = (input_properties[:bands] == 4)
14
14
 
15
15
  output_tmp = Tempfile.new(["img", input_has_alpha ? ".png" : ".jpg"])
16
16
 
17
17
  transform = calculate_transform(input_path, input_properties, viewport_size, interest_point)
18
- run_transform(input_path, output_tmp.path, transform)
18
+ run_transform(input_path, output_tmp.path, transform, quality)
19
19
 
20
20
  return output_tmp.path
21
21
  end
@@ -24,28 +24,24 @@ module InsanoImageResizer
24
24
  # read in the image headers to discover the width and height of the image.
25
25
  # There's actually some extra metadata we ignore here, but this seems to be
26
26
  # the only way to get width and height from VIPS.
27
- result = run("vips im_printdesc '#{input_path}'")
28
-
29
- # for some reason, the response isn't just YAML. It's one line of text in parenthesis
30
- # followed by YAML. Let's chop off the first line to get to the good stuff.
31
- result = result[(result.index(')') + 2)..-1]
32
- return YAML::load(result)
27
+ result = {}
28
+ result[:w] = run("vips im_header_int Xsize '#{input_path}'").to_f
29
+ result[:h] = run("vips im_header_int Ysize '#{input_path}'").to_f
30
+ result[:bands] = run("vips im_header_int Bands '#{input_path}'").to_f
31
+ return result
33
32
  end
34
33
 
35
34
  def calculate_transform(input_path, input_properties, viewport_size, interest_point)
36
35
 
37
- # Pull out the properties we're interested in
38
- image_size = {w: input_properties["width"].to_f, h: input_properties["height"].to_f}
39
-
40
36
  # By default, the interest size is 30% of the total image size.
41
37
  # In the future, this could be a parameter, and you'd pass the # of pixels around
42
38
  # the POI you are interested in.
43
39
  if (interest_point[:xf])
44
- interest_point[:x] = image_size[:w] * interest_point[:xf]
40
+ interest_point[:x] = input_properties[:w] * interest_point[:xf]
45
41
  end
46
42
 
47
43
  if (interest_point[:yf])
48
- interest_point[:y] = image_size[:h] * interest_point[:yf]
44
+ interest_point[:y] = input_properties[:h] * interest_point[:yf]
49
45
  end
50
46
 
51
47
  if (interest_point[:region] == nil)
@@ -53,27 +49,27 @@ module InsanoImageResizer
53
49
  end
54
50
 
55
51
  if (interest_point[:x] == nil)
56
- interest_point[:x] = image_size[:w] * 0.5
52
+ interest_point[:x] = input_properties[:w] * 0.5
57
53
  interest_point[:region] = 1
58
54
  end
59
55
  if (interest_point[:y] == nil)
60
- interest_point[:y] = image_size[:h] * 0.5
56
+ interest_point[:y] = input_properties[:h] * 0.5
61
57
  interest_point[:region] = 1
62
58
  end
63
59
 
64
- interest_size = {w: image_size[:w] * interest_point[:region], h: image_size[:h] * interest_point[:region]}
60
+ interest_size = {w: input_properties[:w] * interest_point[:region], h: input_properties[:h] * interest_point[:region]}
65
61
 
66
62
  # Has the user specified both the width and the height of the viewport? If they haven't,
67
63
  # let's go ahead and fill in the missing properties for them so that they get output at
68
64
  # the original aspect ratio of the image.
69
65
  if ((viewport_size[:w] == nil) && (viewport_size[:h] == nil))
70
- viewport_size = {w: input_properties["width"], h: input_properties["height"]}
66
+ viewport_size = {w: input_properties[:w], h: input_properties[:h]}
71
67
 
72
68
  elsif (viewport_size[:w] == nil)
73
- viewport_size[:w] = viewport_size[:h] * (input_properties["width"] / input_properties["height"])
69
+ viewport_size[:w] = (viewport_size[:h] * (input_properties[:w].to_f / input_properties[:h].to_f)).round
74
70
 
75
71
  elsif (viewport_size[:h] == nil)
76
- viewport_size[:h] = viewport_size[:w] * (input_properties["height"] / input_properties["width"])
72
+ viewport_size[:h] = (viewport_size[:w] * (input_properties[:h].to_f / input_properties[:w].to_f)).round
77
73
  end
78
74
 
79
75
  # how can we take our current image and fit it into the viewport? Time for
@@ -85,14 +81,14 @@ module InsanoImageResizer
85
81
  # we won't get a simple scale-to-fill. We'll get a more zoomed-in version
86
82
  # showing just the 1/3 around the interest_point.
87
83
 
88
- scale_to_fill = [viewport_size[:w] / image_size[:w], viewport_size[:h] / image_size[:h]].max
84
+ scale_to_fill = [viewport_size[:w] / input_properties[:w].to_f, viewport_size[:h] / input_properties[:h].to_f].max
89
85
 
90
- scale_to_interest = [interest_size[:w] / image_size[:w], interest_size[:h] / image_size[:h]].max
86
+ scale_to_interest = [interest_size[:w] / input_properties[:w].to_f, interest_size[:h] / input_properties[:h].to_f].max
91
87
 
92
88
  log.debug("POI: ")
93
89
  log.debug(interest_point)
94
- log.debug("Image size: ")
95
- log.debug(image_size)
90
+ log.debug("Image properties: ")
91
+ log.debug(input_properties)
96
92
  log.debug("Requested viewport size: ")
97
93
  log.debug(viewport_size)
98
94
  log.debug("scale_to_fill: %f" % scale_to_fill)
@@ -104,10 +100,10 @@ module InsanoImageResizer
104
100
  # cool! Now, let's figure out what the content offset within the image should be.
105
101
  # We want to keep the point of interest in view whenever possible. First, let's
106
102
  # compute an optimal frame around the POI:
107
- best_region = {x: interest_point[:x].to_f - (image_size[:w] * scale_for_best_region) / 2,
108
- y: interest_point[:y].to_f - (image_size[:h] * scale_for_best_region) / 2,
109
- w: image_size[:w] * scale_for_best_region,
110
- h: image_size[:h] * scale_for_best_region}
103
+ best_region = {x: interest_point[:x].to_f - (input_properties[:w].to_f * scale_for_best_region) / 2,
104
+ y: interest_point[:y].to_f - (input_properties[:h].to_f * scale_for_best_region) / 2,
105
+ w: input_properties[:w].to_f * scale_for_best_region,
106
+ h: input_properties[:h].to_f * scale_for_best_region}
111
107
 
112
108
  # Up to this point, we've been using 'scale_for_best_region' to be the preferred scale of the image.
113
109
  # So, scale could be 1/3 if we want to show the area around the POI, or 1 if we're fitting a whole image
@@ -117,7 +113,7 @@ module InsanoImageResizer
117
113
  # the image fit within the viewport. This is different from the previous scale—if we wanted to fit 1/3 of
118
114
  # the image in a 100x100 pixel viewport, we computed best_region using that 1/3, and now we need to find
119
115
  # the scale that will fit it into 100px.
120
- scale = [scale_to_fill, viewport_size[:w] / best_region[:w], viewport_size[:h] / best_region[:h]].max
116
+ scale = [scale_to_fill, viewport_size[:w].to_f / best_region[:w], viewport_size[:h].to_f / best_region[:h]].max
121
117
 
122
118
  # Next, we scale the best_region so that it is in final coordinates. When we perform the affine transform,
123
119
  # it will SCALE the entire image and then CROP it to a region, so our transform rect needs to be in the
@@ -140,12 +136,12 @@ module InsanoImageResizer
140
136
 
141
137
  # alright—now our transform most likely extends beyond the bounds of the image
142
138
  # data. Let's add some constraints that push it within the bounds of the image.
143
- if (transform[:x] + transform[:w] > image_size[:w] * scale)
144
- transform[:x] = image_size[:w] * scale - transform[:w]
139
+ if (transform[:x] + transform[:w] > input_properties[:w].to_f * scale)
140
+ transform[:x] = input_properties[:w].to_f * scale - transform[:w]
145
141
  end
146
142
 
147
- if (transform[:y] + transform[:h] > image_size[:h] * scale)
148
- transform[:y] = image_size[:h] * scale - transform[:h]
143
+ if (transform[:y] + transform[:h] > input_properties[:h].to_f * scale)
144
+ transform[:y] = input_properties[:h].to_f * scale - transform[:h]
149
145
  end
150
146
 
151
147
  if (transform[:x] < 0)
@@ -162,33 +158,51 @@ module InsanoImageResizer
162
158
  return transform
163
159
  end
164
160
 
165
- def run_transform(input_path, output_path, transform)
161
+ def run_transform(input_path, output_path, transform, quality = 60)
166
162
  # Call through to VIPS:
167
163
  # int im_affinei(in, out, interpolate, a, b, c, d, dx, dy, x, y, w, h)
168
164
  # The first six params are a transformation matrix. A and D are used for X and Y
169
165
  # scale, the other two are b = Y skew and c = X skew. TX and TY are translations
170
166
  # but don't seem to be used.
171
167
  # The last four params define a rect of the source image that is transformed.
172
- log.debug(output_path[-3..-1])
173
-
174
- if ((transform[:scale] < 0.5) && (output_path[-3..-1] == "jpg"))
175
- scale = transform[:scale]
176
- size = [transform[:w], transform[:h]].max
177
-
178
- transform[:scale] = scale * 2
179
- transform[:x] *= 2
180
- transform[:y] *= 2
181
- transform[:w] *= 2
182
- transform[:h] *= 2
168
+ output_extension = output_path[-3..-1]
169
+ quality_extension = ""
183
170
 
184
-
185
- log.debug("Using two-pass transform")
186
- run("vips im_affinei '#{input_path}' '#{output_path}' bilinear #{transform[:scale]} 0 0 #{transform[:scale]} 0 0 #{transform[:x]} #{transform[:y]} #{transform[:w]} #{transform[:h]}")
187
- run("vipsthumbnail -s #{size} --nosharpen -o '%s_thumb.jpg:65' '#{output_path}'")
188
- FileUtils.mv(output_path[0..-5]+"_thumb.jpg", output_path)
171
+ if (output_extension == "jpg")
172
+ quality_extension = ":#{quality}"
173
+ end
174
+
175
+ if (transform[:scale] < 0.5)
176
+ # 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
177
+ # is that the interpolators, such as bilinear and bicubic, don't produce very good results when scaling an image
178
+ # by more than 1/2. Instead, we use a high-speed shrinking function to reduce the image by the smallest integer scale
179
+ # greater than the desired scale, and then go the rest of the way with an interpolated affine transform.
180
+ shrink_factor = (1.0 / transform[:scale]).floor
181
+
182
+ # To ensure that we actually do both passes, don't let the im_shrink go all the way. This will result in terrible
183
+ # looking shrunken images, since im_shrink basically just cuts pixels out.
184
+ if (shrink_factor == 1.0 / transform[:scale])
185
+ shrink_factor -= 1
186
+ end
187
+
188
+ transform[:scale] *= shrink_factor
189
+ transform[:x] *= shrink_factor
190
+ transform[:y] *= shrink_factor
191
+ transform[:w] *= shrink_factor
192
+ transform[:h] *= shrink_factor
193
+
194
+ if (input_path[-4..-3] != ".")
195
+ FileUtils.mv(input_path, input_path+"."+output_extension)
196
+ input_path = input_path + "." + output_extension
197
+ end
198
+ intermediate_path = input_path[0..-4]+"_shrunk." + output_extension
199
+
200
+ run("vips im_shrink '#{input_path}' '#{intermediate_path}#{quality_extension}' #{shrink_factor} #{shrink_factor}")
201
+ run("vips im_affinei_all '#{intermediate_path}' '#{output_path}#{quality_extension}' bicubic #{transform[:scale]} 0 0 #{transform[:scale]} 0 0")
202
+ FileUtils.rm(intermediate_path)
189
203
 
190
204
  else
191
- run("vips im_affinei '#{input_path}' '#{output_path}' bilinear #{transform[:scale]} 0 0 #{transform[:scale]} 0 0 #{transform[:x]} #{transform[:y]} #{transform[:w]} #{transform[:h]}")
205
+ run("vips im_affinei '#{input_path}' '#{output_path}#{quality_extension}' bilinear #{transform[:scale]} 0 0 #{transform[:scale]} 0 0 #{transform[:x]} #{transform[:y]} #{transform[:w]} #{transform[:h]}")
192
206
 
193
207
  end
194
208
 
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.3.3
4
+ version: 0.3.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-25 00:00:00.000000000 Z
12
+ date: 2012-06-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec