Module: ChunkyPNG::Canvas::Drawing

Included in:
ChunkyPNG::Canvas
Defined in:
lib/chunky_png/canvas/drawing.rb

Overview

Note:

Drawing operations will not fail when something is drawn outside of the bounds of the canvas; these pixels will simply be ignored.

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.

See Also:

Instance Method Summary collapse

Instance Method Details

#bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK) ⇒ Chunky:PNG::Canvas

Draws a Bezier curve

Parameters:

  • A (Array, Point)

    collection of control points

Returns:

  • (Chunky:PNG::Canvas)

    Itself, with the curve drawn



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/chunky_png/canvas/drawing.rb', line 35

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

  return self
end

#circle(x0, y0, radius, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) ⇒ ChunkyPNG::Canvas

Draws a circle on the canvas.

Parameters:

  • x0 (Integer)

    The x-coordinate of the center of the circle.

  • y0 (Integer)

    The y-coordinate of the center of the circle.

  • radius (Integer)

    The radius of the circle from the center point.

  • stroke_color (Integer) (defaults to: ChunkyPNG::Color::BLACK)

    The color to use for the line.

  • fill_color (Integer) (defaults to: ChunkyPNG::Color::TRANSPARENT)

    The color to use that fills the circle.

Returns:



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/chunky_png/canvas/drawing.rb', line 236

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|
      line(x0 - length, y0 - y, x0 + length, y0 - y, fill_color) if length > 0
      line(x0 - length, y0 + y, x0 + length, y0 + y, fill_color) if length > 0 && y > 0
    end
  end

  return self
end

#compose_pixel(x, y, color) ⇒ Integer

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

Parameters:

  • x (Integer)

    The x-coordinate of the pixel to blend.

  • y (Integer)

    The y-coordinate of the pixel to blend.

  • color (Integer)

    The foreground color to blend with

Returns:

  • (Integer)

    The composed color.



19
20
21
22
# File 'lib/chunky_png/canvas/drawing.rb', line 19

def compose_pixel(x, y, color)
  return unless include_xy?(x, y)
  compose_pixel_unsafe(x, y, ChunkyPNG::Color.parse(color))
end

#compose_pixel_unsafe(x, y, color) ⇒ Integer

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

Parameters:

  • x (Integer)

    The x-coordinate of the pixel to blend.

  • y (Integer)

    The y-coordinate of the pixel to blend.

  • color (Integer)

    The foreground color to blend with

Returns:

  • (Integer)

    The composed color.



28
29
30
# File 'lib/chunky_png/canvas/drawing.rb', line 28

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

#line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true) ⇒ ChunkyPNG::Canvas Also known as: line

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

Parameters:

  • x0 (Integer)

    The x-coordinate of the first control point.

  • y0 (Integer)

    The y-coordinate of the first control point.

  • x1 (Integer)

    The x-coordinate of the second control point.

  • y1 (Integer)

    The y-coordinate of the second control point.

  • stroke_color (Integer)

    The color to use for this line.

  • inclusive (true, false) (defaults to: true)

    Whether to draw the last pixel. Set to false when drawing multiple lines in a path.

Returns:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/chunky_png/canvas/drawing.rb', line 93

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))
      compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w)) if inclusive || i > 0
      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))
      compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w)) if inclusive || i > 0
      x0 += sx
    end
    compose_pixel(x1, y1, stroke_color) if inclusive
  end
  
  return self
end

#polygon(path, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) ⇒ ChunkyPNG::Canvas

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

Parameters:

  • The (Array, String)

    control point vector. Accepts everything Vector accepts.

  • stroke_color (Integer) (defaults to: ChunkyPNG::Color::BLACK)

    The stroke color to use for this polygon.

  • fill_color (Integer) (defaults to: ChunkyPNG::Color::TRANSPARENT)

    The fill color to use for this polygon.

Returns:

Raises:

  • (ArgumentError)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/chunky_png/canvas/drawing.rb', line 161

def polygon(path, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT)
  
  vector = ChunkyPNG::Vector(*path)
  raise ArgumentError, "A polygon requires at least 3 points" if path.length < 3

  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

  return self
end

#rect(x0, y0, x1, y1, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT) ⇒ ChunkyPNG::Canvas

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

Parameters:

  • x0 (Integer)

    The x-coordinate of the first control point.

  • y0 (Integer)

    The y-coordinate of the first control point.

  • x1 (Integer)

    The x-coordinate of the second control point.

  • y1 (Integer)

    The y-coordinate of the second control point.

  • stroke_color (Integer) (defaults to: ChunkyPNG::Color::BLACK)

    The line color to use for this rectangle.

  • fill_color (Integer) (defaults to: ChunkyPNG::Color::TRANSPARENT)

    The fill color to use for this rectangle.

Returns:



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/chunky_png/canvas/drawing.rb', line 205

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)
  
  return self
end