lib/sugar_png.rb in sugar_png-0.0.1 vs lib/sugar_png.rb in sugar_png-0.4.0

- old
+ new

@@ -1,5 +1,167 @@ require 'zpng' -SugarPNG = ZPNG +require 'sugar_png/dyn_accessor' +require 'sugar_png/color' +require 'sugar_png/border' +require 'sugar_png/image' +require 'sugar_png/font' +require 'sugar_png/glyph' -require 'sugar_png/datastream' +class SugarPNG + DEFAULT_BG = :transparent + DEFAULT_FG = :black + + Canvas = Datastream = Image + + class Exception < ::Exception; end + class ArgumentError < Exception; end + + extend DynAccessor + dyn_accessor :width, :height, :zoom, :depth + dyn_accessor :bg => %w'background bg_color background_color' + dyn_accessor :fg => %w'foreground fg_color foreground_color color' + + def initialize h={}, &block + @bg = DEFAULT_BG + @fg = DEFAULT_FG + @zoom = 1 + clear + + if block_given? + if block.arity == 1 + yield self + else + instance_eval &block + end + end + + # boring non-magic hash + h.each do |k,v| + self.send "#{k}=", v + end + end + + # reset all drawings + def clear + @pixels = Hash.new{ |k,v| k[v] = {} } + @borders = [] + end + + # set pixels + # accepted coordinate values: + # a) boring Integers + # b) neat Arrays + # c) long Ranges + # d) super Enumerators + # + # accepted color values: see SugarPNG::Color + def []= ax, ay, color + Array(ay).each do |y| + Array(ax).each do |x| + @pixels[y][x] = color + end + end + end + + # same as above, but color argument can be optional + def pixel ax, ay, color = @fg + Array(ay).each do |y| + Array(ax).each do |x| + @pixels[y][x] = color + end + end + end + alias :pixels :pixel + + %w'put_pixel set_pixel point dot'.each do |x| + # plural & singular aliases to increase entropy & prevent global singularity + class_eval "alias :#{x} :pixel; alias :#{x}s :pixels" + end + + # draw image border with specified color + def border size, color = nil + color ||= @fg || @bg + @borders << Border.new( size.is_a?(Hash) ? size : {:size => size, :color => color} ) + end + + # same as border, but default color is background + def padding size, color = @bg + border size, color + end + + # draw a single glyph, used from within text() + def draw_glyph glyph, x0, y, color + #TODO: optimize? + glyph.to_a.each do |row| + x = x0 + row.each do |bit| + self[x,y] = color if bit == 1 + x += 1 + end + y += 1 + end + end + + # draws text, optional arguments are :color, :x, :y + def text text, h = {} + font = @font ||= Font.new + color = h[:color] || @fg + y = h[:y] || 0 + text.split(/[\r\n]+/).each do |line| + x = h[:x] || 0 + line.each_char do |c| + glyph = font[c] + draw_glyph glyph, x, y, color + x += glyph.width + end + y += font.height + end + end + + # export PNG to file + def save fname + File.open(fname, "wb"){ |f| f<<to_s } + end + + # get PNG as bytestream, for saving it to file manually, or for sending via HTTP + def to_s + height = @height || ((t=@pixels.keys.max) && t+1 ) || 0 + width = @width || ((t=@pixels.values.map(&:keys).map(&:max).max) && t+1 ) || 0 + + xofs = yofs = 0 + xmax = width-1 + ymax = height-1 + + if @borders.any? + width += @borders.map(&:width).inject(&:+) + height += @borders.map(&:height).inject(&:+) + xofs += @borders.map(&:left).inject(&:+) + yofs += @borders.map(&:top).inject(&:+) + end + + raise(Exception.new("invalid image height #{height}")) if height <= 0 + raise(Exception.new("invalid image width #{width}")) if width <= 0 + + img = Image.new :width => width, :height => height, :depth => @depth + img.clear(_color(@bg)) if @bg + img.draw_borders(@borders.each{ |b| b.color = _color(b.color)} ) + + @pixels.each do |y, xh| + next if y>ymax + xh.each do |x, c| + next if x>xmax + img[x+xofs,y+yofs] = _color(c) + end + end + + img.zoom(@zoom).export + end + alias :export :to_s + + private + + # create color from any of the supported color representations + def _color c + c = c.is_a?(ZPNG::Color) ? c : Color.new(c, :depth => @depth) + end +end