corners.rb
require 'rubygems'
require 'Rmagick'
require 'ostruct'

# put a shadow behind an image
# the resulting image will be the same size - the caller has to allow space for
# sigma*2 shadow width, and offset if supplied
# returns original image if no sigma, or 0 sigma, passed
def shadow_image(image, sigma, params = {})
  params = OpenStruct.new({
      :opacity => 1.0,
      :offsetx => 0,
      :offsety => 0
    }.merge(params))
  return image if sigma.nil? || sigma == 0
  shadow = image.shadow(params.offsetx,params.offsety,sigma,params.opacity)
  imgl = Magick::ImageList.new
  imgl << shadow
  imgl << image
# ugly - TODO: find a cleaner way to shadow without crap...  try composite under???
# note if original image had space for shadow, this just throws away blank stuff
  imgl.flatten_images.crop(0,0,image.columns,image.rows)
end
# a 2-layer round rect - so you can put a shadow on a border...
def roundrect_multi(prefix, width, height, colour, params = {})
  params = OpenStruct.new({
    :radius => 16,
    :bordercolour => colour,
    :bordersize => nil,
    :shadowcolour => '#000000',
    :shadowsigma => 4,
    :shadowoffset => 3,
    :shadowopacity => 1,
    :bshadowcolour => '#000000',
    :bshadowsigma => 2,
    :bshadowoffset => 0,
    :bshadowopacity => 0.5,
    :dialog_opacity => 0.8
  }.merge(params))
  has_border = !params.bordersize.nil?
  border_pad = has_border ? params.bordersize/2 : 0  # amount border extends beyond rect size on each side
  border_width = has_border ? border_pad * 2 + 1 : 0  # actual width of border
  # note that a border of size 6 is actually 7 pixels wide, 
  # whereas a border of size 7 is (I think) 9 pixels, with the outer 2 almost blank.
  
  # need to offset everythng to provide room for the halo (up to sigma*2 in size)
  halo_size = params.shadowsigma*2
  pad_left = halo_size - params.shadowoffset
  pad_top = halo_size - params.shadowoffset
  pad_right = halo_size + params.shadowoffset
  pad_bottom = halo_size + params.shadowoffset
  bhalo_size = has_border ? params.bshadowsigma*2 : 0
  bpad_left = has_border ? bhalo_size - params.bshadowoffset : 0
  bpad_top = has_border ? bhalo_size - params.bshadowoffset : 0
  bpad_right = has_border ? bhalo_size + params.bshadowoffset : 0
  bpad_bottom = has_border ? bhalo_size + params.bshadowoffset : 0
  full_width = width + border_width + pad_left + pad_right
  full_height = height + border_width + pad_top + pad_bottom
  puts "\nGenerating images with prefix #{prefix}"
  puts "with corner radius #{params.radius} and " + (has_border ? "a #{border_width} pixel border." : "no border.")
  puts "Rectangle size is #{width}x#{height} - image full size including borders and shadows is #{full_width}x#{full_height}"
  puts "gap to left writeable : halo #{pad_left} + border width #{border_width} + border shadow #{bpad_right} = #{pad_left + border_width + bpad_right}"
  puts "gap to left inner (slice): halo #{pad_left} + border outer #{border_pad} + radius #{params.radius} = #{pad_left + border_pad + params.radius}"
  puts "gap to right writeable : halo #{pad_right} + border width #{border_width} + border shadow #{bpad_left} = #{pad_left + border_width + bpad_left}"
  puts "gap to right inner (slice): halo #{pad_right} + border outer #{border_pad} + radius #{params.radius} = #{pad_right + border_pad + params.radius}"
  puts "gap to top writeable : halo #{pad_top} + border width #{border_width} + border shadow #{bpad_bottom} = #{pad_top + border_width + bpad_bottom}"
  puts "gap to top inner (slice): halo #{pad_top} + border outer #{border_pad} + radius #{params.radius} = #{pad_top + border_pad + params.radius}"
  puts "gap to bottom writeable : halo #{pad_bottom} + border width #{border_width} + border shadow #{bpad_top} = #{pad_left + border_width + bpad_left}"
  puts "gap to bottom inner (slice): halo #{pad_bottom} + border outer #{border_pad} + radius #{params.radius} = #{pad_right + border_pad + params.radius}"

  gif_img = Magick::Image.new(full_width, full_height) { self.background_color = 'transparent'}

  gc = Magick::Draw.new

  # border only
  if has_border
  border = Magick::Image.new(full_width, full_height) { self.background_color = 'transparent'}
  gc.stroke(params.bordercolour)
  gc.stroke_linecap('round')
  gc.stroke_width(params.bordersize)
  gc.fill('transparent')
  gc.roundrectangle(pad_left + border_pad,pad_top + border_pad, pad_left + border_pad+width,pad_top + border_pad+height, params.radius, params.radius)
  gc.draw(border)
  border = shadow_image(border, params.bshadowsigma,:opacity => params.bshadowopacity, :offsetx => params.bshadowoffset, :offsety => params.bshadowoffset)
  end
  # background only
  img = Magick::Image.new(full_width, full_height) { self.background_color = 'transparent'}
  gc.stroke(colour)
  gc.stroke_width(0)
  gc.fill(colour)
  gc.roundrectangle(pad_left + border_pad,pad_top + border_pad, pad_left + border_pad+width,pad_top + border_pad+height, params.radius, params.radius)
  gc.draw(img)
  img = shadow_image(img, params.shadowsigma,:opacity => params.shadowopacity, :offsetx => params.shadowoffset, :offsety => params.shadowoffset)

  # put the border over the image!
  if has_border
    imgl = Magick::ImageList.new
    imgl << img
    imgl << border
    img = imgl.flatten_images.crop(0,0,full_width, full_height)
  end
  
  gif_img = img.dup
  # set opacity of everything for the png version.  Don't do this for the gif or it'll be blank!
  img.quantum_operator(Magick::MultiplyQuantumOperator, params.dialog_opacity, Magick::AlphaChannel)
  
  img.write("#{prefix}.png")
  gif_img.write("#{prefix}.gif")
  puts "wrote #{prefix}.png, #{prefix}.gif"
end

roundrect_multi("dark_border", 100,100, '#0066FF', {:bordercolour => '#0033FF', :bordersize => 6, :radius => 16, :shadowoffset => 4, :bshadowoffset => 1})
roundrect_multi("shad_border", 100,100, '#0066FF', { :bordersize => 6, :radius => 16, :shadowoffset => 4, :bshadowoffset => 1})
roundrect_multi("no_border", 100,100, '#0066FF', {:bordersize => 0, :radius => 16, :shadowoffset => 4})