diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f929901..c28ff4f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ SET(obs-stream-effects_HEADERS "${PROJECT_SOURCE_DIR}/source/filter-blur.h" "${PROJECT_SOURCE_DIR}/source/filter-shape.h" "${PROJECT_SOURCE_DIR}/source/filter-transform.h" + "${PROJECT_SOURCE_DIR}/source/source-mirror.h" "${PROJECT_SOURCE_DIR}/source/gs-helper.h" "${PROJECT_SOURCE_DIR}/source/gs-effect.h" "${PROJECT_SOURCE_DIR}/source/gs-indexbuffer.h" @@ -55,6 +56,7 @@ SET(obs-stream-effects_SOURCES "${PROJECT_SOURCE_DIR}/source/filter-blur.cpp" "${PROJECT_SOURCE_DIR}/source/filter-shape.cpp" "${PROJECT_SOURCE_DIR}/source/filter-transform.cpp" + "${PROJECT_SOURCE_DIR}/source/source-mirror.cpp" "${PROJECT_SOURCE_DIR}/source/gs-helper.cpp" "${PROJECT_SOURCE_DIR}/source/gs-effect.cpp" "${PROJECT_SOURCE_DIR}/source/gs-indexbuffer.cpp" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 1d9e05ee..a092aa28 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -54,4 +54,16 @@ Filter.Blur.Size.Description="Area size of the blur, large sizes may cause:\n- S Filter.Blur.Bilateral.Smoothing="Smoothing" Filter.Blur.Bilateral.Sharpness="Sharpness" Filter.Blur.ColorFormat="Color Format" +Source.Mirror="Source Mirror" +Source.Mirror.Source="Source" +Source.Mirror.Source.Size="Source Size" +Source.Mirror.Scaling="Rescale Source" +Source.Mirror.Scaling.Method="Filter" +Source.Mirror.Scaling.Method.Point="Point" +Source.Mirror.Scaling.Method.Bilinear="Bilinear" +Source.Mirror.Scaling.Method.BilinearLowRes="Bilinear (Low Resolution)" +Source.Mirror.Scaling.Method.Bicubic="Bicubic" +Source.Mirror.Scaling.Method.Lanczos="Lanczos" +Source.Mirror.Scaling.Size="Size" + Advanced="Advanced" diff --git a/source/source-mirror.cpp b/source/source-mirror.cpp new file mode 100644 index 00000000..07ae415d --- /dev/null +++ b/source/source-mirror.cpp @@ -0,0 +1,337 @@ +/* +* Modern effects for a modern Streamer +* Copyright (C) 2017 Michael Fabian Dirks +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +*/ + +#include "source-mirror.h" +#include "strings.h" +#include +#include +#include + +#define S_SOURCE_MIRROR "Source.Mirror" +#define P_SOURCE "Source.Mirror.Source" +#define P_SOURCE_SIZE "Source.Mirror.Source.Size" +#define P_SCALING "Source.Mirror.Scaling" +#define P_SCALING_METHOD "Source.Mirror.Scaling.Method" +#define P_SCALING_METHOD_POINT "Source.Mirror.Scaling.Method.Point" +#define P_SCALING_METHOD_BILINEAR "Source.Mirror.Scaling.Method.Bilinear" +#define P_SCALING_METHOD_BILINEARLOWRES "Source.Mirror.Scaling.Method.BilinearLowRes" +#define P_SCALING_METHOD_BICUBIC "Source.Mirror.Scaling.Method.Bicubic" +#define P_SCALING_METHOD_LANCZOS "Source.Mirror.Scaling.Method.Lanczos" +#define P_SCALING_SIZE "Source.Mirror.Scaling.Size" + +enum class ScalingMethod : int64_t { + Point, + Bilinear, + BilinearLowRes, + Bicubic, + Lanczos, +}; + +Source::MirrorAddon* inst; +INITIALIZER(SourceMirrorInit) { + initializerFunctions.push_back([] { + inst = new Source::MirrorAddon(); + }); + finalizerFunctions.push_back([] { + delete inst; + }); +} + +Source::MirrorAddon::MirrorAddon() { + memset(&osi, 0, sizeof(obs_source_info)); + osi.id = "obs-stream-effects-source-mirror"; + osi.type = OBS_SOURCE_TYPE_INPUT; + osi.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW; + + osi.get_name = get_name; + osi.get_defaults = get_defaults; + osi.get_properties = get_properties; + osi.get_width = get_width; + osi.get_height = get_height; + osi.create = create; + osi.destroy = destroy; + osi.update = update; + osi.activate = activate; + osi.deactivate = deactivate; + osi.video_tick = video_tick; + osi.video_render = video_render; + + obs_register_source(&osi); +} + +Source::MirrorAddon::~MirrorAddon() {} + +const char * Source::MirrorAddon::get_name(void *) { + return P_TRANSLATE(S_SOURCE_MIRROR); +} + +void Source::MirrorAddon::get_defaults(obs_data_t *data) { + obs_data_set_default_string(data, P_SOURCE, ""); + obs_data_set_default_bool(data, P_SCALING, false); + obs_data_set_default_string(data, P_SCALING_SIZE, "100x100"); + obs_data_set_default_int(data, P_SCALING_METHOD, (int64_t)ScalingMethod::Bilinear); +} + +bool Source::MirrorAddon::modified_properties(obs_properties_t *pr, obs_property_t *p, obs_data_t *data) { + if (obs_properties_get(pr, P_SOURCE) == p) { + obs_source_t* target = obs_get_source_by_name(obs_data_get_string(data, P_SOURCE)); + if (target) { + std::vector buf(256); + sprintf_s(buf.data(), buf.size(), "%ldx%ld\0", + obs_source_get_width(target), obs_source_get_height(target)); + obs_data_set_string(data, P_SOURCE_SIZE, buf.data()); + } else { + obs_data_set_string(data, P_SOURCE_SIZE, "0x0"); + } + } + + if (obs_properties_get(pr, P_SCALING) == p) { + bool show = obs_data_get_bool(data, P_SCALING); + obs_property_set_visible(obs_properties_get(pr, P_SCALING_METHOD), show); + obs_property_set_visible(obs_properties_get(pr, P_SCALING_SIZE), show); + return true; + } + + return false; +} + +bool UpdateSourceListCB(void *ptr, obs_source_t* src) { + obs_property_t* p = (obs_property_t*)ptr; + obs_property_list_add_string(p, obs_source_get_name(src), obs_source_get_name(src)); + return true; +} + +void UpdateSourceList(obs_property_t* p) { + obs_property_list_clear(p); + obs_enum_sources(UpdateSourceListCB, p); +} + +obs_properties_t * Source::MirrorAddon::get_properties(void *ptr) { + obs_properties_t* pr = obs_properties_create(); + obs_property_t* p = nullptr; + + p = obs_properties_add_list(pr, P_SOURCE, P_TRANSLATE(P_SOURCE), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SOURCE))); + obs_property_set_modified_callback(p, modified_properties); + UpdateSourceList(p); + + p = obs_properties_add_text(pr, P_SOURCE_SIZE, P_TRANSLATE(P_SOURCE_SIZE), OBS_TEXT_DEFAULT); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SOURCE_SIZE))); + obs_property_set_enabled(p, false); + + p = obs_properties_add_bool(pr, P_SCALING, P_TRANSLATE(P_SCALING)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING))); + obs_property_set_modified_callback(p, modified_properties); + + p = obs_properties_add_list(pr, P_SCALING_METHOD, P_TRANSLATE(P_SCALING_METHOD), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_METHOD))); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_POINT), (int64_t)ScalingMethod::Point); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BILINEAR), (int64_t)ScalingMethod::Bilinear); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BILINEARLOWRES), (int64_t)ScalingMethod::BilinearLowRes); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BICUBIC), (int64_t)ScalingMethod::Bicubic); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_LANCZOS), (int64_t)ScalingMethod::Lanczos); + + p = obs_properties_add_text(pr, P_SCALING_SIZE, P_TRANSLATE(P_SCALING_SIZE), + OBS_TEXT_DEFAULT); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_SIZE))); + + return pr; +} + +void * Source::MirrorAddon::create(obs_data_t *data, obs_source_t *source) { + return new Source::Mirror(data, source); +} + +void Source::MirrorAddon::destroy(void *p) { + delete static_cast(p); +} + +uint32_t Source::MirrorAddon::get_width(void *p) { + return static_cast(p)->get_width(); +} + +uint32_t Source::MirrorAddon::get_height(void *p) { + return static_cast(p)->get_height(); +} + +void Source::MirrorAddon::update(void *p, obs_data_t *data) { + static_cast(p)->update(data); +} + +void Source::MirrorAddon::activate(void *p) { + static_cast(p)->activate(); +} + +void Source::MirrorAddon::deactivate(void *p) { + static_cast(p)->deactivate(); +} + +void Source::MirrorAddon::video_tick(void *p, float t) { + static_cast(p)->video_tick(t); +} + +void Source::MirrorAddon::video_render(void *p, gs_effect_t *ef) { + static_cast(p)->video_render(ef); +} + +Source::Mirror::Mirror(obs_data_t* data, obs_source_t* src) { + m_active = true; + m_source = src; + m_target = nullptr; + + m_rescale = false; + m_width = m_height = 1; + m_renderTarget = std::make_unique(GS_RGBA, GS_ZS_NONE); + m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + + update(data); +} + +Source::Mirror::~Mirror() { + +} + +uint32_t Source::Mirror::get_width() { + if (m_rescale && m_width > 0) + return m_width; + if (m_target && m_target != m_source) + return obs_source_get_width(m_target); + return 1; +} + +uint32_t Source::Mirror::get_height() { + if (m_rescale && m_height > 0) + return m_height; + if (m_target && m_target != m_source) + return obs_source_get_height(m_target); + return 1; +} + +void Source::Mirror::update(obs_data_t* data) { + // Update selected source. + const char* source = obs_data_get_string(data, P_SOURCE); + m_target = obs_get_source_by_name(source); + m_targetName = source; + + // Rescaling + m_rescale = obs_data_get_bool(data, P_SCALING); + if (m_rescale) { // Parse rescaling settings. + uint32_t width, height; + + // Read value. + const char* size = obs_data_get_string(data, P_SCALING_SIZE); + const char* xpos = strchr(size, 'x'); + if (xpos != nullptr) { + // Width + width = strtoul(size, nullptr, 10); + if (errno == ERANGE || width == 0) { + m_rescale = false; + m_width = 1; + } else { + m_width = width; + } + + height = strtoul(xpos + 1, nullptr, 10); + if (errno == ERANGE || height == 0) { + m_rescale = false; + m_height = 1; + } else { + m_height = height; + } + } else { + m_rescale = false; + m_width = 1; + m_height = 1; + } + + ScalingMethod scaler = (ScalingMethod)obs_data_get_int(data, P_SCALING_METHOD); + switch (scaler) { + case ScalingMethod::Bilinear: + m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + break; + case ScalingMethod::BilinearLowRes: + m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_BILINEAR_LOWRES); + break; + case ScalingMethod::Bicubic: + m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_BICUBIC); + break; + case ScalingMethod::Lanczos: + m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_LANCZOS); + break; + case ScalingMethod::Point: + default: + m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + break; + } + } +} + +void Source::Mirror::activate() { + m_active = true; +} + +void Source::Mirror::deactivate() { + m_active = false; +} + +void Source::Mirror::video_tick(float) { + if (!m_target) + m_target = obs_get_source_by_name(m_targetName.c_str()); +} + +void Source::Mirror::video_render(gs_effect_t* effect) { + if (!m_active || (m_source == m_target) || (m_width == 0 || m_height == 0)) + return; + + if (m_rescale && m_width > 0 && m_height > 0 && m_scalingEffect) { + uint32_t sw, sh; + sw = obs_source_get_width(m_target); + sh = obs_source_get_height(m_target); + + // Store original Source Texture + try { + vec4 black; vec4_zero(&black); + auto op = m_renderTarget->Render(m_width, m_height); + gs_ortho(0, sw, 0, sh, 0, 1); + gs_clear(GS_CLEAR_COLOR, &black, 0, 0); + + gs_eparam_t *scale_param = gs_effect_get_param_by_name(m_scalingEffect, "base_dimension_i"); + if (scale_param) { + struct vec2 base_res_i = { + 1.0f / (float)sw, + 1.0f / (float)sh + }; + gs_effect_set_vec2(scale_param, &base_res_i); + } + while (gs_effect_loop(m_scalingEffect, "Draw")) { + obs_source_video_render(m_target); + } + } catch (...) { + return; + } + + while (gs_effect_loop(obs_get_base_effect(OBS_EFFECT_DEFAULT), "Draw")) { + obs_source_draw(m_renderTarget->GetTextureObject(), 0, 0, m_width, m_height, false); + } + } else { + obs_source_video_render(m_target); + } +} diff --git a/source/source-mirror.h b/source/source-mirror.h new file mode 100644 index 00000000..39bd3f16 --- /dev/null +++ b/source/source-mirror.h @@ -0,0 +1,77 @@ +/* +* Modern effects for a modern Streamer +* Copyright (C) 2017 Michael Fabian Dirks +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +*/ + +#pragma once +#include "plugin.h" +#include "gs-rendertarget.h" +#include + +namespace Source { + class MirrorAddon { + public: + MirrorAddon(); + ~MirrorAddon(); + + static const char *get_name(void *); + static void get_defaults(obs_data_t *); + static bool modified_properties(obs_properties_t *, obs_property_t *, obs_data_t *); + static obs_properties_t *get_properties(void *); + + static void *create(obs_data_t *, obs_source_t *); + static void destroy(void *); + + static uint32_t get_width(void *); + static uint32_t get_height(void *); + + static void update(void *, obs_data_t *); + static void activate(void *); + static void deactivate(void *); + static void video_tick(void *, float); + static void video_render(void *, gs_effect_t *); + + private: + obs_source_info osi; + }; + + class Mirror { + public: + Mirror(obs_data_t*, obs_source_t*); + ~Mirror(); + + uint32_t get_width(); + uint32_t get_height(); + + void update(obs_data_t*); + void activate(); + void deactivate(); + void video_tick(float); + void video_render(gs_effect_t*); + + private: + bool m_active; + obs_source_t* m_source; + obs_source_t* m_target; + std::string m_targetName; + + bool m_rescale = false; + uint32_t m_width, m_height; + std::unique_ptr m_renderTarget; + gs_effect_t* m_scalingEffect; + }; +};