1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <util/dstr.h>
4 #include <obs-module.h>
5 #include <util/platform.h>
6 #include <graphics/vec2.h>
7 #include <graphics/math-defs.h>
8 
9 /* clang-format off */
10 
11 #define S_RESOLUTION                    "resolution"
12 #define S_SAMPLING                      "sampling"
13 #define S_UNDISTORT                     "undistort"
14 
15 #define T_RESOLUTION                    obs_module_text("Resolution")
16 #define T_NONE                          obs_module_text("None")
17 #define T_SAMPLING                      obs_module_text("ScaleFiltering")
18 #define T_SAMPLING_POINT                obs_module_text("ScaleFiltering.Point")
19 #define T_SAMPLING_BILINEAR             obs_module_text("ScaleFiltering.Bilinear")
20 #define T_SAMPLING_BICUBIC              obs_module_text("ScaleFiltering.Bicubic")
21 #define T_SAMPLING_LANCZOS              obs_module_text("ScaleFiltering.Lanczos")
22 #define T_SAMPLING_AREA                 obs_module_text("ScaleFiltering.Area")
23 #define T_UNDISTORT                     obs_module_text("UndistortCenter")
24 #define T_BASE                          obs_module_text("Base.Canvas")
25 
26 #define S_SAMPLING_POINT                "point"
27 #define S_SAMPLING_BILINEAR             "bilinear"
28 #define S_SAMPLING_BICUBIC              "bicubic"
29 #define S_SAMPLING_LANCZOS              "lanczos"
30 #define S_SAMPLING_AREA                 "area"
31 
32 /* clang-format on */
33 
34 struct scale_filter_data {
35 	obs_source_t *context;
36 	gs_effect_t *effect;
37 	gs_eparam_t *image_param;
38 	gs_eparam_t *dimension_param;
39 	gs_eparam_t *dimension_i_param;
40 	gs_eparam_t *undistort_factor_param;
41 	struct vec2 dimension;
42 	struct vec2 dimension_i;
43 	double undistort_factor;
44 	int cx_in;
45 	int cy_in;
46 	int cx_out;
47 	int cy_out;
48 	enum obs_scale_type sampling;
49 	gs_samplerstate_t *point_sampler;
50 	bool aspect_ratio_only;
51 	bool target_valid;
52 	bool valid;
53 	bool undistort;
54 	bool upscale;
55 	bool base_canvas_resolution;
56 };
57 
scale_filter_name(void * unused)58 static const char *scale_filter_name(void *unused)
59 {
60 	UNUSED_PARAMETER(unused);
61 	return obs_module_text("ScaleFilter");
62 }
63 
scale_filter_update(void * data,obs_data_t * settings)64 static void scale_filter_update(void *data, obs_data_t *settings)
65 {
66 	struct scale_filter_data *filter = data;
67 	int ret;
68 
69 	const char *res_str = obs_data_get_string(settings, S_RESOLUTION);
70 	const char *sampling = obs_data_get_string(settings, S_SAMPLING);
71 
72 	filter->valid = true;
73 	filter->base_canvas_resolution = false;
74 
75 	if (strcmp(res_str, T_BASE) == 0) {
76 		struct obs_video_info ovi;
77 		obs_get_video_info(&ovi);
78 		filter->aspect_ratio_only = false;
79 		filter->base_canvas_resolution = true;
80 		filter->cx_in = ovi.base_width;
81 		filter->cy_in = ovi.base_height;
82 	} else {
83 		ret = sscanf(res_str, "%dx%d", &filter->cx_in, &filter->cy_in);
84 		if (ret == 2) {
85 			filter->aspect_ratio_only = false;
86 		} else {
87 			ret = sscanf(res_str, "%d:%d", &filter->cx_in,
88 				     &filter->cy_in);
89 			if (ret != 2) {
90 				filter->valid = false;
91 				return;
92 			}
93 
94 			filter->aspect_ratio_only = true;
95 		}
96 	}
97 
98 	if (astrcmpi(sampling, S_SAMPLING_POINT) == 0) {
99 		filter->sampling = OBS_SCALE_POINT;
100 
101 	} else if (astrcmpi(sampling, S_SAMPLING_BILINEAR) == 0) {
102 		filter->sampling = OBS_SCALE_BILINEAR;
103 
104 	} else if (astrcmpi(sampling, S_SAMPLING_LANCZOS) == 0) {
105 		filter->sampling = OBS_SCALE_LANCZOS;
106 
107 	} else if (astrcmpi(sampling, S_SAMPLING_AREA) == 0) {
108 		filter->sampling = OBS_SCALE_AREA;
109 
110 	} else { /* S_SAMPLING_BICUBIC */
111 		filter->sampling = OBS_SCALE_BICUBIC;
112 	}
113 
114 	filter->undistort = obs_data_get_bool(settings, S_UNDISTORT);
115 }
116 
scale_filter_destroy(void * data)117 static void scale_filter_destroy(void *data)
118 {
119 	struct scale_filter_data *filter = data;
120 
121 	obs_enter_graphics();
122 	gs_samplerstate_destroy(filter->point_sampler);
123 	obs_leave_graphics();
124 	bfree(data);
125 }
126 
scale_filter_create(obs_data_t * settings,obs_source_t * context)127 static void *scale_filter_create(obs_data_t *settings, obs_source_t *context)
128 {
129 	struct scale_filter_data *filter =
130 		bzalloc(sizeof(struct scale_filter_data));
131 	struct gs_sampler_info sampler_info = {0};
132 
133 	filter->context = context;
134 
135 	obs_enter_graphics();
136 	filter->point_sampler = gs_samplerstate_create(&sampler_info);
137 	obs_leave_graphics();
138 
139 	scale_filter_update(filter, settings);
140 	return filter;
141 }
142 
scale_filter_tick(void * data,float seconds)143 static void scale_filter_tick(void *data, float seconds)
144 {
145 	struct scale_filter_data *filter = data;
146 	enum obs_base_effect type;
147 	obs_source_t *target;
148 	bool lower_than_2x;
149 	double cx_f;
150 	double cy_f;
151 	int cx;
152 	int cy;
153 
154 	if (filter->base_canvas_resolution) {
155 		struct obs_video_info ovi;
156 		obs_get_video_info(&ovi);
157 		filter->cx_in = ovi.base_width;
158 		filter->cy_in = ovi.base_height;
159 	}
160 
161 	target = obs_filter_get_target(filter->context);
162 	filter->cx_out = 0;
163 	filter->cy_out = 0;
164 
165 	filter->target_valid = !!target;
166 	if (!filter->target_valid)
167 		return;
168 
169 	cx = obs_source_get_base_width(target);
170 	cy = obs_source_get_base_height(target);
171 
172 	if (!cx || !cy) {
173 		filter->target_valid = false;
174 		return;
175 	}
176 
177 	filter->cx_out = cx;
178 	filter->cy_out = cy;
179 
180 	if (!filter->valid)
181 		return;
182 
183 	/* ------------------------- */
184 
185 	cx_f = (double)cx;
186 	cy_f = (double)cy;
187 
188 	double old_aspect = cx_f / cy_f;
189 	double new_aspect = (double)filter->cx_in / (double)filter->cy_in;
190 
191 	if (filter->aspect_ratio_only) {
192 		if (fabs(old_aspect - new_aspect) <= EPSILON) {
193 			filter->target_valid = false;
194 			return;
195 		} else {
196 			if (new_aspect > old_aspect) {
197 				filter->cx_out = (int)(cy_f * new_aspect);
198 			} else {
199 				filter->cy_out = (int)(cx_f / new_aspect);
200 			}
201 		}
202 	} else {
203 		filter->cx_out = filter->cx_in;
204 		filter->cy_out = filter->cy_in;
205 	}
206 
207 	vec2_set(&filter->dimension, (float)cx, (float)cy);
208 	vec2_set(&filter->dimension_i, 1.0f / (float)cx, 1.0f / (float)cy);
209 
210 	if (filter->undistort) {
211 		filter->undistort_factor = new_aspect / old_aspect;
212 	} else {
213 		filter->undistort_factor = 1.0;
214 	}
215 
216 	filter->upscale = false;
217 
218 	/* ------------------------- */
219 
220 	lower_than_2x = filter->cx_out < cx / 2 || filter->cy_out < cy / 2;
221 
222 	if (lower_than_2x && filter->sampling != OBS_SCALE_POINT) {
223 		type = OBS_EFFECT_BILINEAR_LOWRES;
224 	} else {
225 		switch (filter->sampling) {
226 		default:
227 		case OBS_SCALE_POINT:
228 		case OBS_SCALE_BILINEAR:
229 			type = OBS_EFFECT_DEFAULT;
230 			break;
231 		case OBS_SCALE_BICUBIC:
232 			type = OBS_EFFECT_BICUBIC;
233 			break;
234 		case OBS_SCALE_LANCZOS:
235 			type = OBS_EFFECT_LANCZOS;
236 			break;
237 		case OBS_SCALE_AREA:
238 			type = OBS_EFFECT_AREA;
239 			if ((filter->cx_out >= cx) && (filter->cy_out >= cy))
240 				filter->upscale = true;
241 			break;
242 		}
243 	}
244 
245 	filter->effect = obs_get_base_effect(type);
246 	filter->image_param =
247 		gs_effect_get_param_by_name(filter->effect, "image");
248 
249 	if (type != OBS_EFFECT_DEFAULT) {
250 		filter->dimension_param = gs_effect_get_param_by_name(
251 			filter->effect, "base_dimension");
252 		filter->dimension_i_param = gs_effect_get_param_by_name(
253 			filter->effect, "base_dimension_i");
254 	} else {
255 		filter->dimension_param = NULL;
256 		filter->dimension_i_param = NULL;
257 	}
258 
259 	if (type == OBS_EFFECT_BICUBIC || type == OBS_EFFECT_LANCZOS) {
260 		filter->undistort_factor_param = gs_effect_get_param_by_name(
261 			filter->effect, "undistort_factor");
262 	} else {
263 		filter->undistort_factor_param = NULL;
264 	}
265 
266 	UNUSED_PARAMETER(seconds);
267 }
268 
scale_filter_render(void * data,gs_effect_t * effect)269 static void scale_filter_render(void *data, gs_effect_t *effect)
270 {
271 	struct scale_filter_data *filter = data;
272 	const char *technique =
273 		filter->undistort ? "DrawUndistort"
274 				  : (filter->upscale ? "DrawUpscale" : "Draw");
275 
276 	if (!filter->valid || !filter->target_valid) {
277 		obs_source_skip_video_filter(filter->context);
278 		return;
279 	}
280 
281 	if (!obs_source_process_filter_begin(filter->context, GS_RGBA,
282 					     OBS_NO_DIRECT_RENDERING))
283 		return;
284 
285 	if (filter->dimension_param)
286 		gs_effect_set_vec2(filter->dimension_param, &filter->dimension);
287 
288 	if (filter->dimension_i_param)
289 		gs_effect_set_vec2(filter->dimension_i_param,
290 				   &filter->dimension_i);
291 
292 	if (filter->undistort_factor_param)
293 		gs_effect_set_float(filter->undistort_factor_param,
294 				    (float)filter->undistort_factor);
295 
296 	if (filter->sampling == OBS_SCALE_POINT)
297 		gs_effect_set_next_sampler(filter->image_param,
298 					   filter->point_sampler);
299 
300 	gs_blend_state_push();
301 	gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
302 
303 	obs_source_process_filter_tech_end(filter->context, filter->effect,
304 					   filter->cx_out, filter->cy_out,
305 					   technique);
306 
307 	gs_blend_state_pop();
308 
309 	UNUSED_PARAMETER(effect);
310 }
311 
312 static const double downscale_vals[] = {1.0,         1.25, (1.0 / 0.75), 1.5,
313 					(1.0 / 0.6), 1.75, 2.0,          2.25,
314 					2.5,         2.75, 3.0};
315 
316 #define NUM_DOWNSCALES (sizeof(downscale_vals) / sizeof(double))
317 
318 static const char *aspects[] = {"16:9", "16:10", "4:3", "1:1"};
319 
320 #define NUM_ASPECTS (sizeof(aspects) / sizeof(const char *))
321 
sampling_modified(obs_properties_t * props,obs_property_t * p,obs_data_t * settings)322 static bool sampling_modified(obs_properties_t *props, obs_property_t *p,
323 			      obs_data_t *settings)
324 {
325 	const char *sampling = obs_data_get_string(settings, S_SAMPLING);
326 
327 	bool has_undistort;
328 	if (astrcmpi(sampling, S_SAMPLING_POINT) == 0) {
329 		has_undistort = false;
330 	} else if (astrcmpi(sampling, S_SAMPLING_BILINEAR) == 0) {
331 		has_undistort = false;
332 	} else if (astrcmpi(sampling, S_SAMPLING_LANCZOS) == 0) {
333 		has_undistort = true;
334 	} else if (astrcmpi(sampling, S_SAMPLING_AREA) == 0) {
335 		has_undistort = false;
336 	} else { /* S_SAMPLING_BICUBIC */
337 		has_undistort = true;
338 	}
339 
340 	obs_property_set_visible(obs_properties_get(props, S_UNDISTORT),
341 				 has_undistort);
342 
343 	UNUSED_PARAMETER(p);
344 	return true;
345 }
346 
scale_filter_properties(void * data)347 static obs_properties_t *scale_filter_properties(void *data)
348 {
349 	obs_properties_t *props = obs_properties_create();
350 	struct obs_video_info ovi;
351 	obs_property_t *p;
352 	uint32_t cx;
353 	uint32_t cy;
354 
355 	struct {
356 		int cx;
357 		int cy;
358 	} downscales[NUM_DOWNSCALES];
359 
360 	/* ----------------- */
361 
362 	obs_get_video_info(&ovi);
363 	cx = ovi.base_width;
364 	cy = ovi.base_height;
365 
366 	for (size_t i = 0; i < NUM_DOWNSCALES; i++) {
367 		downscales[i].cx = (int)((double)cx / downscale_vals[i]);
368 		downscales[i].cy = (int)((double)cy / downscale_vals[i]);
369 	}
370 
371 	p = obs_properties_add_list(props, S_SAMPLING, T_SAMPLING,
372 				    OBS_COMBO_TYPE_LIST,
373 				    OBS_COMBO_FORMAT_STRING);
374 	obs_property_set_modified_callback(p, sampling_modified);
375 	obs_property_list_add_string(p, T_SAMPLING_POINT, S_SAMPLING_POINT);
376 	obs_property_list_add_string(p, T_SAMPLING_BILINEAR,
377 				     S_SAMPLING_BILINEAR);
378 	obs_property_list_add_string(p, T_SAMPLING_BICUBIC, S_SAMPLING_BICUBIC);
379 	obs_property_list_add_string(p, T_SAMPLING_LANCZOS, S_SAMPLING_LANCZOS);
380 	obs_property_list_add_string(p, T_SAMPLING_AREA, S_SAMPLING_AREA);
381 
382 	/* ----------------- */
383 
384 	p = obs_properties_add_list(props, S_RESOLUTION, T_RESOLUTION,
385 				    OBS_COMBO_TYPE_EDITABLE,
386 				    OBS_COMBO_FORMAT_STRING);
387 
388 	obs_property_list_add_string(p, T_NONE, T_NONE);
389 	obs_property_list_add_string(p, T_BASE, T_BASE);
390 
391 	for (size_t i = 0; i < NUM_ASPECTS; i++)
392 		obs_property_list_add_string(p, aspects[i], aspects[i]);
393 
394 	for (size_t i = 0; i < NUM_DOWNSCALES; i++) {
395 		char str[32];
396 		snprintf(str, 32, "%dx%d", downscales[i].cx, downscales[i].cy);
397 		obs_property_list_add_string(p, str, str);
398 	}
399 
400 	obs_properties_add_bool(props, S_UNDISTORT, T_UNDISTORT);
401 
402 	/* ----------------- */
403 
404 	UNUSED_PARAMETER(data);
405 	return props;
406 }
407 
scale_filter_defaults(obs_data_t * settings)408 static void scale_filter_defaults(obs_data_t *settings)
409 {
410 	obs_data_set_default_string(settings, S_SAMPLING, S_SAMPLING_BICUBIC);
411 	obs_data_set_default_string(settings, S_RESOLUTION, T_NONE);
412 	obs_data_set_default_bool(settings, S_UNDISTORT, 0);
413 }
414 
scale_filter_width(void * data)415 static uint32_t scale_filter_width(void *data)
416 {
417 	struct scale_filter_data *filter = data;
418 	return (uint32_t)filter->cx_out;
419 }
420 
scale_filter_height(void * data)421 static uint32_t scale_filter_height(void *data)
422 {
423 	struct scale_filter_data *filter = data;
424 	return (uint32_t)filter->cy_out;
425 }
426 
427 struct obs_source_info scale_filter = {
428 	.id = "scale_filter",
429 	.type = OBS_SOURCE_TYPE_FILTER,
430 	.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB,
431 	.get_name = scale_filter_name,
432 	.create = scale_filter_create,
433 	.destroy = scale_filter_destroy,
434 	.video_tick = scale_filter_tick,
435 	.video_render = scale_filter_render,
436 	.update = scale_filter_update,
437 	.get_properties = scale_filter_properties,
438 	.get_defaults = scale_filter_defaults,
439 	.get_width = scale_filter_width,
440 	.get_height = scale_filter_height,
441 };
442