GLVideo the future video library for processing

This library uses GStreamer-1.0 (and hence possibility of hardware acceleration). First you need to make sure you can use the library on vanilla processing, for Archlinux this may need a re-compile see my fork. Here is a more complicated sketch by Gottfreid Haider, translated to JRubyArt see original here.

Note how we are able to take advantage of ruby features to make the sketch much more intelligible and more Object Orientated. Nevertheless kudos to Gottfreid Haider for the original sketch and for creating the library.

NB: The glvideo library is still somewhat experimental and the interface may well change.

load_libraries :glvideo, :corners
include_package 'gohai.glvideo'

# placeholder for image sources
ImageSource = Struct.new(:video, :image)

attr_reader :sources, :sel, :video, :corners, :quads, :last_mouse_move
RES = 5	# number of subdivisions (e.g. 5 x 5)

def setup
  @last_mouse_move = 0
  no_cursor
  @video = GLMovie.new(self, data_path('launch2.mp4'))
  video.loop
  image = load_image(data_path('checkerboard.png'))
  @sources = ImageSource.new(video, image)
  @sel = sources.video
  @corners = Corners.new(width, height, sources.image.width / 2, sources.image.height / 2)
  @quads = create_mesh(sel, corners, RES)
end

def draw
  background(0)
  video.read if sel.respond_to?(:read) && video.available
  # regenerate mesh if we're dragging a corner
  if corners.selected? && (pmouse_x != mouse_x || pmouse_y != mouse_y)
    corners.set_corner(mouse_x, mouse_y)
    # this improves performance, but will be replaced by a
    # more elegant way in a future release
    @quads = []
    Java::JavaLang::System.gc # this is  generally not recommended
    @quads = create_mesh(sel, corners, RES)
  end

  # display
  quads.map { |quad| shape(quad) } unless quads.empty?
  # hide the mouse cursor after two seconds
  if pmouse_x != mouse_x || pmouse_y != mouse_y
    cursor
    @last_mouse_move = millis
  elsif !last_mouse_move.zero? && 2000 < millis - last_mouse_move
    no_cursor
    @last_mouse_move = 0
  end
end

def mouse_pressed
  corners.each_with_index do |corner, i|
    return corners.set_index(i) if dist(mouse_x, mouse_y, corner.x, corner.y) < 20
  end
  # no corner? then switch texture
  @sel = sel.respond_to?(:loop) ? sources.image : sources.video
  @quads = create_mesh(sel, corners, RES)
end

def mouse_released
  corners.set_index(-1)
end

def create_mesh(tex, corners, res)
  transform = PerspectiveTransform.get_quad_to_quad(
    0, 0, tex.width, 0,                   # top left, top right
    tex.width, tex.height, 0, tex.height, # bottom right, bottom left
    corners[0].x, corners[0].y, corners[1].x, corners[1].y,
    corners[2].x, corners[2].y, corners[3].x, corners[3].y
  )
  warp_perspective = WarpPerspective.new(transform)
  x_step = tex.width.to_f / res
  y_step = tex.height.to_f / res
  quads = []
  (0...res).each do |y|
    (0...res).each do |x|
      texture_mode(NORMAL)
      sh = create_shape
      sh.begin_shape(QUAD)
      sh.no_stroke
      sh.texture(tex)
      sh.normal(0, 0, 1)
      point = warp_perspective.map_dest_point(x * x_step, y * y_step)
      sh.vertex(point.get_x.to_f, point.get_y.to_f, 0, x.to_f / res, y.to_f / res)
      point = warp_perspective.map_dest_point((x + 1) * x_step, y * y_step)
      sh.vertex(point.get_x.to_f, point.get_y.to_f, 0, (x + 1).to_f / res, y.to_f / res)
      point = warp_perspective.map_dest_point((x + 1) * x_step, (y + 1) * y_step)
      sh.vertex(point.get_x.to_f, point.get_y.to_f, 0, (x + 1).to_f / res, (y + 1).to_f / res)
      point = warp_perspective.map_dest_point(x * x_step, (y + 1) * y_step)
      sh.vertex(point.get_x.to_f, point.get_y.to_f, 0, x.to_f / res, (y + 1).to_f / res)
      sh.end_shape
      quads[y * res + x] = sh
    end
  end
  quads
end

def settings
  full_screen(P2D)
end

Here is the corners.rb library that just how flexible ruby is

# frozen_string_literal: true
require 'forwardable'
# placeholder for vector coordinates
Vect = Struct.new(:x, :y)

# A class to contain frame corners, uses forwardable
class Corners
  include Enumerable
  extend Forwardable
  def_delegators :@corners, :each_with_index, :[], :[]=, :<<
  attr_reader :idx

  def initialize(width, height, ws, wh)
    @corners = [
      Vect.new(width / 2 - ws, height / 2 - wh),
      Vect.new(width / 2 + ws, height / 2 - wh),
      Vect.new(width / 2 + ws, height / 2 + wh),
      Vect.new(width / 2 - ws, height / 2 + wh)
    ]
    @idx = -1
  end

  def set_corner(mx, my)
    self[idx] = Vect.new(mx, my)
  end

  def selected?
    idx != -1
  end

  def set_index(sel)
    @idx = sel
  end
end