Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(907)

Side by Side Diff: experimental/skpdiff/SkPMetric.cpp

Issue 19608005: move skpdiff into tools (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « experimental/skpdiff/SkPMetric.h ('k') | experimental/skpdiff/SkPMetricUtil_generated.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #include <cmath>
2
3 #include "SkBitmap.h"
4 #include "skpdiff_util.h"
5 #include "SkPMetric.h"
6 #include "SkPMetricUtil_generated.h"
7
8 struct RGB {
9 float r, g, b;
10 };
11
12 struct LAB {
13 float l, a, b;
14 };
15
16 template<class T>
17 struct Image2D {
18 int width;
19 int height;
20 T* image;
21
22 Image2D(int w, int h)
23 : width(w),
24 height(h) {
25 SkASSERT(w > 0);
26 SkASSERT(h > 0);
27 image = SkNEW_ARRAY(T, w * h);
28 }
29
30 ~Image2D() {
31 SkDELETE_ARRAY(image);
32 }
33
34 void readPixel(int x, int y, T* pixel) const {
35 SkASSERT(x >= 0);
36 SkASSERT(y >= 0);
37 SkASSERT(x < width);
38 SkASSERT(y < height);
39 *pixel = image[y * width + x];
40 }
41
42 T* getRow(int y) const {
43 return &image[y * width];
44 }
45
46 void writePixel(int x, int y, const T& pixel) {
47 SkASSERT(x >= 0);
48 SkASSERT(y >= 0);
49 SkASSERT(x < width);
50 SkASSERT(y < height);
51 image[y * width + x] = pixel;
52 }
53 };
54
55 typedef Image2D<float> ImageL;
56 typedef Image2D<RGB> ImageRGB;
57 typedef Image2D<LAB> ImageLAB;
58
59 template<class T>
60 struct ImageArray
61 {
62 int slices;
63 Image2D<T>** image;
64
65 ImageArray(int w, int h, int s)
66 : slices(s) {
67 SkASSERT(s > 0);
68 image = SkNEW_ARRAY(Image2D<T>*, s);
69 for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
70 image[sliceIndex] = SkNEW_ARGS(Image2D<T>, (w, h));
71 }
72 }
73
74 ~ImageArray() {
75 for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
76 SkDELETE(image[sliceIndex]);
77 }
78 SkDELETE_ARRAY(image);
79 }
80
81 Image2D<T>* getLayer(int z) const {
82 SkASSERT(z >= 0);
83 SkASSERT(z < slices);
84 return image[z];
85 }
86 };
87
88 typedef ImageArray<float> ImageL3D;
89
90
91 #define MAT_ROW_MULT(rc,gc,bc) r*rc + g*gc + b*bc
92
93 static void adobergb_to_cielab(float r, float g, float b, LAB* lab) {
94 // Conversion of Adobe RGB to XYZ taken from from "Adobe RGB (1998) ColorIma ge Encoding"
95 // URL:http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
96 // Section: 4.3.5.3
97 // See Also: http://en.wikipedia.org/wiki/Adobe_rgb
98 float x = MAT_ROW_MULT(0.57667f, 0.18556f, 0.18823f);
99 float y = MAT_ROW_MULT(0.29734f, 0.62736f, 0.07529f);
100 float z = MAT_ROW_MULT(0.02703f, 0.07069f, 0.99134f);
101
102 // The following is the white point in XYZ, so it's simply the row wise addi tion of the above
103 // matrix.
104 const float xw = 0.5767f + 0.185556f + 0.188212f;
105 const float yw = 0.297361f + 0.627355f + 0.0752847f;
106 const float zw = 0.0270328f + 0.0706879f + 0.991248f;
107
108 // This is the XYZ color point relative to the white point
109 float f[3] = { x / xw, y / yw, z / zw };
110
111 // Conversion from XYZ to LAB taken from
112 // http://en.wikipedia.org/wiki/CIELAB#Forward_transformation
113 for (int i = 0; i < 3; i++) {
114 if (f[i] >= 0.008856f) {
115 f[i] = SkPMetricUtil::get_cube_root(f[i]);
116 } else {
117 f[i] = 7.787f * f[i] + 4.0f / 29.0f;
118 }
119 }
120 lab->l = 116.0f * f[1] - 16.0f;
121 lab->a = 500.0f * (f[0] - f[1]);
122 lab->b = 200.0f * (f[1] - f[2]);
123 }
124
125 /// 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 SkASSERT(bitmap->config() == SkBitmap::kARGB_8888_Config);
128
129 int width = bitmap->width();
130 int height = bitmap->height();
131 SkASSERT(outImageLAB->width == width);
132 SkASSERT(outImageLAB->height == height);
133
134 bitmap->lockPixels();
135 RGB rgb;
136 LAB lab;
137 for (int y = 0; y < height; y++) {
138 unsigned char* row = (unsigned char*)bitmap->getAddr(0, y);
139 for (int x = 0; x < width; x++) {
140 // Perform gamma correction which is assumed to be 2.2
141 rgb.r = SkPMetricUtil::get_gamma(row[x * 4 + 2]);
142 rgb.g = SkPMetricUtil::get_gamma(row[x * 4 + 1]);
143 rgb.b = SkPMetricUtil::get_gamma(row[x * 4 + 0]);
144 adobergb_to_cielab(rgb.r, rgb.g, rgb.b, &lab);
145 outImageLAB->writePixel(x, y, lab);
146 }
147 }
148 bitmap->unlockPixels();
149 }
150
151 // From Barten SPIE 1989
152 static float contrast_sensitivity(float cyclesPerDegree, float luminance) {
153 float a = 440.0f * powf(1.0f + 0.7f / luminance, -0.2f);
154 float b = 0.3f * powf(1.0f + 100.0f / luminance, 0.15f);
155 return a *
156 cyclesPerDegree *
157 expf(-b * cyclesPerDegree) *
158 sqrtf(1.0f + 0.06f * expf(b * cyclesPerDegree));
159 }
160
161 #if 0
162 // We're keeping these around for reference and in case the lookup tables are no longer desired.
163 // They are no longer called by any code in this file.
164
165 // From Daly 1993
166 static float visual_mask(float contrast) {
167 float x = powf(392.498f * contrast, 0.7f);
168 x = powf(0.0153f * x, 4.0f);
169 return powf(1.0f + x, 0.25f);
170 }
171
172 // From Ward Larson Siggraph 1997
173 static float threshold_vs_intensity(float adaptationLuminance) {
174 float logLum = log10f(adaptationLuminance);
175 float x;
176 if (logLum < -3.94f) {
177 x = -2.86f;
178 } else if (logLum < -1.44f) {
179 x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f;
180 } else if (logLum < -0.0184f) {
181 x = logLum - 0.395f;
182 } else if (logLum < 1.9f) {
183 x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f;
184 } else {
185 x = logLum - 1.255f;
186 }
187 return powf(10.0f, x);
188 }
189
190 #endif
191
192 /// Simply takes the L channel from the input and puts it into the output
193 static void lab_to_l(const ImageLAB* imageLAB, ImageL* outImageL) {
194 for (int y = 0; y < imageLAB->height; y++) {
195 for (int x = 0; x < imageLAB->width; x++) {
196 LAB lab;
197 imageLAB->readPixel(x, y, &lab);
198 outImageL->writePixel(x, y, lab.l);
199 }
200 }
201 }
202
203 /// Convolves an image with the given filter in one direction and saves it to th e output image
204 static void convolve(const ImageL* imageL, bool vertical, ImageL* outImageL) {
205 SkASSERT(imageL->width == outImageL->width);
206 SkASSERT(imageL->height == outImageL->height);
207
208 const float matrix[] = { 0.05f, 0.25f, 0.4f, 0.25f, 0.05f };
209 const int matrixCount = sizeof(matrix) / sizeof(float);
210 const int radius = matrixCount / 2;
211
212 // Keep track of what rows are being operated on for quick access.
213 float* rowPtrs[matrixCount]; // Because matrixCount is constant, this won't create a VLA
214 for (int y = radius; y < matrixCount; y++) {
215 rowPtrs[y] = imageL->getRow(y - radius);
216 }
217 float* writeRow = outImageL->getRow(0);
218
219 for (int y = 0; y < imageL->height; y++) {
220 for (int x = 0; x < imageL->width; x++) {
221 float lSum = 0.0f;
222 for (int xx = -radius; xx <= radius; xx++) {
223 int nx = x;
224 int ny = y;
225
226 // We mirror at edges so that edge pixels that the filter weight ing still makes
227 // sense.
228 if (vertical) {
229 ny += xx;
230 if (ny < 0) {
231 ny = -ny;
232 }
233 if (ny >= imageL->height) {
234 ny = imageL->height + (imageL->height - ny - 1);
235 }
236 } else {
237 nx += xx;
238 if (nx < 0) {
239 nx = -nx;
240 }
241 if (nx >= imageL->width) {
242 nx = imageL->width + (imageL->width - nx - 1);
243 }
244 }
245
246 float weight = matrix[xx + radius];
247 lSum += rowPtrs[ny - y + radius][nx] * weight;
248 }
249 writeRow[x] = lSum;
250 }
251 // As we move down, scroll the row pointers down with us
252 for (int y = 0; y < matrixCount - 1; y++)
253 {
254 rowPtrs[y] = rowPtrs[y + 1];
255 }
256 rowPtrs[matrixCount - 1] += imageL->width;
257 writeRow += imageL->width;
258 }
259 }
260
261 static double pmetric(const ImageLAB* baselineLAB, const ImageLAB* testLAB, SkTD Array<SkIPoint>* poi) {
262 int width = baselineLAB->width;
263 int height = baselineLAB->height;
264 int maxLevels = 0;
265
266 // Calculates how many levels to make by how many times the image can be div ided in two
267 int smallerDimension = width < height ? width : height;
268 for ( ; smallerDimension > 1; smallerDimension /= 2) {
269 maxLevels++;
270 }
271
272 const float fov = SK_ScalarPI / 180.0f * 45.0f;
273 float contrastSensitivityMax = contrast_sensitivity(3.248f, 100.0f);
274 float pixelsPerDegree = width / (2.0f * tanf(fov * 0.5f) * 180.0f / SK_Scala rPI);
275
276 ImageL3D baselineL(width, height, maxLevels);
277 ImageL3D testL(width, height, maxLevels);
278 ImageL scratchImageL(width, height);
279 float* cyclesPerDegree = SkNEW_ARRAY(float, maxLevels);
280 float* thresholdFactorFrequency = SkNEW_ARRAY(float, maxLevels - 2);
281 float* contrast = SkNEW_ARRAY(float, maxLevels - 2);
282
283 lab_to_l(baselineLAB, baselineL.getLayer(0));
284 lab_to_l(testLAB, testL.getLayer(0));
285
286 // Compute cpd - Cycles per degree on the pyramid
287 cyclesPerDegree[0] = 0.5f * pixelsPerDegree;
288 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
289 cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f;
290 }
291
292 // Contrast sensitivity is based on image dimensions. Therefore it cannot be statically
293 // generated.
294 float* contrastSensitivityTable = SkNEW_ARRAY(float, maxLevels * 1000);
295 for (int levelIndex = 0; levelIndex < maxLevels; levelIndex++) {
296 for (int csLum = 0; csLum < 1000; csLum++) {
297 contrastSensitivityTable[levelIndex * 1000 + csLum] =
298 contrast_sensitivity(cyclesPerDegree[levelIndex], (float)csLum / 10.0 f + 1e-5f);
299 }
300 }
301
302 // Compute G - The convolved lum for the baseline
303 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
304 convolve(baselineL.getLayer(levelIndex - 1), false, &scratchImageL);
305 convolve(&scratchImageL, true, baselineL.getLayer(levelIndex));
306 }
307 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
308 convolve(testL.getLayer(levelIndex - 1), false, &scratchImageL);
309 convolve(&scratchImageL, true, testL.getLayer(levelIndex));
310 }
311
312 // Compute F_freq - The elevation f
313 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
314 float cpd = cyclesPerDegree[levelIndex];
315 thresholdFactorFrequency[levelIndex] = contrastSensitivityMax /
316 contrast_sensitivity(cpd, 100.0f) ;
317 }
318
319 int failures = 0;
320 // Calculate F
321 for (int y = 0; y < height; y++) {
322 for (int x = 0; x < width; x++) {
323 float lBaseline;
324 float lTest;
325 baselineL.getLayer(0)->readPixel(x, y, &lBaseline);
326 testL.getLayer(0)->readPixel(x, y, &lTest);
327
328 float avgLBaseline;
329 float avgLTest;
330 baselineL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLBaseline);
331 testL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLTest);
332
333 float lAdapt = 0.5f * (avgLBaseline + avgLTest);
334 if (lAdapt < 1e-5f) {
335 lAdapt = 1e-5f;
336 }
337
338 float contrastSum = 0.0f;
339 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
340 float baselineL0, baselineL1, baselineL2;
341 float testL0, testL1, testL2;
342 baselineL.getLayer(levelIndex + 0)->readPixel(x, y, &baselineL0) ;
343 testL. getLayer(levelIndex + 0)->readPixel(x, y, &testL0);
344 baselineL.getLayer(levelIndex + 1)->readPixel(x, y, &baselineL1) ;
345 testL. getLayer(levelIndex + 1)->readPixel(x, y, &testL1);
346 baselineL.getLayer(levelIndex + 2)->readPixel(x, y, &baselineL2) ;
347 testL. getLayer(levelIndex + 2)->readPixel(x, y, &testL2);
348
349 float baselineContrast1 = fabsf(baselineL0 - baselineL1);
350 float testContrast1 = fabsf(testL0 - testL1);
351 float numerator = (baselineContrast1 > testContrast1) ?
352 baselineContrast1 : testContrast1;
353
354 float baselineContrast2 = fabsf(baselineL2);
355 float testContrast2 = fabsf(testL2);
356 float denominator = (baselineContrast2 > testContrast2) ?
357 baselineContrast2 : testContrast2;
358
359 // Avoid divides by close to zero
360 if (denominator < 1e-5f) {
361 denominator = 1e-5f;
362 }
363 contrast[levelIndex] = numerator / denominator;
364 contrastSum += contrast[levelIndex];
365 }
366
367 if (contrastSum < 1e-5f) {
368 contrastSum = 1e-5f;
369 }
370
371 float F = 0.0f;
372 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
373 float contrastSensitivity = contrastSensitivityTable[levelIndex * 1000 +
374 (int)(lAdap t * 10.0)];
375 float mask = SkPMetricUtil::get_visual_mask(contrast[levelIndex] *
376 contrastSensitivity) ;
377
378 F += contrast[levelIndex] +
379 thresholdFactorFrequency[levelIndex] * mask / contrastSum;
380 }
381
382 if (F < 1.0f) {
383 F = 1.0f;
384 }
385
386 if (F > 10.0f) {
387 F = 10.0f;
388 }
389
390
391 bool isFailure = false;
392 if (fabsf(lBaseline - lTest) > F * SkPMetricUtil::get_threshold_vs_i ntensity(lAdapt)) {
393 isFailure = true;
394 } else {
395 LAB baselineColor;
396 LAB testColor;
397 baselineLAB->readPixel(x, y, &baselineColor);
398 testLAB->readPixel(x, y, &testColor);
399 float contrastA = baselineColor.a - testColor.a;
400 float contrastB = baselineColor.b - testColor.b;
401 float colorScale = 1.0f;
402 if (lAdapt < 10.0f) {
403 colorScale = lAdapt / 10.0f;
404 }
405 colorScale *= colorScale;
406
407 if ((contrastA * contrastA + contrastB * contrastB) * colorScale > F)
408 {
409 isFailure = true;
410 }
411 }
412
413 if (isFailure) {
414 failures++;
415 poi->push()->set(x, y);
416 }
417 }
418 }
419
420 SkDELETE_ARRAY(cyclesPerDegree);
421 SkDELETE_ARRAY(contrast);
422 SkDELETE_ARRAY(thresholdFactorFrequency);
423 SkDELETE_ARRAY(contrastSensitivityTable);
424 return 1.0 - (double)failures / (width * height);
425 }
426
427 const char* SkPMetric::getName() {
428 return "perceptual";
429 }
430
431 int SkPMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) {
432 double startTime = get_seconds();
433 int diffID = fQueuedDiffs.count();
434 QueuedDiff& diff = fQueuedDiffs.push_back();
435 diff.result = 0.0;
436
437 // Ensure the images are comparable
438 if (baseline->width() != test->width() || baseline->height() != test->height () ||
439 baseline->width() <= 0 || baseline->height() <= 0) {
440 diff.finished = true;
441 return diffID;
442 }
443
444 ImageLAB baselineLAB(baseline->width(), baseline->height());
445 ImageLAB testLAB(baseline->width(), baseline->height());
446
447 bitmap_to_cielab(baseline, &baselineLAB);
448 bitmap_to_cielab(test, &testLAB);
449
450 diff.result = pmetric(&baselineLAB, &testLAB, &diff.poi);
451
452 SkDebugf("Time: %f\n", (get_seconds() - startTime));
453
454 return diffID;
455 }
456
457
458 void SkPMetric::deleteDiff(int id) {
459
460 }
461
462 bool SkPMetric::isFinished(int id) {
463 return fQueuedDiffs[id].finished;
464 }
465
466 double SkPMetric::getResult(int id) {
467 return fQueuedDiffs[id].result;
468 }
469
470 int SkPMetric::getPointsOfInterestCount(int id) {
471 return fQueuedDiffs[id].poi.count();
472 }
473
474 SkIPoint* SkPMetric::getPointsOfInterest(int id) {
475 return fQueuedDiffs[id].poi.begin();
476 }
OLDNEW
« no previous file with comments | « experimental/skpdiff/SkPMetric.h ('k') | experimental/skpdiff/SkPMetricUtil_generated.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698