| OLD | NEW |
| 1 #include <cmath> | 1 #include <cmath> |
| 2 | 2 |
| 3 #include "SkBitmap.h" | 3 #include "SkBitmap.h" |
| 4 #include "skpdiff_util.h" | 4 #include "skpdiff_util.h" |
| 5 #include "SkPMetric.h" | 5 #include "SkPMetric.h" |
| 6 #include "SkPMetricUtil_generated.h" |
| 6 | 7 |
| 7 struct RGB { | 8 struct RGB { |
| 8 float r, g, b; | 9 float r, g, b; |
| 9 }; | 10 }; |
| 10 | 11 |
| 11 struct LAB { | 12 struct LAB { |
| 12 float l, a, b; | 13 float l, a, b; |
| 13 }; | 14 }; |
| 14 | 15 |
| 15 template<class T> | 16 template<class T> |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 105 const float yw = 0.297361f + 0.627355f + 0.0752847f; | 106 const float yw = 0.297361f + 0.627355f + 0.0752847f; |
| 106 const float zw = 0.0270328f + 0.0706879f + 0.991248f; | 107 const float zw = 0.0270328f + 0.0706879f + 0.991248f; |
| 107 | 108 |
| 108 // This is the XYZ color point relative to the white point | 109 // This is the XYZ color point relative to the white point |
| 109 float f[3] = { x / xw, y / yw, z / zw }; | 110 float f[3] = { x / xw, y / yw, z / zw }; |
| 110 | 111 |
| 111 // Conversion from XYZ to LAB taken from | 112 // Conversion from XYZ to LAB taken from |
| 112 // http://en.wikipedia.org/wiki/CIELAB#Forward_transformation | 113 // http://en.wikipedia.org/wiki/CIELAB#Forward_transformation |
| 113 for (int i = 0; i < 3; i++) { | 114 for (int i = 0; i < 3; i++) { |
| 114 if (f[i] >= 0.008856f) { | 115 if (f[i] >= 0.008856f) { |
| 115 f[i] = powf(f[i], 1.0f / 3.0f); | 116 f[i] = SkPMetricUtil::get_cube_root(f[i]); |
| 116 } else { | 117 } else { |
| 117 f[i] = 7.787f * f[i] + 4.0f / 29.0f; | 118 f[i] = 7.787f * f[i] + 4.0f / 29.0f; |
| 118 } | 119 } |
| 119 } | 120 } |
| 120 lab->l = 116.0f * f[1] - 16.0f; | 121 lab->l = 116.0f * f[1] - 16.0f; |
| 121 lab->a = 500.0f * (f[0] - f[1]); | 122 lab->a = 500.0f * (f[0] - f[1]); |
| 122 lab->b = 200.0f * (f[1] - f[2]); | 123 lab->b = 200.0f * (f[1] - f[2]); |
| 123 } | 124 } |
| 124 | 125 |
| 125 /// Converts a 8888 bitmap to LAB color space and puts it into the output | 126 /// Converts a 8888 bitmap to LAB color space and puts it into the output |
| 126 static void bitmap_to_cielab(const SkBitmap* bitmap, ImageLAB* outImageLAB) { | 127 static void bitmap_to_cielab(const SkBitmap* bitmap, ImageLAB* outImageLAB) { |
| 127 SkASSERT(bitmap->config() == SkBitmap::kARGB_8888_Config); | 128 SkASSERT(bitmap->config() == SkBitmap::kARGB_8888_Config); |
| 128 | 129 |
| 129 int width = bitmap->width(); | 130 int width = bitmap->width(); |
| 130 int height = bitmap->height(); | 131 int height = bitmap->height(); |
| 131 SkASSERT(outImageLAB->width == width); | 132 SkASSERT(outImageLAB->width == width); |
| 132 SkASSERT(outImageLAB->height == height); | 133 SkASSERT(outImageLAB->height == height); |
| 133 | 134 |
| 134 bitmap->lockPixels(); | 135 bitmap->lockPixels(); |
| 135 RGB rgb; | 136 RGB rgb; |
| 136 LAB lab; | 137 LAB lab; |
| 137 for (int y = 0; y < height; y++) { | 138 for (int y = 0; y < height; y++) { |
| 138 unsigned char* row = (unsigned char*)bitmap->getAddr(0, y); | 139 unsigned char* row = (unsigned char*)bitmap->getAddr(0, y); |
| 139 for (int x = 0; x < width; x++) { | 140 for (int x = 0; x < width; x++) { |
| 140 // Perform gamma correction which is assumed to be 2.2 | 141 // Perform gamma correction which is assumed to be 2.2 |
| 141 rgb.r = powf(row[x * 4 + 2] / 255.0f, 2.2f); | 142 rgb.r = SkPMetricUtil::get_gamma(row[x * 4 + 2]); |
| 142 rgb.g = powf(row[x * 4 + 1] / 255.0f, 2.2f); | 143 rgb.g = SkPMetricUtil::get_gamma(row[x * 4 + 1]); |
| 143 rgb.b = powf(row[x * 4 + 0] / 255.0f, 2.2f); | 144 rgb.b = SkPMetricUtil::get_gamma(row[x * 4 + 0]); |
| 144 adobergb_to_cielab(rgb.r, rgb.g, rgb.b, &lab); | 145 adobergb_to_cielab(rgb.r, rgb.g, rgb.b, &lab); |
| 145 outImageLAB->writePixel(x, y, lab); | 146 outImageLAB->writePixel(x, y, lab); |
| 146 } | 147 } |
| 147 } | 148 } |
| 148 bitmap->unlockPixels(); | 149 bitmap->unlockPixels(); |
| 149 } | 150 } |
| 150 | 151 |
| 151 // From Barten SPIE 1989 | 152 // From Barten SPIE 1989 |
| 152 static float contrast_sensitivity(float cyclesPerDegree, float luminance) { | 153 static float contrast_sensitivity(float cyclesPerDegree, float luminance) { |
| 153 float a = 440.0f * powf(1.0f + 0.7f / luminance, -0.2f); | 154 float a = 440.0f * powf(1.0f + 0.7f / luminance, -0.2f); |
| 154 float b = 0.3f * powf(1 + 100.0 / luminance, 0.15f); | 155 float b = 0.3f * powf(1 + 100.0 / luminance, 0.15f); |
| 155 return a * | 156 return a * |
| 156 cyclesPerDegree * | 157 cyclesPerDegree * |
| 157 expf(-b * cyclesPerDegree) * | 158 expf(-b * cyclesPerDegree) * |
| 158 sqrtf(1.0f + 0.06f * expf(b * cyclesPerDegree)); | 159 sqrtf(1.0f + 0.06f * expf(b * cyclesPerDegree)); |
| 159 } | 160 } |
| 160 | 161 |
| 162 #if 0 |
| 163 // We're keeping these around for reference and in case the lookup tables are no
longer desired. |
| 164 // They are no longer called by any code in this file. |
| 165 |
| 161 // From Daly 1993 | 166 // From Daly 1993 |
| 162 static float visual_mask(float contrast) { | 167 static float visual_mask(float contrast) { |
| 163 float x = powf(392.498f * contrast, 0.7f); | 168 float x = powf(392.498f * contrast, 0.7f); |
| 164 x = powf(0.0153f * x, 4.0f); | 169 x = powf(0.0153f * x, 4.0f); |
| 165 return powf(1.0f + x, 0.25f); | 170 return powf(1.0f + x, 0.25f); |
| 166 } | 171 } |
| 167 | 172 |
| 168 // From Ward Larson Siggraph 1997 | 173 // From Ward Larson Siggraph 1997 |
| 169 static float threshold_vs_intensity(float adaptationLuminance) { | 174 static float threshold_vs_intensity(float adaptationLuminance) { |
| 170 float logLum = log10f(adaptationLuminance); | 175 float logLum = log10f(adaptationLuminance); |
| 171 float x; | 176 float x; |
| 172 if (logLum < -3.94f) { | 177 if (logLum < -3.94f) { |
| 173 x = -2.86f; | 178 x = -2.86f; |
| 174 } else if (logLum < -1.44f) { | 179 } else if (logLum < -1.44f) { |
| 175 x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f; | 180 x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f; |
| 176 } else if (logLum < -0.0184f) { | 181 } else if (logLum < -0.0184f) { |
| 177 x = logLum - 0.395f; | 182 x = logLum - 0.395f; |
| 178 } else if (logLum < 1.9f) { | 183 } else if (logLum < 1.9f) { |
| 179 x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f; | 184 x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f; |
| 180 } else { | 185 } else { |
| 181 x = logLum - 1.255f; | 186 x = logLum - 1.255f; |
| 182 } | 187 } |
| 183 return powf(10.0f, x); | 188 return powf(10.0f, x); |
| 184 } | 189 } |
| 185 | 190 |
| 191 #endif |
| 192 |
| 186 /// Simply takes the L channel from the input and puts it into the output | 193 /// Simply takes the L channel from the input and puts it into the output |
| 187 static void lab_to_l(const ImageLAB* imageLAB, ImageL* outImageL) { | 194 static void lab_to_l(const ImageLAB* imageLAB, ImageL* outImageL) { |
| 188 for (int y = 0; y < imageLAB->height; y++) { | 195 for (int y = 0; y < imageLAB->height; y++) { |
| 189 for (int x = 0; x < imageLAB->width; x++) { | 196 for (int x = 0; x < imageLAB->width; x++) { |
| 190 LAB lab; | 197 LAB lab; |
| 191 imageLAB->readPixel(x, y, &lab); | 198 imageLAB->readPixel(x, y, &lab); |
| 192 outImageL->writePixel(x, y, lab.l); | 199 outImageL->writePixel(x, y, lab.l); |
| 193 } | 200 } |
| 194 } | 201 } |
| 195 } | 202 } |
| 196 | 203 |
| 197 /// Convolves an image with the given filter in one direction and saves it to th
e output image | 204 /// Convolves an image with the given filter in one direction and saves it to th
e output image |
| 198 static void convolve(const ImageL* imageL, bool vertical, ImageL* outImageL) { | 205 static void convolve(const ImageL* imageL, bool vertical, ImageL* outImageL) { |
| 199 SkASSERT(imageL->width == outImageL->width); | 206 SkASSERT(imageL->width == outImageL->width); |
| 200 SkASSERT(imageL->height == outImageL->height); | 207 SkASSERT(imageL->height == outImageL->height); |
| 201 | 208 |
| 202 const float matrix[] = { 0.05f, 0.25f, 0.4f, 0.25f, 0.05f }; | 209 const float matrix[] = { 0.05f, 0.25f, 0.4f, 0.25f, 0.05f }; |
| 203 const int matrixCount = sizeof(matrix) / sizeof(float); | 210 const int matrixCount = sizeof(matrix) / sizeof(float); |
| 204 const int radius = matrixCount / 2; | 211 const int radius = matrixCount / 2; |
| 205 | 212 |
| 206 // Keep track of what rows are being operated on for quick access. | 213 // Keep track of what rows are being operated on for quick access. |
| 207 float* rowPtrs[matrixCount]; // Because matrixCount is constant, this won't
create a VLA | 214 float* rowPtrs[matrixCount]; // Because matrixCount is constant, this won't
create a VLA |
| 208 for (int y = radius; y < matrixCount; y++) { | 215 for (int y = radius; y < matrixCount; y++) { |
| 209 rowPtrs[y] = imageL->getRow(y - radius); | 216 rowPtrs[y] = imageL->getRow(y - radius); |
| 210 } | 217 } |
| 211 float* writeRow = outImageL->getRow(0); | 218 float* writeRow = outImageL->getRow(0); |
| 212 | 219 |
| 213 | |
| 214 for (int y = 0; y < imageL->height; y++) { | 220 for (int y = 0; y < imageL->height; y++) { |
| 215 for (int x = 0; x < imageL->width; x++) { | 221 for (int x = 0; x < imageL->width; x++) { |
| 216 float lSum = 0.0f; | 222 float lSum = 0.0f; |
| 217 for (int xx = -radius; xx <= radius; xx++) { | 223 for (int xx = -radius; xx <= radius; xx++) { |
| 218 int nx = x; | 224 int nx = x; |
| 219 int ny = y; | 225 int ny = y; |
| 220 | 226 |
| 221 // We mirror at edges so that edge pixels that the filter weight
ing still makes | 227 // We mirror at edges so that edge pixels that the filter weight
ing still makes |
| 222 // sense. | 228 // sense. |
| 223 if (vertical) { | 229 if (vertical) { |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 271 | 277 |
| 272 lab_to_l(baselineLAB, baselineL.getLayer(0)); | 278 lab_to_l(baselineLAB, baselineL.getLayer(0)); |
| 273 lab_to_l(testLAB, testL.getLayer(0)); | 279 lab_to_l(testLAB, testL.getLayer(0)); |
| 274 | 280 |
| 275 // Compute cpd - Cycles per degree on the pyramid | 281 // Compute cpd - Cycles per degree on the pyramid |
| 276 cyclesPerDegree[0] = 0.5f * pixelsPerDegree; | 282 cyclesPerDegree[0] = 0.5f * pixelsPerDegree; |
| 277 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { | 283 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { |
| 278 cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f; | 284 cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f; |
| 279 } | 285 } |
| 280 | 286 |
| 287 // Contrast sensitivity is based on image dimensions. Therefore it cannot be
statically |
| 288 // generated. |
| 289 float* contrastSensitivityTable = SkNEW_ARRAY(float, maxLevels * 1000); |
| 290 for (int levelIndex = 0; levelIndex < maxLevels; levelIndex++) { |
| 291 for (int csLum = 0; csLum < 1000; csLum++) { |
| 292 contrastSensitivityTable[levelIndex * 1000 + csLum] = |
| 293 contrast_sensitivity(cyclesPerDegree[levelIndex], (float)csLum / 10.0
f + 1e-5f); |
| 294 } |
| 295 } |
| 296 |
| 281 // Compute G - The convolved lum for the baseline | 297 // Compute G - The convolved lum for the baseline |
| 282 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { | 298 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { |
| 283 convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL); | 299 convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL); |
| 284 convolve(&scratchImageL, true, baselineL.getLayer(levelIndex)); | 300 convolve(&scratchImageL, true, baselineL.getLayer(levelIndex)); |
| 285 } | 301 } |
| 286 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { | 302 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { |
| 287 convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL); | 303 convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL); |
| 288 convolve(&scratchImageL, true, testL.getLayer(levelIndex)); | 304 convolve(&scratchImageL, true, testL.getLayer(levelIndex)); |
| 289 } | 305 } |
| 290 | 306 |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 332 | 348 |
| 333 float baselineContrast2 = fabsf(baselineL2); | 349 float baselineContrast2 = fabsf(baselineL2); |
| 334 float testContrast2 = fabsf(testL2); | 350 float testContrast2 = fabsf(testL2); |
| 335 float denominator = (baselineContrast2 > testContrast2) ? | 351 float denominator = (baselineContrast2 > testContrast2) ? |
| 336 baselineContrast2 : testContrast2; | 352 baselineContrast2 : testContrast2; |
| 337 | 353 |
| 338 // Avoid divides by close to zero | 354 // Avoid divides by close to zero |
| 339 if (denominator < 1e-5) { | 355 if (denominator < 1e-5) { |
| 340 denominator = 1e-5; | 356 denominator = 1e-5; |
| 341 } | 357 } |
| 342 | |
| 343 contrast[levelIndex] = numerator / denominator; | 358 contrast[levelIndex] = numerator / denominator; |
| 344 contrastSum += contrast[levelIndex]; | 359 contrastSum += contrast[levelIndex]; |
| 345 } | 360 } |
| 346 | 361 |
| 347 if (contrastSum < 1e-5) { | 362 if (contrastSum < 1e-5) { |
| 348 contrastSum = 1e-5; | 363 contrastSum = 1e-5; |
| 349 } | 364 } |
| 350 | 365 |
| 351 float F = 0.0f; | 366 float F = 0.0f; |
| 352 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) { | 367 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) { |
| 353 float mask = visual_mask(contrast[levelIndex] * | 368 float contrastSensitivity = contrastSensitivityTable[levelIndex
* 1000 + |
| 354 contrast_sensitivity(cyclesPerDegree[levelIndex], l
Adapt)); | 369 (int)(lAdap
t * 10.0)]; |
| 370 float mask = SkPMetricUtil::get_visual_mask(contrast[levelIndex]
* |
| 371 contrastSensitivity)
; |
| 355 | 372 |
| 356 F += contrast[levelIndex] + | 373 F += contrast[levelIndex] + |
| 357 thresholdFactorFrequency[levelIndex] * mask / contrastSum; | 374 thresholdFactorFrequency[levelIndex] * mask / contrastSum; |
| 358 } | 375 } |
| 359 | 376 |
| 360 if (F < 1.0f) { | 377 if (F < 1.0f) { |
| 361 F = 1.0f; | 378 F = 1.0f; |
| 362 } | 379 } |
| 363 | 380 |
| 364 if (F > 10.0f) { | 381 if (F > 10.0f) { |
| 365 F = 10.0f; | 382 F = 10.0f; |
| 366 } | 383 } |
| 367 | 384 |
| 368 | 385 |
| 369 bool isFailure = false; | 386 bool isFailure = false; |
| 370 if (fabsf(lBaseline - lTest) > F * threshold_vs_intensity(lAdapt)) { | 387 if (fabsf(lBaseline - lTest) > F * SkPMetricUtil::get_threshold_vs_i
ntensity(lAdapt)) { |
| 371 isFailure = true; | 388 isFailure = true; |
| 372 } else { | 389 } else { |
| 373 LAB baselineColor; | 390 LAB baselineColor; |
| 374 LAB testColor; | 391 LAB testColor; |
| 375 baselineLAB->readPixel(x, y, &baselineColor); | 392 baselineLAB->readPixel(x, y, &baselineColor); |
| 376 testLAB->readPixel(x, y, &testColor); | 393 testLAB->readPixel(x, y, &testColor); |
| 377 float contrastA = baselineColor.a - testColor.a; | 394 float contrastA = baselineColor.a - testColor.a; |
| 378 float contrastB = baselineColor.b - testColor.b; | 395 float contrastB = baselineColor.b - testColor.b; |
| 379 float colorScale = 1.0f; | 396 float colorScale = 1.0f; |
| 380 if (lAdapt < 10.0f) { | 397 if (lAdapt < 10.0f) { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 391 if (isFailure) { | 408 if (isFailure) { |
| 392 failures++; | 409 failures++; |
| 393 poi->push()->set(x, y); | 410 poi->push()->set(x, y); |
| 394 } | 411 } |
| 395 } | 412 } |
| 396 } | 413 } |
| 397 | 414 |
| 398 SkDELETE_ARRAY(cyclesPerDegree); | 415 SkDELETE_ARRAY(cyclesPerDegree); |
| 399 SkDELETE_ARRAY(contrast); | 416 SkDELETE_ARRAY(contrast); |
| 400 SkDELETE_ARRAY(thresholdFactorFrequency); | 417 SkDELETE_ARRAY(thresholdFactorFrequency); |
| 418 SkDELETE_ARRAY(contrastSensitivityTable); |
| 401 return 1.0 - (double)failures / (width * height); | 419 return 1.0 - (double)failures / (width * height); |
| 402 } | 420 } |
| 403 | 421 |
| 404 const char* SkPMetric::getName() { | 422 const char* SkPMetric::getName() { |
| 405 return "perceptual"; | 423 return "perceptual"; |
| 406 } | 424 } |
| 407 | 425 |
| 408 int SkPMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { | 426 int SkPMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { |
| 427 double startTime = get_seconds(); |
| 409 int diffID = fQueuedDiffs.count(); | 428 int diffID = fQueuedDiffs.count(); |
| 410 double startTime = get_seconds(); | |
| 411 QueuedDiff& diff = fQueuedDiffs.push_back(); | 429 QueuedDiff& diff = fQueuedDiffs.push_back(); |
| 412 diff.result = 0.0; | 430 diff.result = 0.0; |
| 413 | 431 |
| 414 // Ensure the images are comparable | 432 // Ensure the images are comparable |
| 415 if (baseline->width() != test->width() || baseline->height() != test->height
() || | 433 if (baseline->width() != test->width() || baseline->height() != test->height
() || |
| 416 baseline->width() <= 0 || baseline->height() <= 0) { | 434 baseline->width() <= 0 || baseline->height() <= 0) { |
| 417 diff.finished = true; | 435 diff.finished = true; |
| 418 return diffID; | 436 return diffID; |
| 419 } | 437 } |
| 420 | 438 |
| 421 ImageLAB baselineLAB(baseline->width(), baseline->height()); | 439 ImageLAB baselineLAB(baseline->width(), baseline->height()); |
| 422 ImageLAB testLAB(baseline->width(), baseline->height()); | 440 ImageLAB testLAB(baseline->width(), baseline->height()); |
| 423 | 441 |
| 424 bitmap_to_cielab(baseline, &baselineLAB); | 442 bitmap_to_cielab(baseline, &baselineLAB); |
| 425 bitmap_to_cielab(test, &testLAB); | 443 bitmap_to_cielab(test, &testLAB); |
| 426 | 444 |
| 427 diff.result = pmetric(&baselineLAB, &testLAB, &diff.poi); | 445 diff.result = pmetric(&baselineLAB, &testLAB, &diff.poi); |
| 428 | 446 |
| 429 SkDebugf("Time: %f\n", (get_seconds() - startTime)); | 447 SkDebugf("Time: %f\n", (get_seconds() - startTime)); |
| 430 | 448 |
| 431 return diffID; | 449 return diffID; |
| 432 } | 450 } |
| 433 | 451 |
| 434 | 452 |
| 435 void SkPMetric::deleteDiff(int id) { | 453 void SkPMetric::deleteDiff(int id) { |
| 436 fQueuedDiffs[id].poi.reset(); | 454 |
| 437 } | 455 } |
| 438 | 456 |
| 439 bool SkPMetric::isFinished(int id) { | 457 bool SkPMetric::isFinished(int id) { |
| 440 return fQueuedDiffs[id].finished; | 458 return fQueuedDiffs[id].finished; |
| 441 } | 459 } |
| 442 | 460 |
| 443 double SkPMetric::getResult(int id) { | 461 double SkPMetric::getResult(int id) { |
| 444 return fQueuedDiffs[id].result; | 462 return fQueuedDiffs[id].result; |
| 445 } | 463 } |
| 446 | 464 |
| 447 int SkPMetric::getPointsOfInterestCount(int id) { | 465 int SkPMetric::getPointsOfInterestCount(int id) { |
| 448 return fQueuedDiffs[id].poi.count(); | 466 return fQueuedDiffs[id].poi.count(); |
| 449 } | 467 } |
| 450 | 468 |
| 451 SkIPoint* SkPMetric::getPointsOfInterest(int id) { | 469 SkIPoint* SkPMetric::getPointsOfInterest(int id) { |
| 452 return fQueuedDiffs[id].poi.begin(); | 470 return fQueuedDiffs[id].poi.begin(); |
| 453 } | 471 } |
| OLD | NEW |