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

Thursday 16 June 2011

Faux Fish Eye Lens in ruby-processing, features control panel

Sketches below show an unadulterated grid, a partial grid with fish eye 'filter' and some text with fish eye 'filter'. Unfortunately the mouse was doing two things (adjusting view and selecting image for 'the gimp') so actual captured image was a bit hit and miss. View the vanilla processing version at openprocessing.


############################
# fish_eye.rb 
# [Pseudo fisheye simulation]
# by Jonsku, September 2010
# From the article by Paul Bourke : http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/domefisheye/fisheye/
#
# translated to ruby-processing by Martin Prout (June 2011) 
# Toggle fisheye, select view, adjust aperture using 'Control Panel'
# Move mouse to change camera position.
############################

require 'beauty'

class Fish_Eye < Processing::App
  load_library :control_panel
  attr_reader :b_pass, :fish_eye_filter, :aperture, :options, :some_text, :img

  def setup()
    size(300, 300)
    setup_control
    text_font(load_font("Ubuntu-Regular-48.vlw"))
    @some_text = "Whatever it is,\n it was inside the Sphere.\n Now it's out, free to act. "
    @img = load_image("pattern.png")
    @fish_eye_filter = false
    @b_pass = BeautyPass.new
    smooth
  end

  def draw()
    background(0)
    case options
    when 'grid'
      draw_grid(20)
    when 'text'
      draw_text
    when 'image'
      display_image
    when 'noise'
      draw_noise
    else
      draw_grid(20)
    end
    if fish_eye_filter
      fisheye
    end
  end

  def setup_control
    control_panel do |c|
      c.title = "Control Panel"
      c.button :t_fisheye
      c.menu(:options, ['grid', 'text', 'image', 'noise' ], 'grid')
      c.slider :aperture, 0.5..Math::PI, 3.142
    end
  end

  def t_fisheye
    @fish_eye_filter = !fish_eye_filter
  end

  def draw_grid(row)
    stroke(255)
    stroke_weight(2)
    for i in 0...row
      line(0, i * height / row, width, i * height / row)
      line(i * height / row, 0, i * height / row, height)
    end
  end

  def draw_text
    fill(0, 255, 0)
    text(some_text,sin(frame_count * 0.1)*(width - text_width(some_text)), 180+Math.sin(Math::PI/2 + frame_count*0.01)*(height-170))
  end

  def display_image
    image(img, 0, 0, width, height)
  end

  def draw_noise
    push_style
    color_mode(RGB,1.0)
    load_pixels
    (0...width).each do |i|
      (0...height).each do |j|
        pixels[i+j*width] = color(noise((frame_count+i)*0.1,j*0.1),noise((frame_count+i)*0.1,j*0.1,0.3),noise((frame_count+i)*0.05,j*0.05,0.6))
      end
    end
    update_pixels
    pop_style
  end

  def fisheye
    half_aperture = aperture / 2
    # define camera positioning normalized coordinates
    cam_x = (2.0 * mouse_x) / (width - 1)
    cam_y = (2.0 * mouse_y) / (width - 1)

    load_pixels
    (0...width).each do |i|
      #transform pixel coordinates to normalised coordinates, range -1 to 1
      x = (2.0 * i) / (width - 1)
      (0...height).each do |j|
        y = (2.0 * j) / (height - 1)
        #radius r to camera
        r = dist(cam_x, cam_y, x, y)
        #at this stage any pixels where r > 1 are ignored
        if (r.abs > 1)
          next
        end
        #angle phi to x axis
        phi = Math.atan2(y - cam_y, x - cam_x)
        #r is mapped onto theta and phi is used directly as the polar coordinates of the direction vector 
        # from the camera into the scene
        theta = half_aperture * r
        #transform normalised coordinates to pixel coordinates
        dx = map(Math.sin(theta) * Math.cos(phi), -1.0, 1, 0, width)
        dy = map(Math.sin(theta) * Math.sin(phi), -1.0, 1, 0, height)

        ##############
        # Because the pixel array uses integer coordinates, black empty pixels will appear in the resulting image.
        # To attempt to fix this, the destination pixel is calculated with 3x3 combinations of roundings (round-round, 
        # the final value will be the average colors. See BeautyPass, it will become clearer.
        # round.floor, round.ceil, floor.floor, floor-round, etc.). Thus one pixel might have several colors but that 
        # is taken into account and NOTE: This works ok for aperture up to 180 but above that it is not enough.
        ###############
        fx = dx.round
        fy = dy.round
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.round
        fy = dy.floor
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.round
        fy = dy.ceil
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.floor
        fy = dy.floor
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.floor
        fy = dy.round
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.floor
        fy = dy.ceil
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.ceil
        fy = dy.ceil
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.ceil
        fy = dy.round
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
        fx = dx.ceil
        fy = dy.floor
        @b_pass.add_color(fx + fy * width, pixels[(i + j * width)])
      end
    end
    @b_pass.render
    @b_pass.reset
  end

end


############################
# beauty.rb (helper class), part of
# --=[Angular fisheye simulation]=--
# by Jonsku, September 2010
# From the article by Paul Bourke : http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/domefisheye/fisheye/
#
# translated to ruby-processing by Martin Prout
#
# Move mouse to change camera position.
############################
class BeautyPass
  include Processing::Proxy

  attr_reader :counts, :rSums, :gSums, :bSums

  def initialize()
    reset()
  end

  def reset()
    @counts = Array.new(width * height, 0)
    @rSums = Array.new(width * height, 0)
    @gSums = Array.new(width * height, 0)
    @bSums = Array.new(width * height, 0)
  end

  def add_color(i, c)
    if (i>0 && i< width * height)
      @rSums[i] += c >> 16 & 0xFF
      @gSums[i] += c >> 8 & 0xFF
      @bSums[i] += c & 0xFF
      @counts[i] +=  1
    end
  end

  def render()
    load_pixels()
    (0...width * height).each do |i|
      if (counts[i]>0)
        pixels[i] = color(rSums[i] / counts[i], gSums[i] / counts[i], bSums[i] / counts[i])
      else
        pixels[i] = color(0) #background color
      end
    end
    update_pixels()
  end
end





Followers

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