From 721f2bdf8f9acf63f4b951010fa205c8a8493265 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Fri, 2 Aug 2019 23:48:55 +0200 Subject: [PATCH] filter-color-grade: Fully feature Color Grading filter Allows controlling Lift, Gamma, Gain, Offset, Tint and various Correction factors directly from within OBS without having to create a new LUT. --- CMakeLists.txt | 3 + data/effects/color-grade.effect | 120 ++++++ data/locale/en-US.ini | 33 ++ source/filters/filter-color-grade.cpp | 505 ++++++++++++++++++++++++++ source/filters/filter-color-grade.hpp | 84 +++++ 5 files changed, 745 insertions(+) create mode 100644 data/effects/color-grade.effect create mode 100644 source/filters/filter-color-grade.cpp create mode 100644 source/filters/filter-color-grade.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a2cb571..8f1f478 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,6 +223,7 @@ SET(PROJECT_DATA_LOCALE SET(PROJECT_DATA_EFFECTS "${PROJECT_SOURCE_DIR}/data/effects/channel-mask.effect" "${PROJECT_SOURCE_DIR}/data/effects/color-conversion.effect" + "${PROJECT_SOURCE_DIR}/data/effects/color-grade.effect" "${PROJECT_SOURCE_DIR}/data/effects/displace.effect" "${PROJECT_SOURCE_DIR}/data/effects/mask.effect" "${PROJECT_SOURCE_DIR}/data/effects/mipgen.effect" @@ -336,6 +337,8 @@ SET(PROJECT_PRIVATE_SOURCE # Filters "${PROJECT_SOURCE_DIR}/source/filters/filter-blur.hpp" "${PROJECT_SOURCE_DIR}/source/filters/filter-blur.cpp" + "${PROJECT_SOURCE_DIR}/source/filters/filter-color-grade.hpp" + "${PROJECT_SOURCE_DIR}/source/filters/filter-color-grade.cpp" "${PROJECT_SOURCE_DIR}/source/filters/filter-custom-shader.hpp" "${PROJECT_SOURCE_DIR}/source/filters/filter-custom-shader.cpp" "${PROJECT_SOURCE_DIR}/source/filters/filter-displacement.hpp" diff --git a/data/effects/color-grade.effect b/data/effects/color-grade.effect new file mode 100644 index 0000000..cb2f016 --- /dev/null +++ b/data/effects/color-grade.effect @@ -0,0 +1,120 @@ +// Parameters +uniform float4x4 ViewProj; +uniform texture2d image; +uniform float4 pLift; +uniform float4 pGamma; +uniform float4 pGain; +uniform float4 pOffset; +uniform float3 pTintLow; +uniform float3 pTintMid; +uniform float3 pTintHig; +uniform float4 pCorrection; + +// Data +sampler_state def_sampler { + Filter = Point; + AddressU = Clamp; + AddressV = Clamp; + MinLOD = 0; + MaxLOD = 0; +}; + +struct VertDataIn { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +struct VertDataOut { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertDataOut VSDefault(VertDataIn v) +{ + VertDataOut ov; + ov.pos = mul(float4(v.pos.xyz, 1.0), ViewProj); + ov.uv = v.uv; + return ov; +} + +float4 Lift(float4 v) +{ + v.rgb = pLift.aaa + v.rgb; + v.rgb = pLift.rgb + v.rgb; + return v; +} + +float4 Gamma(float4 v) +{ + v.rgb = pow(pow(v.rgb, pGamma.rgb), pGamma.aaa); + return v; +} + +float4 Gain(float4 v) +{ + v.rgb *= pGain.rgb; + v.rgb *= pGain.a; + return v; +} + +float4 Offset(float4 v) +{ + v.rgb = pOffset.aaa + v.rgb; + v.rgb = pOffset.rgb + v.rgb; + return v; +} + +float4 RGBtoHSV(float4 RGBA) { + const float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + const float e = 1.0e-10; + float4 p = lerp(float4(RGBA.bg, K.wz), float4(RGBA.gb, K.xy), step(RGBA.b, RGBA.g)); + float4 q = lerp(float4(p.xyw, RGBA.r), float4(RGBA.r, p.yzx), step(p.x, RGBA.r)); + float d = q.x - min(q.w, q.y); + return float4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, RGBA.a); +} + +float4 HSVtoRGB(float4 HSVA) { + const float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + float4 v = float4(0,0,0,0); + v.rgb = HSVA.z * lerp(K.xxx, clamp(abs(frac(HSVA.xxx + K.xyz) * 6.0 - K.www) - K.xxx, 0.0, 1.0), HSVA.y); + v.a = HSVA.a; + return v; +} + +float4 Tint(float4 v) +{ + float4 v1 = RGBtoHSV(v); + float3 tint = float3(0,0,0); + if (v1.b > 0.5) { + tint = lerp(pTintMid, pTintHig, v1.b * 2.0 - 1.0); + } else { + tint = lerp(pTintLow, pTintMid, v1.b * 2.0); + } + v.rgb *= tint; + return v; +} + +float4 Correction(float4 v) +{ + float4 v1 = RGBtoHSV(v); + v1.r += pCorrection.r; + v1.g *= pCorrection.g; + v1.b *= pCorrection.b; + float4 v2 = HSVtoRGB(v1); + v2.rgb = ((v2.rgb - 0.5) * max(pCorrection.a, 0)) + 0.5; + return v2; +} + +float4 PSColorGrade(VertDataOut v) : TARGET +{ + return Correction(Tint(Offset(Gain(Gamma(Lift(image.Sample(def_sampler, v.uv))))))); +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v); + pixel_shader = PSColorGrade(v); + } +} diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 8c346a0..c268d93 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -106,6 +106,39 @@ Filter.Blur.Mask.Alpha.Description="Filter the mask by this alpha value before a Filter.Blur.Mask.Multiplier="Mask Multiplier" Filter.Blur.Mask.Multiplier.Description="Multiply the final mask value by this value." +# Filter - Color Grade +Filter.ColorGrade="Color Grading" +Filter.ColorGrade.Lift="Lift" +Filter.ColorGrade.Lift.Red="Red Lift" +Filter.ColorGrade.Lift.Green="Green Lift" +Filter.ColorGrade.Lift.Blue="Blue Lift" +Filter.ColorGrade.Lift.All="All Lift" +Filter.ColorGrade.Gamma.Red="Red Gamma" +Filter.ColorGrade.Gamma.Green="Green Gamma" +Filter.ColorGrade.Gamma.Blue="Blue Gamma" +Filter.ColorGrade.Gamma.All="All Gamma" +Filter.ColorGrade.Gain.Red="Red Gain" +Filter.ColorGrade.Gain.Green="Green Gain" +Filter.ColorGrade.Gain.Blue="Blue Gain" +Filter.ColorGrade.Gain.All="All Gain" +Filter.ColorGrade.Offset.Red="Red Offset" +Filter.ColorGrade.Offset.Green="Green Offset" +Filter.ColorGrade.Offset.Blue="Blue Offset" +Filter.ColorGrade.Offset.All="All Offset" +Filter.ColorGrade.Tint.Shadow.Red="Shadow Red Tint" +Filter.ColorGrade.Tint.Shadow.Green="Shadow Green Tint" +Filter.ColorGrade.Tint.Shadow.Blue="Shadow Blue Tint" +Filter.ColorGrade.Tint.Midtone.Red="Midtone Red Tint" +Filter.ColorGrade.Tint.Midtone.Green="Midtone Green Tint" +Filter.ColorGrade.Tint.Midtone.Blue="Midtone Blue Tint" +Filter.ColorGrade.Tint.Highlight.Red="Highlight Red Tint" +Filter.ColorGrade.Tint.Highlight.Green="Highlight Green Tint" +Filter.ColorGrade.Tint.Highlight.Blue="Highlight Blue Tint" +Filter.ColorGrade.Correction.Hue="Hue Shift" +Filter.ColorGrade.Correction.Saturation="Saturation" +Filter.ColorGrade.Correction.Lightness="Lightness" +Filter.ColorGrade.Correction.Contrast="Contrast" + # Filter - Displacement Filter.Displacement="Displacement Mapping" Filter.Displacement.File="File" diff --git a/source/filters/filter-color-grade.cpp b/source/filters/filter-color-grade.cpp new file mode 100644 index 0000000..8965b50 --- /dev/null +++ b/source/filters/filter-color-grade.cpp @@ -0,0 +1,505 @@ +/* + * 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 "filter-color-grade.hpp" +#include "strings.hpp" +#include "util-math.hpp" + +// OBS +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4201) +#endif +#include +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#define ST "Filter.ColorGrade" +#define ST_LIFT ST ".Lift" +#define ST_LIFT_(x) ST_LIFT "." D_VSTR(x) +#define ST_GAMMA ST ".Gamma" +#define ST_GAMMA_(x) ST_GAMMA "." D_VSTR(x) +#define ST_GAIN ST ".Gain" +#define ST_GAIN_(x) ST_GAIN "." D_VSTR(x) +#define ST_OFFSET ST ".Offset" +#define ST_OFFSET_(x) ST_OFFSET "." D_VSTR(x) +#define ST_TINT ST ".Tint" +#define ST_TINT_(x, y) ST_TINT "." D_VSTR(x) "." D_VSTR(y) +#define ST_CORRECTION ST ".Correction" +#define ST_CORRECTION_(x) ST_CORRECTION "." D_VSTR(x) + +#define RED Red +#define GREEN Green +#define BLUE Blue +#define ALL All +#define HUE Hue +#define SATURATION Saturation +#define LIGHTNESS Lightness +#define CONTRAST Contrast +#define TONE_LOW Shadow +#define TONE_MID Midtone +#define TONE_HIG Highlight + +// Initializer & Finalizer +INITIALIZER(FilterColorGradeInit) +{ + initializerFunctions.push_back([] { filter::color_grade::color_grade_factory::initialize(); }); + finalizerFunctions.push_back([] { filter::color_grade::color_grade_factory::finalize(); }); +} + +const char* get_name(void*) +{ + return P_TRANSLATE(ST); +} + +void get_defaults(obs_data_t* data) +{ + obs_data_set_default_double(data, ST_LIFT_(RED), 0); + obs_data_set_default_double(data, ST_LIFT_(GREEN), 0); + obs_data_set_default_double(data, ST_LIFT_(BLUE), 0); + obs_data_set_default_double(data, ST_LIFT_(ALL), 0); + obs_data_set_default_double(data, ST_GAMMA_(RED), 0); + obs_data_set_default_double(data, ST_GAMMA_(GREEN), 0); + obs_data_set_default_double(data, ST_GAMMA_(BLUE), 0); + obs_data_set_default_double(data, ST_GAMMA_(ALL), 0); + obs_data_set_default_double(data, ST_GAIN_(RED), 100.0); + obs_data_set_default_double(data, ST_GAIN_(GREEN), 100.0); + obs_data_set_default_double(data, ST_GAIN_(BLUE), 100.0); + obs_data_set_default_double(data, ST_GAIN_(ALL), 100.0); + obs_data_set_default_double(data, ST_OFFSET_(RED), 0.0); + obs_data_set_default_double(data, ST_OFFSET_(GREEN), 0.0); + obs_data_set_default_double(data, ST_OFFSET_(BLUE), 0.0); + obs_data_set_default_double(data, ST_OFFSET_(ALL), 0.0); + obs_data_set_default_double(data, ST_TINT_(TONE_LOW, RED), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_LOW, GREEN), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_LOW, BLUE), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_MID, RED), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_MID, GREEN), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_MID, BLUE), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_HIG, RED), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_HIG, GREEN), 100.0); + obs_data_set_default_double(data, ST_TINT_(TONE_HIG, BLUE), 100.0); + obs_data_set_default_double(data, ST_CORRECTION_(HUE), 0.0); + obs_data_set_default_double(data, ST_CORRECTION_(SATURATION), 100.0); + obs_data_set_default_double(data, ST_CORRECTION_(LIGHTNESS), 100.0); + obs_data_set_default_double(data, ST_CORRECTION_(CONTRAST), 100.0); +} + +obs_properties_t* get_properties(void*) +{ + obs_properties_t* pr = obs_properties_create(); + + { + obs_properties_t* grp = pr; + if (!util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(pr, ST_LIFT, P_TRANSLATE(ST_LIFT), OBS_GROUP_NORMAL, grp); + } + + obs_properties_add_float_slider(grp, ST_LIFT_(RED), P_TRANSLATE(ST_LIFT_(RED)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_LIFT_(GREEN), P_TRANSLATE(ST_LIFT_(GREEN)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_LIFT_(BLUE), P_TRANSLATE(ST_LIFT_(BLUE)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_LIFT_(ALL), P_TRANSLATE(ST_LIFT_(ALL)), -1000.0, 1000.0, 0.01); + } + + { + obs_properties_t* grp = pr; + if (!util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(pr, ST_GAMMA, P_TRANSLATE(ST_GAMMA), OBS_GROUP_NORMAL, grp); + } + + obs_properties_add_float_slider(grp, ST_GAMMA_(RED), P_TRANSLATE(ST_GAMMA_(RED)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_GAMMA_(GREEN), P_TRANSLATE(ST_GAMMA_(GREEN)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_GAMMA_(BLUE), P_TRANSLATE(ST_GAMMA_(BLUE)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_GAMMA_(ALL), P_TRANSLATE(ST_GAMMA_(ALL)), -1000.0, 1000.0, 0.01); + } + + { + obs_properties_t* grp = pr; + if (!util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(pr, ST_GAIN, P_TRANSLATE(ST_GAIN), OBS_GROUP_NORMAL, grp); + } + + obs_properties_add_float_slider(grp, ST_GAIN_(RED), P_TRANSLATE(ST_GAIN_(RED)), 0.01, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_GAIN_(GREEN), P_TRANSLATE(ST_GAIN_(GREEN)), 0.01, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_GAIN_(BLUE), P_TRANSLATE(ST_GAIN_(BLUE)), 0.01, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_GAIN_(ALL), P_TRANSLATE(ST_GAIN_(ALL)), 0.01, 1000.0, 0.01); + } + + { + obs_properties_t* grp = pr; + if (!util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(pr, ST_OFFSET, P_TRANSLATE(ST_OFFSET), OBS_GROUP_NORMAL, grp); + } + + obs_properties_add_float_slider(grp, ST_OFFSET_(RED), P_TRANSLATE(ST_OFFSET_(RED)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_OFFSET_(GREEN), P_TRANSLATE(ST_OFFSET_(GREEN)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_OFFSET_(BLUE), P_TRANSLATE(ST_OFFSET_(BLUE)), -1000.0, 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_OFFSET_(ALL), P_TRANSLATE(ST_OFFSET_(ALL)), -1000.0, 1000.0, 0.01); + } + + { + obs_properties_t* grp = pr; + if (!util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(pr, ST_TINT, P_TRANSLATE(ST_TINT), OBS_GROUP_NORMAL, grp); + } + + obs_properties_add_float_slider(grp, ST_TINT_(TONE_LOW, RED), P_TRANSLATE(ST_TINT_(TONE_LOW, RED)), 0, 1000.0, + 0.01); + obs_properties_add_float_slider(grp, ST_TINT_(TONE_LOW, GREEN), P_TRANSLATE(ST_TINT_(TONE_LOW, GREEN)), 0, + 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_TINT_(TONE_LOW, BLUE), P_TRANSLATE(ST_TINT_(TONE_LOW, BLUE)), 0, 1000.0, + 0.01); + + obs_properties_add_float_slider(grp, ST_TINT_(TONE_MID, RED), P_TRANSLATE(ST_TINT_(TONE_MID, RED)), 0, 1000.0, + 0.01); + obs_properties_add_float_slider(grp, ST_TINT_(TONE_MID, GREEN), P_TRANSLATE(ST_TINT_(TONE_MID, GREEN)), 0, + 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_TINT_(TONE_MID, BLUE), P_TRANSLATE(ST_TINT_(TONE_MID, BLUE)), 0, 1000.0, + 0.01); + + obs_properties_add_float_slider(grp, ST_TINT_(TONE_HIG, RED), P_TRANSLATE(ST_TINT_(TONE_HIG, RED)), 0, 1000.0, + 0.01); + obs_properties_add_float_slider(grp, ST_TINT_(TONE_HIG, GREEN), P_TRANSLATE(ST_TINT_(TONE_HIG, GREEN)), 0, + 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_TINT_(TONE_HIG, BLUE), P_TRANSLATE(ST_TINT_(TONE_HIG, BLUE)), 0, 1000.0, + 0.01); + } + + { + obs_properties_t* grp = pr; + if (!util::are_property_groups_broken()) { + grp = obs_properties_create(); + obs_properties_add_group(pr, ST_OFFSET, P_TRANSLATE(ST_OFFSET), OBS_GROUP_NORMAL, grp); + } + + obs_properties_add_float_slider(grp, ST_CORRECTION_(HUE), P_TRANSLATE(ST_CORRECTION_(HUE)), -180, 180.0, 0.01); + obs_properties_add_float_slider(grp, ST_CORRECTION_(SATURATION), P_TRANSLATE(ST_CORRECTION_(SATURATION)), 0.0, + 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_CORRECTION_(LIGHTNESS), P_TRANSLATE(ST_CORRECTION_(LIGHTNESS)), 0.0, + 1000.0, 0.01); + obs_properties_add_float_slider(grp, ST_CORRECTION_(CONTRAST), P_TRANSLATE(ST_CORRECTION_(CONTRAST)), 0.0, + 1000.0, 0.01); + } + + return pr; +} + +void* create(obs_data_t* data, obs_source_t* source) +try { + return new filter::color_grade::color_grade_instance(data, source); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to create: %s", obs_source_get_name(source), ex.what()); + return nullptr; +} + +void destroy(void* ptr) +try { + delete reinterpret_cast(ptr); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to destroy: %s", ex.what()); +} + +uint32_t get_width(void* ptr) +try { + return reinterpret_cast(ptr)->get_width(); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to get width: %s", ex.what()); +} + +uint32_t get_height(void* ptr) +try { + return reinterpret_cast(ptr)->get_height(); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to get height: %s", ex.what()); +} + +void update(void* ptr, obs_data_t* data) +try { + reinterpret_cast(ptr)->update(data); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to update: %s", ex.what()); +} + +void activate(void* ptr) +try { + reinterpret_cast(ptr)->activate(); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to activate: %s", ex.what()); +} + +void deactivate(void* ptr) +try { + reinterpret_cast(ptr)->deactivate(); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to deactivate: %s", ex.what()); +} + +void video_tick(void* ptr, float time) +try { + reinterpret_cast(ptr)->video_tick(time); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to tick video: %s", ex.what()); +} + +void video_render(void* ptr, gs_effect_t* effect) +try { + reinterpret_cast(ptr)->video_render(effect); +} catch (std::exception& ex) { + P_LOG_ERROR(" Failed to render video: %s", ex.what()); +} + +static std::shared_ptr factory_instance = nullptr; + +void filter::color_grade::color_grade_factory::initialize() +{ + factory_instance = std::make_shared(); +} + +void filter::color_grade::color_grade_factory::finalize() +{ + factory_instance.reset(); +} + +std::shared_ptr filter::color_grade::color_grade_factory::get() +{ + return factory_instance; +} + +filter::color_grade::color_grade_factory::color_grade_factory() +{ + memset(&sourceInfo, 0, sizeof(obs_source_info)); + sourceInfo.id = "obs-stream-effects-filter-color-grade"; + sourceInfo.type = OBS_SOURCE_TYPE_FILTER; + sourceInfo.output_flags = OBS_SOURCE_VIDEO; + sourceInfo.get_name = get_name; + sourceInfo.get_defaults = get_defaults; + sourceInfo.get_properties = get_properties; + + sourceInfo.create = create; + sourceInfo.destroy = destroy; + sourceInfo.update = update; + sourceInfo.activate = activate; + sourceInfo.deactivate = deactivate; + sourceInfo.video_tick = video_tick; + sourceInfo.video_render = video_render; + + obs_register_source(&sourceInfo); +} + +filter::color_grade::color_grade_factory::~color_grade_factory() {} + +filter::color_grade::color_grade_instance::~color_grade_instance() {} + +filter::color_grade::color_grade_instance::color_grade_instance(obs_data_t* data, obs_source_t* context) + : _active(true), _self(context) +{ + update(data); + + { + char* file = obs_module_file("effects/color-grade.effect"); + try { + _effect = std::make_shared(file); + bfree(file); + } catch (std::runtime_error& ex) { + P_LOG_ERROR(" Loading effect '%s' failed with error(s): %s", file, ex.what()); + bfree(file); + throw ex; + } + } + { + _rt_source = std::make_unique(GS_RGBA, GS_ZS_NONE); + { + auto op = _rt_source->render(1, 1); + } + _tex_source = _rt_source->get_texture(); + } + { + _rt_grade = std::make_unique(GS_RGBA, GS_ZS_NONE); + { + auto op = _rt_grade->render(1, 1); + } + _tex_grade = _rt_grade->get_texture(); + } +} + +uint32_t filter::color_grade::color_grade_instance::get_width() +{ + return 0; +} + +uint32_t filter::color_grade::color_grade_instance::get_height() +{ + return 0; +} + +float_t fix_gamma_value(double_t v) +{ + if (v < 0.0) { + return static_cast(-v + 1.0); + } else { + return static_cast(1.0 / (v + 1.0)); + } +} + +void filter::color_grade::color_grade_instance::update(obs_data_t* data) +{ + _lift.x = static_cast(obs_data_get_double(data, ST_LIFT_(RED)) / 100.0); + _lift.y = static_cast(obs_data_get_double(data, ST_LIFT_(GREEN)) / 100.0); + _lift.z = static_cast(obs_data_get_double(data, ST_LIFT_(BLUE)) / 100.0); + _lift.w = static_cast(obs_data_get_double(data, ST_LIFT_(ALL)) / 100.0); + _gamma.x = fix_gamma_value(obs_data_get_double(data, ST_GAMMA_(RED)) / 100.0); + _gamma.y = fix_gamma_value(obs_data_get_double(data, ST_GAMMA_(GREEN)) / 100.0); + _gamma.z = fix_gamma_value(obs_data_get_double(data, ST_GAMMA_(BLUE)) / 100.0); + _gamma.w = fix_gamma_value(obs_data_get_double(data, ST_GAMMA_(ALL)) / 100.0); + _gain.x = static_cast(obs_data_get_double(data, ST_GAIN_(RED)) / 100.0); + _gain.y = static_cast(obs_data_get_double(data, ST_GAIN_(GREEN)) / 100.0); + _gain.z = static_cast(obs_data_get_double(data, ST_GAIN_(BLUE)) / 100.0); + _gain.w = static_cast(obs_data_get_double(data, ST_GAIN_(ALL)) / 100.0); + _offset.x = static_cast(obs_data_get_double(data, ST_OFFSET_(RED)) / 100.0); + _offset.y = static_cast(obs_data_get_double(data, ST_OFFSET_(GREEN)) / 100.0); + _offset.z = static_cast(obs_data_get_double(data, ST_OFFSET_(BLUE)) / 100.0); + _offset.w = static_cast(obs_data_get_double(data, ST_OFFSET_(ALL)) / 100.0); + _tint_low.x = static_cast(obs_data_get_double(data, ST_TINT_(TONE_LOW, RED)) / 100.0); + _tint_low.y = static_cast(obs_data_get_double(data, ST_TINT_(TONE_LOW, GREEN)) / 100.0); + _tint_low.z = static_cast(obs_data_get_double(data, ST_TINT_(TONE_LOW, BLUE)) / 100.0); + _tint_mid.x = static_cast(obs_data_get_double(data, ST_TINT_(TONE_MID, RED)) / 100.0); + _tint_mid.y = static_cast(obs_data_get_double(data, ST_TINT_(TONE_MID, GREEN)) / 100.0); + _tint_mid.z = static_cast(obs_data_get_double(data, ST_TINT_(TONE_MID, BLUE)) / 100.0); + _tint_hig.x = static_cast(obs_data_get_double(data, ST_TINT_(TONE_HIG, RED)) / 100.0); + _tint_hig.y = static_cast(obs_data_get_double(data, ST_TINT_(TONE_HIG, GREEN)) / 100.0); + _tint_hig.z = static_cast(obs_data_get_double(data, ST_TINT_(TONE_HIG, BLUE)) / 100.0); + _correction.x = static_cast(obs_data_get_double(data, ST_CORRECTION_(HUE)) / 360.0); + _correction.y = static_cast(obs_data_get_double(data, ST_CORRECTION_(SATURATION)) / 100.0); + _correction.z = static_cast(obs_data_get_double(data, ST_CORRECTION_(LIGHTNESS)) / 100.0); + _correction.w = static_cast(obs_data_get_double(data, ST_CORRECTION_(CONTRAST)) / 100.0); +} + +void filter::color_grade::color_grade_instance::activate() +{ + _active = true; +} + +void filter::color_grade::color_grade_instance::deactivate() +{ + _active = false; +} + +void filter::color_grade::color_grade_instance::video_tick(float) +{ + _source_updated = false; + _grade_updated = false; +} + +void filter::color_grade::color_grade_instance::video_render(gs_effect_t*) +{ + // Grab initial values. + obs_source_t* parent = obs_filter_get_parent(_self); + obs_source_t* target = obs_filter_get_target(_self); + uint32_t width = obs_source_get_base_width(target); + uint32_t height = obs_source_get_base_height(target); + gs_effect_t* effect_default = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + + // Skip filter if anything is wrong. + if (!_active || !parent || !target || !width || !height || !effect_default) { + obs_source_skip_video_filter(_self); + return; + } + + if (!_source_updated) { + if (obs_source_process_filter_begin(_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) { + auto op = _rt_source->render(width, height); + gs_blend_state_push(); + gs_reset_blend_state(); + gs_set_cull_mode(GS_NEITHER); + gs_enable_color(true, true, true, true); + gs_enable_blending(false); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_ortho(0, static_cast(width), 0, static_cast(height), -1., 1.); + obs_source_process_filter_end(_self, effect_default, width, height); + gs_blend_state_pop(); + } + + _tex_source = _rt_source->get_texture(); + _source_updated = true; + } + + if (!_grade_updated) { + { + auto op = _rt_grade->render(width, height); + gs_blend_state_push(); + gs_reset_blend_state(); + gs_set_cull_mode(GS_NEITHER); + gs_enable_color(true, true, true, true); + gs_enable_blending(false); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_ortho(0, static_cast(width), 0, static_cast(height), -1., 1.); + + if (_effect->has_parameter("image")) + _effect->get_parameter("image").set_texture(_tex_source); + if (_effect->has_parameter("pLift")) + _effect->get_parameter("pLift").set_float4(_lift); + if (_effect->has_parameter("pGamma")) + _effect->get_parameter("pGamma").set_float4(_gamma); + if (_effect->has_parameter("pGain")) + _effect->get_parameter("pGain").set_float4(_gain); + if (_effect->has_parameter("pOffset")) + _effect->get_parameter("pOffset").set_float4(_offset); + if (_effect->has_parameter("pTintLow")) + _effect->get_parameter("pTintLow").set_float3(_tint_low); + if (_effect->has_parameter("pTintMid")) + _effect->get_parameter("pTintMid").set_float3(_tint_mid); + if (_effect->has_parameter("pTintHig")) + _effect->get_parameter("pTintHig").set_float3(_tint_hig); + if (_effect->has_parameter("pCorrection")) + _effect->get_parameter("pCorrection").set_float4(_correction); + + while (gs_effect_loop(_effect->get_object(), "Draw")) { + gs_draw_sprite(nullptr, 0, width, height); + } + + gs_blend_state_pop(); + } + + _tex_grade = _rt_grade->get_texture(); + _source_updated = true; + } + + // Render final result. + { + auto shader = obs_get_base_effect(OBS_EFFECT_DEFAULT); + gs_enable_depth_test(false); + while (gs_effect_loop(shader, "Draw")) { + gs_effect_set_texture(gs_effect_get_param_by_name(shader, "image"), + _tex_grade ? _tex_grade->get_object() : nullptr); + gs_draw_sprite(nullptr, 0, width, height); + } + } +} diff --git a/source/filters/filter-color-grade.hpp b/source/filters/filter-color-grade.hpp new file mode 100644 index 0000000..49bdc7d --- /dev/null +++ b/source/filters/filter-color-grade.hpp @@ -0,0 +1,84 @@ +/* + * 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 +#include +#include "obs/gs/gs-mipmapper.hpp" +#include "obs/gs/gs-rendertarget.hpp" +#include "obs/gs/gs-texture.hpp" +#include "obs/gs/gs-vertexbuffer.hpp" +#include "plugin.hpp" + +namespace filter { + namespace color_grade { + class color_grade_factory { + obs_source_info sourceInfo; + + public: // Singleton + static void initialize(); + static void finalize(); + static std::shared_ptr get(); + + public: + color_grade_factory(); + ~color_grade_factory(); + }; + + class color_grade_instance { + bool _active; + obs_source_t* _self; + + std::shared_ptr _effect; + + // Source + std::unique_ptr _rt_source; + std::shared_ptr _tex_source; + bool _source_updated; + + // Grading + std::unique_ptr _rt_grade; + std::shared_ptr _tex_grade; + bool _grade_updated; + + // Parameters + vec4 _lift; + vec4 _gamma; + vec4 _gain; + vec4 _offset; + vec3 _tint_low; + vec3 _tint_mid; + vec3 _tint_hig; + vec4 _correction; + + public: + ~color_grade_instance(); + color_grade_instance(obs_data_t*, obs_source_t*); + + 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*); + }; + } // namespace color_grade +} // namespace filter