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.
- data/lib/insano_image_resizer/processor.rb +65 -51
- metadata +2 -2
@@ -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[
|
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 =
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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] =
|
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] =
|
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] =
|
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] =
|
56
|
+
interest_point[:y] = input_properties[:h] * 0.5
|
61
57
|
interest_point[:region] = 1
|
62
58
|
end
|
63
59
|
|
64
|
-
interest_size = {w:
|
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[
|
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[
|
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[
|
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] /
|
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] /
|
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
|
95
|
-
log.debug(
|
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 - (
|
108
|
-
y: interest_point[:y].to_f - (
|
109
|
-
w:
|
110
|
-
h:
|
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] >
|
144
|
-
transform[:x] =
|
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] >
|
148
|
-
transform[:y] =
|
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
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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.
|
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-
|
12
|
+
date: 2012-06-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|