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