Skip to content

Commit

Permalink
docs for the PNG formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
jamis committed Dec 19, 2010
1 parent cadf04e commit d417a0d
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 40 deletions.
80 changes: 62 additions & 18 deletions lib/theseus/formatters/png.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@

module Theseus
module Formatters
# This is an abstract superclass for PNG formatters. It simply provides some common
# utility and drawing methods that subclasses can take advantage of, to render
# mazes to a PNG canvas.
#
# Colors are given as 32-bit integers, with each RGBA component occupying 1 byte.
# R is the highest byte, A is the lowest byte. In other words, 0xFF0000FF is an
# opaque red, and 0x7f7f7f7f is a semi-transparent gray. 0x0 is fully transparent.
#
# You may also provide the colors as hexadecimal string values, and they will be
# converted to the corresponding integers.
class PNG
# The default options. Note that not all PNG formatters honor all of these options;
# specifically, +:wall_width+ is not consistently supported across all formatters.
DEFAULTS = {
:cell_size => 10,
:wall_width => 1,
Expand All @@ -15,13 +27,45 @@ class PNG
:solution => false
}

# North, whether in the under or primary plane
ANY_N = Maze::N | (Maze::N << Maze::UNDER_SHIFT)

# South, whether in the under or primary plane
ANY_S = Maze::S | (Maze::S << Maze::UNDER_SHIFT)

# West, whether in the under or primary plane
ANY_W = Maze::W | (Maze::W << Maze::UNDER_SHIFT)

# East, whether in the under or primary plane
ANY_E = Maze::E | (Maze::E << Maze::UNDER_SHIFT)

# The options to use for the formatter. These are the ones passed
# to the constructor, plus the ones from the DEFAULTS hash.
attr_reader :options

# The +options+ must be a hash of any of the following options:
#
# [:cell_size] The number of pixels on a side that each cell
# should occupy. Different maze types will use that
# space differently. Also, the cell padding is applied
# inside the cell, and so consumes some of the area.
# The default is 10.
# [:wall_width] How thick the walls should be drawn. The default is 1.
# Note that not all PNG formatters will honor this value
# (yet).
# [:wall_color] The color to use when drawing the wall. Defaults to black.
# [:cell_color] The color to use when drawing the cell. Defaults to white.
# [:solution_color] The color to use when drawing the solution path. This is
# only used when the :solution option is given.
# [:background] The color to use for the background of the maze. Defaults
# to transparent.
# [:outer_padding] The extra padding (in pixels) to add around the outside
# edge of the maze. Defaults to 2.
# [:cell_padding] The padding (in pixels) to add around the inside of each
# cell. This has the effect of separating the cells. The
# default cell padding is 1.
# [:solution] A boolean value indicating whether or not to draw the
# solution path as well. The default is false.
def initialize(maze, options)
@options = DEFAULTS.merge(options)

Expand All @@ -37,10 +81,15 @@ def initialize(maze, options)
end
end

# Returns the raw PNG data for the formatter.
def to_blob
@blob
end

# Returns the color at the given point by considering all provided paths. The
# +:color: metadata from the first path that is set at the given point is
# returned. If no path describes the given point, then the value of the
# +:cell_color+ option is returned.
def color_at(pt, direction=nil)
@paths.each do |path|
return path[:color] if direction ? path.path?(pt, direction) : path.set?(pt)
Expand All @@ -49,34 +98,23 @@ def color_at(pt, direction=nil)
return @options[:cell_color]
end

# returns the projection of point p onto the line that passes through a and c
def project(p, a, c)
# equation of line ac, y = m1 * x + b1
m1 = (c[1] - a[1]) / (c[0] - a[0]).to_f
b1 = a[1] - m1 * a[0]

# equation of line through p that is perpendicular to ac
# y = m2 * x + b2
m2 = -1 / m1
b2 = p[1] - m2 * p[0]

# solve for intersection of two lines
dx = (m1 * a[0] - a[1] - m2 * p[0] + p[1]) / (m1 - m2)
dy = m1 * dx + b1

return [dx, dy]
end

# Returns a new 2-tuple (x2,y2), where x2 is point[0] + dx, and y2 is point[1] + dy.
def move(point, dx, dy)
[point[0] + dx, point[1] + dy]
end

# Clamps the value +x+ so that it lies between +low+ and +hi+. In other words,
# returns +low+ if +x+ is less than +low+, and +high+ if +x+ is greater than
# +high+, and returns +x+ otherwise.
def clamp(x, low, hi)
x = low if x < low
x = hi if x > hi
return x
end

# Draws a line from +p1+ to +p2+ on the given canvas object, in the given
# color. The coordinates of the given points are clamped (naively) to lie
# within the canvas' bounds.
def line(canvas, p1, p2, color)
canvas.line(
clamp(p1[0].round, 0, canvas.width-1),
Expand All @@ -86,6 +124,8 @@ def line(canvas, p1, p2, color)
color)
end

# Fills the rectangle defined by the given coordinates with the given color.
# The coordinates are clamped to lie within the canvas' bounds.
def fill_rect(canvas, x0, y0, x1, y1, color)
x0 = clamp(x0, 0, canvas.width-1)
y0 = clamp(y0, 0, canvas.height-1)
Expand All @@ -98,6 +138,10 @@ def fill_rect(canvas, x0, y0, x1, y1, color)
end
end

# Fills the polygon defined by the +points+ array, with the given +color+.
# Each element of +points+ must be a 2-tuple describing a vertex of the
# polygon. It is assumed that the polygon is closed. All points are
# clamped (naively) to lie within the canvas' bounds.
def fill_poly(canvas, points, color)
min_y = 1_000_000
max_y = -1_000_000
Expand Down
13 changes: 12 additions & 1 deletion lib/theseus/formatters/png/delta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
module Theseus
module Formatters
class PNG
# Renders a DeltaMaze to a PNG canvas. Does not currently support the
# +:wall_width+ option.
#
# You will almost never access this class directly. Instead, use
# DeltaMaze#to(:png, options) to return the raw PNG data directly.
class Delta < PNG
# Create and return a fully initialized PNG::Delta object, with the
# maze rendered. To get the maze data, call #to_blob.
#
# See Theseus::Formatters::PNG for a list of all supported options.
def initialize(maze, options={})
super

Expand All @@ -23,7 +32,9 @@ def initialize(maze, options={})
@blob = canvas.to_blob
end

def draw_cell(canvas, point, up, x, y, cell)
private

def draw_cell(canvas, point, up, x, y, cell) #:nodoc:
return if cell == 0

p1 = [x + options[:cell_size] / 2.0, up ? (y + options[:cell_padding]) : (y + options[:cell_size] - options[:cell_padding])]
Expand Down
16 changes: 13 additions & 3 deletions lib/theseus/formatters/png/orthogonal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
module Theseus
module Formatters
class PNG
# Renders an OrthogonalMaze to a PNG canvas.
#
# You will almost never access this class directly. Instead, use
# OrthogonalMaze#to(:png, options) to return the raw PNG data directly.
class Orthogonal < PNG
# Create and return a fully initialized PNG::Orthogonal object, with the
# maze rendered. To get the maze data, call #to_blob.
#
# See Theseus::Formatters::PNG for a list of all supported options.
def initialize(maze, options={})
super

Expand All @@ -28,7 +36,9 @@ def initialize(maze, options={})
@blob = canvas.to_blob
end

def draw_cell(canvas, point, x, y, cell)
private

def draw_cell(canvas, point, x, y, cell) #:nodoc:
return if cell == 0

fill_rect(canvas, x + @d1, y + @d1, x + @d2, y + @d2, color_at(point))
Expand All @@ -48,7 +58,7 @@ def draw_cell(canvas, point, x, y, cell)
draw_horizontal(canvas, x + options[:cell_size], y, -1, east || east_under, !east || east_under, color_at(point, ANY_E))
end

def draw_vertical(canvas, x, y, direction, corridor, wall, color)
def draw_vertical(canvas, x, y, direction, corridor, wall, color) #:nodoc:
if corridor
fill_rect(canvas, x + @d1, y, x + @d2, y + @d1 * direction, color)
fill_rect(canvas, x + @d1 - @w1, y - (@w1 * direction), x + @d1 + @w2, y + (@d1 + @w2) * direction, options[:wall_color])
Expand All @@ -60,7 +70,7 @@ def draw_vertical(canvas, x, y, direction, corridor, wall, color)
end
end

def draw_horizontal(canvas, x, y, direction, corridor, wall, color)
def draw_horizontal(canvas, x, y, direction, corridor, wall, color) #:nodoc:
if corridor
fill_rect(canvas, x, y + @d1, x + @d1 * direction, y + @d2, color)
fill_rect(canvas, x - (@w1 * direction), y + @d1 - @w1, x + (@d1 + @w2) * direction, y + @d1 + @w2, options[:wall_color])
Expand Down
13 changes: 12 additions & 1 deletion lib/theseus/formatters/png/sigma.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
module Theseus
module Formatters
class PNG
# Renders a SigmaMaze to a PNG canvas. Does not currently support the
# +:wall_width+ option.
#
# You will almost never access this class directly. Instead, use
# SigmaMaze#to(:png, options) to return the raw PNG data directly.
class Sigma < PNG
# Create and return a fully initialized PNG::Sigma object, with the
# maze rendered. To get the maze data, call #to_blob.
#
# See Theseus::Formatters::PNG for a list of all supported options.
def initialize(maze, options={})
super

Expand All @@ -25,7 +34,9 @@ def initialize(maze, options={})
@blob = canvas.to_blob
end

def draw_cell(canvas, point, shifted, x, y, cell)
private

def draw_cell(canvas, point, shifted, x, y, cell) #:nodoc:
return if cell == 0

size = options[:cell_size] - options[:cell_padding] * 2
Expand Down
15 changes: 13 additions & 2 deletions lib/theseus/formatters/png/upsilon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
module Theseus
module Formatters
class PNG
# Renders a UpsilonMaze to a PNG canvas. Does not currently support the
# +:wall_width+ option.
#
# You will almost never access this class directly. Instead, use
# UpsilonMaze#to(:png, options) to return the raw PNG data directly.
class Upsilon < PNG
# Create and return a fully initialized PNG::Upsilon object, with the
# maze rendered. To get the maze data, call #to_blob.
#
# See Theseus::Formatters::PNG for a list of all supported options.
def initialize(maze, options={})
super

Expand Down Expand Up @@ -35,7 +44,9 @@ def initialize(maze, options={})
@blob = canvas.to_blob
end

def draw_octogon_cell(canvas, point, x, y, cell, metrics)
private

def draw_octogon_cell(canvas, point, x, y, cell, metrics) #:nodoc:
p1 = [x + options[:cell_padding] + metrics[:s4], y + options[:cell_padding]]
p2 = [x + options[:cell_size] - options[:cell_padding] - metrics[:s4], p1[1]]
p3 = [x + options[:cell_size] - options[:cell_padding], y + options[:cell_padding] + metrics[:s4]]
Expand Down Expand Up @@ -90,7 +101,7 @@ def draw_octogon_cell(canvas, point, x, y, cell, metrics)
line(canvas, p8, p1, options[:wall_color]) if cell & Maze::NW == 0
end

def draw_square_cell(canvas, point, x, y, cell, metrics)
def draw_square_cell(canvas, point, x, y, cell, metrics) #:nodoc:
v = options[:cell_padding] + metrics[:s4]
p1 = [x + v, y + v]
p2 = [x + options[:cell_size] - v, y + options[:cell_size] - v]
Expand Down
Loading

0 comments on commit d417a0d

Please sign in to comment.