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] points A collection of control points @param [Integer] stroke_color @return [Chunky:PNG::Canvas] Itself, with the curve drawn

   # File lib/chunky_png/canvas/drawing.rb
37 def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK)
38   points = ChunkyPNG::Vector(*points)
39   case points.length
40     when 0, 1 then return self
41     when 2 then return line(points[0].x, points[0].y, points[1].x, points[1].y, stroke_color)
42   end
43 
44   curve_points = []
45 
46   t     = 0
47   n     = points.length - 1
48 
49   while t <= 100
50     bicof = 0
51     cur_p = ChunkyPNG::Point.new(0, 0)
52 
53     # Generate a float of t.
54     t_f = t / 100.00
55 
56     cur_p.x += ((1 - t_f)**n) * points[0].x
57     cur_p.y += ((1 - t_f)**n) * points[0].y
58 
59     for i in 1...points.length - 1
60       bicof = binomial_coefficient(n, i)
61 
62       cur_p.x += (bicof * (1 - t_f)**(n - i)) * (t_f**i) * points[i].x
63       cur_p.y += (bicof * (1 - t_f)**(n - i)) * (t_f**i) * points[i].y
64       i += 1
65     end
66 
67     cur_p.x += (t_f**n) * points[n].x
68     cur_p.y += (t_f**n) * points[n].y
69 
70     curve_points << cur_p
71 
72     t += 1
73   end
74 
75   curve_points.each_cons(2) do |p1, p2|
76     line_xiaolin_wu(p1.x.round, p1.y.round, p2.x.round, p2.y.round, stroke_color)
77   end
78 
79   self
80 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
239 def circle(x0, y0, radius, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT)
240   stroke_color = ChunkyPNG::Color.parse(stroke_color)
241   fill_color   = ChunkyPNG::Color.parse(fill_color)
242 
243   f = 1 - radius
244   dd_f_x = 1
245   dd_f_y = -2 * radius
246   x = 0
247   y = radius
248 
249   compose_pixel(x0, y0 + radius, stroke_color)
250   compose_pixel(x0, y0 - radius, stroke_color)
251   compose_pixel(x0 + radius, y0, stroke_color)
252   compose_pixel(x0 - radius, y0, stroke_color)
253 
254   lines = [radius - 1] unless fill_color == ChunkyPNG::Color::TRANSPARENT
255 
256   while x < y
257 
258     if f >= 0
259       y -= 1
260       dd_f_y += 2
261       f += dd_f_y
262     end
263 
264     x += 1
265     dd_f_x += 2
266     f += dd_f_x
267 
268     unless fill_color == ChunkyPNG::Color::TRANSPARENT
269       lines[y] = lines[y] ? [lines[y], x - 1].min : x - 1
270       lines[x] = lines[x] ? [lines[x], y - 1].min : y - 1
271     end
272 
273     compose_pixel(x0 + x, y0 + y, stroke_color)
274     compose_pixel(x0 - x, y0 + y, stroke_color)
275     compose_pixel(x0 + x, y0 - y, stroke_color)
276     compose_pixel(x0 - x, y0 - y, stroke_color)
277 
278     unless x == y
279       compose_pixel(x0 + y, y0 + x, stroke_color)
280       compose_pixel(x0 - y, y0 + x, stroke_color)
281       compose_pixel(x0 + y, y0 - x, stroke_color)
282       compose_pixel(x0 - y, y0 - x, stroke_color)
283     end
284   end
285 
286   unless fill_color == ChunkyPNG::Color::TRANSPARENT
287     lines.each_with_index do |length, y_offset|
288       if length > 0
289         line(x0 - length, y0 - y_offset, x0 + length, y0 - y_offset, fill_color)
290       end
291       if length > 0 && y_offset > 0
292         line(x0 - length, y0 + y_offset, x0 + length, y0 + y_offset, fill_color)
293       end
294     end
295   end
296 
297   self
298 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
19 def compose_pixel(x, y, color)
20   return unless include_xy?(x, y)
21   compose_pixel_unsafe(x, y, ChunkyPNG::Color.parse(color))
22 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
29 def compose_pixel_unsafe(x, y, color)
30   set_pixel(x, y, ChunkyPNG::Color.compose(color, get_pixel(x, y)))
31 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
 92 def line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true)
 93   stroke_color = ChunkyPNG::Color.parse(stroke_color)
 94 
 95   dx = x1 - x0
 96   sx = dx < 0 ? -1 : 1
 97   dx *= sx
 98   dy = y1 - y0
 99   sy = dy < 0 ? -1 : 1
100   dy *= sy
101 
102   if dy == 0 # vertical line
103     x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
104       compose_pixel(x, y0, stroke_color)
105     end
106 
107   elsif dx == 0 # horizontal line
108     y0.step(inclusive ? y1 : y1 - sy, sy) do |y|
109       compose_pixel(x0, y, stroke_color)
110     end
111 
112   elsif dx == dy # diagonal
113     x0.step(inclusive ? x1 : x1 - sx, sx) do |x|
114       compose_pixel(x, y0, stroke_color)
115       y0 += sy
116     end
117 
118   elsif dy > dx  # vertical displacement
119     compose_pixel(x0, y0, stroke_color)
120     e_acc = 0
121     e = ((dx << 16) / dy.to_f).round
122     (dy - 1).downto(0) do |i|
123       e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
124       x0 += sx if e_acc <= e_acc_temp
125       w = 0xff - (e_acc >> 8)
126       compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
127       if inclusive || i > 0
128         compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w))
129       end
130       y0 += sy
131     end
132     compose_pixel(x1, y1, stroke_color) if inclusive
133 
134   else # horizontal displacement
135     compose_pixel(x0, y0, stroke_color)
136     e_acc = 0
137     e = ((dy << 16) / dx.to_f).round
138     (dx - 1).downto(0) do |i|
139       e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xffff
140       y0 += sy if e_acc <= e_acc_temp
141       w = 0xff - (e_acc >> 8)
142       compose_pixel(x0, y0, ChunkyPNG::Color.fade(stroke_color, w))
143       if inclusive || i > 0
144         compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w))
145       end
146       x0 += sx
147     end
148     compose_pixel(x1, y1, stroke_color) if inclusive
149   end
150 
151   self
152 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] path 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
164 def polygon(path, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT)
165   vector = ChunkyPNG::Vector(*path)
166   if path.length < 3
167     raise ArgumentError, "A polygon requires at least 3 points"
168   end
169 
170   stroke_color = ChunkyPNG::Color.parse(stroke_color)
171   fill_color   = ChunkyPNG::Color.parse(fill_color)
172 
173   # Fill
174   unless fill_color == ChunkyPNG::Color::TRANSPARENT
175     vector.y_range.each do |y|
176       intersections = []
177       vector.edges.each do |p1, p2|
178         if (p1.y < y && p2.y >= y) || (p2.y < y && p1.y >= y)
179           intersections << (p1.x + (y - p1.y).to_f / (p2.y - p1.y) * (p2.x - p1.x)).round
180         end
181       end
182 
183       intersections.sort!
184       0.step(intersections.length - 1, 2) do |i|
185         intersections[i].upto(intersections[i + 1]) do |x|
186           compose_pixel(x, y, fill_color)
187         end
188       end
189     end
190   end
191 
192   # Stroke
193   vector.each_edge do |(from_x, from_y), (to_x, to_y)|
194     line(from_x, from_y, to_x, to_y, stroke_color, false)
195   end
196 
197   self
198 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
209 def rect(x0, y0, x1, y1, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT)
210   stroke_color = ChunkyPNG::Color.parse(stroke_color)
211   fill_color   = ChunkyPNG::Color.parse(fill_color)
212 
213   # Fill
214   unless fill_color == ChunkyPNG::Color::TRANSPARENT
215     [x0, x1].min.upto([x0, x1].max) do |x|
216       [y0, y1].min.upto([y0, y1].max) do |y|
217         compose_pixel(x, y, fill_color)
218       end
219     end
220   end
221 
222   # Stroke
223   line(x0, y0, x0, y1, stroke_color, false)
224   line(x0, y1, x1, y1, stroke_color, false)
225   line(x1, y1, x1, y0, stroke_color, false)
226   line(x1, y0, x0, y0, stroke_color, false)
227 
228   self
229 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
309 def binomial_coefficient(n, k)
310   return  1 if n == k || k == 0
311   return  n if k == 1
312   return -1 if n < k
313 
314   # calculate factorials
315   fact_n = (2..n).inject(1) { |carry, i| carry * i }
316   fact_k = (2..k).inject(1) { |carry, i| carry * i }
317   fact_n_sub_k = (2..(n - k)).inject(1) { |carry, i| carry * i }
318 
319   fact_n / (fact_k * fact_n_sub_k)
320 end