1#include <cmath>
2#include <math.h>
3
4#include "SkBitmap.h"
5#include "skpdiff_util.h"
6#include "SkPMetric.h"
7#include "SkPMetricUtil_generated.h"
8
9struct RGB {
10    float r, g, b;
11};
12
13struct LAB {
14    float l, a, b;
15};
16
17template<class T>
18struct Image2D {
19    int width;
20    int height;
21    T* image;
22
23    Image2D(int w, int h)
24        : width(w),
25          height(h) {
26        SkASSERT(w > 0);
27        SkASSERT(h > 0);
28        image = SkNEW_ARRAY(T, w * h);
29    }
30
31    ~Image2D() {
32        SkDELETE_ARRAY(image);
33    }
34
35    void readPixel(int x, int y, T* pixel) const {
36        SkASSERT(x >= 0);
37        SkASSERT(y >= 0);
38        SkASSERT(x < width);
39        SkASSERT(y < height);
40        *pixel = image[y * width + x];
41    }
42
43    T* getRow(int y) const {
44        return &image[y * width];
45    }
46
47    void writePixel(int x, int y, const T& pixel) {
48        SkASSERT(x >= 0);
49        SkASSERT(y >= 0);
50        SkASSERT(x < width);
51        SkASSERT(y < height);
52        image[y * width + x] = pixel;
53    }
54};
55
56typedef Image2D<float> ImageL;
57typedef Image2D<RGB> ImageRGB;
58typedef Image2D<LAB> ImageLAB;
59
60template<class T>
61struct ImageArray
62{
63    int slices;
64    Image2D<T>** image;
65
66    ImageArray(int w, int h, int s)
67        : slices(s) {
68        SkASSERT(s > 0);
69        image = SkNEW_ARRAY(Image2D<T>*, s);
70        for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
71            image[sliceIndex] = SkNEW_ARGS(Image2D<T>, (w, h));
72        }
73    }
74
75    ~ImageArray() {
76        for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
77            SkDELETE(image[sliceIndex]);
78        }
79        SkDELETE_ARRAY(image);
80    }
81
82    Image2D<T>* getLayer(int z) const {
83        SkASSERT(z >= 0);
84        SkASSERT(z < slices);
85        return image[z];
86    }
87};
88
89typedef ImageArray<float> ImageL3D;
90
91
92#define MAT_ROW_MULT(rc,gc,bc) r*rc + g*gc + b*bc
93
94static void adobergb_to_cielab(float r, float g, float b, LAB* lab) {
95    // Conversion of Adobe RGB to XYZ taken from from "Adobe RGB (1998) ColorImage 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);
102
103    // The following is the white point in XYZ, so it's simply the row wise addition of the above
104    // matrix.
105    const float xw = 0.5767f + 0.185556f + 0.188212f;
106    const float yw = 0.297361f + 0.627355f + 0.0752847f;
107    const float zw = 0.0270328f + 0.0706879f + 0.991248f;
108
109    // This is the XYZ color point relative to the white point
110    float f[3] = { x / xw, y / yw, z / zw };
111
112    // Conversion from XYZ to LAB taken from
113    // http://en.wikipedia.org/wiki/CIELAB#Forward_transformation
114    for (int i = 0; i < 3; i++) {
115        if (f[i] >= 0.008856f) {
116            f[i] = SkPMetricUtil::get_cube_root(f[i]);
117        } else {
118            f[i] = 7.787f * f[i] + 4.0f / 29.0f;
119        }
120    }
121    lab->l = 116.0f * f[1] - 16.0f;
122    lab->a = 500.0f * (f[0] - f[1]);
123    lab->b = 200.0f * (f[1] - f[2]);
124}
125
126/// Converts a 8888 bitmap to LAB color space and puts it into the output
127static bool bitmap_to_cielab(const SkBitmap* bitmap, ImageLAB* outImageLAB) {
128    SkBitmap bm8888;
129    if (bitmap->colorType() != kN32_SkColorType) {
130        if (!bitmap->copyTo(&bm8888, kN32_SkColorType)) {
131            return false;
132        }
133        bitmap = &bm8888;
134    }
135
136    int width = bitmap->width();
137    int height = bitmap->height();
138    SkASSERT(outImageLAB->width == width);
139    SkASSERT(outImageLAB->height == height);
140
141    bitmap->lockPixels();
142    RGB rgb;
143    LAB lab;
144    for (int y = 0; y < height; y++) {
145        unsigned char* row = (unsigned char*)bitmap->getAddr(0, y);
146        for (int x = 0; x < width; x++) {
147            // Perform gamma correction which is assumed to be 2.2
148            rgb.r = SkPMetricUtil::get_gamma(row[x * 4 + 2]);
149            rgb.g = SkPMetricUtil::get_gamma(row[x * 4 + 1]);
150            rgb.b = SkPMetricUtil::get_gamma(row[x * 4 + 0]);
151            adobergb_to_cielab(rgb.r, rgb.g, rgb.b, &lab);
152            outImageLAB->writePixel(x, y, lab);
153        }
154    }
155    bitmap->unlockPixels();
156    return true;
157}
158
159// From Barten SPIE 1989
160static float contrast_sensitivity(float cyclesPerDegree, float luminance) {
161    float a = 440.0f * powf(1.0f + 0.7f / luminance, -0.2f);
162    float b = 0.3f * powf(1.0f + 100.0f / luminance, 0.15f);
163    float exp = expf(-b * cyclesPerDegree);
164    float root = sqrtf(1.0f + 0.06f * expf(b * cyclesPerDegree));
165    if (!SkScalarIsFinite(exp)  || !SkScalarIsFinite(root)) {
166        return 0;
167    }
168    return a * cyclesPerDegree * exp * root;
169}
170
171#if 0
172// We're keeping these around for reference and in case the lookup tables are no longer desired.
173// They are no longer called by any code in this file.
174
175// From Daly 1993
176 static float visual_mask(float contrast) {
177    float x = powf(392.498f * contrast, 0.7f);
178    x = powf(0.0153f * x, 4.0f);
179    return powf(1.0f + x, 0.25f);
180}
181
182// From Ward Larson Siggraph 1997
183static float threshold_vs_intensity(float adaptationLuminance) {
184    float logLum = log10f(adaptationLuminance);
185    float x;
186    if (logLum < -3.94f) {
187        x = -2.86f;
188    } else if (logLum < -1.44f) {
189        x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f;
190    } else if (logLum < -0.0184f) {
191        x = logLum - 0.395f;
192    } else if (logLum < 1.9f) {
193        x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f;
194    } else {
195        x = logLum - 1.255f;
196    }
197    return powf(10.0f, x);
198}
199
200#endif
201
202/// Simply takes the L channel from the input and puts it into the output
203static void lab_to_l(const ImageLAB* imageLAB, ImageL* outImageL) {
204    for (int y = 0; y < imageLAB->height; y++) {
205        for (int x = 0; x < imageLAB->width; x++) {
206            LAB lab;
207            imageLAB->readPixel(x, y, &lab);
208            outImageL->writePixel(x, y, lab.l);
209        }
210    }
211}
212
213/// Convolves an image with the given filter in one direction and saves it to the output image
214static void convolve(const ImageL* imageL, bool vertical, ImageL* outImageL) {
215    SkASSERT(imageL->width == outImageL->width);
216    SkASSERT(imageL->height == outImageL->height);
217
218    const float matrix[] = { 0.05f, 0.25f, 0.4f, 0.25f, 0.05f };
219    const int matrixCount = sizeof(matrix) / sizeof(float);
220    const int radius = matrixCount / 2;
221
222    // Keep track of what rows are being operated on for quick access.
223    float* rowPtrs[matrixCount]; // Because matrixCount is constant, this won't create a VLA
224    for (int y = radius; y < matrixCount; y++) {
225        rowPtrs[y] = imageL->getRow(y - radius);
226    }
227    float* writeRow = outImageL->getRow(0);
228
229    for (int y = 0; y < imageL->height; y++) {
230        for (int x = 0; x < imageL->width; x++) {
231            float lSum = 0.0f;
232            for (int xx = -radius; xx <= radius; xx++) {
233                int nx = x;
234                int ny = y;
235
236                // We mirror at edges so that edge pixels that the filter weighting still makes
237                // sense.
238                if (vertical) {
239                    ny += xx;
240                    if (ny < 0) {
241                        ny = -ny;
242                    }
243                    if (ny >= imageL->height) {
244                        ny = imageL->height + (imageL->height - ny - 1);
245                    }
246                } else {
247                    nx += xx;
248                    if (nx < 0) {
249                        nx = -nx;
250                    }
251                    if (nx >= imageL->width) {
252                        nx = imageL->width + (imageL->width - nx - 1);
253                    }
254                }
255
256                float weight = matrix[xx + radius];
257                lSum += rowPtrs[ny - y + radius][nx] * weight;
258            }
259            writeRow[x] = lSum;
260        }
261        // As we move down, scroll the row pointers down with us
262        for (int y = 0; y < matrixCount - 1; y++)
263        {
264            rowPtrs[y] = rowPtrs[y + 1];
265        }
266        rowPtrs[matrixCount - 1] += imageL->width;
267        writeRow += imageL->width;
268    }
269}
270
271static double pmetric(const ImageLAB* baselineLAB, const ImageLAB* testLAB, int* poiCount) {
272    SkASSERT(baselineLAB);
273    SkASSERT(testLAB);
274    SkASSERT(poiCount);
275
276    int width = baselineLAB->width;
277    int height = baselineLAB->height;
278    int maxLevels = 0;
279
280    // Calculates how many levels to make by how many times the image can be divided in two
281    int smallerDimension = width < height ? width : height;
282    for ( ; smallerDimension > 1; smallerDimension /= 2) {
283        maxLevels++;
284    }
285
286    // We'll be creating new arrays with maxLevels - 2, and ImageL3D requires maxLevels > 0,
287    // so just return failure if we're less than 3.
288    if (maxLevels <= 2) {
289        return 0.0;
290    }
291
292    const float fov = SK_ScalarPI / 180.0f * 45.0f;
293    float contrastSensitivityMax = contrast_sensitivity(3.248f, 100.0f);
294    float pixelsPerDegree = width / (2.0f * tanf(fov * 0.5f) * 180.0f / SK_ScalarPI);
295
296    ImageL3D baselineL(width, height, maxLevels);
297    ImageL3D testL(width, height, maxLevels);
298    ImageL scratchImageL(width, height);
299    float* cyclesPerDegree = SkNEW_ARRAY(float, maxLevels);
300    float* thresholdFactorFrequency = SkNEW_ARRAY(float, maxLevels - 2);
301    float* contrast = SkNEW_ARRAY(float, maxLevels - 2);
302
303    lab_to_l(baselineLAB, baselineL.getLayer(0));
304    lab_to_l(testLAB, testL.getLayer(0));
305
306    // Compute cpd - Cycles per degree on the pyramid
307    cyclesPerDegree[0] = 0.5f * pixelsPerDegree;
308    for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
309        cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f;
310    }
311
312    // Contrast sensitivity is based on image dimensions. Therefore it cannot be statically
313    // generated.
314    float* contrastSensitivityTable = SkNEW_ARRAY(float, maxLevels * 1000);
315    for (int levelIndex = 0; levelIndex < maxLevels; levelIndex++) {
316        for (int csLum = 0; csLum < 1000; csLum++) {
317           contrastSensitivityTable[levelIndex * 1000 + csLum] =
318           contrast_sensitivity(cyclesPerDegree[levelIndex], (float)csLum / 10.0f + 1e-5f);
319       }
320    }
321
322    // Compute G - The convolved lum for the baseline
323    for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
324        convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL);
325        convolve(&scratchImageL, true, baselineL.getLayer(levelIndex));
326    }
327    for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
328        convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL);
329        convolve(&scratchImageL, true, testL.getLayer(levelIndex));
330    }
331
332    // Compute F_freq - The elevation f
333    for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
334        float cpd = cyclesPerDegree[levelIndex];
335        thresholdFactorFrequency[levelIndex] = contrastSensitivityMax /
336                                               contrast_sensitivity(cpd, 100.0f);
337    }
338
339    // Calculate F
340    for (int y = 0; y < height; y++) {
341        for (int x = 0; x < width; x++) {
342            float lBaseline;
343            float lTest;
344            baselineL.getLayer(0)->readPixel(x, y, &lBaseline);
345            testL.getLayer(0)->readPixel(x, y, &lTest);
346
347            float avgLBaseline;
348            float avgLTest;
349            baselineL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLBaseline);
350            testL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLTest);
351
352            float lAdapt = 0.5f * (avgLBaseline + avgLTest);
353            if (lAdapt < 1e-5f) {
354                lAdapt = 1e-5f;
355            }
356
357            float contrastSum = 0.0f;
358            for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
359                float baselineL0, baselineL1, baselineL2;
360                float testL0, testL1, testL2;
361                baselineL.getLayer(levelIndex + 0)->readPixel(x, y, &baselineL0);
362                testL.    getLayer(levelIndex + 0)->readPixel(x, y, &testL0);
363                baselineL.getLayer(levelIndex + 1)->readPixel(x, y, &baselineL1);
364                testL.    getLayer(levelIndex + 1)->readPixel(x, y, &testL1);
365                baselineL.getLayer(levelIndex + 2)->readPixel(x, y, &baselineL2);
366                testL.    getLayer(levelIndex + 2)->readPixel(x, y, &testL2);
367
368                float baselineContrast1 = fabsf(baselineL0 - baselineL1);
369                float testContrast1     = fabsf(testL0 - testL1);
370                float numerator = (baselineContrast1 > testContrast1) ?
371                                   baselineContrast1 : testContrast1;
372
373                float baselineContrast2 = fabsf(baselineL2);
374                float testContrast2     = fabsf(testL2);
375                float denominator = (baselineContrast2 > testContrast2) ?
376                                    baselineContrast2 : testContrast2;
377
378                // Avoid divides by close to zero
379                if (denominator < 1e-5f) {
380                    denominator = 1e-5f;
381                }
382                contrast[levelIndex] = numerator / denominator;
383                contrastSum += contrast[levelIndex];
384            }
385
386            if (contrastSum < 1e-5f) {
387                contrastSum = 1e-5f;
388            }
389
390            float F = 0.0f;
391            for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
392                float contrastSensitivity = contrastSensitivityTable[levelIndex * 1000 +
393                                                                     (int)(lAdapt * 10.0)];
394                float mask = SkPMetricUtil::get_visual_mask(contrast[levelIndex] *
395                                                            contrastSensitivity);
396
397                F += contrast[levelIndex] +
398                     thresholdFactorFrequency[levelIndex] * mask / contrastSum;
399            }
400
401            if (F < 1.0f) {
402                F = 1.0f;
403            }
404
405            if (F > 10.0f) {
406                F = 10.0f;
407            }
408
409
410            bool isFailure = false;
411            if (fabsf(lBaseline - lTest) > F * SkPMetricUtil::get_threshold_vs_intensity(lAdapt)) {
412                isFailure = true;
413            } else {
414                LAB baselineColor;
415                LAB testColor;
416                baselineLAB->readPixel(x, y, &baselineColor);
417                testLAB->readPixel(x, y, &testColor);
418                float contrastA = baselineColor.a - testColor.a;
419                float contrastB = baselineColor.b - testColor.b;
420                float colorScale = 1.0f;
421                if (lAdapt < 10.0f) {
422                    colorScale = lAdapt / 10.0f;
423                }
424                colorScale *= colorScale;
425
426                if ((contrastA * contrastA + contrastB * contrastB) * colorScale > F)
427                {
428                    isFailure = true;
429                }
430            }
431
432            if (isFailure) {
433                (*poiCount)++;
434            }
435        }
436    }
437
438    SkDELETE_ARRAY(cyclesPerDegree);
439    SkDELETE_ARRAY(contrast);
440    SkDELETE_ARRAY(thresholdFactorFrequency);
441    SkDELETE_ARRAY(contrastSensitivityTable);
442    return 1.0 - (double)(*poiCount) / (width * height);
443}
444
445bool SkPMetric::diff(SkBitmap* baseline, SkBitmap* test, const BitmapsToCreate& bitmapsToCreate,
446                     Result* result) const {
447    double startTime = get_seconds();
448
449    // Ensure the images are comparable
450    if (baseline->width() != test->width() || baseline->height() != test->height() ||
451                    baseline->width() <= 0 || baseline->height() <= 0) {
452        return false;
453    }
454
455    ImageLAB baselineLAB(baseline->width(), baseline->height());
456    ImageLAB testLAB(baseline->width(), baseline->height());
457
458    if (!bitmap_to_cielab(baseline, &baselineLAB) || !bitmap_to_cielab(test, &testLAB)) {
459        return true;
460    }
461
462    result->poiCount = 0;
463    result->result = pmetric(&baselineLAB, &testLAB, &result->poiCount);
464    result->timeElapsed = get_seconds() - startTime;
465
466    return true;
467}
468