Ruby Processing

Here is my blog in which I will describe my experiments with ruby-processing, find out more about ruby-processing at:- https://github.com/jashkenas/ruby-processing compatible with processing-2.2.1 and https://github.com/monkstone/cf3ruby for my version of the cfdg DSL (context-free-art)

Wednesday, 25 March 2015

Implicit isosurface sketch re-worked for JRubyArt

Since the release of the toxiclibs gem (example below use gem version 0.3.0.pre), it is now quite easy to work with the toxiclibs libraries in JRubyArt see sketch below. This being a bare sketch should be run with 'k9 run implicit.rb'

#
# This example implements a custom VolumetricSpace uMath.sing an implicit function
# to calculate each voxel. This is slower than the default array or HashMap
# based implementations, but also has much less memory requirements and so might
# be an interesting and more viable approach for very highres voxel spaces
# (e.g. >32 million voxels). This implementation here also demonstrates how to
# achieve an upper boundary on the iso value (in addition to the one given and
# acting as lower threshold when computing the iso surface)
#
# Usage:
# drag mouse to rotate camera
# mouse wheel zoom in/out
# l: apply laplacian mesh smooth
# 
#

# 
# Copyright (c) 2010 Karsten Schmidt & ruby-procesMath.sing version Martin Prout 2013
# This sketch relies on a custom ruby-procesMath.sing mesh_to_vbo library
# 
# This library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
# 
# http://creativecommons.org/licenses/LGPL/2.1/
# 
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

require 'toxiclibs'

load_library :mesh_to_vbo

RES = 64
ISO = 0.2
MAX_ISO = 0.66

attr_reader :mesh, :vbo, :curr_zoom, :implicit

def setup
  size(720,720, P3D)
  Processing::ArcBall.init(self)
  @vbo = MeshToVBO.new(self)
  @curr_zoom = 1
  vol = EvaluatingVolume.new(TVec3D.new(400,400,400), RES, RES, RES, MAX_ISO)
  surface = Volume::HashIsoSurface.new(vol)
  @mesh = Toxi::WETriangleMesh.new
  surface.compute_surface_mesh(mesh, ISO)
  @is_wire_frame = false
  no_stroke
  @implicit = vbo.meshToVBO(mesh, true)
  implicit.setFill(color(222, 222, 222))
  implicit.setAmbient(color(50, 50, 50))
  implicit.setShininess(color(10, 10, 10))
  implicit.setSpecular(color(50, 50, 50))
end

def draw
  background(0)
  lights
  define_lights
  shape(implicit)
end

def key_pressed
  case key
  when 'l', 'L'
    Toxi::LaplacianSmooth.new.filter(mesh, 1)
    @implicit = vbo.meshToVBO(mesh, true)
    # new mesh so need to set finish
    implicit.setFill(color(222, 222, 222))
    implicit.setAmbient(color(50, 50, 50))
    implicit.setShininess(color(10, 10, 10))
    implicit.setSpecular(color(50, 50, 50))
  when 's', 'S'
    save_frame("implicit.png")
  end
end

def define_lights
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, -150, 0)
  directional_light(0, 30, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

class EvaluatingVolume < Volume::VolumetricSpace

  attr_reader :upper_bound, :lut
  FREQ = Math::PI * 3.8

  def initialize(scal_vec, resX, resY, resZ, upper_limit)
    super(scal_vec, resX, resY, resZ)
    @upper_bound = upper_limit
  end

  def clear
    # nothing to do here
  end

  def getVoxelAt(i)
    getVoxel(i % resX, (i % sliceRes) / resX, i / sliceRes)
  end

  def getVoxel(x, y, z)  # can't overload so we renamed
    val = 0
    if (x > 0 && x < resX1 && y > 0 && y < resY1 && z > 0 && z < resZ1)
      xx = x * 1.0 / resX - 0.5  # NB: careful about integer division !!!
      yy = y * 1.0 / resY - 0.5
      zz = z * 1.0 / resZ - 0.5
      #val = Math.sin(xx * FREQ) + Math.cos(yy * FREQ) + Math.sin(zz * FREQ)
      val = Math.cos(xx * FREQ) * Math.sin(yy* FREQ) + Math.cos(yy* FREQ) * Math.sin(zz* FREQ) + Math.cos(zz* FREQ)* Math.sin(xx* FREQ)
      if (val > upper_bound)
        val = 0
      end
    end
    return val
  end
end


The library code:-
############################################
# mesh_to_vbo.rb
# a ruby library to convert toxi.mesh object
# to vbo (PShape) written by Martin Prout
############################################
class MeshToVBO
  PShape = Java::ProcessingCore::PShape
  attr_reader :parent

  def initialize(parent)
    @parent = parent
  end

  def meshToVBO(mesh, smth)
    retained = parent.create_shape
    retained.begin_shape(PShape::TRIANGLES)
    if smth
      mesh.compute_vertex_normals
      mesh.getFaces.each do |f|
        retained.normal(f.a.normal.x, f.a.normal.y, f.a.normal.z)
        retained.vertex(f.a.x, f.a.y, f.a.z)
        retained.normal(f.b.normal.x, f.b.normal.y, f.b.normal.z)
        retained.vertex(f.b.x, f.b.y, f.b.z)
        retained.normal(f.c.normal.x, f.c.normal.y, f.c.normal.z)
        retained.vertex(f.c.x, f.c.y, f.c.z)
      end
    else
      mesh.get_faces.each do |f|
        retained.normal(f.normal.x, f.normal.y, f.normal.z)
        retained.vertex(f.a.x, f.a.y, f.a.z)
        retained.vertex(f.b.x, f.b.y, f.b.z)
        retained.vertex(f.c.x, f.c.y, f.c.z)
      end
    end
    retained.end_shape
    retained
  end

  # variant
  # input array of meshes, output an array of shapes
  def meshToRetained(mesh, smth)
    mesh.map { |m| meshToVBO(m, smth) }
  end
end

Tuesday, 24 March 2015

Toxiclibs gem released

I've just released a toxiclibs gem that can be used with JRubyArt or ruby-processing (examples uses gem version 0.3.0.pre))
# A ruby processing sketch (needs re-factoring for jruby_art)
#
#
# This example implements a custom VolumetricSpace using an implicit function
# to calculate each voxel. This is slower than the default array or HashMap
# based implementations, but also has much less memory requirements and so might
# be an interesting and more viable approach for very highres voxel spaces
# (e.g. >32 million voxels). This implementation here also demonstrates how to
# achieve an upper boundary on the iso value (in addition to the one given and
# acting as lower threshold when computing the iso surface)
#
# Usage:
# drag mouse to rotate camera
# w: toggle wireframe on/off
# mouse wheel to zoom in/out
# l: apply laplacian mesh smooth
#
#

#
# Copyright (c) 2010 Karsten Schmidt & ruby-processing version Martin Prout 2012
# This sketch relies on a custom toxiclibscore library for PovRAY export
#
# This library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
#
# http://creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

require 'toxiclibs'
load_library 'vecmath' # uncomment this line for ruby-processing
RES = 64
ISO = 0.2
MAX_ISO = 0.66
attr_reader :mesh, :gfx, :curr_zoom, :is_wire_frame

def setup
  size(720, 720, P3D)
  ArcBall.init(self)
  @gfx = Gfx::ToxiclibsSupport.new(self)
  vol = EvaluatingVolume.new(Toxi::Vec3D.new(400, 400, 400), RES, RES, RES, MAX_ISO)
  surface = Volume::HashIsoSurface.new(vol)
  @mesh = Toxi::WETriangleMesh.new
  surface.compute_surface_mesh(mesh, ISO)
  @is_wire_frame = false
end

def draw
  background(0)
  if is_wire_frame
    no_fill
    stroke(255)
  else
    fill(255)
    no_stroke
    define_lights
    lights
  end
  @gfx.mesh(mesh, true)
end

def key_pressed
  case key
  when 'w', 'W'
    @is_wire_frame = !is_wire_frame
  when 'l', 'L'
    Toxi::LaplacianSmooth.new.filter(mesh, 1)
  when 's', 'S'
    save_frame('implicit.png')
  end
end

def define_lights
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, -150, 0)
  directional_light(0, 30, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

# Creating a volumetric space class
#
class EvaluatingVolume < Volume::VolumetricSpace
  include Processing::Proxy
  attr_reader :upper_bound
  FREQ = PI * 3.8

  def initialize(scal_vec, resX, resY, resZ, upper_limit)
    super(scal_vec, resX, resY, resZ)
    @upper_bound = upper_limit
  end

  def clear
    # nothing to do here
  end

  def getVoxelAt(i)
    getVoxel(i % resX, (i % sliceRes) / resX, i / sliceRes)
  end

  def getVoxel(x, y, z)  # can't overload so we renamed
    val = 0
    if x > 0 && x < resX1 && y > 0 && y < resY1 && z > 0 && z < resZ1
      xx = x * 1.0 / resX - 0.5  # NB: careful about integer division !!!
      yy = y * 1.0 / resY - 0.5
      zz = z * 1.0 / resZ - 0.5
      val = cos(xx * FREQ) * sin(yy * FREQ) + cos(yy * FREQ) * sin(zz* FREQ) + cos(zz * FREQ) * sin(xx * FREQ)
      # val = sin(xx * FREQ) + cos(yy * FREQ) + sin(zz * FREQ)
      # val = sin(xx * FREQ) * (xx * FREQ) + sin(yy * FREQ) * (yy * FREQ) + sin(zz * FREQ) * (zz * FREQ)
      val = 0 if val > upper_bound
    end
    val
  end
end

Monday, 9 March 2015

Watch a Handy Sketch in ruby-processing

There is this other attempt at doing ruby-processing called processing.rb (by someone who appears to be allergic to objects) here is their signature sketch in ruby-processing, what is is wrong with 'rp5 watch'? Improvements made to their example (yes I know I cheated with boundary check):-
  • avoid unecessary use of instance eval (it is all over processing.rb examples)
  • use rand(range) this is ruby after all
  • use __persistent__ to warn ruby compiler of a singleton (advisable for jruby 9000)
  • use map to create an array
  • use Struct to create a simple Boundary class
load_library :handy
java_import org.gicentre.handy.HandyRenderer

BALL_NUM = 6
BALL_COLORS = [[255, 0, 0], [255, 255, 0], [64, 64, 255]]
attr_reader :balls, :handy

def setup
  size(400, 400)
  HandyRenderer.__persistent__ = true
  @handy = HandyRenderer.new(self)
  @balls = (0...BALL_NUM).map { |i| Ball.new(i, handy) }
end

def draw
  background(color(234, 215, 182))
  fill(color(0, 255, 0))
  handy.rect(20, 20, 360, 20)
  handy.rect(20, 360, 360, 20)
  handy.rect(20, 40, 20, 320)
  handy.rect(360, 40, 20, 320)
  balls.each(&:draw)
end

# Bouncing ball
class Ball
  include Processing::Proxy
  attr_reader :x, :y, :radius, :size, :renderer, :boundary
  def initialize(id, renderer)
    @renderer = renderer
    @x, @y = rand(100..300), rand(100..300)
    @vx, @vy = rand(-6..6), rand(-6..6)
    @size = rand(60..100)
    @radius = size / 2.0
    @color = color(*BALL_COLORS[id % BALL_COLORS.size])
    @boundary = Boundary.new(40 + radius, 360 - radius)
  end

  def draw
    @x += @vx
    @y += @vy
    @vy += 0.1
    @vx = -@vx unless boundary.include? x
    @vy *= -0.99 unless boundary.include? y
    fill(@color)
    renderer.ellipse(x, y, size, size)
  end
end

Boundary = Struct.new(:lower, :upper) do
  def include?(x)
    (lower...upper).cover? x
  end
end

Friday, 6 March 2015

Bezier patch sketch in JRubyArt

Occasionally I see people trawling over really old sketches in this blog, and I wonder what are they thinking? when program versions (ie newer ruby-processing versions) change or there are new opportunities such as JRubyArt, why not look at the "hot" stuff. Anyway I revised this sketch for JRubyArt (no need to import :vecmath it is built in)....
Sketch also makes use of AppRender to make for direct conversion from Vec3D to vertex/normal.
require 'jruby_art'

# bezier patch By Marius Watz:
# http://www.openprocessing.org/sketch/57709
# Normal calculation added by Andres Colubri
# Direct port of sample code by Paul Bourke.
# Original code: http://paulbourke.net/geometry/bezier/
#
# hit "spacebar" to generate a new shape and save current
#

class Bezier < Processing::AppGL
  NI = 4
  NJ = 5
  RESI = NI * 10
  RESJ = NJ * 10

  attr_accessor :outp, :inp, :normp, :auto_normals, :renderer

  def setup
    size(1024, 768, P3D)
    ArcBall.init(self)
    @auto_normals = false
    @renderer = AppRender.new(self)
    build
  end

  def draw
    background(255)
    smooth(8)
    lights
    no_stroke
    fill(192, 192, 192)
    define_lights
    lights
    (0...RESI - 1).each do |i|
      begin_shape(QUAD_STRIP)
      (0...RESJ).each do |j|
        normp[i][j].to_normal(renderer) unless auto_normals
        outp[i][j].to_vertex(renderer)
        outp[i + 1][j].to_vertex(renderer)
      end
      end_shape
    end
  end

  def define_lights
    ambient_light(60, 60, 60)
    point_light(30, 30, 30, 0, 0, 0)
    directional_light(40, 40, 50, 1, 0, 0)
    spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, 0.5, PI / 2, 2)
  end


  def key_pressed
    return unless key == ' '
    save_frame('bez_patch.png')
    build
  end

  def build
    @outp = []
    @normp = []
    @inp = []
    uitang = Vec3D.new
    ujtang = Vec3D.new
    (0...NI).each do |i|
      row = []
      (0...NJ).each do |j|
        row << Vec3D.new(i, j, (rand * 6) - 3)
      end
      inp << row
    end
    (0...RESI).each do |i|
      mui = i.fdiv(RESI - 1)
      row = []
      row_n = []
      (0...RESJ).each do |j|
        muj = j.fdiv(RESJ - 1)
        vect = Vec3D.new
        uitang = Vec3D.new
        ujtang = Vec3D.new
        (0...NI).each do |ki|
          bi = bezier_blend(ki, mui, NI)
          dbi = d_bezier_blend(ki, mui, NI)
          (0...NJ).each do |kj|
            bj = bezier_blend(kj, muj, NJ)
            dbj = d_bezier_blend(kj, muj, NJ)
            vect.x += (inp[ki][kj].x * bi * bj)
            vect.y += (inp[ki][kj].y * bi * bj)
            vect.z += (inp[ki][kj].z * bi * bj)
            uitang.x += (inp[ki][kj].x * dbi * bj)
            uitang.y += (inp[ki][kj].y * dbi * bj)
            uitang.z += (inp[ki][kj].z * dbi * bj)
            ujtang.x += (inp[ki][kj].x * bi * dbj)
            ujtang.y += (inp[ki][kj].y * bi * dbj)
            ujtang.z += (inp[ki][kj].z * bi * dbj)
          end
        end
        vect += Vec3D.new(-NI / 2, -NJ / 2, 0)
        vect *= 100
        row << vect
        uitang.normalize!
        row_n << uitang.cross(ujtang.normalize)
      end
      @outp << row
      @normp << row_n
    end
  end

  def bezier_blend(k, mu,  n)
    blend = 1.0
    nn = n
    kn = k
    nkn = n - k
    while (nn >= 1)
      blend *= nn
      nn -= 1
      if kn > 1
        blend = blend.fdiv(kn)
        kn -= 1
      end
      if nkn > 1
        blend = blend.fdiv(nkn)
        nkn -= 1
      end
    end
    blend *= mu**k.to_f if k > 0
    return blend * (1 - mu)**(n - k).to_f if n - k > 0
    blend
  end

  def d_bezier_blend(k, mu,  n)
    dblendf = 1.0
    nn = n
    kn = k
    nkn = n - k
    while (nn >= 1)
      dblendf *= nn
      nn -= 1
      if kn > 1
        dblendf = dblendf.fdiv(kn)
        kn -= 1
      end
      if nkn > 1
        dblendf = dblendf.fdiv(nkn)
        nkn -= 1
      end
    end
    fk = 1
    dk = 0
    fnk = 1
    dnk = 0
    if k > 0
      fk = mu**k.to_f
      dk = k * mu**(k - 1).to_f
    end
    if n - k > 0
      fnk = (1 - mu)**(n - k).to_f
      dnk = (k - n) * (1 - mu)**(n - k - 1).to_f
    end
    dblendf * (dk * fnk + fk * dnk)
  end
end

Bezier.new(title: 'Bezier')

Sunday, 22 February 2015

More video library stuff with JRubyArt

The latest JRubyArt has a configurable library loader here is it being used to load the processing-3.0 video library:-
The library can be in a local 'library', folder in the default "~/.jruby_art/libraries", or in a "~/.jruby_art/libraries/config.yml" defined alternative folder.
require 'jruby_art'

# Test your Webcam with this sketch
class TestCapture < Processing::App
  load_library :video
  include_package 'processing.video'
  attr_reader :cam

  def setup
    size(960, 544)
    cameras = Capture.list
    fail 'There are no cameras available for capture.' if (cameras.length == 0)
    p 'Matching cameras available:'
    size_pattern = Regexp.new(format('%dx%d', width, height))
    select = cameras.grep size_pattern # filter available cameras
    select.uniq.map { |cam| p cam.strip }
    fail 'There are no matching cameras.' if (select.length == 0)
    start_capture(select[0])
  end

  def start_capture(cam_string)
    # The camera can be initialized directly using an
    # element from the array returned by list:
    @cam = Capture.new(self, cam_string)
    p format('Using camera %s', cam_string)
    cam.start
  end

  def draw
    return unless cam.available
    cam.read
    image(cam, 0, 0)
    # The following does the same, and is faster when just drawing the image
    # without any additional resizing, transformations, or tint.
    # set(0, 0, cam)
  end
end

TestCapture.new(title: 'Test Capture')

Thursday, 19 February 2015

ASCII Capture sketch with JRubyArt

To get this sketch to work I placed the processing library jars in an adjacent library folder, also needed font in adjacent data folder. See jruby_art on github. It occurs to me that a library naming convention and library_loader utility like ruby-processing would be nice to have, perhaps with default location '~/.jruby_art/libraries' with a local library alternative (WIP) library_loader since implemented for JRubyArt.
#
# ASCII Video
# by Ben Fry, translated to jruby_art by Martin Prout.
#
#
# Text chars have been used to represent images since the earliest computers.
# This sketch is a simple homage that re-interprets live video as ASCII text.
# See the key_pressed function for more options, like changing the font size.
#
require 'jruby_art'

Dir[File.join('library', '*.jar')].each do |jar|
  require_relative jar
end

class AsciiVideoCapture < Processing::App
  attr_reader :bright, :char, :cheat_screen, :font, :font_size, :letters, :video
  # All ASCII characters, sorted according to their visual density
  LETTER_STRING = %q{ .`-_':,;^=+/\"|)\\<>)iv%xclrs{*}I?!][1taeo7zjLunT#JCwfy325Fp6mqSghVd4EgXPGZbYkOA&8U$@KHDBWNMR0Q}
  LETTER_ORDER = LETTER_STRING.scan(/./)

  def setup
    size(640, 480)
    init_video
    @font_size = 1.5
    @font = load_font(data_path('UniversLTStd-Light-48.vlw'))
    # for the 256 levels of brightness, distribute the letters across
    # the an array of 256 elements to use for the lookup
    @letters = (0...256).map do |i|
      LETTER_ORDER[map1d(i, (0...256), (0...LETTER_ORDER.length))]
    end
    # current brightness for each point
    @bright = Array.new(video.width * video.height, 128)
  end

  def init_video
    # This the default video input, see the test_capture
    # example if it creates an error
    @video = Java::ProcessingVideo::Capture.new(self, 160, 120)
    # Start capturing the images from the camera
    video.start
    @cheat_screen = false
  end

  def capture_event(c)
    c.read
    background 0
  end

  def draw
    return unless (video.available == true)
    capture_event(video)
    push_matrix
    hgap = width / video.width
    vgap = height / video.height
    scale([hgap, vgap].max * font_size)
    text_font(font, font_size)
    index = 0
    video.load_pixels
    (0...video.height).each do
      # Move down for next line
      translate(0,  1.0 / font_size)
      push_matrix
      (0...video.width).each do
        pixel_color = video.pixels[index]
        # Faster method of calculating r, g, b than red(), green(), blue()
        r = pixel_color >> 16 & 0xff
        g = pixel_color >> 8 & 0xff
        b = pixel_color & 0xff
        # Another option would be to properly calculate brightness as luminance:
        # luminance = 0.3*red + 0.59*green + 0.11*blue
        # Or you could instead red + green + blue, and make the the values[] array
        # 256*3 elements long instead of just 256.
        pixel_bright = [r, g, b].max
        # The 0.1 value is used to damp the changes so that letters flicker less
        diff = pixel_bright - bright[index]
        bright[index] += diff * 0.1
        fill(pixel_color)
        text(letters[bright[index]], 0, 0)
        # Move to the next pixel
        index += 1
        # Move over for next character
        translate(1.0 / font_size, 0)
      end
      pop_matrix
    end
    pop_matrix
    # image(video, 0, height - video.height)
    # set() is faster than image() when drawing untransformed images
    set(0, height - video.height, video) if cheat_screen
  end

  MESSAGE = <<-EOS
  Controls are:
  g to save_frame, f & F to set font size
  c to toggle cheat screen display
  EOS

  #
  # Handle key presses:
  # 'c' toggles the cheat screen that shows the original image in the corner
  # 'g' grabs an image and saves the frame to a tiff image
  # 'f' and 'F' increase and decrease the font size
  #
  def key_pressed
    case key
    when 'g' then save_frame
    when 'c' then @cheat_screen = !cheat_screen
    when 'f' then @font_size *= 1.1
    when 'F' then @font_size *= 0.9
    else
      warn MESSAGE
    end
  end
end

AsciiVideoCapture.new(title: 'Video Capture Sketch')

Sunday, 8 February 2015

MDArray gem is available to JRubyArt (handy for image processing)

Sketch makes use of MDArray convenience index function. Requires to be run as "k9 run sobel.rb" to get image stuff to work, but interestingly mdarray gem also just loads...(cf ruby-processing)
require 'jruby_art'
require 'mdarray'

class Strobel < Processing::App
  attr_reader :img, :edge

  def setup
    size 300, 225
    @img = load_image('engine.png')
    @edge = create_image(300, 225, ALPHA)
    edge.load_pixels
    img.load_pixels
    generate(img)
  end

  def draw
    image edge, 0, 0
    filter(GRAY)
  end

  def generate(from_image)
    sbx = MDArray.int([3, 3], [-1, 0, 1, -2, 0, 2, -1, 0, 1])
    sby = MDArray.int([3, 3], [1, 2, 1, 0, 0, 0, -1, -2, -1])
    edg = MDArray.int([225, 300])  # this seems Irish, but is correct
    pxls = from_image.pixels
    (1...from_image.width - 2).each do |x|
      (1...from_image.height - 2).each do |y|
        pixel_x = (sbx[0, 0] * pxls[x - 1 + width * (y - 1)]) + (sbx[0, 1] * pxls[x + width * (y - 1)]) + (sbx[0, 2] * pxls[x + 1+ width * (y - 1)]) +
          (sbx[1, 0] * pxls[x - 1+ width * y])   + (sbx[1, 1] * pxls[x + width * y])   + (sbx[1, 2] * pxls[x + 1+ width * y]) +
          (sbx[2, 0] * pxls[x - 1+ width * y + 1]) + (sbx[2, 1] * pxls[x + width * y + 1]) + (sbx[2, 2] * pxls[x + 1 + width * (y + 1)])
        pixel_y = (sby[0, 0] * pxls[x - 1+ width * (y - 1)]) + (sby[0, 1] * pxls[x + width * (y - 1)]) + (sby[0, 2] * pxls[x + 1+ width * (y - 1)]) +
          (sby[1, 0] * pxls[x - 1+ width * y])   + (sby[1, 1] * pxls[x + width * y])   + (sby[1, 2] * pxls[x + 1+ width * y]) +
          (sby[2, 0] * pxls[x - 1+ width * (y + 1)]) + (sby[2, 1] * pxls[x + width * (y + 1)]) + (sby[2, 2] * pxls[x + 1+ width * (y + 1)])
        val = Math.hypot(pixel_x, pixel_y).ceil
        edg[y, x] = val
      end
    end
    edge.pixels = edg.to_a # load pixels from MDArray
  end

  def mouse_pressed
    save('engine_edge.png')
  end
end

Strobel.new(title: 'Sobel Edge Detect')
before
edge detected

Followers

About Me

My Photo
I am currently the lead developer of ruby-processing.