scale-filter.c 12 KB

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