# This is a simple script showing how to create a "plasma cloud" in
# JRuby.  This is *very* slow way of implementing this, but hopefully
# it's an instructive example.  See this page for more details:
#
#   http://pacific.mpi-cbg.de/wiki/index.php/JRuby_Scripting

# ------------------------------------------------------------------------

# The width and the height; these are global variables so that
# "to_index" can use them:
$w = 400
$h = 300

# Create a single slice RGB image and get the pixels array:
cp = ij.process.ColorProcessor.new($w,$h)
$i = ij.ImagePlus.new "Plasma Cloud", cp
pixels = cp.getPixels

# A helper function to find the index into the pixel array:
def to_index( x, y )
  x + y * $w
end

# Take a list of integer values and return a list with the midpoint
# between each inserted into the list:
def subdivide( points )
  new_points = []
  points.each_index do |index|
    next if index == (points.length - 1)
    min = points[index]
    max = points[index+1]
    new_points.push min
    mid = (min + max) / 2
    new_points.push mid
  end
  new_points.push points.last
  new_points
end

# Seed the random number generator so we get a different cloud each time:
$rng = java.util.Random.new( Time.now.to_i )

# Keep track of the last time we updated the display:
$last_time_displayed = java.lang.System.currentTimeMillis
$update_every = 1000

def set_interpolated_points( pixels, x_min, x_mid, x_max, y_min, y_mid, y_max )
  # Don't redraw all the time, only every $update_every milliseconds
  now = java.lang.System.currentTimeMillis
  if (now - $last_time_displayed) > $update_every
    $last_time_displayed = now
    $i.updateAndDraw
  end
  # Do nothing if there are no pixels to fill in:
  return if (((x_max - x_min) <= 1) && ((y_max - y_min) <= 1))
  # Set the points in the middle of the top row and the bottom row:
  if x_mid != x_min
    pixels[ to_index( x_mid, y_min ) ] =
      color_between( x_max - x_mid,
                     [ pixels[ to_index( x_min, y_min ) ],
                       pixels[ to_index( x_max, y_min ) ] ] )
    pixels[ to_index( x_mid, y_max ) ] =
      color_between( x_max - x_mid,
                     [ pixels[ to_index( x_min, y_max ) ],
                       pixels[ to_index( x_max, y_max ) ] ] )
  end
  # Set the points in the middle of the left colum and the right column:
  if y_mid != y_min
    pixels[ to_index( x_min, y_mid ) ] =
      color_between( y_max - y_mid,
                     [ pixels[ to_index( x_min, y_min ) ],
                       pixels[ to_index( x_min, y_max ) ] ] )      
    pixels[ to_index( x_max, y_mid ) ] =
      color_between( y_max - y_mid,
                     [ pixels[ to_index( x_max, y_min ) ],
                       pixels[ to_index( x_max, y_max ) ] ] )
  end
  # Now the middle point:
  xdiff = (x_max - x_min) / 2.0
  ydiff = (y_max - y_min) / 2.0
  separation = Math.sqrt( xdiff*xdiff + ydiff*ydiff )
  pixels[ to_index( x_mid, y_mid ) ] =
    color_between( separation,
                   [ pixels[ to_index( x_min, y_min ) ],
                     pixels[ to_index( x_max, y_min ) ],
                     pixels[ to_index( x_min, y_max ) ],
                     pixels[ to_index( x_max, y_max ) ] ] )
end

# Get a random RGB value for the initial corner points:
def random_color
  r = $rng.nextInt 256
  g = $rng.nextInt 256
  b = $rng.nextInt 256
  b + (g << 8) + (r << 16)
end

# Return 'old_value' plus some noise up to 'greatest_difference',
# making sure we don't return a value > 255 or < 0:
def add_noise( old_value, greatest_difference )
  new_value = old_value + $rng.nextInt( 2 * greatest_difference ) - greatest_difference
  if new_value > 255
    255
  elsif new_value < 0
    0
  else
    new_value
  end
end

# 'colors' is a list of the colors at 'separation' distance form some
# point; return a color which is an average of those plus some random
# noise linearly related to the separation:
def color_between( separation, colors )
  separation = 1 if separation < 1
  sum_red = sum_green = sum_blue = n = 0
  colors.each do |c|
    n += 1
    sum_blue  += c & 0xFF
    sum_green += (c >> 8)  & 0xFF;
    sum_red   += (c >> 16) & 0xFF;
  end
  # The basic value is the mean of the surrounding colors:
  new_r = sum_red / n
  new_g = sum_green / n
  new_b = sum_blue / n
  # Let's say we can add a random value between -256 and 256 when the
  # separation is half the maximum of $w and $h, and we can only add 0
  # if they're adjacent:
  greatest_difference = Integer( ( 256.0 * separation ) / ([ $w, $h ].max / 2) )
  new_r = add_noise( new_r, greatest_difference )
  new_g = add_noise( new_g, greatest_difference )
  new_b = add_noise( new_b, greatest_difference )
  # Now return the result:
  new_b + (new_g << 8) + (new_r << 16)
end

# Set random colors in the corners:
pixels[ 0 ] = random_color
pixels[ to_index( 0, $h - 1 ) ] = random_color
pixels[ to_index( $w - 1, 0 ) ] = random_color
pixels[ to_index( $w - 1, $h - 1 ) ] = random_color

x_points = [ 0, $w - 1 ]
y_points = [ 0, $h - 1 ]

did_something_last_time = true

$i.show

while true

  did_something_last_time = false

  # Divide up the x_points and y_points to find the midpoints to add:

  new_x_points = subdivide x_points
  new_y_points = subdivide y_points

  # Now for each sub-rectangle we should have set the colours of the
  # corners, so set the interpolated midpoints based on those:

  new_y_points.each_index do |y_min_index|
    next unless (y_min_index % 2) == 0
    next unless y_min_index < (new_y_points.length - 2)
    y_min = new_y_points[y_min_index]
    y_mid = new_y_points[y_min_index+1]
    y_max = new_y_points[y_min_index+2]

    new_x_points.each_index do |x_min_index|
      next unless (x_min_index % 2) == 0
      next unless x_min_index < (new_x_points.length - 2)
      x_min = new_x_points[x_min_index]
      x_mid = new_x_points[x_min_index+1]
      x_max = new_x_points[x_min_index+2]      

      set_interpolated_points( pixels, x_min, x_mid, x_max, y_min, y_mid, y_max )
      
    end
  end

  x_points = new_x_points.uniq
  y_points = new_y_points.uniq

  # We can break when the list of edge points in x and y is the same
  # as the width and the height:
  break if (x_points.length == $w) && (y_points.length == $h)

end

$i.updateAndDraw
