Home | History | Annotate | Download | only in skpdiff
      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 
     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 
     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 
     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 
     89 typedef ImageArray<float> ImageL3D;
     90 
     91 
     92 #define MAT_ROW_MULT(rc,gc,bc) r*rc + g*gc + b*bc
     93 
     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
    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
    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
    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
    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 
    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 
    445 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