mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-12-28 18:41:14 +00:00
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:
parent
a6ce6c5860
commit
721f2bdf8f
5 changed files with 745 additions and 0 deletions
|
@ -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"
|
||||
|
|
120
data/effects/color-grade.effect
Normal file
120
data/effects/color-grade.effect
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
505
source/filters/filter-color-grade.cpp
Normal file
505
source/filters/filter-color-grade.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
84
source/filters/filter-color-grade.hpp
Normal file
84
source/filters/filter-color-grade.hpp
Normal 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
|
Loading…
Reference in a new issue