scale-filter.c 11 KB

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