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

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

Issue 2717263003: color: Update ICC histograms based on results (Closed)
Patch Set: Add obsolete tags Created 3 years, 9 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/skia_color_space_util.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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" 10 #include "base/logging.h"
11 11
12 namespace gfx { 12 namespace gfx {
13 13
14 namespace { 14 namespace {
15 15
16 // Evaluate the gradient of the linear component of fn 16 // Solve for the parameter fC, given the parameter fD, assuming fF is zero.
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, 17 void SkTransferFnSolveLinear(SkColorSpaceTransferFn* fn,
27 const float* x, 18 const float* x,
28 const float* t, 19 const float* t,
29 size_t n) { 20 size_t n) {
30 // If this has no linear segment, don't try to solve for one. 21 // If this has no linear segment, don't try to solve for one.
31 if (fn->fD <= 0) { 22 fn->fC = 1;
32 fn->fC = 1; 23 fn->fF = 0;
33 fn->fF = 0; 24 if (fn->fD <= 0)
34 return; 25 return;
35 }
36 26
37 // Let ne_lhs be the left hand side of the normal equations, and let ne_rhs 27 // Because this is a linear least squares fit of a single variable, our normal
38 // be the right hand side. This is a 4x4 matrix, but we will only update the 28 // equations are 1x1. Use the same framework as in SolveNonlinear, even though
39 // upper-left 2x2 sub-matrix. 29 // this is pretty trivial.
40 SkMatrix44 ne_lhs; 30 float ne_lhs = 0;
41 SkVector4 ne_rhs; 31 float ne_rhs = 0;
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 32
50 // Add the contributions from each sample to the normal equations. 33 // 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) { 34 for (size_t i = 0; i < n; ++i) {
53 // Ignore points in the nonlinear segment. 35 // Ignore points in the nonlinear segment.
54 if (x[i] >= fn->fD) 36 if (x[i] >= fn->fD)
55 continue; 37 continue;
56 x_linear_max = std::max(x_linear_max, x[i]);
57 38
58 // Let J be the gradient of fn with respect to parameters C and F, evaluated 39 // Let J be the gradient of fn with respect to parameter C, evaluated at
59 // at this point. 40 // this point, and let r be the residual at this point.
60 float J[2]; 41 float J = x[i];
61 SkTransferFnEvalGradientLinear(*fn, x[i], &J[0], &J[1]);
62
63 // Let r be the residual at this point.
64 float r = t[i]; 42 float r = t[i];
65 43
66 // Update the normal equations left hand side with the outer product of J 44 // Update the normal equations left and right hand sides.
67 // with itself. 45 ne_lhs += J * J;
68 for (int row = 0; row < 2; ++row) { 46 ne_rhs += J * r;
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 } 47 }
77 48
78 // If we only have a single x point at 0, that isn't enough to construct a 49 // If we only had 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 50 // linear segment, so add an additional point connecting to the nonlinear
80 // segment. 51 // segment.
81 if (x_linear_max == 0) { 52 if (ne_lhs == 0) {
82 float J[2]; 53 float J = fn->fD;
83 SkTransferFnEvalGradientLinear(*fn, fn->fD, &J[0], &J[1]);
84 float r = SkTransferFnEval(*fn, fn->fD); 54 float r = SkTransferFnEval(*fn, fn->fD);
85 for (int row = 0; row < 2; ++row) { 55 ne_lhs += J * J;
86 for (int col = 0; col < 2; ++col) { 56 ne_lhs += J * r;
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 } 57 }
92 58
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. 59 // Update the transfer function.
100 fn->fC = solution.fData[0]; 60 fn->fC = ne_rhs / ne_lhs;
101 fn->fF = solution.fData[1]; 61 fn->fF = 0;
102 } 62 }
103 63
104 // Evaluate the gradient of the nonlinear component of fn 64 // Evaluate the gradient of the nonlinear component of fn. This assumes that
65 // |fn| maps 1 to 1, and therefore fE is implicity 1 - pow(fA + fB, fG).
105 void SkTransferFnEvalGradientNonlinear(const SkColorSpaceTransferFn& fn, 66 void SkTransferFnEvalGradientNonlinear(const SkColorSpaceTransferFn& fn,
106 float x, 67 float x,
107 float* d_fn_d_fA_at_x, 68 float* d_fn_d_fA_at_x,
108 float* d_fn_d_fB_at_x, 69 float* d_fn_d_fB_at_x,
109 float* d_fn_d_fE_at_x,
110 float* d_fn_d_fG_at_x) { 70 float* d_fn_d_fG_at_x) {
111 float base = fn.fA * x + fn.fB; 71 float Ax_plus_B = fn.fA * x + fn.fB;
112 if (base > 0.f) { 72 float A_plus_B = fn.fA + fn.fB;
113 *d_fn_d_fA_at_x = fn.fG * x * std::pow(base, fn.fG - 1.f); 73 if (Ax_plus_B >= 0.f && A_plus_B >= 0.f) {
114 *d_fn_d_fB_at_x = fn.fG * std::pow(base, fn.fG - 1.f); 74 float Ax_plus_B_to_G = std::pow(fn.fA * x + fn.fB, fn.fG);
115 *d_fn_d_fE_at_x = 1.f; 75 float Ax_plus_B_to_G_minus_1 = std::pow(fn.fA * x + fn.fB, fn.fG - 1.f);
116 *d_fn_d_fG_at_x = std::pow(base, fn.fG) * std::log(base); 76 float A_plus_B_to_G = std::pow(fn.fA + fn.fB, fn.fG);
77 float A_plus_B_to_G_minus_1 = std::pow(fn.fA + fn.fB, fn.fG - 1.f);
78 *d_fn_d_fA_at_x =
79 (x * Ax_plus_B_to_G_minus_1 - A_plus_B_to_G_minus_1) * fn.fG;
80 *d_fn_d_fB_at_x = (Ax_plus_B_to_G_minus_1 - A_plus_B_to_G_minus_1) * fn.fG;
81 *d_fn_d_fG_at_x = Ax_plus_B_to_G * std::log(Ax_plus_B) -
82 A_plus_B_to_G * std::log(A_plus_B);
117 } else { 83 } else {
118 *d_fn_d_fA_at_x = 0.f; 84 *d_fn_d_fA_at_x = 0.f;
119 *d_fn_d_fB_at_x = 0.f; 85 *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; 86 *d_fn_d_fG_at_x = 0.f;
122 } 87 }
123 } 88 }
124 89
125 // Take one Gauss-Newton step updating fA, fB, fE, and fG, given fD. 90 // Take one Gauss-Newton step updating fA, fB, fE, and fG, given fD.
126 bool SkTransferFnGaussNewtonStepNonlinear(SkColorSpaceTransferFn* fn, 91 bool SkTransferFnGaussNewtonStepNonlinear(SkColorSpaceTransferFn* fn,
127 float* error_Linfty_before, 92 float* error_Linfty_before,
128 const float* x, 93 const float* x,
129 const float* t, 94 const float* t,
130 size_t n) { 95 size_t n) {
131 float kEpsilon = 1.f / 1024.f; 96 float kEpsilon = 1.f / 1024.f;
132 // Let ne_lhs be the left hand side of the normal equations, and let ne_rhs 97 // Let ne_lhs be the left hand side of the normal equations, and let ne_rhs
133 // be the right hand side. 98 // be the right hand side. Zero the diagonal of |ne_lhs| and all of |ne_rhs|.
134 SkMatrix44 ne_lhs; 99 SkMatrix44 ne_lhs(SkMatrix44::kIdentity_Constructor);
135 SkVector4 ne_rhs; 100 SkVector4 ne_rhs;
136 for (int row = 0; row < 4; ++row) { 101 for (int row = 0; row < 3; ++row) {
137 for (int col = 0; col < 4; ++col) { 102 ne_lhs.set(row, row, 0);
138 ne_lhs.set(row, col, 0);
139 }
140 ne_rhs.fData[row] = 0; 103 ne_rhs.fData[row] = 0;
141 } 104 }
142 105
143 // Add the contributions from each sample to the normal equations. 106 // Add the contributions from each sample to the normal equations.
144 *error_Linfty_before = 0; 107 *error_Linfty_before = 0;
145 for (size_t i = 0; i < n; ++i) { 108 for (size_t i = 0; i < n; ++i) {
146 // Ignore points in the linear segment. 109 // Ignore points in the linear segment.
147 if (x[i] < fn->fD) 110 if (x[i] < fn->fD)
148 continue; 111 continue;
112
149 // Let J be the gradient of fn with respect to parameters A, B, E, and G, 113 // Let J be the gradient of fn with respect to parameters A, B, E, and G,
150 // evaulated at this point. 114 // evaulated at this point.
151 float J[4]; 115 float J[3];
152 SkTransferFnEvalGradientNonlinear(*fn, x[i], &J[0], &J[1], &J[2], &J[3]); 116 SkTransferFnEvalGradientNonlinear(*fn, x[i], &J[0], &J[1], &J[2]);
117
153 // Let r be the residual at this point; 118 // Let r be the residual at this point;
154 float r = t[i] - SkTransferFnEval(*fn, x[i]); 119 float r = t[i] - SkTransferFnEval(*fn, x[i]);
155 *error_Linfty_before += std::abs(r); 120 *error_Linfty_before += std::abs(r);
121
156 // Update the normal equations left hand side with the outer product of J 122 // Update the normal equations left hand side with the outer product of J
157 // with itself. 123 // with itself.
158 for (int row = 0; row < 4; ++row) { 124 for (int row = 0; row < 3; ++row) {
159 for (int col = 0; col < 4; ++col) { 125 for (int col = 0; col < 3; ++col) {
160 ne_lhs.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]); 126 ne_lhs.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]);
161 } 127 }
128
162 // Update the normal equations right hand side the product of J with the 129 // Update the normal equations right hand side the product of J with the
163 // residual 130 // residual
164 ne_rhs.fData[row] += J[row] * r; 131 ne_rhs.fData[row] += J[row] * r;
165 } 132 }
166 } 133 }
167 134
168 // Note that if fG = 1, then the normal equations will be singular (because, 135 // 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 136 // fB cancels out with itself. This could be handled better by using QR
170 // problems, fix fE (row/column 3) in these circumstances. 137 // factorization instead of solving the normal equations.
171 if (std::abs(fn->fG - 1) < kEpsilon) { 138 if (std::abs(fn->fG - 1) < kEpsilon) {
172 for (int row = 0; row < 4; ++row) { 139 for (int row = 0; row < 3; ++row) {
173 float value = (row == 2) ? 1.f : 0.f; 140 float value = (row == 1) ? 1.f : 0.f;
174 ne_lhs.set(row, 2, value); 141 ne_lhs.set(row, 1, value);
175 ne_lhs.set(2, row, value); 142 ne_lhs.set(1, row, value);
176 } 143 }
177 ne_rhs.fData[2] = 0.f; 144 ne_rhs.fData[1] = 0.f;
145 fn->fB = 0.f;
178 } 146 }
179 147
180 // Solve the normal equations. 148 // Solve the normal equations.
181 SkMatrix44 ne_lhs_inv; 149 SkMatrix44 ne_lhs_inv;
182 if (!ne_lhs.invert(&ne_lhs_inv)) 150 if (!ne_lhs.invert(&ne_lhs_inv))
183 return false; 151 return false;
184 SkVector4 step = ne_lhs_inv * ne_rhs; 152 SkVector4 step = ne_lhs_inv * ne_rhs;
185 153
186 // Update the transfer function. 154 // Update the transfer function.
187 fn->fA += step.fData[0]; 155 fn->fA += step.fData[0];
188 fn->fB += step.fData[1]; 156 fn->fB += step.fData[1];
189 fn->fE += step.fData[2]; 157 fn->fG += step.fData[2];
190 fn->fG += step.fData[3]; 158
159 // Clamp fA to the valid range.
160 fn->fA = std::max(fn->fA, 0.f);
161
162 // Shift fB to ensure that fA+fB > 0.
163 if (fn->fA + fn->fB < 0.f)
164 fn->fB = -fn->fA;
165
166 // Compute fE such that 1 maps to 1.
167 fn->fE = 1.f - std::pow(fn->fA + fn->fB, fn->fG);
191 return true; 168 return true;
192 } 169 }
193 170
194 // Solve for fA, fB, fE, and fG, given fD. The initial value of |fn| is the 171 // Solve for fA, fB, fE, and fG, given fD. The initial value of |fn| is the
195 // point from which iteration starts. 172 // point from which iteration starts.
196 bool SkTransferFnSolveNonlinear(SkColorSpaceTransferFn* fn, 173 bool SkTransferFnSolveNonlinear(SkColorSpaceTransferFn* fn,
197 const float* x, 174 const float* x,
198 const float* t, 175 const float* t,
199 size_t n) { 176 size_t n) {
200 // Take a maximum of 16 Gauss-Newton steps. 177 // Take a maximum of 16 Gauss-Newton steps.
(...skipping 30 matching lines...) Expand all
231 fn->fA = 1.f; 208 fn->fA = 1.f;
232 if (std::abs(fn->fB) < kRoundEpsilon) 209 if (std::abs(fn->fB) < kRoundEpsilon)
233 fn->fB = 0; 210 fn->fB = 0;
234 if (std::abs(fn->fE) < kRoundEpsilon) 211 if (std::abs(fn->fE) < kRoundEpsilon)
235 fn->fE = 0; 212 fn->fE = 0;
236 if (std::abs(fn->fG - 1.f) < kRoundEpsilon) 213 if (std::abs(fn->fG - 1.f) < kRoundEpsilon)
237 fn->fG = 1.f; 214 fn->fG = 1.f;
238 return true; 215 return true;
239 } 216 }
240 217
241 void SkApproximateTransferFnInternal(const float* x, 218 bool SkApproximateTransferFnInternal(const float* x,
242 const float* t, 219 const float* t,
243 size_t n, 220 size_t n,
244 SkColorSpaceTransferFn* fn, 221 SkColorSpaceTransferFn* fn) {
245 float* error,
246 bool* nonlinear_fit_converged) {
247 // First, guess at a value of fD. Assume that the nonlinear segment applies 222 // 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 223 // to all x >= 0.1. This is generally a safe assumption (fD is usually less
249 // than 0.1). 224 // than 0.1).
250 fn->fD = 0.1f; 225 fn->fD = 0.1f;
251 226
252 // Do a nonlinear regression on the nonlinear segment. Use a number of guesses 227 // 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. 228 // for the initial value of fG, because not all values will converge.
254 *nonlinear_fit_converged = false; 229 bool nonlinear_fit_converged = false;
255 { 230 {
256 const size_t kNumInitialGammas = 4; 231 const size_t kNumInitialGammas = 4;
257 float initial_gammas[kNumInitialGammas] = {1.f, 2.f, 3.f, 0.5f}; 232 float initial_gammas[kNumInitialGammas] = {2.f, 1.f, 3.f, 0.5f};
258 for (size_t i = 0; i < kNumInitialGammas; ++i) { 233 for (size_t i = 0; i < kNumInitialGammas; ++i) {
259 fn->fG = initial_gammas[i]; 234 fn->fG = initial_gammas[i];
260 fn->fA = 1; 235 fn->fA = 1;
261 fn->fB = 0; 236 fn->fB = 0;
262 fn->fC = 1; 237 fn->fC = 1;
263 fn->fE = 0; 238 fn->fE = 0;
264 fn->fF = 0; 239 fn->fF = 0;
265 if (SkTransferFnSolveNonlinear(fn, x, t, n)) { 240 if (SkTransferFnSolveNonlinear(fn, x, t, n)) {
266 *nonlinear_fit_converged = true; 241 nonlinear_fit_converged = true;
267 break; 242 break;
268 } 243 }
269 } 244 }
270 } 245 }
246 if (!nonlinear_fit_converged)
247 return false;
271 248
272 // Now walk back fD from our initial guess to the point where our nonlinear 249 // 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). 250 // fit no longer fits (or all the way to 0 if it fits).
274 if (*nonlinear_fit_converged) { 251 {
275 // Find the L-infinity error of this nonlinear fit (using our old fD value). 252 // Find the L-infinity error of this nonlinear fit (using our old fD value).
276 float max_error_in_nonlinear_fit = 0; 253 float max_error_in_nonlinear_fit = 0;
277 for (size_t i = 0; i < n; ++i) { 254 for (size_t i = 0; i < n; ++i) {
278 if (x[i] < fn->fD) 255 if (x[i] < fn->fD)
279 continue; 256 continue;
280 float error_at_xi = std::abs(t[i] - SkTransferFnEval(*fn, x[i])); 257 float error_at_xi = std::abs(t[i] - SkTransferFnEval(*fn, x[i]));
281 max_error_in_nonlinear_fit = 258 max_error_in_nonlinear_fit =
282 std::max(max_error_in_nonlinear_fit, error_at_xi); 259 std::max(max_error_in_nonlinear_fit, error_at_xi);
283 } 260 }
284 261
(...skipping 19 matching lines...) Expand all
304 } 281 }
305 } 282 }
306 283
307 // Now let fD be the highest sample of x that is above the threshold where 284 // Now let fD be the highest sample of x that is above the threshold where
308 // the nonlinear segment does not fit. 285 // the nonlinear segment does not fit.
309 fn->fD = 1.f; 286 fn->fD = 1.f;
310 for (size_t i = 0; i < n; ++i) { 287 for (size_t i = 0; i < n; ++i) {
311 if (x[i] > max_x_where_nonlinear_does_not_fit) 288 if (x[i] > max_x_where_nonlinear_does_not_fit)
312 fn->fD = std::min(fn->fD, x[i]); 289 fn->fD = std::min(fn->fD, x[i]);
313 } 290 }
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 } 291 }
321 292
322 // Compute the linear segment, now that we have our definitive fD. 293 // Compute the linear segment, now that we have our definitive fD.
323 SkTransferFnSolveLinear(fn, x, t, n); 294 SkTransferFnSolveLinear(fn, x, t, n);
324 295 return true;
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 } 296 }
342 297
343 } // namespace 298 } // namespace
344 299
345 float SkTransferFnEval(const SkColorSpaceTransferFn& fn, float x) { 300 float SkTransferFnEval(const SkColorSpaceTransferFn& fn, float x) {
346 if (x < 0.f) 301 if (x < 0.f)
347 return 0.f; 302 return 0.f;
348 if (x < fn.fD) 303 if (x < fn.fD)
349 return fn.fC * x + fn.fF; 304 return fn.fC * x + fn.fF;
350 return std::pow(fn.fA * x + fn.fB, fn.fG) + fn.fE; 305 return std::pow(fn.fA * x + fn.fB, fn.fG) + fn.fE;
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
384 const float kStep = 1.f / 8.f; 339 const float kStep = 1.f / 8.f;
385 const float kEpsilon = 2.5f / 256.f; 340 const float kEpsilon = 2.5f / 256.f;
386 for (float x = 0; x <= 1.f; x += kStep) { 341 for (float x = 0; x <= 1.f; x += kStep) {
387 float a_of_x = SkTransferFnEval(a, x); 342 float a_of_x = SkTransferFnEval(a, x);
388 if (std::abs(a_of_x - x) > kEpsilon) 343 if (std::abs(a_of_x - x) > kEpsilon)
389 return false; 344 return false;
390 } 345 }
391 return true; 346 return true;
392 } 347 }
393 348
394 void SkApproximateTransferFn(const float* x, 349 bool SkApproximateTransferFn(const float* x,
395 const float* t, 350 const float* t,
396 size_t n, 351 size_t n,
397 SkColorSpaceTransferFn* fn, 352 SkColorSpaceTransferFn* fn) {
398 float* error, 353 if (SkApproximateTransferFnInternal(x, t, n, fn))
399 bool* nonlinear_fit_converged) { 354 return true;
400 SkApproximateTransferFnInternal(x, t, n, fn, error, nonlinear_fit_converged); 355 fn->fA = 1;
356 fn->fB = 0;
357 fn->fC = 1;
358 fn->fD = 0;
359 fn->fE = 0;
360 fn->fF = 0;
361 fn->fG = 1;
362 return false;
401 } 363 }
402 364
403 bool SkMatrixIsApproximatelyIdentity(const SkMatrix44& m) { 365 bool SkMatrixIsApproximatelyIdentity(const SkMatrix44& m) {
404 const float kEpsilon = 1.f / 256.f; 366 const float kEpsilon = 1.f / 256.f;
405 for (int i = 0; i < 4; ++i) { 367 for (int i = 0; i < 4; ++i) {
406 for (int j = 0; j < 4; ++j) { 368 for (int j = 0; j < 4; ++j) {
407 float identity_value = i == j ? 1 : 0; 369 float identity_value = i == j ? 1 : 0;
408 float value = m.get(i, j); 370 float value = m.get(i, j);
409 if (std::abs(identity_value - value) > kEpsilon) 371 if (std::abs(identity_value - value) > kEpsilon)
410 return false; 372 return false;
411 } 373 }
412 } 374 }
413 return true; 375 return true;
414 } 376 }
415 377
416 } // namespace gfx 378 } // namespace gfx
OLDNEW
« no previous file with comments | « 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