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.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2019-08-02 23:48:55 +02:00
parent a6ce6c5860
commit 721f2bdf8f
5 changed files with 745 additions and 0 deletions

View file

@ -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"

View file

@ -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);
}
}

View file

@ -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"

View file

@ -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 <graphics/graphics.h>
#include <graphics/matrix4.h>
#include <util/platform.h>
#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("<filter-color-grade> Failed to create: %s", obs_source_get_name(source), ex.what());
return nullptr;
}
void destroy(void* ptr)
try {
delete reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr);
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to destroy: %s", ex.what());
}
uint32_t get_width(void* ptr)
try {
return reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr)->get_width();
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to get width: %s", ex.what());
}
uint32_t get_height(void* ptr)
try {
return reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr)->get_height();
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to get height: %s", ex.what());
}
void update(void* ptr, obs_data_t* data)
try {
reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr)->update(data);
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to update: %s", ex.what());
}
void activate(void* ptr)
try {
reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr)->activate();
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to activate: %s", ex.what());
}
void deactivate(void* ptr)
try {
reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr)->deactivate();
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to deactivate: %s", ex.what());
}
void video_tick(void* ptr, float time)
try {
reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr)->video_tick(time);
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to tick video: %s", ex.what());
}
void video_render(void* ptr, gs_effect_t* effect)
try {
reinterpret_cast<filter::color_grade::color_grade_instance*>(ptr)->video_render(effect);
} catch (std::exception& ex) {
P_LOG_ERROR("<filter-color-grade> Failed to render video: %s", ex.what());
}
static std::shared_ptr<filter::color_grade::color_grade_factory> factory_instance = nullptr;
void filter::color_grade::color_grade_factory::initialize()
{
factory_instance = std::make_shared<filter::color_grade::color_grade_factory>();
}
void filter::color_grade::color_grade_factory::finalize()
{
factory_instance.reset();
}
std::shared_ptr<filter::color_grade::color_grade_factory> 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<gs::effect>(file);
bfree(file);
} catch (std::runtime_error& ex) {
P_LOG_ERROR("<filter-color-grade> Loading effect '%s' failed with error(s): %s", file, ex.what());
bfree(file);
throw ex;
}
}
{
_rt_source = std::make_unique<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
{
auto op = _rt_source->render(1, 1);
}
_tex_source = _rt_source->get_texture();
}
{
_rt_grade = std::make_unique<gs::rendertarget>(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<float_t>(-v + 1.0);
} else {
return static_cast<float_t>(1.0 / (v + 1.0));
}
}
void filter::color_grade::color_grade_instance::update(obs_data_t* data)
{
_lift.x = static_cast<float_t>(obs_data_get_double(data, ST_LIFT_(RED)) / 100.0);
_lift.y = static_cast<float_t>(obs_data_get_double(data, ST_LIFT_(GREEN)) / 100.0);
_lift.z = static_cast<float_t>(obs_data_get_double(data, ST_LIFT_(BLUE)) / 100.0);
_lift.w = static_cast<float_t>(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<float_t>(obs_data_get_double(data, ST_GAIN_(RED)) / 100.0);
_gain.y = static_cast<float_t>(obs_data_get_double(data, ST_GAIN_(GREEN)) / 100.0);
_gain.z = static_cast<float_t>(obs_data_get_double(data, ST_GAIN_(BLUE)) / 100.0);
_gain.w = static_cast<float_t>(obs_data_get_double(data, ST_GAIN_(ALL)) / 100.0);
_offset.x = static_cast<float_t>(obs_data_get_double(data, ST_OFFSET_(RED)) / 100.0);
_offset.y = static_cast<float_t>(obs_data_get_double(data, ST_OFFSET_(GREEN)) / 100.0);
_offset.z = static_cast<float_t>(obs_data_get_double(data, ST_OFFSET_(BLUE)) / 100.0);
_offset.w = static_cast<float_t>(obs_data_get_double(data, ST_OFFSET_(ALL)) / 100.0);
_tint_low.x = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_LOW, RED)) / 100.0);
_tint_low.y = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_LOW, GREEN)) / 100.0);
_tint_low.z = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_LOW, BLUE)) / 100.0);
_tint_mid.x = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_MID, RED)) / 100.0);
_tint_mid.y = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_MID, GREEN)) / 100.0);
_tint_mid.z = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_MID, BLUE)) / 100.0);
_tint_hig.x = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_HIG, RED)) / 100.0);
_tint_hig.y = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_HIG, GREEN)) / 100.0);
_tint_hig.z = static_cast<float_t>(obs_data_get_double(data, ST_TINT_(TONE_HIG, BLUE)) / 100.0);
_correction.x = static_cast<float_t>(obs_data_get_double(data, ST_CORRECTION_(HUE)) / 360.0);
_correction.y = static_cast<float_t>(obs_data_get_double(data, ST_CORRECTION_(SATURATION)) / 100.0);
_correction.z = static_cast<float_t>(obs_data_get_double(data, ST_CORRECTION_(LIGHTNESS)) / 100.0);
_correction.w = static_cast<float_t>(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<float_t>(width), 0, static_cast<float_t>(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<float_t>(width), 0, static_cast<float_t>(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);
}
}
}

View file

@ -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 <memory>
#include <vector>
#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<color_grade_factory> get();
public:
color_grade_factory();
~color_grade_factory();
};
class color_grade_instance {
bool _active;
obs_source_t* _self;
std::shared_ptr<gs::effect> _effect;
// Source
std::unique_ptr<gs::rendertarget> _rt_source;
std::shared_ptr<gs::texture> _tex_source;
bool _source_updated;
// Grading
std::unique_ptr<gs::rendertarget> _rt_grade;
std::shared_ptr<gs::texture> _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