Standalone Library

April 27, 2011 | news

A little history and an update: after Rich Kilmer integrated my graphics library into Apple's MacRuby project as part of the "HotCocoa" library, the HotCocoa project was split out from MacRuby into its own gem at github.com/richkilmer/hotcocoa. Then, Matt Aimonetti extracted the graphics library out of HotCocoa into its OWN standalone library, MacRuby Graphics. Here's my fork of that project, where future development efforts will be directed: github.com/drtoast/macruby_graphics

Particles/Flowers

July 2, 2008 | drawing

This example uses a system of particles that travel upwards from the bottom of the canvas. The paths taken by the particles are rendered as stems, and a "flower" is drawn at each endpoint.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# initialize canvas
canvas = Canvas.new :type => :image, :filename => 'particles.png', :size => [400,400]
canvas.background(Color.black)

# load images and grab colors
img = Image.new('italy.jpg').saturation(1.9)
redcolors = img.colors(100)
img = Image.new('v2.jpg').saturation(1.9)
bluecolors = img.colors(100)

# create flower head shape
head = Path.new.oval(0,0,10,10,:center)
petals = 3
for i in 1..petals do
  head.rotate(360/petals)
  head.oval(0,10,5,5,:center)
  head.oval(0,17,2,2,:center)
end
# randomize head attributes
head.randomize :fill, redcolors
head.randomize :stroke, bluecolors
head.randomize :scale, 0.2..2.0
head.randomize :rotation, 0..360

# create particles
total_particles = 100
total_iterations = 100
particles = []
for i in 0...total_particles do
  # start particle at random point at bottom of canvas
  x = random(canvas.width/2 - 50,canvas.width/2 + 50)
  p = Particle.new(x,0)
  p.velocity_x = random(-0.5,0.5)   # set initial x velocity
  p.velocity_y = random(1.0,3.0)    # set initial y velocity
  p.acceleration = 0.1            # set drag or acceleration
  particles[i] = p          # add particle to array
end

# animate particles
for frame in 0...total_iterations do
  for i in 0...total_particles do
    particles[i].move
  end
end

# draw particle trails and heads
for i in 0...total_particles do
  canvas.push
  # choose a stem color
  color = choose(bluecolors).a(0.7).analog(20,0.7)
  canvas.stroke(color)
  canvas.strokewidth(random(0.5,2.0))
  # draw the particle
  particles[i].draw(canvas)
  # go to the last particle position and draw the flower head
  canvas.translate(particles[i].points[-1][0],particles[i].points[-1][1])
  canvas.draw(head)
  canvas.pop
end

canvas.save

Text Spirograph

July 2, 2008 | text

Text can be positioned and scaled arbitrarily on the canvas, rendered using any font that is installed on your system.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'spiro.png', :size => [400,400]
canvas.background(Color.beige)
canvas.fill(Color.black)
canvas.font('Book Antiqua')
canvas.fontsize(12)
canvas.translate(200,200)

# rotate, draw text, repeat
for frame in 1..180
    canvas.push
    canvas.rotate((frame*2) + 120)
    canvas.translate(70,0)
    canvas.text("Going ...", 80, 0)
    canvas.rotate(frame * 6)
    canvas.text("Around and", 20, 0)
    canvas.pop
end

# save the canvas
canvas.save

Iterating Paths

July 2, 2008 | drawing

By sending the "increment" method to a Path object, you can specify changes that will continue to add up or "drift" each time the path is drawn to the canvas.
#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# create a new 400x400 pixel canvas to draw on
canvas = Canvas.new :type => :image, :filename => 'iterating.png', :size => [400,400]
canvas.background(Color.white)

# create a petal shape with base at (0,0), size 40x150, and bulge at 30px
shape = Path.new.petal(0,0,40,150,30)
# add a circle
shape.oval(-10,20,20,20)
# color it red
shape.fill(Color.red)

# increment shape parameters by the specified amount each iteration,
# or by a random value selected from the specified range
shape.increment(:rotation, 5.0)
#shape.increment(:scale, 0.95)
shape.increment(:scalex, 0.99)
shape.increment(:scaley, 0.96)
shape.increment(:x, 10.0)
shape.increment(:y, 12.0)
shape.increment(:hue,-0.02..0.02)
shape.increment(:saturation, -0.1..0.1)
shape.increment(:brightness, -0.1..0.1)
shape.increment(:alpha, -0.1..0.1)

# draw 200 petals on the canvas starting at location 50,200
canvas.translate(50,220)
canvas.draw(shape,0,0,200)
canvas.save

Randomizing Paths

July 2, 2008 | drawing

By applying the "randomize" method to a Path object, you can specify attributes that should be randomized each time the object is drawn to the canvas.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# create a new 400x400 pixel canvas to draw on
canvas = Canvas.new :type => :image, :filename => 'randomizing.png', :size => [400,400]
canvas.background(Color.white)

# create a flower shape
shape = Path.new
petals = 5
for i in 1..petals do
  shape.petal(0,0,40,100)       # petal at x,y with width,height
  shape.rotate(360 / petals)    # rotate by 1/5th
end

# randomize shape parameters
shape.randomize :fill, Color.blue.complementary
shape.randomize :stroke, Color.blue.complementary
shape.randomize :strokewidth, 1.0..10.0
shape.randomize :rotation, 0..360
shape.randomize :scale, 0.5..1.0
shape.randomize :scalex, 0.5..1.0
shape.randomize :scaley, 0.5..1.0
shape.randomize :alpha, 0.5..1.0
# shape.randomize :hue, 0.5..0.8
shape.randomize :saturation, 0.0..1.0
shape.randomize :brightness, 0.0..1.0
shape.randomize :x, -100.0..100.0
shape.randomize :y, -100.0..100.0

# draw 50 flowers starting at the center of the canvas
canvas.translate(200,200)
canvas.draw(shape,0,0,100)
canvas.save

Substrate

July 2, 2008 | drawing

This example is based on "Substrate" by Jared Tarbell (complexification.net). Some modifications were made to the color rendering algorithm. (Warning: the new MacRuby version eats a lot more memory than the older RubyCocoa version, so you may want to lower the number of cracks or frames)

#!/usr/local/bin/macruby

# Ported to HotCocoa::Graphics by James Reynolds 6/1/2008 from:
# Substrate Watercolor
# j.tarbell   June, 2004
# Albuquerque, New Mexico
# complexification.net

include HotCocoa
include Graphics

FRAMES = 800
DIMX = 400
DIMY = 400

# sand painters
COLORSIZE = 2.0
CRACKSIZE = 1.0
MAXCRACKS = 100
SANDGRAINS = 64

# color parameters
MAXPAL = 256
NUMPAL = 0
HUEDRIFT = 0.0

# grab colors
i = Image.new('v2.jpg')
GOODCOLOR = i.colors(MAXPAL)

# grid of CRACKS
CGRID = Array.new(DIMX*DIMY);
CRACKS = Array.new(MAXCRACKS);
$numcracks = 0;
C = Canvas.new :type => :image, :filename => 'substrate.png', :size => [400,400]
C.background(Color.white);

# METHODS --------------------------------------------------

def makeCrack()
  if ($numcracks<MAXCRACKS)
    # make a new crack instance
    CRACKS[$numcracks] = Crack.new
    $numcracks += 1
  end
end

def beginstuff
  # erase crack grid
  for y in 0...DIMY do
    for x in 0...DIMX do
      CGRID[y*DIMX+x] = 10001;
    end
  end
  # make random crack seeds
  for k in 0...16 do
    i = rand(DIMX*DIMY-1)
    CGRID[i]=rand(360)
  end

  # make just three CRACKS
  $numcracks=0;
  for k in 0...3 do
    makeCrack()
  end
  C.background(Color.white);
end

# OBJECTS -------------------------------------------------------

class Crack

  def initialize
    puts "Crack.new"
    # find placement along existing crack
    @sp = SandPainter.new(C)
    @sp.grains = SANDGRAINS
    @sp.grainsize = COLORSIZE
    @sp.huedrift = HUEDRIFT
    @sp.color = choose(GOODCOLOR)
    @verbose = true
    findStart();
  end

  def findStart()

    # pick random point
    px=0;
    py=0;

    # shift until crack is found
    found=false;
    timeout = 0;
    #while ((!found) && (timeout < 1000))
    while ((!found))# || (timeout > 1000))
      px = rand(DIMX)
      py = rand(DIMY)
      if (CGRID[py*DIMX+px]<10000)
        found=true;
        puts "found crack at #{px},#{py} with angle #{CGRID[py*DIMX+px]} (#{found})" if @verbose
      else
        #puts "checked #{px},#{py}" if @verbose
      end
      timeout += 1
    end

    if (found)
      # start crack
      a = CGRID[py*DIMX+px];
      if (random(100)<50)
        a-=90+random(-2,2.1)#.to_i;
      else
        a+=90+random(-2,2.1)#.to_i;
      end
      startCrack(px,py,a);
    else
      println("timeout: "+timeout);
    end
  end

  def startCrack(x, y, t)
    puts "starting crack at #{x},#{y} with angle #{t}" if @verbose
    @x=x;
    @y=y;
    @t=t;#%360;
    @x+=0.61*cos(@t*PI/180);
    @y+=0.61*sin(@t*PI/180);
  end

  def move()
    # continue cracking
    @x+=0.42*cos(@t*PI/180);
    @y+=0.42*sin(@t*PI/180);

    # bound check
    z = 0.33;
    cx = (@x+random(-z,z)).to_i;  # add fuzz
    cy = (@y+random(-z,z)).to_i;

    # draw sand painter
    regionColor();

    # draw black crack
    C.fill(Color.black);
    C.oval(@x+random(-z,z),@y+random(-z,z),CRACKSIZE,CRACKSIZE);

    if ((cx>=0) && (cx<DIMX) && (cy>=0) && (cy<DIMY))
      # safe to check
      if ((CGRID[cy*DIMX+cx]>10000) || (CGRID[cy*DIMX+cx]-@t).abs<5)
        # continue cracking
        CGRID[cy*DIMX+cx]=@t.to_i
      elsif ((CGRID[cy*DIMX+cx]-@t).abs>2)
        # crack encountered (not self), stop cracking
        findStart();
        makeCrack();
      end
    else
      # out of bounds, stop cracking
      findStart();
      makeCrack();
    end
  end

  def regionColor()
    # start checking one step away
    rx=@x;
    ry=@y;
    openspace=true;

    # find extents of open space
    while (openspace)
      # move perpendicular to crack
      rx+=0.81*sin(@t*PI/180);
      ry-=0.81*cos(@t*PI/180);
      cx = rx.to_i
      cy = ry.to_i
      if ((cx>=0) && (cx<DIMX) && (cy>=0) && (cy<DIMY))
        # safe to check
        if (CGRID[cy*DIMX+cx]>10000)
          # space is open
        else
          openspace=false;
        end
      else
        openspace=false;
      end
    end
    # draw sand painter
    @sp.render(@x,@y,rx,ry);
  end
end

beginstuff

for i in 0..FRAMES do
  # crack all CRACKS
  for n in 0...$numcracks do
    puts "frame #{i} moving crack #{n}"
    CRACKS[n].move();
  end
end

C.save

Drawing Hair

July 2, 2008 | drawing

Applying the "hair" method to a Rope object will render organic flowing threads to the canvas.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# create a new 400x400 pixel canvas to draw on
canvas = Canvas.new :type => :image, :filename => 'hair.png', :size => [400,400]

# choose a random color and set the background to a darker variant
clr = Color.random.a(0.5)
canvas.background(clr.copy.darken(0.6))

# create a new rope with 200 fibers
rope = Rope.new(canvas)
rope.width = 100
rope.fibers = 200
rope.strokewidth = 0.4
rope.roundness = 3.0

# randomly rotate the canvas from its center
canvas.translate(canvas.width/2,canvas.height/2)
canvas.rotate(random(0,360))
canvas.translate(-canvas.width/2,-canvas.height/2)

# draw 50 ropes
ropes = 50
for i in 0..ropes do
    canvas.stroke(clr.copy.analog(20, 0.8))   # rotate hue up to 20 deg left/right, vary brightness/saturation by up to 70%
    rope.x0 = -100                            # start rope off bottom left of canvas
    rope.y0 = -100
    rope.x1 = canvas.width + 100              # end rope off top right of canvas
    rope.y1 = canvas.height + 100
    rope.hair                                 # draw rope in organic "hair" style
end

# save the canvas
canvas.save

Drawing Ribbons

July 2, 2008 | drawing

Applying the "ribbon" method to a Rope object will render smooth, flowing ribbons to the canvas.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# create a new 400x400 pixel canvas to draw on
canvas = Canvas.new :type => :image, :filename => 'ribbons.png', :size => [400,400]

# choose a random color and set the background to a darker variant
clr = Color.random.a(0.5)
canvas.background(clr.copy.darken(0.6))

# create a new rope with 200 fibers
rope = Rope.new(canvas)
rope.width = 500
rope.fibers = 200
rope.strokewidth = 1.0
rope.roundness = 1.5

# randomly rotate the canvas from its center
canvas.translate(canvas.width/2,canvas.height/2)
canvas.rotate(random(0,360))
canvas.translate(-canvas.width/2,-canvas.height/2)

# draw 20 ropes
ropes = 20
for i in 0..ropes do
    canvas.stroke(clr.copy.analog(10, 0.7))   # rotate hue up to 10 deg left/right, vary brightness/saturation by up to 70%
    rope.x0 = -100                            # start rope off bottom left of canvas
    rope.y0 = -100
    rope.x1 = canvas.width + 200              # end rope off top right of canvas
    rope.y1 = canvas.height + 200
    rope.ribbon                               # draw rope in smooth "ribbon" style
end

# save the canvas
canvas.save

Image Moving and Scaling

July 2, 2008 | images

Various scaling and moving operations are available for images. You can scale an image proportionally by a percentage, scale it to fit proportionally within a bounding box, or scale width and height to fit exactly within a bounding box.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'moving.png', :size => [400,400]
canvas.background Color.white
canvas.font 'Skia'
canvas.fontsize 14
canvas.fill Color.black
canvas.stroke Color.red

# load an image
img = Image.new 'v2.jpg'

# SCALE (multiply both dimensions by a scaling factor)
img.scale(0.2)
canvas.draw(img,0,240)  # draw the image at the specified coordinates
canvas.text("scale to 20%",0,220)

# FIT (scale to fit within the given dimensions, maintaining original aspect ratio)
img.reset               # first reset the image to its original size
img.fit(100,100)
canvas.fill(Color.white)
canvas.rect(120,240,100,100)
canvas.fill(Color.black)
canvas.draw(img,133,240)
canvas.text("fit into 100x100",120,220)

# RESIZE (scale to fit exactly within the given dimensions)
img.reset
img.resize(100,100)
canvas.draw(img,240,240)
canvas.text("resize to 100x100",240,220)

# CROP (to the largest square containing image data)
img.reset
img.scale(0.2).crop
canvas.draw(img,0,100)
canvas.text("crop max square",0,80)

# CROP (within a rectangle starting at x,y with width,height)
img.reset
img.scale(0.3).crop(0,0,100,100)
canvas.draw(img,120,100)
canvas.text("crop to 100x100",120,80)

# ROTATE
img.origin(:center)
img.rotate(45)
canvas.draw(img,300,140)
canvas.text("rotate 45 degrees",250,50)

#img.origin(:center)   # center the image
#img.translate(0,-150)    # move the image

canvas.save

Image Effects

July 2, 2008 | images

You can apply various Photoshop-style filters to images, then render them to the canvas.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'effects.png', :size => [400,400]
canvas.background(Color.white)
canvas.font('Skia')
canvas.fontsize(14)
canvas.fill(Color.black)

# load image file
img = Image.new('v2.jpg')

# set image width/height, starting position, and increment position
w,h = [100,100]
x,y = [0,290]
xoffset = 105
yoffset = 130

# ORIGINAL image, resized to fit within w,h:
img.fit(w,h)
canvas.draw(img,x,y)
canvas.text("original",x,y-15)
x += xoffset

# CRYSTALLIZE: apply a "crystallize" effect with the given radius
img.reset.fit(w,h)
img.crystallize(5.0)
canvas.draw(img,x,y)
canvas.text("crystallize",x,y-15)
x += xoffset

# BLOOM: apply a "bloom" effect with the given radius and intensity
img.reset.fit(w,h)
img.bloom(10, 1.0)
canvas.draw(img,x,y)
canvas.text("bloom",x,y-15)
x += xoffset

# EDGES: detect edges
img.reset.fit(w,h)
img.edges(10)
canvas.draw(img,x,y)
canvas.text("edges",x,y-15)
x += xoffset

# (go to next row)
x = 0
y -= yoffset

# POSTERIZE: reduce image to the specified number of colors
img.reset.fit(w,h)
img.posterize(8)
canvas.draw(img,x,y)
canvas.text("posterize",x,y-15)
x += xoffset

# TWIRL: rotate around x,y with radius and angle
img.reset.fit(w,h)
img.twirl(35,50,40,90)
canvas.draw(img,x,y)
canvas.text("twirl",x,y-15)
x += xoffset

# EDGEWORK: simulate a woodcut print
img.reset.fit(w,h)
canvas.rect(x,y,img.width,img.height) # needs a black background
img.edgework(0.5)
canvas.draw(img,x,y)
canvas.text("edgework",x,y-15)
x += xoffset

# DISPLACEMENT: use a second image as a displacement map
img.reset.fit(w,h)
img2 = Image.new('italy.jpg').resize(img.width,img.height)
img.displacement(img2, 30.0)
canvas.draw(img,x,y)
canvas.text("displacement",x,y-15)
x += xoffset

# (go to next row)
x = 0
y -= yoffset

# DOTSCREEN: simulate a dot screen: center point, angle(0-360), width(1-50), and sharpness(0-1)
img.reset.fit(w,h)
img.dotscreen(0,0,45,5,0.7)
canvas.draw(img,x,y)
canvas.text("dotscreen",x,y-15)
x += xoffset

# SHARPEN: sharpen using the given radius and intensity
img.reset.fit(w,h)
img.sharpen(2.0,2.0)
canvas.draw(img,x,y)
canvas.text("sharpen",x,y-15)
x += xoffset

# BLUR: apply a gaussian blur with the given radius
img.reset.fit(w,h)
img.blur(3.0)
canvas.draw(img,x,y)
canvas.text("blur",x,y-15)
x += xoffset

# MOTION BLUR: apply a motion blur with the given radius and angle
img.reset.fit(w,h)
img.motionblur(10.0,90)
canvas.draw(img,x,y)
canvas.text("motion blur",x,y-15)
x += xoffset

# save the canvas
canvas.save

Image Color Adjustments

July 2, 2008 | color, images

You can apply various color adjustments to a loaded image, then render it to the canvas. You can also grab colors from an image for use in a gradient or to paint objects on the canvas.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'colors.png', :size => [400,400]
canvas.background(Color.white)
canvas.font('Skia')
canvas.fontsize(14)
canvas.fill(Color.black)

# LOAD IMAGE
img = Image.new('v2.jpg')

w,h = [100,100]
x,y = [0,290]
xoffset = 105
yoffset = 130

# ORIGINAL image, resized to fit within w,h:
img.fit(w,h)
canvas.draw(img,x,y)
canvas.text("original",x,y-15)
x += xoffset

# HUE: rotate color wheel by degrees
img.reset.fit(w,h)
img.hue(45)
canvas.draw(img,x,y)
canvas.text("hue",x,y-15)
x += xoffset

# EXPOSURE: increase/decrease exposure by f-stops
img.reset.fit(w,h)
img.exposure(1.0)
canvas.draw(img,x,y)
canvas.text("exposure",x,y-15)
x += xoffset

# SATURATION: adjust saturation by multiplier
img.reset.fit(w,h)
img.saturation(2.0)
canvas.draw(img,x,y)
canvas.text("saturation",x,y-15)
x += xoffset

# (go to next row)
x = 0
y -= yoffset

# CONTRAST: adjust contrast by multiplier
img.reset.fit(w,h)
img.contrast(2.0)
canvas.draw(img,x,y)
canvas.text("contrast",x,y-15)
x += xoffset

# BRIGHTNESS: adjust brightness
img.reset.fit(w,h)
img.brightness(0.5)
canvas.draw(img,x,y)
canvas.text("brightness",x,y-15)
x += xoffset

# MONOCHROME: convert to a monochrome image
img.reset.fit(w,h)
img.monochrome(Color.orange)
canvas.draw(img,x,y)
canvas.text("monochrome",x,y-15)
x += xoffset

# WHITEPOINT: remap the white point
img.reset.fit(w,h)
img.whitepoint(Color.whiteish)
canvas.draw(img,x,y)
canvas.text("white point",x,y-15)
x += xoffset

# (go to next row)
x = 0
y -= yoffset

# CHAINING: apply multiple effects at once
img.reset.fit(w,h)
img.hue(60).saturation(2.0).contrast(2.5)
canvas.draw(img,x,y)
canvas.text("multi effects",x,y-15)
x += xoffset

# COLORS: sample random colors from the image and render as a gradient
img.reset.fit(w,h)              # reset the image and scale to fit within w,h
colors = img.colors(10).sort!   # select 10 random colors and sort by brightness
gradient = Gradient.new(colors) # create a new gradient using the selected colors
rect = Path.new.rect(x,y,img.width,img.height) # create a rectangle the size of the image
canvas.beginclip(rect)          # begin clipping so the gradient will only fill the rectangle
canvas.gradient(gradient,x,y,x+img.width,y+img.height) # draw the gradient between opposite corners of the rectangle
canvas.endclip                  # end clipping so we can draw on the rest of the canvas
canvas.text("get colors",x,y-15)    # add text label
x += xoffset

canvas.save

Image Blend Modes

July 2, 2008 | images

You can use various Photoshop-style blend modes to composite two or more images by applying the "blend" method to an Image object.

#!/usr/local/bin/macruby

require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'blend.png', :size => [400,730]
canvas.background(Color.white)
canvas.font('Skia')
canvas.fontsize(14)

# set image width,height
w,h = [95,95]

# set initial drawing position
x,y = [0,canvas.height - h - 10]

# load and resize two images
imgA = Image.new('v2.jpg').resize(w,h)
imgB = Image.new('italy.jpg').resize(w,h)

# add image B to image A using each blending mode, and draw to canvas
for blendmode in [:normal,:multiply,:screen,:overlay,:darken,:lighten,
  :colordodge,:colorburn,:softlight,:hardlight,:difference,:exclusion,
  :hue,:saturation,:color,:luminosity,:maximum,:minimum,:add,:atop,
  :in,:out,:over] do
  imgA.reset.resize(w,h)
  imgA.blend(imgB, blendmode)
  canvas.draw(imgA,x,y)
  canvas.text(blendmode,x,y-15)
  x += w + 5
  if (x > canvas.width - w + 5)
    x = 0
    y -= h + 25
  end
end

canvas.save

Gradients

June 30, 2008 | color

Radial or linear gradients may be drawn by first specifying their main constituent colors, then applying to the canvas with a start and endpoint. (warning: crashes in MacRuby 0.4)

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# FIXME: SEGFAULT!!

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'gradients.png', :size => [400,400]
canvas.background(Color.black)

# create a new gradient
gradient = Gradient.new

# set the component colors of the gradient
gradient.set(Color.black,Color.blue,Color.red.darken,Color.orange)

# draw a linear gradient starting at 50,50 and ending at 200,200
canvas.gradient(gradient, 50,50,200,200)

# do not extend gradient beyond its start/endpoints
gradient.pre(false)
gradient.post(false)

# draw a radial gradient starting at 200,200 with radius 100
canvas.radial(gradient, 200,200,100)

# save the canvas
canvas.save

Color Parsing

June 30, 2008 | color, images

You can load an image, grab an array of colors from it, and then apply those colors to other objects on the canvas.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'parsing.png', :size => [400,400]
canvas.background(Color.black)
canvas.shadow

# load an image and select 100 random colors
image = Image.new('italy.jpg')
randomcolors = image.colors(100)

# create a 20x20 square at 0,0
square = Path.new.rect(0,0,20,20,:center)

# randomize the color, scale, and rotation of the square
square.randomize(:fill, randomcolors)
square.randomize(:scale, 1.0..5.0)
square.randomize(:rotation, 0..360)

# draw 100 squares and the original image
canvas.grid(square,10,10)
image.fit(120,120)
canvas.draw(image)
canvas.save

Color Rules

June 29, 2008 | color

Various sets of colors can be generated from a single starting color by using the various classical color harmony schemes.  For more information, see Color Theory on Wikipedia.

#!/usr/local/bin/macruby

framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics

# set up the canvas and font
canvas = Canvas.new :type => :image, :filename => 'harmony.png', :size => [400,400]
canvas.background(Color.white)
canvas.font('Skia')
canvas.fontsize(12)

# create a new color with the specified red, green, blue, and alpha values
blue = Color.new(0.0,0.4,0.6,1.0)

# draw the original color
canvas.translate(135,350)
canvas.text("original color",-115,10)
canvas.fill(blue)
canvas.rect(0,0,255,30)
canvas.fill(Color.black)

# create a rectangle to use as a color swatch
swatch = Path.new.rect(0,0,15,30)
swatch.increment(:x, 15)

# draw harmony schemes derived from this color
for scheme in [
  :complementary,:split_complementary,:left_complement,
  :right_complement,:monochrome,:triad,:tetrad,:compound,
  :flipped_compound
  ] do
  canvas.translate(0,-38)
  canvas.text(scheme,-115,10)
  swatch.increment(:fill, blue.send(scheme).sort)
  canvas.draw(swatch,0,0,17)
end

# save the canvas
canvas.save