scale-filter.c 8.9 KB

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