Quick & Dirty Image Resizer with Ruby and RMagick
Aug 02
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.

