[macruby-changes] [686] MacRuby/trunk/lib/hotcocoa

source_changes at macosforge.org source_changes at macosforge.org
Wed Oct 29 21:59:31 PDT 2008


Revision: 686
          http://trac.macosforge.org/projects/ruby/changeset/686
Author:   rich at infoether.com
Date:     2008-10-29 21:59:30 -0700 (Wed, 29 Oct 2008)
Log Message:
-----------
add hotcocoa/graphics from James Reynolds RubyCocoaGraphics library

Added Paths:
-----------
    MacRuby/trunk/lib/hotcocoa/graphics/
    MacRuby/trunk/lib/hotcocoa/graphics/canvas.rb
    MacRuby/trunk/lib/hotcocoa/graphics/color.rb
    MacRuby/trunk/lib/hotcocoa/graphics/elements/
    MacRuby/trunk/lib/hotcocoa/graphics/elements/particle.rb
    MacRuby/trunk/lib/hotcocoa/graphics/elements/rope.rb
    MacRuby/trunk/lib/hotcocoa/graphics/elements/sandpainter.rb
    MacRuby/trunk/lib/hotcocoa/graphics/gradient.rb
    MacRuby/trunk/lib/hotcocoa/graphics/image.rb
    MacRuby/trunk/lib/hotcocoa/graphics/path.rb
    MacRuby/trunk/lib/hotcocoa/graphics/pdf.rb
    MacRuby/trunk/lib/hotcocoa/graphics.rb

Added: MacRuby/trunk/lib/hotcocoa/graphics/canvas.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/canvas.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/canvas.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,815 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+
+module HotCocoa::Graphics
+
+  # drawing destination for writing a PDF, PNG, GIF, JPG, or TIF file
+  class Canvas
+    
+    BlendModes = {
+      :normal => KCGBlendModeNormal,
+      :darken => KCGBlendModeDarken,
+      :multiply => KCGBlendModeMultiply,
+      :screen => KCGBlendModeScreen,
+      :overlay => KCGBlendModeOverlay,
+      :darken => KCGBlendModeDarken,
+      :lighten => KCGBlendModeLighten,
+      :colordodge => KCGBlendModeColorDodge,
+      :colorburn => KCGBlendModeColorBurn,
+      :softlight => KCGBlendModeSoftLight,
+      :hardlight => KCGBlendModeHardLight,
+      :difference => KCGBlendModeDifference,
+      :exclusion => KCGBlendModeExclusion,
+      :hue => KCGBlendModeHue,
+      :saturation => KCGBlendModeSaturation,
+      :color => KCGBlendModeColor,
+      :luminosity => KCGBlendModeLuminosity,
+    }
+    BlendModes.default(KCGBlendModeNormal)
+    
+    DefaultOptions = {:quality => 0.8, :width => 400, :height => 400}
+  
+    attr_accessor :width, :height
+    
+    class << self
+      def for_rendering(options={}, &block)
+        options[:type] = :render
+        Canvas.new(options, &block)
+      end
+      
+      def for_pdf(options={}, &block)
+        options[:type] = :pdf
+        Canvas.new(options, &block)
+      end
+      
+      def for_image(options={}, &block)
+        options[:type] = :image
+        Canvas.new(options, &block)
+      end
+      
+      def for_context(options={}, &block)
+        options[:type] = :context
+        Canvas.new(options, &block)
+      end
+    end
+  
+    # create a new canvas with the given width, height, and output filename (pdf, png, jpg, gif, or tif)
+    def initialize(options={}, &block)
+      if options[:size]
+        options[:width] = options[:size][0]
+        options[:height] = options[:size][1]
+      end
+      options = DefaultOptions.merge(options)
+    
+      @width = options[:width]
+      @height = options[:height]
+      @output = options[:filename]
+      @stacksize = 0
+      @colorspace = CGColorSpaceCreateDeviceRGB() # => CGColorSpaceRef
+      @autoclosepath = false
+    
+      case options[:type]
+      when :pdf
+        @filetype = :pdf
+        # CREATE A PDF DRAWING CONTEXT
+        # url = NSURL.fileURLWithPath(image)
+        url = CFURLCreateFromFileSystemRepresentation(nil, @output, @output.length, false)
+        pdfrect = CGRect.new(CGPoint.new(0, 0), CGSize.new(width, height)) # Landscape
+        #@ctx = CGPDFContextCreateWithURL(url, pdfrect, nil)
+        consumer = CGDataConsumerCreateWithURL(url);
+        pdfcontext = CGPDFContextCreate(consumer, pdfrect, nil);
+        CGPDFContextBeginPage(pdfcontext, nil)
+        @ctx = pdfcontext
+      when :image, :render
+        # CREATE A BITMAP DRAWING CONTEXT
+        @filetype = File.extname(@output).downcase[1..-1].intern if options[:type] == :image
+
+        @bits_per_component = 8
+        @colorspace = CGColorSpaceCreateDeviceRGB() # => CGColorSpaceRef
+        #alpha = KCGImageAlphaNoneSkipFirst # opaque background
+        alpha = KCGImageAlphaPremultipliedFirst # transparent background
+
+        # 8 integer bits/component; 32 bits/pixel; 3-component colorspace; kCGImageAlphaPremultipliedFirst; 57141 bytes/row.
+        bytes = @bits_per_component * 4 * @width.ceil
+        @ctx = CGBitmapContextCreate(nil, @width, @height, @bits_per_component, bytes, @colorspace, alpha) # =>  CGContextRef
+      when :context
+        @ctx = options[:context]
+      else
+        raise "ERROR: output file type #{ext} not recognized"
+      end
+
+      # antialiasing
+      CGContextSetAllowsAntialiasing(@ctx, true)
+
+      # set defaults
+      fill          # set the default fill
+      nostroke      # no stroke by default
+      strokewidth   # set the default stroke width
+      font          # set the default font
+      antialias     # set the default antialias state
+      autoclosepath # set the autoclosepath default
+      quality(options[:quality])   # set the compression default
+      push  # save the pristine default default graphics state (retrieved by calling "reset")
+      push  # create a new graphics state for the user to mess up
+      if block
+        case block.arity
+        when 0
+          send(:instance_eval, &block)
+        else
+          block.call(self)
+        end
+      end
+    end
+
+    # SET CANVAS GLOBAL PARAMETERS
+
+    # print drawing functions if verbose is true
+    def verbose(tf=true)
+      @verbose = tf
+    end
+  
+    # set whether or not drawn paths should be antialiased (true/false)
+    def antialias(tf=true)
+      CGContextSetShouldAntialias(@ctx, tf)
+    end
+  
+    # set the alpha value for subsequently drawn objects
+    def alpha(val=1.0)
+      CGContextSetAlpha(@ctx, val)
+    end
+  
+    # set compression (0.0 = max, 1.0 = none)
+    def quality(factor=0.8)
+      @quality = factor
+    end
+  
+    # set the current fill (given a Color object, or RGBA values)
+    def fill(r=0, g=0, b=0, a=1)
+      case r
+      when Graphics::Color
+        g = r.g
+        b = r.b
+        a = r.a
+        r = r.r
+      end
+      CGContextSetRGBFillColor(@ctx, r, g, b, a) # RGBA
+      @fill = true
+    end
+  
+    # remove current fill
+    def nofill
+      CGContextSetRGBFillColor(@ctx, 0.0, 0.0, 0.0, 0.0) # RGBA
+      @fill = nil
+    end
+  
+    # SET CANVAS STROKE PARAMETERS
+  
+    # set stroke color (given a Color object, or RGBA values)
+    def stroke(r=0, g=0, b=0, a=1.0)
+      case r
+      when Color
+        g = r.g
+        b = r.b
+        a = r.a
+        r = r.r
+      end
+      CGContextSetRGBStrokeColor(@ctx, r, g, b, a) # RGBA
+      @stroke = true
+    end
+  
+    # set stroke width
+    def strokewidth(width=1)
+      CGContextSetLineWidth(@ctx, width.to_f)
+    end
+  
+    # don't use a stroke for subsequent drawing operations
+    def nostroke
+      CGContextSetRGBStrokeColor(@ctx, 0, 0, 0, 0) # RGBA
+      @stroke = false
+    end
+  
+    # set cap style to round, square, or butt
+    def linecap(style=:butt)
+      case style
+      when :round
+        cap = KCGLineCapRound
+      when :square
+        cap = KCGLineCapSquare
+      when :butt
+        cap = KCGLineCapButt
+      else
+        raise "ERROR: line cap style not recognized: #{style}"
+      end
+      CGContextSetLineCap(@ctx,cap)
+    end
+  
+    # set line join style to round, miter, or bevel
+    def linejoin(style=:miter)
+      case style
+      when :round
+        join = KCGLineJoinRound
+      when :bevel
+        join = KCGLineJoinBevel
+      when :miter
+        join = KCGLineJoinMiter
+      else
+        raise "ERROR: line join style not recognized: #{style}"
+      end
+      CGContextSetLineJoin(@ctx,join)
+    end
+  
+    # set lengths of dashes and spaces, and distance before starting dashes
+    def linedash(lengths=[10,2], phase=0.0)
+      count=lengths.size
+      CGContextSetLineDash(@ctx, phase, lengths, count)
+    end
+  
+    # revert to solid lines
+    def nodash
+      CGContextSetLineDash(@ctx, 0.0, nil, 0)
+    end
+  
+  
+    # DRAWING SHAPES ON CANVAS
+    
+    # draw a rectangle starting at x,y and having dimensions w,h
+    def rect(x=0, y=0, w=20, h=20, reg=@registration)
+      # center the rectangle
+      if (reg == :center)
+        x = x - w / 2
+        y = y - h / 2
+      end
+      CGContextAddRect(@ctx, [x, y, w, h])
+      CGContextDrawPath(@ctx, KCGPathFillStroke)
+    end
+  
+    # inscribe an oval starting at x,y inside a rectangle having dimensions w,h
+    def oval(x=0, y=0, w=20, h=20, reg=@registration)
+      # center the oval
+      if (reg == :center)
+        x = x - w / 2
+        y = y - w / 2
+      end
+      CGContextAddEllipseInRect(@ctx, [x, y, w, h])
+      CGContextDrawPath(@ctx, KCGPathFillStroke) # apply fill and stroke
+    end
+  
+    # draw a background color (given a Color object, or RGBA values)
+    def background(r=1, g=1, b=1, a=1.0)
+      case r
+      when Color
+        g = r.g
+        b = r.b
+        a = r.a
+        r = r.r
+      end
+      push
+      CGContextSetRGBFillColor(@ctx, r, g, b, a) # RGBA
+      rect(0,0, at width, at height)
+      pop
+    end
+  
+    # draw a radial gradiant starting at sx,sy with radius er
+    # optional: specify ending at ex,ey and starting radius sr
+    def radial(gradient, sx=@width/2, sy=@height/2, er=@width/2, ex=sx, ey=sy, sr=0.0)
+      #options = KCGGradientDrawsBeforeStartLocation
+      #options = KCGGradientDrawsAfterEndLocation
+      CGContextDrawRadialGradient(@ctx, gradient.gradient, [sx, sy], sr, [ex, ey], er, gradient.pre + gradient.post)
+    end
+  
+    # draw an axial(linear) gradient starting at sx,sy and ending at ex,ey
+    def gradient(gradient=Gradient.new, start_x=@width/2, start_y=0, end_x=@width/2, end_y=@height)
+      #options = KCGGradientDrawsBeforeStartLocation
+      #options = KCGGradientDrawsAfterEndLocation
+      CGContextDrawLinearGradient(@ctx, gradient.gradient, [start_x, start_y], [end_x, end_y], gradient.pre + gradient.post)
+    end
+
+    # draw a cartesian coordinate grid for reference
+    def cartesian(res=50, stroke=1.0, fsize=10)
+      # save previous state
+      new_state do
+        # set font and stroke
+        fontsize(fsize)
+        fill(Color.black)
+        stroke(Color.red)
+        strokewidth(stroke)
+        # draw vertical numbered grid lines
+        for x in (-width / res)..(width / res) do
+          line(x * res, -height, x * res, height)
+          text("#{x * res}", x * res, 0)
+        end
+        # draw horizontal numbered grid lines
+        for y in (-height / res)..(height / res) do
+          line(-width, y * res, width, y * res)
+          text("#{y * res}", 0, y * res)
+        end
+        # draw lines intersecting center of canvas
+        stroke(Color.black)
+        line(-width, -height, width, height)
+        line(width, -height, -width, height)
+        line(0, height, width, 0)
+        line(width / 2, 0, width / 2, height)
+        line(0, height / 2, width, height / 2)
+        # restore previous state
+      end
+    end
+  
+  
+    # DRAWING COMPLETE PATHS TO CANVAS
+  
+    # draw a line starting at x1,y1 and ending at x2,y2
+    def line(x1, y1, x2, y2)
+      CGContextAddLines(@ctx, [[x1, y1], [x2, y2]], 2)
+      CGContextDrawPath(@ctx, KCGPathStroke) # apply stroke
+      endpath
+      
+    end
+  
+    # draw a series of lines connecting the given array of points
+    def lines(points)
+      CGContextAddLines(@ctx, points, points.size)
+      CGContextDrawPath(@ctx, KCGPathStroke) # apply stroke
+      endpath
+    end
+  
+    # draw the arc of a circle with center point x,y, radius, start angle (0 deg = 12 o'clock) and end angle
+    def arc(x, y, radius, start_angle, end_angle)
+      start_angle = radians(90-start_angle)
+      end_angle = radians(90-end_angle)
+      clockwise = 1 # 1 = clockwise, 0 = counterclockwise
+      CGContextAddArc(@ctx, x, y, radius, start_angle, end_angle, clockwise)
+      CGContextDrawPath(@ctx, KCGPathStroke)
+    end
+  
+    # draw a bezier curve from the current point, given the coordinates of two handle control points and an end point
+    def curve(cp1x, cp1y, cp2x, cp2y, x1, y1, x2, y2)
+      beginpath(x1, y1)
+      CGContextAddCurveToPoint(@ctx, cp1x, cp1y, cp2x, cp2y, x2, y2)
+      endpath
+    end
+  
+    # draw a quadratic bezier curve from x1,y1 to x2,y2, given the coordinates of one control point
+    def qcurve(cpx, cpy, x1, y1, x2, y2)
+      beginpath(x1, y1)
+      CGContextAddQuadCurveToPoint(@ctx, cpx, cpy, x2, y2)
+      endpath
+    end
+  
+    # draw the given Path object
+    def draw(object, *args)
+      case object
+      when Path
+        draw_path(object, *args)
+      when Image
+        draw_image(object, *args)
+      else
+        raise ArgumentError.new("first parameter must be a Path or Image object not a #{object.class}")
+      end
+    end
+
+    # CONSTRUCTING PATHS ON CANVAS
+
+    # if true, automatically close the path after it is ended
+    def autoclosepath(tf=false)
+      @autoclosepath = tf
+    end
+    
+    def new_path(x, y, &block)
+      beginpath(x, y)
+      block.call
+      endpath
+    end
+    
+    # begin drawing a path at x,y
+    def beginpath(x, y)
+      CGContextBeginPath(@ctx)
+      CGContextMoveToPoint(@ctx, x, y)
+    end
+
+    # end the current path and draw it
+    def endpath
+      CGContextClosePath(@ctx) if @autoclosepath
+      #mode = KCGPathStroke
+      mode = KCGPathFillStroke
+      CGContextDrawPath(@ctx, mode) # apply fill and stroke
+    end
+  
+    # move the "pen" to x,y
+    def moveto(x, y)
+      CGContextMoveToPoint(@ctx, x, y)
+    end
+
+    # draw a line from the current point to x,y
+    def lineto(x, y)
+      CGContextAddLineToPoint(@ctx ,x, y)
+    end
+  
+    # draw a bezier curve from the current point, given the coordinates of two handle control points and an end point
+    def curveto(cp1x, cp1y, cp2x, cp2y, x, y)
+      CGContextAddCurveToPoint(@ctx, cp1x, cp1y, cp2x, cp2y, x, y)
+    end
+
+    # draw a quadratic bezier curve from the current point, given the coordinates of one control point and an end point
+    def qcurveto(cpx, cpy, x, y)
+      CGContextAddQuadCurveToPoint(@ctx, cpx, cpy, x, y)
+    end
+  
+    # draw an arc given the endpoints of two tangent lines and a radius
+    def arcto(x1, y1, x2, y2, radius)
+      CGContextAddArcToPoint(@ctx, x1, y1, x2, y2, radius)
+    end
+    
+    # draw the path in a grid with rows, columns
+    def grid(path, rows=10, cols=10)
+      push
+      rows.times do |row|
+        tx = (row+1) * (self.height / rows) - (self.height / rows) / 2
+        cols.times do |col|
+          ty = (col+1) * (self.width / cols) - (self.width / cols) / 2
+          push
+          translate(tx, ty)
+          draw(path)
+          pop
+        end
+      end
+      pop
+    end
+  
+
+    # TRANSFORMATIONS
+  
+    # set registration mode to :center or :corner
+    def registration(mode=:center)
+      @registration = mode
+    end
+  
+    # rotate by the specified degrees
+    def rotate(deg=0)
+      CGContextRotateCTM(@ctx, radians(-deg));
+    end
+  
+    # translate drawing context by x,y
+    def translate(x, y)
+      CGContextTranslateCTM(@ctx, x, y);
+    end
+  
+    # scale drawing context by x,y
+    def scale(x, y=x)
+      CGContextScaleCTM(@ctx, x, y)
+    end
+  
+    def skew(x=0, y=0)
+      x = Math::PI * x / 180.0
+      y = Math::PI * y / 180.0
+      transform = CGAffineTransformMake(1.0, Math::tan(y), Math::tan(x), 1.0, 0.0, 0.0)
+      CGContextConcatCTM(@ctx, transform)
+    end
+  
+  
+    # STATE
+    
+    def new_state(&block)
+      push
+      block.call
+      pop
+    end
+  
+    # push the current drawing context onto the stack
+    def push
+      CGContextSaveGState(@ctx)
+      @stacksize = @stacksize + 1
+    end
+  
+    # pop the previous drawing context off the stack
+    def pop
+      CGContextRestoreGState(@ctx)
+      @stacksize = @stacksize - 1
+    end
+  
+    # restore the initial context
+    def reset
+      until (@stacksize <= 1)
+        pop  # retrieve graphics states until we get to the default state
+      end
+      push  # push the retrieved pristine default state back onto the stack
+    end
+  
+  
+    # EFFECTS
+  
+    # apply a drop shadow with offset dx,dy, alpha, and blur
+    def shadow(dx=0.0, dy=0.0, a=2.0/3.0, blur=5)
+      color = CGColorCreate(@colorspace, [0.0, 0.0, 0.0, a])
+      CGContextSetShadowWithColor(@ctx, [dx, dy], blur, color)
+    end
+  
+    # apply a glow with offset dx,dy, alpha, and blur
+    def glow(dx=0.0, dy=0.0, a=2.0/3.0, blur=5)
+      color = CGColorCreate(@colorspace, [1.0, 1.0, 0.0, a])
+      CGContextSetShadowWithColor(@ctx, [dx, dy], blur, color)
+    end
+  
+    # stop using a shadow
+    def noshadow
+      CGContextSetShadowWithColor(@ctx, [0,0], 1, nil)
+    end
+  
+    # set the canvas blend mode (:normal, :darken, :multiply, :screen, etc)
+    def blend(mode)
+      CGContextSetBlendMode(@ctx, BlendModes[mode])
+    end
+  
+  
+    # CLIPPING MASKS
+    
+    # clip subsequent drawing operations within the given path
+    def beginclip(p, &block)
+      push
+      CGContextAddPath(@ctx, p.path)
+      CGContextClip(@ctx)
+      if block
+        block.call
+        endclip
+      end
+    end
+  
+    # stop clipping drawing operations
+    def endclip
+      pop
+    end
+  
+    # DRAW TEXT TO CANVAS
+  
+    # NOTE: may want to switch to ATSUI for text handling
+    # http://developer.apple.com/documentation/Carbon/Reference/ATSUI_Reference/Reference/reference.html
+  
+    # write the text at x,y using the current fill
+    def text(txt="A", x=0, y=0)
+      txt = txt.to_s unless txt.kind_of?(String)
+      if @registration == :center
+        width = textwidth(txt)
+        x = x - width / 2
+        y = y + @fsize / 2
+      end
+      CGContextShowTextAtPoint(@ctx, x, y, txt, txt.length)
+    end
+  
+    # determine the width of the given text without drawing it
+    def textwidth(txt, width=nil)
+      push
+      start = CGContextGetTextPosition(@ctx)
+      CGContextSetTextDrawingMode(@ctx, KCGTextInvisible)
+      CGContextShowText(@ctx, txt, txt.length)
+      final = CGContextGetTextPosition(@ctx)
+      pop
+      final.x - start.x
+    end
+  
+    # def textheight(txt)
+    #   # need to use ATSUI
+    # end
+    # 
+    # def textmetrics(txt)
+    #   # need to use ATSUI
+    # end
+  
+    # set font by name and optional size
+    def font(name="Helvetica", size=nil)
+      fontsize(size) if size
+      @fname = name
+      fontsize unless @fsize
+      CGContextSelectFont(@ctx, @fname, @fsize, KCGEncodingMacRoman)
+    end
+  
+    # set font size in points
+    def fontsize(points=20)
+      @fsize = points
+      font unless @fname
+      #CGContextSetFontSize(@ctx,points)
+      CGContextSelectFont(@ctx, @fname, @fsize, KCGEncodingMacRoman)
+    end
+  
+  
+    # SAVING/EXPORTING
+  
+    # return a CGImage of the canvas for reprocessing (only works if using a bitmap context)
+    def cgimage
+      CGBitmapContextCreateImage(@ctx)  # => CGImageRef (works with bitmap context only)
+      #cgimageref = CGImageCreate(@width, @height, @bits_per_component, nil,nil, at colorspace, nil, @provider,nil,true,KCGRenderingIntentDefault)
+    end
+  
+    # return a CIImage of the canvas for reprocessing (only works if using a bitmap context)
+    def ciimage
+      cgimageref = self.cgimage
+      CIImage.imageWithCGImage(cgimageref) # CIConcreteImage (CIImage)
+    end
+
+    # begin a new PDF page
+    def newpage
+      if (@filetype == :pdf)
+        CGContextFlush(@ctx)
+        CGPDFContextEndPage(@ctx)
+        CGPDFContextBeginPage(@ctx, nil)
+      else
+        puts "WARNING: newpage only valid when using PDF output"
+      end
+    end
+  
+    # save the image to a file
+    def save
+    
+      properties = {}
+  #    exif = {}
+      # KCGImagePropertyExifDictionary
+  #    exif[KCGImagePropertyExifUserComment] = 'Image downloaded from www.sheetmusicplus.com'
+  #    exif[KCGImagePropertyExifAuxOwnerName] = 'www.sheetmusicplus.com'
+      if @filetype == :pdf
+        CGPDFContextEndPage(@ctx)
+        CGContextFlush(@ctx)
+        return
+      elsif @filetype == :png
+        format = NSPNGFileType
+      elsif @filetype == :tif
+        format = NSTIFFFileType
+        properties[NSImageCompressionMethod] = NSTIFFCompressionLZW
+        #properties[NSImageCompressionMethod] = NSTIFFCompressionNone
+      elsif @filetype == :gif
+        format = NSGIFFileType
+        #properties[NSImageDitherTransparency] = 0 # 1 = dithered, 0 = not dithered
+        #properties[NSImageRGBColorTable] = nil # For GIF input and output. It consists of a 768 byte NSData object that contains a packed RGB table with each component being 8 bits.
+      elsif @filetype == :jpg
+        format = NSJPEGFileType
+        properties[NSImageCompressionFactor] = @quality # (jpeg compression, 0.0 = max, 1.0 = none)
+        #properties[NSImageEXIFData] = exif
+      end
+      cgimageref = CGBitmapContextCreateImage(@ctx)                      # => CGImageRef
+      bitmaprep = NSBitmapImageRep.alloc.initWithCGImage(cgimageref)     # => NSBitmapImageRep
+      blob = bitmaprep.representationUsingType(format, properties:properties) # => NSConcreteData
+      blob.writeToFile(@output, atomically:true)
+      true
+    end
+
+    # open the output file in its associated application
+    def open
+      system "open #{@output}"
+    end
+    
+    # def save(dest)
+    ## http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_data_mgr/chapter_11_section_3.html
+    #   properties = {
+    #     
+    #   }
+    #   cgimageref = CGBitmapContextCreateImage(@ctx)                   # => CGImageRef
+    #   destination = CGImageDestinationCreateWithURL(NSURL.fileURLWithPath(dest)) # => CGImageDestinationRef
+    #   CGImageDestinationSetProperties(destination,properties)
+    #   CGImageDestinationAddImage(cgimageref)
+    # end
+    
+    private
+    
+      # DRAWING PATHS ON A CANVAS
+
+      def draw_path(p, tx=0, ty=0, iterations=1)
+        new_state do
+          iterations.times do |i|
+            if (i > 0)
+              # INCREMENT TRANSFORM:
+              # translate x, y
+              translate(choose(p.inc[:x]), choose(p.inc[:y]))
+              # choose a rotation factor from the range
+              rotate(choose(p.inc[:rotation]))
+              # choose a scaling factor from the range
+              sc = choose(p.inc[:scale])
+              sx = choose(p.inc[:scalex]) * sc
+              sy = p.inc[:scaley] ? choose(p.inc[:scaley]) * sc : sx * sc
+              scale(sx, sy)
+            end
+
+            new_state do
+              # PICK AND ADJUST FILL/STROKE COLORS:
+              [:fill,:stroke].each do |kind|
+                # PICK A COLOR
+                if (p.inc[kind]) then
+                  # increment color from array
+                  colorindex = i % p.inc[kind].size
+                  c = p.inc[kind][colorindex].copy
+                else
+                  c = p.rand[kind]
+                  case c
+                  when Array
+                    c = choose(c).copy
+                  when Color
+                    c = c.copy
+                  else
+                    next
+                  end
+                end
+
+                if (p.inc[:hue] or p.inc[:saturation] or p.inc[:brightness])
+                  # ITERATE COLOR
+                  if (p.inc[:hue])
+                    newhue = (c.hue + choose(p.inc[:hue])) % 1
+                    c.hue(newhue)
+                  end
+                  if (p.inc[:saturation])
+                    newsat = (c.saturation + choose(p.inc[:saturation]))
+                    c.saturation(newsat)
+                  end
+                  if (p.inc[:brightness])
+                    newbright = (c.brightness + choose(p.inc[:brightness]))
+                    c.brightness(newbright)
+                  end
+                  if (p.inc[:alpha])
+                    newalpha = (c.a + choose(p.inc[:alpha]))
+                    c.a(newalpha)
+                  end
+                  p.rand[kind] = c
+                else
+                  # RANDOMIZE COLOR
+                  c.hue(choose(p.rand[:hue])) if p.rand[:hue]
+                  c.saturation(choose(p.rand[:saturation])) if p.rand[:saturation]
+                  c.brightness(choose(p.rand[:brightness])) if p.rand[:brightness]
+                end
+
+                # APPLY COLOR
+                fill(c) if kind == :fill
+                stroke(c) if kind == :stroke
+              end
+              # choose a stroke width from the range
+              strokewidth(choose(p.rand[:strokewidth])) if p.rand[:strokewidth]
+              # choose an alpha level from the range
+              alpha(choose(p.rand[:alpha])) if p.rand[:alpha]
+
+              # RANDOMIZE TRANSFORM:
+              # translate x, y
+              translate(choose(p.rand[:x]), choose(p.rand[:y]))
+              # choose a rotation factor from the range
+              rotate(choose(p.rand[:rotation]))
+              # choose a scaling factor from the range
+              sc = choose(p.rand[:scale])
+              sx = choose(p.rand[:scalex]) * sc
+              sy = p.rand[:scaley] ? choose(p.rand[:scaley]) * sc : sx * sc
+              scale(sx,sy)
+
+              # DRAW
+              if (tx > 0 || ty > 0)
+                translate(tx, ty)
+              end
+
+              CGContextAddPath(@ctx, p.path) if p.class == Path
+              CGContextDrawPath(@ctx, KCGPathFillStroke) # apply fill and stroke
+
+              # if there's an image, draw it clipped by the path
+              if (p.image)
+                beginclip(p)
+                image(p.image)
+                endclip
+              end
+
+            end
+          end
+        end
+      end
+      
+      # DRAWING IMAGES ON CANVAS
+
+      # draw the specified image at x,y with dimensions w,h.
+      # "img" may be a path to an image, or an Image instance
+      def draw_image(img, x=0, y=0, w=nil, h=nil, pagenum=1)
+        new_state do
+          if (img.kind_of?(Pdf))
+            w ||= img.width(pagenum)
+            h ||= img.height(pagenum)
+            if(@registration == :center)
+              x = x - w / 2
+              y = y - h / 2
+            end
+            img.draw(@ctx, x, y, w, h, pagenum)
+          elsif(img.kind_of?(String) || img.kind_of?(Image))
+            img = Image.new(img) if img.kind_of?(String)
+            w ||= img.width
+            h ||= img.height
+            img.draw(@ctx, x, y, w, h)
+          else
+            raise ArgumentError.new("canvas.image: not a recognized image type: #{img.class}")
+          end
+        end
+      end
+      
+  
+  end
+
+end


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/canvas.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/color.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/color.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/color.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,780 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+
+  # define and manipulate colors in RGBA format
+  class Color
+  
+    # License: GPL - includes ports of some code by Tom De Smedt, Frederik De Bleser
+  
+    #attr_accessor :r,:g,:b,:a
+    attr_accessor :rgb
+    # def initialize(r=0.0,g=0.0,b=0.0,a=1.0)
+    #   @c = CGColorCreate(@colorspace, [r,g,b,a])
+    # end
+  
+    # create a new color with the given RGBA values
+    def initialize(r=0.0, g=0.0, b=1.0, a=1.0)
+      @nsColor = NSColor.colorWithDeviceRed r, green:g, blue:b, alpha:a
+      @rgb = @nsColor.colorUsingColorSpaceName NSDeviceRGBColorSpace
+      self
+    end
+  
+    COLORNAMES = {
+        "lightpink"            => [1.00, 0.71, 0.76],
+        "pink"                 => [1.00, 0.75, 0.80],
+        "crimson"              => [0.86, 0.08, 0.24],
+        "lavenderblush"        => [1.00, 0.94, 0.96],
+        "palevioletred"        => [0.86, 0.44, 0.58],
+        "hotpink"              => [1.00, 0.41, 0.71],
+        "deeppink"             => [1.00, 0.08, 0.58],
+        "mediumvioletred"      => [0.78, 0.08, 0.52],
+        "orchid"               => [0.85, 0.44, 0.84],
+        "thistle"              => [0.85, 0.75, 0.85],
+        "plum"                 => [0.87, 0.63, 0.87],
+        "violet"               => [0.93, 0.51, 0.93],
+        "fuchsia"              => [1.00, 0.00, 1.00],
+        "darkmagenta"          => [0.55, 0.00, 0.55],
+        "purple"               => [0.50, 0.00, 0.50],
+        "mediumorchid"         => [0.73, 0.33, 0.83],
+        "darkviolet"           => [0.58, 0.00, 0.83],
+        "darkorchid"           => [0.60, 0.20, 0.80],
+        "indigo"               => [0.29, 0.00, 0.51],
+        "blueviolet"           => [0.54, 0.17, 0.89],
+        "mediumpurple"         => [0.58, 0.44, 0.86],
+        "mediumslateblue"      => [0.48, 0.41, 0.93],
+        "slateblue"            => [0.42, 0.35, 0.80],
+        "darkslateblue"        => [0.28, 0.24, 0.55],
+        "ghostwhite"           => [0.97, 0.97, 1.00],
+        "lavender"             => [0.90, 0.90, 0.98],
+        "blue"                 => [0.00, 0.00, 1.00],
+        "mediumblue"           => [0.00, 0.00, 0.80],
+        "darkblue"             => [0.00, 0.00, 0.55],
+        "navy"                 => [0.00, 0.00, 0.50],
+        "midnightblue"         => [0.10, 0.10, 0.44],
+        "royalblue"            => [0.25, 0.41, 0.88],
+        "cornflowerblue"       => [0.39, 0.58, 0.93],
+        "lightsteelblue"       => [0.69, 0.77, 0.87],
+        "lightslategray"       => [0.47, 0.53, 0.60],
+        "slategray"            => [0.44, 0.50, 0.56],
+        "dodgerblue"           => [0.12, 0.56, 1.00],
+        "aliceblue"            => [0.94, 0.97, 1.00],
+        "steelblue"            => [0.27, 0.51, 0.71],
+        "lightskyblue"         => [0.53, 0.81, 0.98],
+        "skyblue"              => [0.53, 0.81, 0.92],
+        "deepskyblue"          => [0.00, 0.75, 1.00],
+        "lightblue"            => [0.68, 0.85, 0.90],
+        "powderblue"           => [0.69, 0.88, 0.90],
+        "cadetblue"            => [0.37, 0.62, 0.63],
+        "darkturquoise"        => [0.00, 0.81, 0.82],
+        "azure"                => [0.94, 1.00, 1.00],
+        "lightcyan"            => [0.88, 1.00, 1.00],
+        "paleturquoise"        => [0.69, 0.93, 0.93],
+        "aqua"                 => [0.00, 1.00, 1.00],
+        "darkcyan"             => [0.00, 0.55, 0.55],
+        "teal"                 => [0.00, 0.50, 0.50],
+        "darkslategray"        => [0.18, 0.31, 0.31],
+        "mediumturquoise"      => [0.28, 0.82, 0.80],
+        "lightseagreen"        => [0.13, 0.70, 0.67],
+        "turquoise"            => [0.25, 0.88, 0.82],
+        "aquamarine"           => [0.50, 1.00, 0.83],
+        "mediumaquamarine"     => [0.40, 0.80, 0.67],
+        "mediumspringgreen"    => [0.00, 0.98, 0.60],
+        "mintcream"            => [0.96, 1.00, 0.98],
+        "springgreen"          => [0.00, 1.00, 0.50],
+        "mediumseagreen"       => [0.24, 0.70, 0.44],
+        "seagreen"             => [0.18, 0.55, 0.34],
+        "honeydew"             => [0.94, 1.00, 0.94],
+        "darkseagreen"         => [0.56, 0.74, 0.56],
+        "palegreen"            => [0.60, 0.98, 0.60],
+        "lightgreen"           => [0.56, 0.93, 0.56],
+        "limegreen"            => [0.20, 0.80, 0.20],
+        "lime"                 => [0.00, 1.00, 0.00],
+        "forestgreen"          => [0.13, 0.55, 0.13],
+        "green"                => [0.00, 0.50, 0.00],
+        "darkgreen"            => [0.00, 0.39, 0.00],
+        "lawngreen"            => [0.49, 0.99, 0.00],
+        "chartreuse"           => [0.50, 1.00, 0.00],
+        "greenyellow"          => [0.68, 1.00, 0.18],
+        "darkolivegreen"       => [0.33, 0.42, 0.18],
+        "yellowgreen"          => [0.60, 0.80, 0.20],
+        "olivedrab"            => [0.42, 0.56, 0.14],
+        "ivory"                => [1.00, 1.00, 0.94],
+        "beige"                => [0.96, 0.96, 0.86],
+        "lightyellow"          => [1.00, 1.00, 0.88],
+        "lightgoldenrodyellow" => [0.98, 0.98, 0.82],
+        "yellow"               => [1.00, 1.00, 0.00],
+        "olive"                => [0.50, 0.50, 0.00],
+        "darkkhaki"            => [0.74, 0.72, 0.42],
+        "palegoldenrod"        => [0.93, 0.91, 0.67],
+        "lemonchiffon"         => [1.00, 0.98, 0.80],
+        "khaki"                => [0.94, 0.90, 0.55],
+        "gold"                 => [1.00, 0.84, 0.00],
+        "cornsilk"             => [1.00, 0.97, 0.86],
+        "goldenrod"            => [0.85, 0.65, 0.13],
+        "darkgoldenrod"        => [0.72, 0.53, 0.04],
+        "floralwhite"          => [1.00, 0.98, 0.94],
+        "oldlace"              => [0.99, 0.96, 0.90],
+        "wheat"                => [0.96, 0.87, 0.07],
+        "orange"               => [1.00, 0.65, 0.00],
+        "moccasin"             => [1.00, 0.89, 0.71],
+        "papayawhip"           => [1.00, 0.94, 0.84],
+        "blanchedalmond"       => [1.00, 0.92, 0.80],
+        "navajowhite"          => [1.00, 0.87, 0.68],
+        "antiquewhite"         => [0.98, 0.92, 0.84],
+        "tan"                  => [0.82, 0.71, 0.55],
+        "burlywood"            => [0.87, 0.72, 0.53],
+        "darkorange"           => [1.00, 0.55, 0.00],
+        "bisque"               => [1.00, 0.89, 0.77],
+        "linen"                => [0.98, 0.94, 0.90],
+        "peru"                 => [0.80, 0.52, 0.25],
+        "peachpuff"            => [1.00, 0.85, 0.73],
+        "sandybrown"           => [0.96, 0.64, 0.38],
+        "chocolate"            => [0.82, 0.41, 0.12],
+        "saddlebrown"          => [0.55, 0.27, 0.07],
+        "seashell"             => [1.00, 0.96, 0.93],
+        "sienna"               => [0.63, 0.32, 0.18],
+        "lightsalmon"          => [1.00, 0.63, 0.48],
+        "coral"                => [1.00, 0.50, 0.31],
+        "orangered"            => [1.00, 0.27, 0.00],
+        "darksalmon"           => [0.91, 0.59, 0.48],
+        "tomato"               => [1.00, 0.39, 0.28],
+        "salmon"               => [0.98, 0.50, 0.45],
+        "mistyrose"            => [1.00, 0.89, 0.88],
+        "lightcoral"           => [0.94, 0.50, 0.50],
+        "snow"                 => [1.00, 0.98, 0.98],
+        "rosybrown"            => [0.74, 0.56, 0.56],
+        "indianred"            => [0.80, 0.36, 0.36],
+        "red"                  => [1.00, 0.00, 0.00],
+        "brown"                => [0.65, 0.16, 0.16],
+        "firebrick"            => [0.70, 0.13, 0.13],
+        "darkred"              => [0.55, 0.00, 0.00],
+        "maroon"               => [0.50, 0.00, 0.00],
+        "white"                => [1.00, 1.00, 1.00],
+        "whitesmoke"           => [0.96, 0.96, 0.96],
+        "gainsboro"            => [0.86, 0.86, 0.86],
+        "lightgrey"            => [0.83, 0.83, 0.83],
+        "silver"               => [0.75, 0.75, 0.75],
+        "darkgray"             => [0.66, 0.66, 0.66],
+        "gray"                 => [0.50, 0.50, 0.50],
+        "grey"                 => [0.50, 0.50, 0.50],
+        "dimgray"              => [0.41, 0.41, 0.41],
+        "dimgrey"              => [0.41, 0.41, 0.41],
+        "black"                => [0.00, 0.00, 0.00],
+        "cyan"                 => [0.00, 0.68, 0.94],
+        #"transparent"          => [0.00, 0.00, 0.00, 0.00],
+        "bark"                 => [0.25, 0.19, 0.13]
+    }
+    
+    RYBWheel = [
+      [  0,   0], [ 15,   8],
+      [ 30,  17], [ 45,  26],
+      [ 60,  34], [ 75,  41],
+      [ 90,  48], [105,  54],
+      [120,  60], [135,  81],
+      [150, 103], [165, 123],
+      [180, 138], [195, 155],
+      [210, 171], [225, 187],
+      [240, 204], [255, 219],
+      [270, 234], [285, 251],
+      [300, 267], [315, 282],
+      [330, 298], [345, 329],
+      [360, 0  ]
+    ]
+      
+    # create a color with the specified name
+    def self.named(name)
+      if COLORNAMES[name]
+        r, g, b = COLORNAMES[name]
+        #puts "matched name #{name}"
+        color = Color.new(r, g, b, 1.0)
+      elsif name.match(/^(dark|deep|light|bright)?(.*?)(ish)?$/)
+        #puts "matched #{$1}-#{$2}-#{$3}"
+        value = $1
+        color_name = $2
+        ish = $3
+        analogval = value ? 0 : 0.1
+        r, g, b = COLORNAMES[color_name] || [0.0, 0.0, 0.0]
+        color = Color.new(r, g, b, 1.0)
+        color = c.analog(20, analogval) if ish
+        color.lighten(0.2) if value and value.match(/light|bright/)
+        color.darken(0.2) if value and value.match(/dark|deep/)
+      else
+        color = Color.black
+      end
+      color
+    end
+  
+    # return the name of the nearest named color
+    def name
+      nearest, d = ["", 1.0]
+      red = r
+      green = g
+      blue = b
+      for hue in COLORNAMES.keys
+        rdiff = (red - COLORNAMES[hue][0]).abs
+        gdiff = (green - COLORNAMES[hue][1]).abs
+        bdiff = (blue - COLORNAMES[hue][2]).abs
+        totaldiff = rdiff + gdiff + bdiff
+        if (totaldiff < d)
+          nearest, d = [hue, totaldiff]
+        end
+      end
+      nearest
+    end
+  
+    # if the method name is not recognized, try creating a color with that name
+    def self.method_missing(name, *args)
+      Color.named(name.to_s.downcase)
+    end
+  
+    # return a copy of this color
+    def copy
+      Color.new(r, g, b, a)
+    end
+  
+    # print the color's component values
+    def to_s
+      "color: #{name} (#{r} #{g} #{b} #{a})"
+    end
+  
+    # sort the color by brightness in an array
+    def <=> othercolor
+       self.brightness <=> othercolor.brightness || self.hue <=> othercolor.hue
+    end
+  
+    # set or retrieve the red component
+    def r(val=nil)
+      if val
+        r, g, b, a = get_rgb
+        set_rgb(val, g, b, a)
+        self
+      else
+        @rgb.redComponent
+      end
+    end
+  
+    # set or retrieve the green component
+    def g(val=nil)
+      if val
+        r, g, b, a = get_rgb
+        set_rgb(r, val, b, a)
+        self
+      else
+        @rgb.greenComponent
+      end
+    end
+  
+    # set or retrieve the blue component
+    def b(val=nil)
+      if val
+        r, g, b, a = get_rgb
+        set_rgb(r, g, val, a)
+        self
+      else
+        @rgb.blueComponent
+      end
+    end
+  
+    # set or retrieve the alpha component
+    def a(val=nil)
+      if val
+        r, g, b, a = get_rgb
+        set_rgb(r, g, b, val)
+        self
+      else
+        @rgb.alphaComponent
+      end
+    end
+  
+    # set or retrieve the hue
+    def hue(val=nil)
+      if val
+        h, s, b, a = get_hsb
+        set_hsb(val, s, b, a)
+        self
+      else
+        @rgb.hueComponent
+      end
+    end
+  
+    # set or retrieve the saturation
+    def saturation(val=nil)
+      if val
+        h, s, b, a = get_hsb
+        set_hsb(h, val, b, a)
+        self
+      else
+        @rgb.saturationComponent
+      end
+    end
+  
+    # set or retrieve the brightness
+    def brightness(val=nil)
+      if val
+        h, s, b, a = get_hsb
+        set_hsb(h, s, val, a)
+        self
+      else
+        @rgb.brightnessComponent
+      end
+    end
+  
+    # decrease saturation by the specified amount
+    def desaturate(step=0.1)
+      saturation(saturation - step)
+      self
+    end
+  
+    # increase the saturation by the specified amount
+    def saturate(step=0.1)
+      saturation(saturation + step)
+      self
+    end
+  
+    # decrease the brightness by the specified amount
+    def darken(step=0.1)
+      brightness(brightness - step)
+      self
+    end
+  
+    # increase the brightness by the specified amount
+    def lighten(step=0.1)
+      brightness(brightness + step)
+      self
+    end
+  
+    # set the R,G,B,A values
+    def set(r, g, b, a=1.0)
+      set_rgb(r, g, b, a)
+      self
+    end
+  
+    # adjust the Red, Green, Blue, Alpha values by the specified amounts
+    def adjust_rgb(r=0.0, g=0.0, b=0.0, a=0.0)
+      r0, g0, b0, a0 = get_rgb
+      set_rgb(r0+r, g0+g, b0+b, a0+a)
+      self
+    end
+  
+    # return RGBA values
+    def get_rgb
+      #@rgb.getRed_green_blue_alpha_()
+      [@rgb.redComponent, @rgb.greenComponent, @rgb.blueComponent, @rgb.alphaComponent]
+    end
+  
+    # set color using RGBA values
+    def set_rgb(r, g, b, a=1.0)
+      @rgb = NSColor.colorWithDeviceRed r, green:g, blue:b, alpha:a
+      self
+    end
+  
+    # return HSBA values
+    def get_hsb
+      #@rgb.getHue_saturation_brightness_alpha_()
+      [@rgb.hueComponent, @rgb.saturationComponent, @rgb.brightnessComponent, @rgb.alphaComponent]
+    end
+  
+    # set color using HSBA values
+    def set_hsb(h,s,b,a=1.0)
+      @rgb = NSColor.colorWithDeviceHue h, saturation:s, brightness:b, alpha:a
+      self
+    end
+  
+    # adjust Hue, Saturation, Brightness, and Alpha by specified amounts
+    def adjust_hsb(h=0.0, s=0.0, b=0.0, a=0.0)
+      h0, s0, b0, a0 = get_hsb
+      set_hsb(h0+h, s0+s, b0+b, a0+a)
+      self
+    end
+  
+    # alter the color by the specified random scaling factor
+    # def ish(angle=10.0,d=0.02)
+    #   # r,g,b,a = get_rgb
+    #   # r = vary(r, variance)
+    #   # g = vary(g, variance)
+    #   # b = vary(b, variance)
+    #   # a = vary(a, variance)
+    #   # set_rgb(r,g,b,a)
+    #   analog(angle,d)
+    #   self
+    # end
+  
+    # create a random color
+    def random
+      set_rgb(rand, rand, rand, 1.0)
+      self
+    end
+  
+    # rotate the color on the artistic RYB color wheel (0 to 360 degrees)
+    def rotate_ryb(angle=180)
+
+      # An artistic color wheel has slightly different opposites
+      # (e.g. purple-yellow instead of purple-lime).
+      # It is mathematically incorrect but generally assumed
+      # to provide better complementary colors.
+      #   
+      # http://en.wikipedia.org/wiki/RYB_color_model
+
+      h = hue * 360
+      angle = angle % 360.0
+      a = 0
+
+      # Approximation of Itten's RYB color wheel.
+      # In HSB, colors hues range from 0-360.
+      # However, on the artistic color wheel these are not evenly distributed. 
+      # The second tuple value contains the actual distribution.
+
+      # Given a hue, find out under what angle it is
+      # located on the artistic color wheel.
+      (RYBWheel.size-1).times do |i|
+        x0,y0 = RYBWheel[i]    
+        x1,y1 = RYBWheel[i+1]
+        y1 += 360 if y1 < y0
+        if y0 <= h && h <= y1
+          a = 1.0 * x0 + (x1-x0) * (h-y0) / (y1-y0)
+          break
+        end
+      end
+    
+      # And the user-given angle (e.g. complement).
+      a = (a+angle) % 360
+
+      # For the given angle, find out what hue is
+      # located there on the artistic color wheel.
+      (RYBWheel.size-1).times do |i|
+        x0,y0 = RYBWheel[i]    
+        x1,y1 = RYBWheel[i+1]
+        y1 += 360 if y1 < y0
+        if x0 <= a && a <= x1
+          h = 1.0 * y0 + (y1-y0) * (a-x0) / (x1-x0)
+          break
+        end
+      end
+    
+      h = h % 360
+      set_hsb(h/360, self.saturation, self.brightness, self.a)
+      self
+    end
+  
+    # rotate the color on the RGB color wheel (0 to 360 degrees)
+    def rotate_rgb(angle=180)
+      hue = (self.hue + 1.0 * angle / 360) % 1
+      set_hsb(hue, self.saturation, self.brightness, @a)
+      self
+    end
+
+    # return a similar color, varying the hue by angle (0-360) and brightness/saturation by d
+    def analog(angle=20, d=0.5)
+      c = self.copy
+      c.rotate_ryb(angle * (rand*2-1))
+      c.lighten(d * (rand*2-1))
+      c.saturate(d * (rand*2-1))
+    end
+  
+    # randomly vary the color within a maximum hue range, saturation range, and brightness range
+    def drift(maxhue=0.1,maxsat=0.3,maxbright=maxsat)
+      # save original values the first time
+      @original_hue ||= self.hue
+      @original_saturation ||= self.saturation
+      @original_brightness ||= self.brightness
+      # get current values
+      current_hue = self.hue
+      current_saturation = self.saturation
+      current_brightness = self.brightness
+      # generate new values
+      randhue = ((rand * maxhue) - maxhue/2.0) + current_hue
+      randhue = inrange(randhue, (@original_hue - maxhue/2.0),(@original_hue + maxhue/2.0))
+      randsat = (rand * maxsat) - maxsat/2.0 + current_saturation
+      randsat = inrange(randsat, @original_saturation - maxsat/2.0, at original_saturation + maxsat/2.0)
+      randbright = (rand * maxbright) - maxbright/2.0 + current_brightness
+      randbright = inrange(randbright, @original_brightness - maxbright/2.0, at original_brightness + maxbright/2.0)
+      # assign new values
+      self.hue(randhue)
+      self.saturation(randsat)
+      self.brightness(randbright)
+      self
+    end
+  
+    # convert to the complementary color (the color at opposite on the artistic color wheel)
+    def complement
+      rotate_ryb(180)
+      self
+    end
+
+    # blend with another color (doesn't work?)
+    # def blend(color, pct=0.5)
+    #   blended = NSColor.blendedColorWithFraction_ofColor(pct,color.rgb)
+    #   @rgb = blended.colorUsingColorSpaceName(NSDeviceRGBColorSpace)
+    #   self
+    # end
+  
+    # create a new RGBA color
+    def self.rgb(r, g, b, a=1.0)
+      Color.new(r,g,b,a)
+    end
+  
+    # create a new HSBA color
+    def self.hsb(h, s, b, a=1.0)
+      Color.new.set_hsb(h,s,b,a)
+    end
+  
+    # create a new gray color with the specified darkness
+    def self.gray(pct=0.5)
+      Color.new(pct,pct,pct,1.0)
+    end
+  
+    # return a random color
+    def self.random
+      Color.new.random
+    end
+
+    # Returns a list of complementary colors. The complement is the color 180 degrees across the artistic RYB color wheel.
+    # 1) ORIGINAL: the original color
+    # 2) CONTRASTING: same hue as original but much darker or lighter
+    # 3) SOFT SUPPORTING: same hue but lighter and less saturated than the original
+    # 4) CONTRASTING COMPLEMENT: a much brighter or darker version of the complement hue
+    # 5) COMPLEMENT: the hue 180 degrees opposite on the RYB color wheel with same brightness/saturation
+    # 6) LIGHT SUPPORTING COMPLEMENT VARIANT: a lighter less saturated version of the complement hue
+    def complementary
+      colors = []
+    
+      # A contrasting color: much darker or lighter than the original.
+      colors.push(self)
+      c = self.copy
+      if self.brightness > 0.4
+        c.brightness(0.1 + c.brightness*0.25)
+      else
+        c.brightness(1.0 - c.brightness*0.25)
+      end
+      colors.push(c)
+    
+      # A soft supporting color: lighter and less saturated.
+      c = self.copy
+      c.brightness(0.3 + c.brightness)
+      c.saturation(0.1 + c.saturation*0.3)
+      colors.push(c)
+    
+      # A contrasting complement: very dark or very light.
+      c_comp = self.copy.complement
+      c = c_comp.copy
+      if c_comp.brightness > 0.3
+        c.brightness(0.1 + c_comp.brightness*0.25)
+      else
+        c.brightness(1.0 - c.brightness*0.25)
+      end
+      colors.push(c)    
+    
+      # The complement
+      colors.push(c_comp)
+    
+      # and a light supporting variant.
+      c = c_comp.copy
+      c.brightness(0.3 + c.brightness)
+      c.saturation(0.1 + c.saturation*0.25)
+      colors.push(c)
+    
+      colors
+    end
+
+    # Returns a list with the split complement of the color.
+    # The split complement are the two colors to the left and right
+    # of the color's complement.
+    def split_complementary(angle=30)
+        colors = []
+        colors.push(self)
+        comp = self.copy.complement
+        colors.push(comp.copy.rotate_ryb(-angle).lighten(0.1))
+        colors.push(comp.copy.rotate_ryb(angle).lighten(0.1))
+        colors
+    end
+  
+    # Returns the left half of the split complement.
+    # A list is returned with the same darker and softer colors
+    # as in the complementary list, but using the hue of the
+    # left split complement instead of the complement itself.
+    def left_complement(angle=-30)
+      left = copy.complement.rotate_ryb(angle).lighten(0.1)
+      colors = complementary
+      colors[3].hue(left.hue)
+      colors[4].hue(left.hue)
+      colors[5].hue(left.hue)
+      colors
+    end
+  
+    # Returns the right half of the split complement.
+    # A list is returned with the same darker and softer colors
+    # as in the complementary list, but using the hue of the
+    # right split complement instead of the complement itself.
+    def right_complement(angle=30)
+      right = copy.complement.rotate_ryb(angle).lighten(0.1)
+      colors = complementary
+      colors[3].hue(right.hue)
+      colors[4].hue(right.hue)
+      colors[5].hue(right.hue)
+      colors
+    end
+  
+    # Returns colors that are next to each other on the wheel.
+    # These yield natural color schemes (like shades of water or sky).
+    # The angle determines how far the colors are apart, 
+    # making it bigger will introduce more variation.
+    # The contrast determines the darkness/lightness of
+    # the analogue colors in respect to the given colors.
+    def analogous(angle=10, contrast=0.25)
+      contrast = inrange(contrast, 0.0, 1.0)
+
+      colors = []
+      colors.push(self)
+
+      for i, j in [[1,2.2], [2,1], [-1,-0.5], [-2,1]] do
+        c = copy.rotate_ryb(angle*i)
+        t = 0.44-j*0.1
+        if brightness - contrast*j < t then
+          c.brightness(t)
+        else
+          c.brightness(self.brightness - contrast*j)
+        end
+        c.saturation(c.saturation - 0.05)
+        colors.push(c)
+      end
+      colors
+    end
+
+    # Returns colors in the same hue with varying brightness/saturation.  
+    def monochrome
+
+        colors = [self]
+
+        c = copy
+        c.brightness(_wrap(brightness, 0.5, 0.2, 0.3))
+        c.saturation(_wrap(saturation, 0.3, 0.1, 0.3))
+        colors.push(c)
+
+        c = copy
+        c.brightness(_wrap(brightness, 0.2, 0.2, 0.6))
+        colors.push(c)
+
+        c = copy
+        c.brightness(max(0.2, brightness+(1-brightness)*0.2))
+        c.saturation(_wrap(saturation, 0.3, 0.1, 0.3))
+        colors.push(c)
+
+        c = self.copy()
+        c.brightness(_wrap(brightness, 0.5, 0.2, 0.3))
+        colors.push(c)
+
+        colors
+    end
+  
+    # Returns a triad of colors.
+    # The triad is made up of this color and two other colors
+    # that together make up an equilateral triangle on 
+    # the artistic color wheel.
+    def triad(angle=120)
+        colors = [self]
+        colors.push(copy.rotate_ryb(angle).lighten(0.1))
+        colors.push(copy.rotate_ryb(-angle).lighten(0.1))
+        colors
+    end
+  
+    # Returns a tetrad of colors.
+    # The tetrad is made up of this color and three other colors
+    # that together make up a cross on the artistic color wheel.
+    def tetrad(angle=90)
+
+        colors = [self]
+
+        c = copy.rotate_ryb(angle)
+        if brightness < 0.5 then
+          c.brightness(c.brightness + 0.2)
+        else
+          c.brightness(c.brightness - 0.2)
+        end 
+        colors.push(c)
+
+        c = copy.rotate_ryb(angle*2)
+        if brightness < 0.5
+          c.brightness(c.brightness + 0.1)
+        else
+          c.brightness(c.brightness - 0.1)
+        end
+      
+        colors.push(c)
+        colors.push(copy.rotate_ryb(angle*3).lighten(0.1))
+        colors
+    end
+  
+    # Roughly the complement and some far analogs.
+    def compound(flip=false)
+      
+        d = (flip ? -1 : 1)
+      
+        colors = [self]
+
+        c = copy.rotate_ryb(30*d)
+        c.brightness(_wrap(brightness, 0.25, 0.6, 0.25))
+        colors.push(c)
+
+        c = copy.rotate_ryb(30*d)
+        c.saturation(_wrap(saturation, 0.4, 0.1, 0.4))
+        c.brightness(_wrap(brightness, 0.4, 0.2, 0.4))
+        colors.push(c)
+
+        c = copy.rotate_ryb(160*d)
+        c.saturation(_wrap(saturation, 0.25, 0.1, 0.25))
+        c.brightness(max(0.2, brightness))
+        colors.push(c)
+
+        c = copy.rotate_ryb(150*d)
+        c.saturation(_wrap(saturation, 0.1, 0.8, 0.1))
+        c.brightness(_wrap(brightness, 0.3, 0.6, 0.3))
+        colors.push(c)
+
+        c = copy.rotate_ryb(150*d)
+        c.saturation(_wrap(saturation, 0.1, 0.8, 0.1))
+        c.brightness(_wrap(brightness, 0.4, 0.2, 0.4))
+        #colors.push(c)
+
+        colors
+    end
+  
+    # Roughly the complement and some far analogs.
+    def flipped_compound
+      compound(true)
+    end
+  
+    private
+  
+      # vary a single color component by a multiplier
+      def vary(original, variance)
+        newvalue = original + (rand * variance * (rand > 0.5 ? 1 : -1))
+        newvalue = inrange(newvalue,0.0,1.0)
+        newvalue
+      end
+  
+      # wrap within range
+      def _wrap(x, min, threshold, plus)
+        if x - min < threshold
+          x + plus
+        else
+          x - min
+        end
+      end
+  
+  end
+
+end


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/color.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/elements/particle.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/elements/particle.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/elements/particle.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,75 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+  
+  # wandering particle with brownian motion
+  class Particle 
+
+    attr_accessor :acceleration, :points, :stroke, :velocity_x, :velocity_y, :x, :y
+
+    # initialize particle origin x,y coordinates (relative to the center)
+    def initialize (x, y, velocity_x=0.0, velocity_y=2.0)
+      @age = 0
+      @acceleration = 0.5
+    
+      @x = x
+      @y = y
+    
+      @previous_x = 0
+      @previous_y = 0
+
+      # initialize velocity
+      @velocity_x=velocity_x
+      @velocity_y=velocity_y
+    
+      # append the point to the array
+      @points = [[@x, @y]]
+      @stroke = Color.white
+    end
+  
+    # move to a new position using brownian motion
+    def move
+      # save old x,y position
+      @previous_x=@x
+      @previous_y=@y
+
+      # move particle by velocity_x,velocity_y
+      @x += @velocity_x
+      @y += @velocity_y
+
+      # randomly increase/decrease direction
+      @velocity_x += random(-1.0, 1.0) * @acceleration
+      @velocity_y += random(-1.0, 1.0) * @acceleration
+
+      # draw a line from the old position to the new
+      #CANVAS.line(@previous_x, at previous_y, at x, at y);
+      @points.push([@x, @y])
+    
+      # grow old
+      @age += 1
+      if @age>200
+        # die and be reborn
+      end
+    
+    end
+  
+    def draw(canvas)
+      canvas.nofill
+      canvas.lines(@points)
+    end
+  
+  end
+end
\ No newline at end of file


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/elements/particle.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/elements/rope.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/elements/rope.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/elements/rope.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,99 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+  
+  class Rope
+  
+    attr_accessor :x0, :y0, :x1, :y1, :width, :fibers, :roundness, :strokewidth
+  
+    def initialize(canvas, options={})
+      @canvas       = canvas
+      @width        = options[:width] || 200
+      @fibers       = options[:fibers] || 200
+      @roundness    = options[:roundness] || 1.0
+      @strokewidth  = options[:strokewidth] || 0.4
+    end
+  
+    def hair(hair_x0=@x0, hair_y0=@y0, hair_x1=@x1, hair_y1=@y1, hair_width=@width, hair_fibers=@fibers)
+      @canvas.push
+      @canvas.strokewidth(@strokewidth)
+      @canvas.autoclosepath(false)
+      @canvas.nofill
+      hair_x0 = choose(hair_x0)
+      hair_y0 = choose(hair_y0)
+      hair_x1 = choose(hair_x1)
+      hair_y1 = choose(hair_y1)
+      vx0 = random(- at canvas.width   / 2,  @canvas.width   / 2) * @roundness
+      vy0 = random(- at canvas.height  / 2,  @canvas.height  / 2) * @roundness
+      vx1 = random(- at canvas.width   / 2,  @canvas.width   / 2) * @roundness
+      vy1 = random(- at canvas.height  / 2,  @canvas.height  / 2) * @roundness
+      hair_fibers.times do |j|
+        #x0,y0,x1,y1 = [@x0.choose, at y0.choose, at x1.choose, at y1.choose]
+        @canvas.beginpath(hair_x0, hair_y0)
+        @canvas.curveto(
+            hair_x0 + vx0 + rand(hair_width), hair_y0 + vy0 + rand(hair_width), # control point 1
+            hair_x1 + vx1, hair_y1 + vy1,                                       # control point 2
+            hair_x1, hair_y1                                                    # end point
+        )
+        @canvas.endpath
+      end
+      @canvas.pop
+    end
+  
+    def ribbon(ribbon_x0=@x0, ribbon_y0=@y0, ribbon_x1=@x1, ribbon_y1=@y1, ribbon_width=@width, ribbon_fibers=@fibers)
+      @canvas.push
+      @canvas.strokewidth(@strokewidth)
+      @canvas.autoclosepath(false)
+      @canvas.nofill
+      black = Color.black
+      white = Color.white
+      ribbon_x0 = choose(ribbon_x0)
+      ribbon_y0 = choose(ribbon_y0)
+      ribbon_x1 = choose(ribbon_x1)
+      ribbon_y1 = choose(ribbon_y1)
+      vx0 = random(- at canvas.width   / 2, @canvas.width  / 2) * @roundness
+      vy0 = random(- at canvas.height  / 2, @canvas.height / 2) * @roundness
+      vx1 = random(- at canvas.width   / 2, @canvas.width  / 2) * @roundness
+      vy1 = random(- at canvas.height  / 2, @canvas.height / 2) * @roundness
+      xwidth = rand(ribbon_width)
+      ywidth = rand(ribbon_width)
+      ribbon_fibers.times do |j|
+        #x0,y0,x1,y1 = [@x0.choose, at y0.choose, at x1.choose, at y1.choose]
+        xoffset = (j-1).to_f * xwidth / ribbon_fibers
+        yoffset = (j-1).to_f * ywidth / ribbon_fibers
+        cpx0 = ribbon_x0 + vx0 + xoffset
+        cpy0 = ribbon_y0 + vy0 + yoffset
+        cpx1 = ribbon_x1 + vx1
+        cpy1 = ribbon_y1 + vy1
+        # debug - show control points
+        # @canvas.fill(black)
+        # @canvas.oval(cpx0,cpy0,5,5,:center)
+        # @canvas.fill(white)
+        # @canvas.oval(cpx1,cpy1,5,5,:center)
+        # @canvas.nofill
+      
+        @canvas.beginpath(x0, y0)
+        @canvas.curveto(
+          cpx0, cpy0,           # control point 1
+          cpx1, cpy1,           # control point 2
+          ribbon_x1, ribbon_y1  # end point
+        )
+        @canvas.endpath
+      end
+      @canvas.pop
+    end
+  end
+end
\ No newline at end of file


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/elements/rope.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/elements/sandpainter.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/elements/sandpainter.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/elements/sandpainter.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,71 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+
+  # draw watercolor-like painted strokes (adapted from code by Jared Tarbell - complexification.net)
+  class SandPainter
+
+    attr_accessor :color, :grains, :grainsize, :maxalpha, :jitter, :huedrift, :saturationdrift, :brightnessdrift
+
+    def initialize(canvas, color=Color.red)
+      @canvas           = canvas
+      @color            = color
+      # set a random initial gain value
+      @gain             = random(0.01, 0.1)
+      @grainsize        = 6.0
+      @grains           = 64
+      @maxalpha         = 0.1
+      @jitter           = 0
+      @huedrift         = 0.2
+      @saturationdrift  = 0.3
+      @brightnessdrift  = 0.3
+    end
+  
+    # render a line that fades out from ox,oy to x,y
+    def render(ox, oy, x, y) 
+      @canvas.push
+      # modulate gain
+      @gain += random(-0.050, 0.050)
+      @gain = inrange(@gain, 0.0, 1.0)
+      # calculate grains by distance
+      #@grains = (sqrt((ox-x)*(ox-x)+(oy-y)*(oy-y))).to_i
+
+      # ramp from 0 to .015 for g = 0 to 1
+      w = @gain / (@grains-1)
+    
+      #raycolor = @color.analog
+      #@color.drift(0.2,0.1,0.1)
+      @color.drift(huedrift, saturationdrift, brightnessdrift)
+      #for i in 0.. at grains do ##RACK change to fixnum.times
+      (@grains + 1).times do |i|
+        # set alpha for this grain (ramp from maxalpha to 0.0 for i = 0 to 64)
+        a = @maxalpha - (i / @grains.to_f) * @maxalpha
+        fillcolor = @color.copy.a(a)
+        @canvas.fill(fillcolor)
+        #C.rect(ox+(x-ox)*sin(sin(i*w)),oy+(y-oy)*sin(sin(i*w)),1,1)
+        scaler = sin(sin(i * w)) # ramp sinusoidally from 0 to 1
+        x1 = ox + (x - ox) * scaler + random(- at jitter, @jitter)
+        y1 = oy + (y - oy) * scaler + random(- at jitter, @jitter)
+        #puts "#{scaler} #{w} #{i} #{a} => #{x1},#{y1} #{scaler}"
+        @canvas.oval(x1, y1, @grainsize, @grainsize, :center)
+        #C.oval(x,y, at grainsize, at grainsize,:center)
+        #C.oval(ox,oy, at grainsize, at grainsize,:center)
+      end
+      @canvas.pop
+    
+    end
+  end
+end
\ No newline at end of file


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/elements/sandpainter.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/gradient.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/gradient.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/gradient.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,63 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+  
+  # draw a smooth gradient between any number of key colors
+  class Gradient
+  
+    attr_reader :gradient, :drawpre, :drawpost
+  
+    # create a new gradient from black to white
+    def initialize(*colors)
+      @colorspace = CGColorSpaceCreateWithName(KCGColorSpaceGenericRGB)
+      colors = colors[0] if colors[0].class == Array
+      set(colors)
+      pre(true)
+      post(true)
+      self
+    end
+  
+    # create a gradient that evenly distributes the given colors
+    def set(colors)
+      colors ||= [Color.black, Color.white]
+      cgcolors = []
+      locations = []
+      increment = 1.0 / (colors.size - 1).to_f
+      i = 0
+      colors.each do |c|
+        cgcolor = CGColorCreate(@colorspace, [c.r, c.g, c.b, c.a])
+        cgcolors.push(cgcolor)
+        location = i * increment
+        locations.push(location)
+        i = i + 1
+      end
+      @gradient = CGGradientCreateWithColors(@colorspace, cgcolors, locations)
+    end
+  
+    # extend gradient before start location? (true/false)
+    def pre(tf=nil)
+      @drawpre = (tf ? KCGGradientDrawsBeforeStartLocation : 0) unless tf.nil?
+      @drawpre
+    end
+
+    # extend gradient after end location? (true/false)
+    def post(tf=nil)
+      @drawpost = (tf ? KCGGradientDrawsAfterEndLocation : 0) unless tf.nil?
+      @drawpost
+    end
+
+  end
+end
\ No newline at end of file


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/gradient.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/image.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/image.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/image.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,488 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+  
+  # load a raw image file for use on a canvas
+  class Image
+
+    
+    BlendModes = {
+      :normal => 'CISourceOverCompositing',
+      :multiply => 'CIMultiplyBlendMode',
+      :screen => 'CIScreenBlendMode',
+      :overlay => 'CIOverlayBlendMode',
+      :darken => 'CIDarkenBlendMode',
+      :lighten => 'CILightenBlendMode',
+      :colordodge => 'CIColorDodgeBlendMode',
+      :colorburn => 'CIColorBurnBlendMode',
+      :softlight => 'CISoftLightBlendMode',
+      :hardlight => 'CIHardLightBlendMode',
+      :difference => 'CIDifferenceBlendMode',
+      :exclusion => 'CIExclusionBlendMode',
+      :hue => 'CIHueBlendMode',
+      :saturation => 'CISaturationBlendMode',
+      :color => 'CIColorBlendMode',
+      :luminosity => 'CILuminosityBlendMode',
+      # following modes not available in CGContext:
+      :maximum => 'CIMaximumCompositing',
+      :minimum => 'CIMinimumCompositing',
+      :add => 'CIAdditionCompositing',
+      :atop => 'CISourceAtopCompositing',
+      :in => 'CISourceInCompositing',
+      :out => 'CISourceOutCompositing',
+      :over => 'CISourceOverCompositing'
+    }
+    BlendModes.default('CISourceOverCompositing')
+      
+    attr_reader :cgimage
+  
+    # load the image from the given path
+    def initialize(img, verbose=false)
+      self.verbose(verbose)
+      case img
+      when String
+        # if it's the path to an image file, load as a CGImage
+        @path = img
+        File.exists?(@path) or raise "ERROR: file not found: #{@path}"
+
+        nsimage = NSImage.alloc.initWithContentsOfFile(img)
+        nsdata = nsimage.TIFFRepresentation
+        @nsbitmapimage = NSBitmapImageRep.imageRepWithData(nsdata)
+        # cgimagesource = CGImageSourceCreateWithData(nsdata) # argh, doesn't work
+        @ciimage = CIImage.alloc.initWithBitmapImageRep(@nsbitmapimage)
+      when Canvas
+        puts "Image.new with canvas" if @verbose
+        @path = 'canvas'
+        @cgimage = img.cgimage
+      when Image
+        # copy image?
+      else
+        raise "ERROR: image type not recognized: #{img.class}"
+      end
+      # save the original
+      @original = @ciimage.copy
+      puts "Image.new from [#{@path}] at [#{x},#{y}] with #{width}x#{height}" if @verbose
+      self
+    end
+  
+    # reload the bitmap image
+    def reset
+      @ciimage = CIImage.alloc.initWithBitmapImageRep(@nsbitmapimage)
+      self
+    end
+  
+    # set registration mode to :center or :corner
+    def registration(mode=:center)
+      @registration = mode
+    end
+  
+    # print the parameters of the path
+    def to_s
+      "image.to_s: #{@path} at [#{x},#{y}] with #{width}x#{height}"
+    end
+  
+    # print drawing functions if verbose is true
+    def verbose(tf=true)
+      @verbose = tf
+    end
+  
+    # return the width of the image
+    def width
+      @ciimage ? @ciimage.extent.size.width : CGImageGetWidth(@cgimage)
+    end
+  
+    # return the height of the image
+    def height
+      @ciimage ? @ciimage.extent.size.height : CGImageGetHeight(@cgimage)
+    end
+  
+    # return the x coordinate of the image's origin
+    def x
+      @ciimage ? @ciimage.extent.origin.x : 0
+    end
+  
+    # return the y coordinate of the image's origin
+    def y
+      @ciimage ? @ciimage.extent.origin.y : 0
+    end
+  
+    # def translate(x,y)
+    #   matrix = CGAffineTransformMakeTranslation(x,y)
+    #   @ciimage = @ciimage.imageByApplyingTransform(matrix)
+    #   @ciimage.extent
+    #   self
+    # end
+  
+    # RESIZING/MOVING
+  
+    # scale the image by multiplying the width by a multiplier, optionally scaling height using aspect ratio
+    def scale(multiplier, aspect=1.0)    
+      puts "image.scale: #{multiplier},#{aspect}" if @verbose
+      filter 'CILanczosScaleTransform', :inputScale => multiplier.to_f, :inputAspectRatio => aspect.to_f
+      self
+    end
+  
+    # scale image to fit within a box of w,h using CIAffineTransform (sharper)
+    def fit2(w, h)
+      width_multiplier = w.to_f / width
+      height_multiplier = h.to_f / height
+      multiplier = width_multiplier < height_multiplier ? width_multiplier : height_multiplier
+      puts "image.fit2: #{multiplier}" if @verbose
+      transform = NSAffineTransform.transform
+      transform.scaleBy(multiplier)
+      filter 'CIAffineTransform', :inputTransform => transform
+      self
+    end
+  
+    # scale image to fit within a box of w,h using CILanczosScaleTransform
+    def fit(w, h)
+      # http://gigliwood.com/weblog/Cocoa/Core_Image,_part_2.html
+      old_w = self.width.to_f
+      old_h = self.height.to_f
+      old_x = self.x
+      old_y = self.y
+    
+      # choose a scaling factor
+      width_multiplier = w.to_f / old_w
+      height_multiplier = h.to_f / old_h
+      multiplier = width_multiplier < height_multiplier ? width_multiplier : height_multiplier
+
+      # crop result to integer pixel dimensions
+      new_width = (self.width * multiplier).truncate
+      new_height = (self.height * multiplier).truncate
+    
+      puts "image.fit: old size #{old_w}x#{old_h}, max target #{w}x#{h}, multiplier #{multiplier}, new size #{new_width}x#{new_height}" if @verbose
+      clamp
+      scale(multiplier)
+      crop(old_x, old_y, new_width, new_height)
+      #origin(:bottomleft)
+      self
+    end
+
+    # resize the image to have new dimensions w,h
+    def resize(w, h)
+      oldw = width
+      oldh = height
+      puts "image.resize #{oldw}x#{oldh} => #{w}x#{h}" if @verbose
+      width_ratio = w.to_f / oldw.to_f
+      height_ratio = h.to_f / oldh.to_f
+      aspect = width_ratio / height_ratio  # (works when stretching tall, gives aspect = 0.65)
+      scale(height_ratio,aspect)
+      origin(:bottomleft)
+      self
+    end
+  
+    # crop the image to a rectangle from x1,y2 with width x height
+    def crop(x=nil,y=nil,w=nil,h=nil)
+    
+      # crop to largest square if no parameters were given
+      unless x
+        if (self.width > self.height)
+          side = self.height
+          x = (self.width - side) / 2
+          y = 0
+        else
+          side = self.width
+          y = (self.height - side) / 2
+          x = 0
+        end
+        w = h = side
+      end
+    
+      puts "image.crop [#{x},#{y}] with #{w},#{h}" if @verbose
+      #vector = CIVector.vectorWithX_Y_Z_W(x.to_f,y.to_f,w.to_f,h.to_f)
+      vector = CIVector.vectorWithX x.to_f, Y:y.to_f, Z:w.to_f, W:h.to_f
+      filter('CICrop', :inputRectangle => vector)
+      origin(:bottomleft)
+      self
+    end
+  
+    # apply an affine transformation using matrix parameters a,b,c,d,tx,ty
+    def transform(a, b, c, d, tx, ty)
+      puts "image.transform #{a},#{b},#{c},#{d},#{tx},#{ty}" if @verbose
+      transform = CGAffineTransformMake(a, b, c, d, tx, ty) # FIXME: needs to be NSAffineTransform?
+      filter 'CIAffineTransform', :inputTransform => transform
+      self
+    end
+  
+    # translate image by tx,ty
+    def translate(tx, ty)
+      puts "image.translate #{tx},#{ty}" if @verbose
+      #transform = CGAffineTransformMakeTranslation(tx,ty);
+      transform = NSAffineTransform.transform
+      transform.translateXBy tx, yBy:ty
+      filter 'CIAffineTransform', :inputTransform => transform
+      self
+    end
+  
+    # rotate image by degrees
+    def rotate(deg)
+      puts "image.rotate #{deg}" if @verbose
+      #transform = CGAffineTransformMakeRotation(radians(deg));
+      transform = NSAffineTransform.transform
+      transform.rotateByDegrees(-deg)
+      filter 'CIAffineTransform', :inputTransform => transform
+      self
+    end
+    
+    # set the origin to the specified location (:center, :bottomleft, etc)
+    def origin(location=:bottomleft)
+      movex, movey = reorient(x, y, width, height, location)
+      translate(movex, movey)
+    end
+  
+  
+    # FILTERS
+  
+    # apply a crystallizing effect with pixel radius 1-100
+    def crystallize(radius=20.0)
+      filter 'CICrystallize', :inputRadius => radius
+      self
+    end
+  
+    # apply a gaussian blur with pixel radius 1-100
+    def blur(radius=10.0)
+      filter 'CIGaussianBlur', :inputRadius => inrange(radius, 1.0, 100.0)
+      self
+    end
+  
+    # sharpen the image given a radius (0-100) and intensity factor
+    def sharpen(radius=2.50, intensity=0.50)
+      filter 'CIUnsharpMask', :inputRadius => radius, :inputIntensity => intensity
+      self
+    end
+
+    # apply a gaussian blur with pixel radius 1-100
+    def motionblur(radius=10.0, angle=90.0)
+      oldx, oldy, oldw, oldh = [x, y, width, height]
+      clamp
+      filter 'CIMotionBlur', :inputRadius => radius, :inputAngle => radians(angle)
+      crop(oldx, oldy, oldw, oldh)
+      self
+    end
+
+    # rotate pixels around x,y with radius and angle
+    def twirl(x=0, y=0, radius=300, angle=90.0)
+      filter 'CITwirlDistortion', :inputCenter => CIVector.vectorWithX(x, Y:y), :inputRadius => radius, :inputAngle => radians(angle)
+      self
+    end
+
+    # apply a bloom effect
+    def bloom(radius=10, intensity=1.0)
+      filter 'CIBloom', :inputRadius => inrange(radius, 0, 100), :inputIntensity => inrange(intensity, 0.0, 1.0)
+      self
+    end
+
+    # adjust the hue of the image by rotating the color wheel from 0 to 360 degrees
+    def hue(angle=180)
+      filter 'CIHueAdjust', :inputAngle => radians(angle)
+      self
+    end
+
+    # remap colors so they fall within shades of a single color
+    def monochrome(color=Color.gray)
+      filter 'CIColorMonochrome', :inputColor => CIColor.colorWithRed(color.r, green:color.g, blue:color.b, alpha:color.a)
+      self
+    end
+  
+    # adjust the reference white point for an image and maps all colors in the source using the new reference
+    def whitepoint(color=Color.white.ish)
+      filter 'CIWhitePointAdjust', :inputColor => CIColor.colorWithRed(color.r, green:color.g, blue:color.b, alpha:color.a)
+      self
+    end
+  
+    # reduce colors with a banding effect
+    def posterize(levels=6.0)
+      filter 'CIColorPosterize', :inputLevels => inrange(levels, 1.0, 300.0)
+      self
+    end
+  
+    # detect edges
+    def edges(intensity=1.0)
+      filter 'CIEdges', :inputIntensity => inrange(intensity, 0.0,10.0)
+      self
+    end
+  
+    # apply woodblock-like effect
+    def edgework(radius=1.0)
+      filter 'CIEdgeWork', :inputRadius => inrange(radius, 0.0,20.0)
+      self
+    end
+  
+    # adjust exposure by f-stop
+    def exposure(ev=0.5)
+      filter 'CIExposureAdjust', :inputEV => inrange(ev, -10.0, 10.0)
+      self
+    end
+  
+    # adjust saturation
+    def saturation(value=1.5)
+      filter 'CIColorControls', :inputSaturation => value, :inputBrightness => 0.0, :inputContrast => 1.0
+      self
+    end
+  
+    # adjust brightness (-1 to 1)
+    def brightness(value=1.1)
+      filter 'CIColorControls', :inputSaturation => 1.0, :inputBrightness => inrange(value, -1.0, 1.0), :inputContrast => 1.0
+      self
+    end
+  
+    # adjust contrast (0 to 4)
+    def contrast(value=1.5)
+      #value = inrange(value,0.25,100.0)
+      filter 'CIColorControls', :inputSaturation => 1.0, :inputBrightness => 0.0, :inputContrast => value
+      self
+    end
+  
+    # fill with a gradient from color0 to color1 from [x0,y0] to [x1,y1]
+    def gradient(color0, color1, x0 = x / 2, y0 = y, x1 = x / 2, y1 = height)
+      filter 'CILinearGradient',
+        :inputColor0 => color0.rgb, 
+        :inputColor1 => color1.rgb, 
+        :inputPoint0 => CIVector.vectorWithX(x0, Y:y0), 
+        :inputPoint1 => CIVector.vectorWithX(x1, Y:y1)
+      self
+    end
+  
+    # use the gray values of the input image as a displacement map (doesn't work with PNG?)
+    def displacement(image, scale=50.0)
+      filter 'CIDisplacementDistortion', :inputDisplacementImage => image.ciimage, :inputScale => inrange(scale, 0.0, 200.0)
+      self
+    end
+  
+    # simulate a halftone screen given a center point, angle(0-360), width(1-50), and sharpness(0-1)
+    def dotscreen(dx=0, dy=0, angle=0, width=6, sharpness=0.7)
+      filter 'CIDotScreen',
+        :inputCenter => CIVector.vectorWithX(dx.to_f, Y:dy.to_f), 
+        :inputAngle => max(0, min(angle, 360)), 
+        :inputWidth => max(1, min(width, 50)), 
+        :inputSharpness => max(0, min(sharpness, 1))
+      self
+    end
+  
+    # extend pixels at edges to infinity for nicer sharpen/blur effects
+    def clamp
+      puts "image.clamp" if @verbose
+      filter 'CIAffineClamp', :inputTransform => NSAffineTransform.transform
+      self
+    end
+  
+    # blend with the given image using mode (:add, etc)
+    def blend(image, mode)
+      case image
+      when String
+        ciimage_background = CIImage.imageWithContentsOfURL(NSURL.fileURLWithPath(image))
+      when Image
+        ciimage_background = image.ciimage
+      else
+        raise "ERROR: Image: type not recognized"
+      end
+      filter BlendModes[mode], :inputBackgroundImage => ciimage_background
+      self
+    end
+  
+    # draw this image to the specified context
+    def draw(ctx,x=0,y=0,w=width,h=height)
+      ciimage
+      # imgx = x + self.x
+      # imyy = y + self.y
+      resize(w, h) if w != self.width || h != self.height
+    
+      # add the ciimage's own origin coordinates to the target point
+      x = x + self.x
+      y = y + self.y
+    
+      puts "image.draw #{x},#{y} #{w}x#{h}" if @verbose
+      cicontext = CIContext.contextWithCGContext ctx, options:nil
+      #cicontext.drawImage_atPoint_fromRect(ciimage, [x,y], CGRectMake(self.x,self.y,w,h))
+      cicontext.drawImage ciimage, atPoint:CGPointMake(x,y), fromRect:CGRectMake(self.x,self.y,w,h)
+    end
+  
+    # return the CIImage for this Image object
+    def ciimage
+      @ciimage ||= CIImage.imageWithCGImage(@cgimage)
+    end
+  
+    # return an array of n colors chosen randomly from the source image.
+    # if type = :grid, choose average colors from each square in a grid with n squares
+    def colors(n=32, type=:random)
+      ciimage
+      colors = []
+      if (type == :grid) then
+        filtername = 'CIAreaAverage'
+        f = CIFilter.filterWithName(filtername)
+        f.setDefaults
+        f.setValue_forKey(@ciimage, 'inputImage')
+    
+        extents = []
+
+        rows = Math::sqrt(n).truncate
+        cols = rows
+        w = self.width
+        h = self.height
+        block_width = w / cols
+        block_height = h / rows
+        rows.times do |row|
+          cols.times do |col|
+            x = col * block_width
+            y = row * block_height
+            extents.push([x, y, block_width, block_height])
+          end
+        end
+        extents.each do |extent|
+          x, y, w, h = extent
+          extent = CIVector.vectorWithX x.to_f, Y:y.to_f, Z:w.to_f, W:h.to_f
+          f.setValue_forKey(extent, 'inputExtent')
+          ciimage = f.valueForKey('outputImage') # CIImageRef
+          nsimg = NSBitmapImageRep.alloc.initWithCIImage(ciimage)
+          nscolor = nsimg.colorAtX 0, y:0 # NSColor
+          #r,g,b,a = nscolor.getRed_green_blue_alpha_()
+          r,b,b,a = [nscolor.redComponent,nscolor.greenComponent,nscolor.blueComponent,nscolor.alphaComponent]
+          colors.push(Color.new(r,g,b,1.0))
+        end
+      elsif (type == :random)
+        nsimg = NSBitmapImageRep.alloc.initWithCIImage(@ciimage)
+        n.times do |i|
+          x = rand(self.width)
+          y = rand(self.height)
+          nscolor = nsimg.colorAtX x, y:y # NSColor
+          #r,g,b,a = nscolor.getRed_green_blue_alpha_()
+          r,g,b,a = [nscolor.redComponent,nscolor.greenComponent,nscolor.blueComponent,nscolor.alphaComponent]
+          colors.push(Color.new(r,g,b,1.0))
+        end
+      end
+      colors
+    end
+  
+    private
+  
+      # apply the named CoreImage filter using a hash of parameters
+      def filter(filtername, parameters)
+        ciimage
+        f = CIFilter.filterWithName(filtername)
+        f.setDefaults
+        f.setValue @ciimage, forKey:'inputImage'
+        parameters.each do |key,value|
+          f.setValue value, forKey:key
+        end
+        puts "image.filter #{filtername}" if @verbose
+        @ciimage = f.valueForKey('outputImage') # CIImageRef
+        self
+      end
+  
+  end
+  
+end


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/image.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/path.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/path.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/path.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,326 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+  
+  # Make a reusable path. Draw it using canvas.draw(path)
+  class Path
+  
+    attr_accessor :path, :rand, :inc, :fill, :stroke, :scale, :strokewidth, :x, :y, :image
+  
+    # create a new path, starting at optional x,y
+    def initialize(x=0, y=0, &block)
+      @path = CGPathCreateMutable()
+      @transform = CGAffineTransformMakeTranslation(0,0)
+      moveto(x, y)
+    
+      # set randomized rendering parameters
+      @rand = {}
+      randomize(:x, 0.0)
+      randomize(:y, 0.0)
+      randomize(:scale, 1.0)
+      randomize(:scalex, 1.0)
+      randomize(:scaley, 1.0)
+      randomize(:rotation, 0.0)
+      randomize(:strokewidth, 1.0)
+    
+      # set incremental rendering parameters
+      @inc = {}
+      increment(:rotation, 0.0)
+      increment(:x, 0.0)
+      increment(:y, 0.0)
+      increment(:scale, 1.0)
+      increment(:scalex, 1.0)
+      increment(:scaley, 1.0)
+
+      @strokewidth = 1.0
+      @x = 0.0
+      @y = 0.0
+      
+      if block
+        case block.arity
+        when 0
+          self.send(:instance_eval, &block)
+        else
+          block.call(self)
+        end
+      end
+      self
+    end
+  
+    def fill(colors=nil)
+      if colors
+        rand[:fill] = colors
+      else
+        @fill
+      end
+    end
+  
+    # randomize the specified parameter within the specified range
+    def randomize(parameter, value)
+      rand[parameter] = value
+    end
+  
+    # increment the specified parameter within the specified range
+    def increment(parameter, value)
+      inc[parameter] = value
+    end
+  
+    # return a mutable clone of this path
+    def clone
+      newpath = self.dup
+      newpath.path = CGPathCreateMutableCopy(@path)
+      newpath
+    end
+
+  
+    # SET PARAMETERS
+  
+    # set registration mode to :center or :corner
+    def registration(mode=:center)
+      @registration = mode
+    end
+  
+    # print drawing operations if verbose is true
+    def verbose(tf=true)
+      @verbose = tf
+    end
+
+    # draw without stroke
+    def nostroke
+      @stroke = nil
+    end
+  
+    # GET PATH INFO
+  
+    # print origin and dimensions
+    def to_s
+      "path.to_s: bounding box at [#{originx},#{originy}] with #{width}x#{height}, current point [#{currentpoint[0]},#{currentpoint[1]}]"
+    end
+  
+    # return the x coordinate of the path's origin
+    def originx
+      CGPathGetBoundingBox(@path).origin.x
+    end
+  
+    # return the y coordinate of the path's origin
+    def originy
+      CGPathGetBoundingBox(@path).origin.y
+    end
+  
+    # return the width of the path's bounding box
+    def width
+      CGPathGetBoundingBox(@path).size.width
+    end
+  
+    # return the height of the path's bounding box
+    def height
+      CGPathGetBoundingBox(@path).size.height
+    end
+  
+    # return the current point
+    def currentpoint
+      point = CGPathGetCurrentPoint(@path)
+      [point.x, point.y]
+    end
+  
+    # true if the path contains the current point # doesn't work?
+    def contains(x,y)
+      eorule = true
+      CGPathContainsPoint(@path, @transform, CGPointMake(x, y), eorule)
+    end
+  
+  
+    # ADD SHAPES TO PATH
+  
+    # add another path to this path
+    def addpath(p)
+      CGPathAddPath(@path, @transform, p.path)
+    end
+
+    # add a rectangle starting at [x,y] with dimensions w x h
+    def rect(x=0, y=0, w=20, h=20, reg=@registration)
+      if reg == :center
+        x = x - w / 2
+        y = y - h / 2
+      end
+      puts "path.rect at [#{x},#{y}] with #{w}x#{h}" if @verbose
+      CGPathAddRect(@path, @transform, CGRectMake(x,y,w,h))
+      self
+    end
+
+    # draw a rounded rectangle using quadratic curved corners (FIXME)
+    def roundrect(x=0, y=0, width=20, height=20, roundness=0, reg=@registration)
+      if roundness == 0
+        p.rect(x, y, width, height, reg)
+      else
+        if reg == :center
+          x = x - self.width / 2
+          y = y - self.height / 2
+        end
+        curve = min(width * roundness, height * roundness)
+        p = Path.new
+        p.moveto(x, y+curve)
+        p.curveto(x, y, x, y, x+curve, y)
+        p.lineto(x+width-curve, y)
+        p.curveto(x+width, y, x+width, y, x+width, y+curve)
+        p.lineto(x+width, y+height-curve)
+        p.curveto(x+width, y+height, x+width, y+height, x+width-curve, y+height)
+        p.lineto(x+curve, y+height)
+        p.curveto(x, y+height, x, y+height, x, y+height-curve)
+        p.endpath
+      end
+      addpath(p)
+      self
+    end
+
+    # create an oval starting at x,y with dimensions w x h, optionally registered at :center
+    def oval(x=0, y=0, w=20, h=20, reg=@registration)
+      if (reg == :center)
+        x = x - w / 2
+        y = y - h / 2
+      end
+      puts "path.oval at [#{x},#{y}] with #{w}x#{h}" if @verbose
+      CGPathAddEllipseInRect(@path, @transform, CGRectMake(x, y, w, h))
+      self
+    end
+
+    # draw a circle with center at x,y with width and (optional) height
+    # def circle(x,y,w,h=w)
+    #   oval(x - w/2, y - h/2, w, h)
+    # end
+
+    # ADD LINES TO PATH
+  
+    # draw a line from x1,x2 to x2,y2
+    def line(x1, y1, x2, y2)
+      CGPathAddLines(@path, @transform, [[x1, y1], [x2, y2]])
+      self
+    end
+
+    # draw the arc of a circle with center point x,y, radius, start angle (0 deg = 12 o'clock) and end angle
+    def arc(x, y, radius, start_angle, end_angle)
+      start_angle = radians(90 - start_angle)
+      end_angle   = radians(90 - end_angle)
+      clockwise   = 1 # 1 = clockwise, 0 = counterclockwise
+      CGPathAddArc(@path, @transform, x, y, radius, start_angle, end_angle, clockwise)
+      self
+    end
+  
+    # draw lines connecting the array of points
+    def lines(points)
+      CGPathAddLines(@path, @transform, points)
+      self
+    end
+
+
+    # CONSTRUCT PATHS IN PATH OBJECT
+  
+    # move the "pen" to x,y
+    def moveto(x, y)
+      CGPathMoveToPoint(@path, @transform,x,y)
+      self
+    end
+  
+    # draw a line from the current point to x,y
+    def lineto(x,y)
+      CGPathAddLineToPoint(@path, @transform, x, y)
+      self
+    end
+
+    # draw a bezier curve from the current point, given the coordinates of two handle control points and an end point
+    def curveto(cp1x, cp1y, cp2x, cp2y, x,  y)
+      CGPathAddCurveToPoint(@path, @transform, cp1x, cp1y, cp2x, cp2y, x, y)
+      self
+    end
+  
+    # draw a quadratic curve given a single control point and an end point
+    def qcurveto(cpx, cpy, x, y)
+      CGPathAddQuadCurveToPoint(@path, @transform, cpx, cpy, x, y)
+      self
+    end
+  
+    # draw an arc given the endpoints of two tangent lines and a radius
+    def arcto(x1, y1, x2, y2, radius)
+      CGPathAddArcToPoint(@path, @transform, x1, y1, x2, y2, radius)
+      self
+    end
+  
+    # end the current path
+    def endpath
+      CGPathCloseSubpath(@path)
+    end
+    
+
+    # TRANSFORMATIONS
+  
+    # specify rotation for subsequent operations
+    def rotate(deg)
+      puts "path.rotate #{deg}" if @verbose
+      @transform = CGAffineTransformRotate(@transform, radians(deg))
+    end
+  
+    # scale by horizontal/vertical scaling factors sx,sy for subsequent drawing operations
+    def scale(sx=nil, sy=nil)
+      if sx == nil && sy == nil
+        @scale
+      else
+        sy = sx unless sy
+        puts "path.scale #{sx}x#{sy}" if @verbose
+        @transform = CGAffineTransformScale(@transform, sx, sy)
+      end
+    end
+  
+    # specify translation by x,y for subsequent drawing operations
+    def translate(x,y)
+      puts "path.translate #{x}x#{y}" if @verbose
+      @transform = CGAffineTransformTranslate(@transform, x, y)
+    end
+
+  
+    # BUILD PRIMITIVES
+  
+    # draw a petal starting at x,y with w x h and center bulge height using quadratic curves
+    def petal(x=0, y=0, w=10, h=50, bulge=h/2)
+      moveto(x,y)
+      qcurveto(x - w, y + bulge, x, y + h)
+      qcurveto(x + w, y + bulge, x, y)
+      endpath
+      self
+    end
+  
+    # duplicate and rotate the Path object the specified number of times
+    def kaleidoscope(path,qty)
+      deg = 360 / qty
+      qty.times do
+        addpath(path)
+        rotate(deg)
+      end
+    end
+  
+    # duplicate and rotate the Path object the specified number of times
+    #path, rotation, scale, translation, iterations
+    def spiral(path=nil, rotation=20, scalex=0.95, scaley=0.95, tx=10, ty=10, iterations=30)
+      iterations.times do
+        addpath(path)
+        rotate(rotation)
+        scale(scalex, scaley)
+        translate(tx, ty)
+      end
+    end
+  
+
+  end
+end
\ No newline at end of file


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/path.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics/pdf.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics/pdf.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics/pdf.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,71 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa::Graphics
+  
+  # parse a PDF file to determine pages, width, height
+  class Pdf
+  
+    attr_reader :pages, :pdf
+  
+    # create a new Pdf object given the original pathname, and password if needed
+    def initialize(original, password = nil)
+      # http://developer.apple.com/documentation/GraphicsImaging/Reference/CGPDFDocument/Reference/reference.html
+      # http://developer.apple.com/documentation/GraphicsImaging/Reference/CGPDFPage/Reference/reference.html
+      @pdf = CGPDFDocumentCreateWithURL(NSURL.fileURLWithPath(original)) # => CGPDFDocumentRef
+      result = CGPDFDocumentUnlockWithPassword(@pdf, password) if password # unlock if necessary
+      @pages = CGPDFDocumentGetNumberOfPages(@pdf) # => 4
+      puts "pdf.new #{original} (#{@pages} pages)" if @verbose
+      self
+    end
+  
+    # print drawing functions to console if verbose is true
+    def verbose(tf)
+      @verbose = tf
+    end
+  
+    # get the width of the specified pagenum
+    def width(pagenum=1)
+      cgpdfpage = page(pagenum)
+      mediabox = CGPDFPageGetBoxRect(cgpdfpage, KCGPDFMediaBox) # => CGRect
+      width = mediabox.size.width # CGRectGetWidth(mediabox)
+      width
+    end
+  
+    # get the height of the specified pagenum
+    def height(pagenum=1)
+      cgpdfpage = page(pagenum)
+      mediabox = CGPDFPageGetBoxRect(cgpdfpage, KCGPDFMediaBox) # => CGRect
+      height = mediabox.size.height # CGRectGetHeight(mediabox)
+      height
+    end
+  
+    # draw pagenum of the pdf document into a rectangle at x,y with dimensions w,h of drawing context ctx 
+    def draw(ctx, x=0, y=0, w=width(pagenum), h=height(pagenum), pagenum=1)
+      rect = CGRectMake(x,y,w,h)
+      puts "pdf.draw page #{pagenum} at [#{x},#{y}] with #{w}x#{h}" if @verbose
+      CGContextDrawPDFDocument(ctx, rect, @pdf, pagenum)
+      true
+    end
+  
+    private
+  
+    # return a CGPDFPageRef for this pagenum
+    def page(pagenum)
+      CGPDFDocumentGetPage(@pdf, pagenum) # => CGPDFPageRef
+    end
+  
+  end
+end
\ No newline at end of file


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics/pdf.rb
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/trunk/lib/hotcocoa/graphics.rb
===================================================================
--- MacRuby/trunk/lib/hotcocoa/graphics.rb	                        (rev 0)
+++ MacRuby/trunk/lib/hotcocoa/graphics.rb	2008-10-30 04:59:30 UTC (rev 686)
@@ -0,0 +1,156 @@
+# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented 
+# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.  
+# With a few lines of easy-to-read code, you can write scripts to draw simple or complex 
+# shapes, lines, and patterns, process and filter images, create abstract art or visualize 
+# scientific data, and much more.
+# 
+# Inspiration for this project was derived from Processing and NodeBox.  These excellent 
+# graphics programming environments are more full-featured than RCG, but they are implemented 
+# in Java and Python, respectively.  RCG was created to offer similar functionality using 
+# the Ruby programming language.
+#
+# Author::    James Reynolds  (mailto:drtoast at drtoast.com)
+# Copyright:: Copyright (c) 2008 James Reynolds
+# License::   Distributes under the same terms as Ruby
+
+module HotCocoa;end # needed in case this is required without hotcocoa
+
+framework 'Cocoa'
+
+module HotCocoa::Graphics
+
+  # UTILITY FUNCTIONS (math/geometry)
+  TEST = 'OK'
+
+  # convert degrees to radians
+  def radians(deg)
+    deg * (Math::PI / 180.0)
+  end
+
+  # convert radians to degrees
+  def degrees(rad)
+    rad * (180 / Math::PI)
+  end
+
+  # return the angle of the line joining the two points
+  def angle(x0, y0, x1, y1)
+    degrees(Math.atan2(y1-y0, x1-x0))
+  end
+
+  # return the distance between two points
+  def distance(x0, y0, x1, y1)
+    Math.sqrt((x1-x0)**2 + (y1-y0)**2)
+  end
+
+  # return the coordinates of a new point at the given distance and angle from a starting point
+  def coordinates(x0, y0, distance, angle)
+    x1 = x0 + Math.cos(radians(angle)) * distance
+    y1 = y0 + Math.sin(radians(angle)) * distance
+    [x1,y1]
+  end
+
+  # return the lesser of a,b
+  def min(a, b)
+    a < b ? a : b
+  end
+
+  # return the greater of a,b
+  def max(a, b)
+    a > b ? a : b
+  end
+
+  # restrict the value to stay within the range
+  def inrange(value, min, max)
+    if value < min
+      min
+    elsif value > max
+      max
+    else
+      value
+    end
+  end
+
+  # return a random number within the range, or a float from 0 to the number
+  def random(left=nil, right=nil)
+    if right
+      rand * (right - left) + left
+    elsif left
+      rand * left
+    else
+      rand
+    end
+  end
+
+  def reflect(x0, y0, x1, y1, d=1.0, a=180)
+    d *= distance(x0, y0, x1, y1)
+    a += angle(x0, y0, x1, y1)
+    x, y = coordinates(x0, y0, d, a)
+    [x,y]
+  end
+
+  def choose(object)
+    case object
+    when Range
+      case object.first
+      when Float
+        rand * (object.last - object.first) + object.first
+      when Integer
+        rand(object.last - object.first + 1) + object.first
+      end
+    when Array
+      object.choice
+    else
+      object
+    end
+  end
+
+  # given an object's x,y coordinates and dimensions, return the distance 
+  # needed to move in order to orient the object at the given location (:center, :bottomleft, etc)
+  def reorient(x, y, w, h, location)
+    case location
+    when :bottomleft
+      movex = -x
+      movey = -y
+    when :centerleft
+      movex = -x
+      movey = -y - h / 2
+    when :topleft
+      movex = -x
+      movey = -x - h
+    when :bottomright
+      movex = -x - w
+      movey = -y
+    when :centerright
+      movex = -x - w
+      movey = -y - h / 2
+    when :topright
+      movex = -x - w
+      movey = -y - h
+    when :bottomcenter
+      movex = -x - w / 2
+      movey = -y
+    when :center
+      movex = -x - w / 2
+      movey = -y - h / 2
+    when :topcenter
+      movex = -x - w / 2
+      movey = -y - h
+    else
+      raise "ERROR: image origin locator not recognized: #{location}"
+    end
+    #newx = oldx + movex
+    #newy = oldy + movey
+    [movex,movey]
+  end
+
+end
+
+require 'hotcocoa/graphics/canvas'
+require 'hotcocoa/graphics/color'
+require 'hotcocoa/graphics/gradient'
+require 'hotcocoa/graphics/image'
+require 'hotcocoa/graphics/path'
+require 'hotcocoa/graphics/pdf'
+require 'hotcocoa/graphics/elements/particle'
+require 'hotcocoa/graphics/elements/rope'
+require 'hotcocoa/graphics/elements/sandpainter'


Property changes on: MacRuby/trunk/lib/hotcocoa/graphics.rb
___________________________________________________________________
Added: svn:executable
   + *
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-changes/attachments/20081029/676133b7/attachment-0001.html>


More information about the macruby-changes mailing list