[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