diff --git a/data/effects/color-grade.effect b/data/effects/color-grade.effect index cb2f0163..7cbf0836 100644 --- a/data/effects/color-grade.effect +++ b/data/effects/color-grade.effect @@ -5,11 +5,27 @@ uniform float4 pLift; uniform float4 pGamma; uniform float4 pGain; uniform float4 pOffset; +uniform int pTintDetection; // 0 = HSV, 1 = HSL, 2 = YUV HD SDR +uniform int pTintMode; // 0 = Linear, 1 = Exp, 2 = Exp2, 3 = Log, 4 = Log10 +uniform float pTintLumaExponent; uniform float3 pTintLow; uniform float3 pTintMid; uniform float3 pTintHig; uniform float4 pCorrection; +#define TINT_DETECTION_HSV 0 +#define TINT_DETECTION_HSL 1 +#define TINT_DETECTION_YUV_SDR 2 + +#define TINT_MODE_LINEAR 0 +#define TINT_MODE_EXP 1 +#define TINT_MODE_EXP2 2 +#define TINT_MODE_LOG 3 +#define TINT_MODE_LOG10 4 + +#define C_e 2,7182818284590452353602874713527 +#define C_log2_e 1.4426950408889634073599246810019 // Windows calculator: log(e(1)) / log(2) + // Data sampler_state def_sampler { Filter = Point; @@ -37,6 +53,90 @@ VertDataOut VSDefault(VertDataIn v) return ov; } +// Utility functions ----------------------------------------------------------- +float4 RGBtoYUV(float4 rgba, float3x3 yuv) { + return float4( + rgba.r * yuv._m00 + rgba.g * yuv._m01 + rgba.b * yuv._m02, + rgba.r * yuv._m10 + rgba.g * yuv._m11 + rgba.b * yuv._m12, + rgba.r * yuv._m20 + rgba.g * yuv._m21 + rgba.b * yuv._m22, + rgba.a + ) + float4(0,0.5,0.5,0); +} + +float4 YUVtoRGB(float4 yuva, float3x3 yuvi) { + yuva.gb -= 0.5; + return float4( + yuva.r * yuvi._m00 + yuva.g * yuvi._m01 + yuva.b * yuvi._m02, + yuva.r * yuvi._m10 + yuva.g * yuvi._m11 + yuva.b * yuvi._m12, + yuva.r * yuvi._m20 + yuva.g * yuvi._m21 + yuva.b * yuvi._m22, + yuva.a); +} + +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 HSLtoRGB(float4 HSLA) { + float3 rgb = clamp( + abs( + mod( + HSLA.x * 6.0 + float3(0.0, 4.0, 2.0), + 6.0 + ) - 3.0 + ) - 1.0, + 0.0, + 1.0 + ); + return float4(HSLA.z + HSLA.y * (rgb - 0.5) * (1.0 - abs(2.0 * HSLA.z - 1.0)), HSLA.a); +} + +float4 RGBtoHSL(float4 RGBA) { + float h = 0.0; + float s = 0.0; + float l = 0.0; + float r = RGBA.r; + float g = RGBA.g; + float b = RGBA.b; + float cMin = min( r, min( g, b ) ); + float cMax = max( r, max( g, b ) ); + + l = ( cMax + cMin ) / 2.0; + if ( cMax > cMin ) { + float cDelta = cMax - cMin; + + //s = l < .05 ? cDelta / ( cMax + cMin ) : cDelta / ( 2.0 - ( cMax + cMin ) ); Original + s = l < .0 ? cDelta / ( cMax + cMin ) : cDelta / ( 2.0 - ( cMax + cMin ) ); + + if ( r == cMax ) { + h = ( g - b ) / cDelta; + } else if ( g == cMax ) { + h = 2.0 + ( b - r ) / cDelta; + } else { + h = 4.0 + ( r - g ) / cDelta; + } + + if ( h < 0.0) { + h += 6.0; + } + h = h / 6.0; + } + return float4( h, s, l, RGBA.a ); +} + +// Actual Code ----------------------------------------------------------------- float4 Lift(float4 v) { v.rgb = pLift.aaa + v.rgb; @@ -64,25 +164,33 @@ float4 Offset(float4 v) 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) { + float value = 0.; + if (pTintDetection == TINT_DETECTION_HSV) { // HSV + value = RGBtoHSV(v).z; + } else if (pTintDetection == TINT_DETECTION_HSL) { // HSL + value = RGBtoHSL(v).z; + } else if (pTintDetection == TINT_DETECTION_YUV_SDR) { // YUV HD SDR + const float3x3 mYUV709n = { // Normalized + 0.2126, 0.7152, 0.0722, + -0.1145721060573399, -0.3854278939426601, 0.5, + 0.5, -0.4541529083058166, -0.0458470916941834 + }; + value = RGBtoYUV(v, mYUV709n).r; + } + + if (pTintMode == TINT_MODE_LINEAR) { // Linear + } else if (pTintMode == TINT_MODE_EXP) { // Exp + value = 1.0 - exp2(value * pTintLumaExponent * -C_log2_e); + } else if (pTintMode == TINT_MODE_EXP2) { // Exp2 + value = 1.0 - exp2(value * value * pTintLumaExponent * pTintLumaExponent * -C_log2_e); + } else if (pTintMode == TINT_MODE_LOG) { // Log + value = (log2(value) + 2.) / 2.333333; + } else if (pTintMode == TINT_MODE_LOG10) { // Log10 + value = (log10(value) + 1.) / 2.; + } + float4 v1 = RGBtoHSV(v); float3 tint = float3(0,0,0); if (v1.b > 0.5) { diff --git a/source/filters/filter-color-grade.cpp b/source/filters/filter-color-grade.cpp index 84bbd6db..f35b1512 100644 --- a/source/filters/filter-color-grade.cpp +++ b/source/filters/filter-color-grade.cpp @@ -44,6 +44,11 @@ #define ST_OFFSET ST ".Offset" #define ST_OFFSET_(x) ST_OFFSET "." D_VSTR(x) #define ST_TINT ST ".Tint" +#define ST_TINT_DETECTION ST_TINT ".Detection" +#define ST_TINT_DETECTION_(x) ST_TINT_DETECTION "." D_VSTR(x) +#define ST_TINT_MODE ST_TINT ".Mode" +#define ST_TINT_MODE_(x) ST_TINT_MODE "." D_VSTR(x) +#define ST_TINT_EXPONENT ST_TINT ".Exponent" #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) @@ -59,6 +64,14 @@ #define TONE_LOW Shadow #define TONE_MID Midtone #define TONE_HIG Highlight +#define DETECTION_HSV HSV +#define DETECTION_HSL HSL +#define DETECTION_YUV_SDR YUV.SDR +#define MODE_LINEAR Linear +#define MODE_EXP Exp +#define MODE_EXP2 Exp2 +#define MODE_LOG Log +#define MODE_LOG10 Log2 // Initializer & Finalizer P_INITIALIZER(FilterColorGradeInit) @@ -91,6 +104,9 @@ void get_defaults(obs_data_t* data) 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_int(data, ST_TINT_MODE, static_cast(filter::color_grade::luma_mode::Exp2)); + obs_data_set_default_int(data, ST_TINT_DETECTION, static_cast(filter::color_grade::detection_mode::HSL)); + obs_data_set_default_double(data, ST_TINT_EXPONENT, 1.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); @@ -117,6 +133,9 @@ bool tool_modified(obs_properties_t* props, obs_property_t* property, obs_data_t {ST_OFFSET, {ST_OFFSET_(RED), ST_OFFSET_(GREEN), ST_OFFSET_(BLUE), ST_OFFSET_(ALL)}}, {ST_TINT, { + ST_TINT_MODE, + ST_TINT_DETECTION, + ST_TINT_EXPONENT, ST_TINT_(TONE_LOW, RED), ST_TINT_(TONE_LOW, GREEN), ST_TINT_(TONE_LOW, BLUE), @@ -221,6 +240,34 @@ obs_properties_t* get_properties(void*) obs_properties_add_group(pr, ST_TINT, D_TRANSLATE(ST_TINT), OBS_GROUP_NORMAL, grp); } + { + auto p = obs_properties_add_list(grp, ST_TINT_MODE, D_TRANSLATE(ST_TINT_MODE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + std::pair els[] = { + {ST_TINT_MODE_(MODE_LINEAR), filter::color_grade::luma_mode::Linear}, + {ST_TINT_MODE_(MODE_EXP), filter::color_grade::luma_mode::Exp}, + {ST_TINT_MODE_(MODE_EXP2), filter::color_grade::luma_mode::Exp2}, + {ST_TINT_MODE_(MODE_LOG), filter::color_grade::luma_mode::Log}, + {ST_TINT_MODE_(MODE_LOG10), filter::color_grade::luma_mode::Log10}}; + for (auto kv : els) { + obs_property_list_add_int(p, D_TRANSLATE(kv.first), static_cast(kv.second)); + } + } + + { + auto p = obs_properties_add_list(grp, ST_TINT_DETECTION, D_TRANSLATE(ST_TINT_DETECTION), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + std::pair els[] = { + {ST_TINT_DETECTION_(DETECTION_HSV), filter::color_grade::detection_mode::HSV}, + {ST_TINT_DETECTION_(DETECTION_HSL), filter::color_grade::detection_mode::HSL}, + {ST_TINT_DETECTION_(DETECTION_YUV_SDR), filter::color_grade::detection_mode::YUV_SDR}}; + for (auto kv : els) { + obs_property_list_add_int(p, D_TRANSLATE(kv.first), static_cast(kv.second)); + } + } + + obs_properties_add_float_slider(grp, ST_TINT_EXPONENT, D_TRANSLATE(ST_TINT_EXPONENT), 0., 10., 0.01); + obs_properties_add_float_slider(grp, ST_TINT_(TONE_LOW, RED), D_TRANSLATE(ST_TINT_(TONE_LOW, RED)), 0, 1000.0, 0.01); obs_properties_add_float_slider(grp, ST_TINT_(TONE_LOW, GREEN), D_TRANSLATE(ST_TINT_(TONE_LOW, GREEN)), 0, @@ -262,67 +309,58 @@ obs_properties_t* get_properties(void*) return pr; } -void* create(obs_data_t* data, obs_source_t* source) -try { +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 { +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 { +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()); return 0; } -uint32_t get_height(void* ptr) -try { +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()); return 0; } -void update(void* ptr, obs_data_t* data) -try { +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 { +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 { +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 { +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 { +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()); @@ -427,35 +465,38 @@ float_t fix_gamma_value(double_t v) 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); + _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_detection = static_cast(obs_data_get_int(data, ST_TINT_DETECTION)); + _tint_luma = static_cast(obs_data_get_int(data, ST_TINT_MODE)); + _tint_exponent = static_cast(obs_data_get_double(data, ST_TINT_EXPONENT)); + _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() @@ -532,6 +573,12 @@ void filter::color_grade::color_grade_instance::video_render(gs_effect_t*) _effect->get_parameter("pGain")->set_float4(_gain); if (_effect->has_parameter("pOffset")) _effect->get_parameter("pOffset")->set_float4(_offset); + if (_effect->has_parameter("pTintDetection")) + _effect->get_parameter("pTintDetection")->set_int(static_cast(_tint_detection)); + if (_effect->has_parameter("pTintMode")) + _effect->get_parameter("pTintMode")->set_int(static_cast(_tint_luma)); + if (_effect->has_parameter("pTintExponent")) + _effect->get_parameter("pTintExponent")->set_float(_tint_exponent); if (_effect->has_parameter("pTintLow")) _effect->get_parameter("pTintLow")->set_float3(_tint_low); if (_effect->has_parameter("pTintMid")) diff --git a/source/filters/filter-color-grade.hpp b/source/filters/filter-color-grade.hpp index 49bdc7da..7e22bfab 100644 --- a/source/filters/filter-color-grade.hpp +++ b/source/filters/filter-color-grade.hpp @@ -29,11 +29,11 @@ namespace filter { namespace color_grade { class color_grade_factory { - obs_source_info sourceInfo; + obs_source_info sourceInfo; public: // Singleton - static void initialize(); - static void finalize(); + static void initialize(); + static void finalize(); static std::shared_ptr get(); public: @@ -41,6 +41,20 @@ namespace filter { ~color_grade_factory(); }; + enum class detection_mode { + HSV, + HSL, + YUV_SDR, + }; + + enum class luma_mode { + Linear, + Exp, + Exp2, + Log, + Log10, + }; + class color_grade_instance { bool _active; obs_source_t* _self; @@ -58,14 +72,17 @@ namespace filter { bool _grade_updated; // Parameters - vec4 _lift; - vec4 _gamma; - vec4 _gain; - vec4 _offset; - vec3 _tint_low; - vec3 _tint_mid; - vec3 _tint_hig; - vec4 _correction; + vec4 _lift; + vec4 _gamma; + vec4 _gain; + vec4 _offset; + detection_mode _tint_detection; + luma_mode _tint_luma; + float_t _tint_exponent; + vec3 _tint_low; + vec3 _tint_mid; + vec3 _tint_hig; + vec4 _correction; public: ~color_grade_instance();