Last Updated: February 25, 2016
·
2.557K
· troylelandshields

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.

Picture

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")