One really cool thing I like about the ImageMagick library (and more importantly, the RMagick library for a ruby-geek like me) is it’s ability to do pretty much anything imaginable with graphics. And that’s just what I needed for my upcoming release of my pending open-source graphics plugin (yet to be publicly named).
For a client of ours, I needed to generate an animated gif pulled from frames of a movie. This way, a small version of the movie could be displayed on the site without loading the flash player and giving the viewer a quick look at the contents.
Well, RMagick’s ImageList class is just what I needed to do this job.
I also already have a system set in place to handle creating thumbnails on the fly and I didn’t want to modify those processes based on the process, I’d rather just call the correct methods and let the system do the work. Luckily, those are already in place for me and ImageList’s handle what they need to do.
I am pretty familiar with the functions in attachment_fu, so for this post I will use some of technoweenie’s code to demonstrate the point. (However, this functionality will be built into my future plugin too… oooo I can’t wait to release it! Just have to finish it up and test the heck out of it;)
So, let’s get started
First thing we have to do is make sure we have an flv. This is because we will be using the excellent flvtool2 executable written in ruby.
Using and open3 in the ruby library and yaml, we can capture the output and grab the duration of the flv file itself:
def get_movie_information(file)
stdin, stdout, stderr = Open3.popen3("flvtool2 -P #{file}")
YAML.load(stdout).to_a[0]
end
We capture the stdout, stdin and stderr using the Open3 library and capture it into a yaml structure. Pretty cool, ey?
Since flvtool2 outputs the file first and the data associated with that in it’s own structure, we have to grab that data. I’m not really interested in the file, so we’ll “discard” that information (you can keep it, if necessary) and just grab the data we want.
To do this, I had to “convert” the hash into an array with the super-slick to_a method on a hash.
def create_screenshot(flv, nf)
info = get_movie_information(flv)
num = (info[1]["duration"] / 10).round * 10
a = build_screenshot_images(flv, nf, num)
list = Magick::ImageList.new(*a)
list.delay = 30
filename = "#{File.dirname(nf)}/#{File.basename(nf, File.extname(nf))}.gif"
list.write(filename)
a.each {|a| FileUtils.rm a.to_s }
filename
end
The only thing here that should be a little fuzzy is the method build_screenshot_images. I’ll get to that.
For the time being, you can just assume that it creates the frames and returns a list of the frames of the movie.
After that, I am calculating the number of frames in the movie that we should pull for the movie and setting that as the number of frames. Then, after creating the files in the build_screenshot_images method, we create a new imagelist and put all those files into the list.
Then, we set a delay on it and write it all back to one image. Of course, there is some cleanup involved and that’s removing all the old frames in that directory so we have a clean directory with the thumbs and the movie itself.
Pretty easy, right?
Following is the build_screenshot images method I kept talking about above:
def build_screenshot_images(flv, nf, num=3)
a = []; b = []
info = get_movie_information(flv)
(num <= 10) ? (num-1).times {|p| b << p.to_i % 10} : num.times {|t| b << t*(info[1]["duration"]/(num-1)).floor}
num.times do |i|
filename = "#{File.dirname(nf)}/#{File.basename(nf, File.extname(nf))}#{i}#{File.extname(nf)}"
system "ffmpeg -i #{flv} -an -ss #{b[i].nil? ? 0 : b[i]}.#{(((rand * 10) / 10)*100).round}00 -an -r 1 -vframes 1 -f image2 #{filename}"
a << filename
end
a
end
Have fun and looking forward to the upcoming plugin release.
Oh, and if you have any questions or problems, lemme know. I’m always around to help
LA adventures
I have been in Los Angeles for the past several days doing some work with… well, my work. I promised a friend I would help him out with his graphics. Unfortunately, I am on a plane and also on a laptop that does not have photoshop/illustrator so I can’t do any work with it now. I had planned on doing some homework up here, but rather than doing that, I think it’s probably a fun idea to try to see what I can do design-wise with a little bit of code. Ooo, could be fun.
Let’s get started.
First off, the project I am helping him out with is a graphics project. He is assigned to develop and brand a music publishing company for a class of his. We had come up with a name and a graphic, so let’s actually design it.
First things first,

the logo
The name of the company is R3CORDS. We can easily create an image using ruby and rmagick:
require 'rubygems'
require "RMagick"
string = "R3cords".upcase
# prepare drawing surface
text = Magick::Draw.new
text.fill = 'white'
text.pointsize = 40
# Create and composite the images
temp_image = Magick::Image.new(w, h) { self.background_color = "none" }
temp_image = temp_image.annotate(text, 0, 0, 5, 40, string)
# WRITE OUT
temp_image.format = "JPG"
temp_image.write('temp.jpg')
As you can see, we are just creating an RMagick Drawing object, telling it a few things we want to look like, such as the background color of the image. We say we want white text against a “none” (black) color background and then we write the text out at 5 pixels from the left and 40 from the top. The pointsize is 40. Then we write it out to an image and we are done, right?
No way, I’m stuck on a plane and there is so much more we can do, let’s not stop here, ey?
Let’s make the logo a little more “recordy.” We’ll add a small outline to the logo text and give the font a weight that makes it stand out a little more:
text.stroke = 'black'
text.stroke_width = 1
OO, that looks fun. Oh, but there is still more! We are going to put this on top of another image and kind of blend it in. The only way a black background would work is if the image were black, and it’s not (which you’ll see in a few minutes).
So rather than using a black background, we’ll change a few things and make it even cooler with a transparent background and perhaps a shadow.
# prepare drawing surface
text = Magick::Draw.new
text.fill = 'white'
text.pointsize = 40
text.font_weight = 800
text.stroke_width = 0
temp_image = Magick::Image.new(w, h) { self.background_color = "transparent" }
temp_image = temp_image.annotate(text, 0, 0, 5, 40, string)
# Make the shadow
shadow = temp_image
shadow = shadow.colorize(1, 1, 8, Magick::Pixel.new(100, 100, 100, 20)) # shadow color can vary to taste
shadow.background_color = "grey24" # was "none"
shadow.border!(4, 4, "white")
shadow = shadow.blur_image(0, 3) # shadow blurriness can vary according to taste
# Composite image over shadow. The y-axis adjustment can vary according to taste.
temp_image = shadow.composite(temp_image, -amplitude, 0, Magick::OverCompositeOp)
OO, weee. So now we added a shadow over the temp_image and took out the background of the other. In order to get this on top of another image, we have to open the new image and composite it on top.
I think we are close to finished with the logo, but I want to add one more cool thing to it.
I wanted to add some neat funky-looking boxes to make it look tech-cool.
Where do we start? Well, I’ve already done it
# Lets create a cool background behind it
neat = Magick::ImageList.new
neat.new_image(background_image.columns, background_image.rows) {
self.background_color = 'transparent'
}
1.upto(background_image.rows) do |i|
c = Magick::Image.new(1, 1+rand(25)) do |m|
m.background_color = '#cccccc'
end
c.rotate!(i*rand(360))
neat.composite!(c, i*rand(5), i+rand(background_image.rows), Magick::OverCompositeOp)
end
And we are done
The full code is down below.
Finished, just in time for beverage service
Oh, and LA was sweet.
require 'rubygems'
require "RMagick"
w = 200
h = 100
string = "R3cords".upcase
amplitude = h * 0.01 # vary according to taste
wavelength = w * 2
temp_image = Magick::Image.new(w*2, h) { self.background_color = "transparent" }
# Make the shadow
shadow = temp_image
shadow = shadow.colorize(1, 1, 8, "#883333") # shadow color can vary to taste
shadow.border!(4, 1, "transparent")
shadow = shadow.blur_image(0, 10) # shadow blurriness can vary according to taste
temp_image = shadow.composite(temp_image, 0, 0, Magick::OverCompositeOp)
# Let's set this aside for a moment and do..
background_image = Magick::Image.read("bg.jpg").first
temp_image = background_image.composite(temp_image, Magick::SouthEastGravity, Magick::OverCompositeOp)
# Lets create a cool background behind it
neat = Magick::ImageList.new
neat.new_image(background_image.columns, background_image.rows) {
self.background_color = 'transparent'
}
1.upto(background_image.rows) do |i|
c = Magick::Image.new(1, 1+rand(25)) do |m|
m.background_color = '#cccccc'
end
c.rotate!(i*rand(360))
neat.composite!(c, i*rand(5), i+rand(background_image.rows), Magick::OverCompositeOp)
end
temp_image = temp_image.dissolve(neat, 0.2, 4.9)
# prepare drawing surface
text = Magick::Draw.new
text.fill = 'white'
# text.text_antialias = false
text.pointsize = 80
# text.rotation = 0
text.font_family = "Helvetica"
text.font_weight = 800
text.stroke_width = 1
text.stroke = 'black'
text.gravity = Magick::SouthEastGravity
temp_image = temp_image.annotate(text, 0, 0, 0, 0, string)
# WRITE OUT
temp_image.format = "JPG"
# self.rmagick_image = canvas
temp_image.write('temp.jpg')
From:

To:

All done entirely without a photo editor. Just one sweet computer and a kick-butt text editor!
I enjoyed this tutorial. I think I’ll write some more in the future. Let me know if it was helpful.