insano_image_resizer 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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