Joking aside, ImageMagick and RMagick give us some pretty powerful tools for adding formatted text to images. Here's a quick example of how to write text with custom per-line formatting.
Writing text is done via RMagick's Magick::Draw. Let's define a simple format that we can pass around that defines our line format in a way that's easy to serialize and deserialize.
For talking purposes, something like this:
{
text: 'Lorem Ipsum', # The text to write
width: 480, # The width of our text box
height: 640, # The height of our text box
alignment: 'north' # Which side of the text box should the text float toward?
x: 0, # Horizontal offset
y: 0, # Vertical offset
size: 40, # Text size
color: 'white', # Text color
outline_color: 'black', # Text outline color
font_weight: 'bold', # Font weight
font_family: 'Helvetica', # Font
font_style: 'Normal', # Font style
line_spacing: 0 # Spacing adjustment between text lines
}
For text box sizing it's often convenient to use the full image dimensions and let alignment handle the rest if you're adding header or footer text.
Obviously that's a lot of formatting options and it would be a pain if we had to specify all of them all the time, so let's make sure we have happy defaults.
I've put together a little helper to wrangle the text formatting and add the text to our image. Typically I prefer to wrap RMagick in a helper class with chainable methods, but we'll keep this simple and just make a little helper.
require 'rmagick'
class TextWriter
class << self
def write(image, text_lines)
text_lines.each { |line| write_line(image, line) }
end
private
def write_line(image, line)
draw = Magick::Draw.new
text_format(line).each { |key, value| draw.send("#{key}=", value) }
draw.annotate(image, line.fetch(:width, 0), line.fetch(:height, 0), line[:x], line[:y], text(line))
end
def text_format(line)
{
interline_spacing: line.fetch(:line_spacing, 0),
font_family: line.fetch(:font, 'Helvetica'),
font_weight: magick_const(line.fetch(:font_weight, 'normal'), 'Weight'),
font_style: magick_const(line.fetch(:font_style, 'normal'), 'Style'),
pointsize: line.fetch(:size, 20),
gravity: magick_const(line.fetch(:alignment, 'north_west'), 'Gravity'),
stroke: line[:outline_color],
fill: line[:color]
}.compact
end
def text(line)
# escape magick characters (https://rmagick.github.io/draw.html#annotate)
line[:text].gsub('%', '\%')
end
def magick_const(weight, category)
# Use .constantize here if you have ActiveSupport included.
# Sanitize this if you're taking input from your users!
Object.const_get("Magick::#{weight.capitalize}#{category}")
end
end
end
Let's try doing something basic with that to generate some simple text on an image. This is leaning heavily on our formatting defaults.
require './text_writer'
format = { width: 480, height: 640, x: 0, y: 20 }
img = Magick::Image.read('wizard:').first
TextWriter.write(img, [
{ text: 'I CAN HAZ', alignment: 'north' }.merge(format),
{ text: 'CHEEZBURGER', alignment: 'south' }.merge(format)
])
img.write('cheezburger_wizard.jpg')
Finally, let's lean more heavily on our customizable formatting to appeal to the youths.
require './text_writer'
format = {
width: 480,
height: 640,
x: 0,
y: 20,
size: 40,
color: 'white',
outline_color: 'black',
font_weight: 'bold'
}
img = Magick::Image.read('wizard:').first
TextWriter.write(img, [
{ text: 'I CAN HAZ', alignment: 'north' }.merge(format),
{ text: 'CHEEZBURGER', alignment: 'south' }.merge(format)
])
img.write('cheezburger_wizard.jpg')