From 82d11f6f25c3c50b7e4f7ea113f0f700a392b7a7 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 6 Jun 2025 18:08:34 +0200 Subject: [PATCH 1/2] fixes particle brightness distribution with new gamma correction Particle System depends on linear brightness distribution, gamma corrected values are non-linear. - added invers gamma table - reversing gamma after brightness distribution makes it linear once again --- wled00/FXparticleSystem.cpp | 18 +++++++++++++++++- wled00/cfg.cpp | 3 ++- wled00/colors.cpp | 12 ++++++++++-- wled00/fcn_declare.h | 4 ++++ wled00/set.cpp | 3 ++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 85264b7f14..bc66417446 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -584,7 +584,7 @@ void ParticleSystem2D::render() { continue; // generate RGB values for particle if (fireIntesity) { // fire mode - brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 20; + brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 5; brightness = min(brightness, (uint32_t)255); baseRGB = ColorFromPaletteWLED(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP); } @@ -600,6 +600,7 @@ void ParticleSystem2D::render() { baseRGB = (CRGB)tempcolor; } } + brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY); } @@ -676,6 +677,14 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint pxlbrightness[1] = (dx * precal2) >> PS_P_SURFACE; // bottom right value equal to (dx * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightness) >> PS_P_SURFACE pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightness) >> PS_P_SURFACE + // adjust brightness such that distribution is linear after gamma correction: + // - scale brigthness with gamma correction (done in render()) + // - apply inverse gamma correction to brightness values + // - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total + pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma + pxlbrightness[1] = gamma8inv(pxlbrightness[1]); + pxlbrightness[2] = gamma8inv(pxlbrightness[2]); + pxlbrightness[3] = gamma8inv(pxlbrightness[3]); if (advPartProps && advPartProps[particleindex].size > 1) { //render particle to a bigger size CRGB renderbuffer[100]; // 10x10 pixel buffer @@ -1467,6 +1476,7 @@ void ParticleSystem1D::render() { baseRGB = (CRGB)tempcolor; } } + brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution renderParticle(i, brightness, baseRGB, particlesettings.wrap); } // apply smear-blur to rendered frame @@ -1534,6 +1544,12 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint //calculate the brightness values for both pixels using linear interpolation (note: in standard rendering out of frame pixels could be skipped but if checks add more clock cycles over all) pxlbrightness[0] = (((int32_t)PS_P_RADIUS_1D - dx) * brightness) >> PS_P_SURFACE_1D; pxlbrightness[1] = (dx * brightness) >> PS_P_SURFACE_1D; + // adjust brightness such that distribution is linear after gamma correction: + // - scale brigthness with gamma correction (done in render()) + // - apply inverse gamma correction to brightness values + // - gamma is applied again in show() -> the resulting brightness distribution is linear but gamma corrected in total + pxlbrightness[0] = gamma8inv(pxlbrightness[0]); // use look-up-table for invers gamma + pxlbrightness[1] = gamma8inv(pxlbrightness[1]); // check if particle has advanced size properties and buffer is available if (advPartProps && advPartProps[particleindex].size > 1) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index de4aa11825..f3d11af3c4 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -532,7 +532,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { gammaCorrectBri = false; gammaCorrectCol = false; } - NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table + NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables + NeoGammaWLEDMethod::calcInverseGammaTable(gammaCorrectVal); JsonObject light_tr = light["tr"]; int tdd = light_tr["dur"] | -1; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 3b3d9ab704..e8094028f2 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -564,10 +564,11 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) { } } -// gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp)) +// gamma lookup tables used for color correction (filled on 1st use (cfg.cpp & set.cpp)) uint8_t NeoGammaWLEDMethod::gammaT[256]; +uint8_t NeoGammaWLEDMethod::gammaT_inv[256]; -// re-calculates & fills gamma table +// re-calculates & fills gamma tables void NeoGammaWLEDMethod::calcGammaTable(float gamma) { for (size_t i = 0; i < 256; i++) { @@ -575,6 +576,13 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) } } +void NeoGammaWLEDMethod::calcInverseGammaTable(float gamma) +{ + for (size_t i = 0; i < 256; i++) { + gammaT_inv[i] = (int)(powf((float)i / 255.0f, 1/gamma) * 255.0f + 0.5f); //inverse + } +} + uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) { if (!gammaCorrectCol) return value; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 486e5c5628..e1d0c64df1 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -159,12 +159,16 @@ class NeoGammaWLEDMethod { [[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel [[gnu::hot]] static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB) static void calcGammaTable(float gamma); // re-calculates & fills gamma table + static void calcInverseGammaTable(float gamma); // re-calculates & fills inverse gamma table static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB) + static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB) private: static uint8_t gammaT[]; + static uint8_t gammaT_inv[]; }; #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) +#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c) [[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend); inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); }; [[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); diff --git a/wled00/set.cpp b/wled00/set.cpp index 038e84b417..74acd88203 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -341,7 +341,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) gammaCorrectBri = false; gammaCorrectCol = false; } - NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table + NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables + NeoGammaWLEDMethod::calcInverseGammaTable(gammaCorrectVal); t = request->arg(F("TD")).toInt(); if (t >= 0) transitionDelayDefault = t; From e6b61992fe35095616c8e42b721e720c9afa7588 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 7 Jun 2025 08:56:57 +0200 Subject: [PATCH 2/2] single function gamma table calculation/ inverse table calculation --- wled00/cfg.cpp | 1 - wled00/colors.cpp | 9 ++------- wled00/fcn_declare.h | 3 +-- wled00/set.cpp | 1 - 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index f3d11af3c4..72ace8dbfa 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -533,7 +533,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { gammaCorrectCol = false; } NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables - NeoGammaWLEDMethod::calcInverseGammaTable(gammaCorrectVal); JsonObject light_tr = light["tr"]; int tdd = light_tr["dur"] | -1; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index e8094028f2..ff6f3ab582 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -571,15 +571,10 @@ uint8_t NeoGammaWLEDMethod::gammaT_inv[256]; // re-calculates & fills gamma tables void NeoGammaWLEDMethod::calcGammaTable(float gamma) { + float gamma_inv = 1.0f / gamma; // inverse gamma for (size_t i = 0; i < 256; i++) { gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f); - } -} - -void NeoGammaWLEDMethod::calcInverseGammaTable(float gamma) -{ - for (size_t i = 0; i < 256; i++) { - gammaT_inv[i] = (int)(powf((float)i / 255.0f, 1/gamma) * 255.0f + 0.5f); //inverse + gammaT_inv[i] = (int)(powf((float)i / 255.0f, gamma_inv) * 255.0f + 0.5f); } } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index e1d0c64df1..8e4233f2c4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -158,8 +158,7 @@ class NeoGammaWLEDMethod { public: [[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel [[gnu::hot]] static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB) - static void calcGammaTable(float gamma); // re-calculates & fills gamma table - static void calcInverseGammaTable(float gamma); // re-calculates & fills inverse gamma table + static void calcGammaTable(float gamma); // re-calculates & fills gamma tables static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB) static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB) private: diff --git a/wled00/set.cpp b/wled00/set.cpp index 74acd88203..6229ba28ef 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -342,7 +342,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) gammaCorrectCol = false; } NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables - NeoGammaWLEDMethod::calcInverseGammaTable(gammaCorrectVal); t = request->arg(F("TD")).toInt(); if (t >= 0) transitionDelayDefault = t;