Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / chromeos / login / screenshot_testing / SkPMetric.cpp
blob0f71ad5d94416c7cdb47acbb4106fca78cf6f31a
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
3 // found in the LICENSE file.
5 // WARNING! This file is copied from third_party/skia/tools/skpdiff and slightly
6 // modified to be compilable outside Skia and suit chromium style. Some comments
7 // can make no sense.
8 // TODO(elizavetai): remove this file and reuse the original one in Skia
10 #include <cmath>
11 #include <math.h>
13 #include "chrome/browser/chromeos/login/screenshot_testing/SkPMetric.h"
14 #include "chrome/browser/chromeos/login/screenshot_testing/SkPMetricUtil_gen.h"
15 #include "third_party/skia/include/core/SkBitmap.h"
17 struct RGB {
18 float r, g, b;
21 struct LAB {
22 float l, a, b;
25 template <class T>
26 struct Image2D {
27 int width;
28 int height;
29 T* image;
31 Image2D(int w, int h) : width(w), height(h) {
32 DCHECK(w > 0);
33 DCHECK(h > 0);
34 image = new T[w * h];
37 ~Image2D() { delete[] image; }
39 void readPixel(int x, int y, T* pixel) const {
40 DCHECK(x >= 0);
41 DCHECK(y >= 0);
42 DCHECK(x < width);
43 DCHECK(y < height);
44 *pixel = image[y * width + x];
47 T* getRow(int y) const { return &image[y * width]; }
49 void writePixel(int x, int y, const T& pixel) {
50 DCHECK(x >= 0);
51 DCHECK(y >= 0);
52 DCHECK(x < width);
53 DCHECK(y < height);
54 image[y * width + x] = pixel;
58 typedef Image2D<float> ImageL;
59 typedef Image2D<RGB> ImageRGB;
60 typedef Image2D<LAB> ImageLAB;
62 template <class T>
63 struct ImageArray {
64 int slices;
65 Image2D<T>** image;
67 ImageArray(int w, int h, int s) : slices(s) {
68 DCHECK(s > 0);
69 image = new Image2D<T>*[s];
70 for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
71 image[sliceIndex] = new Image2D<T>(w, h);
75 ~ImageArray() {
76 for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
77 delete image[sliceIndex];
79 delete[] image;
82 Image2D<T>* getLayer(int z) const {
83 DCHECK(z >= 0);
84 DCHECK(z < slices);
85 return image[z];
89 typedef ImageArray<float> ImageL3D;
91 #define MAT_ROW_MULT(rc, gc, bc) r* rc + g* gc + b* bc
93 static void adobergb_to_cielab(float r, float g, float b, LAB* lab) {
94 // Conversion of Adobe RGB to XYZ taken from from "Adobe RGB (1998) ColorImage
95 // Encoding"
96 // URL:http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
97 // Section: 4.3.5.3
98 // See Also: http://en.wikipedia.org/wiki/Adobe_rgb
99 float x = MAT_ROW_MULT(0.57667f, 0.18556f, 0.18823f);
100 float y = MAT_ROW_MULT(0.29734f, 0.62736f, 0.07529f);
101 float z = MAT_ROW_MULT(0.02703f, 0.07069f, 0.99134f);
103 // The following is the white point in XYZ, so it's simply the row wise
104 // addition of the above
105 // matrix.
106 const float xw = 0.5767f + 0.185556f + 0.188212f;
107 const float yw = 0.297361f + 0.627355f + 0.0752847f;
108 const float zw = 0.0270328f + 0.0706879f + 0.991248f;
110 // This is the XYZ color point relative to the white point
111 float f[3] = {x / xw, y / yw, z / zw};
113 // Conversion from XYZ to LAB taken from
114 // http://en.wikipedia.org/wiki/CIELAB#Forward_transformation
115 for (int i = 0; i < 3; i++) {
116 if (f[i] >= 0.008856f) {
117 f[i] = SkPMetricUtil::get_cube_root(f[i]);
118 } else {
119 f[i] = 7.787f * f[i] + 4.0f / 29.0f;
122 lab->l = 116.0f * f[1] - 16.0f;
123 lab->a = 500.0f * (f[0] - f[1]);
124 lab->b = 200.0f * (f[1] - f[2]);
127 /// Converts a 8888 bitmap to LAB color space and puts it into the output
128 static bool bitmap_to_cielab(const SkBitmap* bitmap, ImageLAB* outImageLAB) {
129 SkBitmap bm8888;
130 if (bitmap->colorType() != kN32_SkColorType) {
131 if (!bitmap->copyTo(&bm8888, kN32_SkColorType)) {
132 return false;
134 bitmap = &bm8888;
137 int width = bitmap->width();
138 int height = bitmap->height();
139 DCHECK(outImageLAB->width == width);
140 DCHECK(outImageLAB->height == height);
142 bitmap->lockPixels();
143 RGB rgb;
144 LAB lab;
145 for (int y = 0; y < height; y++) {
146 unsigned char* row = (unsigned char*)bitmap->getAddr(0, y);
147 for (int x = 0; x < width; x++) {
148 // Perform gamma correction which is assumed to be 2.2
149 rgb.r = SkPMetricUtil::get_gamma(row[x * 4 + 2]);
150 rgb.g = SkPMetricUtil::get_gamma(row[x * 4 + 1]);
151 rgb.b = SkPMetricUtil::get_gamma(row[x * 4 + 0]);
152 adobergb_to_cielab(rgb.r, rgb.g, rgb.b, &lab);
153 outImageLAB->writePixel(x, y, lab);
156 bitmap->unlockPixels();
157 return true;
160 // From Barten SPIE 1989
161 static float contrast_sensitivity(float cyclesPerDegree, float luminance) {
162 float a = 440.0f * powf(1.0f + 0.7f / luminance, -0.2f);
163 float b = 0.3f * powf(1.0f + 100.0f / luminance, 0.15f);
164 float exp = expf(-b * cyclesPerDegree);
165 float root = sqrtf(1.0f + 0.06f * expf(b * cyclesPerDegree));
166 if (!SkScalarIsFinite(exp) || !SkScalarIsFinite(root)) {
167 return 0;
169 return a * cyclesPerDegree * exp * root;
172 #if 0
173 // We're keeping these around for reference and in case
174 // the lookup tables are no longer desired.
175 // They are no longer called by any code in this file.
177 // From Daly 1993
178 static float visual_mask(float contrast) {
179 float x = powf(392.498f * contrast, 0.7f);
180 x = powf(0.0153f * x, 4.0f);
181 return powf(1.0f + x, 0.25f);
184 // From Ward Larson Siggraph 1997
185 static float threshold_vs_intensity(float adaptationLuminance) {
186 float logLum = log10f(adaptationLuminance);
187 float x;
188 if (logLum < -3.94f) {
189 x = -2.86f;
190 } else if (logLum < -1.44f) {
191 x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f;
192 } else if (logLum < -0.0184f) {
193 x = logLum - 0.395f;
194 } else if (logLum < 1.9f) {
195 x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f;
196 } else {
197 x = logLum - 1.255f;
199 return powf(10.0f, x);
202 #endif
204 /// Simply takes the L channel from the input and puts it into the output
205 static void lab_to_l(const ImageLAB* imageLAB, ImageL* outImageL) {
206 for (int y = 0; y < imageLAB->height; y++) {
207 for (int x = 0; x < imageLAB->width; x++) {
208 LAB lab;
209 imageLAB->readPixel(x, y, &lab);
210 outImageL->writePixel(x, y, lab.l);
215 /// Convolves an image with the given filter in one direction and saves it to
216 /// the output image
217 static void convolve(const ImageL* imageL, bool vertical, ImageL* outImageL) {
218 DCHECK(imageL->width == outImageL->width);
219 DCHECK(imageL->height == outImageL->height);
221 const float matrix[] = {0.05f, 0.25f, 0.4f, 0.25f, 0.05f};
222 const int matrixCount = sizeof(matrix) / sizeof(float);
223 const int radius = matrixCount / 2;
225 // Keep track of what rows are being operated on for quick access.
226 float* rowPtrs[matrixCount]; // Because matrixCount is constant, this won't
227 // create a VLA
228 for (int y = radius; y < matrixCount; y++) {
229 rowPtrs[y] = imageL->getRow(y - radius);
231 float* writeRow = outImageL->getRow(0);
233 for (int y = 0; y < imageL->height; y++) {
234 for (int x = 0; x < imageL->width; x++) {
235 float lSum = 0.0f;
236 for (int xx = -radius; xx <= radius; xx++) {
237 int nx = x;
238 int ny = y;
240 // We mirror at edges so that edge pixels that the filter weighting
241 // still makes
242 // sense.
243 if (vertical) {
244 ny += xx;
245 if (ny < 0) {
246 ny = -ny;
248 if (ny >= imageL->height) {
249 ny = imageL->height + (imageL->height - ny - 1);
251 } else {
252 nx += xx;
253 if (nx < 0) {
254 nx = -nx;
256 if (nx >= imageL->width) {
257 nx = imageL->width + (imageL->width - nx - 1);
261 float weight = matrix[xx + radius];
262 lSum += rowPtrs[ny - y + radius][nx] * weight;
264 writeRow[x] = lSum;
266 // As we move down, scroll the row pointers down with us
267 for (int y = 0; y < matrixCount - 1; y++) {
268 rowPtrs[y] = rowPtrs[y + 1];
270 rowPtrs[matrixCount - 1] += imageL->width;
271 writeRow += imageL->width;
275 static double pmetric(const ImageLAB* baselineLAB,
276 const ImageLAB* testLAB,
277 int* poiCount) {
278 DCHECK(baselineLAB);
279 DCHECK(testLAB);
280 DCHECK(poiCount);
282 int width = baselineLAB->width;
283 int height = baselineLAB->height;
284 int maxLevels = 0;
286 // Calculates how many levels to make by how many times the image can be
287 // divided in two
288 int smallerDimension = width < height ? width : height;
289 for (; smallerDimension > 1; smallerDimension /= 2) {
290 maxLevels++;
293 // We'll be creating new arrays with maxLevels - 2, and ImageL3D requires
294 // maxLevels > 0,
295 // so just return failure if we're less than 3.
296 if (maxLevels <= 2) {
297 return 0.0;
300 const float fov = SK_ScalarPI / 180.0f * 45.0f;
301 float contrastSensitivityMax = contrast_sensitivity(3.248f, 100.0f);
302 float pixelsPerDegree =
303 width / (2.0f * tanf(fov * 0.5f) * 180.0f / SK_ScalarPI);
305 ImageL3D baselineL(width, height, maxLevels);
306 ImageL3D testL(width, height, maxLevels);
307 ImageL scratchImageL(width, height);
308 float* cyclesPerDegree = new float[maxLevels];
309 float* thresholdFactorFrequency = new float[maxLevels - 2];
310 float* contrast = new float[maxLevels - 2];
312 lab_to_l(baselineLAB, baselineL.getLayer(0));
313 lab_to_l(testLAB, testL.getLayer(0));
315 // Compute cpd - Cycles per degree on the pyramid
316 cyclesPerDegree[0] = 0.5f * pixelsPerDegree;
317 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
318 cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f;
321 // Contrast sensitivity is based on image dimensions. Therefore it cannot be
322 // statically
323 // generated.
324 float* contrastSensitivityTable = new float[maxLevels * 1000];
325 for (int levelIndex = 0; levelIndex < maxLevels; levelIndex++) {
326 for (int csLum = 0; csLum < 1000; csLum++) {
327 contrastSensitivityTable[levelIndex * 1000 + csLum] =
328 contrast_sensitivity(cyclesPerDegree[levelIndex],
329 (float)csLum / 10.0f + 1e-5f);
333 // Compute G - The convolved lum for the baseline
334 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
335 convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL);
336 convolve(&scratchImageL, true, baselineL.getLayer(levelIndex));
338 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
339 convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL);
340 convolve(&scratchImageL, true, testL.getLayer(levelIndex));
343 // Compute F_freq - The elevation f
344 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
345 float cpd = cyclesPerDegree[levelIndex];
346 thresholdFactorFrequency[levelIndex] =
347 contrastSensitivityMax / contrast_sensitivity(cpd, 100.0f);
350 // Calculate F
351 for (int y = 0; y < height; y++) {
352 for (int x = 0; x < width; x++) {
353 float lBaseline;
354 float lTest;
355 baselineL.getLayer(0)->readPixel(x, y, &lBaseline);
356 testL.getLayer(0)->readPixel(x, y, &lTest);
358 float avgLBaseline;
359 float avgLTest;
360 baselineL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLBaseline);
361 testL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLTest);
363 float lAdapt = 0.5f * (avgLBaseline + avgLTest);
364 if (lAdapt < 1e-5f) {
365 lAdapt = 1e-5f;
368 float contrastSum = 0.0f;
369 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
370 float baselineL0, baselineL1, baselineL2;
371 float testL0, testL1, testL2;
372 baselineL.getLayer(levelIndex + 0)->readPixel(x, y, &baselineL0);
373 testL.getLayer(levelIndex + 0)->readPixel(x, y, &testL0);
374 baselineL.getLayer(levelIndex + 1)->readPixel(x, y, &baselineL1);
375 testL.getLayer(levelIndex + 1)->readPixel(x, y, &testL1);
376 baselineL.getLayer(levelIndex + 2)->readPixel(x, y, &baselineL2);
377 testL.getLayer(levelIndex + 2)->readPixel(x, y, &testL2);
379 float baselineContrast1 = fabsf(baselineL0 - baselineL1);
380 float testContrast1 = fabsf(testL0 - testL1);
381 float numerator = (baselineContrast1 > testContrast1)
382 ? baselineContrast1
383 : testContrast1;
385 float baselineContrast2 = fabsf(baselineL2);
386 float testContrast2 = fabsf(testL2);
387 float denominator = (baselineContrast2 > testContrast2)
388 ? baselineContrast2
389 : testContrast2;
391 // Avoid divides by close to zero
392 if (denominator < 1e-5f) {
393 denominator = 1e-5f;
395 contrast[levelIndex] = numerator / denominator;
396 contrastSum += contrast[levelIndex];
399 if (contrastSum < 1e-5f) {
400 contrastSum = 1e-5f;
403 float F = 0.0f;
404 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
405 float contrastSensitivity =
406 contrastSensitivityTable[levelIndex * 1000 + (int)(lAdapt * 10.0)];
407 float mask = SkPMetricUtil::get_visual_mask(contrast[levelIndex] *
408 contrastSensitivity);
410 F += contrast[levelIndex] +
411 thresholdFactorFrequency[levelIndex] * mask / contrastSum;
414 if (F < 1.0f) {
415 F = 1.0f;
418 if (F > 10.0f) {
419 F = 10.0f;
422 bool isFailure = false;
423 if (fabsf(lBaseline - lTest) >
424 F * SkPMetricUtil::get_threshold_vs_intensity(lAdapt)) {
425 isFailure = true;
426 } else {
427 LAB baselineColor;
428 LAB testColor;
429 baselineLAB->readPixel(x, y, &baselineColor);
430 testLAB->readPixel(x, y, &testColor);
431 float contrastA = baselineColor.a - testColor.a;
432 float contrastB = baselineColor.b - testColor.b;
433 float colorScale = 1.0f;
434 if (lAdapt < 10.0f) {
435 colorScale = lAdapt / 10.0f;
437 colorScale *= colorScale;
439 if ((contrastA * contrastA + contrastB * contrastB) * colorScale > F) {
440 isFailure = true;
444 if (isFailure) {
445 (*poiCount)++;
450 delete[] cyclesPerDegree;
451 delete[] contrast;
452 delete[] thresholdFactorFrequency;
453 delete[] contrastSensitivityTable;
454 return 1.0 - (double)(*poiCount) / (width * height);
457 bool SkPMetric::diff(SkBitmap* baseline,
458 SkBitmap* test,
459 const SkImageDiffer::BitmapsToCreate& bitmapsToCreate,
460 SkImageDiffer::Result* result) {
461 // Ensure the images are comparable
462 if (baseline->width() != test->width() ||
463 baseline->height() != test->height() || baseline->width() <= 0 ||
464 baseline->height() <= 0) {
465 return false;
468 ImageLAB baselineLAB(baseline->width(), baseline->height());
469 ImageLAB testLAB(baseline->width(), baseline->height());
471 if (!bitmap_to_cielab(baseline, &baselineLAB) ||
472 !bitmap_to_cielab(test, &testLAB)) {
473 return true;
476 result->poiCount = 0;
477 result->result = pmetric(&baselineLAB, &testLAB, &result->poiCount);
479 return true;