diff --git a/plugins/obs-filters/CMakeLists.txt b/plugins/obs-filters/CMakeLists.txt index 58c63a99f..1e5f5e48b 100644 --- a/plugins/obs-filters/CMakeLists.txt +++ b/plugins/obs-filters/CMakeLists.txt @@ -3,6 +3,7 @@ project(obs-filters) set(obs-filters_SOURCES obs-filters.c async-delay-filter.c + crop-filter.c mask-filter.c) add_library(obs-filters MODULE diff --git a/plugins/obs-filters/crop-filter.c b/plugins/obs-filters/crop-filter.c new file mode 100644 index 000000000..385b1f3d1 --- /dev/null +++ b/plugins/obs-filters/crop-filter.c @@ -0,0 +1,224 @@ +#include +#include + +struct crop_filter_data { + obs_source_t *context; + + gs_effect_t *effect; + gs_eparam_t *param_mul; + gs_eparam_t *param_add; + + int left; + int right; + int top; + int bottom; + uint32_t abs_cx; + uint32_t abs_cy; + uint32_t width; + uint32_t height; + bool absolute; +}; + +static const char *crop_filter_get_name(void) +{ + return obs_module_text("CropFilter"); +} + +static void *crop_filter_create(obs_data_t *settings, obs_source_t *context) +{ + struct crop_filter_data *filter = bzalloc(sizeof(*filter)); + char *effect_path = obs_module_file("crop_filter.effect"); + + filter->context = context; + + obs_enter_graphics(); + filter->effect = gs_effect_create_from_file(effect_path, NULL); + obs_leave_graphics(); + + bfree(effect_path); + + if (!filter->effect) { + bfree(filter); + return NULL; + } + + filter->param_mul = gs_effect_get_param_by_name(filter->effect, + "mul_val"); + filter->param_add = gs_effect_get_param_by_name(filter->effect, + "add_val"); + + obs_source_update(context, settings); + return filter; +} + +static void crop_filter_destroy(void *data) +{ + struct crop_filter_data *filter = data; + + obs_enter_graphics(); + gs_effect_destroy(filter->effect); + obs_leave_graphics(); + + bfree(filter); +} + +static void crop_filter_update(void *data, obs_data_t *settings) +{ + struct crop_filter_data *filter = data; + + filter->absolute = !obs_data_get_bool(settings, "relative"); + filter->left = (int)obs_data_get_int(settings, "left"); + filter->top = (int)obs_data_get_int(settings, "top"); + filter->right = (int)obs_data_get_int(settings, "right"); + filter->bottom = (int)obs_data_get_int(settings, "bottom"); + filter->abs_cx = (int)obs_data_get_int(settings, "cx"); + filter->abs_cy = (int)obs_data_get_int(settings, "cy"); +} + +static bool relative_clicked(obs_properties_t *props, obs_property_t *p, + obs_data_t *settings) +{ + bool relative = obs_data_get_bool(settings, "relative"); + + obs_property_set_description(obs_properties_get(props, "left"), + relative ? obs_module_text("Crop.Left") : "X"); + obs_property_set_description(obs_properties_get(props, "top"), + relative ? obs_module_text("Crop.Top") : "Y"); + + obs_property_set_visible(obs_properties_get(props, "right"), relative); + obs_property_set_visible(obs_properties_get(props, "bottom"), relative); + obs_property_set_visible(obs_properties_get(props, "cx"), !relative); + obs_property_set_visible(obs_properties_get(props, "cy"), !relative); + + UNUSED_PARAMETER(p); + return true; +} + +static obs_properties_t *crop_filter_properties(void *data) +{ + obs_properties_t *props = obs_properties_create(); + + obs_property_t *p = obs_properties_add_bool(props, "relative", + obs_module_text("Crop.Relative")); + + obs_property_set_modified_callback(p, relative_clicked); + + obs_properties_add_int(props, "left", obs_module_text("Crop.Left"), + 0, 8192, 1); + obs_properties_add_int(props, "top", obs_module_text("Crop.Top"), + 0, 8192, 1); + obs_properties_add_int(props, "right", obs_module_text("Crop.Right"), + 0, 8192, 1); + obs_properties_add_int(props, "bottom", obs_module_text("Crop.Bottom"), + 0, 8192, 1); + obs_properties_add_int(props, "cx", obs_module_text("Crop.Width"), + 0, 8192, 1); + obs_properties_add_int(props, "cy", obs_module_text("Crop.Height"), + 0, 8192, 1); + + UNUSED_PARAMETER(data); + return props; +} + +static void crop_filter_defaults(obs_data_t *settings) +{ + obs_data_set_default_bool(settings, "relative", true); +} + +static void calc_crop_dimensions(struct crop_filter_data *filter, + struct vec2 *mul_val, struct vec2 *add_val) +{ + obs_source_t *target = obs_filter_get_target(filter->context); + uint32_t width; + uint32_t height; + uint32_t total; + + if (!target) { + width = 0; + height = 0; + } else { + width = obs_source_get_base_width(target); + height = obs_source_get_base_height(target); + } + + if (filter->absolute) { + uint32_t max_abs_cx = (filter->left + filter->abs_cx); + if (max_abs_cx > width) max_abs_cx = width; + max_abs_cx -= filter->left; + + total = max_abs_cx < width ? (width - max_abs_cx) : 0; + } else { + total = filter->left + filter->right; + } + filter->width = total > width ? 0 : (width - total); + + if (filter->absolute) { + uint32_t max_abs_cy = (filter->top + filter->abs_cy); + if (max_abs_cy > height) max_abs_cy = height; + max_abs_cy -= filter->top; + + total = max_abs_cy < height ? (height - max_abs_cy) : 0; + } else { + total = filter->top + filter->bottom; + } + filter->height = total > height ? 0 : (height - total); + + if (width && filter->width) { + mul_val->x = (float)filter->width / (float)width; + add_val->x = (float)filter->left / (float)width; + } + + if (height && filter->height) { + mul_val->y = (float)filter->height / (float)height; + add_val->y = (float)filter->top / (float)height; + } +} + +static void crop_filter_render(void *data, gs_effect_t *effect) +{ + struct crop_filter_data *filter = data; + struct vec2 mul_val; + struct vec2 add_val; + + vec2_zero(&mul_val); + vec2_zero(&add_val); + calc_crop_dimensions(filter, &mul_val, &add_val); + + obs_source_process_filter_begin(filter->context, GS_RGBA, + OBS_NO_DIRECT_RENDERING); + + gs_effect_set_vec2(filter->param_mul, &mul_val); + gs_effect_set_vec2(filter->param_add, &add_val); + + obs_source_process_filter_end(filter->context, filter->effect, + filter->width, filter->height); + + UNUSED_PARAMETER(effect); +} + +static uint32_t crop_filter_width(void *data) +{ + struct crop_filter_data *crop = data; + return crop->width; +} + +static uint32_t crop_filter_height(void *data) +{ + struct crop_filter_data *crop = data; + return crop->height; +} + +struct obs_source_info crop_filter = { + .id = "crop_filter", + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = crop_filter_get_name, + .create = crop_filter_create, + .destroy = crop_filter_destroy, + .update = crop_filter_update, + .get_properties = crop_filter_properties, + .get_defaults = crop_filter_defaults, + .video_render = crop_filter_render, + .get_width = crop_filter_width, + .get_height = crop_filter_height +}; diff --git a/plugins/obs-filters/data/crop_filter.effect b/plugins/obs-filters/data/crop_filter.effect new file mode 100644 index 000000000..614ecbcc3 --- /dev/null +++ b/plugins/obs-filters/data/crop_filter.effect @@ -0,0 +1,38 @@ +uniform float4x4 ViewProj; +uniform texture2d image; + +uniform float2 mul_val; +uniform float2 add_val; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSCrop(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv * mul_val + add_val; + return vert_out; +} + +float4 PSCrop(VertData v_in) : TARGET +{ + return image.Sample(textureSampler, v_in.uv); +} + +technique Draw +{ + pass + { + vertex_shader = VSCrop(v_in); + pixel_shader = PSCrop(v_in); + } +} diff --git a/plugins/obs-filters/data/locale/en-US.ini b/plugins/obs-filters/data/locale/en-US.ini index f961b36e7..91ec6f304 100644 --- a/plugins/obs-filters/data/locale/en-US.ini +++ b/plugins/obs-filters/data/locale/en-US.ini @@ -1,5 +1,6 @@ MaskFilter="Image Mask/Blend" AsyncDelayFilter="Video Delay (Async)" +CropFilter="Crop" DelayMs="Delay (milliseconds)" Type="Type" MaskBlendType.MaskColor="Alpha Mask (Color Channel)" @@ -15,3 +16,10 @@ Brightness="Brightness" Gamma="Gamma" BrowsePath.Images="All Image Files" BrowsePath.AllFiles="All Files" +Crop.Left="Left" +Crop.Right="Right" +Crop.Top="Top" +Crop.Bottom="Bottom" +Crop.Width="Width" +Crop.Height="Height" +Crop.Relative="Relative" diff --git a/plugins/obs-filters/obs-filters.c b/plugins/obs-filters/obs-filters.c index 23f9fed15..19f366485 100644 --- a/plugins/obs-filters/obs-filters.c +++ b/plugins/obs-filters/obs-filters.c @@ -5,11 +5,13 @@ OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("obs-filters", "en-US") extern struct obs_source_info mask_filter; +extern struct obs_source_info crop_filter; extern struct obs_source_info async_delay_filter; bool obs_module_load(void) { obs_register_source(&mask_filter); + obs_register_source(&crop_filter); obs_register_source(&async_delay_filter); return true; }