RMagick - Using Layers
Background
The below image is created entirely from code written in Ruby (https://github.com/troylelandshields/bigAcalendar) with the help of RMagick. RMagick is an awesome Ruby interface to ImageMagick.
RMagick has the ability to write text to an image by creating a 'draw' object and using the annotate method.
In general, it is pretty easy to programmatically place the text where you want, but in my Big A Calendar I needed to place white text exactly in the middle of the black dots. Calculating the exact pixel to lay the text with annotate seemed like it was going to be a bigger headache than I wanted to deal with (especially since I might want to change the text size, etc).
Create a Sub Image
My solution was really simple: create a small image in which I could center the circle and date, then added this to the correct place on the calendar.
The below method creates a small image with the circle graphic I want to place the date on.
def createDateLabelBg
@date_lbl_bg = Image.new(@size, @size){
self.background_color = 'transparent'
}
#This is the bigger, all-black circle that makes the black stroke
cir = Draw.new
cir.fill = @date_lbl_bg_color.to_s
cir.circle(@date_lbl_bg.rows/2, @date_lbl_bg.columns/2,
2, @date_lbl_bg.columns/2)
cir.draw(@date_lbl_bg)
cir = Draw.new
cir.fill='black'
cir.stroke('white')
cir.stroke_width(12).
cir.circle(@date_lbl_bg.rows/2, @date_lbl_bg.columns/2,
24, @date_lbl_bg.columns/2)
cir.draw(@date_lbl_bg)
end
Later I can write the date exactly in the middle of this circle with no need to calculate any tricky positioning (notice the long list of 0s in the annotate method call):
date_lbl_bg = createDateLabelBg
date_lbl = Draw.new
date_lbl.gravity = CenterGravity
date_lbl.pointsize = @lbl_size - 90
date_lbl.fill = @date_color.to_s
#Just use 0's and it's centered perfectly!
date_lbl.annotate(date_lbl_bg, 0, 0, 0, 0, date.to_s)
This also comes with the advantage that my circle and date are now tied perfectly together, and I am free to place them wherever I want as one unit.
I use a similar method to place the letters that label the days of the week because I want it to be easy to keep that letter centered over the columns in the calendar rather than keep it placed in one specific place on the page.
Create a Base Image
Once you have your sub images designed the way you like, you can place them on top of a base image wherever you want.
Create a base image that is the size of the final image you want and add it to a new ImageList object.
baseImg = Image.new(14850, 10800){self.background_color = 'white'}
imgList = ImageList.new
imgList.push(baseImg)
Combine the Images
Add all of your other images to your new ImageList. Each image in this is on its own layer and has a position somewhere on the base image. Specify where you want the image placed with the 'page' attribute of the image.
img.page = Rectangle.new(width, height, xPos, yPos) #x & y are relative to upper left corner
Finally, flatten and write the image to get one nice image with everything in the position you wanted it.
final = imgList.flatten_images
final.write("final.png")