image_resizer 0.1.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/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +45 -0
- data/LICENSE +22 -0
- data/README.md +56 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/image_resizer.gemspec +93 -0
- data/lib/image_resizer/analyzer.rb +51 -0
- data/lib/image_resizer/configurable.rb +206 -0
- data/lib/image_resizer/encoder.rb +55 -0
- data/lib/image_resizer/has_filename.rb +24 -0
- data/lib/image_resizer/loggable.rb +28 -0
- data/lib/image_resizer/processor.rb +220 -0
- data/lib/image_resizer/shell.rb +48 -0
- data/lib/image_resizer/temp_object.rb +216 -0
- data/lib/image_resizer/utils.rb +44 -0
- data/lib/image_resizer.rb +10 -0
- data/samples/DSC02119.JPG +0 -0
- data/samples/a.jp2 +0 -0
- data/samples/beach.jpg +0 -0
- data/samples/beach.png +0 -0
- data/samples/egg.png +0 -0
- data/samples/landscape.png +0 -0
- data/samples/round.gif +0 -0
- data/samples/sample.docx +0 -0
- data/samples/taj.jpg +0 -0
- data/samples/white pixel.png +0 -0
- data/spec/image_resizer/analyzer_spec.rb +78 -0
- data/spec/image_resizer/configurable_spec.rb +479 -0
- data/spec/image_resizer/encoder_spec.rb +41 -0
- data/spec/image_resizer/has_filename_spec.rb +88 -0
- data/spec/image_resizer/loggable_spec.rb +80 -0
- data/spec/image_resizer/processor_spec.rb +590 -0
- data/spec/image_resizer/shell_spec.rb +34 -0
- data/spec/image_resizer/temp_object_spec.rb +442 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/argument_matchers.rb +19 -0
- data/spec/support/image_matchers.rb +58 -0
- data/spec/support/simple_matchers.rb +53 -0
- data/tmp/test_file +1 -0
- metadata +147 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
module ImageResizer
|
2
|
+
class Processor
|
3
|
+
|
4
|
+
GRAVITIES = {
|
5
|
+
'nw' => 'NorthWest',
|
6
|
+
'n' => 'North',
|
7
|
+
'ne' => 'NorthEast',
|
8
|
+
'w' => 'West',
|
9
|
+
'c' => 'Center',
|
10
|
+
'e' => 'East',
|
11
|
+
'sw' => 'SouthWest',
|
12
|
+
's' => 'South',
|
13
|
+
'se' => 'SouthEast'
|
14
|
+
}
|
15
|
+
|
16
|
+
# Geometry string patterns
|
17
|
+
RESIZE_GEOMETRY = /^\d*x\d*[><%^!]?$|^\d+@$/ # e.g. '300x200!'
|
18
|
+
CROPPED_RESIZE_GEOMETRY = /^(\d+)x(\d+)#(\w{1,2})?$/ # e.g. '20x50#ne'
|
19
|
+
CROP_GEOMETRY = /^(\d+)x(\d+)([+-]\d+)?([+-]\d+)?(\w{1,2})?$/ # e.g. '30x30+10+10'
|
20
|
+
THUMB_GEOMETRY = Regexp.union RESIZE_GEOMETRY, CROPPED_RESIZE_GEOMETRY, CROP_GEOMETRY
|
21
|
+
|
22
|
+
include Configurable
|
23
|
+
include Utils
|
24
|
+
|
25
|
+
|
26
|
+
def resize(temp_object, options={})
|
27
|
+
width = options[:width].to_i
|
28
|
+
height = options[:height].to_i
|
29
|
+
|
30
|
+
if height == 0 && width == 0
|
31
|
+
temp_object.file
|
32
|
+
elsif height == 0
|
33
|
+
_resize(temp_object, "#{width}x")
|
34
|
+
elsif width == 0
|
35
|
+
_resize(temp_object, "x#{height}")
|
36
|
+
else
|
37
|
+
if options[:crop_from_top_if_portrait]
|
38
|
+
analyzer = ImageResizer::Analyzer.new
|
39
|
+
center_of_gravity = analyzer.aspect_ratio(temp_object) >= 1 ? 'c' : 'n'
|
40
|
+
else
|
41
|
+
center_of_gravity = 'c'
|
42
|
+
end
|
43
|
+
|
44
|
+
resize_and_crop(temp_object, :width => width.to_i, :height => height.to_i, :gravity => center_of_gravity)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def _resize(temp_object, geometry)
|
49
|
+
convert(temp_object, "-resize #{geometry}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def auto_orient(temp_object)
|
53
|
+
convert(temp_object, "-auto-orient")
|
54
|
+
end
|
55
|
+
|
56
|
+
def crop(temp_object, opts={})
|
57
|
+
width = opts[:width]
|
58
|
+
height = opts[:height]
|
59
|
+
gravity = GRAVITIES[opts[:gravity]]
|
60
|
+
x = "#{opts[:x] || 0}"
|
61
|
+
x = '+' + x unless x[/^[+-]/]
|
62
|
+
y = "#{opts[:y] || 0}"
|
63
|
+
y = '+' + y unless y[/^[+-]/]
|
64
|
+
repage = opts[:repage] == false ? '' : '+repage'
|
65
|
+
|
66
|
+
resize = opts[:resize] ? "-resize #{opts[:resize]} " : ''
|
67
|
+
gravity = gravity ? "-gravity #{gravity} " : ''
|
68
|
+
|
69
|
+
convert(temp_object, "#{resize}#{gravity}-crop #{width}x#{height}#{x}#{y} #{repage}")
|
70
|
+
end
|
71
|
+
|
72
|
+
def resize_and_crop_around_point(temp_object, options)
|
73
|
+
analyzer = ImageResizer::Analyzer.new
|
74
|
+
|
75
|
+
desired_width = options[:width].to_i
|
76
|
+
desired_height = options[:height].to_i
|
77
|
+
desired_ratio = desired_height > 0 ? desired_width.to_f / desired_height : 0
|
78
|
+
|
79
|
+
original_width = analyzer.width(temp_object)
|
80
|
+
original_height = analyzer.height(temp_object)
|
81
|
+
original_ratio = original_width.to_f / original_height
|
82
|
+
|
83
|
+
if desired_ratio > original_ratio
|
84
|
+
width = original_width
|
85
|
+
height = width / desired_ratio
|
86
|
+
else
|
87
|
+
height = original_height
|
88
|
+
width = height * desired_ratio
|
89
|
+
end
|
90
|
+
|
91
|
+
focus_x = options[:point][0] * original_width
|
92
|
+
focus_y = options[:point][1] * original_height
|
93
|
+
|
94
|
+
half_width = width * 0.5
|
95
|
+
half_height = height * 0.5
|
96
|
+
|
97
|
+
upper_left_x = [focus_x - half_width, 0].max
|
98
|
+
upper_left_y = [focus_y - half_height, 0].max
|
99
|
+
|
100
|
+
lower_right_x = upper_left_x + width
|
101
|
+
lower_right_y = upper_left_y + height
|
102
|
+
|
103
|
+
x_offset = [lower_right_x - original_width, 0].max
|
104
|
+
y_offset = [lower_right_y - original_height, 0].max
|
105
|
+
|
106
|
+
upper_left_x -= x_offset
|
107
|
+
upper_left_y -= y_offset
|
108
|
+
|
109
|
+
lower_right_x -= x_offset
|
110
|
+
lower_right_y -= y_offset
|
111
|
+
|
112
|
+
upper_left_x_percent = upper_left_x / original_width
|
113
|
+
upper_left_y_percent = upper_left_y / original_height
|
114
|
+
|
115
|
+
lower_right_x_percent = lower_right_x / original_width
|
116
|
+
lower_right_y_percent = lower_right_y / original_height
|
117
|
+
|
118
|
+
crop_to_frame_and_resize(temp_object,
|
119
|
+
:upper_left => [upper_left_x_percent, upper_left_y_percent],
|
120
|
+
:lower_right => [lower_right_x_percent, lower_right_y_percent],
|
121
|
+
:width => desired_width,
|
122
|
+
:height => desired_height
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def crop_to_frame_and_resize(temp_object, options)
|
128
|
+
analyzer = ImageResizer::Analyzer.new
|
129
|
+
|
130
|
+
desired_width = options[:width].to_i
|
131
|
+
desired_height = options[:height].to_i
|
132
|
+
|
133
|
+
upper_left_x_percent = options[:upper_left].first
|
134
|
+
upper_left_y_percent = options[:upper_left].last
|
135
|
+
|
136
|
+
lower_right_x_percent = options[:lower_right].first
|
137
|
+
lower_right_y_percent = options[:lower_right].last
|
138
|
+
|
139
|
+
original_width = analyzer.width(temp_object)
|
140
|
+
original_height = analyzer.height(temp_object)
|
141
|
+
|
142
|
+
upper_left_x = (original_width * upper_left_x_percent).round
|
143
|
+
upper_left_y = (original_height * upper_left_y_percent).round
|
144
|
+
frame_width = (original_width * (lower_right_x_percent - upper_left_x_percent)).round
|
145
|
+
frame_height = (original_height * (lower_right_y_percent - upper_left_y_percent)).round
|
146
|
+
|
147
|
+
if desired_width == 0 && frame_height > 0
|
148
|
+
ratio = frame_width.to_f / frame_height
|
149
|
+
desired_width = (desired_height * ratio).round
|
150
|
+
end
|
151
|
+
|
152
|
+
if desired_height == 0 && frame_width > 0
|
153
|
+
ratio = frame_height.to_f / frame_width
|
154
|
+
desired_height = (desired_width * ratio).round
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
convert(temp_object, "-crop #{frame_width}x#{frame_height}+#{upper_left_x}+#{upper_left_y} -resize #{desired_width}x#{desired_height} +repage")
|
159
|
+
end
|
160
|
+
|
161
|
+
def flip(temp_object)
|
162
|
+
convert(temp_object, "-flip")
|
163
|
+
end
|
164
|
+
|
165
|
+
def flop(temp_object)
|
166
|
+
convert(temp_object, "-flop")
|
167
|
+
end
|
168
|
+
|
169
|
+
def greyscale(temp_object)
|
170
|
+
convert(temp_object, "-colorspace Gray")
|
171
|
+
end
|
172
|
+
alias grayscale greyscale
|
173
|
+
|
174
|
+
def resize_and_crop(temp_object, opts={})
|
175
|
+
if !opts[:width] && !opts[:height]
|
176
|
+
return temp_object
|
177
|
+
elsif !opts[:width] || !opts[:height]
|
178
|
+
attrs = identify(temp_object)
|
179
|
+
opts[:width] ||= attrs[:width]
|
180
|
+
opts[:height] ||= attrs[:height]
|
181
|
+
end
|
182
|
+
|
183
|
+
opts[:gravity] ||= 'c'
|
184
|
+
|
185
|
+
opts[:resize] = "#{opts[:width]}x#{opts[:height]}^^"
|
186
|
+
crop(temp_object, opts)
|
187
|
+
end
|
188
|
+
|
189
|
+
def rotate(temp_object, amount, opts={})
|
190
|
+
convert(temp_object, "-rotate #{amount}#{opts[:qualifier]}")
|
191
|
+
end
|
192
|
+
|
193
|
+
def strip(temp_object)
|
194
|
+
convert(temp_object, "-strip")
|
195
|
+
end
|
196
|
+
|
197
|
+
def thumb(temp_object, geometry)
|
198
|
+
case geometry
|
199
|
+
when RESIZE_GEOMETRY
|
200
|
+
resize(temp_object, geometry)
|
201
|
+
when CROPPED_RESIZE_GEOMETRY
|
202
|
+
resize_and_crop(temp_object, :width => $1, :height => $2, :gravity => $3)
|
203
|
+
when CROP_GEOMETRY
|
204
|
+
crop(temp_object,
|
205
|
+
:width => $1,
|
206
|
+
:height => $2,
|
207
|
+
:x => $3,
|
208
|
+
:y => $4,
|
209
|
+
:gravity => $5
|
210
|
+
)
|
211
|
+
else raise ArgumentError, "Didn't recognise the geometry string #{geometry}"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def convert(temp_object, args='', format=nil)
|
216
|
+
format ? [super, {:format => format.to_sym}] : super
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
3
|
+
module ImageResizer
|
4
|
+
module Shell
|
5
|
+
|
6
|
+
include Configurable
|
7
|
+
configurable_attr :log_commands, false
|
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
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module ImageResizer
|
6
|
+
|
7
|
+
# A TempObject is used for HOLDING DATA.
|
8
|
+
# It's the thing that is passed between the datastore, the processor and the encoder, and is useful
|
9
|
+
# for separating how the data was created and how it is later accessed.
|
10
|
+
#
|
11
|
+
# You can initialize it various ways:
|
12
|
+
#
|
13
|
+
# temp_object = ImageResizer::TempObject.new('this is the content') # with a String
|
14
|
+
# temp_object = ImageResizer::TempObject.new(Pathname.new('path/to/content')) # with a Pathname
|
15
|
+
# temp_object = ImageResizer::TempObject.new(File.new('path/to/content')) # with a File
|
16
|
+
# temp_object = ImageResizer::TempObject.new(some_tempfile) # with a Tempfile
|
17
|
+
# temp_object = ImageResizer::TempObject.new(some_other_temp_object) # with another TempObject
|
18
|
+
#
|
19
|
+
# However, no matter how it was initialized, you can always access the data a number of ways:
|
20
|
+
#
|
21
|
+
# temp_object.data # returns a data string
|
22
|
+
# temp_object.file # returns a file object holding the data
|
23
|
+
# temp_object.path # returns a path for the file
|
24
|
+
#
|
25
|
+
# The data/file are created lazily, something which you may wish to take advantage of.
|
26
|
+
#
|
27
|
+
# For example, if a TempObject is initialized with a file, and temp_object.data is never called, then
|
28
|
+
# the data string will never be loaded into memory.
|
29
|
+
#
|
30
|
+
# Conversely, if the TempObject is initialized with a data string, and neither temp_object.file nor temp_object.path
|
31
|
+
# are ever called, then the filesystem will never be hit.
|
32
|
+
#
|
33
|
+
class TempObject
|
34
|
+
|
35
|
+
include HasFilename
|
36
|
+
|
37
|
+
# Exceptions
|
38
|
+
class Closed < RuntimeError; end
|
39
|
+
|
40
|
+
# Instance Methods
|
41
|
+
|
42
|
+
def initialize(obj, meta={})
|
43
|
+
if obj.is_a? TempObject
|
44
|
+
@data = obj.get_data
|
45
|
+
@tempfile = obj.get_tempfile
|
46
|
+
@pathname = obj.get_pathname
|
47
|
+
elsif obj.is_a? String
|
48
|
+
@data = obj
|
49
|
+
elsif obj.is_a? Tempfile
|
50
|
+
@tempfile = obj
|
51
|
+
elsif obj.is_a? File
|
52
|
+
@pathname = Pathname.new(obj.path)
|
53
|
+
elsif obj.is_a? Pathname
|
54
|
+
@pathname = obj
|
55
|
+
elsif obj.respond_to?(:tempfile)
|
56
|
+
@tempfile = obj.tempfile
|
57
|
+
elsif obj.respond_to?(:path) # e.g. Rack::Test::UploadedFile
|
58
|
+
@pathname = Pathname.new(obj.path)
|
59
|
+
else
|
60
|
+
raise ArgumentError, "#{self.class.name} must be initialized with a String, a Pathname, a File, a Tempfile, another TempObject, something that responds to .tempfile, or something that responds to .path"
|
61
|
+
end
|
62
|
+
|
63
|
+
@tempfile.close if @tempfile
|
64
|
+
|
65
|
+
# Original filename
|
66
|
+
@original_filename = if obj.respond_to?(:original_filename)
|
67
|
+
obj.original_filename
|
68
|
+
elsif @pathname
|
69
|
+
@pathname.basename.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
# Meta
|
73
|
+
@meta = meta
|
74
|
+
@meta[:name] ||= @original_filename if @original_filename
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :original_filename
|
78
|
+
attr_accessor :meta
|
79
|
+
|
80
|
+
def name
|
81
|
+
meta[:name]
|
82
|
+
end
|
83
|
+
|
84
|
+
def name=(name)
|
85
|
+
meta[:name] = name
|
86
|
+
end
|
87
|
+
|
88
|
+
def data
|
89
|
+
raise Closed, "can't read data as TempObject has been closed" if closed?
|
90
|
+
@data ||= file{|f| f.read }
|
91
|
+
end
|
92
|
+
|
93
|
+
def tempfile
|
94
|
+
raise Closed, "can't read from tempfile as TempObject has been closed" if closed?
|
95
|
+
@tempfile ||= begin
|
96
|
+
case
|
97
|
+
when @data
|
98
|
+
@tempfile = new_tempfile(@data)
|
99
|
+
when @pathname
|
100
|
+
@tempfile = copy_to_tempfile(@pathname.expand_path)
|
101
|
+
end
|
102
|
+
@tempfile
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def file(&block)
|
107
|
+
f = tempfile.open
|
108
|
+
tempfile.binmode
|
109
|
+
if block_given?
|
110
|
+
ret = yield f
|
111
|
+
tempfile.close unless tempfile.closed?
|
112
|
+
else
|
113
|
+
ret = f
|
114
|
+
end
|
115
|
+
ret
|
116
|
+
end
|
117
|
+
|
118
|
+
def path
|
119
|
+
@pathname ? @pathname.expand_path.to_s : tempfile.path
|
120
|
+
end
|
121
|
+
|
122
|
+
def size
|
123
|
+
@data ? @data.bytesize : File.size(path)
|
124
|
+
end
|
125
|
+
|
126
|
+
def each(&block)
|
127
|
+
to_io do |io|
|
128
|
+
while part = io.read(block_size)
|
129
|
+
yield part
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_file(path, opts={})
|
135
|
+
mode = opts[:mode] || 0644
|
136
|
+
prepare_path(path) unless opts[:mkdirs] == false
|
137
|
+
if @data
|
138
|
+
File.open(path, 'wb', mode){|f| f.write(@data) }
|
139
|
+
else
|
140
|
+
FileUtils.cp(self.path, path)
|
141
|
+
File.chmod(mode, path)
|
142
|
+
end
|
143
|
+
File.new(path, 'rb')
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_io(&block)
|
147
|
+
@data ? StringIO.open(@data, 'rb', &block) : file(&block)
|
148
|
+
end
|
149
|
+
|
150
|
+
def close
|
151
|
+
@tempfile.close! if @tempfile
|
152
|
+
@data = nil
|
153
|
+
@closed = true
|
154
|
+
end
|
155
|
+
|
156
|
+
def closed?
|
157
|
+
!!@closed
|
158
|
+
end
|
159
|
+
|
160
|
+
def inspect
|
161
|
+
content_string = case
|
162
|
+
when @data
|
163
|
+
data_string = size > 20 ? "#{@data[0..20]}..." : @data
|
164
|
+
"data=#{data_string.inspect}"
|
165
|
+
when @pathname then "pathname=#{@pathname.inspect}"
|
166
|
+
when @tempfile then "tempfile=#{@tempfile.inspect}"
|
167
|
+
end
|
168
|
+
"<#{self.class.name} #{content_string} >"
|
169
|
+
end
|
170
|
+
|
171
|
+
def unique_id
|
172
|
+
@unique_id ||= "#{object_id}#{rand(1000000)}"
|
173
|
+
end
|
174
|
+
|
175
|
+
protected
|
176
|
+
|
177
|
+
# We don't use normal accessors here because #data etc. do more than just return the instance var
|
178
|
+
def get_data
|
179
|
+
@data
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_pathname
|
183
|
+
@pathname
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_tempfile
|
187
|
+
@tempfile
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def block_size
|
193
|
+
8192
|
194
|
+
end
|
195
|
+
|
196
|
+
def copy_to_tempfile(path)
|
197
|
+
tempfile = new_tempfile
|
198
|
+
FileUtils.cp path, tempfile.path
|
199
|
+
tempfile
|
200
|
+
end
|
201
|
+
|
202
|
+
def new_tempfile(content=nil)
|
203
|
+
tempfile = ext ? Tempfile.new(['ImageResizer', ".#{ext}"]) : Tempfile.new('ImageResizer')
|
204
|
+
tempfile.binmode
|
205
|
+
tempfile.write(content) if content
|
206
|
+
tempfile.close
|
207
|
+
tempfile
|
208
|
+
end
|
209
|
+
|
210
|
+
def prepare_path(path)
|
211
|
+
dir = File.dirname(path)
|
212
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module ImageResizer
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
include Shell
|
7
|
+
include Loggable
|
8
|
+
include Configurable
|
9
|
+
configurable_attr :convert_command, "convert"
|
10
|
+
configurable_attr :identify_command, "identify"
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def convert(temp_object=nil, args='', format=nil)
|
15
|
+
tempfile = new_tempfile(format)
|
16
|
+
run convert_command, %(#{quote(temp_object.path) if temp_object} #{args} #{quote(tempfile.path)})
|
17
|
+
tempfile
|
18
|
+
end
|
19
|
+
|
20
|
+
def identify(temp_object)
|
21
|
+
# example of details string:
|
22
|
+
# myimage.png PNG 200x100 200x100+0+0 8-bit DirectClass 31.2kb
|
23
|
+
format, width, height, depth = raw_identify(temp_object).scan(/([A-Z0-9]+) (\d+)x(\d+) .+ (\d+)-bit/)[0]
|
24
|
+
{
|
25
|
+
:format => format.downcase.to_sym,
|
26
|
+
:width => width.to_i,
|
27
|
+
:height => height.to_i,
|
28
|
+
:depth => depth.to_i
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def raw_identify(temp_object, args='')
|
33
|
+
run identify_command, "#{args} #{quote(temp_object.path)}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def new_tempfile(ext=nil)
|
37
|
+
tempfile = ext ? Tempfile.new(['ImageResizer', ".#{ext}"]) : Tempfile.new('ImageResizer')
|
38
|
+
tempfile.binmode
|
39
|
+
tempfile.close
|
40
|
+
tempfile
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
require 'image_resizer/has_filename'
|
3
|
+
require 'image_resizer/loggable'
|
4
|
+
require 'image_resizer/configurable'
|
5
|
+
require 'image_resizer/shell'
|
6
|
+
require 'image_resizer/utils'
|
7
|
+
require 'image_resizer/analyzer'
|
8
|
+
require 'image_resizer/encoder'
|
9
|
+
require 'image_resizer/processor'
|
10
|
+
require 'image_resizer/temp_object'
|
Binary file
|
data/samples/a.jp2
ADDED
Binary file
|
data/samples/beach.jpg
ADDED
Binary file
|
data/samples/beach.png
ADDED
Binary file
|
data/samples/egg.png
ADDED
Binary file
|
Binary file
|
data/samples/round.gif
ADDED
Binary file
|
data/samples/sample.docx
ADDED
Binary file
|
data/samples/taj.jpg
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ImageResizer::Analyzer do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@image = ImageResizer::TempObject.new(SAMPLES_DIR.join('beach.png'))
|
7
|
+
@analyzer = ImageResizer::Analyzer.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return the width" do
|
11
|
+
@analyzer.width(@image).should == 280
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return the height" do
|
15
|
+
@analyzer.height(@image).should == 355
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return the aspect ratio" do
|
19
|
+
@analyzer.aspect_ratio(@image).should == (280.0/355.0)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should say if it's portrait" do
|
23
|
+
@analyzer.portrait?(@image).should be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should say if it's landscape" do
|
27
|
+
@analyzer.landscape?(@image).should be_false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return the number of colours" do
|
31
|
+
@analyzer.number_of_colours(@image).should == 34703
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return the depth" do
|
35
|
+
@analyzer.depth(@image).should == 8
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return the format" do
|
39
|
+
@analyzer.format(@image).should == :png
|
40
|
+
end
|
41
|
+
|
42
|
+
%w(width height aspect_ratio number_of_colours depth format portrait? landscape?).each do |meth|
|
43
|
+
it "should throw unable_to_handle in #{meth.inspect} if it's not an image file" do
|
44
|
+
suppressing_stderr do
|
45
|
+
temp_object = ImageResizer::TempObject.new('blah')
|
46
|
+
lambda{
|
47
|
+
@analyzer.send(meth, temp_object)
|
48
|
+
}.should throw_symbol(:unable_to_handle)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should say if it's an image" do
|
54
|
+
@analyzer.image?(@image).should == true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should say if it's not an image" do
|
58
|
+
suppressing_stderr do
|
59
|
+
@analyzer.image?(ImageResizer::TempObject.new('blah')).should == false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should work for images with spaces in the filename" do
|
64
|
+
image = ImageResizer::TempObject.new(SAMPLES_DIR.join('white pixel.png'))
|
65
|
+
@analyzer.width(image).should == 1
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should work (width) for images with capital letter extensions" do
|
69
|
+
image = ImageResizer::TempObject.new(SAMPLES_DIR.join('DSC02119.JPG'))
|
70
|
+
@analyzer.width(image).should == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should work (width) for images with numbers in the format" do
|
74
|
+
image = ImageResizer::TempObject.new(SAMPLES_DIR.join('a.jp2'))
|
75
|
+
@analyzer.width(image).should == 1
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|