module ChunkyPNG::Canvas::Drawing

Module that adds some primitive drawing methods to {ChunkyPNG::Canvas}.

All of these methods change the current canvas instance and do not create a new one, even though the method names do not end with a bang.

@note Drawing operations will not fail when something is drawn outside of

the bounds of the canvas; these pixels will simply be ignored.

@see ChunkyPNG::Canvas

Public Instance Methods

bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK) click to toggle source

Draws a Bezier curve @param [Array, Point] A collection of control points @return [Chunky:PNG::Canvas] Itself, with the curve drawn

   # File lib/chunky_png/canvas/drawing.rb
38 def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK)
39   points = ChunkyPNG::Vector(*points)
40   case points.length
41     when 0, 1; return self
42     when 2; return line(points[0].x, points[0].y, points[1].x, points[1].y, stroke_color)
43   end
44 
45   curve_points = Array.new
46 
47   t     = 0
48   n     = points.length - 1
49   bicof = 0
50 
51   while t <= 100
52     cur_p = ChunkyPNG::Point.new(0,0)
53 
54     # Generate a float of t.
55     t_f = t / 100.00
56 
57     cur_p.x += ((1 - t_f) ** n) * points[0].x
58     cur_p.y += ((1 - t_f) ** n) * points[0].y
59 
60     for i in 1...points.length - 1
61       bicof = binomial_coefficient(n , i)
62 
63       cur_p.x += (bicof * (1 - t_f) ** (n - i)) *  (t_f ** i) * points[i].x
64       cur_p.y += (bicof * (1 - t_f) ** (n - i)) *  (t_f ** i) * points[i].y
65       i += 1
66     end
67 
68     cur_p.x += (t_f ** n) * points[n].x
69     cur_p.y += (t_f ** n) * points[n].y
70 
71     curve_points << cur_p
72 
73     bicof = 0
74     t += 1
75   end
76 
77   curve_points.each_cons(2) do |p1, p2|
78     line_xiaolin_wu(p1.x.round, p1.y.round, p2.x.round, p2.y.round, stroke_color)
79   end
80 
81   self
82 end
circle(x0, y0, radius, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) click to toggle source

Draws a circle on the canvas.

@param [Integer] x0 The x-coordinate of the center of the circle. @param [Integer] y0 The y-coordinate of the center of the circle. @param [Integer] radius The radius of the circle from the center point. @param [Integer] stroke_color The color to use for the line. @param [Integer] fill_color The color to use that fills the circle. @return [ChunkyPNG::Canvas] Itself, with the circle drawn.

    # File lib/chunky_png/canvas/drawing.rb
251 def circle(x0, y0, radius,
252            stroke_color = ChunkyPNG::Color::BLACK,
253            fill_color   = ChunkyPNG::Color::TRANSPARENT)
254 
255   stroke_color = ChunkyPNG::Color.parse(stroke_color)
256   fill_color   = ChunkyPNG::Color.parse(fill_color)
257 
258   f = 1 - radius
259   ddF_x = 1
260   ddF_y = -2 * radius
261   x = 0
262   y = radius
263 
264   compose_pixel(x0, y0 + radius, stroke_color)
265   compose_pixel(x0, y0 - radius, stroke_color)
266   compose_pixel(x0 + radius, y0, stroke_color)
267   compose_pixel(x0 - radius, y0, stroke_color)
268 
269   lines = [radius - 1] unless fill_color == ChunkyPNG::Color::TRANSPARENT
270 
271   while x < y
272 
273     if f >= 0
274       y -= 1
275       ddF_y += 2
276       f += ddF_y
277     end
278 
279     x += 1
280     ddF_x += 2
281     f += ddF_x
282 
283     unless fill_color == ChunkyPNG::Color::TRANSPARENT
284       lines[y] = lines[y] ? [lines[y], x - 1].min : x - 1
285       lines[x] = lines[x] ? [lines[x], y - 1].min : y - 1
286     end
287 
288     compose_pixel(x0 + x, y0 + y, stroke_color)
289     compose_pixel(x0 - x, y0 + y, stroke_color)
290     compose_pixel(x0 + x, y0 - y, stroke_color)
291     compose_pixel(x0 - x, y0 - y, stroke_color)
292 
293     unless x == y
294       compose_pixel(x0 + y, y0 + x, stroke_color)
295       compose_pixel(x0 - y, y0 + x, stroke_color)
296       compose_pixel(x0 + y, y0 - x, stroke_color)
297       compose_pixel(x0 - y, y0 - x, stroke_color)
298     end
299   end
300 
301   unless fill_color == ChunkyPNG::Color::TRANSPARENT
302     lines.each_with_index do |length, y_offset|
303       if length > 0
304         line(x0 - length, y0 - y_offset, x0 + length, y0 - y_offset, fill_color)
305       end
306       if length > 0 && y_offset > 0
307         line(x0 - length, y0 + y_offset, x0 + length, y0 + y_offset, fill_color)
308       end
309     end
310   end
311 
312   self
313 end
compose_pixel(x, y, color) click to toggle source

Composes a pixel on the canvas by alpha blending a color with its background color.

@param [Integer] x The x-coordinate of the pixel to blend. @param [Integer] y The y-coordinate of the pixel to blend. @param [Integer] color The foreground color to blend with @return [Integer] The composed color.

   # File lib/chunky_png/canvas/drawing.rb
21 def compose_pixel(x, y, color)
22   return unless include_xy?(x, y)
23   compose_pixel_unsafe(x, y, ChunkyPNG::Color.parse(color))
24 end
compose_pixel_unsafe(x, y, color) click to toggle source

Composes a pixel on the canvas by alpha blending a color with its background color, without bounds checking.

@param (see compose_pixel) @return [Integer] The composed color.

   # File lib/chunky_png/canvas/drawing.rb
31 def compose_pixel_unsafe(x, y, color)
32   set_pixel(x, y, ChunkyPNG::Color.compose(color, get_pixel(x, y)))
33 end
line(x0, y0, x1, y1, stroke_color, inclusive = true)
Alias for: line_xiaolin_wu
line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true) click to toggle source

Draws an anti-aliased line using Xiaolin Wu's algorithm.

@param [Integer] x0 The x-coordinate of the first control point. @param [Integer] y0 The y-coordinate of the first control point. @param [Integer] x1 The x-coordinate of the second control point. @param [Integer] y1 The y-coordinate of the second control point. @param [Integer] stroke_color The color to use for this line. @param [true, false] inclusive Whether to draw the last pixel. Set to

false when drawing multiple lines in a path.

@return [ChunkyPNG::Canvas] Itself, with the line drawn.

    # File lib/chunky_png/canvas/drawing.rb
 94 def line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true)
 95   stroke_color = ChunkyPNG::Color.parse(stroke_color)
 96 
 97   dx = x1 - x0
 98   sx = dx < 0 ? -1 : 1
 99   dx *= sx
100   dy = y1 - y0
101   sy = dy < 0 ? -1 : 1
102   dy *= sy
103 
104   if dy == 0 # vertical line
105     x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
106       compose_pixel(x, y0, stroke_color)
107     end
108 
109   elsif dx == 0 # horizontal line
110     y0.step(inclusive ? y1 : y1 - sy, sy) do |y|
111       compose_pixel(x0, y, stroke_color)
112     end
113 
114   elsif dx == dy # diagonal
115     x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
116       compose_pixel(x, y0, stroke_color)
117       y0 += sy
118     end
119 
120   elsif dy > dx  # vertical displacement
121     compose_pixel(x0, y0, stroke_color)
122     e_acc = 0
123     e = ((dx << 16) / dy.to_f).round
124     (dy - 1).downto(0) do |i|
125       e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
126       x0 += sx if e_acc <= e_acc_temp
127       w = 0xff - (e_acc >> 8)
128       compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
129       if inclusive || i > 0
130         compose_pixel(x0 + sx,
131                       y0 + sy,
132                       ChunkyPNG::Color.fade(stroke_color, 0xff - w))
133       end
134       y0 += sy
135     end
136     compose_pixel(x1, y1, stroke_color) if inclusive
137 
138   else # horizontal displacement
139     compose_pixel(x0, y0, stroke_color)
140     e_acc = 0
141     e = ((dy << 16) / dx.to_f).round
142     (dx - 1).downto(0) do |i|
143       e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
144       y0 += sy if e_acc <= e_acc_temp
145       w = 0xff - (e_acc >> 8)
146       compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
147       if inclusive || i > 0
148         compose_pixel(x0 + sx,
149                       y0 + sy,
150                       ChunkyPNG::Color.fade(stroke_color, 0xff - w))
151       end
152       x0 += sx
153     end
154     compose_pixel(x1, y1, stroke_color) if inclusive
155   end
156 
157   self
158 end
Also aliased as: line
polygon(path, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) click to toggle source

Draws a polygon on the canvas using the stroke_color, filled using the fill_color if any.

@param [Array, String] The control point vector. Accepts everything

{ChunkyPNG.Vector} accepts.

@param [Integer] stroke_color The stroke color to use for this polygon. @param [Integer] fill_color The fill color to use for this polygon. @return [ChunkyPNG::Canvas] Itself, with the polygon drawn.

    # File lib/chunky_png/canvas/drawing.rb
170 def polygon(path,
171             stroke_color = ChunkyPNG::Color::BLACK,
172             fill_color   = ChunkyPNG::Color::TRANSPARENT)
173 
174   vector = ChunkyPNG::Vector(*path)
175   if path.length < 3
176     raise ArgumentError, 'A polygon requires at least 3 points'
177   end
178 
179   stroke_color = ChunkyPNG::Color.parse(stroke_color)
180   fill_color   = ChunkyPNG::Color.parse(fill_color)
181 
182   # Fill
183   unless fill_color == ChunkyPNG::Color::TRANSPARENT
184     vector.y_range.each do |y|
185       intersections = []
186       vector.edges.each do |p1, p2|
187         if (p1.y < y && p2.y >= y) || (p2.y < y && p1.y >= y)
188           intersections << (p1.x + (y - p1.y).to_f / (p2.y - p1.y) * (p2.x - p1.x)).round
189         end
190       end
191 
192       intersections.sort!
193       0.step(intersections.length - 1, 2) do |i|
194         intersections[i].upto(intersections[i + 1]) do |x|
195           compose_pixel(x, y, fill_color)
196         end
197       end
198     end
199   end
200 
201   # Stroke
202   vector.each_edge do |(from_x, from_y), (to_x, to_y)|
203     line(from_x, from_y, to_x, to_y, stroke_color, false)
204   end
205 
206   self
207 end
rect(x0, y0, x1, y1, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) click to toggle source

Draws a rectangle on the canvas, using two control points.

@param [Integer] x0 The x-coordinate of the first control point. @param [Integer] y0 The y-coordinate of the first control point. @param [Integer] x1 The x-coordinate of the second control point. @param [Integer] y1 The y-coordinate of the second control point. @param [Integer] stroke_color The line color to use for this rectangle. @param [Integer] fill_color The fill color to use for this rectangle. @return [ChunkyPNG::Canvas] Itself, with the rectangle drawn.

    # File lib/chunky_png/canvas/drawing.rb
218 def rect(x0, y0, x1, y1,
219          stroke_color = ChunkyPNG::Color::BLACK,
220          fill_color   = ChunkyPNG::Color::TRANSPARENT)
221 
222   stroke_color = ChunkyPNG::Color.parse(stroke_color)
223   fill_color   = ChunkyPNG::Color.parse(fill_color)
224 
225   # Fill
226   unless fill_color == ChunkyPNG::Color::TRANSPARENT
227     [x0, x1].min.upto([x0, x1].max) do |x|
228       [y0, y1].min.upto([y0, y1].max) do |y|
229         compose_pixel(x, y, fill_color)
230       end
231     end
232   end
233 
234   # Stroke
235   line(x0, y0, x0, y1, stroke_color, false)
236   line(x0, y1, x1, y1, stroke_color, false)
237   line(x1, y1, x1, y0, stroke_color, false)
238   line(x1, y0, x0, y0, stroke_color, false)
239 
240   self
241 end

Private Instance Methods

binomial_coefficient(n, k) click to toggle source

Calculates the binomial coefficient for n over k.

@param [Integer] n first parameter in coeffient (the number on top when

looking at the mathematic formula)

@param [Integer] k k-element, second parameter in coeffient (the number

on the bottom when looking at the mathematic formula)

@return [Integer] The binomial coeffcient of (n,k)

    # File lib/chunky_png/canvas/drawing.rb
324 def binomial_coefficient(n, k)
325   return  1 if n == k || k == 0
326   return  n if k == 1
327   return -1 if n < k
328 
329   # calculate factorials
330   fact_n = (2..n).inject(1) { |carry, i| carry * i }
331   fact_k = (2..k).inject(1) { |carry, i| carry * i }
332   fact_n_sub_k = (2..(n - k)).inject(1) { |carry, i| carry * i }
333 
334   fact_n / (fact_k * fact_n_sub_k)
335 end