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

Side by Side Diff: ui/gfx/skia_color_space_util.cc

Issue 2715453002: Add UMAs for numerical approximation of table-based transfer functions. (Closed)
Patch Set: Review feedback Created 3 years, 10 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
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 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/skia_color_space_util.h" 5 #include "ui/gfx/skia_color_space_util.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <cmath> 8 #include <cmath>
9 9
10 #include "base/logging.h"
11
10 namespace gfx { 12 namespace gfx {
11 13
12 namespace { 14 namespace {
13 } 15
16 // Evaluate the gradient of the linear component of fn
17 void SkTransferFnEvalGradientLinear(const SkColorSpaceTransferFn& fn,
18 float x,
19 float* d_fn_d_fC_at_x,
20 float* d_fn_d_fF_at_x) {
21 *d_fn_d_fC_at_x = x;
22 *d_fn_d_fF_at_x = 1.f;
23 }
24
25 // Solve for the parameters fC and fF, given the parameter fD.
26 void SkTransferFnSolveLinear(SkColorSpaceTransferFn* fn,
27 const float* x,
28 const float* t,
29 size_t n) {
30 // If this has no linear segment, don't try to solve for one.
31 if (fn->fD <= 0) {
32 fn->fC = 1;
33 fn->fF = 0;
34 return;
35 }
36
37 // Let ne_lhs be the left hand side of the normal equations, and let ne_rhs
38 // be the right hand side. This is a 4x4 matrix, but we will only update the
39 // upper-left 2x2 sub-matrix.
40 SkMatrix44 ne_lhs;
41 SkVector4 ne_rhs;
42 for (int row = 0; row < 2; ++row) {
43 for (int col = 0; col < 2; ++col) {
44 ne_lhs.set(row, col, 0);
45 }
46 }
47 for (int row = 0; row < 4; ++row)
48 ne_rhs.fData[row] = 0;
49
50 // Add the contributions from each sample to the normal equations.
51 float x_linear_max = 0;
52 for (size_t i = 0; i < n; ++i) {
53 // Ignore points in the nonlinear segment.
54 if (x[i] >= fn->fD)
55 continue;
56 x_linear_max = std::max(x_linear_max, x[i]);
57
58 // Let J be the gradient of fn with respect to parameters C and F, evaluated
59 // at this point.
60 float J[2];
61 SkTransferFnEvalGradientLinear(*fn, x[i], &J[0], &J[1]);
62
63 // Let r be the residual at this point.
64 float r = t[i];
65
66 // Update the normal equations left hand side with the outer product of J
67 // with itself.
68 for (int row = 0; row < 2; ++row) {
69 for (int col = 0; col < 2; ++col) {
70 ne_lhs.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]);
71 }
72 // Update the normal equations right hand side the product of J with the
73 // residual
74 ne_rhs.fData[row] += J[row] * r;
75 }
76 }
77
78 // If we only have a single x point at 0, that isn't enough to construct a
79 // linear segment, so add an additional point connecting to the nonlinear
80 // segment.
81 if (x_linear_max == 0) {
82 float J[2];
83 SkTransferFnEvalGradientLinear(*fn, fn->fD, &J[0], &J[1]);
84 float r = SkTransferFnEval(*fn, fn->fD);
85 for (int row = 0; row < 2; ++row) {
86 for (int col = 0; col < 2; ++col) {
87 ne_lhs.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]);
88 }
89 ne_rhs.fData[row] += J[row] * r;
90 }
91 }
92
93 // Solve the normal equations.
94 SkMatrix44 ne_lhs_inv;
95 bool invert_result = ne_lhs.invert(&ne_lhs_inv);
96 DCHECK(invert_result);
97 SkVector4 solution = ne_lhs_inv * ne_rhs;
98
99 // Update the transfer function.
100 fn->fC = solution.fData[0];
101 fn->fF = solution.fData[1];
102 }
103
104 // Evaluate the gradient of the nonlinear component of fn
105 void SkTransferFnEvalGradientNonlinear(const SkColorSpaceTransferFn& fn,
106 float x,
107 float* d_fn_d_fA_at_x,
108 float* d_fn_d_fB_at_x,
109 float* d_fn_d_fE_at_x,
110 float* d_fn_d_fG_at_x) {
111 float base = fn.fA * x + fn.fB;
112 if (base > 0.f) {
113 *d_fn_d_fA_at_x = fn.fG * x * std::pow(base, fn.fG - 1.f);
114 *d_fn_d_fB_at_x = fn.fG * std::pow(base, fn.fG - 1.f);
115 *d_fn_d_fE_at_x = 1.f;
116 *d_fn_d_fG_at_x = std::pow(base, fn.fG) * std::log(base);
117 } else {
118 *d_fn_d_fA_at_x = 0.f;
119 *d_fn_d_fB_at_x = 0.f;
120 *d_fn_d_fE_at_x = 0.f;
121 *d_fn_d_fG_at_x = 0.f;
122 }
123 }
124
125 // Take one Gauss-Newton step updating fA, fB, fE, and fG, given fD.
126 bool SkTransferFnGaussNewtonStepNonlinear(SkColorSpaceTransferFn* fn,
127 float* error_Linfty_before,
128 const float* x,
129 const float* t,
130 size_t n) {
131 float kEpsilon = 1.f / 1024.f;
132 // Let ne_lhs be the left hand side of the normal equations, and let ne_rhs
133 // be the right hand side.
134 SkMatrix44 ne_lhs;
135 SkVector4 ne_rhs;
136 for (int row = 0; row < 4; ++row) {
137 for (int col = 0; col < 4; ++col) {
138 ne_lhs.set(row, col, 0);
139 }
140 ne_rhs.fData[row] = 0;
141 }
142
143 // Add the contributions from each sample to the normal equations.
144 *error_Linfty_before = 0;
145 for (size_t i = 0; i < n; ++i) {
146 // Ignore points in the linear segment.
147 if (x[i] < fn->fD)
148 continue;
149 // Let J be the gradient of fn with respect to parameters A, B, E, and G,
150 // evaulated at this point.
151 float J[4];
152 SkTransferFnEvalGradientNonlinear(*fn, x[i], &J[0], &J[1], &J[2], &J[3]);
153 // Let r be the residual at this point;
154 float r = t[i] - SkTransferFnEval(*fn, x[i]);
155 *error_Linfty_before += std::abs(r);
156 // Update the normal equations left hand side with the outer product of J
157 // with itself.
158 for (int row = 0; row < 4; ++row) {
159 for (int col = 0; col < 4; ++col) {
160 ne_lhs.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]);
161 }
162 // Update the normal equations right hand side the product of J with the
163 // residual
164 ne_rhs.fData[row] += J[row] * r;
165 }
166 }
167
168 // Note that if fG = 1, then the normal equations will be singular (because,
169 // when fG = 1, because fA and fE are equivalent parameters). To avoid
170 // problems, fix fE (row/column 3) in these circumstances.
171 if (std::abs(fn->fG - 1) < kEpsilon) {
172 for (int row = 0; row < 4; ++row) {
173 float value = (row == 2) ? 1.f : 0.f;
174 ne_lhs.set(row, 2, value);
175 ne_lhs.set(2, row, value);
176 }
177 ne_rhs.fData[2] = 0.f;
178 }
179
180 // Solve the normal equations.
181 SkMatrix44 ne_lhs_inv;
182 if (!ne_lhs.invert(&ne_lhs_inv))
183 return false;
184 SkVector4 step = ne_lhs_inv * ne_rhs;
185
186 // Update the transfer function.
187 fn->fA += step.fData[0];
188 fn->fB += step.fData[1];
189 fn->fE += step.fData[2];
190 fn->fG += step.fData[3];
191 return true;
192 }
193
194 // Solve for fA, fB, fE, and fG, given fD. The initial value of |fn| is the
195 // point from which iteration starts.
196 bool SkTransferFnSolveNonlinear(SkColorSpaceTransferFn* fn,
197 const float* x,
198 const float* t,
199 size_t n) {
200 // Take a maximum of 16 Gauss-Newton steps.
201 const size_t kNumSteps = 16;
202
203 // The L-infinity error before each step.
204 float step_error[kNumSteps] = {0};
205
206 for (size_t step = 0; step < kNumSteps; ++step) {
207 // If the normal equations are singular, we can't continue.
208 if (!SkTransferFnGaussNewtonStepNonlinear(fn, &step_error[step], x, t, n))
209 return false;
210
211 // If the error is inf or nan, we are clearly not converging.
212 if (std::isnan(step_error[step]) || std::isinf(step_error[step]))
213 return false;
214
215 // If our error is non-negligable and increasing then we are not in the
216 // region of convergence.
217 const float kNonNegligbleErrorEpsilon = 1.f / 256.f;
218 const float kGrowthFactor = 1.25f;
219 if (step > 2 && step_error[step] > kNonNegligbleErrorEpsilon) {
220 if (step_error[step - 1] * kGrowthFactor < step_error[step] &&
221 step_error[step - 2] * kGrowthFactor < step_error[step - 1]) {
222 return false;
223 }
224 }
225 }
226
227 // We've converged to a reasonable solution. If some of the parameters are
228 // extremely close to 0 or 1, set them to 0 or 1.
229 const float kRoundEpsilon = 1.f / 1024.f;
230 if (std::abs(fn->fA - 1.f) < kRoundEpsilon)
231 fn->fA = 1.f;
232 if (std::abs(fn->fB) < kRoundEpsilon)
233 fn->fB = 0;
234 if (std::abs(fn->fE) < kRoundEpsilon)
235 fn->fE = 0;
236 if (std::abs(fn->fG - 1.f) < kRoundEpsilon)
237 fn->fG = 1.f;
238 return true;
239 }
240
241 void SkApproximateTransferFnInternal(const float* x,
242 const float* t,
243 size_t n,
244 SkColorSpaceTransferFn* fn,
245 float* error,
246 bool* nonlinear_fit_converged) {
247 // First, guess at a value of fD. Assume that the nonlinear segment applies
248 // to all x >= 0.1. This is generally a safe assumption (fD is usually less
249 // than 0.1).
250 fn->fD = 0.1f;
251
252 // Do a nonlinear regression on the nonlinear segment. Use a number of guesses
253 // for the initial value of fG, because not all values will converge.
254 *nonlinear_fit_converged = false;
255 {
256 const size_t kNumInitialGammas = 4;
257 float initial_gammas[kNumInitialGammas] = {1.f, 2.f, 3.f, 0.5f};
258 for (size_t i = 0; i < kNumInitialGammas; ++i) {
259 fn->fG = initial_gammas[i];
260 fn->fA = 1;
261 fn->fB = 0;
262 fn->fC = 1;
263 fn->fE = 0;
264 fn->fF = 0;
265 if (SkTransferFnSolveNonlinear(fn, x, t, n)) {
266 *nonlinear_fit_converged = true;
267 break;
268 }
269 }
270 }
271
272 // Now walk back fD from our initial guess to the point where our nonlinear
273 // fit no longer fits (or all the way to 0 if it fits).
274 if (*nonlinear_fit_converged) {
275 // Find the L-infinity error of this nonlinear fit (using our old fD value).
276 float max_error_in_nonlinear_fit = 0;
277 for (size_t i = 0; i < n; ++i) {
278 if (x[i] < fn->fD)
279 continue;
280 float error_at_xi = std::abs(t[i] - SkTransferFnEval(*fn, x[i]));
281 max_error_in_nonlinear_fit =
282 std::max(max_error_in_nonlinear_fit, error_at_xi);
283 }
284
285 // Now find the maximum x value where this nonlinear fit is no longer
286 // accurate or no longer defined.
287 fn->fD = 0.f;
288 float max_x_where_nonlinear_does_not_fit = -1.f;
289 for (size_t i = 0; i < n; ++i) {
290 bool nonlinear_fits_xi = true;
291 if (fn->fA * x[i] + fn->fB < 0) {
292 // The nonlinear segment is undefined when fA * x + fB is less than 0.
293 nonlinear_fits_xi = false;
294 } else {
295 // Define "no longer accurate" as "has more than 10% more error than
296 // the maximum error in the fit segment".
297 float error_at_xi = std::abs(t[i] - SkTransferFnEval(*fn, x[i]));
298 if (error_at_xi > 1.1f * max_error_in_nonlinear_fit)
299 nonlinear_fits_xi = false;
300 }
301 if (!nonlinear_fits_xi) {
302 max_x_where_nonlinear_does_not_fit =
303 std::max(max_x_where_nonlinear_does_not_fit, x[i]);
304 }
305 }
306
307 // Now let fD be the highest sample of x that is above the threshold where
308 // the nonlinear segment does not fit.
309 fn->fD = 1.f;
310 for (size_t i = 0; i < n; ++i) {
311 if (x[i] > max_x_where_nonlinear_does_not_fit)
312 fn->fD = std::min(fn->fD, x[i]);
313 }
314 } else {
315 // If the nonlinear fit didn't converge, then try a linear fit on the full
316 // range. Note that fD must be strictly greater than 1 because the nonlinear
317 // segment starts at fD.
318 const float kEpsilon = 1.f / 256.f;
319 fn->fD = 1.f + kEpsilon;
320 }
321
322 // Compute the linear segment, now that we have our definitive fD.
323 SkTransferFnSolveLinear(fn, x, t, n);
324
325 // If the nonlinear fit didn't converge, re-express the linear fit in
326 // canonical form as a nonlinear fit with fG as 1.
327 if (!*nonlinear_fit_converged) {
328 fn->fA = fn->fC;
329 fn->fB = fn->fF;
330 fn->fC = 1;
331 fn->fD = 0;
332 fn->fE = 0;
333 fn->fF = 0;
334 fn->fG = 1;
335 }
336
337 // Return the L-infinity error of the total approximation.
338 *error = 0.f;
339 for (size_t i = 0; i < n; ++i)
340 *error = std::max(*error, std::abs(t[i] - SkTransferFnEval(*fn, x[i])));
341 }
342
343 } // namespace
14 344
15 float SkTransferFnEval(const SkColorSpaceTransferFn& fn, float x) { 345 float SkTransferFnEval(const SkColorSpaceTransferFn& fn, float x) {
16 if (x < 0.f) 346 if (x < 0.f)
17 return 0.f; 347 return 0.f;
18 if (x < fn.fD) 348 if (x < fn.fD)
19 return fn.fC * x + fn.fF; 349 return fn.fC * x + fn.fF;
20 return std::pow(fn.fA * x + fn.fB, fn.fG) + fn.fE; 350 return std::pow(fn.fA * x + fn.fB, fn.fG) + fn.fE;
21 } 351 }
22 352
23 SkColorSpaceTransferFn SkTransferFnInverse(const SkColorSpaceTransferFn& fn) { 353 SkColorSpaceTransferFn SkTransferFnInverse(const SkColorSpaceTransferFn& fn) {
(...skipping 30 matching lines...) Expand all
54 const float kStep = 1.f / 8.f; 384 const float kStep = 1.f / 8.f;
55 const float kEpsilon = 2.5f / 256.f; 385 const float kEpsilon = 2.5f / 256.f;
56 for (float x = 0; x <= 1.f; x += kStep) { 386 for (float x = 0; x <= 1.f; x += kStep) {
57 float a_of_x = SkTransferFnEval(a, x); 387 float a_of_x = SkTransferFnEval(a, x);
58 if (std::abs(a_of_x - x) > kEpsilon) 388 if (std::abs(a_of_x - x) > kEpsilon)
59 return false; 389 return false;
60 } 390 }
61 return true; 391 return true;
62 } 392 }
63 393
394 void SkApproximateTransferFn(const float* x,
395 const float* t,
396 size_t n,
397 SkColorSpaceTransferFn* fn,
398 float* error,
399 bool* nonlinear_fit_converged) {
400 SkApproximateTransferFnInternal(x, t, n, fn, error, nonlinear_fit_converged);
401 }
402
64 bool SkMatrixIsApproximatelyIdentity(const SkMatrix44& m) { 403 bool SkMatrixIsApproximatelyIdentity(const SkMatrix44& m) {
65 const float kEpsilon = 1.f / 256.f; 404 const float kEpsilon = 1.f / 256.f;
66 for (int i = 0; i < 4; ++i) { 405 for (int i = 0; i < 4; ++i) {
67 for (int j = 0; j < 4; ++j) { 406 for (int j = 0; j < 4; ++j) {
68 float identity_value = i == j ? 1 : 0; 407 float identity_value = i == j ? 1 : 0;
69 float value = m.get(i, j); 408 float value = m.get(i, j);
70 if (std::abs(identity_value - value) > kEpsilon) 409 if (std::abs(identity_value - value) > kEpsilon)
71 return false; 410 return false;
72 } 411 }
73 } 412 }
74 return true; 413 return true;
75 } 414 }
76 415
77 } // namespace gfx 416 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698