lib/mini_magick.rb in mini_magick-3.4 vs lib/mini_magick.rb in mini_magick-3.5.0

- old
+ new

@@ -1,9 +1,10 @@ require 'tempfile' require 'subexec' require 'stringio' require 'pathname' +require 'shellwords' module MiniMagick class << self attr_accessor :processor attr_accessor :timeout @@ -18,25 +19,25 @@ return elsif `type -P gm`.size > 0 self.processor = "gm" end end - + def image_magick_version @@version ||= Gem::Version.create(`mogrify --version`.split(" ")[2].split("-").first) end - + def minimum_image_magick_version @@minimum_version ||= Gem::Version.create("6.6.3") end - + def valid_version_installed? image_magick_version >= minimum_image_magick_version end end - 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 dissolve 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 quantize 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} + MOGRIFY_COMMANDS = %w{adaptive-blur adaptive-resize adaptive-sharpen adjoin affine alpha annotate antialias append attenuate authenticate auto-gamma auto-level auto-orient backdrop background bench bias black-point-compensation black-threshold blend blue-primary blue-shift blur border bordercolor borderwidth brightness-contrast cache caption cdl channel charcoal chop clamp clip clip-mask clip-path clone clut coalesce colorize colormap color-matrix colors colorspace combine comment compose composite compress contrast contrast-stretch convolve crop cycle debug decipher deconstruct define delay delete density depth descend deskew despeckle direction displace display dispose dissimilarity-threshold dissolve distort dither draw duplicate edge emboss encipher encoding endian enhance equalize evaluate evaluate-sequence extent extract family features fft fill filter flatten flip floodfill flop font foreground format frame function fuzz fx gamma gaussian-blur geometry gravity green-primary hald-clut help highlight-color iconGeometry iconic identify ift immutable implode insert intent interlace interpolate interline-spacing interword-spacing kerning label lat layers level level-colors limit linear-stretch linewidth liquid-rescale list log loop lowlight-color magnify map mask mattecolor median metric mode modulate monitor monochrome morph morphology mosaic motion-blur name negate noise normalize opaque ordered-dither orient page paint path pause pen perceptible ping pointsize polaroid poly posterize precision preview print process profile quality quantize quiet radial-blur raise random-threshold red-primary regard-warnings region remap remote render repage resample resize respect-parentheses reverse roll rotate sample sampling-factor scale scene screen seed segment selective-blur separate sepia-tone set shade shadow shared-memory sharpen shave shear sigmoidal-contrast silent size sketch smush snaps solarize sparse-color splice spread statistic stegano stereo stretch strip stroke strokewidth style subimage-search swap swirl synchronize taint text-font texture threshold thumbnail tile tile-offset tint title transform transparent transparent-color transpose transverse treedepth trim type undercolor unique-colors units unsharp update verbose version view vignette virtual-pixel visual watermark wave weight white-point white-threshold window window-group write} IMAGE_CREATION_OPERATORS = %w{canvas caption gradient label logo pattern plasma radial radient rose text tile xc } class Error < RuntimeError; end class Invalid < StandardError; end @@ -60,11 +61,13 @@ # @param ext [String] A manual extension to use for reading the file. Not required, but if you are having issues, give this a try. # @return [Image] def read(stream, ext = nil) if stream.is_a?(String) stream = StringIO.new(stream) - elsif stream.is_a?(File) + elsif stream.is_a?(StringIO) + # Do nothing, we want a StringIO-object + elsif stream.respond_to? :path if File.respond_to?(:binread) stream = StringIO.new File.binread(stream.path.to_s) else stream = StringIO.new File.open(stream.path.to_s,"rb") { |f| f.read } end @@ -219,33 +222,33 @@ # @return [String, Numeric, Array, Time, Object] Depends on the method called! Defaults to String for unknown commands def [](value) # Why do I go to the trouble of putting in newlines? Because otherwise animated gifs screw everything up case value.to_s when "colorspace" - run_command("identify", "-quiet", "-format", format_option("%r"), escaped_path).split("\n")[0] + run_command("identify", "-format", format_option("%r"), escaped_path).split("\n")[0].strip when "format" - run_command("identify", "-quiet", "-format", format_option("%m"), escaped_path).split("\n")[0] + run_command("identify", "-format", format_option("%m"), escaped_path).split("\n")[0] when "height" - run_command("identify", "-quiet", "-format", format_option("%h"), escaped_path).split("\n")[0].to_i + run_command("identify", "-format", format_option("%h"), escaped_path).split("\n")[0].to_i when "width" - run_command("identify", "-quiet", "-format", format_option("%w"), escaped_path).split("\n")[0].to_i + run_command("identify", "-format", format_option("%w"), escaped_path).split("\n")[0].to_i when "dimensions" - run_command("identify", "-quiet", "-format", format_option("%w %h"), escaped_path).split("\n")[0].split.map{|v|v.to_i} + run_command("identify", "-format", format_option("%w %h"), escaped_path).split("\n")[0].split.map{|v|v.to_i} when "size" File.size(@path) # Do this because calling identify -format "%b" on an animated gif fails! when "original_at" # Get the EXIF original capture as a Time object Time.local(*self["EXIF:DateTimeOriginal"].split(/:|\s+/)) rescue nil when /^EXIF\:/i - result = run_command('identify', '-quiet', '-format', "\"%[#{value}]\"", escaped_path).chop + result = run_command('identify', '-format', "\"%[#{value}]\"", escaped_path).chop if result.include?(",") read_character_data(result) else result end else - run_command('identify', '-quiet', '-format', "\"#{value}\"", escaped_path).split("\n")[0] + run_command('identify', '-format', "\"#{value}\"", escaped_path).split("\n")[0] end end # Sends raw commands to imagemagick's `mogrify` command. The image path is automatically appended to the command. # @@ -265,34 +268,35 @@ # # Formatting an animation into a non-animated type will result in ImageMagick creating multiple # pages (starting with 0). You can choose which page you want to manipulate. We default to the # first page. # + # If you would like to convert between animated formats, pass nil as your + # page and ImageMagick will copy all of the pages. + # # @param format [String] The target format... like 'jpg', 'gif', 'tiff', etc. - # @param page [Integer] If this is an animated gif, say which 'page' you want with an integer. Leave as default if you don't care. + # @param page [Integer] If this is an animated gif, say which 'page' you want + # with an integer. Default 0 will convert only the first page; 'nil' will + # convert all pages. # @return [nil] def format(format, page = 0) c = CommandBuilder.new('mogrify', '-format', format) yield c if block_given? - c << @path + if page + c << @path + "[#{page}]" + else + c << @path + end run(c) old_path = @path.dup @path.sub!(/(\.\w*)?$/, ".#{format}") File.delete(old_path) if old_path != @path unless File.exists?(@path) - begin - FileUtils.copy_file(@path.sub(".#{format}", "-#{page}.#{format}"), @path) - rescue => ex - raise MiniMagick::Error, "Unable to format to #{format}; #{ex}" unless File.exist?(@path) - end + raise MiniMagick::Error, "Unable to format to #{format}" end - ensure - Dir[@path.sub(/(\.\w+)?$/, "-[0-9]*.#{format}")].each do |fname| - File.unlink(fname) - end end # Collapse images with sequences to the first frame (ie. animated gifs) and # preserve quality def collapse! @@ -337,11 +341,11 @@ # If an unknown method is called then it is sent through the mogrify program # Look here to find all the commands (http://www.imagemagick.org/script/mogrify.php) def method_missing(symbol, *args) combine_options do |c| - c.method_missing(symbol, *args) + c.send(symbol, *args) end end # You can use multiple commands together using this method. Very easy to use! # @@ -351,14 +355,14 @@ # c.thumbnail "300x500>" # c.background background # end # # @yieldparam command [CommandBuilder] - def combine_options(tool = :mogrify, &block) + def combine_options(tool = "mogrify", &block) c = CommandBuilder.new(tool) - c << @path if tool == :convert + c << @path if tool.to_s == "convert" block.call(c) c << @path run(c) end @@ -392,10 +396,11 @@ def run_command(command, *args) # -ping "efficiently determine image characteristics." if command == 'identify' args.unshift '-ping' + args.unshift '-quiet' unless MiniMagick.processor.to_s == 'gm' end run(CommandBuilder.new(command, *args)) end @@ -438,35 +443,51 @@ result end end class CommandBuilder - attr :args - attr :command + attr_reader :args - def initialize(command, *options) - @command = command + def initialize(tool, *options) + @tool = tool @args = [] options.each { |arg| push(arg) } end def command - "#{MiniMagick.processor} #{@command} #{@args.join(' ')}".strip + "#{MiniMagick.processor} #{@tool} #{@args.join(' ')}".strip end - def method_missing(symbol, *options) - guessed_command_name = symbol.to_s.gsub('_','-') - if guessed_command_name == "format" - raise Error, "You must call 'format' on the image object directly!" - elsif MOGRIFY_COMMANDS.include?(guessed_command_name) - add_command(guessed_command_name, *options) + # Add each mogrify command in both underscore and dash format + MOGRIFY_COMMANDS.each do |mogrify_command| + + # Example of what is generated here: + # + # def auto_orient(*options) + # add_command("auto-orient", *options) + # self + # end + # alias_method :"auto-orient", :auto_orient + + dashed_command = mogrify_command.to_s.gsub("_","-") + underscored_command = mogrify_command.to_s.gsub("-","_") + + define_method(underscored_command) do |*options| + add_command(__method__.to_s.gsub("_","-"), *options) self - elsif IMAGE_CREATION_OPERATORS.include?(guessed_command_name) - add_creation_operator(guessed_command_name, *options) + end + alias_method dashed_command, underscored_command + end + + def format(*options) + raise Error, "You must call 'format' on the image object directly!" + end + + IMAGE_CREATION_OPERATORS.each do |operator| + define_method operator do |*options| + add_creation_operator(__method__.to_s, *options) self - else - super(symbol, *args) end end def +(*options) push(@args.pop.gsub(/^-/, '+')) @@ -483,12 +504,12 @@ options.each do |o| push escape_string(o) end end end - + def escape_string(value) - '"' + value + '"' + Shellwords.escape(value.to_s) end def add_creation_operator(command, *options) creation_command = command if options.any?