From bd1ede883d50c42e17bca2b0c1197618f2d68509 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Sat, 21 Mar 2015 18:32:06 -0700 Subject: [PATCH] obs-filters: Add crop filter Allows any source to be cropped, though note that this renders to texture first, so for more optimal results, cropping values should probably be put in to capture sources that can be cropped as they're actually rendered by the source. --- plugins/obs-filters/CMakeLists.txt | 1 + plugins/obs-filters/crop-filter.c | 224 ++++++++++++++++++++ plugins/obs-filters/data/crop_filter.effect | 38 ++++ plugins/obs-filters/data/locale/en-US.ini | 8 + plugins/obs-filters/obs-filters.c | 2 + 5 files changed, 273 insertions(+) create mode 100644 plugins/obs-filters/crop-filter.c create mode 100644 plugins/obs-filters/data/crop_filter.effect 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; }