blob: aa038539780f8d0dc802c2b951b6d6b19456e6a1 [file] [log] [blame]
Brian Osman7c979f52019-02-12 13:27:51 -05001/*
2* Copyright 2019 Google LLC
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
8#include "SkCurve.h"
9
10#include "SkRandom.h"
11#include "SkReflected.h"
12
Brian Osman8b6283f2019-02-14 16:55:21 -050013static SkScalar eval_cubic(const SkScalar* pts, SkScalar x) {
14 SkScalar ix = (1 - x);
15 return pts[0]*ix*ix*ix + pts[1]*3*ix*ix*x + pts[2]*3*ix*x*x + pts[3]*x*x*x;
Brian Osman7c979f52019-02-12 13:27:51 -050016}
17
Brian Osman125daa42019-02-20 12:25:20 -050018static SkColor4f operator+(SkColor4f c1, SkColor4f c2) {
19 return { c1.fR + c2.fR, c1.fG + c2.fG, c1.fB + c2.fB, c1.fA + c2.fA };
20}
21
22static SkColor4f eval_cubic(const SkColor4f* pts, SkScalar x) {
23 SkScalar ix = (1 - x);
24 return pts[0]*(ix*ix*ix) + pts[1]*(3*ix*ix*x) + pts[2]*(3*ix*x*x) + pts[3]*(x*x*x);
25}
26
Brian Osman8b6283f2019-02-14 16:55:21 -050027SkScalar SkCurveSegment::eval(SkScalar x, SkRandom& random) const {
28 SkScalar result = fConstant ? fMin[0] : eval_cubic(fMin, x);
29 if (fRanged) {
30 result += ((fConstant ? fMax[0] : eval_cubic(fMax, x)) - result) * random.nextF();
31 }
32 if (fBidirectional && random.nextBool()) {
33 result = -result;
34 }
35 return result;
36}
37
38void SkCurveSegment::visitFields(SkFieldVisitor* v) {
39 v->visit("Constant", fConstant);
Brian Osman7c979f52019-02-12 13:27:51 -050040 v->visit("Ranged", fRanged);
Brian Osman8b6283f2019-02-14 16:55:21 -050041 v->visit("Bidirectional", fBidirectional);
Brian Osman7c979f52019-02-12 13:27:51 -050042 v->visit("A0", fMin[0]);
43 v->visit("B0", fMin[1]);
44 v->visit("C0", fMin[2]);
45 v->visit("D0", fMin[3]);
46 v->visit("A1", fMax[0]);
47 v->visit("B1", fMax[1]);
48 v->visit("C1", fMax[2]);
49 v->visit("D1", fMax[3]);
50}
51
Brian Osman8b6283f2019-02-14 16:55:21 -050052SkScalar SkCurve::eval(SkScalar x, SkRandom& random) const {
53 SkASSERT(fSegments.count() == fXValues.count() + 1);
54
55 int i = 0;
56 for (; i < fXValues.count(); ++i) {
57 if (x <= fXValues[i]) {
58 break;
59 }
60 }
61
62 SkScalar rangeMin = (i == 0) ? 0.0f : fXValues[i - 1];
63 SkScalar rangeMax = (i == fXValues.count()) ? 1.0f : fXValues[i];
64 SkScalar segmentX = (x - rangeMin) / (rangeMax - rangeMin);
Brian Osman112aa2d2019-02-15 10:45:56 -050065 if (!SkScalarIsFinite(segmentX)) {
66 segmentX = rangeMin;
67 }
Brian Osman8b6283f2019-02-14 16:55:21 -050068 SkASSERT(0.0f <= segmentX && segmentX <= 1.0f);
69 return fSegments[i].eval(segmentX, random);
Brian Osman7c979f52019-02-12 13:27:51 -050070}
71
Brian Osman8b6283f2019-02-14 16:55:21 -050072void SkCurve::visitFields(SkFieldVisitor* v) {
73 v->visit("XValues", fXValues);
74 v->visit("Segments", fSegments);
75
76 // Validate and fixup
77 if (fSegments.empty()) {
78 fSegments.push_back().setConstant(0.0f);
79 }
80 fXValues.resize_back(fSegments.count() - 1);
81 for (int i = 0; i < fXValues.count(); ++i) {
82 fXValues[i] = SkTPin(fXValues[i], i > 0 ? fXValues[i - 1] : 0.0f, 1.0f);
83 }
Brian Osman7c979f52019-02-12 13:27:51 -050084}
Brian Osman112aa2d2019-02-15 10:45:56 -050085
Brian Osmane12e4992019-02-19 15:39:18 -050086// TODO: This implementation is extremely conservative, because it uses the position of the control
87// points as the actual range. The curve typically doesn't reach that far. Evaluating the curve at
88// each of [0, 1/3, 2/3, 1] would be tighter, but can be too tight in some cases.
Brian Osman112aa2d2019-02-15 10:45:56 -050089void SkCurve::getExtents(SkScalar extents[2]) const {
90 extents[0] = INFINITY;
91 extents[1] = -INFINITY;
92 auto extend = [=](SkScalar y) {
93 extents[0] = SkTMin(extents[0], y);
94 extents[1] = SkTMax(extents[1], y);
95 };
96 for (const auto& segment : fSegments) {
97 for (int i = 0; i < (segment.fConstant ? 1 : 4); ++i) {
98 extend(segment.fMin[i]);
99 if (segment.fRanged) {
100 extend(segment.fMax[i]);
101 }
102 }
103 }
104}
Brian Osman125daa42019-02-20 12:25:20 -0500105
106SkColor4f SkColorCurveSegment::eval(SkScalar x, SkRandom& random) const {
107 SkColor4f result = fConstant ? fMin[0] : eval_cubic(fMin, x);
108 if (fRanged) {
109 result = result +
110 ((fConstant ? fMax[0] : eval_cubic(fMax, x)) + (result * -1)) * random.nextF();
111 }
112 return result;
113}
114
115void SkColorCurveSegment::visitFields(SkFieldVisitor* v) {
116 v->visit("Constant", fConstant);
117 v->visit("Ranged", fRanged);
118 v->visit("A0", fMin[0]);
119 v->visit("B0", fMin[1]);
120 v->visit("C0", fMin[2]);
121 v->visit("D0", fMin[3]);
122 v->visit("A1", fMax[0]);
123 v->visit("B1", fMax[1]);
124 v->visit("C1", fMax[2]);
125 v->visit("D1", fMax[3]);
126}
127
128SkColor4f SkColorCurve::eval(SkScalar x, SkRandom& random) const {
129 SkASSERT(fSegments.count() == fXValues.count() + 1);
130
131 int i = 0;
132 for (; i < fXValues.count(); ++i) {
133 if (x <= fXValues[i]) {
134 break;
135 }
136 }
137
138 SkScalar rangeMin = (i == 0) ? 0.0f : fXValues[i - 1];
139 SkScalar rangeMax = (i == fXValues.count()) ? 1.0f : fXValues[i];
140 SkScalar segmentX = (x - rangeMin) / (rangeMax - rangeMin);
141 if (!SkScalarIsFinite(segmentX)) {
142 segmentX = rangeMin;
143 }
144 SkASSERT(0.0f <= segmentX && segmentX <= 1.0f);
145 return fSegments[i].eval(segmentX, random);
146}
147
148void SkColorCurve::visitFields(SkFieldVisitor* v) {
149 v->visit("XValues", fXValues);
150 v->visit("Segments", fSegments);
151
152 // Validate and fixup
153 if (fSegments.empty()) {
154 fSegments.push_back().setConstant(SkColor4f{ 1.0f, 1.0f, 1.0f, 1.0f });
155 }
156 fXValues.resize_back(fSegments.count() - 1);
157 for (int i = 0; i < fXValues.count(); ++i) {
158 fXValues[i] = SkTPin(fXValues[i], i > 0 ? fXValues[i - 1] : 0.0f, 1.0f);
159 }
160}