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