module ChunkyPNG::Canvas::Drawing

def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK)

Returns:
  • (Chunky:PNG::Canvas) - Itself, with the curve drawn

Parameters:
  • A (Array, Point) -- collection of control points
def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK)
  points = ChunkyPNG::Vector(*points)
  case points.length
    when 0, 1; return self
    when 2; return line(points[0].x, points[0].y, points[1].x, points[1].y, stroke_color)
  end
  curve_points = Array.new
  t     = 0
  n     = points.length - 1
  bicof = 0
  while t <= 100
    cur_p = ChunkyPNG::Point.new(0,0)
    # Generate a float of t.
    t_f = t / 100.00
    cur_p.x += ((1 - t_f) ** n) * points[0].x
    cur_p.y += ((1 - t_f) ** n) * points[0].y
    for i in 1...points.length - 1
      bicof = binomial_coefficient(n , i)
      cur_p.x += (bicof * (1 - t_f) ** (n - i)) *  (t_f ** i) * points[i].x
      cur_p.y += (bicof * (1 - t_f) ** (n - i)) *  (t_f ** i) * points[i].y
      i += 1
    end
    cur_p.x += (t_f ** n) * points[n].x
    cur_p.y += (t_f ** n) * points[n].y
    curve_points << cur_p
    bicof = 0
    t += 1
  end
  curve_points.each_cons(2) do |p1, p2|
    line_xiaolin_wu(p1.x.round, p1.y.round, p2.x.round, p2.y.round, stroke_color)
  end
  self
end

def binomial_coefficient(n, k)

Returns:
  • (Integer) - The binomial coeffcient of (n,k)

Parameters:
  • k (Integer) -- k-element, second parameter in coeffient (the number
  • n (Integer) -- first parameter in coeffient (the number on top when
def binomial_coefficient(n, k)
  return  1 if n == k || k == 0
  return  n if k == 1
  return -1 if n < k
  # calculate factorials
  fact_n = (2..n).inject(1) { |carry, i| carry * i }
  fact_k = (2..k).inject(1) { |carry, i| carry * i }
  fact_n_sub_k = (2..(n - k)).inject(1) { |carry, i| carry * i }
  fact_n / (fact_k * fact_n_sub_k)
end

def circle(x0, y0, radius,

Returns:
  • (ChunkyPNG::Canvas) - Itself, with the circle drawn.

Parameters:
  • fill_color (Integer) -- The color to use that fills the circle.
  • stroke_color (Integer) -- The color to use for the line.
  • radius (Integer) -- The radius of the circle from the center point.
  • y0 (Integer) -- The y-coordinate of the center of the circle.
  • x0 (Integer) -- The x-coordinate of the center of the circle.
def circle(x0, y0, radius,
           stroke_color = ChunkyPNG::Color::BLACK,
           fill_color   = ChunkyPNG::Color::TRANSPARENT)
  stroke_color = ChunkyPNG::Color.parse(stroke_color)
  fill_color   = ChunkyPNG::Color.parse(fill_color)
  f = 1 - radius
  ddF_x = 1
  ddF_y = -2 * radius
  x = 0
  y = radius
  compose_pixel(x0, y0 + radius, stroke_color)
  compose_pixel(x0, y0 - radius, stroke_color)
  compose_pixel(x0 + radius, y0, stroke_color)
  compose_pixel(x0 - radius, y0, stroke_color)
  lines = [radius - 1] unless fill_color == ChunkyPNG::Color::TRANSPARENT
  while x < y
    if f >= 0
      y -= 1
      ddF_y += 2
      f += ddF_y
    end
    x += 1
    ddF_x += 2
    f += ddF_x
    unless fill_color == ChunkyPNG::Color::TRANSPARENT
      lines[y] = lines[y] ? [lines[y], x - 1].min : x - 1
      lines[x] = lines[x] ? [lines[x], y - 1].min : y - 1
    end
    compose_pixel(x0 + x, y0 + y, stroke_color)
    compose_pixel(x0 - x, y0 + y, stroke_color)
    compose_pixel(x0 + x, y0 - y, stroke_color)
    compose_pixel(x0 - x, y0 - y, stroke_color)
    unless x == y
      compose_pixel(x0 + y, y0 + x, stroke_color)
      compose_pixel(x0 - y, y0 + x, stroke_color)
      compose_pixel(x0 + y, y0 - x, stroke_color)
      compose_pixel(x0 - y, y0 - x, stroke_color)
    end
  end
  unless fill_color == ChunkyPNG::Color::TRANSPARENT
    lines.each_with_index do |length, y|
      if length > 0
        line(x0 - length, y0 - y, x0 + length, y0 - y, fill_color)
      end
      if length > 0 && y > 0
        line(x0 - length, y0 + y, x0 + length, y0 + y, fill_color)
      end
    end
  end
  self
end

def compose_pixel(x, y, color)

Returns:
  • (Integer) - The composed color.

Parameters:
  • color (Integer) -- The foreground color to blend with
  • y (Integer) -- The y-coordinate of the pixel to blend.
  • x (Integer) -- The x-coordinate of the pixel to blend.
def compose_pixel(x, y, color)
  return unless include_xy?(x, y)
  compose_pixel_unsafe(x, y, ChunkyPNG::Color.parse(color))
end

def compose_pixel_unsafe(x, y, color)

Returns:
  • (Integer) - The composed color.

Parameters:
  • () --
def compose_pixel_unsafe(x, y, color)
  set_pixel(x, y, ChunkyPNG::Color.compose(color, get_pixel(x, y)))
end

def line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true)

Returns:
  • (ChunkyPNG::Canvas) - Itself, with the line drawn.

Parameters:
  • inclusive (true, false) -- Whether to draw the last pixel. Set to
  • stroke_color (Integer) -- The color to use for this line.
  • y1 (Integer) -- The y-coordinate of the second control point.
  • x1 (Integer) -- The x-coordinate of the second control point.
  • y0 (Integer) -- The y-coordinate of the first control point.
  • x0 (Integer) -- The x-coordinate of the first control point.
def line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true)
  stroke_color = ChunkyPNG::Color.parse(stroke_color)
  dx = x1 - x0
  sx = dx < 0 ? -1 : 1
  dx *= sx
  dy = y1 - y0
  sy = dy < 0 ? -1 : 1
  dy *= sy
  if dy == 0 # vertical line
    x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
      compose_pixel(x, y0, stroke_color)
    end
  elsif dx == 0 # horizontal line
    y0.step(inclusive ? y1 : y1 - sy, sy) do |y|
      compose_pixel(x0, y, stroke_color)
    end
  elsif dx == dy # diagonal
    x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
      compose_pixel(x, y0, stroke_color)
      y0 += sy
    end
  elsif dy > dx  # vertical displacement
    compose_pixel(x0, y0, stroke_color)
    e_acc = 0
    e = ((dx << 16) / dy.to_f).round
    (dy - 1).downto(0) do |i|
      e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
      x0 += sx if e_acc <= e_acc_temp
      w = 0xff - (e_acc >> 8)
      compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
      if inclusive || i > 0
        compose_pixel(x0 + sx,
                      y0 + sy,
                      ChunkyPNG::Color.fade(stroke_color, 0xff - w))
      end
      y0 += sy
    end
    compose_pixel(x1, y1, stroke_color) if inclusive
  else # horizontal displacement
    compose_pixel(x0, y0, stroke_color)
    e_acc = 0
    e = ((dy << 16) / dx.to_f).round
    (dx - 1).downto(0) do |i|
      e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
      y0 += sy if e_acc <= e_acc_temp
      w = 0xff - (e_acc >> 8)
      compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
      if inclusive || i > 0
        compose_pixel(x0 + sx,
                      y0 + sy,
                      ChunkyPNG::Color.fade(stroke_color, 0xff - w))
      end
      x0 += sx
    end
    compose_pixel(x1, y1, stroke_color) if inclusive
  end
  self
end

def polygon(path,

Returns:
  • (ChunkyPNG::Canvas) - Itself, with the polygon drawn.

Parameters:
  • fill_color (Integer) -- The fill color to use for this polygon.
  • stroke_color (Integer) -- The stroke color to use for this polygon.
  • The (Array, String) -- control point vector. Accepts everything
def polygon(path,
            stroke_color = ChunkyPNG::Color::BLACK,
            fill_color   = ChunkyPNG::Color::TRANSPARENT)
  vector = ChunkyPNG::Vector(*path)
  if path.length < 3
    raise ArgumentError, 'A polygon requires at least 3 points'
  end
  stroke_color = ChunkyPNG::Color.parse(stroke_color)
  fill_color   = ChunkyPNG::Color.parse(fill_color)
  # Fill
  unless fill_color == ChunkyPNG::Color::TRANSPARENT
    vector.y_range.each do |y|
      intersections = []
      vector.edges.each do |p1, p2|
        if (p1.y < y && p2.y >= y) || (p2.y < y && p1.y >= y)
          intersections << (p1.x + (y - p1.y).to_f / (p2.y - p1.y) * (p2.x - p1.x)).round
        end
      end
      intersections.sort!
      0.step(intersections.length - 1, 2) do |i|
        intersections[i].upto(intersections[i + 1]) do |x|
          compose_pixel(x, y, fill_color)
        end
      end
    end
  end
  # Stroke
  vector.each_edge do |(from_x, from_y), (to_x, to_y)|
    line(from_x, from_y, to_x, to_y, stroke_color, false)
  end
  self
end

def rect(x0, y0, x1, y1,

Returns:
  • (ChunkyPNG::Canvas) - Itself, with the rectangle drawn.

Parameters:
  • fill_color (Integer) -- The fill color to use for this rectangle.
  • stroke_color (Integer) -- The line color to use for this rectangle.
  • y1 (Integer) -- The y-coordinate of the second control point.
  • x1 (Integer) -- The x-coordinate of the second control point.
  • y0 (Integer) -- The y-coordinate of the first control point.
  • x0 (Integer) -- The x-coordinate of the first control point.
def rect(x0, y0, x1, y1,
         stroke_color = ChunkyPNG::Color::BLACK,
         fill_color   = ChunkyPNG::Color::TRANSPARENT)
  stroke_color = ChunkyPNG::Color.parse(stroke_color)
  fill_color   = ChunkyPNG::Color.parse(fill_color)
  # Fill
  unless fill_color == ChunkyPNG::Color::TRANSPARENT
    [x0, x1].min.upto([x0, x1].max) do |x|
      [y0, y1].min.upto([y0, y1].max) do |y|
        compose_pixel(x, y, fill_color)
      end
    end
  end
  # Stroke
  line(x0, y0, x0, y1, stroke_color, false)
  line(x0, y1, x1, y1, stroke_color, false)
  line(x1, y1, x1, y0, stroke_color, false)
  line(x1, y0, x0, y0, stroke_color, false)
  self
end