#!/usr/bin/env ruby # # resize.rb - command line interface for RMagick # # Copyright:: (C) 2007 Toshiaki Katayama # License:: The Ruby License # Website:: http://kumamushi.org/~k/resize/ # Version:: 1.7 (2007/6/10) # require 'rubygems' require 'getoptlong' require 'RMagick' $opts = Hash.new args = GetoptLong.new( [ '--scale', '-s', GetoptLong::REQUIRED_ARGUMENT ], [ '--width', '-w', GetoptLong::REQUIRED_ARGUMENT ], [ '--height', '-h', GetoptLong::REQUIRED_ARGUMENT ], [ '--rotate', '-r', GetoptLong::REQUIRED_ARGUMENT ], [ '--trim', '-t', GetoptLong::REQUIRED_ARGUMENT ], [ '--crop', '-c', GetoptLong::REQUIRED_ARGUMENT ], [ '--format', '-f', GetoptLong::REQUIRED_ARGUMENT ], [ '--effect', '-e', GetoptLong::REQUIRED_ARGUMENT ], [ '--watermark', '-m', GetoptLong::REQUIRED_ARGUMENT ], [ '--polaroid', '-p', GetoptLong::REQUIRED_ARGUMENT ], [ '--output', '-o', GetoptLong::REQUIRED_ARGUMENT ], [ '--quiet', '-q', GetoptLong::NO_ARGUMENT ] ) args.each_option do |name, value| case name when /--scale/ $opts[:scale] = value.to_f when /--width/ $opts[:width] = value.to_i when /--height/ $opts[:height] = value.to_i when /--rotate/ $opts[:rotate] = value.to_i when /--trim/ $opts[:trim] = value.to_i when /--crop/ $opts[:crop] = value.to_s when /--format/ $opts[:format] = value.to_s when /--effect/ $opts[:effect] = value.to_s when /--watermark/ $opts[:watermark] = value.to_s when /--polaroid/ $opts[:polaroid] = value.to_s when /--output/ $opts[:output] = value.to_s when /--quiet/ $opts[:quiet] = true end end def show_usage prog = File.basename($0) usage = %Q[ Usage: % #{prog} \[options...\] files Options: -s or --scale number Specify the scaling factor of images in float. % #{prog} -s 0.1 image1.jpg image2.jpg # 1/10 scaled image -w or --width number Specify the width of resized images in pixel. (aspect ratio will be retained if not used with -h option) % #{prog} -w 100 image1.jpg image2.jpg -h or --height number Specify the height of resized images in pixel. (aspect ratio will be retained if not used with -w option) % #{prog} -h 50 image1.jpg image2.jpg -r or --rotate number Specify the rotation of images in degree. % #{prog} -r 90 image1.jpg image2.jpg # clockwise % #{prog} -r -90 image1.jpg image2.jpg # counter-clockwise -t or --trim number Specify the number of pixels to be trimmed from edges. If the number is 0, edges in the same color as corner pixels are removed (or made transparent if the number is negative). % #{prog} -t 10 image1.jpg image2.jpg % #{prog} -t 0 album.png # remove white edges % #{prog} -t -1 album.png # transparent edges -c or --crop "x,y,w,h" Specify the coordinates of upper left and lower right to be cropped. % #{prog} -t 10,20,100,50 photo.jpg # 100x50 at (10, 20) -e or --effect type Specify type of effect. (Available effects: "shadow", "reflect", "raise", "vignette", "sketch", "emboss", "negate", "flip", "flop" "gray", "sepia", "blur", "round", "roundedge") % #{prog} -e shadow image1.jpg image2.jpg # drop shadow -m or --watermark string Specify the label for watermark effect in string. % #{prog} -p "Kumamushi" image1.jpg image2.jpg -p or --polaroid string Specify the label for poraloid effect in string. % #{prog} -p "Kyoto trip" image1.jpg image2.jpg -f or --format type Specify the output file format. Useful for file format conversion and png output for some effects. (if not specified, file format is unchanged) % #{prog} -f gif image1.jpg image2.jpg # convert to gif % #{prog} -f png -p "my cat" -s 0.1 photo.jpg # polaroid in png -o or --output string Specify the output file name (and the file format by suffix). % #{prog} -o icon.png -s 0.1 image1.jpg # 1/10 image in png -q or --quiet Suppress verbose output (filenames and exif information). Note: GraphicsMagick does not support most of effects and polaroid mode. See also: http://kumamushi.org/~k/resize/ ] puts usage exit end def filter(image) if degree = $opts[:rotate] image = image.rotate(degree) end if effect = $opts[:effect] case effect when "gray" image = image.quantize(256, Magick::GRAYColorspace) when "sepia" image = image.sepiatone(Magick::MaxRGB * 0.8) when "blur" image = image.gaussian_blur(0.0, 3.0) when "round", "roundedge" w = image.columns h = image.rows r = [ 20, (w + h) / 15 ].min mask = Magick::Image.new(image.columns, image.rows) fill = Magick::Draw.new fill.fill('black') fill.rectangle(0,0, w,h) fill.fill('white') fill.fill_opacity(100) fill.roundrectangle(1,1, w-2,h-2, r,r) fill.draw(mask) mask.matte = false #image.matte = true image.mask = mask #image.opacity = Magick::TransparentOpacity image = image.composite(mask, Magick::CenterGravity, Magick::CopyOpacityCompositeOp) if effect[/roundedge/] # with edge version edge = Magick::Draw.new edge.fill_opacity(0) edge.stroke('#333333') edge.stroke_width(2) edge.roundrectangle(1,1, w-2,h-2, r,r) edge.draw(image) end when "shadow" foreground = Magick::Image.new(image.columns + 20, image.rows + 20) { self.background_color = 'white' } foreground = foreground.transparent('white') # or use matte_floodfill(0,0) foreground = foreground.composite(image, Magick::NorthWestGravity, 10, 10, Magick::OverCompositeOp) shadow = foreground.shadow.negate # or remove .negate for shadow in white image = shadow.composite(foreground, Magick::NorthWestGravity, 2, 2, Magick::OverCompositeOp) when "reflect" background = Magick::Image.new(image.columns, image.rows * 1.5) { self.background_color = 'white' } background = background.transparent('white') # or use matte_floodfill(0,0) reflection = image.wet_floor background = background.composite(image, Magick::NorthWestGravity, Magick::OverCompositeOp) image = background.composite(reflection, Magick::NorthWestGravity, 0, image.rows, Magick::OverCompositeOp) when "sketch" sketch = image.quantize(256, Magick::GRAYColorspace) sketch = sketch.equalize sketch = sketch.sketch(0, 10.0, 135) image = image.dissolve(sketch, 0.6, 0.4) when "vignette" image = image.vignette when "emboss" image = image.emboss when "negate" image = image.negate when "raise" image = image.raise when "flip" image = image.flip when "flop" image = image.flop end end if caption = $opts[:watermark] =begin # vertical mode (text need to be transformed) width = [ image.columns * 0.1, 100 ].min height = image.rows offset = image.columns - width + 1 band = Magick::Image.new(width, height) { self.background_color = 'black' } =end # horizontal mode width = image.columns height = [ image.rows * 0.1, 100 ].min offset = image.rows - height + 1 band = Magick::Image.new(width, height) { self.background_color = 'black' } label = Magick::Draw.new # label.annotate(band, 0, 0, 0, 40, caption) { label.annotate(band, width - 10, height*0.5, 0, height*0.25, caption) { self.font_family = 'Helvetica' self.fill = 'white' self.stroke = 'transparent' # vertical mode #self.pointsize = width / 2 # horizontal mode self.pointsize = height / 2 self.gravity = Magick::EastGravity } # vertical mode #image = image.dissolve(band, 0.2, 0.8, offset, 0) # horizontal mode image = image.dissolve(band, 0.2, 0.8, 0, offset) end if caption = $opts[:polaroid] w, h = image.columns, image.rows image[:caption] = caption image = image.polaroid { self.gravity = Magick::CenterGravity } image.change_geometry!("#{w}x#{h}") do |cols, rows, img| img.resize!(cols, rows) end end return image end def resize(image) if crop = $opts[:crop] x, y, w, h, = crop.split(/,\s*/) image = image.crop(x.to_i, y.to_i, w.to_i, h.to_i) end if trim = $opts[:trim] if trim > 0 image = image.shave(trim, trim) elsif trim < 0 corner = image.view(0,0,1,1)[0][0] image.fuzz = "#{trim.abs}%" image = image.transparent(corner) else # trim == 0 image.fuzz = "15%" image = image.trim end end if scale = $opts[:scale] image = image.scale(scale) else w = $opts[:width] h = $opts[:height] dummy_max = 10000 if w and h image = image.resize(w, h) elsif w image = image.resize_to_fit(w, dummy_max) elsif h image = image.resize_to_fit(dummy_max, h) else # file format conversion image = image.cur_image # or half sized image #image = image.minify end end return image end def rename(filepath) if $opts[:output] return $opts[:output] else suffix = filepath[/\.[^.]+$/] format = $opts[:format] || suffix[/[A-z]+/] dirname = File.dirname(filepath) basename = File.basename(filepath, suffix) return "#{dirname}/#{basename}-resize.#{format}" end end def show_exif_info(image) exif = image.get_exif_by_entry exif = image.properties if exif.empty? exif.sort.each do |k, v| puts [k.rjust(34), v].join(" : ") end end def image_size(image) w = image.columns h = image.rows return "#{w}x#{h}" end if ARGV.empty? show_usage end ARGV.each do |filepath| save_file_name = rename(filepath) original_image = Magick::ImageList.new(filepath) unless $opts[:quiet] puts "=== Processing #{filepath} (#{image_size(original_image)}) ===" show_exif_info(original_image) end unless $opts.keys.empty? resized_image = resize(original_image) filtered_image = filter(resized_image) filtered_image.write(save_file_name) unless $opts[:quiet] puts ">>> #{filepath} (#{image_size(original_image)}) -> #{save_file_name} (#{image_size(filtered_image)})" end end end