mini_magick 1.2.5 → 1.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/README.rdoc CHANGED
@@ -1,7 +1,8 @@
1
1
  = MiniMagick
2
2
 
3
- A ruby wrapper for ImageMagick command line.
3
+ A ruby wrapper for ImageMagick or GraphicsMagick command line.
4
4
 
5
+ Tested on both Ruby 1.9.2 and Ruby 1.8.7.
5
6
 
6
7
  == Why?
7
8
 
@@ -69,4 +70,4 @@ http://www.imagemagick.org/script/command-line-options.php#format
69
70
 
70
71
  == Requirements
71
72
 
72
- You must have ImageMagick installed.
73
+ You must have ImageMagick or GraphicsMagick installed.
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
4
5
 
5
6
  $:.unshift(File.dirname(__FILE__) + "/lib")
6
7
  require 'mini_magick'
@@ -10,7 +11,7 @@ task :default => :test
10
11
 
11
12
  desc 'Test the mini_magick plugin.'
12
13
  Rake::TestTask.new(:test) do |t|
13
- t.libs << 'lib'
14
+ t.libs << 'test'
14
15
  t.pattern = 'test/**/*_test.rb'
15
16
  t.verbose = true
16
17
  end
@@ -25,44 +26,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
25
26
  rdoc.rdoc_files.include('lib/**/*.rb')
26
27
  end
27
28
 
28
- # Jewler tasks
29
-
30
- begin
31
- require 'jeweler'
32
- Jeweler::Tasks.new do |gemspec|
33
- gemspec.name = "mini_magick"
34
- gemspec.summary = "Manipulate images with minimal use of memory."
35
- gemspec.email = "probablycorey@gmail.com"
36
- gemspec.homepage = "http://github.com/probablycorey/mini_magick"
37
- gemspec.authors = ["Corey Johnson"]
38
- gemspec.rubyforge_project = "mini-magick"
39
- end
40
- rescue LoadError
41
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
42
- end
43
-
44
- begin
45
- require 'rake/contrib/sshpublisher'
46
- namespace :rubyforge do
47
-
48
- desc "Release gem and RDoc documentation to RubyForge"
49
- task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
50
-
51
- namespace :release do
52
- desc "Publish RDoc to RubyForge."
53
- task :docs => [:rdoc] do
54
- config = YAML.load(
55
- File.read(File.expand_path('~/.rubyforge/user-config.yml'))
56
- )
57
-
58
- host = "#{config['username']}@rubyforge.org"
59
- remote_dir = "/var/www/gforge-projects/the-perfect-gem/"
60
- local_dir = 'rdoc'
61
-
62
- Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
63
- end
64
- end
65
- end
66
- rescue LoadError
67
- puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
29
+ spec = eval(File.read('mini_magick.gemspec'))
30
+ Rake::GemPackageTask.new(spec) do |pkg|
31
+ pkg.gem_spec = spec
68
32
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.5
1
+ 1.3.3
@@ -0,0 +1,2 @@
1
+ require 'mini_magick'
2
+ MiniMagick.processor = :gm
data/lib/mini_magick.rb CHANGED
@@ -1,12 +1,22 @@
1
- require "open-uri"
2
- require "stringio"
3
- require "fileutils"
4
- require "open3"
5
-
6
- require File.join(File.dirname(__FILE__), '/image_temp_file')
1
+ require 'tempfile'
7
2
 
8
3
  module MiniMagick
9
- class MiniMagickError < RuntimeError; end
4
+ class << self
5
+ attr_accessor :processor
6
+ attr_accessor :use_subexec
7
+ attr_accessor :timeout
8
+ end
9
+
10
+ MOGRIFY_COMMANDS = %w{adaptive-blur adaptive-resize adaptive-sharpen adjoin affine alpha annotate antialias append authenticate auto-gamma auto-level auto-orient background bench iterations bias black-threshold blue-primary point blue-shift factor blur border bordercolor brightness-contrast caption string cdl filename channel type charcoal radius chop clip clamp clip-mask filename clip-path id clone index clut contrast-stretch coalesce colorize color-matrix colors colorspace type combine comment string compose operator composite compress type contrast convolve coefficients crop cycle amount decipher filename debug events define format:option deconstruct delay delete index density depth despeckle direction type display server dispose method distort type coefficients dither method draw string edge radius emboss radius encipher filename encoding type endian type enhance equalize evaluate operator evaluate-sequence operator extent extract family name fft fill filter type flatten flip floodfill flop font name format string frame function name fuzz distance fx expression gamma gaussian-blur geometry gravity type green-primary point help identify ifft implode amount insert index intent type interlace type interline-spacing interpolate method interword-spacing kerning label string lat layers method level limit type linear-stretch liquid-rescale log format loop iterations mask filename mattecolor median radius modulate monitor monochrome morph morphology method kernel motion-blur negate noise radius normalize opaque ordered-dither NxN orient type page paint radius ping pointsize polaroid angle posterize levels precision preview type print string process image-filter profile filename quality quantizespace quiet radial-blur angle raise random-threshold low,high red-primary point regard-warnings region remap filename render repage resample resize respect-parentheses roll rotate degrees sample sampling-factor scale scene seed segments selective-blur separate sepia-tone threshold set attribute shade degrees shadow sharpen shave shear sigmoidal-contrast size sketch solarize threshold splice spread radius strip stroke strokewidth stretch type style type swap indexes swirl degrees texture filename threshold thumbnail tile filename tile-offset tint transform transparent transparent-color transpose transverse treedepth trim type type undercolor unique-colors units type unsharp verbose version view vignette virtual-pixel method wave weight type white-point point white-threshold write filename}
11
+
12
+ # Subexec only works with 1.9
13
+ if RUBY_VERSION[0..2].to_f < 1.8
14
+ self.use_subexec = true
15
+ require 'subexec'
16
+ end
17
+
18
+ class Error < RuntimeError; end
19
+ class Invalid < StandardError; end
10
20
 
11
21
  class Image
12
22
  attr :path
@@ -18,7 +28,7 @@ module MiniMagick
18
28
  class << self
19
29
  def from_blob(blob, ext = nil)
20
30
  begin
21
- tempfile = ImageTempFile.new(ext)
31
+ tempfile = Tempfile.new(['mini_magick', ext.to_s])
22
32
  tempfile.binmode
23
33
  tempfile.write(blob)
24
34
  ensure
@@ -65,7 +75,12 @@ module MiniMagick
65
75
  # Get the EXIF original capture as a Time object
66
76
  Time.local(*self["EXIF:DateTimeOriginal"].split(/:|\s+/)) rescue nil
67
77
  when /^EXIF\:/i
68
- run_command('identify', '-format', "\"%[#{value}]\"", @path).chop
78
+ result = run_command('identify', '-format', "\"%[#{value}]\"", @path).chop
79
+ if result.include?(",")
80
+ read_character_data(result)
81
+ else
82
+ result
83
+ end
69
84
  else
70
85
  run_command('identify', '-format', "\"#{value}\"", @path).split("\n")[0]
71
86
  end
@@ -84,14 +99,14 @@ module MiniMagick
84
99
  run_command("mogrify", "-format", format, @path)
85
100
 
86
101
  old_path = @path.dup
87
- @path.sub!(/(\.\w+)?$/, ".#{format}")
102
+ @path.sub!(/(\.\w*)?$/, ".#{format}")
88
103
  File.delete(old_path) unless old_path == @path
89
104
 
90
105
  unless File.exists?(@path)
91
106
  begin
92
107
  FileUtils.copy_file(@path.sub(".#{format}", "-#{page}.#{format}"), @path)
93
- rescue e
94
- raise MiniMagickError, "Unable to format to #{format}; #{e}" unless File.exist?(@path)
108
+ rescue => ex
109
+ raise MiniMagickError, "Unable to format to #{format}; #{ex}" unless File.exist?(@path)
95
110
  end
96
111
  end
97
112
  ensure
@@ -99,6 +114,12 @@ module MiniMagick
99
114
  File.unlink(fname)
100
115
  end
101
116
  end
117
+
118
+ # Collapse images with sequences to the first frame (ie. animated gifs) and
119
+ # preserve quality
120
+ def collapse!
121
+ run_command("mogrify", "-quality", "100", "#{path}[0]")
122
+ end
102
123
 
103
124
  # Writes the temporary image that we are using for processing to the output path
104
125
  def write(output_path)
@@ -118,9 +139,15 @@ module MiniMagick
118
139
  # If an unknown method is called then it is sent through the morgrify program
119
140
  # Look here to find all the commands (http://www.imagemagick.org/script/mogrify.php)
120
141
  def method_missing(symbol, *args)
121
- args.push(@path) # push the path onto the end
122
- run_command("mogrify", "-#{symbol}", *args)
123
- self
142
+ guessed_command_name = symbol.to_s.gsub('_','-')
143
+
144
+ if MOGRIFY_COMMANDS.include?(guessed_command_name)
145
+ args.push(@path) # push the path onto the end
146
+ run_command("mogrify", "-#{guessed_command_name}", *args)
147
+ self
148
+ else
149
+ super(symbol, *args)
150
+ end
124
151
  end
125
152
 
126
153
  # You can use multiple commands together using this method
@@ -143,22 +170,57 @@ module MiniMagick
143
170
  def run_command(command, *args)
144
171
  args.collect! do |arg|
145
172
  # args can contain characters like '>' so we must escape them, but don't quote switches
146
- if arg !~ /^\+|\-/
173
+ if arg !~ /^[\+\-]/
147
174
  "\"#{arg}\""
148
175
  else
149
176
  arg.to_s
150
177
  end
151
178
  end
152
179
 
153
- command = "#{command} #{args.join(' ')}"
154
- output = `#{command} 2>&1`
180
+ command = "#{MiniMagick.processor} #{command} #{args.join(' ')}".strip
155
181
 
156
- if $?.exitstatus != 0
157
- raise MiniMagickError, "ImageMagick command (#{command.inspect}) failed: #{{:status_code => $?, :output => output}.inspect}"
182
+ if ::MiniMagick.use_subexec
183
+ sub = Subexec.run(command, :timeout => MiniMagick.timeout)
184
+ exit_status = sub.exitstatus
185
+ output = sub.output
186
+ else
187
+ output = `#{command} 2>&1`
188
+ exit_status = $?.exitstatus
189
+ end
190
+
191
+ if exit_status != 0
192
+ # Clean up after ourselves in case of an error
193
+ destroy!
194
+
195
+ # Raise the appropriate error
196
+ if output =~ /no decode delegate/i || output =~ /did not return an image/i
197
+ raise Invalid, output
198
+ else
199
+ # TODO: should we do something different if the command times out ...?
200
+ # its definitely better for logging.. otherwise we dont really know
201
+ raise Error, "Command (#{command.inspect}) failed: #{{:status_code => exit_status, :output => output}.inspect}"
202
+ end
158
203
  else
159
204
  output
160
205
  end
161
206
  end
207
+
208
+ def destroy!
209
+ return if tempfile.nil?
210
+ File.unlink(tempfile.path)
211
+ @tempfile = nil
212
+ end
213
+
214
+ private
215
+ # Sometimes we get back a list of character values
216
+ def read_character_data(list_of_characters)
217
+ chars = list_of_characters.gsub(" ", "").split(",")
218
+ result = ""
219
+ chars.each do |val|
220
+ result << ("%c" % val.to_i)
221
+ end
222
+ result
223
+ end
162
224
  end
163
225
 
164
226
  class CommandBuilder
@@ -169,7 +231,7 @@ module MiniMagick
169
231
  end
170
232
 
171
233
  def method_missing(symbol, *args)
172
- @args << "-#{symbol}"
234
+ @args << "-#{symbol.to_s.gsub('_','-')}"
173
235
  @args += args
174
236
  end
175
237
 
Binary file
data/test/animation.gif CHANGED
Binary file
@@ -1,5 +1,6 @@
1
+ require 'rubygems'
1
2
  require 'test/unit'
2
- require File.join(File.dirname(__FILE__), '../lib/mini_magick')
3
+ require File.expand_path('../../lib/mini_magick', __FILE__)
3
4
 
4
5
  class CommandBuilderTest < Test::Unit::TestCase
5
6
  include MiniMagick
@@ -17,4 +18,10 @@ class CommandBuilderTest < Test::Unit::TestCase
17
18
  c.lingo "mome fingo"
18
19
  assert_equal "-resize 30x40 -input 1 3 4 -lingo mome fingo", c.args.join(" ")
19
20
  end
21
+
22
+ def test_dashed
23
+ c = CommandBuilder.new
24
+ c.auto_orient
25
+ assert_equal "-auto-orient", c.args.join(" ")
26
+ end
20
27
  end
data/test/image_test.rb CHANGED
@@ -1,5 +1,8 @@
1
+ require 'rubygems'
1
2
  require 'test/unit'
2
- require File.join(File.dirname(__FILE__), '../lib/mini_magick')
3
+ require File.expand_path('../../lib/mini_magick', __FILE__)
4
+
5
+ #MiniMagick.processor = :gm
3
6
 
4
7
  class ImageTest < Test::Unit::TestCase
5
8
  include MiniMagick
@@ -7,24 +10,29 @@ class ImageTest < Test::Unit::TestCase
7
10
  CURRENT_DIR = File.dirname(File.expand_path(__FILE__)) + "/"
8
11
 
9
12
  SIMPLE_IMAGE_PATH = CURRENT_DIR + "simple.gif"
13
+ MINUS_IMAGE_PATH = CURRENT_DIR + "simple-minus.gif"
10
14
  TIFF_IMAGE_PATH = CURRENT_DIR + "leaves.tiff"
11
15
  NOT_AN_IMAGE_PATH = CURRENT_DIR + "not_an_image.php"
12
16
  GIF_WITH_JPG_EXT = CURRENT_DIR + "actually_a_gif.jpg"
13
17
  EXIF_IMAGE_PATH = CURRENT_DIR + "trogdor.jpg"
18
+ ORIENTED_IMAGE_PATH = CURRENT_DIR + "oliver.jpg"
14
19
  ANIMATION_PATH = CURRENT_DIR + "animation.gif"
15
20
 
16
21
  def test_image_from_blob
17
22
  File.open(SIMPLE_IMAGE_PATH, "rb") do |f|
18
23
  image = Image.from_blob(f.read)
24
+ image.destroy!
19
25
  end
20
26
  end
21
27
 
22
28
  def test_image_from_file
23
29
  image = Image.from_file(SIMPLE_IMAGE_PATH)
30
+ image.destroy!
24
31
  end
25
32
 
26
33
  def test_image_new
27
34
  image = Image.new(SIMPLE_IMAGE_PATH)
35
+ image.destroy!
28
36
  end
29
37
 
30
38
  def test_image_write
@@ -37,11 +45,13 @@ class ImageTest < Test::Unit::TestCase
37
45
  ensure
38
46
  File.delete output_path
39
47
  end
48
+ image.destroy!
40
49
  end
41
50
 
42
51
  def test_not_an_image
43
- assert_raise(MiniMagickError) do
52
+ assert_raise(MiniMagick::Invalid) do
44
53
  image = Image.new(NOT_AN_IMAGE_PATH)
54
+ image.destroy!
45
55
  end
46
56
  end
47
57
 
@@ -51,29 +61,34 @@ class ImageTest < Test::Unit::TestCase
51
61
  assert_equal 55, image[:height]
52
62
  assert_equal [150, 55], image[:dimensions]
53
63
  assert_match(/^gif$/i, image[:format])
64
+ image.destroy!
54
65
  end
55
66
 
56
67
  def test_tiff
57
68
  image = Image.new(TIFF_IMAGE_PATH)
58
69
  assert_equal "tiff", image[:format].downcase
59
- assert_equal 295, image[:width]
60
- assert_equal 242, image[:height]
70
+ assert_equal 50, image[:width]
71
+ assert_equal 41, image[:height]
72
+ image.destroy!
61
73
  end
62
74
 
63
- def test_animation_pages
64
- image = Image.from_file(ANIMATION_PATH)
65
- image.format "png", 0
66
- assert_equal "png", image[:format].downcase
67
- end
75
+ # def test_animation_pages
76
+ # image = Image.from_file(ANIMATION_PATH)
77
+ # image.format "png", 0
78
+ # assert_equal "png", image[:format].downcase
79
+ # image.destroy!
80
+ # end
68
81
 
69
- def test_animation_size
70
- image = Image.from_file(ANIMATION_PATH)
71
- assert_equal image[:size], 76631
72
- end
82
+ # def test_animation_size
83
+ # image = Image.from_file(ANIMATION_PATH)
84
+ # assert_equal image[:size], 76631
85
+ # image.destroy!
86
+ # end
73
87
 
74
88
  def test_gif_with_jpg_format
75
89
  image = Image.new(GIF_WITH_JPG_EXT)
76
90
  assert_equal "gif", image[:format].downcase
91
+ image.destroy!
77
92
  end
78
93
 
79
94
  def test_image_resize
@@ -83,6 +98,7 @@ class ImageTest < Test::Unit::TestCase
83
98
  assert_equal 20, image[:width]
84
99
  assert_equal 30, image[:height]
85
100
  assert_match(/^gif$/i, image[:format])
101
+ image.destroy!
86
102
  end
87
103
 
88
104
  def test_image_resize_with_minimum
@@ -92,6 +108,7 @@ class ImageTest < Test::Unit::TestCase
92
108
 
93
109
  assert_equal original_width, image[:width]
94
110
  assert_equal original_height, image[:height]
111
+ image.destroy!
95
112
  end
96
113
 
97
114
  def test_image_combine_options_resize_blur
@@ -104,6 +121,17 @@ class ImageTest < Test::Unit::TestCase
104
121
  assert_equal 20, image[:width]
105
122
  assert_equal 30, image[:height]
106
123
  assert_match(/^gif$/i, image[:format])
124
+ image.destroy!
125
+ end
126
+
127
+ def test_image_combine_options_with_filename_with_minusses_in_it
128
+ image = Image.from_file(SIMPLE_IMAGE_PATH)
129
+ assert_nothing_raised do
130
+ image.combine_options do |c|
131
+ c.draw "image Over 0,0 10,10 '#{MINUS_IMAGE_PATH}'"
132
+ end
133
+ end
134
+ image.destroy!
107
135
  end
108
136
 
109
137
  def test_exif
@@ -111,6 +139,16 @@ class ImageTest < Test::Unit::TestCase
111
139
  assert_equal('0220', image["exif:ExifVersion"])
112
140
  image = Image.from_file(SIMPLE_IMAGE_PATH)
113
141
  assert_equal('', image["EXIF:ExifVersion"])
142
+ image.destroy!
143
+ end
144
+
145
+ # The test here isn't really to check to see if
146
+ # the auto-orient function of ImageMagick works,
147
+ # but to make sure we can send dashed commands.
148
+ def test_auto_rotate
149
+ image = Image.from_file(EXIF_IMAGE_PATH)
150
+ image.auto_orient
151
+ image.destroy!
114
152
  end
115
153
 
116
154
  def test_original_at
@@ -118,17 +156,20 @@ class ImageTest < Test::Unit::TestCase
118
156
  assert_equal(Time.local('2005', '2', '23', '23', '17', '24'), image[:original_at])
119
157
  image = Image.from_file(SIMPLE_IMAGE_PATH)
120
158
  assert_nil(image[:original_at])
159
+ image.destroy!
121
160
  end
122
161
 
123
162
  def test_tempfile_at_path
124
163
  image = Image.from_file(TIFF_IMAGE_PATH)
125
164
  assert_equal image.path, image.tempfile.path
165
+ image.destroy!
126
166
  end
127
167
 
128
168
  def test_tempfile_at_path_after_format
129
169
  image = Image.from_file(TIFF_IMAGE_PATH)
130
170
  image.format('png')
131
171
  assert_equal image.path, image.tempfile.path
172
+ image.destroy!
132
173
  end
133
174
 
134
175
  def test_previous_tempfile_deleted_after_format
@@ -136,12 +177,25 @@ class ImageTest < Test::Unit::TestCase
136
177
  before = image.path.dup
137
178
  image.format('png')
138
179
  assert !File.exist?(before)
180
+ image.destroy!
139
181
  end
140
-
141
- def test_mini_magick_error_when_referencing_not_existing_page
142
- image = Image.from_file(ANIMATION_PATH)
143
- assert_raises MiniMagickError do
144
- image.format('png', 31415)
182
+
183
+ def test_bad_method_bug
184
+ image = Image.from_file(TIFF_IMAGE_PATH)
185
+ begin
186
+ image.to_blog
187
+ rescue NoMethodError
188
+ assert true
145
189
  end
190
+ image.to_blob
191
+ assert true #we made it this far without error
192
+ image.destroy!
146
193
  end
194
+
195
+ # def test_mini_magick_error_when_referencing_not_existing_page
196
+ # image = Image.from_file(ANIMATION_PATH)
197
+ # image.format('png')
198
+ # assert_equal image[:format], 'PNG'
199
+ # image.destroy!
200
+ # end
147
201
  end
data/test/leaves.tiff CHANGED
Binary file