1 #include <algorithm>
2 #include <cstdint>
3 #include "common/alloc.h"
4 #include "common/except.h"
5 #include "common/make_unique.h"
6 #include "common/pixel.h"
7 #include "graph/basic_filter.h"
8 #include "graph/filtergraph.h"
9 #include "graph/image_filter.h"
10 
11 #include "gtest/gtest.h"
12 #include "audit_buffer.h"
13 #include "mock_filter.h"
14 
15 namespace {
16 
17 using zimg::graph::node_id;
18 using zimg::graph::plane_mask;
19 using zimg::graph::id_map;
20 using zimg::graph::invalid_id;
21 
enabled_planes(bool color)22 plane_mask enabled_planes(bool color)
23 {
24 	plane_mask mask{};
25 	mask[zimg::graph::PLANE_Y] = true;
26 	mask[zimg::graph::PLANE_U] = color;
27 	mask[zimg::graph::PLANE_V] = color;
28 	return mask;
29 }
30 
id_to_map(node_id id,bool color)31 id_map id_to_map(node_id id, bool color)
32 {
33 	id_map map = zimg::graph::null_ids;
34 	map[zimg::graph::PLANE_Y] = id;
35 	map[zimg::graph::PLANE_U] = color ? id : invalid_id;
36 	map[zimg::graph::PLANE_V] = color ? id : invalid_id;
37 	return map;
38 }
39 
40 
41 template <class T>
42 class AuditImage : public AuditBuffer<T> {
43 	unsigned m_width;
44 	unsigned m_height;
45 public:
AuditImage(AuditBufferType buffer_type,unsigned width,unsigned height,zimg::PixelType type,unsigned subsample_w,unsigned subsample_h)46 	AuditImage(AuditBufferType buffer_type, unsigned width, unsigned height, zimg::PixelType type, unsigned subsample_w, unsigned subsample_h) :
47 		AuditBuffer<T>(buffer_type, width, height, type, zimg::graph::BUFFER_MAX, subsample_w, subsample_h),
48 		m_width{ width },
49 		m_height{ height }
50 	{}
51 
validate()52 	void validate()
53 	{
54 		for (unsigned i = 0; i < m_height; ++i) {
55 			ASSERT_FALSE(AuditBuffer<T>::detect_write(i, 0, m_width)) << "unexpected write at line: " << i;
56 		}
57 	}
58 };
59 
60 } // namespace
61 
62 
TEST(FilterGraphTest,test_noop)63 TEST(FilterGraphTest, test_noop)
64 {
65 	const unsigned w = 640;
66 	const unsigned h = 480;
67 	const zimg::PixelType type = zimg::PixelType::BYTE;
68 
69 	for (unsigned x = 0; x < 2; ++x) {
70 		SCOPED_TRACE(!!x);
71 
72 		bool color = !!x;
73 		AuditBufferType buffer_type = color ? AuditBufferType::COLOR_RGB : AuditBufferType::PLANE;
74 
75 		zimg::graph::FilterGraph graph;
76 		node_id id = graph.add_source({ w, h, type }, 0, 0, enabled_planes(color));
77 		graph.set_output(id_to_map(id, color));
78 
79 		AuditImage<uint8_t> src_image{ buffer_type, w, h, type, 0, 0 };
80 		AuditImage<uint8_t> dst_image{ buffer_type, w, h, type, 0, 0 };
81 		zimg::AlignedVector<char> tmp(graph.get_tmp_size());
82 
83 		src_image.default_fill();
84 		graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
85 
86 		SCOPED_TRACE("validating src");
87 		src_image.validate();
88 		SCOPED_TRACE("validating dst");
89 		dst_image.validate();
90 	}
91 }
92 
TEST(FilterGraphTest,test_noop_subsampling)93 TEST(FilterGraphTest, test_noop_subsampling)
94 {
95 	const unsigned w = 640;
96 	const unsigned h = 480;
97 	const zimg::PixelType type = zimg::PixelType::BYTE;
98 
99 	for (unsigned sw = 0; sw < 3; ++sw) {
100 		for (unsigned sh = 0; sh < 3; ++sh) {
101 			SCOPED_TRACE(sw);
102 			SCOPED_TRACE(sh);
103 
104 			zimg::graph::FilterGraph graph;
105 			node_id id = graph.add_source({ w, h, type }, sw, sh, enabled_planes(true));
106 			graph.set_output(id_to_map(id, true));
107 
108 			AuditImage<uint8_t> src_image{ AuditBufferType::COLOR_YUV, w, h, type, sw, sh };
109 			AuditImage<uint8_t> dst_image{ AuditBufferType::COLOR_YUV, w, h, type, sw, sh };
110 			zimg::AlignedVector<char> tmp(graph.get_tmp_size());
111 
112 			src_image.default_fill();
113 			graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
114 
115 			SCOPED_TRACE("validating src");
116 			src_image.validate();
117 			SCOPED_TRACE("validating dst");
118 			dst_image.validate();
119 		}
120 	}
121 }
122 
TEST(FilterGraphTest,test_basic)123 TEST(FilterGraphTest, test_basic)
124 {
125 	const unsigned w = 640;
126 	const unsigned h = 480;
127 	const zimg::PixelType type = zimg::PixelType::WORD;
128 
129 	const uint8_t test_byte1 = 0xCD;
130 	const uint8_t test_byte2 = 0xDD;
131 	const uint8_t test_byte3 = 0xDC;
132 
133 	for (unsigned x = 0; x < 2; ++x) {
134 		SCOPED_TRACE(!!x);
135 
136 		bool color = !!x;
137 		AuditBufferType buffer_type = color ? AuditBufferType::COLOR_RGB : AuditBufferType::PLANE;
138 
139 		zimg::graph::ImageFilter::filter_flags flags1{};
140 		flags1.has_state = true;
141 		flags1.entire_row = true;
142 		flags1.entire_plane = true;
143 		flags1.color = !!x;
144 
145 		zimg::graph::ImageFilter::filter_flags flags2{};
146 		flags2.has_state = true;
147 		flags2.entire_row = false;
148 		flags2.entire_plane = false;
149 		flags2.color = !!x;
150 
151 		auto filter1 = std::make_shared<SplatFilter<uint16_t>>(w, h, type, flags1);
152 		auto filter2 = std::make_shared<SplatFilter<uint16_t>>(w, h, type, flags2);
153 
154 		filter1->set_input_val(test_byte1);
155 		filter1->set_output_val(test_byte2);
156 
157 		filter2->set_input_val(test_byte2);
158 		filter2->set_output_val(test_byte3);
159 
160 		zimg::graph::FilterGraph graph;
161 		node_id id = graph.add_source({ w, h, type }, 0, 0, enabled_planes(color));
162 
163 		id = graph.attach_filter(filter1, id_to_map(id, color), enabled_planes(color));
164 		id = graph.attach_filter(filter2, id_to_map(id, color), enabled_planes(color));
165 		graph.set_output(id_to_map(id, color));
166 
167 		AuditImage<uint16_t> src_image{ buffer_type, w, h, type, 0, 0 };
168 		AuditImage<uint16_t> dst_image{ buffer_type, w, h, type, 0, 0 };
169 		zimg::AlignedVector<char> tmp(graph.get_tmp_size());
170 
171 		src_image.set_fill_val(test_byte1);
172 		src_image.default_fill();
173 		graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
174 		dst_image.set_fill_val(test_byte3);
175 
176 		ASSERT_EQ(1U, filter1->get_total_calls());
177 		ASSERT_EQ(h, filter2->get_total_calls());
178 
179 		SCOPED_TRACE("validating src");
180 		src_image.validate();
181 		SCOPED_TRACE("validating dst");
182 		dst_image.validate();
183 	}
184 }
185 
TEST(FilterGraphTest,test_skip_plane)186 TEST(FilterGraphTest, test_skip_plane)
187 {
188 	const unsigned w = 640;
189 	const unsigned h = 480;
190 	const zimg::PixelType type = zimg::PixelType::FLOAT;
191 
192 	const uint8_t test_byte1 = 0xCD;
193 	const uint8_t test_byte2 = 0xDD;
194 	const uint8_t test_byte3 = 0xDC;
195 
196 	for (unsigned x = 0; x < 2; ++x) {
197 		SCOPED_TRACE(!!x);
198 
199 		zimg::graph::ImageFilter::filter_flags flags1{};
200 		flags1.has_state = true;
201 		flags1.entire_row = true;
202 		flags1.color = true;
203 
204 		zimg::graph::ImageFilter::filter_flags flags2{};
205 		flags2.has_state = false;
206 		flags2.entire_row = true;
207 		flags2.color = false;
208 
209 		auto filter1 = std::make_shared<SplatFilter<float>>(w, h, type, flags1);
210 		auto filter2 = std::make_shared<SplatFilter<float>>(w, h, type, flags2);
211 
212 		filter1->set_input_val(test_byte1);
213 		filter1->set_output_val(test_byte2);
214 
215 		filter2->set_input_val(test_byte2);
216 		filter2->set_output_val(test_byte3);
217 
218 		zimg::graph::FilterGraph graph;
219 		node_id id_y = graph.add_source({ w, h, type }, 0, 0, enabled_planes(true));
220 		id_y = graph.attach_filter(filter1, id_to_map(id_y, true), enabled_planes(true));
221 
222 		node_id id_u = id_y;
223 		node_id id_v = id_y;
224 
225 		if (x) {
226 			id_y = graph.attach_filter(filter2, id_to_map(id_y, false), enabled_planes(false));
227 		} else {
228 			id_u = graph.attach_filter(filter2, { invalid_id, id_u, invalid_id, invalid_id }, { false, true, false, false });
229 			id_v = graph.attach_filter(filter2, { invalid_id, invalid_id, id_v, invalid_id }, { false, false, true, false });
230 		}
231 
232 		graph.set_output({ id_y, id_u, id_v, invalid_id });
233 
234 		AuditImage<float> src_image{ AuditBufferType::COLOR_YUV, w, h, type, 0, 0 };
235 		AuditImage<float> dst_image{ AuditBufferType::COLOR_YUV, w, h, type, 0, 0 };
236 		zimg::AlignedVector<char> tmp(graph.get_tmp_size());
237 
238 		src_image.set_fill_val(test_byte1);
239 		src_image.default_fill();
240 
241 		graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
242 
243 		if (x) {
244 			dst_image.set_fill_val(test_byte3, 0);
245 			dst_image.set_fill_val(test_byte2, 1);
246 			dst_image.set_fill_val(test_byte2, 2);
247 		} else {
248 			dst_image.set_fill_val(test_byte2, 0);
249 			dst_image.set_fill_val(test_byte3, 1);
250 			dst_image.set_fill_val(test_byte3, 2);
251 		}
252 
253 		ASSERT_EQ(h, filter1->get_total_calls());
254 		ASSERT_EQ(h * (x ? 1 : 2), filter2->get_total_calls());
255 
256 		SCOPED_TRACE("validating src");
257 		src_image.validate();
258 		SCOPED_TRACE("validating dst");
259 		dst_image.validate();
260 	}
261 }
262 
TEST(FilterGraphTest,test_color_to_grey)263 TEST(FilterGraphTest, test_color_to_grey)
264 {
265 	const unsigned w = 640;
266 	const unsigned h = 480;
267 	const zimg::PixelType type = zimg::PixelType::BYTE;
268 
269 	const uint8_t test_byte1 = 0xCD;
270 	const uint8_t test_byte2 = 0xDC;
271 
272 	zimg::graph::ImageFilter::filter_flags flags{};
273 	flags.has_state = true;
274 	flags.entire_row = true;
275 	flags.color = true;
276 
277 	auto filter = std::make_shared<SplatFilter<uint8_t>>(w, h, type, flags);
278 	filter->set_input_val(test_byte1);
279 	filter->set_output_val(test_byte2);
280 
281 	zimg::graph::FilterGraph graph;
282 	node_id id = graph.add_source({ w, h, type }, 0, 0, enabled_planes(true));
283 
284 	id = graph.attach_filter(filter, id_to_map(id, true), enabled_planes(true));
285 	graph.set_output(id_to_map(id, false));
286 
287 	AuditImage<uint8_t> src_image{ AuditBufferType::COLOR_YUV, w, h, type, 0, 0 };
288 	AuditImage<uint8_t> dst_image{ AuditBufferType::PLANE, w, h, type, 0, 0 };
289 	zimg::AlignedVector<char> tmp(graph.get_tmp_size());
290 
291 	src_image.set_fill_val(test_byte1);
292 	src_image.default_fill();
293 
294 	graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
295 
296 	dst_image.set_fill_val(test_byte2);
297 
298 	ASSERT_EQ(h, filter->get_total_calls());
299 
300 	SCOPED_TRACE("validating src");
301 	src_image.validate();
302 	SCOPED_TRACE("validating dst");
303 	dst_image.validate();
304 }
305 
TEST(FilterGraphTest,test_grey_to_color_rgb)306 TEST(FilterGraphTest, test_grey_to_color_rgb)
307 {
308 	const unsigned w = 640;
309 	const unsigned h = 480;
310 	const zimg::PixelType type = zimg::PixelType::BYTE;
311 
312 	const uint8_t test_byte1 = 0xCD;
313 	const uint8_t test_byte2 = 0xDD;
314 	const uint8_t test_byte3 = 0xDC;
315 
316 	zimg::graph::ImageFilter::filter_flags flags1{};
317 	flags1.has_state = true;
318 	flags1.entire_row = true;
319 	flags1.color = false;
320 
321 	zimg::graph::ImageFilter::filter_flags flags2{};
322 	flags2.has_state = true;
323 	flags2.entire_row = true;
324 	flags2.color = true;
325 
326 	auto filter1 = std::make_shared<SplatFilter<uint8_t>>(w, h, type, flags1);
327 	auto filter2 = std::make_shared<SplatFilter<uint8_t>>(w, h, type, flags2);
328 
329 	filter1->set_input_val(test_byte1);
330 	filter1->set_output_val(test_byte2);
331 
332 	filter2->set_input_val(test_byte2);
333 	filter2->set_output_val(test_byte3);
334 
335 	zimg::graph::FilterGraph graph;
336 	node_id id = graph.add_source({ w, h, type }, 0, 0, enabled_planes(false));
337 
338 	id = graph.attach_filter(filter1, id_to_map(id, false), enabled_planes(false));
339 
340 	auto rgbextend = ztd::make_unique<zimg::graph::RGBExtendFilter>(w, h, type);
341 	id = graph.attach_filter(std::move(rgbextend), id_to_map(id, false), enabled_planes(true));
342 
343 	id = graph.attach_filter(filter2, id_to_map(id, true), enabled_planes(true));
344 	graph.set_output(id_to_map(id, true));
345 
346 	AuditImage<uint8_t> src_image{ AuditBufferType::PLANE, w, h, type, 0, 0 };
347 	AuditImage<uint8_t> dst_image{ AuditBufferType::COLOR_RGB, w, h, type, 0, 0 };
348 	zimg::AlignedVector<char> tmp(graph.get_tmp_size());
349 
350 	src_image.set_fill_val(test_byte1);
351 	src_image.default_fill();
352 
353 	graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
354 
355 	dst_image.set_fill_val(test_byte3);
356 
357 	ASSERT_EQ(h, filter1->get_total_calls());
358 	ASSERT_EQ(h, filter2->get_total_calls());
359 
360 	SCOPED_TRACE("validating src");
361 	src_image.validate();
362 	SCOPED_TRACE("validating dst");
363 	dst_image.validate();
364 }
365 
TEST(FilterGraphTest,test_grey_to_color_yuv)366 TEST(FilterGraphTest, test_grey_to_color_yuv)
367 {
368 	const unsigned w = 640;
369 	const unsigned h = 480;
370 	const zimg::PixelType type = zimg::PixelType::BYTE;
371 
372 	const uint8_t test_byte1 = 0xCD;
373 	const uint8_t test_byte2 = 0xDD;
374 	const uint8_t test_byte2_uv = 128;
375 
376 	zimg::graph::ImageFilter::filter_flags flags{};
377 	flags.has_state = true;
378 	flags.entire_row = true;
379 	flags.color = false;
380 
381 	auto filter = std::make_shared<SplatFilter<uint8_t>>(w, h, type, flags);
382 	filter->set_input_val(test_byte1);
383 	filter->set_output_val(test_byte2);
384 
385 	zimg::graph::FilterGraph graph;
386 	node_id id_y = graph.add_source({ w, h, type }, 0, 0, enabled_planes(false));
387 	node_id id_u = id_y;
388 	node_id id_v = id_y;
389 
390 	id_y = graph.attach_filter(filter, id_to_map(id_y, false), enabled_planes(false));
391 
392 	zimg::graph::ValueInitializeFilter::value_type init_val{};
393 	init_val.b = test_byte2_uv;
394 	auto chroma_init = std::make_shared<zimg::graph::ValueInitializeFilter>(w >> 1, h >> 1, type, init_val);
395 	id_u = graph.attach_filter(chroma_init, zimg::graph::null_ids, { false, true, false, false });
396 	id_v = graph.attach_filter(chroma_init, zimg::graph::null_ids, { false, false, true, false });
397 
398 	graph.set_output({ id_y, id_u, id_v, invalid_id });
399 
400 	AuditImage<uint8_t> src_image{ AuditBufferType::PLANE, w, h, type, 0, 0 };
401 	AuditImage<uint8_t> dst_image{ AuditBufferType::COLOR_YUV, w, h, type, 1, 1 };
402 	zimg::AlignedVector<char> tmp(graph.get_tmp_size());
403 
404 	src_image.set_fill_val(test_byte1);
405 	src_image.default_fill();
406 
407 	graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
408 
409 	dst_image.set_fill_val(test_byte2, 0);
410 	dst_image.set_fill_val(test_byte2_uv, 1);
411 	dst_image.set_fill_val(test_byte2_uv, 2);
412 
413 	ASSERT_EQ(h, filter->get_total_calls());
414 
415 	SCOPED_TRACE("validating src");
416 	src_image.validate();
417 	SCOPED_TRACE("validating dst");
418 	dst_image.validate();
419 }
420 
TEST(FilterGraphTest,test_support)421 TEST(FilterGraphTest, test_support)
422 {
423 	const unsigned w = 1024;
424 	const unsigned h = 576;
425 	const zimg::PixelType type = zimg::PixelType::HALF;
426 
427 	const uint8_t test_byte1 = 0xCD;
428 	const uint8_t test_byte2 = 0xDD;
429 	const uint8_t test_byte3 = 0xDC;
430 
431 	for (unsigned x = 0; x < 2; ++x) {
432 		SCOPED_TRACE(!!x);
433 
434 		auto filter1 = std::make_shared<SplatFilter<uint16_t>>(w, h, type);
435 		auto filter2 = std::make_shared<SplatFilter<uint16_t>>(w, h, type);
436 
437 		filter1->set_input_val(test_byte1);
438 		filter1->set_output_val(test_byte2);
439 
440 		filter2->set_input_val(test_byte2);
441 		filter2->set_output_val(test_byte3);
442 
443 		if (x) {
444 			filter1->set_horizontal_support(5);
445 			filter1->set_simultaneous_lines(5);
446 
447 			filter2->set_horizontal_support(3);
448 			filter2->set_simultaneous_lines(3);
449 		} else {
450 			filter1->set_horizontal_support(3);
451 			filter1->set_simultaneous_lines(3);
452 
453 			filter2->set_horizontal_support(5);
454 			filter2->set_simultaneous_lines(5);
455 		}
456 
457 		zimg::graph::FilterGraph graph;
458 		node_id id = graph.add_source({ w, h, type }, 0, 0, enabled_planes(false));
459 
460 		id = graph.attach_filter(filter1, id_to_map(id, false), enabled_planes(false));
461 		id = graph.attach_filter(filter2, id_to_map(id, false), enabled_planes(false));
462 		graph.set_output(id_to_map(id, false));
463 
464 		graph.set_tile_width(512);
465 
466 		AuditImage<uint16_t> src_image{ AuditBufferType::PLANE, w, h, type, 0, 0 };
467 		AuditImage<uint16_t> dst_image{ AuditBufferType::PLANE, w, h, type, 0, 0 };
468 		zimg::AlignedVector<char> tmp(graph.get_tmp_size());
469 
470 		src_image.set_fill_val(test_byte1);
471 		src_image.default_fill();
472 
473 		if (x) {
474 			EXPECT_EQ(8U, graph.get_input_buffering());
475 			EXPECT_EQ(4U, graph.get_output_buffering());
476 		} else {
477 			EXPECT_EQ(4U, graph.get_input_buffering());
478 			EXPECT_EQ(8U, graph.get_output_buffering());
479 		}
480 
481 		graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), nullptr, nullptr);
482 		dst_image.set_fill_val(test_byte3);
483 
484 		SCOPED_TRACE("validating src");
485 		src_image.validate();
486 		SCOPED_TRACE("validating dst");
487 		dst_image.validate();
488 
489 		if (x) {
490 			EXPECT_EQ(2 * 116U, filter1->get_total_calls());
491 			EXPECT_EQ(2 * 192U, filter2->get_total_calls());
492 		} else {
493 			EXPECT_EQ(2 * 192U, filter1->get_total_calls());
494 			EXPECT_EQ(2 * 116U, filter2->get_total_calls());
495 		}
496 	}
497 }
498 
TEST(FilterGraphTest,test_callback)499 TEST(FilterGraphTest, test_callback)
500 {
501 	static const unsigned w = 1024;
502 	static const unsigned h = 576;
503 	const zimg::PixelType type = zimg::PixelType::BYTE;
504 
505 	const uint8_t test_byte1 = 0xCD;
506 	const uint8_t test_byte2 = 0xFF;
507 	const uint8_t test_byte3 = 0xDC;
508 
509 	struct callback_data {
510 		zimg::graph::ColorImageBuffer<void> buffer;
511 		unsigned subsample_w;
512 		unsigned subsample_h;
513 		unsigned call_count;
514 		uint8_t byte_val;
515 	};
516 
517 	auto cb = [](void *ptr, unsigned i, unsigned left, unsigned right) -> int
518 	{
519 		callback_data *xptr = static_cast<callback_data *>(ptr);
520 
521 		if (right > w)
522 			return 1;
523 
524 		EXPECT_LT(i, h);
525 		EXPECT_EQ(0U, i % (1 << xptr->subsample_h));
526 		EXPECT_LT(left, right);
527 		EXPECT_LE(right, w);
528 
529 		if (HasFailure())
530 			return 1;
531 
532 		for (unsigned ii = i; ii < i + (1 << xptr->subsample_h); ++ii) {
533 			const auto &buf = zimg::graph::static_buffer_cast<uint8_t>(xptr->buffer[0]);
534 
535 			std::fill(buf[ii] + left, buf[ii] + right, xptr->byte_val);
536 		}
537 		for (unsigned p = 1; p < 3; ++p) {
538 			const auto &chroma_buf = zimg::graph::static_buffer_cast<uint8_t>(xptr->buffer[p]);
539 			unsigned i_chroma = i >> xptr->subsample_h;
540 			unsigned left_chroma = (left % 2 ? left - 1 : left) >> xptr->subsample_w;
541 			unsigned right_chroma = (right % 2 ? right + 1 : right) >> xptr->subsample_w;
542 
543 			std::fill(chroma_buf[i_chroma] + left_chroma, chroma_buf[i_chroma] + right_chroma, xptr->byte_val);
544 		}
545 
546 		++xptr->call_count;
547 		return 0;
548 	};
549 
550 	for (unsigned sw = 0; sw < 3; ++sw) {
551 		for (unsigned sh = 0; sh < 3; ++sh) {
552 			for (unsigned x = 0; x < 2; ++x) {
553 				SCOPED_TRACE(sw);
554 				SCOPED_TRACE(sh);
555 				SCOPED_TRACE(!!x);
556 
557 				zimg::graph::ImageFilter::filter_flags flags{};
558 				flags.entire_row = !!x;
559 				flags.color = false;
560 
561 				auto filter1 = std::make_shared<SplatFilter<uint8_t>>(w, h, type, flags);
562 				auto filter2 = std::make_shared<SplatFilter<uint8_t>>(w >> sw, h >> sh, type, flags);
563 
564 				filter1->set_input_val(test_byte1);
565 				filter1->set_output_val(test_byte2);
566 
567 				filter2->set_input_val(test_byte1);
568 				filter2->set_output_val(test_byte2);
569 
570 				zimg::graph::FilterGraph graph;
571 				node_id id_y = graph.add_source({ w, h, type }, sw, sh, enabled_planes(true));
572 				node_id id_u = id_y;
573 				node_id id_v = id_y;
574 
575 				id_y = graph.attach_filter(filter1, id_to_map(id_y, false), enabled_planes(false));
576 				id_u = graph.attach_filter(filter2, { invalid_id, id_u, invalid_id, invalid_id }, { false, true, false, false });
577 				id_v = graph.attach_filter(filter2, { invalid_id, invalid_id, id_v, invalid_id }, { false, false, true, false });
578 				graph.set_output({ id_y, id_u, id_v, invalid_id });
579 
580 				graph.set_tile_width(512);
581 
582 				AuditImage<uint8_t> src_image{ AuditBufferType::COLOR_RGB, w, h, type, sw, sh };
583 				AuditImage<uint8_t> tmp_image{ AuditBufferType::COLOR_RGB, w, h, type, sw, sh };
584 				AuditImage<uint8_t> dst_image{ AuditBufferType::COLOR_RGB, w, h, type, sw, sh };
585 				zimg::AlignedVector<char> tmp(graph.get_tmp_size());
586 
587 				callback_data cb1_data = { src_image.as_write_buffer(), sw, sh, 0, test_byte1 };
588 				callback_data cb2_data = { dst_image.as_write_buffer(), sw, sh, 0, test_byte3 };
589 
590 				src_image.set_fill_val(test_byte1);
591 				tmp_image.set_fill_val(test_byte2);
592 				dst_image.set_fill_val(test_byte3);
593 
594 				graph.process(src_image.as_read_buffer(), tmp_image.as_write_buffer(), tmp.data(), { cb, &cb1_data }, { cb, &cb2_data });
595 
596 				SCOPED_TRACE("validating src");
597 				src_image.validate();
598 				SCOPED_TRACE("validating tmp");
599 				tmp_image.validate();
600 				SCOPED_TRACE("validating dst");
601 				dst_image.validate();
602 
603 				EXPECT_EQ((h >> sh) * (x ? 1 : 2), cb1_data.call_count);
604 				EXPECT_EQ((h >> sh) * (x ? 1 : 2), cb2_data.call_count);
605 			}
606 		}
607 	}
608 }
609 
TEST(FilterGraphTest,test_callback_failed)610 TEST(FilterGraphTest, test_callback_failed)
611 {
612 	const unsigned w = 640;
613 	const unsigned h = 480;
614 	zimg::PixelType type = zimg::PixelType::BYTE;
615 
616 	auto cb = [](void *, unsigned i, unsigned left, unsigned right) -> int
617 	{
618 		return 1;
619 	};
620 
621 	zimg::graph::FilterGraph graph;
622 	node_id id = graph.add_source({ w, h, type }, 0, 0, enabled_planes(false));
623 	graph.set_output(id_to_map(id, false));
624 
625 	AuditImage<uint8_t> src_image{ AuditBufferType::PLANE, w, h, type, 0, 0 };
626 	AuditImage<uint8_t> dst_image{ AuditBufferType::PLANE, w, h, type, 0, 0 };
627 	zimg::AlignedVector<char> tmp(graph.get_tmp_size());
628 
629 	src_image.set_fill_val(255);
630 	dst_image.set_fill_val(0);
631 
632 	src_image.default_fill();
633 	dst_image.default_fill();
634 
635 	ASSERT_THROW(graph.process(src_image.as_read_buffer(), dst_image.as_write_buffer(), tmp.data(), { cb, nullptr }, nullptr), zimg::error::UserCallbackFailed);
636 
637 	SCOPED_TRACE("validating dst");
638 	dst_image.validate();
639 }
640