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})