1 /*
2  * Copyright © 2017 Intel Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  *
23  * Authors:
24  *  Paul Kocialkowski <paul.kocialkowski@linux.intel.com>
25  */
26 
27 #include "config.h"
28 
29 #include <fcntl.h>
30 #include <pixman.h>
31 #include <cairo.h>
32 #include <gsl/gsl_statistics_double.h>
33 #include <gsl/gsl_fit.h>
34 
35 #include "igt_frame.h"
36 #include "igt_core.h"
37 
38 /**
39  * SECTION:igt_frame
40  * @short_description: Library for frame-related tests
41  * @title: Frame
42  * @include: igt_frame.h
43  *
44  * This library contains helpers for frame-related tests. This includes common
45  * frame dumping as well as frame comparison helpers.
46  */
47 
48 /**
49  * igt_frame_dump_is_enabled:
50  *
51  * Get whether frame dumping is enabled.
52  *
53  * Returns: A boolean indicating whether frame dumping is enabled
54  */
igt_frame_dump_is_enabled(void)55 bool igt_frame_dump_is_enabled(void)
56 {
57 	return igt_frame_dump_path != NULL;
58 }
59 
igt_write_frame_to_png(cairo_surface_t * surface,int fd,const char * qualifier,const char * suffix)60 static void igt_write_frame_to_png(cairo_surface_t *surface, int fd,
61 				   const char *qualifier, const char *suffix)
62 {
63 	char path[PATH_MAX];
64 	const char *test_name;
65 	const char *subtest_name;
66 	cairo_status_t status;
67 	int index;
68 
69 	test_name = igt_test_name();
70 	subtest_name = igt_subtest_name();
71 
72 	if (suffix)
73 		snprintf(path, PATH_MAX, "%s/frame-%s-%s-%s-%s.png",
74 			 igt_frame_dump_path, test_name, subtest_name, qualifier,
75 			 suffix);
76 	else
77 		snprintf(path, PATH_MAX, "%s/frame-%s-%s-%s.png",
78 			 igt_frame_dump_path, test_name, subtest_name, qualifier);
79 
80 	igt_debug("Dumping %s frame to %s...\n", qualifier, path);
81 
82 	status = cairo_surface_write_to_png(surface, path);
83 
84 	igt_assert_eq(status, CAIRO_STATUS_SUCCESS);
85 
86 	index = strlen(path);
87 
88 	if (fd >= 0 && index < (PATH_MAX - 1)) {
89 		path[index++] = '\n';
90 		path[index] = '\0';
91 
92 		write(fd, path, strlen(path));
93 	}
94 }
95 
96 /**
97  * igt_write_compared_frames_to_png:
98  * @reference: The reference cairo surface
99  * @capture: The captured cairo surface
100  * @reference_suffix: The suffix to give to the reference png file
101  * @capture_suffix: The suffix to give to the capture png file
102  *
103  * Write previously compared frames to png files.
104  */
igt_write_compared_frames_to_png(cairo_surface_t * reference,cairo_surface_t * capture,const char * reference_suffix,const char * capture_suffix)105 void igt_write_compared_frames_to_png(cairo_surface_t *reference,
106 				      cairo_surface_t *capture,
107 				      const char *reference_suffix,
108 				      const char *capture_suffix)
109 {
110 	char *id;
111 	const char *test_name;
112 	const char *subtest_name;
113 	char path[PATH_MAX];
114 	int fd = -1;
115 
116 	if (!igt_frame_dump_is_enabled())
117 		return;
118 
119 	id = getenv("IGT_FRAME_DUMP_ID");
120 
121 	test_name = igt_test_name();
122 	subtest_name = igt_subtest_name();
123 
124 	if (id)
125 		snprintf(path, PATH_MAX, "%s/frame-%s-%s-%s.txt",
126 			 igt_frame_dump_path, test_name, subtest_name, id);
127 	else
128 		snprintf(path, PATH_MAX, "%s/frame-%s-%s.txt",
129 			 igt_frame_dump_path, test_name, subtest_name);
130 
131 	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
132 	igt_assert(fd >= 0);
133 
134 	igt_debug("Writing dump report to %s...\n", path);
135 
136 	igt_write_frame_to_png(reference, fd, "reference", reference_suffix);
137 	igt_write_frame_to_png(capture, fd, "capture", capture_suffix);
138 
139 	close(fd);
140 }
141 
142 /**
143  * igt_check_analog_frame_match:
144  * @reference: The reference cairo surface
145  * @capture: The captured cairo surface
146  *
147  * Checks that the analog image contained in the chamelium frame dump matches
148  * the given framebuffer.
149  *
150  * In order to determine whether the frame matches the reference, the following
151  * reasoning is implemented:
152  * 1. The absolute error for each color value of the reference is collected.
153  * 2. The average absolute error is calculated for each color value of the
154  *    reference and must not go above 60 (23.5 % of the total range).
155  * 3. A linear fit for the average absolute error from the pixel value is
156  *    calculated, as a DAC-ADC chain is expected to have a linear error curve.
157  * 4. The linear fit is correlated with the actual average absolute error for
158  *    the frame and the correlation coefficient is checked to be > 0.985,
159  *    indicating a match with the expected error trend.
160  *
161  * Most errors (e.g. due to scaling, rotation, color space, etc) can be
162  * reliably detected this way, with a minimized number of false-positives.
163  * However, the brightest values (250 and up) are ignored as the error trend
164  * is often not linear there in practice due to clamping.
165  *
166  * Returns: a boolean indicating whether the frames match
167  */
168 
igt_check_analog_frame_match(cairo_surface_t * reference,cairo_surface_t * capture)169 bool igt_check_analog_frame_match(cairo_surface_t *reference,
170 				  cairo_surface_t *capture)
171 {
172 	pixman_image_t *reference_src, *capture_src;
173 	int w, h;
174 	int error_count[3][256][2] = { 0 };
175 	double error_average[4][250];
176 	double error_trend[250];
177 	double c0, c1, cov00, cov01, cov11, sumsq;
178 	double correlation;
179 	unsigned char *reference_pixels, *capture_pixels;
180 	unsigned char *p;
181 	unsigned char *q;
182 	bool match = true;
183 	int diff;
184 	int x, y;
185 	int i, j;
186 
187 	w = cairo_image_surface_get_width(reference);
188 	h = cairo_image_surface_get_height(reference);
189 
190 	reference_src = pixman_image_create_bits(
191 	    PIXMAN_x8r8g8b8, w, h,
192 	    (void*)cairo_image_surface_get_data(reference),
193 	    cairo_image_surface_get_stride(reference));
194 	reference_pixels = (unsigned char *) pixman_image_get_data(reference_src);
195 
196 	capture_src = pixman_image_create_bits(
197 	    PIXMAN_x8r8g8b8, w, h,
198 	    (void*)cairo_image_surface_get_data(capture),
199 	    cairo_image_surface_get_stride(capture));
200 	capture_pixels = (unsigned char *) pixman_image_get_data(capture_src);
201 
202 	/* Collect the absolute error for each color value */
203 	for (x = 0; x < w; x++) {
204 		for (y = 0; y < h; y++) {
205 			p = &capture_pixels[(x + y * w) * 4];
206 			q = &reference_pixels[(x + y * w) * 4];
207 
208 			for (i = 0; i < 3; i++) {
209 				diff = (int) p[i] - q[i];
210 				if (diff < 0)
211 					diff = -diff;
212 
213 				error_count[i][q[i]][0] += diff;
214 				error_count[i][q[i]][1]++;
215 			}
216 		}
217 	}
218 
219 	/* Calculate the average absolute error for each color value */
220 	for (i = 0; i < 250; i++) {
221 		error_average[0][i] = i;
222 
223 		for (j = 1; j < 4; j++) {
224 			error_average[j][i] = (double) error_count[j-1][i][0] /
225 					      error_count[j-1][i][1];
226 
227 			if (error_average[j][i] > 60) {
228 				igt_warn("Error average too high (%f)\n",
229 					 error_average[j][i]);
230 
231 				match = false;
232 				goto complete;
233 			}
234 		}
235 	}
236 
237 	/*
238 	 * Calculate error trend from linear fit.
239 	 * A DAC-ADC chain is expected to have a linear absolute error on
240 	 * most of its range
241 	 */
242 	for (i = 1; i < 4; i++) {
243 		gsl_fit_linear((const double *) &error_average[0], 1,
244 			       (const double *) &error_average[i], 1, 250,
245 			       &c0, &c1, &cov00, &cov01, &cov11, &sumsq);
246 
247 		for (j = 0; j < 250; j++)
248 			error_trend[j] = c0 + j * c1;
249 
250 		correlation = gsl_stats_correlation((const double *) &error_trend,
251 						    1,
252 						    (const double *) &error_average[i],
253 						    1, 250);
254 
255 		if (correlation < 0.985) {
256 			igt_warn("Error with reference not correlated (%f)\n",
257 				 correlation);
258 
259 			match = false;
260 			goto complete;
261 		}
262 	}
263 
264 complete:
265 	pixman_image_unref(reference_src);
266 	pixman_image_unref(capture_src);
267 
268 	return match;
269 }
270 
271 #define XR24_COLOR_VALUE(data, stride, x, y, c) \
272 	*((uint8_t *)(data) + (y) * (stride) + 4 * (x) + (c))
273 
274 /**
275  * igt_check_checkerboard_frame_match:
276  * @reference: The reference cairo surface
277  * @capture: The captured cairo surface
278  *
279  * Checks that the reference frame matches the captured frame using a
280  * method designed for checkerboard patterns. These patterns are made of
281  * consecutive rectangular shapes with alternating solid colors.
282  *
283  * The intent of this method is to cover cases where the captured result is
284  * not pixel-perfect due to features such as scaling or YUV conversion and
285  * subsampling. Such effects are mostly noticeable on the edges of the
286  * patterns, so they are detected and excluded from the comparison.
287  *
288  * The algorithm works with two major steps. First, the edges of the reference
289  * pattern are detected on the x and y axis independently. The detection is done
290  * by calculating an absolute difference with a span of a few pixels before and
291  * after the current position on the given axis, accumulated for each color
292  * component. If the sum is above a given threshold on one of the axis, the
293  * position is marked as an edge. In the second step, the pixel values are
294  * compared per-component, excluding positions that were marked as edges or
295  * that are at a transition between an edge and a non-edge. An error threshold
296  * (for each individual color component) is used to mark the position as
297  * erroneous or not. The ratio of erroneous pixels over compared pixels (that
298  * does not count excluded pixels) is then calculated and compared to the error
299  * rate threshold to determine whether the frames match or not.
300  *
301  * Returns: a boolean indicating whether the frames match
302  */
igt_check_checkerboard_frame_match(cairo_surface_t * reference,cairo_surface_t * capture)303 bool igt_check_checkerboard_frame_match(cairo_surface_t *reference,
304 					cairo_surface_t *capture)
305 {
306 	unsigned int width, height, ref_stride, cap_stride;
307 	void *ref_data, *cap_data;
308 	unsigned char *edges_map;
309 	unsigned int x, y, c;
310 	unsigned int errors = 0, pixels = 0;
311 	unsigned int edge_threshold = 100;
312 	unsigned int color_error_threshold = 24;
313 	double error_rate_threshold = 0.01;
314 	double error_rate;
315 	unsigned int span = 2;
316 	bool match = false;
317 
318 	width = cairo_image_surface_get_width(reference);
319 	height = cairo_image_surface_get_height(reference);
320 
321 	ref_stride = cairo_image_surface_get_stride(reference);
322 	ref_data = cairo_image_surface_get_data(reference);
323 	igt_assert(ref_data);
324 
325 	cap_stride = cairo_image_surface_get_stride(capture);
326 	cap_data = cairo_image_surface_get_data(capture);
327 	igt_assert(cap_data);
328 
329 	edges_map = calloc(1, width * height);
330 	igt_assert(edges_map);
331 
332 	/* First pass to detect the pattern edges. */
333 	for (y = 0; y < height; y++) {
334 		if (y < span || y > (height - span - 1))
335 			continue;
336 
337 		for (x = 0; x < width; x++) {
338 			unsigned int xdiff = 0, ydiff = 0;
339 
340 			if (x < span || x > (width - span - 1))
341 				continue;
342 
343 			for (c = 0; c < 3; c++) {
344 				xdiff += abs(XR24_COLOR_VALUE(ref_data, ref_stride, x + span, y, c) -
345 					     XR24_COLOR_VALUE(ref_data, ref_stride, x - span, y, c));
346 				ydiff += abs(XR24_COLOR_VALUE(ref_data, ref_stride, x, y + span, c) -
347 					     XR24_COLOR_VALUE(ref_data, ref_stride, x, y - span, c));
348 			}
349 
350 			edges_map[y * width + x] = (xdiff > edge_threshold ||
351 						    ydiff > edge_threshold);
352 		}
353 	}
354 
355 	/* Second pass to detect errors. */
356 	for (y = 0; y < height; y++) {
357 		for (x = 0; x < width; x++) {
358 			bool error = false;
359 
360 			if (edges_map[y * width + x])
361 				continue;
362 
363 			for (c = 0; c < 3; c++) {
364 				unsigned int diff;
365 
366 				/* Compare the reference and capture values. */
367 				diff = abs(XR24_COLOR_VALUE(ref_data, ref_stride, x, y, c) -
368 					   XR24_COLOR_VALUE(cap_data, cap_stride, x, y, c));
369 
370 				if (diff > color_error_threshold)
371 					error = true;
372 			}
373 
374 			/* Allow error if coming on or off an edge (on x). */
375 			if (error && x >= span && x <= (width - span - 1) &&
376 			    edges_map[y * width + (x - span)] !=
377 			    edges_map[y * width + (x + span)])
378 				continue;
379 
380 			/* Allow error if coming on or off an edge (on y). */
381 			if (error && y >= span && y <= (height - span - 1) &&
382 			    edges_map[(y - span) * width + x] !=
383 			    edges_map[(y + span) * width + x] && error)
384 				continue;
385 
386 			if (error)
387 				errors++;
388 
389 			pixels++;
390 		}
391 	}
392 
393 	free(edges_map);
394 
395 	error_rate = (double) errors / pixels;
396 
397 	if (error_rate < error_rate_threshold)
398 		match = true;
399 
400 	igt_debug("Checkerboard pattern %s with error rate %f %%\n",
401 		  match ? "matched" : "not matched", error_rate * 100);
402 
403 	return match;
404 }
405