123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 |
- #include <stdio.h>
- #include <stdlib.h>
- #include <util/dstr.h>
- #include <obs-module.h>
- #include <util/platform.h>
- #include <graphics/vec2.h>
- #include <graphics/math-defs.h>
- /* clang-format off */
- #define S_RESOLUTION "resolution"
- #define S_SAMPLING "sampling"
- #define S_UNDISTORT "undistort"
- #define T_RESOLUTION obs_module_text("Resolution")
- #define T_NONE obs_module_text("None")
- #define T_SAMPLING obs_module_text("ScaleFiltering")
- #define T_SAMPLING_POINT obs_module_text("ScaleFiltering.Point")
- #define T_SAMPLING_BILINEAR obs_module_text("ScaleFiltering.Bilinear")
- #define T_SAMPLING_BICUBIC obs_module_text("ScaleFiltering.Bicubic")
- #define T_SAMPLING_LANCZOS obs_module_text("ScaleFiltering.Lanczos")
- #define T_SAMPLING_AREA obs_module_text("ScaleFiltering.Area")
- #define T_UNDISTORT obs_module_text("UndistortCenter")
- #define T_BASE obs_module_text("Base.Canvas")
- #define S_SAMPLING_POINT "point"
- #define S_SAMPLING_BILINEAR "bilinear"
- #define S_SAMPLING_BICUBIC "bicubic"
- #define S_SAMPLING_LANCZOS "lanczos"
- #define S_SAMPLING_AREA "area"
- /* clang-format on */
- struct scale_filter_data {
- obs_source_t *context;
- gs_effect_t *effect;
- gs_eparam_t *image_param;
- gs_eparam_t *dimension_param;
- gs_eparam_t *dimension_i_param;
- gs_eparam_t *undistort_factor_param;
- gs_eparam_t *multiplier_param;
- struct vec2 dimension;
- struct vec2 dimension_i;
- double undistort_factor;
- int cx_in;
- int cy_in;
- int cx_out;
- int cy_out;
- enum obs_scale_type sampling;
- gs_samplerstate_t *point_sampler;
- bool aspect_ratio_only;
- bool target_valid;
- bool valid;
- bool can_undistort;
- bool undistort;
- bool upscale;
- bool base_canvas_resolution;
- };
- static const char *scale_filter_name(void *unused)
- {
- UNUSED_PARAMETER(unused);
- return obs_module_text("ScaleFilter");
- }
- static void scale_filter_update(void *data, obs_data_t *settings)
- {
- struct scale_filter_data *filter = data;
- int ret;
- const char *res_str = obs_data_get_string(settings, S_RESOLUTION);
- const char *sampling = obs_data_get_string(settings, S_SAMPLING);
- filter->valid = true;
- filter->base_canvas_resolution = false;
- if (strcmp(res_str, T_BASE) == 0) {
- struct obs_video_info ovi;
- obs_get_video_info(&ovi);
- filter->aspect_ratio_only = false;
- filter->base_canvas_resolution = true;
- filter->cx_in = ovi.base_width;
- filter->cy_in = ovi.base_height;
- } else {
- ret = sscanf(res_str, "%dx%d", &filter->cx_in, &filter->cy_in);
- if (ret == 2) {
- filter->aspect_ratio_only = false;
- } else {
- ret = sscanf(res_str, "%d:%d", &filter->cx_in,
- &filter->cy_in);
- if (ret != 2) {
- filter->valid = false;
- return;
- }
- filter->aspect_ratio_only = true;
- }
- }
- if (astrcmpi(sampling, S_SAMPLING_POINT) == 0) {
- filter->sampling = OBS_SCALE_POINT;
- } else if (astrcmpi(sampling, S_SAMPLING_BILINEAR) == 0) {
- filter->sampling = OBS_SCALE_BILINEAR;
- } else if (astrcmpi(sampling, S_SAMPLING_LANCZOS) == 0) {
- filter->sampling = OBS_SCALE_LANCZOS;
- } else if (astrcmpi(sampling, S_SAMPLING_AREA) == 0) {
- filter->sampling = OBS_SCALE_AREA;
- } else { /* S_SAMPLING_BICUBIC */
- filter->sampling = OBS_SCALE_BICUBIC;
- }
- filter->can_undistort = obs_data_get_bool(settings, S_UNDISTORT);
- }
- static void scale_filter_destroy(void *data)
- {
- struct scale_filter_data *filter = data;
- obs_enter_graphics();
- gs_samplerstate_destroy(filter->point_sampler);
- obs_leave_graphics();
- bfree(data);
- }
- static void *scale_filter_create(obs_data_t *settings, obs_source_t *context)
- {
- struct scale_filter_data *filter =
- bzalloc(sizeof(struct scale_filter_data));
- struct gs_sampler_info sampler_info = {0};
- filter->context = context;
- obs_enter_graphics();
- filter->point_sampler = gs_samplerstate_create(&sampler_info);
- obs_leave_graphics();
- scale_filter_update(filter, settings);
- return filter;
- }
- static void scale_filter_tick(void *data, float seconds)
- {
- struct scale_filter_data *filter = data;
- enum obs_base_effect type;
- obs_source_t *target;
- bool lower_than_2x;
- double cx_f;
- double cy_f;
- int cx;
- int cy;
- if (filter->base_canvas_resolution) {
- struct obs_video_info ovi;
- obs_get_video_info(&ovi);
- filter->cx_in = ovi.base_width;
- filter->cy_in = ovi.base_height;
- }
- target = obs_filter_get_target(filter->context);
- filter->cx_out = 0;
- filter->cy_out = 0;
- filter->target_valid = !!target;
- if (!filter->target_valid)
- return;
- cx = obs_source_get_base_width(target);
- cy = obs_source_get_base_height(target);
- if (!cx || !cy) {
- filter->target_valid = false;
- return;
- }
- filter->cx_out = cx;
- filter->cy_out = cy;
- if (!filter->valid)
- return;
- /* ------------------------- */
- cx_f = (double)cx;
- cy_f = (double)cy;
- double old_aspect = cx_f / cy_f;
- double new_aspect = (double)filter->cx_in / (double)filter->cy_in;
- if (filter->aspect_ratio_only) {
- if (fabs(old_aspect - new_aspect) <= EPSILON) {
- filter->target_valid = false;
- return;
- } else {
- if (new_aspect > old_aspect) {
- filter->cx_out = (int)(cy_f * new_aspect);
- } else {
- filter->cy_out = (int)(cx_f / new_aspect);
- }
- }
- } else {
- filter->cx_out = filter->cx_in;
- filter->cy_out = filter->cy_in;
- }
- vec2_set(&filter->dimension, (float)cx, (float)cy);
- vec2_set(&filter->dimension_i, 1.0f / (float)cx, 1.0f / (float)cy);
- filter->undistort = false;
- filter->upscale = false;
- /* ------------------------- */
- lower_than_2x = filter->cx_out < cx / 2 || filter->cy_out < cy / 2;
- if (lower_than_2x && filter->sampling != OBS_SCALE_POINT) {
- type = OBS_EFFECT_BILINEAR_LOWRES;
- } else {
- switch (filter->sampling) {
- default:
- case OBS_SCALE_POINT:
- case OBS_SCALE_BILINEAR:
- type = OBS_EFFECT_DEFAULT;
- break;
- case OBS_SCALE_BICUBIC:
- type = OBS_EFFECT_BICUBIC;
- filter->undistort = filter->can_undistort;
- break;
- case OBS_SCALE_LANCZOS:
- type = OBS_EFFECT_LANCZOS;
- filter->undistort = filter->can_undistort;
- break;
- case OBS_SCALE_AREA:
- type = OBS_EFFECT_AREA;
- if ((filter->cx_out >= cx) && (filter->cy_out >= cy))
- filter->upscale = true;
- break;
- }
- }
- filter->undistort_factor = filter->undistort ? (new_aspect / old_aspect)
- : 1.0;
- filter->effect = obs_get_base_effect(type);
- filter->image_param =
- gs_effect_get_param_by_name(filter->effect, "image");
- if (type != OBS_EFFECT_DEFAULT) {
- filter->dimension_param = gs_effect_get_param_by_name(
- filter->effect, "base_dimension");
- filter->dimension_i_param = gs_effect_get_param_by_name(
- filter->effect, "base_dimension_i");
- } else {
- filter->dimension_param = NULL;
- filter->dimension_i_param = NULL;
- }
- if (type == OBS_EFFECT_BICUBIC || type == OBS_EFFECT_LANCZOS) {
- filter->undistort_factor_param = gs_effect_get_param_by_name(
- filter->effect, "undistort_factor");
- } else {
- filter->undistort_factor_param = NULL;
- }
- filter->multiplier_param =
- gs_effect_get_param_by_name(filter->effect, "multiplier");
- UNUSED_PARAMETER(seconds);
- }
- static const char *
- get_tech_name_and_multiplier(const struct scale_filter_data *filter,
- enum gs_color_space current_space,
- enum gs_color_space source_space,
- float *multiplier)
- {
- *multiplier = 1.f;
- switch (source_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- case GS_CS_709_EXTENDED:
- if (current_space == GS_CS_709_SCRGB)
- *multiplier = obs_get_video_sdr_white_level() / 80.f;
- break;
- case GS_CS_709_SCRGB:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- case GS_CS_709_EXTENDED:
- *multiplier = 80.f / obs_get_video_sdr_white_level();
- break;
- case GS_CS_709_SCRGB:
- break;
- }
- }
- const char *tech_name = "Draw";
- if (filter->undistort) {
- tech_name = "DrawUndistort";
- switch (source_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- if (current_space == GS_CS_709_SCRGB)
- tech_name = "DrawUndistortMultiply";
- break;
- case GS_CS_709_EXTENDED:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawUndistortTonemap";
- break;
- case GS_CS_709_SCRGB:
- tech_name = "DrawUndistortMultiply";
- break;
- case GS_CS_709_EXTENDED:
- break;
- }
- break;
- case GS_CS_709_SCRGB:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawUndistortMultiplyTonemap";
- break;
- case GS_CS_709_EXTENDED:
- tech_name = "DrawUndistortMultiply";
- break;
- case GS_CS_709_SCRGB:
- break;
- }
- }
- } else if (filter->upscale) {
- tech_name = "DrawUpscale";
- switch (source_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- if (current_space == GS_CS_709_SCRGB)
- tech_name = "DrawUpscaleMultiply";
- break;
- case GS_CS_709_EXTENDED:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawUpscaleTonemap";
- break;
- case GS_CS_709_SCRGB:
- tech_name = "DrawUpscaleMultiply";
- break;
- case GS_CS_709_EXTENDED:
- break;
- }
- break;
- case GS_CS_709_SCRGB:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawUpscaleMultiplyTonemap";
- break;
- case GS_CS_709_EXTENDED:
- tech_name = "DrawUpscaleMultiply";
- break;
- case GS_CS_709_SCRGB:
- break;
- }
- }
- } else {
- switch (source_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- if (current_space == GS_CS_709_SCRGB)
- tech_name = "DrawMultiply";
- break;
- case GS_CS_709_EXTENDED:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawTonemap";
- break;
- case GS_CS_709_SCRGB:
- tech_name = "DrawMultiply";
- break;
- case GS_CS_709_EXTENDED:
- break;
- }
- break;
- case GS_CS_709_SCRGB:
- switch (current_space) {
- case GS_CS_SRGB:
- case GS_CS_SRGB_16F:
- tech_name = "DrawMultiplyTonemap";
- break;
- case GS_CS_709_EXTENDED:
- tech_name = "DrawMultiply";
- break;
- case GS_CS_709_SCRGB:
- break;
- }
- }
- }
- return tech_name;
- }
- static void scale_filter_render(void *data, gs_effect_t *effect)
- {
- UNUSED_PARAMETER(effect);
- struct scale_filter_data *filter = data;
- if (!filter->valid || !filter->target_valid) {
- obs_source_skip_video_filter(filter->context);
- return;
- }
- const enum gs_color_space preferred_spaces[] = {
- GS_CS_SRGB,
- GS_CS_SRGB_16F,
- GS_CS_709_EXTENDED,
- };
- const enum gs_color_space source_space = obs_source_get_color_space(
- obs_filter_get_target(filter->context),
- OBS_COUNTOF(preferred_spaces), preferred_spaces);
- float multiplier;
- const char *technique = get_tech_name_and_multiplier(
- filter, gs_get_color_space(), source_space, &multiplier);
- const enum gs_color_format format =
- gs_get_format_from_space(source_space);
- if (obs_source_process_filter_begin_with_color_space(
- filter->context, format, source_space,
- OBS_NO_DIRECT_RENDERING)) {
- if (filter->dimension_param)
- gs_effect_set_vec2(filter->dimension_param,
- &filter->dimension);
- if (filter->dimension_i_param)
- gs_effect_set_vec2(filter->dimension_i_param,
- &filter->dimension_i);
- if (filter->undistort_factor_param)
- gs_effect_set_float(filter->undistort_factor_param,
- (float)filter->undistort_factor);
- if (filter->multiplier_param)
- gs_effect_set_float(filter->multiplier_param,
- multiplier);
- if (filter->sampling == OBS_SCALE_POINT)
- gs_effect_set_next_sampler(filter->image_param,
- filter->point_sampler);
- gs_blend_state_push();
- gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
- obs_source_process_filter_tech_end(filter->context,
- filter->effect,
- filter->cx_out,
- filter->cy_out, technique);
- gs_blend_state_pop();
- }
- }
- static const double downscale_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5,
- (1.0 / 0.6), 1.75, 2.0, 2.25,
- 2.5, 2.75, 3.0};
- #define NUM_DOWNSCALES (sizeof(downscale_vals) / sizeof(double))
- static const char *aspects[] = {"16:9", "16:10", "4:3", "1:1"};
- #define NUM_ASPECTS (sizeof(aspects) / sizeof(const char *))
- static bool sampling_modified(obs_properties_t *props, obs_property_t *p,
- obs_data_t *settings)
- {
- const char *sampling = obs_data_get_string(settings, S_SAMPLING);
- bool has_undistort;
- if (astrcmpi(sampling, S_SAMPLING_POINT) == 0) {
- has_undistort = false;
- } else if (astrcmpi(sampling, S_SAMPLING_BILINEAR) == 0) {
- has_undistort = false;
- } else if (astrcmpi(sampling, S_SAMPLING_LANCZOS) == 0) {
- has_undistort = true;
- } else if (astrcmpi(sampling, S_SAMPLING_AREA) == 0) {
- has_undistort = false;
- } else { /* S_SAMPLING_BICUBIC */
- has_undistort = true;
- }
- obs_property_set_visible(obs_properties_get(props, S_UNDISTORT),
- has_undistort);
- UNUSED_PARAMETER(p);
- return true;
- }
- static obs_properties_t *scale_filter_properties(void *data)
- {
- obs_properties_t *props = obs_properties_create();
- struct obs_video_info ovi;
- obs_property_t *p;
- uint32_t cx;
- uint32_t cy;
- struct {
- int cx;
- int cy;
- } downscales[NUM_DOWNSCALES];
- /* ----------------- */
- obs_get_video_info(&ovi);
- cx = ovi.base_width;
- cy = ovi.base_height;
- for (size_t i = 0; i < NUM_DOWNSCALES; i++) {
- downscales[i].cx = (int)((double)cx / downscale_vals[i]);
- downscales[i].cy = (int)((double)cy / downscale_vals[i]);
- }
- p = obs_properties_add_list(props, S_SAMPLING, T_SAMPLING,
- OBS_COMBO_TYPE_LIST,
- OBS_COMBO_FORMAT_STRING);
- obs_property_set_modified_callback(p, sampling_modified);
- obs_property_list_add_string(p, T_SAMPLING_POINT, S_SAMPLING_POINT);
- obs_property_list_add_string(p, T_SAMPLING_BILINEAR,
- S_SAMPLING_BILINEAR);
- obs_property_list_add_string(p, T_SAMPLING_BICUBIC, S_SAMPLING_BICUBIC);
- obs_property_list_add_string(p, T_SAMPLING_LANCZOS, S_SAMPLING_LANCZOS);
- obs_property_list_add_string(p, T_SAMPLING_AREA, S_SAMPLING_AREA);
- /* ----------------- */
- p = obs_properties_add_list(props, S_RESOLUTION, T_RESOLUTION,
- OBS_COMBO_TYPE_EDITABLE,
- OBS_COMBO_FORMAT_STRING);
- obs_property_list_add_string(p, T_NONE, T_NONE);
- obs_property_list_add_string(p, T_BASE, T_BASE);
- for (size_t i = 0; i < NUM_ASPECTS; i++)
- obs_property_list_add_string(p, aspects[i], aspects[i]);
- for (size_t i = 0; i < NUM_DOWNSCALES; i++) {
- char str[32];
- snprintf(str, sizeof(str), "%dx%d", downscales[i].cx,
- downscales[i].cy);
- obs_property_list_add_string(p, str, str);
- }
- obs_properties_add_bool(props, S_UNDISTORT, T_UNDISTORT);
- /* ----------------- */
- UNUSED_PARAMETER(data);
- return props;
- }
- static void scale_filter_defaults(obs_data_t *settings)
- {
- obs_data_set_default_string(settings, S_SAMPLING, S_SAMPLING_BICUBIC);
- obs_data_set_default_string(settings, S_RESOLUTION, T_NONE);
- obs_data_set_default_bool(settings, S_UNDISTORT, 0);
- }
- static uint32_t scale_filter_width(void *data)
- {
- struct scale_filter_data *filter = data;
- return (uint32_t)filter->cx_out;
- }
- static uint32_t scale_filter_height(void *data)
- {
- struct scale_filter_data *filter = data;
- return (uint32_t)filter->cy_out;
- }
- static enum gs_color_space
- scale_filter_get_color_space(void *data, size_t count,
- const enum gs_color_space *preferred_spaces)
- {
- const enum gs_color_space potential_spaces[] = {
- GS_CS_SRGB,
- GS_CS_SRGB_16F,
- GS_CS_709_EXTENDED,
- };
- struct scale_filter_data *const filter = data;
- const enum gs_color_space source_space = obs_source_get_color_space(
- obs_filter_get_target(filter->context),
- OBS_COUNTOF(potential_spaces), potential_spaces);
- enum gs_color_space space = source_space;
- for (size_t i = 0; i < count; ++i) {
- space = preferred_spaces[i];
- if (space == source_space)
- break;
- }
- return space;
- }
- struct obs_source_info scale_filter = {
- .id = "scale_filter",
- .type = OBS_SOURCE_TYPE_FILTER,
- .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB,
- .get_name = scale_filter_name,
- .create = scale_filter_create,
- .destroy = scale_filter_destroy,
- .video_tick = scale_filter_tick,
- .video_render = scale_filter_render,
- .update = scale_filter_update,
- .get_properties = scale_filter_properties,
- .get_defaults = scale_filter_defaults,
- .get_width = scale_filter_width,
- .get_height = scale_filter_height,
- .video_get_color_space = scale_filter_get_color_space,
- };
|