fix low brightness gradient "jumpyness"

during testing at low brightness I noticed that gradients can be "jumping" in colors quite wildly, turning a smooth gradient into a flickering mess. This is due to the color hue preservation being inaccurate and a bit too aggressive. This can be seen for example using a gradient palette and "Running" FX.
Removing the hue preservation completely fixes it but leaves color artefacts for example visible in PS Fire at very low brightness: the bright part of the flames gets a pink hue. This change is a compromise to fix both problems to a "good enough" state
This commit is contained in:
Damian Schneider
2025-10-12 15:18:48 +02:00
committed by Frank
parent b215c8209d
commit 82ba119402
2 changed files with 33 additions and 50 deletions

View File

@@ -82,34 +82,25 @@ IRAM_ATTR_YN uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) // WLEDMM
IRAM_ATTR_YN __attribute__((hot)) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
{
if (amount == 255) return c1; // WLEDMM small optimization - plus it avoids over-fading in "video" mode
if (amount == 0) return 0; // WLEDMM shortcut
if (c1 == 0 || amount == 0) return 0; // black or no change
if (amount == 255) return c1;
uint32_t addRemains = 0;
uint32_t scaledcolor = 0; // color order is: W R G B from MSB to LSB
uint16_t w = W(c1); // WLEDMM 16bit to make sure the compiler uses 32bit (not 64bit) for the math
uint16_t r = R(c1);
uint16_t g = G(c1);
uint16_t b = B(c1);
if (video) {
uint16_t scale = amount; // 32bit for faster calculation
// bugfix: doing "+1" after shifting is obviously wrong
// optimization: ((r && scale) ? 1 : 0) can be simplified to "if (r > 0) +1" ; if we arive here, then scale != 0 and scale < 255
if (w>0) scaledcolor |= (((w * scale) >> 8) +1) << 24; // WLEDMM small speedup when no white channel
if (r>0) scaledcolor |= (((r * scale) >> 8) +1) << 16;
if (g>0) scaledcolor |= (((g * scale) >> 8) +1) << 8;
if (b>0) scaledcolor |= ((b * scale) >> 8) +1;
return scaledcolor;
if (!video) amount++; // add one for correct scaling using bitshifts
else {
// video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue
uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels
uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // determine dominant channel for hue preservation
addRemains = r && (r<<5) > maxc ? 0x00010000 : 0; // note: setting color preservation threshold too high results in flickering and
addRemains |= g && (g<<5) > maxc ? 0x00000100 : 0; // jumping colors in low brightness gradients. Multiplying the color preserves
addRemains |= b && (b<<5) > maxc ? 0x00000001 : 0; // better accuracy than dividing the maxc. Shifting by 5 is a good compromise
addRemains |= w ? 0x01000000 : 0; // i.e. remove color channel if <13% of max
}
else {
uint16_t scale = 1 + amount;
if (w>0) scaledcolor |= ((w * scale) >> 8) << 24; // WLEDMM small speedup when no white channel
scaledcolor |= ((r * scale) >> 8) << 16;
scaledcolor |= (g * scale) & 0x0000FF00; // WLEDMM faster than right-left shift "" >>8 ) <<8"
scaledcolor |= (b * scale) >> 8;
return scaledcolor;
}
}
#endif
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK; // scale white and green
return (rb | wg) + addRemains;
}#endif
void setRandomColor(byte* rgb)
{