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