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:

  • points (Array, Point)

    A collection of control points

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

Returns:

  • (Chunky:PNG::Canvas)

    Itself, with the curve drawn



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
81
82
# File 'lib/chunky_png/canvas/drawing.rb', line 39

def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK)
  points = ChunkyPNG::Vector(*points)
  case points.length
    when 0, 1 then return self
    when 2 then return line(points[0].x, points[0].y, points[1].x, points[1].y, stroke_color)
  end

  curve_points = []

  t     = 0
  n     = points.length - 1

  while t <= 100
    bicof = 0
    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

    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

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



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
293
294
295
296
297
298
299
300
# File 'lib/chunky_png/canvas/drawing.rb', line 241

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
  dd_f_x = 1
  dd_f_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
      dd_f_y += 2
      f += dd_f_y
    end

    x += 1
    dd_f_x += 2
    f += dd_f_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_offset|
      if length > 0
        line(x0 - length, y0 - y_offset, x0 + length, y0 - y_offset, fill_color)
      end
      if length > 0 && y_offset > 0
        line(x0 - length, y0 + y_offset, x0 + length, y0 + y_offset, fill_color)
      end
    end
  end

  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.



21
22
23
24
# File 'lib/chunky_png/canvas/drawing.rb', line 21

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.



31
32
33
# File 'lib/chunky_png/canvas/drawing.rb', line 31

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:



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
151
152
153
154
# File 'lib/chunky_png/canvas/drawing.rb', line 94

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

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

  • path (Array, String)

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



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
195
196
197
198
199
200
# File 'lib/chunky_png/canvas/drawing.rb', line 166

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

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



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/chunky_png/canvas/drawing.rb', line 211

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