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 |
161 // From Daly 1993 | 162 // From Daly 1993 |
162 static float visual_mask(float contrast) { | 163 float visual_mask(float contrast) { |
163 float x = powf(392.498f * contrast, 0.7f); | 164 float x = powf(392.498f * contrast, 0.7f); |
164 x = powf(0.0153f * x, 4.0f); | 165 x = powf(0.0153f * x, 4.0f); |
165 return powf(1.0f + x, 0.25f); | 166 return powf(1.0f + x, 0.25f); |
166 } | 167 } |
167 | 168 |
168 // From Ward Larson Siggraph 1997 | 169 // From Ward Larson Siggraph 1997 |
169 static float threshold_vs_intensity(float adaptationLuminance) { | 170 float threshold_vs_intensity(float adaptationLuminance) { |
170 float logLum = log10f(adaptationLuminance); | 171 float logLum = log10f(adaptationLuminance); |
171 float x; | 172 float x; |
172 if (logLum < -3.94f) { | 173 if (logLum < -3.94f) { |
173 x = -2.86f; | 174 x = -2.86f; |
174 } else if (logLum < -1.44f) { | 175 } else if (logLum < -1.44f) { |
175 x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f; | 176 x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f; |
176 } else if (logLum < -0.0184f) { | 177 } else if (logLum < -0.0184f) { |
177 x = logLum - 0.395f; | 178 x = logLum - 0.395f; |
178 } else if (logLum < 1.9f) { | 179 } else if (logLum < 1.9f) { |
179 x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f; | 180 x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f; |
(...skipping 23 matching lines...) Expand all Loading... | |
203 const int matrixCount = sizeof(matrix) / sizeof(float); | 204 const int matrixCount = sizeof(matrix) / sizeof(float); |
204 const int radius = matrixCount / 2; | 205 const int radius = matrixCount / 2; |
205 | 206 |
206 // Keep track of what rows are being operated on for quick access. | 207 // 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 | 208 float* rowPtrs[matrixCount]; // Because matrixCount is constant, this won't create a VLA |
208 for (int y = radius; y < matrixCount; y++) { | 209 for (int y = radius; y < matrixCount; y++) { |
209 rowPtrs[y] = imageL->getRow(y - radius); | 210 rowPtrs[y] = imageL->getRow(y - radius); |
210 } | 211 } |
211 float* writeRow = outImageL->getRow(0); | 212 float* writeRow = outImageL->getRow(0); |
212 | 213 |
213 | |
214 for (int y = 0; y < imageL->height; y++) { | 214 for (int y = 0; y < imageL->height; y++) { |
215 for (int x = 0; x < imageL->width; x++) { | 215 for (int x = 0; x < imageL->width; x++) { |
216 float lSum = 0.0f; | 216 float lSum = 0.0f; |
217 for (int xx = -radius; xx <= radius; xx++) { | 217 for (int xx = -radius; xx <= radius; xx++) { |
218 int nx = x; | 218 int nx = x; |
219 int ny = y; | 219 int ny = y; |
220 | 220 |
221 // We mirror at edges so that edge pixels that the filter weight ing still makes | 221 // We mirror at edges so that edge pixels that the filter weight ing still makes |
222 // sense. | 222 // sense. |
223 if (vertical) { | 223 if (vertical) { |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
271 | 271 |
272 lab_to_l(baselineLAB, baselineL.getLayer(0)); | 272 lab_to_l(baselineLAB, baselineL.getLayer(0)); |
273 lab_to_l(testLAB, testL.getLayer(0)); | 273 lab_to_l(testLAB, testL.getLayer(0)); |
274 | 274 |
275 // Compute cpd - Cycles per degree on the pyramid | 275 // Compute cpd - Cycles per degree on the pyramid |
276 cyclesPerDegree[0] = 0.5f * pixelsPerDegree; | 276 cyclesPerDegree[0] = 0.5f * pixelsPerDegree; |
277 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { | 277 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { |
278 cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f; | 278 cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f; |
279 } | 279 } |
280 | 280 |
281 // Contrast sensitivity is based on image dimensions. Therefore it cannot be statically | |
282 // generated. | |
283 float * contrastSensitivityTable = SkNEW_ARRAY(float, maxLevels * 1000); | |
djsollen
2013/07/16 13:55:11
float*
| |
284 for (int levelIndex = 0; levelIndex < maxLevels; levelIndex++) { | |
285 for (int csLum = 0; csLum < 1000; csLum++) { | |
286 contrastSensitivityTable[levelIndex * 1000 + csLum] = | |
287 contrast_sensitivity(cyclesPerDegree[levelIndex], (float)csLum / 10.0 f + 1e-5f); | |
288 } | |
289 } | |
290 | |
281 // Compute G - The convolved lum for the baseline | 291 // Compute G - The convolved lum for the baseline |
282 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { | 292 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { |
283 convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL); | 293 convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL); |
284 convolve(&scratchImageL, true, baselineL.getLayer(levelIndex)); | 294 convolve(&scratchImageL, true, baselineL.getLayer(levelIndex)); |
285 } | 295 } |
286 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { | 296 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) { |
287 convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL); | 297 convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL); |
288 convolve(&scratchImageL, true, testL.getLayer(levelIndex)); | 298 convolve(&scratchImageL, true, testL.getLayer(levelIndex)); |
289 } | 299 } |
290 | 300 |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
332 | 342 |
333 float baselineContrast2 = fabsf(baselineL2); | 343 float baselineContrast2 = fabsf(baselineL2); |
334 float testContrast2 = fabsf(testL2); | 344 float testContrast2 = fabsf(testL2); |
335 float denominator = (baselineContrast2 > testContrast2) ? | 345 float denominator = (baselineContrast2 > testContrast2) ? |
336 baselineContrast2 : testContrast2; | 346 baselineContrast2 : testContrast2; |
337 | 347 |
338 // Avoid divides by close to zero | 348 // Avoid divides by close to zero |
339 if (denominator < 1e-5) { | 349 if (denominator < 1e-5) { |
340 denominator = 1e-5; | 350 denominator = 1e-5; |
341 } | 351 } |
342 | |
343 contrast[levelIndex] = numerator / denominator; | 352 contrast[levelIndex] = numerator / denominator; |
344 contrastSum += contrast[levelIndex]; | 353 contrastSum += contrast[levelIndex]; |
345 } | 354 } |
346 | 355 |
347 if (contrastSum < 1e-5) { | 356 if (contrastSum < 1e-5) { |
348 contrastSum = 1e-5; | 357 contrastSum = 1e-5; |
349 } | 358 } |
350 | 359 |
351 float F = 0.0f; | 360 float F = 0.0f; |
352 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) { | 361 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) { |
353 float mask = visual_mask(contrast[levelIndex] * | 362 float mask = SkPMetricUtil::get_visual_mask(contrast[levelIndex] * |
354 contrast_sensitivity(cyclesPerDegree[levelIndex], l Adapt)); | 363 contrastSensitivityTable[levelInde x * 1000 + (int)(lAdapt * 10.0)]); |
djsollen
2013/07/16 13:55:11
100 char limit
| |
355 | 364 |
356 F += contrast[levelIndex] + | 365 F += contrast[levelIndex] + |
357 thresholdFactorFrequency[levelIndex] * mask / contrastSum; | 366 thresholdFactorFrequency[levelIndex] * mask / contrastSum; |
358 } | 367 } |
359 | 368 |
360 if (F < 1.0f) { | 369 if (F < 1.0f) { |
361 F = 1.0f; | 370 F = 1.0f; |
362 } | 371 } |
363 | 372 |
364 if (F > 10.0f) { | 373 if (F > 10.0f) { |
365 F = 10.0f; | 374 F = 10.0f; |
366 } | 375 } |
367 | 376 |
368 | 377 |
369 bool isFailure = false; | 378 bool isFailure = false; |
370 if (fabsf(lBaseline - lTest) > F * threshold_vs_intensity(lAdapt)) { | 379 if (fabsf(lBaseline - lTest) > F * SkPMetricUtil::get_threshold_vs_i ntensity(lAdapt)) { |
371 isFailure = true; | 380 isFailure = true; |
372 } else { | 381 } else { |
373 LAB baselineColor; | 382 LAB baselineColor; |
374 LAB testColor; | 383 LAB testColor; |
375 baselineLAB->readPixel(x, y, &baselineColor); | 384 baselineLAB->readPixel(x, y, &baselineColor); |
376 testLAB->readPixel(x, y, &testColor); | 385 testLAB->readPixel(x, y, &testColor); |
377 float contrastA = baselineColor.a - testColor.a; | 386 float contrastA = baselineColor.a - testColor.a; |
378 float contrastB = baselineColor.b - testColor.b; | 387 float contrastB = baselineColor.b - testColor.b; |
379 float colorScale = 1.0f; | 388 float colorScale = 1.0f; |
380 if (lAdapt < 10.0f) { | 389 if (lAdapt < 10.0f) { |
(...skipping 10 matching lines...) Expand all Loading... | |
391 if (isFailure) { | 400 if (isFailure) { |
392 failures++; | 401 failures++; |
393 poi->push()->set(x, y); | 402 poi->push()->set(x, y); |
394 } | 403 } |
395 } | 404 } |
396 } | 405 } |
397 | 406 |
398 SkDELETE_ARRAY(cyclesPerDegree); | 407 SkDELETE_ARRAY(cyclesPerDegree); |
399 SkDELETE_ARRAY(contrast); | 408 SkDELETE_ARRAY(contrast); |
400 SkDELETE_ARRAY(thresholdFactorFrequency); | 409 SkDELETE_ARRAY(thresholdFactorFrequency); |
410 SkDELETE_ARRAY(contrastSensitivityTable); | |
401 return 1.0 - (double)failures / (width * height); | 411 return 1.0 - (double)failures / (width * height); |
402 } | 412 } |
403 | 413 |
404 const char* SkPMetric::getName() { | 414 const char* SkPMetric::getName() { |
405 return "perceptual"; | 415 return "perceptual"; |
406 } | 416 } |
407 | 417 |
408 int SkPMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { | 418 int SkPMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) { |
419 double startTime = get_seconds(); | |
409 int diffID = fQueuedDiffs.count(); | 420 int diffID = fQueuedDiffs.count(); |
410 double startTime = get_seconds(); | |
411 QueuedDiff& diff = fQueuedDiffs.push_back(); | 421 QueuedDiff& diff = fQueuedDiffs.push_back(); |
412 diff.result = 0.0; | 422 diff.result = 0.0; |
413 | 423 |
414 // Ensure the images are comparable | 424 // Ensure the images are comparable |
415 if (baseline->width() != test->width() || baseline->height() != test->height () || | 425 if (baseline->width() != test->width() || baseline->height() != test->height () || |
416 baseline->width() <= 0 || baseline->height() <= 0) { | 426 baseline->width() <= 0 || baseline->height() <= 0) { |
417 diff.finished = true; | 427 diff.finished = true; |
418 return diffID; | 428 return diffID; |
419 } | 429 } |
420 | 430 |
421 ImageLAB baselineLAB(baseline->width(), baseline->height()); | 431 ImageLAB baselineLAB(baseline->width(), baseline->height()); |
422 ImageLAB testLAB(baseline->width(), baseline->height()); | 432 ImageLAB testLAB(baseline->width(), baseline->height()); |
423 | 433 |
424 bitmap_to_cielab(baseline, &baselineLAB); | 434 bitmap_to_cielab(baseline, &baselineLAB); |
425 bitmap_to_cielab(test, &testLAB); | 435 bitmap_to_cielab(test, &testLAB); |
426 | 436 |
427 diff.result = pmetric(&baselineLAB, &testLAB, &diff.poi); | 437 diff.result = pmetric(&baselineLAB, &testLAB, &diff.poi); |
428 | 438 |
429 SkDebugf("Time: %f\n", (get_seconds() - startTime)); | 439 SkDebugf("Time: %f\n", (get_seconds() - startTime)); |
430 | 440 |
431 return diffID; | 441 return diffID; |
432 } | 442 } |
433 | 443 |
434 | 444 |
435 void SkPMetric::deleteDiff(int id) { | 445 void SkPMetric::deleteDiff(int id) { |
436 fQueuedDiffs[id].poi.reset(); | 446 //fQueuedDiffs[id].poi.reset(); |
437 } | 447 } |
438 | 448 |
439 bool SkPMetric::isFinished(int id) { | 449 bool SkPMetric::isFinished(int id) { |
440 return fQueuedDiffs[id].finished; | 450 return fQueuedDiffs[id].finished; |
441 } | 451 } |
442 | 452 |
443 double SkPMetric::getResult(int id) { | 453 double SkPMetric::getResult(int id) { |
444 return fQueuedDiffs[id].result; | 454 return fQueuedDiffs[id].result; |
445 } | 455 } |
446 | 456 |
447 int SkPMetric::getPointsOfInterestCount(int id) { | 457 int SkPMetric::getPointsOfInterestCount(int id) { |
448 return fQueuedDiffs[id].poi.count(); | 458 return fQueuedDiffs[id].poi.count(); |
449 } | 459 } |
450 | 460 |
451 SkIPoint* SkPMetric::getPointsOfInterest(int id) { | 461 SkIPoint* SkPMetric::getPointsOfInterest(int id) { |
452 return fQueuedDiffs[id].poi.begin(); | 462 return fQueuedDiffs[id].poi.begin(); |
453 } | 463 } |
OLD | NEW |