Skip to content

HUB75: Some 2D effects missing pixel trails in v16.0 (Black Hole, Lissajous, Spaceships)  #5664

Closed

Description

@softhack007

In WLED 16.0.0, the "Black hole" effect does not produce the same long pixels trails as in previous releases (trails used to be up to ~1/3 turn long)

Changing the "fade rate" slide has almost zero effect on the trail length (never longer than 2-3 pixels).
The "Blur" checkbox does affect brightness, but also does not help to get longer pixels trails.

Tested with a 64x32 HUB75 matrix (esp32-S3), gamma = 2.2, no white balance correction; target FPS = unlimited.

Edit: this could be another case of brightness-gamma adjustments, similar to

Activity

  1. changed the title [-]Black hole effect: missing pixel trails in 16.0[/-] [+]Black hole effect: missing pixel trails in v16.0[/+] on Jun 3, 2026
  2. softhack007 commented on Jun 3, 2026

    @softhack007
    MemberAuthor

    @coderabbitai please check if the Segment::blur() and Segment::fade_out() / Segment::fadeToBlackBy() are still producing the same results, considering the different gamma / rendering architectures of 16.0 and 0.15.x.

    • 16.0: effects operate in "non-gamma" space, the pixels pipeline applies scale(gamma32(pixelColor), brightness) when passing pixels to the driver level, after all effects have calculated their pixels. output drivers do not perform brightness scaling or gamma adjustments.
    • 0.15.0 effects operate in "gamma-corrected" space, some effects have "hand-tuned" parameters for blur() and fade_out() to achieve pixels trails. Pixels from effects are passed on to the driver level; the driver scales brightness but does not re-apply gamma.
  3. softhack007 commented on Jun 3, 2026

    @softhack007
    MemberAuthor

    @DedeHai i think we've discussed the topic already, just wanted to check if the rabbit comes up with new aspects or consequences that we did not look into yet.

  4. coderabbitai commented on Jun 3, 2026

    @coderabbitai
    Contributor
    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    echo "=== Segment::blur() ==="
    rg -n "blur" wled00/FX_fcn.cpp -A 2 -B 2 | head -60
    
    echo ""
    echo "=== Searching blur implementation ==="
    ast-grep --pattern 'void Segment::blur($$$) {
      $$$
    }'
    
    echo ""
    echo "=== Searching fade_out implementation ==="
    ast-grep --pattern 'void Segment::fade_out($$$) {
      $$$
    }'
    
    echo ""
    echo "=== Searching fadeToBlackBy implementation ==="
    ast-grep --pattern 'void Segment::fadeToBlackBy($$$) {
      $$$
    }'

    Length of output: 1637


    🏁 Script executed:

    #!/bin/bash
    # Find functions in FX_fcn.cpp
    rg -n "void Segment::blur\|void Segment::fade_out\|void Segment::fadeToBlackBy" wled00/FX_fcn.cpp
    
    # Also check the header
    rg -n "blur\|fade_out\|fadeToBlackBy" wled00/FX.h | head -30

    Length of output: 469


    🏁 Script executed:

    #!/bin/bash
    # Get full blur implementation
    sed -n '1082,1140p' wled00/FX_fcn.cpp
    
    echo "---"
    # Find fade_out and fadeToBlackBy
    rg -n "fade_out\|fadeToBlackBy" wled00/FX_fcn.cpp | head -30

    Length of output: 3006


    🏁 Script executed:

    #!/bin/bash
    rg -rn "void Segment::fade_out\|void Segment::fadeToBlackBy" wled00/
    rg -rn "fade_out\|fadeToBlackBy" wled00/FX_fcn.cpp | head -40

    Length of output: 385


    🏁 Script executed:

    #!/bin/bash
    echo "=== fade_out/fadeToBlackBy in all wled00 files ==="
    rg -rn "fade_out\|fadeToBlackBy" wled00/ | grep -v ".o:" | head -50
    
    echo ""
    echo "=== getPixelColorRaw / setPixelColorRaw ==="
    rg -n "getPixelColorRaw\|setPixelColorRaw" wled00/FX_fcn.cpp | head -20
    
    echo ""
    echo "=== fast_color_scale definition ==="
    rg -rn "fast_color_scale\|color_fade\|color_add" wled00/ --include="*.h" | head -30

    Length of output: 1025


    🏁 Script executed:

    #!/bin/bash
    echo "=== Search fade_out and fadeToBlackBy everywhere ==="
    rg -rn "fade_out\|fadeToBlackBy" wled00/
    
    echo ""
    echo "=== Search getPixelColorRaw ==="
    rg -rn "getPixelColorRaw" wled00/
    
    echo ""
    echo "=== fast_color_scale ==="
    rg -rn "fast_color_scale" wled00/
    
    echo ""
    echo "=== color_add definition ==="
    rg -rn "inline.*color_add\|uint32_t color_add" wled00/
    
    echo ""
    echo "=== Segment fade methods in FX.h ==="
    rg -n "fade" wled00/FX.h | head -30

    Length of output: 5291


    🏁 Script executed:

    #!/bin/bash
    echo "=== fade_out, fadeToBlackBy, fadeToSecondaryBy implementations ==="
    rg -n "fade_out\|fadeToBlackBy\|fadeToSecondaryBy" wled00/FX_fcn.cpp
    
    echo ""
    echo "=== setPixelColor vs setPixelColorRaw ==="
    rg -n "setPixelColorRaw\|void Segment::setPixelColor" wled00/FX_fcn.cpp | head -20
    
    echo ""
    echo "=== fast_color_scale / n() in colors.h ==="
    sed -n '1,50p' wled00/colors.h
    
    echo ""
    echo "=== Output pipeline around line 1735-1760 ==="
    sed -n '1720,1770p' wled00/FX_fcn.cpp

    Length of output: 5771


    🏁 Script executed:

    #!/bin/bash
    echo "=== Line context around fade functions in FX_fcn.cpp ==="
    grep -n "fade_out\|fadeToBlack\|fadeToSecondary" wled00/FX_fcn.cpp
    
    echo ""
    echo "=== getPixelColorRaw returns n(i) - let's see setPixelColorRaw ==="
    rg -n "setPixelColorRaw" wled00/FX.h | head -10
    
    echo ""
    echo "=== Segment::setPixelColor in FX_fcn.cpp ==="
    rg -n "Segment::setPixelColor" wled00/FX_fcn.cpp | head -10

    Length of output: 1269


    🏁 Script executed:

    #!/bin/bash
    echo "=== fade_out (line 1045), fadeToSecondaryBy (line 1069), fadeToBlackBy (line 1076) ==="
    sed -n '1045,1085p' wled00/FX_fcn.cpp
    
    echo ""
    echo "=== setPixelColor (line 727) - first ~40 lines ==="
    sed -n '727,780p' wled00/FX_fcn.cpp
    
    echo ""
    echo "=== color_fade definition in colors.h ==="
    rg -n "color_fade\|color_add\|color_blend" wled00/colors.h | head -20

    Length of output: 4714



    @softhack007, great question. After reviewing all three functions and the output pipeline, here is the analysis:


    Analysis: blur(), fade_out(), fadeToBlackBy() in v0.16.0 vs v0.15.x

    All three operate exclusively on the raw pixel buffer (getPixelColorRaw / setPixelColorRaw → direct pixels[i] access, no gamma). The critical difference comes from what those pixel values represent and what happens downstream.

    Pipeline difference recap

    v0.15.x v0.16.0
    Pixel buffer content Gamma-encoded (~sRGB) values Linear light values
    Driver-level processing Scales brightness (linear multiply) gamma32(c) applied, then passes to bus
    LED output for stored value v v gamma32(v) ≈ v^2.8 / 255^1.8

    1. fadeToBlackBy(fadeBy) — Trails fade faster in v0.16.0

    setPixelColorRaw(i, fast_color_scale(getPixelColorRaw(i), 255-fadeBy));
    // i.e.: pixels[i] = pixels[i] * (255-fadeBy) / 255
    • v0.15.x: pixel values are gamma-encoded. The raw linear multiply produces a fade that tracks approximately with perceived brightness steps because the stored values already encode the gamma curve.
    • v0.16.0: pixel values are linear. The same linear multiply is correct physics, but the output gamma32() compresses lower values more aggressively. A pixel at mid-value fades quickly in absolute terms, but gamma32 makes the low end of the curve map near-zero faster — trails appear to extinguish sooner for the same fadeBy value.

    Net effect: for the same fadeBy parameter, fade trails are visually shorter/dimmer in v0.16.0. Effects with hand-tuned fadeBy values (e.g. comet trails) will look noticeably different.


    2. blur(blur_amount) — Blurs appear slightly brighter

    uint8_t keep = smear ? 255 : 255 - blur_amount;
    uint8_t seep = blur_amount >> 1;
    // pixel = keep * self + seep * left + seep * right (roughly)
    • v0.15.x: blur is performed on gamma-encoded values → gamma-space averaging → result slightly darker than linear average (nonlinear mixing).
    • v0.16.0: blur is performed on linear values → linear averaging is physically correct, but the linear average is then gamma-compressed at output. Since gamma compresses higher values more than proportionally, bright blurred halos will appear subtly broader/brighter vs v0.15.x for the same blur_amount.

    Net effect: blur looks more physically correct in v0.16.0 but differs from v0.15.x tuning. Effects that relied on a specific blur_amount for their "glow radius" will see it look slightly brighter.


    3. fade_out(rate) and fadeToSecondaryBy(fadeBy) — Domain mismatch with colors[1]

    // fade_out: interpolates pixels[j] toward colors[1]
    // fadeToSecondaryBy: color_blend(getPixelColorRaw(i), colors[1], fadeBy)

    This is the most problematic case. colors[1] is SEGCOLOR(1) — a raw sRGB value from the UI color picker, stored without any gamma transformation. In v0.16.0 the pixel buffer holds linear values, so:

    • v0.15.x: both pixel buffer values and colors[1] were approximately gamma-encoded → interpolation was in a consistent domain.
    • v0.16.0: pixel buffer is linear, colors[1] is sRGB → mixing linear and sRGB values in the same interpolation. The interpolated result will be in neither space correctly. Once output gamma32() is applied, the secondary color endpoint will be doubly gamma-corrected (once implicitly via sRGB encoding, once at output), making it appear dimmer than intended.

    Summary table

    Function Behavior change in v0.16.0 Severity
    fadeToBlackBy() Trails die faster; same fadeBy produces shorter trails Medium — hand-tuned effects differ
    fadeToSecondaryBy() Secondary color endpoint is effectively double-gamma'd Medium
    fade_out() Same domain mismatch; secondary color too dark Medium
    blur() Slightly brighter/broader halos for same blur_amount Low–Medium

    Potential fixes

    1. fadeToBlackBy()/blur(): These are operating correctly in linear space for v0.16.0. The visual mismatch vs v0.15.x is inherent to the architecture change. Effects relying on specific trail lengths may need their fade/blur parameters retuned.

    2. fade_out() / fadeToSecondaryBy(): The secondary color domain mismatch could be corrected by pre-converting colors[1] with gamma32inv() before the interpolation, so both endpoints are in linear space before setPixelColorRaw:

      uint32_t target = NeoGammaWLEDMethod::inverseGamma32(colors[1]);
      // then use target instead of colors[1] in the blend

      This would make the fade-to-secondary behave consistently with the rest of the linear pipeline.

    Note: This analysis uses the long-term memory context about WLED v0.16.0's gamma pipeline (gamma32 applied at output, SEGCOLOR values are raw sRGB from UI picker), which was established in prior sessions.


    🧠 Learnings used
    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-11T21:39:46.794Z
    Learning: In WLED v0.16.0 FX.cpp, the following effects directly compute RGB colors without palettes and need gamma8inv() to restore v0.15.0 visual behavior due to the new gamma pipeline: (1) mode_DJLight - CRGB(fftResult/2); (2) mode_2Dmatrix - hardcoded RGBW32(175,255,175,0)/RGBW32(27,130,39,0) default Matrix green; (3) mode_traffic_light - hardcoded 0x00FF0000/0x00EECC00/0x0000FF00; (4) mode_2DFunkyPlank - CHSV(fftResult,255,map(fftResult,0,255,10,255)) where the v channel needs gamma8inv(); (5) mode_waterfall peak indicator CRGB(CHSV(92,92,92)) where v=92 needs gamma8inv(). Separate bug: mode_tv_simulator applies gamma8() manually in both v0.15.0 AND current, causing double gamma in v0.16.0 - fix is to REMOVE the 3 explicit gamma8() calls at the nr/ng/nb assignments, not to add gamma8inv(). Already correctly handled: mode_pride_2015 (has gamma32inv()) and mode_2DDistortionWaves (manual gamma8 removed, pipeline handles it).
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:30:07.598Z
    Learning: In WLED 16.0.0 twinklefox_base() in wled00/FX.cpp, bg = gamma32inv(bg) applied to SEGCOLOR(1) is incorrect — it over-brightens the background because SEGCOLOR values are sRGB-encoded and the output pipeline already applies gamma32() to compensate. The correct approach is to leave bg unmodified for setPixelColor() output. For the deltabright brightness comparison, since c from gamma-cleaned palettes is in linear space while bg (raw sRGB) is not, the comparison can be made consistent by converting c to sRGB-like space: CRGBW cc2 = gamma32inv(c); cbright = cc2.getAverageLight().
    

    Learnt from: DedeHai
    Repo: wled/WLED PR: 4798
    File: wled00/FX.cpp:7531-7533
    Timestamp: 2025-08-26T11:51:21.817Z
    Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-11T21:43:36.003Z
    Learning: In WLED's wled00/FX.cpp, the following effects build colors directly from computed/hardcoded values without using palettes/rainbow/user-defined colors, and have identical color logic in v0.15.0 without gamma8inv. In v0.16.0, these effects need gamma8inv() wrapping to restore previous appearance due to the new render-path gamma correction:
    1. mode_traffic_light - hardcoded 0xFF0000, 0xEECC00, 0x00FF00
    2. mode_pacifica - hardcoded floor CRGB(2,6,10) and CRGB(2,5,7); overage highlights
    3. mode_2Dmatrix - hardcoded default RGBW32(175,255,175,0) and RGBW32(27,130,39,0)
    4. mode_2DSunradiation - HeatColor() output goes directly to setPixelColorXY
    5. mode_2DFunkyPlank - CHSV(fftResult[band], 255, v) audio-reactive colors
    6. mode_waterfall - CHSV(92,92,92) peak indicator (minor)
    NOTE: mode_2Ddistortionwaves is NOT a candidate - v0.15.0 applied explicit gamma8() in the effect itself (lines 7581-7583), which was correctly removed in v0.16.0 to rely on render-path gamma instead. Pride2015/ColorWaves already applies gamma32inv() explicitly.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:30:07.598Z
    Learning: In WLED 16.0.0 (wled00/FX_fcn.cpp), gamma32() is applied automatically in the output pipeline (Line 1741-1743) to all values stored via setPixelColor(). Therefore, user-picked colors from SEGCOLOR/SEGCOLOR(x) are already sRGB-encoded (gamma ~2.2), and the output gamma32() approximately undoes that encoding, resulting in correct LED luminance. Effects should NOT apply gamma32inv() to SEGCOLOR values before passing to setPixelColor() — doing so would over-brighten them. Only effect-generated linear/perceptual values benefit from gamma32inv() pre-processing (e.g. palette colors, computed brightness values).
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:25:46.961Z
    Learning: In WLED (wled00/), user-defined segment colors (`SEGCOLOR(x)`, stored in `Segment::colors[]`) are raw sRGB values from the UI color picker — no gamma transformation is applied anywhere in the ingestion path (confirmed in json.cpp and FX_fcn.cpp beginDraw()). However, treating them as "gamma-encoded" requiring `gamma32inv()` before `setPixelColor()` is imprecise: sRGB gamma (~2.2) and WLED's LED gamma (~2.2–2.8) are different curves. From the LED pipeline's perspective, sRGB values are close to linear. The safety of applying `gamma32inv()` to SEGCOLOR values depends on whether WLED's configured LED gamma matches sRGB's gamma exactly. softhack007 confirmed this.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-11T20:53:20.707Z
    Learning: In WLED, `gamma8inv()` (macro for `NeoGammaWLEDMethod::rawInverseGamma8()`) is an established pattern for pre-compensating values that will later pass through the gamma correction pipeline in `show()`. It is already used in FX_fcn.cpp (Line 1419 for opacity) and FXparticleSystem.cpp (Lines 681, 805, 1534-1535 for pixel brightness). The function is a no-op when `gammaCorrectCol` is false, making it self-consistent. When effects in `wled00/FX.cpp` build colors directly from raw data (e.g., FFT values) and those colors appear too dim in v0.16.0 vs v0.15.0, the fix is to wrap each component in `gamma8inv()` to compensate for the new gamma correction step added in the render path between strip and bus level in v0.16.0.
    

    Learnt from: DedeHai
    Repo: wled/WLED PR: 4615
    File: wled00/FXparticleSystem.cpp:678-680
    Timestamp: 2026-03-29T06:10:40.732Z
    Learning: In WLED's FXparticleSystem.cpp, `gamma8inv()` is a lookup-table call that applies the inverse of the gamma curve — it is NOT a no-op (identity function) when `gammaCorrectCol` is false. All calls to `gamma8inv()` on particle brightness values (pxlbrightness[] in renderParticle 2D standard render, the large-particle ellipse path in renderLargeParticle 2D, and pxlbrightness[] in renderParticle 1D) must be guarded by `if (gammaCorrectCol)` to avoid pre-distorting brightness when gamma correction is disabled. Without the guard, particles render noticeably darker/nonlinear in gamma-disabled configurations because the inverse correction is applied but no forward gamma correction follows in show().
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 5635
    File: wled00/FX.cpp:2680-2683
    Timestamp: 2026-05-20T11:34:59.048Z
    Learning: In WLED v16.0.0, colors returned from ColorFromPalette/SEGMENT.color_from_palette are already in linear (gamma-free) space. Do not apply gamma32inv() to palette-derived colors (e.g., Twinklefox twinkle pixels) before brightness calculations; doing so will artificially brighten and can alter hue. Background handling may need gamma8/32 inversions as applicable, but palette colors should be left as-is for linear comparisons.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:32:53.376Z
    Learning: In WLED (wled00/FX_fcn.cpp), `setPixelColor()` applies `gamma32()` forward gamma correction at output (line ~1742). User-picked segment colors (`SEGCOLOR(x)`) are stored as raw sRGB values with no gamma transformation in the ingestion path. Since sRGB gamma (~2.2) approximates WLED LED gamma (~2.2-2.8), user-picked colors should be passed to `setPixelColor()` WITHOUT applying `gamma32inv()` first. Applying `gamma32inv` to a user-picked SEGCOLOR before setPixelColor causes over-brightness because it decodes the sRGB encoding and then the output pipeline re-applies gamma, resulting in a net perceptual boost beyond the user's intention.
    

    Learnt from: DedeHai
    Repo: wled/WLED PR: 5601
    File: wled00/FX_fcn.cpp:301-323
    Timestamp: 2026-05-14T17:13:17.872Z
    Learning: In `wled00/FX_fcn.cpp`, inside `Segment::startTransition()`, the `segmentCopy && !_t->_oldSegment` branch intentionally does NOT refresh `_t->_colors`, `_t->_bri`, or `_t->_cct` to the current blended values before restarting the transition timer and creating `_t->_oldSegment`. The `_oldSegment`-based path handles visual continuity differently from the pure FADE path, and this asymmetry is deliberate. Do not flag this as a bug or suggest syncing those fields in that branch.
    
  5. softhack007 commented on Jun 3, 2026

    @softhack007
    MemberAuthor

    Update: the "Lissajous" effect has similar problems in v16.0: the "Fade Rate" slider has almost zero effect (in 0.15.0 you could smear the curve), and the "Blur" slider just dims the effect down, without much visible blur.

  6. changed the title [-]Black hole effect: missing pixel trails in v16.0[/-] [+]Black Hole and Lissajous effects: missing pixel trails in v16.0[/+] on Jun 3, 2026
  7. softhack007 commented on Jun 3, 2026

    @softhack007
    MemberAuthor

    "Spaceships" also has similar problems: the "Blur" slider has zero effect.

  8. softhack007 commented on Jun 3, 2026

    @softhack007
    MemberAuthor

    @coderabbitai please compare the Segment::blur() implementation between main and 0_15_x branches. Are there any code changes in the blur() function that could explain the different behaviour?

  9. changed the title [-]Black Hole and Lissajous effects: missing pixel trails in v16.0[/-] [+]Some 2D effects missing pixel trails in v16.0 (Black Hole, Lissajous, Spaceships) [/+] on Jun 3, 2026
  10. coderabbitai commented on Jun 3, 2026

    @coderabbitai
    Contributor
    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    echo "=== blur() on current (main) branch ==="
    grep -n "void Segment::blur" wled00/FX_fcn.cpp
    # get line number and show the function
    LINE=$(grep -n "void Segment::blur" wled00/FX_fcn.cpp | head -1 | cut -d: -f1)
    echo "blur starts at line $LINE"
    sed -n "${LINE},$((LINE+80))p" wled00/FX_fcn.cpp
    
    echo ""
    echo "=== blur() on 0_15_x branch ==="
    git show origin/0_15_x:wled00/FX_fcn.cpp | grep -n "void Segment::blur" | head -5
    LINE15=$(git show origin/0_15_x:wled00/FX_fcn.cpp | grep -n "void Segment::blur" | head -1 | cut -d: -f1)
    echo "blur starts at line $LINE15 on 0_15_x"
    git show origin/0_15_x:wled00/FX_fcn.cpp | sed -n "${LINE15},$((LINE15+80))p"
    
    echo ""
    echo "=== git diff blur() between branches ==="
    git diff origin/0_15_x..HEAD -- wled00/FX_fcn.cpp | grep -A 5 -B 5 "blur"

    Length of output: 5084


    🏁 Script executed:

    #!/bin/bash
    echo "=== Available remote branches ==="
    git branch -r | head -30
    
    echo ""
    echo "=== Fetch 0_15_x from GitHub directly ==="
    gh api repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=0_15_x --jq '.download_url' | xargs curl -s > /tmp/FX_fcn_0_15_x.cpp 2>&1
    echo "File size: $(wc -l < /tmp/FX_fcn_0_15_x.cpp)"
    
    echo ""
    echo "=== blur() on 0_15_x ==="
    grep -n "void Segment::blur" /tmp/FX_fcn_0_15_x.cpp
    LINE15=$(grep -n "void Segment::blur" /tmp/FX_fcn_0_15_x.cpp | head -1 | cut -d: -f1)
    echo "blur starts at line $LINE15"
    sed -n "${LINE15},$((LINE15+70))p" /tmp/FX_fcn_0_15_x.cpp
    
    echo ""
    echo "=== Also check blur2D on 0_15_x ==="
    grep -n "void Segment::blur2D\|void.*blur2D" /tmp/FX_fcn_0_15_x.cpp
    LINE2D=$(grep -n "void Segment::blur2D" /tmp/FX_fcn_0_15_x.cpp | head -1 | cut -d: -f1)
    if [ -n "$LINE2D" ]; then
      sed -n "${LINE2D},$((LINE2D+60))p" /tmp/FX_fcn_0_15_x.cpp
    fi
    
    echo ""
    echo "=== Also check blur2D on main ==="
    grep -n "void Segment::blur2D\|void.*blur2D" wled00/FX_fcn.cpp
    LINE2D_MAIN=$(grep -n "void Segment::blur2D" wled00/FX_fcn.cpp | head -1 | cut -d: -f1)
    if [ -n "$LINE2D_MAIN" ]; then
      sed -n "${LINE2D_MAIN},$((LINE2D_MAIN+60))p" wled00/FX_fcn.cpp
    fi
    
    echo ""
    echo "=== Check getPixelColorRaw / setPixelColorRaw on both ==="
    grep -n "getPixelColorRaw\|setPixelColorRaw" /tmp/FX_fcn_0_15_x.cpp | head -10
    echo "---main---"
    grep -n "getPixelColorRaw\|setPixelColorRaw" wled00/FX_fcn.cpp | head -10

    Length of output: 5407


    🏁 Script executed:

    #!/bin/bash
    echo "=== blur2D in main branch ==="
    rg -rn "void.*blur2D\|Segment::blur2D" wled00/
    echo ""
    
    echo "=== blur2D implementation on main ==="
    rg -rn "blur2D" wled00/FX_fcn.cpp | head -10
    LINE_B2D=$(grep -n "void Segment::blur2D\|Segment::blur2D" wled00/FX_fcn.cpp | head -1 | cut -d: -f1)
    if [ -n "$LINE_B2D" ]; then
      sed -n "${LINE_B2D},$((LINE_B2D+80))p" wled00/FX_fcn.cpp
    fi
    
    # Also check FX_2Dfunctions.cpp or similar
    fd "FX_2D" wled00/ --exec grep -n "void.*blur2D" {}
    
    echo ""
    echo "=== blur2D in 0_15_x ==="
    gh api "repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=0_15_x" --jq '.download_url' | xargs curl -s | grep -n "blur2D" | head -20
    
    echo ""
    echo "=== Check if blur2D is in FX.h header (both versions) ==="
    grep -n "blur2D\|blur(" wled00/FX.h | head -20
    echo "---0_15_x header---"
    gh api "repos/wled/WLED/contents/wled00/FX.h?ref=0_15_x" --jq '.download_url' | xargs curl -s | grep -n "blur2D\|blur(" | head -20
    
    echo ""
    echo "=== color_add definition on main (check for overflow/saturating parameter) ==="
    rg -n "inline.*color_add\|uint32_t color_add\|color_add" wled00/colors.h | head -20
    
    echo ""
    echo "=== color_add in 0_15_x colors.h ==="
    gh api "repos/wled/WLED/contents/wled00/colors.h?ref=0_15_x" --jq '.download_url' | xargs curl -s | grep -n "color_add" | head -20

    Length of output: 2982


    🏁 Script executed:

    #!/bin/bash
    echo "=== blur2D implementation on main (line 246) ==="
    sed -n '246,330p' wled00/FX_fcn.cpp
    
    echo ""
    echo "=== color_add definition on main ==="
    rg -rn "inline.*color_add\|uint32_t color_add" wled00/ | head -10
    rg -rn "color_add" wled00/colors.h | head -10
    rg -rn "^.*color_add" wled00/FX.h | head -10
    
    echo ""
    echo "=== fast_color_scale definition ==="
    rg -rn "fast_color_scale" wled00/ | head -10
    
    echo ""
    echo "=== color_add / fast_color_scale in wled00 headers ==="
    rg -rn "color_add\|fast_color_scale" wled00/wled.h wled00/const.h 2>/dev/null | head -20
    
    echo ""
    echo "=== Search all cpp/h files for color_add definition ==="
    rg -rn "^inline.*color_add\|^uint32_t color_add\|^static.*color_add" wled00/ | head -10
    
    echo ""
    echo "=== Get blur2D in 0_15_x ==="
    gh api "repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=0_15_x" --jq '.download_url' | xargs curl -s > /tmp/FX_fcn_0_15_x.cpp
    grep -n "void Segment::blur2D" /tmp/FX_fcn_0_15_x.cpp
    LINE_2D=$(grep -n "void Segment::blur2D" /tmp/FX_fcn_0_15_x.cpp | head -1 | cut -d: -f1)
    if [ -n "$LINE_2D" ]; then
      sed -n "${LINE_2D},$((LINE_2D+80))p" /tmp/FX_fcn_0_15_x.cpp
    fi

    Length of output: 6623


    🏁 Script executed:

    #!/bin/bash
    echo "=== Find blur2D in main (correct line) ==="
    grep -n "void Segment::blur2D" wled00/FX_fcn.cpp
    LINE=$(grep -n "void Segment::blur2D" wled00/FX_fcn.cpp | head -1 | cut -d: -f1)
    echo "Line: $LINE"
    sed -n "${LINE},$((LINE+70))p" wled00/FX_fcn.cpp
    
    echo ""
    echo "=== Find blur2D in all main branch files ==="
    rg -rn "void Segment::blur2D" wled00/
    
    echo ""
    echo "=== colors.h - full content for color_add / fast_color_scale ==="
    cat wled00/colors.h
    
    echo ""
    echo "=== blur2D in 0_15_x — check all files ==="
    for f in FX_fcn.cpp FX.cpp FX.h; do
      echo "--- $f ---"
      gh api "repos/wled/WLED/contents/wled00/${f}?ref=0_15_x" --jq '.download_url' | xargs curl -s | grep -n "void Segment::blur2D\|void blur2D" | head -5
    done
    
    echo ""
    echo "=== Get blur2D impl from 0_15_x FX_fcn.cpp ==="
    gh api "repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=0_15_x" --jq '.download_url' | xargs curl -s > /tmp/fx15.cpp
    LINE2D=$(grep -n "void Segment::blur2D" /tmp/fx15.cpp | head -1 | cut -d: -f1)
    echo "0_15_x blur2D at line: $LINE2D"
    [ -n "$LINE2D" ] && sed -n "${LINE2D},$((LINE2D+70))p" /tmp/fx15.cpp

    Length of output: 12680


    🏁 Script executed:

    #!/bin/bash
    echo "=== blur2D in main FX_2Dfcn.cpp ==="
    grep -n "void Segment::blur2D\|void.*blur2D" wled00/FX_2Dfcn.cpp
    LINE=$(grep -n "void Segment::blur2D" wled00/FX_2Dfcn.cpp | head -1 | cut -d: -f1)
    echo "Line: $LINE"
    sed -n "${LINE},$((LINE+80))p" wled00/FX_2Dfcn.cpp
    
    echo ""
    echo "=== Find blur2D in 0_15_x — list all cpp files ==="
    gh api "repos/wled/WLED/git/trees/0_15_x?recursive=1" --jq '.tree[] | select(.path | test("wled00/.*\\.cpp$")) | .path' 2>/dev/null | head -30
    
    echo ""
    echo "=== Try FX_2Dfcn.cpp in 0_15_x ==="
    gh api "repos/wled/WLED/contents/wled00/FX_2Dfcn.cpp?ref=0_15_x" --jq '.download_url' 2>/dev/null | xargs curl -s 2>/dev/null | grep -n "void Segment::blur2D\|void.*blur2D" | head -5
    LINE2D_15=$(gh api "repos/wled/WLED/contents/wled00/FX_2Dfcn.cpp?ref=0_15_x" --jq '.download_url' 2>/dev/null | xargs curl -s 2>/dev/null | grep -n "void Segment::blur2D" | head -1 | cut -d: -f1)
    echo "Line in 0_15_x FX_2Dfcn.cpp: $LINE2D_15"
    if [ -n "$LINE2D_15" ]; then
      gh api "repos/wled/WLED/contents/wled00/FX_2Dfcn.cpp?ref=0_15_x" --jq '.download_url' | xargs curl -s > /tmp/fx2d_15.cpp
      sed -n "${LINE2D_15},$((LINE2D_15+80))p" /tmp/fx2d_15.cpp
    fi

    Length of output: 8400



    @softhack007, here is the full comparison. I found three concrete code-level regressions and one architectural change:


    blur() and blur2D(): main vs 0_15_x

    Bug 1 — seep formula in smear mode (regression in both blur() and blur2D())

    Code smear=false smear=true
    v0.15.x seep = blur_amount >> (1 + smear) blur_amount / 2 blur_amount / 4
    v0.16.0 seep = blur_amount >> 1 blur_amount / 2 blur_amount / 2 ← doubled

    When smear=true, v0.16.0 spreads twice as much color into each neighbor compared to v0.15.x. This is a direct regression — effects using blur(..., smear=true) (e.g. comet trails, Lissajous smear) will look significantly brighter/broader in v0.16.0. The fix:

    // FX_fcn.cpp (blur) and FX_2Dfcn.cpp (blur2D) — restore original smear logic:
    uint8_t seep = blur_amount >> (1 + smear);   // v0.15.x formula

    Bug 2 — color_add preserveCR flag removed

    Code
    v0.15.x color_add(curnew, carryover, true) — preserveCR=true: on overflow, scales result back proportionally to preserve hue
    v0.16.0 color_add(cur, carryover) — preserveCR=false: per-channel clamp, hue shifts toward white on overflow

    In smear mode with bright pixels, v0.15.x preserved hue when neighbor contributions summed above 255; v0.16.0 clips per-channel and can produce white-shifted results.


    Architectural change — linear vs gamma-encoded pixel domain

    Pixel read/write Domain
    v0.15.x getPixelColor() / setPixelColor() Gamma-encoded (~sRGB) values in buffer; driver scales brightness
    v0.16.0 getPixelColorRaw() / setPixelColorRaw() Linear light values in buffer; gamma32() applied once at output

    Blur arithmetic (keep * self + seep * neighbors) is physically correct in linear space (v0.16.0), but the gamma curve is very steep at low values. After blurring in linear space, gamma32() at output compresses low-value pixels much harder than v0.15.x did — fading trails extinguish faster for the same blur_amount. This explains the "dims the effect down" complaint for Lissajous. There is no simple per-call fix for this; effects need their blur_amount parameters retuned.


    Change — blur2D() signature

    Branch Signature
    v0.15.x blur2D(uint8_t blur_amount, bool smear)
    v0.16.0 blur2D(uint8_t blur_x, uint8_t blur_y, bool smear)

    blur() calls blur2D(blur_amount, blur_amount, smear) symmetrically, so for effects that call blur() directly there is no net difference. The new signature allows asymmetric 2D blurs.


    Summary

    Issue Root cause
    Smear-based trails look different / brighter seep = blur_amount >> 1 instead of >> (1 + smear) — regression
    Blur "dims the effect" (Lissajous) Linear-domain blur + gamma at output compresses low values more steeply
    Hue shifts at high blur with bright pixels preserveCR flag removed from color_add calls

    The seep formula is an actionable bug fix. The other two are consequences of the deliberate architectural change to linear-space rendering.


    ✏️ Learnings added
    Learnt from: softhack007
    URL: https://github.com/wled/WLED/issues/5664
    
    Timestamp: 2026-06-03T22:33:06.467Z
    Learning: In WLED (wled00/FX_fcn.cpp and wled00/FX_2Dfcn.cpp), the v0.16.0 `blur()` and `blur2D()` functions have a regression vs v0.15.x in smear mode: v0.16.0 uses `seep = blur_amount >> 1` unconditionally, whereas v0.15.x used `seep = blur_amount >> (1 + smear)`. When smear=true, v0.16.0 spreads twice as much color into each neighbor pixel as v0.15.x did. The fix is to restore the `>> (1 + smear)` formula in both functions. Additionally, v0.15.x used `color_add(..., true)` (preserveCR=true, proportional scale-back on overflow) while v0.16.0 uses `color_add(...)` without it, causing possible hue shifts toward white at high blur values. These changes explain why smear-based comet/trail effects look different in v0.16.0.
    

    Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


    🧠 Learnings used
    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:30:07.598Z
    Learning: In WLED 16.0.0 twinklefox_base() in wled00/FX.cpp, bg = gamma32inv(bg) applied to SEGCOLOR(1) is incorrect — it over-brightens the background because SEGCOLOR values are sRGB-encoded and the output pipeline already applies gamma32() to compensate. The correct approach is to leave bg unmodified for setPixelColor() output. For the deltabright brightness comparison, since c from gamma-cleaned palettes is in linear space while bg (raw sRGB) is not, the comparison can be made consistent by converting c to sRGB-like space: CRGBW cc2 = gamma32inv(c); cbright = cc2.getAverageLight().
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-11T21:39:46.794Z
    Learning: In WLED v0.16.0 FX.cpp, the following effects directly compute RGB colors without palettes and need gamma8inv() to restore v0.15.0 visual behavior due to the new gamma pipeline: (1) mode_DJLight - CRGB(fftResult/2); (2) mode_2Dmatrix - hardcoded RGBW32(175,255,175,0)/RGBW32(27,130,39,0) default Matrix green; (3) mode_traffic_light - hardcoded 0x00FF0000/0x00EECC00/0x0000FF00; (4) mode_2DFunkyPlank - CHSV(fftResult,255,map(fftResult,0,255,10,255)) where the v channel needs gamma8inv(); (5) mode_waterfall peak indicator CRGB(CHSV(92,92,92)) where v=92 needs gamma8inv(). Separate bug: mode_tv_simulator applies gamma8() manually in both v0.15.0 AND current, causing double gamma in v0.16.0 - fix is to REMOVE the 3 explicit gamma8() calls at the nr/ng/nb assignments, not to add gamma8inv(). Already correctly handled: mode_pride_2015 (has gamma32inv()) and mode_2DDistortionWaves (manual gamma8 removed, pipeline handles it).
    

    Learnt from: DedeHai
    Repo: wled/WLED PR: 4798
    File: wled00/FX.cpp:7531-7533
    Timestamp: 2025-08-26T11:51:21.817Z
    Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:32:53.376Z
    Learning: In WLED (wled00/FX_fcn.cpp), `setPixelColor()` applies `gamma32()` forward gamma correction at output (line ~1742). User-picked segment colors (`SEGCOLOR(x)`) are stored as raw sRGB values with no gamma transformation in the ingestion path. Since sRGB gamma (~2.2) approximates WLED LED gamma (~2.2-2.8), user-picked colors should be passed to `setPixelColor()` WITHOUT applying `gamma32inv()` first. Applying `gamma32inv` to a user-picked SEGCOLOR before setPixelColor causes over-brightness because it decodes the sRGB encoding and then the output pipeline re-applies gamma, resulting in a net perceptual boost beyond the user's intention.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-11T20:53:20.707Z
    Learning: In WLED, `gamma8inv()` (macro for `NeoGammaWLEDMethod::rawInverseGamma8()`) is an established pattern for pre-compensating values that will later pass through the gamma correction pipeline in `show()`. It is already used in FX_fcn.cpp (Line 1419 for opacity) and FXparticleSystem.cpp (Lines 681, 805, 1534-1535 for pixel brightness). The function is a no-op when `gammaCorrectCol` is false, making it self-consistent. When effects in `wled00/FX.cpp` build colors directly from raw data (e.g., FFT values) and those colors appear too dim in v0.16.0 vs v0.15.0, the fix is to wrap each component in `gamma8inv()` to compensate for the new gamma correction step added in the render path between strip and bus level in v0.16.0.
    

    Learnt from: DedeHai
    Repo: wled/WLED PR: 4615
    File: wled00/FXparticleSystem.cpp:678-680
    Timestamp: 2026-03-29T06:10:40.732Z
    Learning: In WLED's FXparticleSystem.cpp, `gamma8inv()` is a lookup-table call that applies the inverse of the gamma curve — it is NOT a no-op (identity function) when `gammaCorrectCol` is false. All calls to `gamma8inv()` on particle brightness values (pxlbrightness[] in renderParticle 2D standard render, the large-particle ellipse path in renderLargeParticle 2D, and pxlbrightness[] in renderParticle 1D) must be guarded by `if (gammaCorrectCol)` to avoid pre-distorting brightness when gamma correction is disabled. Without the guard, particles render noticeably darker/nonlinear in gamma-disabled configurations because the inverse correction is applied but no forward gamma correction follows in show().
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-11T21:43:36.003Z
    Learning: In WLED's wled00/FX.cpp, the following effects build colors directly from computed/hardcoded values without using palettes/rainbow/user-defined colors, and have identical color logic in v0.15.0 without gamma8inv. In v0.16.0, these effects need gamma8inv() wrapping to restore previous appearance due to the new render-path gamma correction:
    1. mode_traffic_light - hardcoded 0xFF0000, 0xEECC00, 0x00FF00
    2. mode_pacifica - hardcoded floor CRGB(2,6,10) and CRGB(2,5,7); overage highlights
    3. mode_2Dmatrix - hardcoded default RGBW32(175,255,175,0) and RGBW32(27,130,39,0)
    4. mode_2DSunradiation - HeatColor() output goes directly to setPixelColorXY
    5. mode_2DFunkyPlank - CHSV(fftResult[band], 255, v) audio-reactive colors
    6. mode_waterfall - CHSV(92,92,92) peak indicator (minor)
    NOTE: mode_2Ddistortionwaves is NOT a candidate - v0.15.0 applied explicit gamma8() in the effect itself (lines 7581-7583), which was correctly removed in v0.16.0 to rely on render-path gamma instead. Pride2015/ColorWaves already applies gamma32inv() explicitly.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:25:46.961Z
    Learning: In WLED (wled00/), user-defined segment colors (`SEGCOLOR(x)`, stored in `Segment::colors[]`) are raw sRGB values from the UI color picker — no gamma transformation is applied anywhere in the ingestion path (confirmed in json.cpp and FX_fcn.cpp beginDraw()). However, treating them as "gamma-encoded" requiring `gamma32inv()` before `setPixelColor()` is imprecise: sRGB gamma (~2.2) and WLED's LED gamma (~2.2–2.8) are different curves. From the LED pipeline's perspective, sRGB values are close to linear. The safety of applying `gamma32inv()` to SEGCOLOR values depends on whether WLED's configured LED gamma matches sRGB's gamma exactly. softhack007 confirmed this.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 5635
    File: wled00/FX.cpp:2680-2683
    Timestamp: 2026-05-20T11:34:59.048Z
    Learning: In WLED v16.0.0, colors returned from ColorFromPalette/SEGMENT.color_from_palette are already in linear (gamma-free) space. Do not apply gamma32inv() to palette-derived colors (e.g., Twinklefox twinkle pixels) before brightness calculations; doing so will artificially brighten and can alter hue. Background handling may need gamma8/32 inversions as applicable, but palette colors should be left as-is for linear comparisons.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-17T16:30:07.598Z
    Learning: In WLED 16.0.0 (wled00/FX_fcn.cpp), gamma32() is applied automatically in the output pipeline (Line 1741-1743) to all values stored via setPixelColor(). Therefore, user-picked colors from SEGCOLOR/SEGCOLOR(x) are already sRGB-encoded (gamma ~2.2), and the output gamma32() approximately undoes that encoding, resulting in correct LED luminance. Effects should NOT apply gamma32inv() to SEGCOLOR values before passing to setPixelColor() — doing so would over-brighten them. Only effect-generated linear/perceptual values benefit from gamma32inv() pre-processing (e.g. palette colors, computed brightness values).
    

    Learnt from: DedeHai
    Repo: wled/WLED PR: 5601
    File: wled00/FX_fcn.cpp:301-323
    Timestamp: 2026-05-14T17:13:17.872Z
    Learning: In `wled00/FX_fcn.cpp`, inside `Segment::startTransition()`, the `segmentCopy && !_t->_oldSegment` branch intentionally does NOT refresh `_t->_colors`, `_t->_bri`, or `_t->_cct` to the current blended values before restarting the transition timer and creating `_t->_oldSegment`. The `_oldSegment`-based path handles visual continuity differently from the pure FADE path, and this asymmetry is deliberate. Do not flag this as a bug or suggest syncing those fields in that branch.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-03-25T12:25:11.610Z
    Learning: In WLED's `Segment::startTransition()` (wled00/FX_fcn.cpp:284), when `isInTransition()` is true and `_t->_oldSegment` already exists, the function silently returns without updating `_t->_start` or `_t->_bri`. This causes a bug where rapid successive on/off toggles during a non-FADE blending transition (e.g., fairy dust) leave the transition clock stale: by the time of the second re-trigger, elapsed time may already exceed `_dur`, so `updateTransitionProgress()` sets `_progress = 0xFFFF` immediately on the next service tick and `stopTransition()` fires — the blending effect never plays. The fix is to always reset `_t->_start = millis()`, `_t->_dur = dur`, and `_t->_bri = currentBri()` (current visible brightness) in the `isInTransition()` branch, regardless of whether `_oldSegment` exists.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2026-05-11T19:39:20.730Z
    Learning: In WLED's `mode_DJLight()` (wled00/FX.cpp), the effect builds color directly as `CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2)` — plain RGB, not CHSV. The /2 division caps values at 127 max, and with gamma correction enabled this produces very dim output (gamma maps ~50% linear to ~22% perceived brightness). The recommended diagnostic is to uncheck "gamma correction" in LED settings. CHSV→CHSV32 replacement (for 16-bit hue precision) applies to other WLED effects that use CHSV, not DJLight in its current form.
    

    Learnt from: softhack007
    Repo: wled/WLED PR: 0
    File: :0-0
    Timestamp: 2025-12-28T14:06:48.772Z
    Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.
    
  11. DedeHai commented on Jun 4, 2026

    @DedeHai
    Collaborator

    @softhack007 does it look the same in "peek"? the preview does not have gamma applied and in peek to me I do not see what you describe. So it's either to do with gamma or might be a property of your HUB75 panel

    btw the rabbit is wrong about the blur stuff.

  12. softhack007 commented on Jun 4, 2026

    @softhack007
    MemberAuthor

    @DedeHai good point, i'll double check 👍 It might even be the HUB75 driver in combination with the 4-scan "outdoor" panel i've used. I have another "indoors" panel, will cross-check with that one, too.

    Edit: agreed about the blur statements from the rabbit - what it says is related to "smear blur" only, even then it does not sound very convincing...

  13. DedeHai commented on Jun 4, 2026

    @DedeHai
    Collaborator

    the smear blur in 0.15 is actually changed from my original implementation and was not changed back to "upstream"

    459156f

  14. changed the title [-]Some 2D effects missing pixel trails in v16.0 (Black Hole, Lissajous, Spaceships) [/-] [+]HUB75: Some 2D effects missing pixel trails in v16.0 (Black Hole, Lissajous, Spaceships) [/+] on Jun 4, 2026
  15. softhack007 commented on Jun 4, 2026

    @softhack007
    MemberAuthor

    @softhack007 does it look the same in "peek"? the preview does not have gamma applied and in peek to me I do not see what you describe. So it's either to do with gamma or might be a property of your HUB75 panel

    @DedeHai you're right - in peek preview the black hole "star trails" are still there, just enabling "blur" shortens trails to 2-3 pixels.
    So it's either the new HUB75 driver, my panel, or something related to gamma handling.

    I'll do some more testing today or tomorrow.

  16. softhack007 commented on Jun 4, 2026

    @softhack007
    MemberAuthor

    Update: found one root cause:

    gamma effectively gets applied twice in the HUB75 path , because we compile without -D NO_CIE1931.

    • strip -> bus: WLED gamma is applied
    • HUB75 display->drawPixelRGB888() applies the internal CIE1931 correction

    ==> two gamma corrections stacked over each other. This perfectly explains that low-brightness trails are not visible. As a cross-check, i've disabled WLED gamma correction, and all pixel trails became visible again.

    The hot fix is to add -D NO_CIE1931 here

    WLED/platformio.ini

    Lines 730 to 737 in bd0ebf9

    ;; Core HUB75 flags - common to every HUB75 build
    build_flags =
    -D WLED_ENABLE_HUB75MATRIX -D NO_GFX
    -D WLED_DEBUG_BUS
    -D LED_TYPES=TYPE_HUB75MATRIX_HS
    ; -D WLED_DEBUG
    ;; Default I2S mic pins disabled (HUB75 uses GPIOs that would otherwise clash).

    I'll try to find a better fix - supporting both NO_CIE1931 and normal HUB75 builds. The idea is that a build with driver CIE enabled can be used together with WLED gamma disabled, for the best possible color accuracy provided at driver level.

    --> will add this in PR #5662

  17. softhack007 commented on Jun 16, 2026

    @softhack007
    MemberAuthor

    solved with #5665

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions