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