has_image 0.1.1 → 0.1.2

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/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.
@@ -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
- commands.resize "#{size}^"
59
- commands.gravity "center"
60
- commands.extent size
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
@@ -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
- options[:thumbnails].each do |thumb_name, size|
145
- thumb = processor.resize(path, size)
146
- thumb.write(File.join(path_for(id), file_name_for(name, thumb_name)))
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
 
@@ -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 test_resize
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_image
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Norman Clarke