-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStroke.cpp
More file actions
230 lines (193 loc) · 7.34 KB
/
Stroke.cpp
File metadata and controls
230 lines (193 loc) · 7.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
81
82
83
84
85
86
87
88
89
90
91
92
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
151
152
153
154
155
156
157
158
159
160
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// Class for creating brush strokes.
// Strokes are represented by cubic B-splines, which we define
// with control points.
// Authors: James Plaut and Niall Williams
#include "Stroke.hpp"
#include "Image.hpp"
using namespace std;
/** Constructor
* x - x-coordinate of the first control point
* y - y-coordinate of the first control point
* radius - Radius of the brush this stroke will be painted with.
*/
Stroke::Stroke(int x, int y, int radius){
this->control_points.push_back(new Vector(x, y));
this->radius = radius;
}
/** Deconstructor
*/
Stroke::~Stroke(){
for (Vector* v : control_points)
delete v;
control_points.clear();
}
/** Set the color of the stroke.
* c - Color of the stroke.
*/
void Stroke::set_color(Color c){
this->color = Color(c.get_r(), c.get_g(), c.get_b());
}
/** Return the color of the stroke.
*/
Color Stroke::get_color(){
return color;
}
/** Return the radius of the stroke.
*/
int Stroke::get_radius(){
return radius;
}
/** Add another control point to the spline.
* x - x-coordinate of the control point being added.
* y - y-coordinate of the control point being added.
*/
void Stroke::add_control_point(int x, int y){
control_points.push_back(new Vector(x, y));
}
/** Return a list of the spline's control points.
*/
vector<Vector*> Stroke::get_control_points(){
return control_points;
}
/**Calculate the curve defined by the stroke's control points,
* and draw the curve onto the canvas. This is where the actual
* painting happens.
* canvas - The image object we are painting on.
* spline_degree - Degree of the spline to draw. In this case, the
* degree is 3 because we are drawing cubic B-splines.
*/
void Stroke::draw_stroke(Image* canvas, int spline_degree){
if (MIN_STROKE_LENGTH > MAX_STROKE_LENGTH){
cerr << "Minimum stroke length must be less than or equal to maxiumum stroke length! Exiting..." << endl;
exit(1);
}
// Draw pointillist style
if (MAX_STROKE_LENGTH == 0){
if (MIN_STROKE_LENGTH != 0){
cerr << "Both minimum and maximum stroke length must be 0! Exiting..." << endl;
exit(1);
}
draw_pointillist(canvas);
return;
}
int num_ctrl_points = control_points.size();
// You can't draw a cubic B-spline with less than 4 control points. If you do,
// it causes a bug where the last control point is (0, 0) so the stroke just cuts
// across the painting to the top-left corner. Very ugly!!
if (num_ctrl_points < 4) return;
int num_knots = control_points.size() + spline_degree + 1;
vector<float> knots = make_knot_vector(num_knots, spline_degree, num_ctrl_points);
if ((control_points.front()->get_x() <= 50 && control_points.front()->get_y() <= 50) ||
(control_points.back()->get_x() <= 50 && control_points.back()->get_y() <= 50))
return;
// Calculate the position of the point along the curve at time t
for (float t = 0.0; t <= 1.0; t += 1.0/STROKE_RESOLUTION){
Vector curve_point = Vector(0.0, 0.0);
// Weight each control point based on t and sum them.
for (int i = 0; i < num_ctrl_points; i++){
Vector* cur_point = control_points[i];
float N = calculate_N(t, i, spline_degree, knots);
curve_point = curve_point + (*cur_point * N);
}
vector<Vector> circle_points = calc_circ((int)curve_point.get_y(),
(int)curve_point.get_x(),
canvas->getHeight(),
canvas->getWidth());
// Paint!
for (Vector point : circle_points){
canvas->setColor(point.get_y(), point.get_x(), color);
}
}
}
/**Build the knot vector which is needed to draw the spline curve.
* m - The number of knots to calculate.
* p - The degree of the spline.
* n - The number of control points for the spline.
*/
vector<float> Stroke::make_knot_vector(int m, int p, int n){
vector<float> knots;
// Forces the curve to start at the first control point.
for (int i = 0; i <= p; i++){
knots.push_back(0.0);
}
for (int i = 1; i < n - p; i++){
knots.push_back((float)i / (float)(n-p+1));
}
// Forces the curve to end at the last control point.
for (int i = 0; i <= p; i++){
knots.push_back(1.0);
}
return knots;
}
/**Calculate the position of the point on the curve at time t along the curve.
* A B-spline is defined recursively. A point along the curve at time t is
* calculated by weighting each of the control points and summing them.
* t - Time variable to denote step-size along the curve. Goes from 0 to 1.
* i - Index of the control point whose weight is being calculated.
* j - Index of the knot.
* knots - List of knots
*/
float Stroke::calculate_N(float t, int i, int j, vector<float> knots){
float t_1 = knots[i];
float t_2 = knots[(i + j)];
float t_3 = knots[(i + 1)];
float t_4 = knots[(i + j + 1)];
// Base case of basis function
if (j == 0){
if (t_1 <= t && t < t_3) return 1;
else return 0;
}
// Check for divide by zero
float temp1 = (t_2 - t_1 == 0) ? 0 : ((t - t_1) / (t_2 - t_1)) * calculate_N(t, i, j-1, knots);
float temp2 = (t_4 - t_3 == 0) ? 0 : ((t_4 - t) / (t_4 - t_3)) * calculate_N(t, i+1, j-1, knots);
return temp1 + temp2;
}
/**Calculate the pixels around a center pixel that fall into a circle that is drawn
* at that pixel center. This is used so we know which pixels to fill in
* when rendering the strokes.
* c_y - y-coordinate of the center of the circle.
* c_x - x-coordinate of the center of the circle.
* r - Radius of the circle.
* height - Height of the image.
* width - Width of the image.
*/
vector<Vector> Stroke::calc_circ(int c_y, int c_x, int height, int width){
vector<Vector> points;
for (int y = c_y - radius; y <= c_y; y++){
for (int x = c_x - radius; x <= c_x; x++){
int distance = (y - c_y)*(y - c_y) + (x - c_x)*(x - c_x);
if (distance <= radius*radius){
// Use symmetry to quickly calculate the points in the other 3
// quandrants of the circle to be drawn.
int y_sym = c_y - (y - c_y);
int x_sym = c_x - (x - c_x);
if (y < 0 || y >= height || x < 0 || x >= width ||
y_sym < 0 || y_sym >= height || x_sym < 0 || x_sym >= width){
break;
}
Vector p1 = Vector(x, y);
Vector p2 = Vector(x_sym, y);
Vector p3 = Vector(x,y_sym);
Vector p4 = Vector(x_sym, y_sym);
points.push_back(p1);
points.push_back(p2);
points.push_back(p3);
points.push_back(p4);
}
}
}
return points;
}
/**Draws in a pointillist style. It just paints circles at
* the stroke's sole control point.
* canvas - Canvas we are painting on.
*/
void Stroke::draw_pointillist(Image* canvas){
for (Vector* p : control_points){
vector<Vector> circle_points = calc_circ(p->get_y(), p->get_x(), canvas->getHeight(), canvas->getWidth());
// Paint!
for (Vector point : circle_points){
canvas->setColor(point.get_y(), point.get_x(), color);
}
}
}