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

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

Powered by Google App Engine
This is Rietveld 408576698