• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
9 struct RGB {
10     float r, g, b;
11 };
12 
13 struct LAB {
14     float l, a, b;
15 };
16 
17 template<class T>
18 struct Image2D {
19     int width;
20     int height;
21     T* image;
22 
Image2DImage2D23     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 
~Image2DImage2D31     ~Image2D() {
32         SkDELETE_ARRAY(image);
33     }
34 
readPixelImage2D35     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 
getRowImage2D43     T* getRow(int y) const {
44         return &image[y * width];
45     }
46 
writePixelImage2D47     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 
56 typedef Image2D<float> ImageL;
57 typedef Image2D<RGB> ImageRGB;
58 typedef Image2D<LAB> ImageLAB;
59 
60 template<class T>
61 struct ImageArray
62 {
63     int slices;
64     Image2D<T>** image;
65 
ImageArrayImageArray66     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 
~ImageArrayImageArray75     ~ImageArray() {
76         for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
77             SkDELETE(image[sliceIndex]);
78         }
79         SkDELETE_ARRAY(image);
80     }
81 
getLayerImageArray82     Image2D<T>* getLayer(int z) const {
83         SkASSERT(z >= 0);
84         SkASSERT(z < slices);
85         return image[z];
86     }
87 };
88 
89 typedef ImageArray<float> ImageL3D;
90 
91 
92 #define MAT_ROW_MULT(rc,gc,bc) r*rc + g*gc + b*bc
93 
adobergb_to_cielab(float r,float g,float b,LAB * lab)94 static 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
bitmap_to_cielab(const SkBitmap * bitmap,ImageLAB * outImageLAB)127 static 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
contrast_sensitivity(float cyclesPerDegree,float luminance)160 static 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
183 static 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
lab_to_l(const ImageLAB * imageLAB,ImageL * outImageL)203 static 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
convolve(const ImageL * imageL,bool vertical,ImageL * outImageL)214 static 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 
pmetric(const ImageLAB * baselineLAB,const ImageLAB * testLAB,int * poiCount)271 static 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 
diff(SkBitmap * baseline,SkBitmap * test,bool computeMask,Result * result) const445 bool SkPMetric::diff(SkBitmap* baseline, SkBitmap* test, bool computeMask, Result* result) const {
446     double startTime = get_seconds();
447 
448     // Ensure the images are comparable
449     if (baseline->width() != test->width() || baseline->height() != test->height() ||
450                     baseline->width() <= 0 || baseline->height() <= 0) {
451         return false;
452     }
453 
454     ImageLAB baselineLAB(baseline->width(), baseline->height());
455     ImageLAB testLAB(baseline->width(), baseline->height());
456 
457     if (!bitmap_to_cielab(baseline, &baselineLAB) || !bitmap_to_cielab(test, &testLAB)) {
458         return true;
459     }
460 
461     result->poiCount = 0;
462     result->result = pmetric(&baselineLAB, &testLAB, &result->poiCount);
463     result->timeElapsed = get_seconds() - startTime;
464 
465     return true;
466 }
467