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

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/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# create a new 400x400 pixel canvas to draw oncanvas=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 30pxshape=Path.new.petal(0,0,40,150,30)# add a circleshape.oval(-10,20,20,20)# color it redshape.fill(Color.red)# increment shape parameters by the specified amount each iteration,# or by a random value selected from the specified rangeshape.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,200canvas.translate(50,220)canvas.draw(shape,0,0,200)canvas.save

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)

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

#!/usr/local/bin/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# create a new 400x400 pixel canvas to draw oncanvas=Canvas.new:type=>:image,:filename=>'hair.png',:size=>[400,400]# choose a random color and set the background to a darker variantclr=Color.random.a(0.5)canvas.background(clr.copy.darken(0.6))# create a new rope with 200 fibersrope=Rope.new(canvas)rope.width=100rope.fibers=200rope.strokewidth=0.4rope.roundness=3.0# randomly rotate the canvas from its centercanvas.translate(canvas.width/2,canvas.height/2)canvas.rotate(random(0,360))canvas.translate(-canvas.width/2,-canvas.height/2)# draw 50 ropesropes=50foriin0..ropesdocanvas.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 canvasrope.y0=-100rope.x1=canvas.width+100# end rope off top right of canvasrope.y1=canvas.height+100rope.hair# draw rope in organic "hair" styleend# save the canvascanvas.save

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

#!/usr/local/bin/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# create a new 400x400 pixel canvas to draw oncanvas=Canvas.new:type=>:image,:filename=>'ribbons.png',:size=>[400,400]# choose a random color and set the background to a darker variantclr=Color.random.a(0.5)canvas.background(clr.copy.darken(0.6))# create a new rope with 200 fibersrope=Rope.new(canvas)rope.width=500rope.fibers=200rope.strokewidth=1.0rope.roundness=1.5# randomly rotate the canvas from its centercanvas.translate(canvas.width/2,canvas.height/2)canvas.rotate(random(0,360))canvas.translate(-canvas.width/2,-canvas.height/2)# draw 20 ropesropes=20foriin0..ropesdocanvas.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 canvasrope.y0=-100rope.x1=canvas.width+200# end rope off top right of canvasrope.y1=canvas.height+200rope.ribbon# draw rope in smooth "ribbon" styleend# save the canvascanvas.save

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/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# set up the canvas and fontcanvas=Canvas.new:type=>:image,:filename=>'moving.png',:size=>[400,400]canvas.backgroundColor.whitecanvas.font'Skia'canvas.fontsize14canvas.fillColor.blackcanvas.strokeColor.red# load an imageimg=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 coordinatescanvas.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 sizeimg.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.resetimg.resize(100,100)canvas.draw(img,240,240)canvas.text("resize to 100x100",240,220)# CROP (to the largest square containing image data)img.resetimg.scale(0.2).cropcanvas.draw(img,0,100)canvas.text("crop max square",0,80)# CROP (within a rectangle starting at x,y with width,height)img.resetimg.scale(0.3).crop(0,0,100,100)canvas.draw(img,120,100)canvas.text("crop to 100x100",120,80)# ROTATEimg.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 imagecanvas.save

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

#!/usr/local/bin/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# set up the canvas and fontcanvas=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 fileimg=Image.new('v2.jpg')# set image width/height, starting position, and increment positionw,h=[100,100]x,y=[0,290]xoffset=105yoffset=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 radiusimg.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 intensityimg.reset.fit(w,h)img.bloom(10,1.0)canvas.draw(img,x,y)canvas.text("bloom",x,y-15)x+=xoffset# EDGES: detect edgesimg.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=0y-=yoffset# POSTERIZE: reduce image to the specified number of colorsimg.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 angleimg.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 printimg.reset.fit(w,h)canvas.rect(x,y,img.width,img.height)# needs a black backgroundimg.edgework(0.5)canvas.draw(img,x,y)canvas.text("edgework",x,y-15)x+=xoffset# DISPLACEMENT: use a second image as a displacement mapimg.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=0y-=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 intensityimg.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 radiusimg.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 angleimg.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 canvascanvas.save

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/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# set up the canvas and fontcanvas=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 IMAGEimg=Image.new('v2.jpg')w,h=[100,100]x,y=[0,290]xoffset=105yoffset=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 degreesimg.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-stopsimg.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 multiplierimg.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=0y-=yoffset# CONTRAST: adjust contrast by multiplierimg.reset.fit(w,h)img.contrast(2.0)canvas.draw(img,x,y)canvas.text("contrast",x,y-15)x+=xoffset# BRIGHTNESS: adjust brightnessimg.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 imageimg.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 pointimg.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=0y-=yoffset# CHAINING: apply multiple effects at onceimg.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 gradientimg.reset.fit(w,h)# reset the image and scale to fit within w,hcolors=img.colors(10).sort!# select 10 random colors and sort by brightnessgradient=Gradient.new(colors)# create a new gradient using the selected colorsrect=Path.new.rect(x,y,img.width,img.height)# create a rectangle the size of the imagecanvas.beginclip(rect)# begin clipping so the gradient will only fill the rectanglecanvas.gradient(gradient,x,y,x+img.width,y+img.height)# draw the gradient between opposite corners of the rectanglecanvas.endclip# end clipping so we can draw on the rest of the canvascanvas.text("get colors",x,y-15)# add text labelx+=xoffsetcanvas.save

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/macrubyrequire'hotcocoa/graphics'includeHotCocoaincludeGraphics# set up the canvas and fontcanvas=Canvas.new:type=>:image,:filename=>'blend.png',:size=>[400,730]canvas.background(Color.white)canvas.font('Skia')canvas.fontsize(14)# set image width,heightw,h=[95,95]# set initial drawing positionx,y=[0,canvas.height-h-10]# load and resize two imagesimgA=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 canvasforblendmodein[:normal,:multiply,:screen,:overlay,:darken,:lighten,:colordodge,:colorburn,:softlight,:hardlight,:difference,:exclusion,:hue,:saturation,:color,:luminosity,:maximum,:minimum,:add,:atop,:in,:out,:over]doimgA.reset.resize(w,h)imgA.blend(imgB,blendmode)canvas.draw(imgA,x,y)canvas.text(blendmode,x,y-15)x+=w+5if(x>canvas.width-w+5)x=0y-=h+25endendcanvas.save

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/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# FIXME: SEGFAULT!!# set up the canvas and fontcanvas=Canvas.new:type=>:image,:filename=>'gradients.png',:size=>[400,400]canvas.background(Color.black)# create a new gradientgradient=Gradient.new# set the component colors of the gradientgradient.set(Color.black,Color.blue,Color.red.darken,Color.orange)# draw a linear gradient starting at 50,50 and ending at 200,200canvas.gradient(gradient,50,50,200,200)# do not extend gradient beyond its start/endpointsgradient.pre(false)gradient.post(false)# draw a radial gradient starting at 200,200 with radius 100canvas.radial(gradient,200,200,100)# save the canvascanvas.save

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/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# set up the canvas and fontcanvas=Canvas.new:type=>:image,:filename=>'parsing.png',:size=>[400,400]canvas.background(Color.black)canvas.shadow# load an image and select 100 random colorsimage=Image.new('italy.jpg')randomcolors=image.colors(100)# create a 20x20 square at 0,0square=Path.new.rect(0,0,20,20,:center)# randomize the color, scale, and rotation of the squaresquare.randomize(:fill,randomcolors)square.randomize(:scale,1.0..5.0)square.randomize(:rotation,0..360)# draw 100 squares and the original imagecanvas.grid(square,10,10)image.fit(120,120)canvas.draw(image)canvas.save

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/macrubyframework'cocoa'require'rubygems'require'hotcocoa/graphics'includeHotCocoaincludeGraphics# set up the canvas and fontcanvas=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 valuesblue=Color.new(0.0,0.4,0.6,1.0)# draw the original colorcanvas.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 swatchswatch=Path.new.rect(0,0,15,30)swatch.increment(:x,15)# draw harmony schemes derived from this colorforschemein[:complementary,:split_complementary,:left_complement,:right_complement,:monochrome,:triad,:tetrad,:compound,:flipped_compound]docanvas.translate(0,-38)canvas.text(scheme,-115,10)swatch.increment(:fill,blue.send(scheme).sort)canvas.draw(swatch,0,0,17)end# save the canvascanvas.save