mini_magick 3.8.1 → 4.0.0.rc

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.

Potentially problematic release.


This version of mini_magick might be problematic. Click here for more details.

Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mini_gmagick.rb +2 -1
  3. data/lib/mini_magick.rb +43 -65
  4. data/lib/mini_magick/configuration.rb +136 -0
  5. data/lib/mini_magick/image.rb +356 -336
  6. data/lib/mini_magick/image/info.rb +104 -0
  7. data/lib/mini_magick/logger.rb +40 -0
  8. data/lib/mini_magick/shell.rb +46 -0
  9. data/lib/mini_magick/tool.rb +233 -0
  10. data/lib/mini_magick/tool/animate.rb +14 -0
  11. data/lib/mini_magick/tool/compare.rb +14 -0
  12. data/lib/mini_magick/tool/composite.rb +14 -0
  13. data/lib/mini_magick/tool/conjure.rb +14 -0
  14. data/lib/mini_magick/tool/convert.rb +14 -0
  15. data/lib/mini_magick/tool/display.rb +14 -0
  16. data/lib/mini_magick/tool/identify.rb +14 -0
  17. data/lib/mini_magick/tool/import.rb +14 -0
  18. data/lib/mini_magick/tool/mogrify.rb +14 -0
  19. data/lib/mini_magick/tool/montage.rb +14 -0
  20. data/lib/mini_magick/tool/stream.rb +14 -0
  21. data/lib/mini_magick/utilities.rb +23 -50
  22. data/lib/mini_magick/version.rb +6 -6
  23. data/spec/fixtures/animation.gif +0 -0
  24. data/spec/fixtures/default.jpg +0 -0
  25. data/spec/fixtures/exif.jpg +0 -0
  26. data/spec/fixtures/image.psd +0 -0
  27. data/spec/fixtures/not_an_image.rb +1 -0
  28. data/spec/lib/mini_magick/configuration_spec.rb +66 -0
  29. data/spec/lib/mini_magick/image_spec.rb +318 -410
  30. data/spec/lib/mini_magick/shell_spec.rb +66 -0
  31. data/spec/lib/mini_magick/tool_spec.rb +90 -0
  32. data/spec/lib/mini_magick/utilities_spec.rb +17 -0
  33. data/spec/lib/mini_magick_spec.rb +23 -47
  34. data/spec/spec_helper.rb +17 -25
  35. data/spec/support/helpers.rb +37 -0
  36. metadata +42 -76
  37. data/lib/mini_magick/command_builder.rb +0 -94
  38. data/lib/mini_magick/errors.rb +0 -4
  39. data/spec/files/actually_a_gif.jpg +0 -0
  40. data/spec/files/animation.gif +0 -0
  41. data/spec/files/composited.jpg +0 -0
  42. data/spec/files/erroneous.jpg +0 -0
  43. data/spec/files/layers.psd +0 -0
  44. data/spec/files/leaves (spaced).tiff +0 -0
  45. data/spec/files/not_an_image.php +0 -1
  46. data/spec/files/png.png +0 -0
  47. data/spec/files/simple-minus.gif +0 -0
  48. data/spec/files/simple.gif +0 -0
  49. data/spec/files/trogdor.jpg +0 -0
  50. data/spec/files/trogdor_capitalized.JPG +0 -0
  51. data/spec/lib/mini_magick/command_builder_spec.rb +0 -153
@@ -0,0 +1,104 @@
1
+ module MiniMagick
2
+ class Image
3
+ # @private
4
+ class Info
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ @info = {}
9
+ end
10
+
11
+ def [](value, *args)
12
+ case value
13
+ when "format", "width", "height", "dimensions", "size"
14
+ cheap_info(value)
15
+ when "colorspace"
16
+ colorspace
17
+ when "mime_type"
18
+ mime_type
19
+ when "resolution"
20
+ resolution(*args)
21
+ when /^EXIF\:/i
22
+ raw_exif(value)
23
+ when "exif"
24
+ exif
25
+ else
26
+ raw(value)
27
+ end
28
+ end
29
+
30
+ def clear
31
+ @info.clear
32
+ end
33
+
34
+ private
35
+
36
+ def cheap_info(value)
37
+ @info.fetch(value) do
38
+ format, width, height, size = self["%m %w %h %b"].split(" ")
39
+
40
+ @info.update(
41
+ "format" => format,
42
+ "width" => Integer(width),
43
+ "height" => Integer(height),
44
+ "dimensions" => [Integer(width), Integer(height)],
45
+ "size" => size.to_i,
46
+ )
47
+
48
+ @info.fetch(value)
49
+ end
50
+ end
51
+
52
+ def colorspace
53
+ @info.fetch("colorspace") do
54
+ @info["colorspace"] = self["%r"]
55
+ end
56
+ end
57
+
58
+ def mime_type
59
+ "image/#{self["format"].downcase}"
60
+ end
61
+
62
+ def resolution(unit = nil)
63
+ output = identify do |b|
64
+ b.units unit if unit
65
+ b.format "%x %y"
66
+ end
67
+ output.split(" ").map(&:to_i)
68
+ end
69
+
70
+ def raw_exif(value)
71
+ self["%[#{value}]"]
72
+ end
73
+
74
+ def exif
75
+ @info.fetch("exif") do
76
+ output = self["%[EXIF:*]"]
77
+ pairs = output.gsub(/^exif:/, "").split("\n").map { |line| line.split("=") }
78
+ exif = Hash[pairs].tap do |hash|
79
+ hash.each do |key, value|
80
+ if value.include?(",")
81
+ # Sometimes exif comes in a comma-separated list of character values
82
+ hash[key] = value.scan(/\d+/).map(&:to_i).map(&:chr).join
83
+ end
84
+ end
85
+ end
86
+
87
+ @info["exif"] = exif
88
+ end
89
+ end
90
+
91
+ def raw(value)
92
+ identify { |b| b.format(value) }
93
+ end
94
+
95
+ def identify
96
+ MiniMagick::Tool::Identify.new do |builder|
97
+ yield builder if block_given?
98
+ builder << "#{@path}[0]"
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,40 @@
1
+ require "benchmark"
2
+
3
+ module MiniMagick
4
+ ##
5
+ # Responsible for logging commands to stdout (activated when
6
+ # `MiniMagick.debug` is set to `true`). Implements a simplified Logger
7
+ # interface.
8
+ #
9
+ # @private
10
+ #
11
+ class Logger
12
+
13
+ attr_accessor :format
14
+
15
+ def initialize(io)
16
+ @io = io
17
+ @format = "[%<duration>.2fs] %<command>s"
18
+ end
19
+
20
+ def debug(command, &action)
21
+ benchmark(action) do |duration|
22
+ output(duration: duration, command: command) if MiniMagick.debug
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def output(data)
29
+ printf @io, "#{format}\n", data
30
+ end
31
+
32
+ def benchmark(action)
33
+ return_value = nil
34
+ duration = Benchmark.realtime { return_value = action.call }
35
+ yield duration
36
+ return_value
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ require "mini_magick/logger"
2
+
3
+ require "open3"
4
+ require "timeout"
5
+
6
+ module MiniMagick
7
+ ##
8
+ # Sends commands to the shell (more precisely, it sends commands directly to
9
+ # the operating system).
10
+ #
11
+ # @private
12
+ #
13
+ class Shell
14
+
15
+ def initialize(whiny = true)
16
+ @whiny = whiny
17
+ end
18
+
19
+ def run(command)
20
+ stdout, stderr, code = execute(command)
21
+
22
+ case code
23
+ when 1
24
+ fail MiniMagick::Error, "`#{command.join(" ")}` failed with error:\n#{stderr}"
25
+ when 127
26
+ fail MiniMagick::Error, stderr
27
+ end if @whiny
28
+
29
+ stdout
30
+ end
31
+
32
+ def execute(command)
33
+ stdout, stderr, status =
34
+ MiniMagick.logger.debug(command.join(" ")) do
35
+ Timeout.timeout(MiniMagick.timeout) do
36
+ Open3.capture3(*command)
37
+ end
38
+ end
39
+
40
+ [stdout, stderr, status.exitstatus]
41
+ rescue Errno::ENOENT
42
+ ["", "executable not found: \"#{command.first}\"", 127]
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,233 @@
1
+ require "mini_magick/shell"
2
+
3
+ module MiniMagick
4
+ ##
5
+ # Abstract class that wraps command-line tools. It shouldn't be used directly,
6
+ # but through one of its subclasses (e.g. {MiniMagick::Tool::Mogrify}). Use
7
+ # this class if you want to be closer to the metal and execute ImageMagick
8
+ # commands directly, but still with a nice Ruby interface.
9
+ #
10
+ # @example
11
+ # MiniMagick::Tool::Mogrify.new do |builder|
12
+ # builder.resize "500x500"
13
+ # builder << "path/to/image.jpg"
14
+ # end
15
+ #
16
+ class Tool
17
+
18
+ autoload :Animate, "mini_magick/tool/animate"
19
+ autoload :Compare, "mini_magick/tool/compare"
20
+ autoload :Composite, "mini_magick/tool/composite"
21
+ autoload :Conjure, "mini_magick/tool/conjure"
22
+ autoload :Convert, "mini_magick/tool/convert"
23
+ autoload :Display, "mini_magick/tool/display"
24
+ autoload :Identify, "mini_magick/tool/identify"
25
+ autoload :Import, "mini_magick/tool/import"
26
+ autoload :Mogrify, "mini_magick/tool/mogrify"
27
+ autoload :Montage, "mini_magick/tool/montage"
28
+ autoload :Stream, "mini_magick/tool/stream"
29
+
30
+ # @private
31
+ def self.inherited(child)
32
+ child_name = child.name.split("::").last.downcase
33
+ child.send :include, MiniMagick::Tool::OptionMethods.new(child_name)
34
+ end
35
+
36
+ ##
37
+ # Aside from classic instantiation, it also accepts a block, and then
38
+ # executes the command in the end.
39
+ #
40
+ # @example
41
+ # version = MiniMagick::Tool::Identify.new { |b| b.version }
42
+ # puts version
43
+ #
44
+ # @return [MiniMagick::Tool, String] If no block is given, returns an
45
+ # instance of the tool, if block is given, returns the output of the
46
+ # command.
47
+ #
48
+ def self.new(*args)
49
+ instance = super(*args)
50
+
51
+ if block_given?
52
+ yield instance
53
+ instance.call
54
+ else
55
+ instance
56
+ end
57
+ end
58
+
59
+ # @private
60
+ attr_reader :name, :args
61
+
62
+ def initialize(name)
63
+ @name = name
64
+ @args = []
65
+ end
66
+
67
+ ##
68
+ # Executes the command that has been built up.
69
+ #
70
+ # @example
71
+ # mogrify = MiniMagick::Tool::Mogrify.new
72
+ # mogrify.resize("500x500")
73
+ # mogrify << "path/to/image.jpg"
74
+ # mogirfy.call # executes `mogrify -resize 500x500 path/to/image.jpg`
75
+ #
76
+ # @param whiny [Boolean] Whether you want an error to be raised when
77
+ # ImageMagick returns an exit code of 1. You may want this because
78
+ # some ImageMagick's commands (`identify -help`) return exit code 1,
79
+ # even though no error happened.
80
+ #
81
+ # @return [String] Output of the command
82
+ #
83
+ def call(whiny = true)
84
+ shell = MiniMagick::Shell.new(whiny)
85
+ shell.run(command).strip
86
+ end
87
+
88
+ ##
89
+ # The currently built-up command.
90
+ #
91
+ # @return [Array<String>]
92
+ #
93
+ # @example
94
+ # mogrify = MiniMagick::Tool::Mogrify.new
95
+ # mogrify.resize "500x500"
96
+ # mogrify.contrast
97
+ # mogrify.command #=> ["mogrify", "-resize", "500x500", "-contrast"]
98
+ #
99
+ def command
100
+ [*executable, *args]
101
+ end
102
+
103
+ ##
104
+ # The executable used for this tool. Respects
105
+ # {MiniMagick::Configuration#cli} and {MiniMagick::Configuration#cli_path}.
106
+ #
107
+ # @return [Array<String>]
108
+ #
109
+ # @example
110
+ # MiniMagick.configure { |config| config.cli = :graphicsmagick }
111
+ # identify = MiniMagick::Tool::Identify.new
112
+ # identify.executable #=> ["gm", "identify"]
113
+ #
114
+ def executable
115
+ exe = [name]
116
+ exe.unshift "gm" if MiniMagick.graphicsmagick?
117
+ exe.unshift File.join(MiniMagick.cli_path, exe.shift) if MiniMagick.cli_path
118
+ exe
119
+ end
120
+
121
+ ##
122
+ # Appends raw options, useful for appending image paths.
123
+ #
124
+ # @return [self]
125
+ #
126
+ def <<(arg)
127
+ args << arg.to_s
128
+ self
129
+ end
130
+
131
+ ##
132
+ # Changes the last operator to its "plus" form.
133
+ #
134
+ # @example
135
+ # mogrify = MiniMagick::Tool::Mogrify.new
136
+ # mogrify.antialias.+
137
+ # mogrify.distort.+("Perspective '0,0,4,5'")
138
+ # mogrify.command #=> ["mogrify", "+antialias", "+distort", "Perspective '0,0,4,5'"]
139
+ #
140
+ # @return [self]
141
+ #
142
+ def +(value = nil)
143
+ args.last.sub!(/^-/, '+')
144
+ args << value.to_s if value
145
+ self
146
+ end
147
+
148
+ private
149
+
150
+ ##
151
+ # Dynamically generates modules with dynamically generated option methods
152
+ # for each command-line tool. It uses the `-help` page of a command-line
153
+ # tool and generates methods from it. It then includes the generated
154
+ # module into the tool class.
155
+ #
156
+ # @private
157
+ #
158
+ class OptionMethods < Module
159
+
160
+ def self.instances
161
+ @instances ||= []
162
+ end
163
+
164
+ def self.new(*args, &block)
165
+ super.tap do |instance|
166
+ self.instances << instance
167
+ end
168
+ end
169
+
170
+ def initialize(tool_name)
171
+ super() do
172
+ @tool_name = tool_name
173
+ reload_methods
174
+ end
175
+ end
176
+
177
+ ##
178
+ # Dynamically generates operator methods from the "-help" page.
179
+ #
180
+ def reload_methods
181
+ instance_methods(false).each { |method| undef_method(method) }
182
+
183
+ self.creation_operator *%w[xc canvas logo rose gradient radial-gradient
184
+ plasma tile pattern label caption text]
185
+
186
+ help = (MiniMagick::Tool.new(@tool_name) << "-help").call(false)
187
+ cli_options = help.scan(/^\s+-[a-z\-]+/).map(&:strip)
188
+ self.option *cli_options
189
+ end
190
+
191
+ ##
192
+ # Creates method based on command-line option's name.
193
+ #
194
+ # mogrify = MiniMagick::Tool.new("mogrify")
195
+ # mogrify.antialias
196
+ # mogrify.depth(8)
197
+ # mogrify.resize("500x500")
198
+ # mogirfy.command.join(" ") #=> "mogrify -antialias -depth "8" -resize "500x500""
199
+ #
200
+ def option(*options)
201
+ options.each do |option|
202
+ define_method(option[1..-1].gsub('-', '_')) do |value = nil|
203
+ self << option
204
+ self << value.to_s if value
205
+ self
206
+ end
207
+ end
208
+ end
209
+
210
+ ##
211
+ # Creates method based on creation operator's name.
212
+ #
213
+ # mogrify = MiniMagick::Tool.new("mogrify")
214
+ # mogrify.canvas("khaki")
215
+ # mogrify.command.join(" ") #=> "mogrify canvas:khaki"
216
+ #
217
+ def creation_operator(*operators)
218
+ operators.each do |operator|
219
+ define_method(operator.gsub('-', '_')) do |value = nil|
220
+ self << "#{operator}:#{value}"
221
+ self
222
+ end
223
+ end
224
+ end
225
+
226
+ def to_s
227
+ "OptionMethods(#{@tool_name})"
228
+ end
229
+
230
+ end
231
+
232
+ end
233
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/animate.php
5
+ #
6
+ class Animate < MiniMagick::Tool
7
+
8
+ def initialize
9
+ super("animate")
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/compare.php
5
+ #
6
+ class Compare < MiniMagick::Tool
7
+
8
+ def initialize
9
+ super("compare")
10
+ end
11
+
12
+ end
13
+ end
14
+ end