paulnicholson-acrylic 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,200 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'cairo'
4
+ require 'gdk_pixbuf2'
5
+ require 'color'
6
+ require 'text_box'
7
+ require 'image_surface_extensions'
8
+ require 'inline'
9
+
10
+ module CairoTools
11
+ include Color
12
+ attr_reader :surface, :cr, :canvas_height, :canvas_width, :top_margin, :right_margin, :bottom_margin, :left_margin
13
+ attr_accessor :preview
14
+
15
+ def generate_image(path, options)
16
+ # dummy context is useful sometimes
17
+ @surface = Cairo::ImageSurface.new(1, 1)
18
+ @cr = Cairo::Context.new(surface)
19
+ draw(*options)
20
+ cr.target.write_to_png(path)
21
+ end
22
+
23
+ def dimensions(width, height)
24
+ @canvas_width, @canvas_height = width, height
25
+ @surface = Cairo::ImageSurface.new(width, height)
26
+ @cr = Cairo::Context.new(surface)
27
+ end
28
+
29
+ def margin(*rect)
30
+ rect = rect + rect if rect.length == 1
31
+ rect = rect + rect if rect.length == 2
32
+ @top_margin, @right_margin, @bottom_margin, @left_margin = rect
33
+ cr.matrix = Cairo::Matrix.identity.translate(left_margin, top_margin)
34
+ end
35
+
36
+ def width
37
+ canvas_width - right_margin - left_margin
38
+ end
39
+
40
+ def height
41
+ canvas_height - top_margin - bottom_margin
42
+ end
43
+
44
+ def transform(matrix, &block)
45
+ old_matrix = cr.matrix
46
+ cr.matrix = matrix
47
+ yield
48
+ cr.matrix = old_matrix
49
+ end
50
+
51
+ # http://www.cairographics.org/cookbook/roundedrectangles/
52
+ def rounded_rectangle(x, y, w, h, radius_x=5, radius_y=radius_x)
53
+ arc_to_bezier = 0.55228475
54
+ radius_x = w / 2 if radius_x > w - radius_x
55
+ radius_y = h / 2 if radius_y > h - radius_y
56
+ c1 = arc_to_bezier * radius_x
57
+ c2 = arc_to_bezier * radius_y
58
+
59
+ cr.new_path
60
+ cr.move_to(x + radius_x, y)
61
+ cr.rel_line_to(w - 2 * radius_x, 0.0)
62
+ cr.rel_curve_to(c1, 0.0, radius_x, c2, radius_x, radius_y)
63
+ cr.rel_line_to(0, h - 2 * radius_y)
64
+ cr.rel_curve_to(0.0, c2, c1 - radius_x, radius_y, -radius_x, radius_y)
65
+ cr.rel_line_to(-w + 2 * radius_x, 0)
66
+ cr.rel_curve_to(-c1, 0, -radius_x, -c2, -radius_x, -radius_y)
67
+ cr.rel_line_to(0, -h + 2 * radius_y)
68
+ cr.rel_curve_to(0.0, -c2, radius_x - c1, -radius_y, radius_x, -radius_y)
69
+ cr.close_path
70
+ end
71
+
72
+ def circular_text(x, y, radius, font_size, text)
73
+ radians = proc {|text| cr.set_font_size(font_size); cr.text_extents(text).x_advance/radius}
74
+ blank = (2*Math::PI - radians[text])/2
75
+ start = blank + Math::PI/2
76
+ partial = ''
77
+ text.split(//).each do |letter|
78
+ theta = start + radians[partial]
79
+ cr.move_to(x+radius*Math.cos(theta), y+radius*Math.sin(theta))
80
+ cr.set_font_matrix Cairo::Matrix.identity.rotate(theta + Math::PI/2).scale(font_size, font_size)
81
+ cr.show_text letter
82
+ theta += radians[letter]
83
+ partial << letter
84
+ end
85
+ end
86
+
87
+ def create_text_box(x, y, width=nil, height=nil, valign=:top)
88
+ TextBox.new(self, x, y, width, height, valign)
89
+ end
90
+
91
+ def draw_text_box(x, y, width=nil, height=nil, valign=:top)
92
+ tb = create_text_box(x, y, width, height, valign)
93
+ yield tb
94
+ tb.draw
95
+ end
96
+
97
+ def set_color(color)
98
+ cr.set_source_rgba(*color.to_rgb.to_a)
99
+ end
100
+
101
+ def linear_gradient(x0, y0, x1, y1, *colors)
102
+ gradient(Cairo::LinearPattern.new(x0, y0, x1, y1), *colors)
103
+ end
104
+
105
+ def radial_gradient(cx0, cy0, r0, cx1, cy1, r1, *colors)
106
+ gradient(Cairo::RadialPattern.new(cx0, cy0, r0, cx1, cy1, r1), *colors)
107
+ end
108
+
109
+ def gradient(gradient, *colors)
110
+ colors.each_with_index do |color, i|
111
+ array = color.respond_to?(:to_rgb) ? color.to_rgb.to_a : color.to_a
112
+ gradient.add_color_stop(i.to_f/(colors.length - 1), *array)
113
+ end
114
+ cr.set_source(gradient)
115
+ end
116
+
117
+ def shadow(radius=3, alpha=1)
118
+ shadow_surface = Cairo::ImageSurface.new(@canvas_width, @canvas_height)
119
+ shadow_cr = Cairo::Context.new(shadow_surface)
120
+ shadow_cr.set_source_rgba(0, 0, 0, alpha)
121
+ shadow_cr.mask(Cairo::SurfacePattern.new(surface))
122
+ shadow_surface.blur(radius)
123
+ shadow_cr.set_source(Cairo::SurfacePattern.new(surface))
124
+ shadow_cr.paint
125
+ @surface = shadow_surface
126
+ @cr = shadow_cr
127
+ end
128
+
129
+ def get_pixel(x, y)
130
+ @surface.get_pixel(x, y)
131
+ end
132
+
133
+ def load_image(path, x=0, y=0)
134
+ image = Gdk::Pixbuf.new(File.join(File.dirname($0), path))
135
+ cr.set_source_pixbuf(image)
136
+ cr.source.matrix = Cairo::Matrix.identity.translate(x, y)
137
+ end
138
+
139
+ def clip!
140
+ i = self.class.new
141
+ w, h = @canvas_width, @canvas_height
142
+ pattern = Cairo::SurfacePattern.new(@surface)
143
+ clip = cr.copy_path
144
+ i.instance_eval do
145
+ dimensions w, h
146
+ cr.append_path clip
147
+ cr.clip
148
+ cr.set_source(pattern)
149
+ cr.paint
150
+ end
151
+ dimensions w, h
152
+ cr.set_source(Cairo::SurfacePattern.new(i.surface))
153
+ cr.paint
154
+ end
155
+
156
+ def transparent!(alpha)
157
+ i = self.class.new
158
+ w, h = @canvas_width, @canvas_height
159
+ pattern = Cairo::SurfacePattern.new(@surface)
160
+ i.instance_eval do
161
+ dimensions w, h
162
+ cr.set_source(pattern)
163
+ cr.paint_with_alpha(alpha)
164
+ end
165
+ @surface = i.surface
166
+ @cr = i.cr
167
+ end
168
+
169
+ def draw_image(image, x=0, y=0, a=1)
170
+ i = self.class.new
171
+ i.instance_eval do
172
+ draw(image)
173
+ end
174
+ cr.set_source(Cairo::SurfacePattern.new(i.surface))
175
+ cr.source.matrix = Cairo::Matrix.identity.translate(-x, -y)
176
+ cr.paint_with_alpha(a)
177
+ end
178
+
179
+ def clouds
180
+ Cairo::ImageSurface.new(129, 129)
181
+ end
182
+ end
183
+
184
+ class Cairo::Context
185
+ inline(:C) do |builder|
186
+ builder.include '<stdlib.h>'
187
+ builder.include '<cairo.h>'
188
+ builder.include '<rb_cairo.h>'
189
+ builder.include '<intern.h>'
190
+ builder.add_compile_flags '`/opt/local/bin/pkg-config --cflags cairo`'
191
+ builder.add_compile_flags '-I/opt/local/lib/ruby/site_ruby/1.8/i686-darwin9/'
192
+ builder.add_compile_flags '-I/opt/local/lib/ruby/gems/1.8/gems/cairo-1.6.2/src'
193
+ builder.c %{
194
+ void paint_with_alpha(double alpha) {
195
+ cairo_t *cr = RVAL2CRCONTEXT(self);
196
+ cairo_paint_with_alpha(cr, alpha);
197
+ }
198
+ }
199
+ end
200
+ end
data/lib/color.rb ADDED
@@ -0,0 +1,149 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'core_ext'
4
+
5
+ module Color
6
+ def rgb(*args)
7
+ RGB.new(*args)
8
+ end
9
+
10
+ def hsl(*args)
11
+ HSL.new(*args)
12
+ end
13
+
14
+ class Base
15
+ def self.inherited(subclass)
16
+ components = subclass.name.sub('Color::', '').downcase.split(//).map(&:to_sym)
17
+ components << :a
18
+ components.each_with_index do |component, i|
19
+ subclass.class_eval <<-"end;"
20
+ def #{component}(value=nil)
21
+ if value
22
+ result = self.class.new(@components)
23
+ result.#{component} = value
24
+ result
25
+ else
26
+ @components[#{i}]
27
+ end
28
+ end
29
+ def #{component}=(x)
30
+ @components[#{i}] = constrain(x)
31
+ end
32
+ end;
33
+ end
34
+ end
35
+
36
+ def initialize(*components)
37
+ if components[0].is_a?(String)
38
+ @components = []
39
+ self.css = components[0]
40
+ else
41
+ @components = components.flatten.map(&method(:constrain))
42
+ end
43
+ @components << 1.0 if @components.size == 3
44
+ end
45
+
46
+ def primary_components
47
+ @components[0..-2]
48
+ end
49
+
50
+ def to_a
51
+ @components
52
+ end
53
+
54
+ def -(other)
55
+ self + other.to_a.map(&:-@)
56
+ end
57
+
58
+ def +(other)
59
+ other_array = other.to_a
60
+ array = component_indicies.map {|i| @components[i] + (other_array[i] || 0)}
61
+ self.class.new(*array)
62
+ end
63
+
64
+ def ==(other)
65
+ other.class == self.class && component_indicies.all? {|i| epsilon(@components[i] - other.to_a[i])}
66
+ end
67
+
68
+ def to_s
69
+ "<#{self.class.name}: #{@components.join(", ")}>"
70
+ end
71
+
72
+ protected
73
+ def component_indicies
74
+ (0..@components.size - 1).to_a
75
+ end
76
+
77
+ def epsilon(x)
78
+ x.abs <= 0.01
79
+ end
80
+
81
+ def constrain(x)
82
+ x > 1.0 ? 1.0 : x < 0.0 ? 0.0 : x
83
+ end
84
+ end
85
+
86
+ class RGB < Base
87
+ def css=(string)
88
+ string.gsub!('#', '')
89
+ self.r, self.g, self.b = string.split(//).in_groups_of(string.length/3).map {|a| a.join.to_i(16).to_f / (16 ** (string.length/3) - 1).to_f}
90
+ end
91
+
92
+ def to_hsl
93
+ max = primary_components.max
94
+ min = primary_components.min
95
+ compute_hue = proc do |numerator, degrees|
96
+ degrees(60) * (numerator / (max - min)) + degrees(degrees)
97
+ end
98
+ l = (max + min)/2.0
99
+ s = (epsilon(l) || epsilon(max - min)) ? 0 :
100
+ l <= 0.5 ? (max - min) / (max + min) :
101
+ (max - min) / (2 - (max + min))
102
+ h = epsilon(max - min) ? 0 :
103
+ max == r ? compute_hue[g - b, g >= b ? 0 : 360] :
104
+ max == g ? compute_hue[b - r, 120] :
105
+ compute_hue[r - g, 240]
106
+ HSL.new(h, s, l, a)
107
+ end
108
+
109
+ def to_css
110
+ "%02X" * 3 % @components.map {|c| c * 255}
111
+ end
112
+
113
+ def to_rgb
114
+ self
115
+ end
116
+
117
+ protected
118
+ def degrees(x)
119
+ x.to_f/360.0
120
+ end
121
+ end
122
+
123
+ class HSL < Base
124
+ def to_rgb
125
+ return RGB.new(l, l, l, a) if epsilon(s)
126
+ q = l < 0.5 ? l * (1.0 + s) : (l + s) - l * s
127
+ p = 2 * l - q
128
+ tc = [h + 1.0/3.0, h, h - 1.0/3.0]
129
+ tc.map! do |x|
130
+ x < 0 ? x + 1.0 :
131
+ x > 1.0 ? x - 1.0 : x
132
+ end
133
+ rgb = tc.map do |tc|
134
+ tc < 1.0/6.0 ? p + ((q - p) * 6.0 * tc) :
135
+ tc < 3.0/6.0 ? q :
136
+ tc < 4.0/6.0 ? p + ((q - p) * 6.0 * (2.0/3.0 - tc)) : p
137
+ end
138
+ RGB.new(rgb + [a])
139
+ end
140
+
141
+ def to_css
142
+ to_rgb.to_css
143
+ end
144
+
145
+ def css=(string)
146
+ self.h, self.s, self.l = RGB.new(string).to_hsl.to_a
147
+ end
148
+ end
149
+ end
data/lib/core_ext.rb ADDED
@@ -0,0 +1,16 @@
1
+ class Symbol
2
+ def self.pattern(name, pattern, &formation)
3
+ define_method(name) {to_s.match(pattern) ? self : formation.call(self).to_sym}
4
+ define_method(name.question) {to_s.match(pattern)}
5
+ end
6
+ pattern(:question, /\?$/) {|s| "#{s}?"}
7
+ pattern(:bang, /!$/) {|s| "#{s}!"}
8
+ pattern(:writer, /=$/) {|s| "#{s}="}
9
+ pattern(:iv, /^@[^@]/) {|s| "@#{s}"}
10
+ end
11
+
12
+ class Numeric
13
+ def deg
14
+ self / 180.0 * Math::PI
15
+ end
16
+ end
data/lib/curve.rb ADDED
@@ -0,0 +1,120 @@
1
+ class Curve < Array
2
+ attr_accessor :cr
3
+
4
+ def initialize(cr)
5
+ @cr = cr
6
+ end
7
+
8
+ def draw
9
+ draw_control_points(self)
10
+ end
11
+
12
+ def draw_control_points(control_points)
13
+ cr.move_to(*control_points.first.point)
14
+ cx1, cy1 = control_points.first.leading_point
15
+ control_points[1..-1].each do |point|
16
+ cx2, cy2 = point.trailing_point
17
+ cr.curve_to(cx1, cy1, cx2, cy2, point.x, point.y)
18
+ cx1, cy1 = point.leading_point
19
+ end
20
+ end
21
+
22
+ def offset_line(distance, flip)
23
+ offset_line = self.map do |p|
24
+ p.move_away_from_line(distance)
25
+ end
26
+ offset_line.each_with_index do |p, i|
27
+ p.d1 *= (p.distance(offset_line[i-1])/self[i].distance(self[i-1])) * 1.1 unless i == 0
28
+ p.d2 *= (p.distance(offset_line[i+1])/self[i].distance(self[i+1])) * 1.1 unless i + 1 == length
29
+ end
30
+ flip ? offset_line.map(&:flip).reverse : offset_line
31
+ end
32
+
33
+ def draw_outline
34
+ there = offset_line(-2, false)
35
+ there.first.d1 = 5
36
+ back = offset_line(2, true)
37
+ back.last.d2 = 2
38
+ draw_control_points(there + back + [there.first])
39
+ cr.stroke
40
+ # cr.fill
41
+ end
42
+
43
+ def debug
44
+ each do |point|
45
+ cr.circle(point.x, point.y, 3)
46
+ cr.fill
47
+ cr.line_width = 1
48
+ # cr.move_to(*point.trailing_point.map {|n| n + 2})
49
+ cr.move_to(*point.trailing_point)
50
+ cr.line_to(*point.leading_point)
51
+ cr.stroke
52
+ end
53
+ end
54
+
55
+ def point(x, y, theta, d1, d2=d1)
56
+ p [x, y, theta, d1, d2]
57
+ push(ControlPoint.new(x, y, theta, d1, d2))
58
+ end
59
+
60
+ def spiral(center_x, center_y, theta_one, delta_theta, phi, &algorithm)
61
+ control_points_per_rotation = 8
62
+ control_points = (control_points_per_rotation * delta_theta.abs / Math::PI / 2).to_i
63
+ radians_between_points = delta_theta / (control_points-1)
64
+ (0...control_points).each do |i|
65
+ theta = theta_one + radians_between_points * i
66
+ radius = algorithm.call(theta)
67
+ x, y = center_x + radius * Math.cos(theta), center_y + radius * Math.sin(theta)
68
+ tangent = theta + phi
69
+ influence = radius * 0.285 * (delta_theta / delta_theta.abs)
70
+ if i == control_points - 1
71
+ point(x, y, tangent, influence, influence * 2)
72
+ elsif i == 0
73
+ point(x, y, tangent, influence * 3, influence)
74
+ else
75
+ point(x, y, tangent, influence)
76
+ end
77
+ end
78
+ end
79
+
80
+ def logarithmic_spiral(center_x, center_y, theta_one, delta_theta, a, b)
81
+ spiral(center_x, center_y, theta_one, delta_theta, Math.atan(1/b)) do |theta|
82
+ (a*Math::E)**(b*theta)
83
+ end
84
+ end
85
+ end
86
+
87
+ class ControlPoint
88
+ attr_accessor :x, :y, :theta, :d1, :d2
89
+ def initialize(x, y, theta, d1, d2=d1)
90
+ @x, @y, @theta, @d1, @d2 = x, y, theta, d1, d2
91
+ end
92
+
93
+ def point
94
+ [x, y]
95
+ end
96
+
97
+ def distance(point)
98
+ Math.sqrt((x - point.x) ** 2 + (y - point.y) ** 2)
99
+ end
100
+
101
+ def trailing_point
102
+ [x - d1 * Math.cos(theta), y - d1 * Math.sin(theta)]
103
+ end
104
+
105
+ def leading_point
106
+ [x + d2 * Math.cos(theta), y + d2 * Math.sin(theta)]
107
+ end
108
+
109
+ def flip
110
+ ControlPoint.new(x, y, theta + 180.deg, d2, d1)
111
+ end
112
+
113
+ def move_away_from_line(delta)
114
+ ControlPoint.new(x + delta * Math.cos(theta - 90.deg) * d1/d1.abs, y + delta * Math.sin(theta - 90.deg) * d1/d1.abs, theta, d1, d2)
115
+ end
116
+
117
+ def move_along_line(delta)
118
+ ControlPoint.new(x + delta * Math.cos(theta) * d1/d1.abs, y + delta * Math.sin(theta) * d1/d1.abs, theta, d1, d2)
119
+ end
120
+ end