| Module | ChunkyPNG::Canvas::Drawing |
| In: |
lib/chunky_png/canvas/drawing.rb
|
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
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, line 35
35: def bezier_curve(points, stroke_color = ChunkyPNG::Color::BLACK)
36:
37: points = ChunkyPNG::Vector(*points)
38: case points.length
39: when 0, 1; return self
40: when 2; return line(points[0].x, points[0].y, points[1].x, points[1].y, stroke_color)
41: end
42:
43: curve_points = Array.new
44:
45: t = 0
46: n = points.length - 1
47: bicof = 0
48:
49: while t <= 100
50: cur_p = ChunkyPNG::Point.new(0,0)
51:
52: # Generate a float of t.
53: t_f = t / 100.00
54:
55: cur_p.x += ((1 - t_f) ** n) * points[0].x
56: cur_p.y += ((1 - t_f) ** n) * points[0].y
57:
58: for i in 1...points.length - 1
59: bicof = binomial_coefficient(n , i)
60:
61: cur_p.x += (bicof * (1 - t_f) ** (n - i)) * (t_f ** i) * points[i].x
62: cur_p.y += (bicof * (1 - t_f) ** (n - i)) * (t_f ** i) * points[i].y
63: i += 1
64: end
65:
66: cur_p.x += (t_f ** n) * points[n].x
67: cur_p.y += (t_f ** n) * points[n].y
68:
69: curve_points << cur_p
70:
71: bicof = 0
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: return self
80: 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, line 236
236: def circle(x0, y0, radius, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT)
237:
238: stroke_color = ChunkyPNG::Color.parse(stroke_color)
239: fill_color = ChunkyPNG::Color.parse(fill_color)
240:
241: f = 1 - radius
242: ddF_x = 1
243: ddF_y = -2 * radius
244: x = 0
245: y = radius
246:
247: compose_pixel(x0, y0 + radius, stroke_color)
248: compose_pixel(x0, y0 - radius, stroke_color)
249: compose_pixel(x0 + radius, y0, stroke_color)
250: compose_pixel(x0 - radius, y0, stroke_color)
251:
252: lines = [radius - 1] unless fill_color == ChunkyPNG::Color::TRANSPARENT
253:
254: while x < y
255:
256: if f >= 0
257: y -= 1
258: ddF_y += 2
259: f += ddF_y
260: end
261:
262: x += 1
263: ddF_x += 2
264: f += ddF_x
265:
266: unless fill_color == ChunkyPNG::Color::TRANSPARENT
267: lines[y] = lines[y] ? [lines[y], x - 1].min : x - 1
268: lines[x] = lines[x] ? [lines[x], y - 1].min : y - 1
269: end
270:
271: compose_pixel(x0 + x, y0 + y, stroke_color)
272: compose_pixel(x0 - x, y0 + y, stroke_color)
273: compose_pixel(x0 + x, y0 - y, stroke_color)
274: compose_pixel(x0 - x, y0 - y, stroke_color)
275:
276: unless x == y
277: compose_pixel(x0 + y, y0 + x, stroke_color)
278: compose_pixel(x0 - y, y0 + x, stroke_color)
279: compose_pixel(x0 + y, y0 - x, stroke_color)
280: compose_pixel(x0 - y, y0 - x, stroke_color)
281: end
282: end
283:
284: unless fill_color == ChunkyPNG::Color::TRANSPARENT
285: lines.each_with_index do |length, y|
286: line(x0 - length, y0 - y, x0 + length, y0 - y, fill_color) if length > 0
287: line(x0 - length, y0 + y, x0 + length, y0 + y, fill_color) if length > 0 && y > 0
288: end
289: end
290:
291: return self
292: 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, line 19
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
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, line 28
28: def compose_pixel_unsafe(x, y, color)
29: set_pixel(x, y, ChunkyPNG::Color.compose(color, get_pixel(x, y)))
30: 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, line 93
93: def line_xiaolin_wu(x0, y0, x1, y1, stroke_color, inclusive = true)
94:
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: compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w)) if inclusive || i > 0
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: compose_pixel(x0 + sx, y0 + sy, ChunkyPNG::Color.fade(stroke_color, 0xff - w)) if inclusive || i > 0
144: x0 += sx
145: end
146: compose_pixel(x1, y1, stroke_color) if inclusive
147: end
148:
149: return self
150: 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, line 161
161: def polygon(path, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT)
162:
163: vector = ChunkyPNG::Vector(*path)
164: raise ArgumentError, "A polygon requires at least 3 points" if path.length < 3
165:
166: stroke_color = ChunkyPNG::Color.parse(stroke_color)
167: fill_color = ChunkyPNG::Color.parse(fill_color)
168:
169: # Fill
170: unless fill_color == ChunkyPNG::Color::TRANSPARENT
171: vector.y_range.each do |y|
172: intersections = []
173: vector.edges.each do |p1, p2|
174: if (p1.y < y && p2.y >= y) || (p2.y < y && p1.y >= y)
175: intersections << (p1.x + (y - p1.y).to_f / (p2.y - p1.y) * (p2.x - p1.x)).round
176: end
177: end
178:
179: intersections.sort!
180: 0.step(intersections.length - 1, 2) do |i|
181: intersections[i].upto(intersections[i + 1]) do |x|
182: compose_pixel(x, y, fill_color)
183: end
184: end
185: end
186: end
187:
188: # Stroke
189: vector.each_edge do |(from_x, from_y), (to_x, to_y)|
190: line(from_x, from_y, to_x, to_y, stroke_color, false)
191: end
192:
193: return self
194: 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, line 205
205: def rect(x0, y0, x1, y1, stroke_color = ChunkyPNG::Color::BLACK, fill_color = ChunkyPNG::Color::TRANSPARENT)
206:
207: stroke_color = ChunkyPNG::Color.parse(stroke_color)
208: fill_color = ChunkyPNG::Color.parse(fill_color)
209:
210: # Fill
211: unless fill_color == ChunkyPNG::Color::TRANSPARENT
212: [x0, x1].min.upto([x0, x1].max) do |x|
213: [y0, y1].min.upto([y0, y1].max) do |y|
214: compose_pixel(x, y, fill_color)
215: end
216: end
217: end
218:
219: # Stroke
220: line(x0, y0, x0, y1, stroke_color, false)
221: line(x0, y1, x1, y1, stroke_color, false)
222: line(x1, y1, x1, y0, stroke_color, false)
223: line(x1, y0, x0, y0, stroke_color, false)
224:
225: return self
226: end