insano_image_resizer 0.3.1 → 0.3.3
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.rb +5 -0
- data/lib/insano_image_resizer/configurable.rb +206 -0
- data/lib/insano_image_resizer/loggable.rb +28 -0
- data/lib/insano_image_resizer/processor.rb +199 -0
- data/lib/insano_image_resizer/shell.rb +48 -0
- metadata +6 -11
- data/.rspec +0 -1
- data/.rvmrc +0 -1
- data/Gemfile +0 -9
- data/Gemfile.lock +0 -45
- data/Rakefile +0 -56
- data/VERSION +0 -1
- data/samples/explanation.png +0 -0
- data/samples/test.jpg +0 -0
- data/samples/test.png +0 -0
- data/spec/spec_helper.rb +0 -61
@@ -0,0 +1,206 @@
|
|
1
|
+
module InsanoImageResizer
|
2
|
+
module Configurable
|
3
|
+
|
4
|
+
# Exceptions
|
5
|
+
class NotConfigured < RuntimeError; end
|
6
|
+
class BadConfigAttribute < RuntimeError; end
|
7
|
+
|
8
|
+
def self.included(klass)
|
9
|
+
klass.class_eval do
|
10
|
+
include Configurable::InstanceMethods
|
11
|
+
extend Configurable::ClassMethods
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class DeferredBlock # Inheriting from Proc causes errors in some versions of Ruby
|
16
|
+
def initialize(blk)
|
17
|
+
@blk = blk
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
@blk.call
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module InstanceMethods
|
26
|
+
|
27
|
+
def configure(&block)
|
28
|
+
yield ConfigurationProxy.new(self)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def configure_with(config, *args, &block)
|
33
|
+
config = saved_config_for(config) if config.is_a?(Symbol)
|
34
|
+
config.apply_configuration(self, *args)
|
35
|
+
configure(&block) if block
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_config_method?(method_name)
|
40
|
+
config_methods.include?(method_name.to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def configuration
|
44
|
+
@configuration ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def config_methods
|
48
|
+
@config_methods ||= self.class.config_methods.dup
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_configuration
|
52
|
+
@default_configuration ||= self.class.default_configuration.dup
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_config_value(key, value)
|
56
|
+
configuration[key] = value
|
57
|
+
child_configurables.each{|c| c.set_if_unset(key, value) }
|
58
|
+
value
|
59
|
+
end
|
60
|
+
|
61
|
+
def use_as_fallback_config(other_configurable)
|
62
|
+
other_configurable.add_child_configurable(self)
|
63
|
+
self.fallback_configurable = other_configurable
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def add_child_configurable(obj)
|
69
|
+
child_configurables << obj
|
70
|
+
config_methods.push(*obj.config_methods)
|
71
|
+
fallback_configurable.config_methods.push(*obj.config_methods) if fallback_configurable
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_if_unset(key, value)
|
75
|
+
set_config_value(key, value) unless set_locally?(key)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
attr_accessor :fallback_configurable
|
81
|
+
|
82
|
+
def child_configurables
|
83
|
+
@child_configurables ||= []
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_locally?(key)
|
87
|
+
instance_variable_defined?("@#{key}")
|
88
|
+
end
|
89
|
+
|
90
|
+
def default_value(key)
|
91
|
+
if default_configuration[key].is_a?(DeferredBlock)
|
92
|
+
default_configuration[key] = default_configuration[key].call
|
93
|
+
end
|
94
|
+
default_configuration[key]
|
95
|
+
end
|
96
|
+
|
97
|
+
def saved_configs
|
98
|
+
self.class.saved_configs
|
99
|
+
end
|
100
|
+
|
101
|
+
def saved_config_for(symbol)
|
102
|
+
config = saved_configs[symbol]
|
103
|
+
if config.nil?
|
104
|
+
raise ArgumentError, "#{symbol.inspect} is not a known configuration - try one of #{saved_configs.keys.join(', ')}"
|
105
|
+
end
|
106
|
+
config = config.call if config.respond_to?(:call)
|
107
|
+
config
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
module ClassMethods
|
113
|
+
|
114
|
+
def default_configuration
|
115
|
+
@default_configuration ||= configurable_ancestors.reverse.inject({}) do |default_config, klass|
|
116
|
+
default_config.merge!(klass.default_configuration)
|
117
|
+
default_config
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def config_methods
|
122
|
+
@config_methods ||= configurable_ancestors.inject([]) do |conf_methods, klass|
|
123
|
+
conf_methods |= klass.config_methods
|
124
|
+
conf_methods
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def nested_configurables
|
129
|
+
@nested_configurables ||= []
|
130
|
+
end
|
131
|
+
|
132
|
+
def register_configuration(name, config=nil, &config_in_block)
|
133
|
+
saved_configs[name] = config_in_block || config
|
134
|
+
end
|
135
|
+
|
136
|
+
def saved_configs
|
137
|
+
@saved_configs ||= {}
|
138
|
+
end
|
139
|
+
|
140
|
+
def configurable_ancestors
|
141
|
+
@configurable_ancestors ||= ancestors.select{|a| a.included_modules.include?(Configurable) } - [self]
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def configurable_attr attribute, default=nil, &blk
|
147
|
+
default_configuration[attribute] = blk ? DeferredBlock.new(blk) : default
|
148
|
+
|
149
|
+
# Define the reader
|
150
|
+
define_method(attribute) do
|
151
|
+
configuration.has_key?(attribute) ? configuration[attribute] : default_value(attribute)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Define the writer
|
155
|
+
define_method("#{attribute}=") do |value|
|
156
|
+
instance_variable_set("@#{attribute}", value)
|
157
|
+
set_config_value(attribute, value)
|
158
|
+
end
|
159
|
+
|
160
|
+
configuration_method attribute
|
161
|
+
configuration_method "#{attribute}="
|
162
|
+
end
|
163
|
+
|
164
|
+
def configuration_method(*method_names)
|
165
|
+
config_methods.push(*method_names.map{|n| n.to_sym }).uniq!
|
166
|
+
end
|
167
|
+
|
168
|
+
def nested_configurable(*method_names)
|
169
|
+
nested_configurables.push(*method_names)
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
class ConfigurationProxy
|
175
|
+
|
176
|
+
def initialize(owner)
|
177
|
+
@owner = owner
|
178
|
+
end
|
179
|
+
|
180
|
+
def method_missing(method_name, *args, &block)
|
181
|
+
if owner.has_config_method?(method_name)
|
182
|
+
attribute = method_name.to_s.tr('=','').to_sym
|
183
|
+
if method_name.to_s =~ /=$/ && owner.has_config_method?(attribute) # a bit hacky - if it has both getter and setter, assume it's a configurable_attr
|
184
|
+
owner.set_config_value(attribute, args.first)
|
185
|
+
else
|
186
|
+
owner.send(method_name, *args, &block)
|
187
|
+
end
|
188
|
+
elsif nested_configurable?(method_name)
|
189
|
+
owner.send(method_name)
|
190
|
+
else
|
191
|
+
raise BadConfigAttribute, "You tried to configure using '#{method_name.inspect}', but the valid config attributes are #{owner.config_methods.map{|a| %('#{a.inspect}') }.sort.join(', ')}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
attr_reader :owner
|
198
|
+
|
199
|
+
def nested_configurable?(method)
|
200
|
+
owner.class.nested_configurables.include?(method.to_sym)
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module InsanoImageResizer
|
4
|
+
module Loggable
|
5
|
+
|
6
|
+
def log
|
7
|
+
case @log_object
|
8
|
+
when nil
|
9
|
+
@log_object = Logger.new($stdout)
|
10
|
+
when Proc
|
11
|
+
@log_object[]
|
12
|
+
when Logger
|
13
|
+
@log_object
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def log=(object)
|
18
|
+
@log_object = object
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :log_object
|
22
|
+
|
23
|
+
def use_same_log_as(object)
|
24
|
+
self.log = proc{ object.log }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module InsanoImageResizer
|
5
|
+
class Processor
|
6
|
+
|
7
|
+
include Configurable
|
8
|
+
include Shell
|
9
|
+
include Loggable
|
10
|
+
|
11
|
+
def process(input_path, viewport_size = {}, interest_point = {})
|
12
|
+
input_properties = fetch_image_properties(input_path)
|
13
|
+
input_has_alpha = (input_properties["bands"] == 4)
|
14
|
+
|
15
|
+
output_tmp = Tempfile.new(["img", input_has_alpha ? ".png" : ".jpg"])
|
16
|
+
|
17
|
+
transform = calculate_transform(input_path, input_properties, viewport_size, interest_point)
|
18
|
+
run_transform(input_path, output_tmp.path, transform)
|
19
|
+
|
20
|
+
return output_tmp.path
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_image_properties(input_path)
|
24
|
+
# read in the image headers to discover the width and height of the image.
|
25
|
+
# There's actually some extra metadata we ignore here, but this seems to be
|
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)
|
33
|
+
end
|
34
|
+
|
35
|
+
def calculate_transform(input_path, input_properties, viewport_size, interest_point)
|
36
|
+
|
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
|
+
# By default, the interest size is 30% of the total image size.
|
41
|
+
# In the future, this could be a parameter, and you'd pass the # of pixels around
|
42
|
+
# the POI you are interested in.
|
43
|
+
if (interest_point[:xf])
|
44
|
+
interest_point[:x] = image_size[:w] * interest_point[:xf]
|
45
|
+
end
|
46
|
+
|
47
|
+
if (interest_point[:yf])
|
48
|
+
interest_point[:y] = image_size[:h] * interest_point[:yf]
|
49
|
+
end
|
50
|
+
|
51
|
+
if (interest_point[:region] == nil)
|
52
|
+
interest_point[:region] = 1
|
53
|
+
end
|
54
|
+
|
55
|
+
if (interest_point[:x] == nil)
|
56
|
+
interest_point[:x] = image_size[:w] * 0.5
|
57
|
+
interest_point[:region] = 1
|
58
|
+
end
|
59
|
+
if (interest_point[:y] == nil)
|
60
|
+
interest_point[:y] = image_size[:h] * 0.5
|
61
|
+
interest_point[:region] = 1
|
62
|
+
end
|
63
|
+
|
64
|
+
interest_size = {w: image_size[:w] * interest_point[:region], h: image_size[:h] * interest_point[:region]}
|
65
|
+
|
66
|
+
# Has the user specified both the width and the height of the viewport? If they haven't,
|
67
|
+
# let's go ahead and fill in the missing properties for them so that they get output at
|
68
|
+
# the original aspect ratio of the image.
|
69
|
+
if ((viewport_size[:w] == nil) && (viewport_size[:h] == nil))
|
70
|
+
viewport_size = {w: input_properties["width"], h: input_properties["height"]}
|
71
|
+
|
72
|
+
elsif (viewport_size[:w] == nil)
|
73
|
+
viewport_size[:w] = viewport_size[:h] * (input_properties["width"] / input_properties["height"])
|
74
|
+
|
75
|
+
elsif (viewport_size[:h] == nil)
|
76
|
+
viewport_size[:h] = viewport_size[:w] * (input_properties["height"] / input_properties["width"])
|
77
|
+
end
|
78
|
+
|
79
|
+
# how can we take our current image and fit it into the viewport? Time for
|
80
|
+
# some fun math! First, let's determine a scale such that the image fits
|
81
|
+
# within the viewport. There are a few rules we want to apply:
|
82
|
+
# 1) The image should _always_ fill the viewport.
|
83
|
+
# 2) The 1/3 of the image around the interest_point should always be visible.
|
84
|
+
# This means that if we try to cram a massive image into a tiny viewport,
|
85
|
+
# we won't get a simple scale-to-fill. We'll get a more zoomed-in version
|
86
|
+
# showing just the 1/3 around the interest_point.
|
87
|
+
|
88
|
+
scale_to_fill = [viewport_size[:w] / image_size[:w], viewport_size[:h] / image_size[:h]].max
|
89
|
+
|
90
|
+
scale_to_interest = [interest_size[:w] / image_size[:w], interest_size[:h] / image_size[:h]].max
|
91
|
+
|
92
|
+
log.debug("POI: ")
|
93
|
+
log.debug(interest_point)
|
94
|
+
log.debug("Image size: ")
|
95
|
+
log.debug(image_size)
|
96
|
+
log.debug("Requested viewport size: ")
|
97
|
+
log.debug(viewport_size)
|
98
|
+
log.debug("scale_to_fill: %f" % scale_to_fill)
|
99
|
+
log.debug("scale_to_interest: %f" % scale_to_interest)
|
100
|
+
|
101
|
+
|
102
|
+
scale_for_best_region = [scale_to_fill, scale_to_interest].max
|
103
|
+
|
104
|
+
# cool! Now, let's figure out what the content offset within the image should be.
|
105
|
+
# We want to keep the point of interest in view whenever possible. First, let's
|
106
|
+
# 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}
|
111
|
+
|
112
|
+
# Up to this point, we've been using 'scale_for_best_region' to be the preferred scale of the image.
|
113
|
+
# 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
|
114
|
+
# in a viewport that is exactly the same aspect ratio.
|
115
|
+
|
116
|
+
# The next step is to compute a scale that should be applied to the image to make this desired section of
|
117
|
+
# the image fit within the viewport. This is different from the previous scale—if we wanted to fit 1/3 of
|
118
|
+
# the image in a 100x100 pixel viewport, we computed best_region using that 1/3, and now we need to find
|
119
|
+
# 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
|
121
|
+
|
122
|
+
# Next, we scale the best_region so that it is in final coordinates. When we perform the affine transform,
|
123
|
+
# it will SCALE the entire image and then CROP it to a region, so our transform rect needs to be in the
|
124
|
+
# coordinate space of the SCALED image, not the initial image.
|
125
|
+
transform = {}
|
126
|
+
transform[:x] = best_region[:x] * scale
|
127
|
+
transform[:y] = best_region[:y] * scale
|
128
|
+
transform[:w] = best_region[:w] * scale
|
129
|
+
transform[:h] = best_region[:h] * scale
|
130
|
+
transform[:scale] = scale
|
131
|
+
|
132
|
+
# transform now represents the region we'd like to have in the final image. All of it, or part of it, may
|
133
|
+
# not actually be within the bounds of the image! We're about to apply some constraints, but first let's
|
134
|
+
# trim the best_region so that it's the SHAPE of the viewport, not just the SCALE of the viewport. Remember,
|
135
|
+
# since the region is still centered around the POI, we can just trim equally on either the W or H as necessary.
|
136
|
+
transform[:x] -= (viewport_size[:w] - transform[:w]) / 2
|
137
|
+
transform[:y] -= (viewport_size[:h] - transform[:h]) / 2
|
138
|
+
transform[:w] = viewport_size[:w]
|
139
|
+
transform[:h] = viewport_size[:h]
|
140
|
+
|
141
|
+
# alright—now our transform most likely extends beyond the bounds of the image
|
142
|
+
# 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]
|
145
|
+
end
|
146
|
+
|
147
|
+
if (transform[:y] + transform[:h] > image_size[:h] * scale)
|
148
|
+
transform[:y] = image_size[:h] * scale - transform[:h]
|
149
|
+
end
|
150
|
+
|
151
|
+
if (transform[:x] < 0)
|
152
|
+
transform[:x] = 0.0
|
153
|
+
end
|
154
|
+
|
155
|
+
if (transform[:y] < 0)
|
156
|
+
transform[:y] = 0.0
|
157
|
+
end
|
158
|
+
|
159
|
+
log.debug("The transform properties:")
|
160
|
+
log.debug(transform)
|
161
|
+
|
162
|
+
return transform
|
163
|
+
end
|
164
|
+
|
165
|
+
def run_transform(input_path, output_path, transform)
|
166
|
+
# Call through to VIPS:
|
167
|
+
# int im_affinei(in, out, interpolate, a, b, c, d, dx, dy, x, y, w, h)
|
168
|
+
# The first six params are a transformation matrix. A and D are used for X and Y
|
169
|
+
# scale, the other two are b = Y skew and c = X skew. TX and TY are translations
|
170
|
+
# but don't seem to be used.
|
171
|
+
# 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
|
183
|
+
|
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)
|
189
|
+
|
190
|
+
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]}")
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
return output_path
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
3
|
+
module InsanoImageResizer
|
4
|
+
module Shell
|
5
|
+
|
6
|
+
include Configurable
|
7
|
+
configurable_attr :log_commands, true
|
8
|
+
|
9
|
+
# Exceptions
|
10
|
+
class CommandFailed < RuntimeError; end
|
11
|
+
|
12
|
+
def run(command, args="")
|
13
|
+
full_command = "#{command} #{escape_args(args)}"
|
14
|
+
log.debug("Running command: #{full_command}") if log_commands
|
15
|
+
begin
|
16
|
+
result = `#{full_command}`
|
17
|
+
rescue Errno::ENOENT
|
18
|
+
raise_shell_command_failed(full_command)
|
19
|
+
end
|
20
|
+
if $?.exitstatus == 1
|
21
|
+
throw :unable_to_handle
|
22
|
+
elsif !$?.success?
|
23
|
+
raise_shell_command_failed(full_command)
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def raise_shell_command_failed(command)
|
29
|
+
raise CommandFailed, "Command failed (#{command}) with exit status #{$?.exitstatus}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def escape_args(args)
|
33
|
+
args.shellsplit.map do |arg|
|
34
|
+
quote arg.gsub(/\\?'/, %q('\\\\''))
|
35
|
+
end.join(' ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def quote(string)
|
39
|
+
q = running_on_windows? ? '"' : "'"
|
40
|
+
q + string + q
|
41
|
+
end
|
42
|
+
|
43
|
+
def running_on_windows?
|
44
|
+
ENV['OS'] && ENV['OS'].downcase == 'windows_nt'
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
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.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -103,18 +103,13 @@ extra_rdoc_files:
|
|
103
103
|
- LICENSE
|
104
104
|
- README.md
|
105
105
|
files:
|
106
|
-
- .
|
107
|
-
- .
|
108
|
-
-
|
109
|
-
-
|
106
|
+
- lib/insano_image_resizer.rb
|
107
|
+
- lib/insano_image_resizer/configurable.rb
|
108
|
+
- lib/insano_image_resizer/loggable.rb
|
109
|
+
- lib/insano_image_resizer/processor.rb
|
110
|
+
- lib/insano_image_resizer/shell.rb
|
110
111
|
- LICENSE
|
111
112
|
- README.md
|
112
|
-
- Rakefile
|
113
|
-
- VERSION
|
114
|
-
- samples/explanation.png
|
115
|
-
- samples/test.jpg
|
116
|
-
- samples/test.png
|
117
|
-
- spec/spec_helper.rb
|
118
113
|
homepage: http://github.com/populr/insano_image_resizer
|
119
114
|
licenses:
|
120
115
|
- MIT
|
data/.rspec
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|
data/.rvmrc
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rvm use 1.9.3@insano_image_resizer
|
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
GEM
|
2
|
-
remote: http://rubygems.org/
|
3
|
-
specs:
|
4
|
-
binding_of_caller (0.6.7)
|
5
|
-
coderay (1.0.6)
|
6
|
-
diff-lcs (1.1.3)
|
7
|
-
git (1.2.5)
|
8
|
-
jeweler (1.8.3)
|
9
|
-
bundler (~> 1.0)
|
10
|
-
git (>= 1.2.5)
|
11
|
-
rake
|
12
|
-
rdoc
|
13
|
-
json (1.6.6)
|
14
|
-
method_source (0.7.1)
|
15
|
-
pry (0.9.8.4)
|
16
|
-
coderay (~> 1.0.5)
|
17
|
-
method_source (~> 0.7.1)
|
18
|
-
slop (>= 2.4.4, < 3)
|
19
|
-
pry-nav (0.2.0)
|
20
|
-
pry (~> 0.9.8.1)
|
21
|
-
pry-stack_explorer (0.4.1)
|
22
|
-
binding_of_caller (~> 0.6.2)
|
23
|
-
pry (~> 0.9.8.2)
|
24
|
-
rake (0.9.2.2)
|
25
|
-
rdoc (3.12)
|
26
|
-
json (~> 1.4)
|
27
|
-
rspec (2.9.0)
|
28
|
-
rspec-core (~> 2.9.0)
|
29
|
-
rspec-expectations (~> 2.9.0)
|
30
|
-
rspec-mocks (~> 2.9.0)
|
31
|
-
rspec-core (2.9.0)
|
32
|
-
rspec-expectations (2.9.1)
|
33
|
-
diff-lcs (~> 1.1.3)
|
34
|
-
rspec-mocks (2.9.0)
|
35
|
-
slop (2.4.4)
|
36
|
-
|
37
|
-
PLATFORMS
|
38
|
-
ruby
|
39
|
-
|
40
|
-
DEPENDENCIES
|
41
|
-
jeweler
|
42
|
-
pry
|
43
|
-
pry-nav
|
44
|
-
pry-stack_explorer
|
45
|
-
rspec
|
data/Rakefile
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'rubygems'
|
4
|
-
require 'bundler'
|
5
|
-
begin
|
6
|
-
Bundler.setup(:default, :development)
|
7
|
-
rescue Bundler::BundlerError => e
|
8
|
-
$stderr.puts e.message
|
9
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
-
exit e.status_code
|
11
|
-
end
|
12
|
-
require 'rake'
|
13
|
-
|
14
|
-
require 'jeweler'
|
15
|
-
Jeweler::Tasks.new do |gem|
|
16
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
-
gem.name = "insano_image_resizer"
|
18
|
-
gem.homepage = "http://github.com/populr/insano_image_resizer"
|
19
|
-
gem.license = "MIT"
|
20
|
-
gem.summary = %Q{An image resizing gem that calls through to VIPS on the command line.}
|
21
|
-
gem.description = %Q{An image resizing gem that generates smaller versions of requested
|
22
|
-
images. Calls through to VIPS on the command line to perform processing,
|
23
|
-
and automatically handles cropping and scaling the requested image, taking
|
24
|
-
a point of interest into account if requested.}
|
25
|
-
gem.email = "ben@populr.me"
|
26
|
-
gem.authors = ["Ben Gotow"]
|
27
|
-
# dependencies defined in Gemfile
|
28
|
-
gem.files.exclude 'spec'
|
29
|
-
gem.files.exclude 'samples'
|
30
|
-
gem.files.exclude 'tmp'
|
31
|
-
gem.files.exclude 'demo.rb'
|
32
|
-
end
|
33
|
-
Jeweler::RubygemsDotOrgTasks.new
|
34
|
-
|
35
|
-
require 'rspec/core'
|
36
|
-
require 'rspec/core/rake_task'
|
37
|
-
RSpec::Core::RakeTask.new(:spec) do |spec|
|
38
|
-
spec.pattern = FileList['spec/**/*_spec.rb']
|
39
|
-
end
|
40
|
-
|
41
|
-
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
42
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
43
|
-
spec.rcov = true
|
44
|
-
end
|
45
|
-
|
46
|
-
task :default => :spec
|
47
|
-
|
48
|
-
require 'rdoc/task'
|
49
|
-
Rake::RDocTask.new do |rdoc|
|
50
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
51
|
-
|
52
|
-
rdoc.rdoc_dir = 'rdoc'
|
53
|
-
rdoc.title = "image_resizer #{version}"
|
54
|
-
rdoc.rdoc_files.include('README*')
|
55
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
-
end
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.3.1
|
data/samples/explanation.png
DELETED
Binary file
|
data/samples/test.jpg
DELETED
Binary file
|
data/samples/test.png
DELETED
Binary file
|
data/spec/spec_helper.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
require "bundler"
|
3
|
-
Bundler.setup(:default, :test)
|
4
|
-
|
5
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
-
require 'rspec'
|
8
|
-
require 'insano_image_resizer'
|
9
|
-
require 'fileutils'
|
10
|
-
require 'pry'
|
11
|
-
require 'pry-nav'
|
12
|
-
require 'pry-stack_explorer'
|
13
|
-
|
14
|
-
# Requires supporting files with custom matchers and macros, etc,
|
15
|
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
16
|
-
|
17
|
-
SAMPLES_DIR = Pathname.new(File.expand_path(File.dirname(__FILE__) + '/../samples')) unless defined?(SAMPLES_DIR)
|
18
|
-
|
19
|
-
def todo
|
20
|
-
raise "TODO"
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
# require 'logger'
|
25
|
-
# LOG_FILE = 'tmp/test.log' unless defined?(LOG_FILE)
|
26
|
-
# FileUtils.rm_rf(LOG_FILE)
|
27
|
-
# def mock_app(extra_stubs={})
|
28
|
-
# mock('app', {
|
29
|
-
# :datastore => mock('datastore', :store => 'some_uid', :retrieve => ["SOME_DATA", {}], :destroy => nil),
|
30
|
-
# :processor => mock('processor', :process => "SOME_PROCESSED_DATA"),
|
31
|
-
# :encoder => mock('encoder', :encode => "SOME_ENCODED_DATA"),
|
32
|
-
# :analyzer => mock('analyzer', :analyse => "some_result", :analysis_methods => Module.new),
|
33
|
-
# :generator => mock('generator', :generate => "SOME_GENERATED_DATA"),
|
34
|
-
# :log => Logger.new(LOG_FILE),
|
35
|
-
# :cache_duration => 10000,
|
36
|
-
# :job_definitions => Module.new
|
37
|
-
# }.merge(extra_stubs)
|
38
|
-
# )
|
39
|
-
# end
|
40
|
-
|
41
|
-
# def test_app
|
42
|
-
# time = Time.now
|
43
|
-
# app = ImageResizer::App.send(:new, "test_#{time.sec}_#{time.usec}".to_sym)
|
44
|
-
# app.log = Logger.new(LOG_FILE)
|
45
|
-
# app.datastore.root_path = 'tmp/file_data_store_test'
|
46
|
-
# app
|
47
|
-
# end
|
48
|
-
|
49
|
-
def suppressing_stderr
|
50
|
-
original_stderr = $stderr.dup
|
51
|
-
tempfile = Tempfile.new('stderr')
|
52
|
-
$stderr.reopen(tempfile) rescue
|
53
|
-
yield
|
54
|
-
ensure
|
55
|
-
tempfile.close!
|
56
|
-
$stderr.reopen(original_stderr)
|
57
|
-
end
|
58
|
-
|
59
|
-
def running_on_windows?
|
60
|
-
ENV['OS'] && ENV['OS'].downcase == 'windows_nt'
|
61
|
-
end
|