sholsinger.com

Categories Photography Résumé About Me

Quick & Dirty Image Resizer with Ruby and RMagick

02 Aug 2012

I needed to resize a folder of images the other day. So I wrote this handy script:

Update (08/10/12) — I had a conversation about shaders and threading with a SIGGRAPH attendee on a flight from LA back home. As a result of that conversation I've decided to multi-thread this script in an effort to make it faster. In the process I beat the crap out of my MacBook Pro but it took the beating like a champ. Thankfully this made me realize that the Ruby garbage collection wasn't working fast enough. So I added some code to free the image resources when they were no longer being used. This had an added benefit of speeding things up even more. I found the Parallel gem which made the multi-threading part of it insanely easy. See for yourself below.

#!/usr/bin/ruby

require "RMagick" # image processing wrapper for ImageMagick
require "Parallel" # https://github.com/grosser/parallel

# ensure dirs are available for writing
["thumbs","full","processed"].each do |dir|
  unless File.directory? dir
    puts "Creating #{dir}/"
    Dir.mkdir dir
  end
end

# encapsulates the image resizing logic
def resizeImage(f)

  # get the first frame of the image (JPGs usually have only one)
  img = Magick::Image.read(f).first
  # prep thumb and full sizes
  thumb = img.resize_to_fit(100, 100)
  full = img.resize_to_fit(580, 580)
  # write resized images
  thumb.write("thumbs/#{f}") {self.quality = 60}
  full.write("full/#{f}") {self.quality = 75}

  # free up RAM
  img.destroy!
  thumb.destroy!
  full.destroy!

end

# Loop through items in the dir
files = Dir.entries "."
# clean file list of known non-files
files.delete_if {|f| [".", "..", ".DS_Store"].index(f) != nil or Dir.directory?(f) }

# let the user know we've begun...
puts ("Resizing #{totalFiles} files...")

# process the images in parallel threads (up to 4)
completed = Parallel.each(files){|file|
  if File.file? file
    resizeImage(file)

    # move the file to processed folder
    system("mv \"#{file}\" processed/")

    # micro-sleep so other processes can get some CPU time
    sleep 0.3
  end
}

# Show a growl notification if available.
system("growlnotify --appIcon Terminal -m 'Finshed resizing images!' Ruby")

puts "Finished resizing #{completed.length} images"

The script uses the multi-process threading model which allows the Parallel gem to spawn a process for every native thread available on each processing core of the machine it's running on. With my early 2012 MBP with an Intel Core i7 quad-core CPU that means 8 threads at once. That's an increase of 800% from what the original script could do. I highly recommend this gem for your simple parallel processing needs. I suppose you might not need the micro-sleep in the worker block if you ran the script through nice but I find that it doesn't affect the speed too much as-is.

I've included the first version for your comparing pleasure. It is quite primitive. I updated it with the bits to free up resources so it should run a bit faster too.

#!/usr/bin/ruby

require "RMagick"

# ensure dirs are available for writing
["thumbs","full","processed"].each do |dir|
  unless File.directory? dir
    Dir.mkdir dir
  end
end

# Loop through items in the dir
files = Dir.entries "."
fileCounter = 1
totalFiles = files.length-2
files.each do |f|
  if File.directory? f
    puts "Skipping directory: #{f}"
    next
  end

  if [".", "..", ".DS_Store"].index(f) == nil

    puts "Resizing file: #{f} - #{fileCounter} of #{totalFiles}"

    img = Magick::Image.read(f).first
    thumb = img.resize_to_fit(100, 100)
    full = img.resize_to_fit(580, 580)
    thumb.write("thumbs/#{f}") {self.quality = 60}
    full.write("full/#{f}") {self.quality = 75}

    # free up RAM
    img.destroy!
    thumb.destroy!
    full.destroy!

    fileCounter += 1
    # move the file to processed folder
    system("mv \"#{f}\" processed/")
  end
  sleep 0.5
end

system("growlnotify -t 'Ruby' -m 'Finshed resizing images!'")

puts "Done."

I sincerely hope this helps someone. Enjoy!

Filed under

Comments
comments powered by Disqus