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.
- 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
|