Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(495)

Side by Side Diff: ui/gfx/geometry/cubic_bezier.cc

Issue 1846733003: UI GFX Geometry: Make UnitBezier a wrapper for gfx::CubicBezier (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add one more TODO for Range. Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ui/gfx/geometry/cubic_bezier.h ('k') | ui/gfx/geometry/cubic_bezier_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/gfx/geometry/cubic_bezier.h" 5 #include "ui/gfx/geometry/cubic_bezier.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <cmath> 8 #include <cmath>
9 9
10 #include "base/logging.h" 10 #include "base/logging.h"
11 11
12 namespace gfx { 12 namespace gfx {
13 13
14 namespace { 14 static const double kBezierEpsilon = 1e-7;
15 15
16 static const double kBezierEpsilon = 1e-7; 16 CubicBezier::CubicBezier(double p1x, double p1y, double p2x, double p2y) {
17 static const int MAX_STEPS = 30; 17 InitCoefficients(p1x, p1y, p2x, p2y);
18 18 InitGradients(p1x, p1y, p2x, p2y);
19 static double eval_bezier(double p1, double p2, double t) { 19 InitRange(p1y, p2y);
20 const double p1_times_3 = 3.0 * p1;
21 const double p2_times_3 = 3.0 * p2;
22 const double h3 = p1_times_3;
23 const double h1 = p1_times_3 - p2_times_3 + 1.0;
24 const double h2 = p2_times_3 - 6.0 * p1;
25 return t * (t * (t * h1 + h2) + h3);
26 } 20 }
27 21
28 static double eval_bezier_derivative(double p1, double p2, double t) { 22 void CubicBezier::InitCoefficients(double p1x,
29 const double h1 = 9.0 * p1 - 9.0 * p2 + 3.0; 23 double p1y,
30 const double h2 = 6.0 * p2 - 12.0 * p1; 24 double p2x,
31 const double h3 = 3.0 * p1; 25 double p2y) {
32 return t * (t * h1 + h2) + h3; 26 // Calculate the polynomial coefficients, implicit first and last control
27 // points are (0,0) and (1,1).
28 cx_ = 3.0 * p1x;
29 bx_ = 3.0 * (p2x - p1x) - cx_;
30 ax_ = 1.0 - cx_ - bx_;
31
32 cy_ = 3.0 * p1y;
33 by_ = 3.0 * (p2y - p1y) - cy_;
34 ay_ = 1.0 - cy_ - by_;
33 } 35 }
34 36
35 // Finds t such that eval_bezier(x1, x2, t) = x. 37 void CubicBezier::InitGradients(double p1x,
36 // There is a unique solution if x1 and x2 lie within (0, 1). 38 double p1y,
37 static double bezier_interp(double x1, 39 double p2x,
38 double x2, 40 double p2y) {
39 double x) { 41 // End-point gradients are used to calculate timing function results
40 DCHECK_GE(1.0, x1); 42 // outside the range [0, 1].
41 DCHECK_LE(0.0, x1); 43 //
42 DCHECK_GE(1.0, x2); 44 // There are three possibilities for the gradient at each end:
43 DCHECK_LE(0.0, x2); 45 // (1) the closest control point is not horizontally coincident with regard to
46 // (0, 0) or (1, 1). In this case the line between the end point and
47 // the control point is tangent to the bezier at the end point.
48 // (2) the closest control point is coincident with the end point. In
49 // this case the line between the end point and the far control
50 // point is tangent to the bezier at the end point.
51 // (3) the closest control point is horizontally coincident with the end
52 // point, but vertically distinct. In this case the gradient at the
53 // end point is Infinite. However, this causes issues when
54 // interpolating. As a result, we break down to a simple case of
55 // 0 gradient under these conditions.
44 56
45 x1 = std::min(std::max(x1, 0.0), 1.0); 57 if (p1x > 0)
46 x2 = std::min(std::max(x2, 0.0), 1.0); 58 start_gradient_ = p1y / p1x;
47 x = std::min(std::max(x, 0.0), 1.0); 59 else if (!p1y && p2x > 0)
48 60 start_gradient_ = p2y / p2x;
49 // We're just going to do bisection for now (for simplicity), but we could
50 // easily do some newton steps if this turns out to be a bottleneck.
51 double t = 0.0;
52 double step = 1.0;
53 for (int i = 0; i < MAX_STEPS; ++i, step *= 0.5) {
54 const double error = eval_bezier(x1, x2, t) - x;
55 if (std::abs(error) < kBezierEpsilon)
56 break;
57 t += error > 0.0 ? -step : step;
58 }
59
60 // We should have terminated the above loop because we got close to x, not
61 // because we exceeded MAX_STEPS. Do a DCHECK here to confirm.
62 DCHECK_GT(kBezierEpsilon, std::abs(eval_bezier(x1, x2, t) - x));
63
64 return t;
65 }
66
67 } // namespace
68
69 CubicBezier::CubicBezier(double x1, double y1, double x2, double y2)
70 : x1_(x1),
71 y1_(y1),
72 x2_(x2),
73 y2_(y2) {
74 InitGradients();
75 }
76
77 CubicBezier::~CubicBezier() {
78 }
79
80 void CubicBezier::InitGradients() {
81 if (x1_ > 0)
82 start_gradient_ = y1_ / x1_;
83 else if (!y1_ && x2_ > 0)
84 start_gradient_ = y2_ / x2_;
85 else 61 else
86 start_gradient_ = 0; 62 start_gradient_ = 0;
87 63
88 if (x2_ < 1) 64 if (p2x < 1)
89 end_gradient_ = (y2_ - 1) / (x2_ - 1); 65 end_gradient_ = (p2y - 1) / (p2x - 1);
90 else if (x2_ == 1 && x1_ < 1) 66 else if (p2x == 1 && p1x < 1)
91 end_gradient_ = (y1_ - 1) / (x1_ - 1); 67 end_gradient_ = (p1y - 1) / (p1x - 1);
92 else 68 else
93 end_gradient_ = 0; 69 end_gradient_ = 0;
94 } 70 }
95 71
96 double CubicBezier::Solve(double x) const { 72 void CubicBezier::InitRange(double p1y, double p2y) {
97 if (x < 0) 73 range_min_ = 0;
98 return start_gradient_ * x; 74 range_max_ = 1;
99 if (x > 1) 75 if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1)
100 return 1.0 + end_gradient_ * (x - 1.0);
101
102 return eval_bezier(y1_, y2_, bezier_interp(x1_, x2_, x));
103 }
104
105 double CubicBezier::Slope(double x) const {
106 double t = bezier_interp(x1_, x2_, x);
107 double dx_dt = eval_bezier_derivative(x1_, x2_, t);
108 double dy_dt = eval_bezier_derivative(y1_, y2_, t);
109 return dy_dt / dx_dt;
110 }
111
112 void CubicBezier::Range(double* min, double* max) const {
113 *min = 0;
114 *max = 1;
115 if (0 <= y1_ && y1_ < 1 && 0 <= y2_ && y2_ <= 1)
116 return; 76 return;
117 77
118 // Represent the function's derivative in the form at^2 + bt + c. 78 const double epsilon = kBezierEpsilon;
79
80 // Represent the function's derivative in the form at^2 + bt + c
81 // as in sampleCurveDerivativeY.
119 // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros 82 // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros
120 // but does not actually give the slope of the curve.) 83 // but does not actually give the slope of the curve.)
121 double a = 3 * (y1_ - y2_) + 1; 84 const double a = 3.0 * ay_;
122 double b = 2 * (y2_ - 2 * y1_); 85 const double b = 2.0 * by_;
123 double c = y1_; 86 const double c = cy_;
124 87
125 // Check if the derivative is constant. 88 // Check if the derivative is constant.
126 if (std::abs(a) < kBezierEpsilon && 89 if (std::abs(a) < epsilon && std::abs(b) < epsilon)
127 std::abs(b) < kBezierEpsilon)
128 return; 90 return;
129 91
130 // Zeros of the function's derivative. 92 // Zeros of the function's derivative.
131 double t_1 = 0; 93 double t1 = 0;
132 double t_2 = 0; 94 double t2 = 0;
133 95
134 if (std::abs(a) < kBezierEpsilon) { 96 if (std::abs(a) < epsilon) {
135 // The function's derivative is linear. 97 // The function's derivative is linear.
136 t_1 = -c / b; 98 t1 = -c / b;
137 } else { 99 } else {
138 // The function's derivative is a quadratic. We find the zeros of this 100 // The function's derivative is a quadratic. We find the zeros of this
139 // quadratic using the quadratic formula. 101 // quadratic using the quadratic formula.
140 double discriminant = b * b - 4 * a * c; 102 double discriminant = b * b - 4 * a * c;
141 if (discriminant < 0) 103 if (discriminant < 0)
142 return; 104 return;
143 double discriminant_sqrt = sqrt(discriminant); 105 double discriminant_sqrt = sqrt(discriminant);
144 t_1 = (-b + discriminant_sqrt) / (2 * a); 106 t1 = (-b + discriminant_sqrt) / (2 * a);
145 t_2 = (-b - discriminant_sqrt) / (2 * a); 107 t2 = (-b - discriminant_sqrt) / (2 * a);
146 } 108 }
147 109
148 double sol_1 = 0; 110 double sol1 = 0;
149 double sol_2 = 0; 111 double sol2 = 0;
150 112
151 if (0 < t_1 && t_1 < 1) 113 if (0 < t1 && t1 < 1)
152 sol_1 = eval_bezier(y1_, y2_, t_1); 114 sol1 = SampleCurveY(t1);
153 115
154 if (0 < t_2 && t_2 < 1) 116 if (0 < t2 && t2 < 1)
155 sol_2 = eval_bezier(y1_, y2_, t_2); 117 sol2 = SampleCurveY(t2);
156 118
157 *min = std::min(std::min(*min, sol_1), sol_2); 119 range_min_ = std::min(std::min(range_min_, sol1), sol2);
158 *max = std::max(std::max(*max, sol_1), sol_2); 120 range_max_ = std::max(std::max(range_max_, sol1), sol2);
121 }
122
123 double CubicBezier::SolveCurveX(double x, double epsilon) const {
124 DCHECK_GE(x, 0.0);
125 DCHECK_LE(x, 1.0);
126
127 double t0;
128 double t1;
129 double t2;
130 double x2;
131 double d2;
132 int i;
133
134 // First try a few iterations of Newton's method -- normally very fast.
135 for (t2 = x, i = 0; i < 8; i++) {
136 x2 = SampleCurveX(t2) - x;
137 if (fabs(x2) < epsilon)
138 return t2;
139 d2 = SampleCurveDerivativeX(t2);
140 if (fabs(d2) < 1e-6)
141 break;
142 t2 = t2 - x2 / d2;
143 }
144
145 // Fall back to the bisection method for reliability.
146 t0 = 0.0;
147 t1 = 1.0;
148 t2 = x;
149
150 while (t0 < t1) {
151 x2 = SampleCurveX(t2);
152 if (fabs(x2 - x) < epsilon)
153 return t2;
154 if (x > x2)
155 t0 = t2;
156 else
157 t1 = t2;
158 t2 = (t1 - t0) * .5 + t0;
159 }
160
161 // Failure.
162 return t2;
163 }
164
165 double CubicBezier::Solve(double x) const {
166 return SolveWithEpsilon(x, kBezierEpsilon);
167 }
168
169 double CubicBezier::SlopeWithEpsilon(double x, double epsilon) const {
170 x = std::min(std::max(x, 0.0), 1.0);
171 double t = SolveCurveX(x, epsilon);
172 double dx = SampleCurveDerivativeX(t);
173 double dy = SampleCurveDerivativeY(t);
174 return dy / dx;
175 }
176
177 double CubicBezier::Slope(double x) const {
178 return SlopeWithEpsilon(x, kBezierEpsilon);
159 } 179 }
160 180
161 } // namespace gfx 181 } // namespace gfx
OLDNEW
« no previous file with comments | « ui/gfx/geometry/cubic_bezier.h ('k') | ui/gfx/geometry/cubic_bezier_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698