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 |