Vidar Hokstad V2.0

Home Blog

Tag: graphics

2008-03-22 01:36 UTC Web2.0 style logo reflection with Ruby and Cairo

Posted in: , , ,
I keep trying to learn more about Cairo, and cheesy logo effects seems to be as good a way as any. This evening I was playing around with clip paths, and decided to use it to create some reflections. Here are the results:


You might want to take a look at my previous examples. This one, as my last one, uses functions from the first example. It also requires that Cairo and the Cairo Ruby binding are installed.

And here's what I did.

First I took the script I used to draw the logo with gradient and replaced the background with black instead of white, and made the border blue. You can find the logo I used here

I named the file "in.png".

Then it was time to write the script - You need to require 'cairo' and add the cairo_image_surface helper function from my first example. As usual I then defined a few variables, starting width/height. The script assumes that the input logo ends at h/2, so adjust accordingly if you use a different image. I then create the surface and open the block

h = 170
w = 350

black = [0.0,0.0,0.0, 1]

cairo_image_surface(w,h, black) do |cr|

The next step is new: I create an image surface from the PNG file. I then set the image as the source. The second and third argument to set_source() are the coordinates where the upper left point of the surface should map to the context (cr) we're drawing with.

I then draw a rectangle with the surface, and fill it. This has the effect of copying the logo onto the image surface we're drawing with.

  image = Cairo::ImageSurface.from_png("in.png")
  cr.set_source(image,40,0)
  cr.rectangle(0,0,w,h/2)
  cr.fill

Then it's time for the reflection effect. The left one first:

 (0..h/2).each do |y|
    cr.rectangle(0,h/2 + y,w,1)
    cr.clip
    cr.set_source(image,40-0.3*(y+1),y*2)
    cr.paint
    cr.set_source_rgba(*black)
    cr.paint(0.1+y/60.0)
    cr.reset_clip
  end

This code is fairly straightforward:

For the lower half of the image, we first create a rectangle of 1.0 unit high and the full width. We use "cr.clip" to define this rectangle as the current clip mask. The clip mask defines what part of what you draw subsequently that show. In effect we're creating a "slit" one pixel high we can draw on.

I then use set_source to reposition the source image (the original logo). By manipulating the y position we turn the image upside down. By manipulating the x position we slant the reflection to the left.

I then use cr.paint to pain the source image onto the "slit" I established by clipping.

Then I use set_source_rgba to switch to black "paint" and paint with alpha blending to gradually erase more and more the further down we get by painting over the slit with black. I chose this approach to making the reflection fade because the logo I used didn't have a transparent background. I could make the background transparent, but didn't think about it at the time. So the alternative approach is to drop painting over with the background color and instead set the alpha mask for the first "cr.paint" call and gradually reduce the alpha value instead of increasing it as we do for the second paint call.

All you need to do to get the wave effect instead is replace the set_source call we used to move the logo each line:

    cr.set_source(image,40-Math.sin(y*0.2)*y/7.0,y*2)

Experiment with different factors to increase the wave amplitude or frequency.

Last but not least you need to write the surface out, and close the block:

  cr.target.write_to_png("test.png")
end


2008-03-20 21:11 UTC Draw a logo with gradients with Ruby and Cairo

Posted in: , ,
UPDATE: I have posted yet another Cairo tutorial: Web2.0 style logo reflection with Ruby and Cairo

After this entry about drawing with Cairo I decided to play around some more. The following example uses the functions I included my previous entry (you'll need to include the require's and functions with the code below for it to work), and generates this logo:

Some variables for colors etc.:

h = 100
w = 300

blue = [0.0,0.0,1.0, 1] black = [0.0,0.0,0.0, 1] shadow = [0.0,0.0,0.0, 0.2] white = [1.0,1.0,1.0, 1] lblue = [0.6,0.6,1.0,1] dblue = [0.0,0.0,0.2,1]

The main function follows below. First we set up the surface and create a gradient, the same way as last time:

cairo_image_surface(w,h, white) do |cr|

rg1 = linear_gradient(0,15,0,h*0.8, :reflect, [0.0,blue],[0.4,lblue],[0.5,lblue],[0.5,dblue],[1.0,blue])

Then we select a font and set the size:

  cr.select_font_face("Arial", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD)
  cr.set_font_size(90.0)

Then we create a path of the text. If you want, you can just use "show_text" instead, but that draws the text directly. Using "text_path" is more flexible, since it turns the text into a path that can be manipulated all the same ways as any other path. In this case we draw the shadow first, using a translucent black:

  cr.move_to(18, 83)
  cr.text_path ("Hello");
  cr.set_source_rgba(shadow)
  cr.set_line_width(4)
  cr.stroke

Then we create the main text path a few pixels offset from the shadow, and fill it:

  cr.move_to(15, 80)
  cr.text_path ("Hello");
  cr.set_source(rg1)
  cr.fill_preserve

See how it's exactly the same as filling the path last time? Only this time the path consists of text instead of some lines.

Then all we have to do is to draw the border around the text and write it out to a file:

  cr.set_source_rgba(*black)
  cr.set_line_width(3)
  cr.stroke

cr.target.write_to_png("test.png") end


2008-03-19 19:32 UTC Simple drawing in Ruby with Cairo

UPDATE: I've just added another howto for Cairo about how to draw a logo with gradients

I've been playing around with Cairo for some time, and have recently been starting to rely on it more and more to avoid having to deal with drawing programs. I'm a command line kind of guy. The very limited graphics on this site, for example, are generated with small Ruby scripts using Cairo (via the rcairo bindings).

After a barrage of StumbleUpon traffic to one of my entries yesterday, I decided I'd like to put up a very in your face panel to point out other recent articles and my feed to Stumbleupon users - I use it a lot myself, and know how quickly people will move on to the next site.

This is what I ended up with: (Note:Cairo installed as well as the Ruby "rcairo" binding that you can find on the Cairo site)

And here is how I did it the hard way, with code:

I dug out a couple of my utility functions - the rcairo API is very verbose, as it's just mirroring the C API. I'd love to have the time to create a cleaner, more Ruby'ish interface, but I simply have better things to do, so at the moment I'm just throwing together bits of code as I need them, and have built up a small selection of utility functions I reuse and gradually clean up:

require 'cairo'

def linear_gradient(x1,y1,x2,y2,extend,*stops) g = Cairo::LinearPattern.new(x1,y1,x2,y2) g.set_extend(eval("Cairo::EXTEND_#{extend.to_s.upcase}")) stops.each {|s| g.add_color_stop_rgba(*s)} g end

def path cr, *pairs first = true pairs.each do |cmd| if cmd == :c cr.close_path first = true elsif first cr.move_to(*cmd) first = false else cr.line_to(*cmd) end end end

def cairo_image_surface(w,h,bg=nil) surface = Cairo::ImageSurface.new(w,h) cr = Cairo::Context.new(surface) if bg cr.set_source_rgba(*bg) cr.paint end yield(cr) end

For the graphics itself, I wanted a simple warning sign, with some subtle borders and gradients.

First some variables to ease life - the dimensions and some colors

h = 100
w = h*1.05
lw = 17       # Line width for the warning sign itself

red = [1.0,0.0,0.0, 1] lred = [1.0,0.3,0.3, 1] black = [0.0,0.0,0.0, 1] white = [1.0,1.0,1.0, 1] owhite = [1.0,0.95,0.9, 1] yellow = [1.0,1.0,0.6, 1] grey = [0.5,0.5,0.5,1]

Then the main section. First I create the surface, and then I set up the gradients:

cairo_image_surface(w+(lw*2),h+(lw*2), white) do |cr|

rg1 = linear_gradient(w*0.4,w*0.8,0,0, :reflect, [0.3,black],[1.0,lred]) rg2 = linear_gradient(w*0.4,w*0.8,0,0, :reflect, [0.3,red], [1.0,owhite]) bg1 = linear_gradient(w*0.3,w*0.3,0,0, :reflect, [0.4,grey], [1.0,black]) bg2 = linear_gradient(w*0.3,w*0.3,0,0, :reflect, [0.2,grey], [0.8,black])

Gradients can be a bit confusing - if you look back at the linear gradient function and compare with one of these calls, though, it's not that hard to explain.

The first four parameters define a line. The gradient is defines as lines running in parallel with the line you specify. Just experiment with different values and you'll quickly see the difference.

The next parameter sets the "extension" mode. Cairo supports a number of different ways of extending a gradient beyond the limits defined by the lines you provide. They include "none" (don't extend), "reflect" (repeat the pattern in alternating directions), "repeat" (repeat in the same direction) and "pad" (fill the rest with the last color).

The remaining parameters are "stops". Each pair consists of a value between 0.0 and 1.0 and define the color at that percentage into the gradient. The colors between each "stop" is found by mixing the colors at the stop to each side in proportion to how close a point is.

This bit determines what the line will look like wherever the path ends (LINE_CAP_ROUND) or changes direction (LINE_JOIN_ROUND):

  cr.set_line_join(Cairo::LINE_JOIN_ROUND)
  cr.set_line_cap(Cairo::LINE_CAP_ROUND)

Translate changes the coordinate space. Read it as if every x/y coordinate following translate gets lw added. It saves a lot of typing:

  cr.translate(lw,lw)

This creates the path for the sign itself. The :c makes my function call "close_path" which results in a closed polygon so that the edges use the join style insted of drawing endpoints:

  path(cr,[w/2,0],[w,h],[0,h],:c)

Then it's time to set various parameters and actually draw:

  # We want to draw with the gradient "rg1"
  cr.set_source(rg1)               
  cr.set_line_width(lw*1.2)   
  # stroke_preserve draws without clearing the path afterwards
  cr.stroke_preserve              
  # ... because we want to use it again, with gradient 'rg2'
  cr.set_source(rg2)               
  cr.set_line_width(lw)
  cr.stroke

This path is far more complex. It's an exclamation mark in two parts, hence the :c in the middle:

  path(cr,[w*0.45,h*0.5],[w*0.55,h*0.5],[w/2,h*0.68],:c,
	  [w*0.48,h*0.8],[w*0.52,h*0.8],[w*0.515,h*0.82],
          [w*0.485,h*0.82],:c)

# This does more or less the same as above, # but notice how it fills to, to make sure the # interior of the exclamation mark is filled in too

cr.set_line_width(lw*0.6) cr.set_source(bg2) cr.fill_preserve cr.stroke_preserve

cr.set_source(bg1) cr.set_line_width(lw*0.4) cr.fill_preserve cr.stroke

Finally we write the context to a PNG file, and close the block:

  cr.target.write_to_png("test.png")
end


Older Entries

About me

E-mail: vidar@hokstad.com
Skype: vhokstad
View my LinkedIn profile

I was born April 21st, 1975, in Oslo, Norway. Since 2000 I've been living in London, UK. I'm married.

I'm working for Aardvark Media as Director of Technology. I'm also currently on the board of SpatialQ, a startup in the GIS space, and an advisor to Skoach, a startup doing a time management app for people with ADD.

Categories

StumbleUpon My link page

(Links I have stumbled and like)