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
8 // TODO(elizavetai): remove this file and reuse the original one in Skia
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"
31 Image2D(int w
, int h
) : width(w
), height(h
) {
37 ~Image2D() { delete[] image
; }
39 void readPixel(int x
, int y
, T
* pixel
) const {
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
) {
54 image
[y
* width
+ x
] = pixel
;
58 typedef Image2D
<float> ImageL
;
59 typedef Image2D
<RGB
> ImageRGB
;
60 typedef Image2D
<LAB
> ImageLAB
;
67 ImageArray(int w
, int h
, int s
) : slices(s
) {
69 image
= new Image2D
<T
>*[s
];
70 for (int sliceIndex
= 0; sliceIndex
< slices
; sliceIndex
++) {
71 image
[sliceIndex
] = new Image2D
<T
>(w
, h
);
76 for (int sliceIndex
= 0; sliceIndex
< slices
; sliceIndex
++) {
77 delete image
[sliceIndex
];
82 Image2D
<T
>* getLayer(int z
) const {
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
96 // URL:http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
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
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
]);
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
) {
130 if (bitmap
->colorType() != kN32_SkColorType
) {
131 if (!bitmap
->copyTo(&bm8888
, kN32_SkColorType
)) {
137 int width
= bitmap
->width();
138 int height
= bitmap
->height();
139 DCHECK(outImageLAB
->width
== width
);
140 DCHECK(outImageLAB
->height
== height
);
142 bitmap
->lockPixels();
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();
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
)) {
169 return a
* cyclesPerDegree
* exp
* root
;
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.
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
);
188 if (logLum
< -3.94f
) {
190 } else if (logLum
< -1.44f
) {
191 x
= powf(0.405f
* logLum
+ 1.6f
, 2.18) - 2.86f
;
192 } else if (logLum
< -0.0184f
) {
194 } else if (logLum
< 1.9f
) {
195 x
= powf(0.249f
* logLum
+ 0.65f
, 2.7f
) - 0.72f
;
199 return powf(10.0f
, x
);
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
++) {
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
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
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
++) {
236 for (int xx
= -radius
; xx
<= radius
; xx
++) {
240 // We mirror at edges so that edge pixels that the filter weighting
248 if (ny
>= imageL
->height
) {
249 ny
= imageL
->height
+ (imageL
->height
- ny
- 1);
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
;
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
,
282 int width
= baselineLAB
->width
;
283 int height
= baselineLAB
->height
;
286 // Calculates how many levels to make by how many times the image can be
288 int smallerDimension
= width
< height
? width
: height
;
289 for (; smallerDimension
> 1; smallerDimension
/= 2) {
293 // We'll be creating new arrays with maxLevels - 2, and ImageL3D requires
295 // so just return failure if we're less than 3.
296 if (maxLevels
<= 2) {
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
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
);
351 for (int y
= 0; y
< height
; y
++) {
352 for (int x
= 0; x
< width
; x
++) {
355 baselineL
.getLayer(0)->readPixel(x
, y
, &lBaseline
);
356 testL
.getLayer(0)->readPixel(x
, y
, &lTest
);
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
) {
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
)
385 float baselineContrast2
= fabsf(baselineL2
);
386 float testContrast2
= fabsf(testL2
);
387 float denominator
= (baselineContrast2
> testContrast2
)
391 // Avoid divides by close to zero
392 if (denominator
< 1e-5f
) {
395 contrast
[levelIndex
] = numerator
/ denominator
;
396 contrastSum
+= contrast
[levelIndex
];
399 if (contrastSum
< 1e-5f
) {
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
;
422 bool isFailure
= false;
423 if (fabsf(lBaseline
- lTest
) >
424 F
* SkPMetricUtil::get_threshold_vs_intensity(lAdapt
)) {
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
) {
450 delete[] cyclesPerDegree
;
452 delete[] thresholdFactorFrequency
;
453 delete[] contrastSensitivityTable
;
454 return 1.0 - (double)(*poiCount
) / (width
* height
);
457 bool SkPMetric::diff(SkBitmap
* baseline
,
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) {
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
)) {
476 result
->poiCount
= 0;
477 result
->result
= pmetric(&baselineLAB
, &testLAB
, &result
->poiCount
);