has_image 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -1
- data/lib/has_image/processor.rb +25 -3
- data/lib/has_image/storage.rb +25 -4
- data/lib/has_image.rb +4 -3
- data/test/processor_test.rb +17 -1
- data/test/storage_test.rb +7 -0
- metadata +1 -1
data/CHANGELOG
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
2008-07-28 Norman Clarke <norman@randomba.org>
|
2
2
|
|
3
|
+
* Added sorted thumbnail processing. This improves thumbnail generation
|
4
|
+
speed by about 25% for 4.5 meg jpegs with 5 thumbnails.
|
5
|
+
* Fixed broken resize for non-fixed-width thumbnails.
|
6
|
+
* Added check for bad geometry strings.
|
3
7
|
* Added dependencies and Rubyforge project to gemspec, updated docs.
|
4
|
-
|
8
|
+
|
5
9
|
2008-07-25 Norman Clarke <norman@randomba.org>
|
6
10
|
|
7
11
|
* First public release.
|
data/lib/has_image/processor.rb
CHANGED
@@ -8,6 +8,21 @@ module HasImage
|
|
8
8
|
attr_accessor :options
|
9
9
|
|
10
10
|
class << self
|
11
|
+
|
12
|
+
# Given a geometry string, return the maxium possible output dimensions.
|
13
|
+
# For example:
|
14
|
+
# area("50x50>") == 2500
|
15
|
+
def area(dimensions)
|
16
|
+
dimensions.split("x")[0].to_i * dimensions.split("x")[1].to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
# "The form of an {extended geometry
|
20
|
+
# string}[http://www.imagemagick.org/script/command-line-options.php?#resize] is
|
21
|
+
# <width>x<height>{+-}<xoffset>{+-}<yoffset>{%}{!}{<}{>}"
|
22
|
+
def geometry_string_valid?(string)
|
23
|
+
string =~ /\A[\d]*x[\d]*([+-][0-9][+-][0-9])?[%@!<>^]?\Z/
|
24
|
+
end
|
25
|
+
|
11
26
|
# Arg should be either a file, or a path. This runs ImageMagick's
|
12
27
|
# "identify" command and looks for an exit status indicating an error. If
|
13
28
|
# there is no error, then ImageMagick has identified the file as something
|
@@ -30,6 +45,7 @@ module HasImage
|
|
30
45
|
# format if necessary. The size should be a valid ImageMagick {geometry
|
31
46
|
# string}[http://www.imagemagick.org/script/command-line-options.php#resize].
|
32
47
|
def resize(file, size)
|
48
|
+
raise InvalidGeometryError.new unless Processor.geometry_string_valid?(size)
|
33
49
|
silence_stderr do
|
34
50
|
path = file.respond_to?(:path) ? file.path : file
|
35
51
|
file.close if file.respond_to?(:close) && !file.closed?
|
@@ -55,9 +71,15 @@ module HasImage
|
|
55
71
|
@image.combine_options do |commands|
|
56
72
|
commands.send("auto-orient".to_sym)
|
57
73
|
commands.strip
|
58
|
-
|
59
|
-
|
60
|
-
|
74
|
+
# Fixed-dimension images
|
75
|
+
if size =~ /\A[\d]*x[\d]*!?\Z/
|
76
|
+
commands.resize "#{size}^"
|
77
|
+
commands.gravity "center"
|
78
|
+
commands.extent size
|
79
|
+
# Non-fixed-dimension images
|
80
|
+
else
|
81
|
+
commands.resize "#{size}"
|
82
|
+
end
|
61
83
|
commands.quality options[:output_quality]
|
62
84
|
end
|
63
85
|
end
|
data/lib/has_image/storage.rb
CHANGED
@@ -105,6 +105,26 @@ module HasImage
|
|
105
105
|
options[:convert_to].to_s.downcase.gsub("jpeg", "jpg")
|
106
106
|
end
|
107
107
|
|
108
|
+
# Returns the options[:thumbnails] hash, coverted to an array and sorted
|
109
|
+
# by thumbnail area, highest to lowest. For example:
|
110
|
+
#
|
111
|
+
# options[:thumbnails] == {:a => "20x20", :b => "2x2", :c => "100x100"}
|
112
|
+
# sorted_thumbnails == [[:c, "100x100"], [:a, "20x20"], [:b, "2x2"]]
|
113
|
+
#
|
114
|
+
# This is done to speed up processing images with several thumbnails. Rather
|
115
|
+
# than create the thumbnail starting from the highest quality version each
|
116
|
+
# time, the next biggest thumbnail is used as the base image for its
|
117
|
+
# immediately smaller variant. For example, given an image with 3 thumbnails
|
118
|
+
# HasImage will use the 800x800 as the basis of the 500x500, and then the
|
119
|
+
# 500x500 as the basis of the 200x200, etc. My benchmarks showed that this
|
120
|
+
# will speed up processing by up to around 25% for a 4.5 meg JPEG with 5
|
121
|
+
# thumbnails.
|
122
|
+
def sorted_thumbnails
|
123
|
+
options[:thumbnails].to_a.sort do |b,a|
|
124
|
+
Processor.area(a[1]) <=> Processor.area(b[1])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
108
128
|
private
|
109
129
|
|
110
130
|
# File name, plus thumbnail suffix, plus extension. For example:
|
@@ -141,13 +161,14 @@ module HasImage
|
|
141
161
|
def install_thumbnails(id, name)
|
142
162
|
FileUtils.mkdir_p path_for(id)
|
143
163
|
path = File.join(path_for(id), file_name_for(name))
|
144
|
-
|
145
|
-
thumb = processor.resize(path,
|
146
|
-
|
164
|
+
sorted_thumbnails.each do |t|
|
165
|
+
thumb = processor.resize(path, t[1])
|
166
|
+
path = File.join(path_for(id), file_name_for(name, t[0]))
|
167
|
+
thumb.write(path)
|
147
168
|
thumb.tempfile.close!
|
148
169
|
end
|
149
170
|
end
|
150
|
-
|
171
|
+
|
151
172
|
# Get the full path for the id. For example:
|
152
173
|
#
|
153
174
|
# /var/sites/example.org/production/public/photos/0000/0001
|
data/lib/has_image.rb
CHANGED
@@ -64,9 +64,10 @@ require 'has_image/view_helpers'
|
|
64
64
|
module HasImage
|
65
65
|
|
66
66
|
class ProcessorError < StandardError ; end
|
67
|
-
class StorageError < StandardError ; end
|
68
|
-
class FileTooBigError < StorageError ; end
|
69
|
-
class FileTooSmallError < StorageError ; end
|
67
|
+
class StorageError < StandardError ; end
|
68
|
+
class FileTooBigError < StorageError ; end
|
69
|
+
class FileTooSmallError < StorageError ; end
|
70
|
+
class InvalidGeometryError < ProcessorError ; end
|
70
71
|
|
71
72
|
class << self
|
72
73
|
|
data/test/processor_test.rb
CHANGED
@@ -13,6 +13,10 @@ class StorageTest < Test::Unit::TestCase
|
|
13
13
|
return @temp_file
|
14
14
|
end
|
15
15
|
|
16
|
+
def test_area
|
17
|
+
assert_equal 2500, HasImage::Processor.area("50x50>")
|
18
|
+
end
|
19
|
+
|
16
20
|
def test_detect_valid_image
|
17
21
|
assert HasImage::Processor.valid?(File.dirname(__FILE__) + "/../test_rails/fixtures/image.jpg")
|
18
22
|
end
|
@@ -28,12 +32,24 @@ class StorageTest < Test::Unit::TestCase
|
|
28
32
|
def test_detect_invalid_image_from_tmp_file
|
29
33
|
assert !HasImage::Processor.valid?(temp_file("bad_image.jpg"))
|
30
34
|
end
|
35
|
+
|
36
|
+
def test_resize_with_invalid_geometry
|
37
|
+
@processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
|
38
|
+
assert_raises HasImage::InvalidGeometryError do
|
39
|
+
@processor.resize(temp_file("image.jpg"), "bad_geometry")
|
40
|
+
end
|
41
|
+
end
|
31
42
|
|
32
|
-
def
|
43
|
+
def test_resize_fixed
|
33
44
|
@processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
|
34
45
|
assert @processor.resize(temp_file("image.jpg"), "100x100")
|
35
46
|
end
|
36
47
|
|
48
|
+
def test_resize_unfixed
|
49
|
+
@processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
|
50
|
+
assert @processor.resize(temp_file("image.jpg"), "1024x768>")
|
51
|
+
end
|
52
|
+
|
37
53
|
def test_resize_and_convert
|
38
54
|
@processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
|
39
55
|
assert @processor.resize(temp_file("image.png"), "100x100")
|
data/test/storage_test.rb
CHANGED
@@ -16,6 +16,13 @@ class StorageTest < Test::Unit::TestCase
|
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
19
|
+
def test_sorted_thumbnails
|
20
|
+
thumbs = {:a => "20x20", :b => "2x2", :c => "100x100"}
|
21
|
+
sorted = [[:c, "100x100"], [:a, "20x20"], [:b, "2x2"]]
|
22
|
+
@storage = HasImage::Storage.new(default_options.merge(:thumbnails => thumbs))
|
23
|
+
assert_equal sorted, @storage.send(:sorted_thumbnails)
|
24
|
+
end
|
25
|
+
|
19
26
|
def test_partitioned_path
|
20
27
|
assert_equal(["0001", "2345"], HasImage::Storage.partitioned_path("12345"))
|
21
28
|
end
|