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
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
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
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
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
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
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
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
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