Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0

Monday 26 April 2010

Using the Voronoi library in ruby processing

This something I knocked up after reading a posting on the processing discourse (exhibition) posted by "guyy". The code is a lot simpler in ruby, and I much prefer the ruby processing control panel to controlP5 gui. You can get the voronoi mesh library here. In my revised version as of 30 April 2010, you can load a different image to work with.


load_libraries 'mesh', 'control_panel'
import 'megamu.mesh.Voronoi'

attr_reader :voronoi, :img, :ready, :upper, :lower, :lowskip, :highskip, :show, :points, :load_file

def setup()
 size(600, 600)
 frame_rate(10)
 #no_cursor()
 @ready = false
 @show = false
 @load_file = true
 setup_control
 @points = Array.new
 puts("done setup")
end

def setup_control
  control_panel do |c|
    c.title = "Control Panel"
    c.slider :upper, 50..255, 200
    c.slider :lower, 0..205, 150
    c.slider :lowskip, 1..20, 1
    c.slider :highskip, 1..20, 7
    c.button :change_image
    c.button :Go
    c.button :view_image
    c.button :save_voronoi
  end
end

def draw()
  background(0)
  stroke(128)
  if (load_file)
    no_loop
    file = select_input("Choose Image File")
    @img = load_image(file)
    @load_file = false
    loop
  end
  if (ready)
    display_voronoi
  end
  if(show && img)
    tint(255,100)
    image(img,0,0)
  end
end

def get_all_points()
  x_array = Array.new  # random array of x points
  y_array = Array.new  # random array of y points
  x = 0
  y = 0

  while(x < (width - highskip))
    x_array.push(x)
    x += rand(highskip) + lowskip
  end

  while(y < (height - highskip))
    y_array.push(y)
    y += rand(highskip) + lowskip
  end

  x_array.each do |pos_x|
    y_array.each do |pos_y|
      b = brightness(img.get(pos_x, pos_y))
      if (b <= upper && b >= lower)
        @points.push([pos_x, pos_y])
      end
    end
  end
  puts("total #{points.size()}")
end

def display_voronoi
  regions = voronoi.get_regions
  stroke(128)
  fill(0)  
  regions.each do |region|
    region_coordinates = region.get_coords
    region.draw(self)
  end

  edges = voronoi.get_edges
  
  edges.each do |edge|
    x0 = edge[0]
    y0 = edge[1]
    x1 = edge[2]
    y1 = edge[3]
    line(x0, y0, x1, y1)
  end

  fill(128)
  no_stroke
end

def mouse_pressed()
  no_loop
  b = brightness(img.get(mouse_x, mouse_y)) if img
  puts "Brightness = #{b}"
  loop
end

def Go()
  no_loop
  @ready = false
  @points.clear
  get_all_points if img
  @voronoi = Voronoi.new(@points.to_java(Java::float[]))
  @ready = true
  loop
end

def save_voronoi
  no_loop
  save_frame
  puts "finished!"
  loop
end

def change_image
  @load_file = !load_file      
end
def view_image
  @show = !show
end


Use this tool to create "voronoi portraits" from greyscale images. Here is the application in action with one of my context free "tree" images. In the screenshot below the view_image button has been selected, after the the voronoi was displayed.



If you move the mouse over regions in the original image, and press it the brightness is printed to the console (helps setting thresholds).

Here is what a saved voronoi image looks like (images are saved as a tif by default).


Here is an image you might recognize (Inverted using the Gimp)








Saturday 24 April 2010

Moire patterns from custom bar shape Context free DSL


#########################
# donut.rb  inspired by Lekkere donut by marksmit
# uses a custom vbar terminal
#########################
load_library 'context_free'

def setup_the_moire
  @moire = ContextFree.define do
    ############ Begin defining custom terminal, a proportional vertical bar
    class << self  
      define_method(:vbar) do |some_options|
        size, options = *self.get_shape_values(some_options)
        w = some_options[:w]|| 0.1    # default vbar width is 0.1
        ratio = w * size
        rot = options[:rotation]
        rect_mode(CENTER)
        rotate(rot) if rot
        rect(-0.5 * ratio, -0.5 * size, 0.5 * ratio, 0.5 * size)
        rotate(-rot) if rot
      end
    end
    ########### End definition of custom terminal 'vbar'

    rule :donut do
      split do
      360.times do
        ring :brightness => 1, :rotation => 1
      end
      end
    end  

    rule :ring do
      rot = 2
      split do
      30.times do |i|
        element :brightness => 1, :rotation => rot * i
      rewind
      end
      end
    end
  
    rule :element do
      vbar :size => 10, :w => 0.005, :brightness => 1
    end
  end
end

def setup
  size 400, 400
  background 255, 255, 0
  smooth
  no_stroke
  setup_the_moire
  draw_it
end

def draw_it
  @moire.render :donut, :start_x => width/2, :start_y => height/2,
               :size => height/15, :stop_size => 1, :color => [0, 1, 1]
end


Faux barcode using ruby-processing Context Free DSL

Here I create a faux bar code (approximates to a 2 from 5 version), the output is random, apart from the start and end bars and the number text. Groups of 5 bars are created, which will on average will contain two wide and 3 narrow bars. The text is drawn using native ruby-processing, the bars are produced using the context free DSL.


#########################
# bar_code.rb (2 of 5)
# demonstrates the vbar
# custom terminal
#########################
load_library 'context_free'

def setup_the_barcode
  @barcode = ContextFree.define do
    ############ Begin defining custom terminal, a proportional vertical bar
    class << self    
      define_method(:vbar) do |some_options|
        size, options = *self.get_shape_values(some_options)
        w = some_options[:w]|| 0.1    # default vbar width is 0.1
        ratio = w * size
        rot = options[:rotation]
        rect_mode(CENTER)
        rotate(rot) if rot
        rect(-0.5 * ratio, -0.4 * size, 0.5 * ratio, 0.6 * size)
        rotate(-rot) if rot
      end
    end
    ########### End definition of custom terminal 'vbar'
    rule :strip do
      2.times do
        end_bar :x => 0.09
      end
      4.times do
        five :x => 0.04
      end
      2.times do
        end_bar :x => 0.09
      end
    end
    
    rule :bar, 1 do             # wide
      vbar :size => 0.8, :w => 0.08, :brightness => -1
    end
  
    rule :bar, 1 do             # wide
      vbar :size => 0.8, :w => 0.06, :brightness => -1
    end
  
    rule :bar, 1.6 do             # narrow
      vbar :size => 0.8, :w => 0.02, :brightness => -1
    end
  
    rule :bar, 1.6 do             # narrow
      vbar :size => 0.8, :w => 0.03, :brightness => -1
    end
  
    rule :end_bar, 1.6 do         # narrow extra long
      vbar :size => 1, :w => 0.03, :brightness => -1
    end

    rule :five do
      5.times do
      bar :x => 0.06
    end
    end    
    end
  end

def setup
  size 350, 200
  text_font(create_font("Dialog.plain", 24), 24)
  background 255, 255, 0
  draw_text
  smooth
  setup_the_barcode
  draw_it
end

def draw_it
  @barcode.render :strip, :start_x => 0, :start_y => height,
               :size => height
end

def draw_text
  code = "23467"
  fill 0
  text code, 40, 80
end




Tuesday 20 April 2010

Adding a custom terminal in ruby-processing context-free DSL

Here I describe how you can extend the ruby processing context free DSL. Previously I overrode the triangle method, but as Jeremy (Ashkenas) pointed out to me in a personal communication I could just as easily define a new terminal (this may lead to further experimentation).

load_library 'context_free'

def setup_the_spiral
  @spiral= ContextFree.define do
    ############ Begin defining custom terminal, an isoceles triangle    
    class << self    
      define_method(:isoceles) do |some_options# isoceles triangle
        size, options = *self.get_shape_values(some_options)
        rot = options[:rotation]
        rotate(rot) if rot
        super.triangle(-0.5 * size, -0.5 * size, -0.5 * size, 0.5 * size, 0.5 * size, 0.5 * size)
        rotate(-rot) if rot
      end
    end
    ########### End definition of custom terminal 'isoceles'
    rule :spiral do
      isoceles :brightness => -1, :rotation => 90
      spiral :rotation => 135, :size => 1/sqrt(2), :x => 1/sqrt(2)
    end
  end
end




I've added a white border using Gimp otherwise the image gets lost in my background.

Saturday 17 April 2010

How to Draw a Tree in Processing

Another query on the discourse was how can I draw a line of fixed length, but with a random angle, someone else came up with standard cos(theta)*x, sin(theta)*y solution (that I usually favour), so I gave this as alternative, or at least a pointer to it. I also pointed out that truly random a angle would be a bad idea, but I neglected to point out the upside-down coordinate system in processing.

def setup
 size(300, 300)
 length = 70
 stroke_weight 3
 translate(width/2, height - 20)
 rotate(-PI/2)
 line(0, 0, length, 0)
 translate(length, 0)
 push_matrix
 rotate(PI/4)
 line(0, 0, length, 0)
 pop_matrix
 push_matrix
 rotate(-PI/4)
 line(0, 0, length, 0)
 pop_matrix
 line(0, 0, length, 0)
 translate(length, 0)
 push_matrix
 rotate(PI/4)
 line(0, 0, length, 0)
 pop_matrix
 push_matrix
 rotate(-PI/4)
 line(0, 0, length, 0)
 pop_matrix
 line(0, 0, length, 0)
end



Then I thought it would be a bit of fun to develop a kind of tree tutorial, so moving from this very procedural approach I thought the least I could do would be to extract a couple of functions. One to produce a trunk element and another to produce a branch element:-

def setup
 size(300, 300)
 length = 70
 stroke_weight 3
 translate(width/2, height - 20)
 rotate(-PI/2)
 trunk length
 branch PI/4, length
 branch -PI/4, length
 trunk length
 length *= 0.8 # reduce length
 branch PI/4, length
 branch -PI/4, length
 trunk length
end

def branch angle, len
  push_matrix
  rotate angle
  line 0, 0, len, 0
  pop_matrix
end

def trunk len
  line 0, 0, len, 0
  translate len, 0
end 





















Making the tree a bit more complicated, creating one new function gave the following:-


def setup
  size(300, 300)
  length = 70
  stroke_weight 3
  translate(width/2, height - 20)
  rotate(-PI/2)
  trunk length
  branch PI/4, length
  branch -PI/4, length
  length *= 0.9 # reduce overall length
  branch 0, length
  trunk length
  branch 0, length
  save_frame "tree3.png"
end

def branch angle, len
  push_matrix
  rotate angle
  trunk len
  angle = PI/4 if angle == 0 # then vertical branch element
  terminal_branch angle, len
  terminal_branch -angle, len
  trunk len
  pop_matrix
end

def terminal_branch angle, len
  push_matrix
  rotate angle
  line 0, 0, len*0.8, 0 # reduce length of terminal branch
  pop_matrix
end

def trunk len
  line 0, 0, len, 0
  translate len, 0
end




Now there has got to be easier way to do this and there are several. One way is to use LSystems to describe the tree, and another is to use recursion like in context free art.












Here are some context free rules (by curran) run with the c++ program cfdg with variation set to JIQ

rule tree 20 {
    CIRCLE{ size 0.25 }
    scraggle { y 0.1 }
}

rule scraggle {
    tree { rotate 5 }
}

rule scraggle {
    tree { rotate -5 }
}

rule tree 1 {
    branch { size 0.7}
}

rule branch {
    tree { rotate -10 }
    tree { rotate 10 }
}



















Here are the cfdg rules above translated to run as ruby-processing context-free DSL script:-

 

#################################################################
# A non deterministic sketch run it until you get a result you like
# uncomment "srand 5" to get a more deterministic result. It looked
# pretty good on my linux box (however I'm not sure how universal the
# random seeding is in jruby)
#################################################################

load_library 'context_free'

def setup_the_tree
  @tree = ContextFree.define do
    rule :trunk, 20 do                        # rule has a probability weighting of 20
      circle :size => 0.25, :brightness => 0  # giving an actual probability = 0.952381
      scraggle :y => -0.1                     # the minus is require by the upside down coordinate system            
    end
 
    rule :trunk, 1 do                         # rule has a probability weighting of 1
      branch :size => 0.7                     # giving an actual probability = 0.047619  
    end
 
    rule :branch do
      split do                               # split is like a branch, rewind returns original context
        trunk :rotation => 10
      rewind
        trunk :rotation => -10
      end
    end

    rule :scraggle do                          # without an explicit weighting    
      trunk :rotation => 5                      # probability of each scraggle rule
    end                                        # is 0.5

    rule :scraggle do
      trunk :rotation => -5
    end
  end
end

def setup
  size 600, 600
  # srand 5
  smooth
  setup_the_tree
  background 255         # NB color mode here is "RGB 255", within context free definition
  draw_it                # the color mode is "HSB 1.0", supports :hue, :saturation, :brightness
end

def draw_it
  @tree.render :trunk,
               :start_x => width/2, :start_y => height * 0.9,
               :size => height/18
end





Follow this link to see an LSystem tree implementation



Context free programs run with multiple definitions of a rule are non-deterministic, so it is quite usual to get different results each time the program is run.

Friday 16 April 2010

Terse Code to use file chooser in Ruby Processing

Over on the processing discourse PhiLho the guru posted a short piece of code to get the app user to enter file path data into a sketch in vanilla processing. It can be incredibly terse in ruby:-


load_path = selectInput()# Opens file chooser
# If a file was selected, print path to file else print erro message
puts (load_path)? load_path : "No File Selected"
Update May 2014 this does not work with recent versions of processing (that rely on refelection crap).

Thursday 15 April 2010

Another funky random sketch with perlin noise

Heres a processing sketch I found over at biothing.org that I've given the ruby treatment.



##################################################
# amp_zoom_in.rb
# adapted from a sketch at http://www.biothing.org
##################################################
attr_reader :col, :a, :b, :c, :k, :amp, :time

def setup()
  size(320, 240, P3D)
  color_mode(RGB, 1.0)
  frame_rate(4)
  load_pixels()
  @time = 0
  @k = 0
end

def draw()
  @time += 0.1   #step ahead in time with each frame
  #now go through each pixel in the window:

  (0...width).each do |x|
    (0...height).each do |y|
      #create values with trig functions, noise, etc acting on x,y and time.  
      #experiment with how these values are modulating over time.

      @a = dist(x,y,width/2,height/2)
      @b=(cos(x/360.0 + time)).abs          # frequency larger # means less
      @c = noise(y/180.0*cos(time/20.0))    # inteference larger # means less

      @amp = k                              # amplitude

      @col = color((cos(a/120.0 + b + c * 10.0).abs)) * amp  
      #combine values into a color somehow.  again the possibilities are limitless
      set(x, y, col)
    end
  end
  update_pixels()
  @k += 1
end





Wednesday 14 April 2010

An Translation Exercise (Very Clever Pseudo 3D Animation)

I've created a little ruby script for myself that translates (somewhat imperfectly) vanilla processing (ie. *.pde files) to ruby processing. Here is the tidied up version of a cool sketch I found on open-processing.

# Made by Jared "BlueThen" C.
# November 5, 2009.
# www.bluethen.com
# translated to ruby processing by monkstone April 14 2010
# variable a is used for determining the shape's y position, coupled with the distance they are from the center.
# setup(), the first function called when the applet is started

attr_reader :a, :y

def setup()
  @a = 0
  @y = 0
  # the applet is set to 500 pixels by 500 pixels
  size(500, 500)
  # RGB mode set to maximum of 6, since we'll be using 6 colors. 0 for black, 6 for white, and everything in between.
  color_mode(RGB, 6)
  # The stroke color is used to determine the border color of each quadrilateral.
  stroke(0)
  # frame rate is set to 30.
  frame_rate(30)
end

def draw()

  # a is decreased by 0.08. it represents the amount of radians the height of our boxes changes, and their speed.
  # if we did nothing to a, then none of our shapes will move, so a is a key component in our formulas.

  @a -= 0.08
  # screen is cleared and background is set to 6 (white).
  background(6)  

  # (x, z) is the ground, while y is vertical)  
  (-7..7).each do |x# loop over range for the x axis
    (-7..7).each do |z# loop over range for the z axis
    
      # The y variable is set to determine the height of the box.
      # We use formula radius * cos(angle) to determine this.
      # Since cosine, when graphed, creates a wave, we can use this to have the boxes transition from small to big smoothly.
      # The radius pretty much stands for our range. cosine alone will return values between -1 and 1, so we multiply this
      # by 24 to increase this value. the formula will return something in between -24 and 24.
      # The angle is in radians. an entire loop (circle) is 2PI radians, or roughly 6.283185.
      # distance is used to create the circular effect. it makes the boxes of the same radius around the center similar.
      # The distance ranges from 0 to 7, so 0.55 * distance will be between 0 and 3.85. this will make the highest and lowest
      # box a little more than half a loop's difference. a is added on, (subtracted if you want to be technical, since a is
      # negative), to provide some sort of change for each frame. if we don't include '+ a' in the algorithm, the boxes would
      # be still.
    
      @y = (24 * cos(0.55 * distance(x,z,0,0) + a)).round()
    
    
      # These are 2 coordinate variations for each quadrilateral.
      # since they can be found in 4 different quadrants (+ and - for x, and + and - for z),
      # we'll only need 2 coordinates for each quadrilateral (but we'll need to pair them up differently
      # for this to work fully).
      #
      # Multiplying the x and z variables by 17 will space them 17 pixels apart.
      # The 8.5 will determine half the width of the box ()
      # 8.5 is used because it is half of 17. since 8.5 is added one way, and 8.5 is subtracted the other way, the total
      # width of each box is 17. this will eliminate any sort of spacing in between each box.
      #
      # If you enable no_stroke(), then the whole thing will appear as one 3d shape. try it.
    
      xm = x * 17 - 8.5
      xt = x * 17 + 8.5
      zm = z * 17 - 8.5
      zt = z * 17 + 8.5
    
      # We use an integer to define the width and height of the window. this is used to save resources on further calculating
      halfw = width/2
      halfh = height/2
    
    
      # Here is where all the isometric calculating is done.
      # We take our 4 coordinates for each quadrilateral, and find their (x,y) coordinates using an isometric formula.
      # You'll probably find a similar formula used in some of my other isometric animations. however, I normally use
      # these in a function. to avoid using repetitive calculation (for each coordinate of each quadrilateral, which
      # would be 3 quads * 4 coords * 3 dimensions = 36 calculations).
      #
      # Formerly, the isometric formula was ((x - z) * cos(radians(30)) + width/2, (x + z) * sin(radians(30)) - y + height/2).
      # However, the cosine and sine are constant, so they could be precalculated. cosine of 30 degrees returns roughly 0.866,
      # which can round to 1, leaving it out would have little artifacts (unless placed side_by_side to accurate versions, where
      # everything would appear wider in this version) sine of 30 returns 0.5.
      #
      # We left out subtracting the y value, since this changes for each quadrilateral coordinate. (-40 for the base, and our y
      # variable) these are later subtracted in the actual quad().
    
      isox1 = (xm - zm + halfw).round
      isoy1 = ((xm + zm) * 0.5 + halfh).round
      isox2 = (xm - zt + halfw).round
      isoy2 = ((xm + zt) * 0.5 + halfh).round
      isox3 = (xt - zt + halfw).round
      isoy3 = ((xt + zt) * 0.5 + halfh).round
      isox4 = (xt - zm + halfw).round
      isoy4 = ((xt + zm) * 0.5 + halfh).round
    
      #the side quads. 2 and 4 is used for the coloring of each of these quads
      fill(2)
      quad(isox2, isoy2 - y, isox3, isoy3 - y, isox3, isoy3 + 40, isox2, isoy2 + 40)
      fill(4)
      quad(isox3, isoy3 - y, isox4, isoy4 - y, isox4, isoy4 + 40, isox3, isoy3 + 40)
    
    
      # the top quadrilateral.
      # y, which ranges between -24 and 24, multiplied by 0.05 ranges between -1.2 and 1.2
      # we add 4 to get the values up to between 2.8 and 5.2.
      # this is a very fair shade of grays, since it doesn't become one extreme or the other.
    
      fill(4 + y * 0.05)
      quad(isox1, isoy1 - y, isox2, isoy2 - y, isox3, isoy3 - y, isox4, isoy4 - y)
    end
  end
end

# the distance formula
def distance(x, y, cx, cy)
  sqrt(sq(cx - x) + sq(cy - y))
end







One of the problems with my script is that it turns all '-' to '_' also it can't cope with C++ style multi-line comments they should probably be banned anyway!!!

Sunday 11 April 2010

Live Editing Ruby-Processing from jEdit

I have just updated my jEdit commando file for ruby processing, to include watch, create, export app and export applet options. I've yet to figure a way for the buffer window to re-open with a newly created ruby processing applet script. However it is no big deal to open the file using the jEdit file chooser. Here are some pics showing an example of a newly created hello_world.rb being edited live using watch:-











The newly created hello_world.rb file open for editing in jEdit







Running the rp5 commando file, setting the rp5 run option to watch.


















The running applet (watch mode)



















Live editing the running applet, note that you can set/reset the applet size, and the the applet will adjust on save.














The running applet (after "saving") still in the original watch mode/thread.















To make things as easy as falling off a bike you could save the following rp5.bsh script in the .jedit/macros folder


// rp5.bsh

new console.commando.CommandoDialog(view,"commando.rp5");



Then you can just click on the macro/rp5 menu, remember to check the commando settings as these are saved during the current jEdit session.

Sunday 4 April 2010

Plasma fractal (and carpet)

This is a variant of processing sketch by Giles Whitaker, that he in turn had adapted from "Plasma Fractal" - written January, 2002 by Justin Seyster. I have somewhat revised the code to make it a bit more efficient, the color is set using bit shifting. I have also introduced a (Sierpinksi) carpet variant which can be selected from the menu. Further I have included a save_it button which only be useful when run as application/script.... Graininess is controlled by a slider, a feature I think Giles introduced (although here we use the ruby-processing control panel).

#######################################
# plasma.rb
# A sketch that demonstrates the use of
# bit shifting to create color values
######################################
load_library 'control_panel'

ALPHA = 255
PHI = (1 +Math.sqrt(5)) / 2
attr_reader :control, :grain, :variant, :redraw


def setup
  size(500, 500)
  @variant = 'plasma'
  @grain = 3
  draw_plasma width, height  
  setup_control
end

def setup_control
  control_panel do |c|
    c.title = "Control Panel"
    c.slider :grain, 3...40, 3
    c.menu :variant, ['plasma', 'carpet']
    c.button :draw_it
    c.button :save_it
  end
end

def draw
end

def draw_it
  draw_plasma width, height
end

def save_it
  save_frame("plasma#{frame_count}.png")
end

def calc_color c
  # alpha value & int adjustment
  r = (c < 0.5)? c : 1.0 - c
  gr = (c >= 0.8)? 1.3 - c : (c < 0.3)? 0.3 - c : c - 0.3
  b = (c < 0.5)? 0.5 - c : c - 0.5
  # r, gr and b are bit shifted by an extra 1 to avoid doubling above values
  col = (ALPHA << 24)|((r * ALPHA).round << 17)|((gr * ALPHA).round << 9)| (b * ALPHA).round << 1
end

def displace num
  max = num / (width + height) * grain
  (rand - 0.5) * max
end

def draw_plasma w,  h
   #Assign the four corners of the intial grid random color values
   #These will end up being the colors of the four corners of the applet.  
   divide_grid(0.0, 0.0, w, h, rand, rand, rand, rand)
end


def divide_grid(x1, y1, w, h, c1, c2, c3, c4)
  new_width = w / 2.0    # NB dividing by 2.0 to ensure float
  new_height = h / 2.0
  c = (c1 + c2 + c3 + c4) / 4.0
  if (w <= 1.0 || h <= 1.0)
    #The four corners of the grid piece will be averaged and drawn as a single pixel    
    set(x1.round, y1.round, calc_color(c))
  else
  middle = c + displace(new_width + new_height)      #Randomly displace the midpoint!
  divisor = (variant == 'plasma')? 2.0 : PHI
  edge1 = (c1 + c2) / 2.0 #Calculate the edges by averaging the two corners of each edge.
  edge2 = (c2 + c3) / divisor
  edge3 = (c3 + c4) / divisor
  edge4 = (c4 + c1) / 2.0

  #Make sure that the midpoint doesn't accidentally "randomly displaced" past the boundaries!      
  middle = (middle <= 0) ? 0 : (middle < 1.0) ? middle : 1.0  

  #Do the operation over again for each of the four new grids.                  
  divide_grid(x1, y1, new_width, new_height, c1, edge1, middle, edge4)
  divide_grid(x1 + new_width, y1, new_width, new_height, edge1, c2, edge2, middle)
  divide_grid(x1 + new_width, y1 + new_height, new_width, new_height, middle, edge2, c3, edge3)
  divide_grid(x1, y1 + new_height, new_width, new_height, edge4, middle, edge3, c4)
  end
end









Followers

Blog Archive

About Me

My photo
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2