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
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.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'particles.png', :size => [400,400]
canvas.background(Color.black)
img = Image.new('italy.jpg').saturation(1.9)
redcolors = img.colors(100)
img = Image.new('v2.jpg').saturation(1.9)
bluecolors = img.colors(100)
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
head.randomize :fill, redcolors
head.randomize :stroke, bluecolors
head.randomize :scale, 0.2..2.0
head.randomize :rotation, 0..360
total_particles = 100
total_iterations = 100
particles = []
for i in 0...total_particles do
x = random(canvas.width/2 - 50,canvas.width/2 + 50)
p = Particle.new(x,0)
p.velocity_x = random(-0.5,0.5)
p.velocity_y = random(1.0,3.0)
p.acceleration = 0.1
particles[i] = p
end
for frame in 0...total_iterations do
for i in 0...total_particles do
particles[i].move
end
end
for i in 0...total_particles do
canvas.push
color = choose(bluecolors).a(0.7).analog(20,0.7)
canvas.stroke(color)
canvas.strokewidth(random(0.5,2.0))
particles[i].draw(canvas)
canvas.translate(particles[i].points[-1][0],particles[i].points[-1][1])
canvas.draw(head)
canvas.pop
end
canvas.save
July 2, 2008 | text
Text can be positioned and scaled arbitrarily on the canvas, rendered using any font that is installed on your system.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
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)
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
canvas.save
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.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'iterating.png', :size => [400,400]
canvas.background(Color.white)
shape = Path.new.petal(0,0,40,150,30)
shape.oval(-10,20,20,20)
shape.fill(Color.red)
shape.increment(:rotation, 5.0)
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)
canvas.translate(50,220)
canvas.draw(shape,0,0,200)
canvas.save
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.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'randomizing.png', :size => [400,400]
canvas.background(Color.white)
shape = Path.new
petals = 5
for i in 1..petals do
shape.petal(0,0,40,100)
shape.rotate(360 / petals)
end
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 :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
canvas.translate(200,200)
canvas.draw(shape,0,0,100)
canvas.save
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)
include HotCocoa
include Graphics
FRAMES = 800
DIMX = 400
DIMY = 400
COLORSIZE = 2.0
CRACKSIZE = 1.0
MAXCRACKS = 100
SANDGRAINS = 64
MAXPAL = 256
NUMPAL = 0
HUEDRIFT = 0.0
i = Image.new('v2.jpg')
GOODCOLOR = i.colors(MAXPAL)
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);
def makeCrack()
if ($numcracks<MAXCRACKS)
CRACKS[$numcracks] = Crack.new
$numcracks += 1
end
end
def beginstuff
for y in 0...DIMY do
for x in 0...DIMX do
CGRID[y*DIMX+x] = 10001;
end
end
for k in 0...16 do
i = rand(DIMX*DIMY-1)
CGRID[i]=rand(360)
end
$numcracks=0;
for k in 0...3 do
makeCrack()
end
C.background(Color.white);
end
class Crack
def initialize
puts "Crack.new"
@sp = SandPainter.new(C)
@sp.grains = SANDGRAINS
@sp.grainsize = COLORSIZE
@sp.huedrift = HUEDRIFT
@sp.color = choose(GOODCOLOR)
@verbose = true
findStart();
end
def findStart()
px=0;
py=0;
found=false;
timeout = 0;
while ((!found))
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
end
timeout += 1
end
if (found)
a = CGRID[py*DIMX+px];
if (random(100)<50)
a-=90+random(-2,2.1)
else
a+=90+random(-2,2.1)
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;
@x+=0.61*cos(@t*PI/180);
@y+=0.61*sin(@t*PI/180);
end
def move()
@x+=0.42*cos(@t*PI/180);
@y+=0.42*sin(@t*PI/180);
z = 0.33;
cx = (@x+random(-z,z)).to_i;
cy = (@y+random(-z,z)).to_i;
regionColor();
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))
if ((CGRID[cy*DIMX+cx]>10000) || (CGRID[cy*DIMX+cx]-@t).abs<5)
CGRID[cy*DIMX+cx]=@t.to_i
elsif ((CGRID[cy*DIMX+cx]-@t).abs>2)
findStart();
makeCrack();
end
else
findStart();
makeCrack();
end
end
def regionColor()
rx=@x;
ry=@y;
openspace=true;
while (openspace)
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))
if (CGRID[cy*DIMX+cx]>10000)
else
openspace=false;
end
else
openspace=false;
end
end
@sp.render(@x,@y,rx,ry);
end
end
beginstuff
for i in 0..FRAMES do
for n in 0...$numcracks do
puts "frame #{i} moving crack #{n}"
CRACKS[n].move();
end
end
C.save
July 2, 2008 | drawing

Applying the "hair" method to a Rope object will render organic flowing threads to the canvas.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'hair.png', :size => [400,400]
clr = Color.random.a(0.5)
canvas.background(clr.copy.darken(0.6))
rope = Rope.new(canvas)
rope.width = 100
rope.fibers = 200
rope.strokewidth = 0.4
rope.roundness = 3.0
canvas.translate(canvas.width/2,canvas.height/2)
canvas.rotate(random(0,360))
canvas.translate(-canvas.width/2,-canvas.height/2)
ropes = 50
for i in 0..ropes do
canvas.stroke(clr.copy.analog(20, 0.8))
rope.x0 = -100
rope.y0 = -100
rope.x1 = canvas.width + 100
rope.y1 = canvas.height + 100
rope.hair
end
canvas.save
July 2, 2008 | drawing

Applying the "ribbon" method to a Rope object will render smooth, flowing ribbons to the canvas.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'ribbons.png', :size => [400,400]
clr = Color.random.a(0.5)
canvas.background(clr.copy.darken(0.6))
rope = Rope.new(canvas)
rope.width = 500
rope.fibers = 200
rope.strokewidth = 1.0
rope.roundness = 1.5
canvas.translate(canvas.width/2,canvas.height/2)
canvas.rotate(random(0,360))
canvas.translate(-canvas.width/2,-canvas.height/2)
ropes = 20
for i in 0..ropes do
canvas.stroke(clr.copy.analog(10, 0.7))
rope.x0 = -100
rope.y0 = -100
rope.x1 = canvas.width + 200
rope.y1 = canvas.height + 200
rope.ribbon
end
canvas.save
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.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
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
img = Image.new 'v2.jpg'
img.scale(0.2)
canvas.draw(img,0,240)
canvas.text("scale to 20%",0,220)
img.reset
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)
img.reset
img.resize(100,100)
canvas.draw(img,240,240)
canvas.text("resize to 100x100",240,220)
img.reset
img.scale(0.2).crop
canvas.draw(img,0,100)
canvas.text("crop max square",0,80)
img.reset
img.scale(0.3).crop(0,0,100,100)
canvas.draw(img,120,100)
canvas.text("crop to 100x100",120,80)
img.origin(:center)
img.rotate(45)
canvas.draw(img,300,140)
canvas.text("rotate 45 degrees",250,50)
canvas.save
July 2, 2008 | images
You can apply various Photoshop-style filters to images, then render them to the canvas.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
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)
img = Image.new('v2.jpg')
w,h = [100,100]
x,y = [0,290]
xoffset = 105
yoffset = 130
img.fit(w,h)
canvas.draw(img,x,y)
canvas.text("original",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.crystallize(5.0)
canvas.draw(img,x,y)
canvas.text("crystallize",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.bloom(10, 1.0)
canvas.draw(img,x,y)
canvas.text("bloom",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.edges(10)
canvas.draw(img,x,y)
canvas.text("edges",x,y-15)
x += xoffset
x = 0
y -= yoffset
img.reset.fit(w,h)
img.posterize(8)
canvas.draw(img,x,y)
canvas.text("posterize",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.twirl(35,50,40,90)
canvas.draw(img,x,y)
canvas.text("twirl",x,y-15)
x += xoffset
img.reset.fit(w,h)
canvas.rect(x,y,img.width,img.height)
img.edgework(0.5)
canvas.draw(img,x,y)
canvas.text("edgework",x,y-15)
x += xoffset
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
x = 0
y -= yoffset
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
img.reset.fit(w,h)
img.sharpen(2.0,2.0)
canvas.draw(img,x,y)
canvas.text("sharpen",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.blur(3.0)
canvas.draw(img,x,y)
canvas.text("blur",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.motionblur(10.0,90)
canvas.draw(img,x,y)
canvas.text("motion blur",x,y-15)
x += xoffset
canvas.save
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.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
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)
img = Image.new('v2.jpg')
w,h = [100,100]
x,y = [0,290]
xoffset = 105
yoffset = 130
img.fit(w,h)
canvas.draw(img,x,y)
canvas.text("original",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.hue(45)
canvas.draw(img,x,y)
canvas.text("hue",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.exposure(1.0)
canvas.draw(img,x,y)
canvas.text("exposure",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.saturation(2.0)
canvas.draw(img,x,y)
canvas.text("saturation",x,y-15)
x += xoffset
x = 0
y -= yoffset
img.reset.fit(w,h)
img.contrast(2.0)
canvas.draw(img,x,y)
canvas.text("contrast",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.brightness(0.5)
canvas.draw(img,x,y)
canvas.text("brightness",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.monochrome(Color.orange)
canvas.draw(img,x,y)
canvas.text("monochrome",x,y-15)
x += xoffset
img.reset.fit(w,h)
img.whitepoint(Color.whiteish)
canvas.draw(img,x,y)
canvas.text("white point",x,y-15)
x += xoffset
x = 0
y -= yoffset
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
img.reset.fit(w,h)
colors = img.colors(10).sort!
gradient = Gradient.new(colors)
rect = Path.new.rect(x,y,img.width,img.height)
canvas.beginclip(rect)
canvas.gradient(gradient,x,y,x+img.width,y+img.height)
canvas.endclip
canvas.text("get colors",x,y-15)
x += xoffset
canvas.save
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.
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'blend.png', :size => [400,730]
canvas.background(Color.white)
canvas.font('Skia')
canvas.fontsize(14)
w,h = [95,95]
x,y = [0,canvas.height - h - 10]
imgA = Image.new('v2.jpg').resize(w,h)
imgB = Image.new('italy.jpg').resize(w,h)
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
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)
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'gradients.png', :size => [400,400]
canvas.background(Color.black)
gradient = Gradient.new
gradient.set(Color.black,Color.blue,Color.red.darken,Color.orange)
canvas.gradient(gradient, 50,50,200,200)
gradient.pre(false)
gradient.post(false)
canvas.radial(gradient, 200,200,100)
canvas.save
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.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'parsing.png', :size => [400,400]
canvas.background(Color.black)
canvas.shadow
image = Image.new('italy.jpg')
randomcolors = image.colors(100)
square = Path.new.rect(0,0,20,20,:center)
square.randomize(:fill, randomcolors)
square.randomize(:scale, 1.0..5.0)
square.randomize(:rotation, 0..360)
canvas.grid(square,10,10)
image.fit(120,120)
canvas.draw(image)
canvas.save
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.
framework 'cocoa'
require 'rubygems'
require 'hotcocoa/graphics'
include HotCocoa
include Graphics
canvas = Canvas.new :type => :image, :filename => 'harmony.png', :size => [400,400]
canvas.background(Color.white)
canvas.font('Skia')
canvas.fontsize(12)
blue = Color.new(0.0,0.4,0.6,1.0)
canvas.translate(135,350)
canvas.text("original color",-115,10)
canvas.fill(blue)
canvas.rect(0,0,255,30)
canvas.fill(Color.black)
swatch = Path.new.rect(0,0,15,30)
swatch.increment(:x, 15)
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
canvas.save