Skip to content

Adding support of subtracting of a white with a given color temperature#1993

Open
lougovsk wants to merge 19 commits into
hyperion-project:masterfrom
lougovsk:feature/custom_white_K
Open

Adding support of subtracting of a white with a given color temperature#1993
lougovsk wants to merge 19 commits into
hyperion-project:masterfrom
lougovsk:feature/custom_white_K

Conversation

@lougovsk
Copy link
Copy Markdown
Contributor

Summary

What kind of change does this PR introduce? (check at least one)

  • Bugfix
  • [X ] Feature
  • Code style update
  • Refactor
  • Docs
  • Build-related changes
  • Other, please describe:

If changing the UI of web configuration, please provide the before/after screenshot:

After
Screenshot 2026-03-31 at 00 46 00

Does this PR introduce a breaking change? (check one)

  • Yes
  • [X ] No

If yes, please describe the impact and migration path for existing setups:

The PR fulfills these requirements:

  • When resolving a specific issue, it's referenced in the PR's body (e.g. Fixes: #xxx[,#xxx], where "xxx" is the issue number)

If adding a new feature, the PR's description includes:

  • A convincing reason for adding this feature
  • Related documents have been updated (docs/docs/en)
  • Related tests have been updated

PLEASE DON'T FORGET TO ADD YOUR CHANGES TO CHANGELOG.MD

  • Yes, CHANGELOG.md is also updated

To avoid wasting your time, it's best to open a feature request issue first and wait for approval before working on it.

Other information:
This MR corresponds to the #1956

google-labs-jules Bot and others added 12 commits March 30, 2026 07:24
This commit introduces RGBW (Red, Green, Blue, White) data handling
for the E1.31 UDP LED device.

Key changes:

1.  **LedDeviceUdpE131.h modifications:**
    *   Included `utils/ColorRgbw.h` and `utils/RgbToRgbw.h`.
    *   Added private member variables:
        *   `_whiteAlgorithm` (RGBW::WhiteAlgorithm) to store the selected white calibration mode.
        *   `_ledRGBWCount` (int) to store the total number of channels (3 for RGB, 4 for RGBW).
        *   `_temp_rgbw` (ColorRgbw) as a temporary variable for color conversion.

2.  **LedDeviceUdpE131.cpp modifications:**
    *   Constructor now initializes `_whiteAlgorithm` to `INVALID` and `_ledRGBWCount` to 0.
    *   `init()` method:
        *   Reads `whiteAlgorithm` from the device configuration (defaults to "white_off").
        *   Converts the string to `RGBW::WhiteAlgorithm`.
        *   Sets `_ledRGBWCount` based on `_ledCount` and whether a white channel is active (3 * _ledCount for RGB, 4 * _ledCount for RGBW).
    *   `write()` method:
        *   Uses `_ledRGBWCount` for `dmxChannelCount`.
        *   Creates a temporary buffer for pixel data.
        *   If `_whiteAlgorithm` is `WHITE_OFF`, copies RGB data directly.
        *   Otherwise, converts RGB to RGBW using `RGBW::Rgb_to_Rgbw` and copies R,G,B,W data.
        *   Populates E1.31 packet properties using the new (potentially RGBW) data buffer.

3.  **JSON Schema Update (libsrc/leddevice/schemas/schema-e131.json):**
    *   Added a `whiteAlgorithm` property to the E1.31 device schema.
    *   This allows users to select the white channel calibration method ("white_off", "subtractive", "additive").
    *   Includes a default value of "white_off".

**Compilation Status:**

The project successfully configures with CMake after installing numerous dependencies (Qt6, libudev, XCB libs, ALSA, CEC, libp8-platform, libusb).
However, the `make` process consistently times out after approximately 6 minutes and 40 seconds, regardless of the number of parallel jobs (`-j nproc`, `-j 2`, `-j 1`) or if a specific target (`hyperiond`) is built.

This suggests a potential hang or an extremely long compilation step for a particular file/module within the project, which could not be identified due to lack of verbose output during the timeout.

Further investigation or environment adjustments are needed to complete the full build.
   ledRGB(W)Count variable from LedDevice instead of creating a new
   locally
2. Fixed the problem where DMX Max channel count was hardcoded to 512.
   WLED e.g. in MultiRGB mode relies on 510 (3*170) channels. On other
   hand for MultiRGBW it expect 512 (4*128) channels. So obviously it
   has to be a parameter.
3. Updated corresponding configuration for WebUI
The previous implementation used the raw limiting channel value (uScale)
as the white LED drive, which undershot by a factor of sumW/wlim. The
correct drive under the 1/3-efficiency model is fRatio*sumW, capped at
255 with fActualRatio back-calculated for the RGB subtraction.

Also fix div-by-zero on low color temperatures (zero white channel
components), replace non-standard u_int16_t/u_int8_t with uint16_t/
uint8_t, fix invalid default whiteTemp=0 -> 5000, fix _customWhite-
Temperature type int -> uint16_t, and improve readability of
ColorRgb::white().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 1/3 efficiency assumption (fRatio * sumW) was over-driving the white
channel by up to 2x at 2700K, causing warm white flood that washed out
saturated colors: magenta lost saturation, green shifted yellow, cyan
lost its blue component. Switch to 1:1 (fRatio * 255) which assumes the
white LED matches one RGB channel in per-channel output — a better fit
for typical RGBW strips.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revert white drive from fRatio*255 back to fRatio*sumW to test whether
the 1/3 efficiency model produces better results now that post-processing
gamma correction is properly configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hyperion-project
Copy link
Copy Markdown

Hello @lougovsk 👋

I'm the Hyperion Project Bot and I want to thank you for
contributing to Hyperion with your pull requests!

To help you and other users test your pull requests faster,
I'll create a link for you to your workflow artifacts.

🔗 https://github.com/hyperion-project/hyperion.ng/actions/runs/23771942253

Of course, if you make changes to your PR, I will create a new link.

Best regards,
Hyperion Project

pow/log -> qPow/qLn in ColorRgb::white(), add <QtMath> include.
round -> qRound, std::numeric_limits::infinity() -> qInf() in
RgbToRgbw.cpp, remove <limits> include.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hyperion-project
Copy link
Copy Markdown

Hey @lougovsk I created a new link to your workflow artifacts:
🔗 https://github.com/hyperion-project/hyperion.ng/actions/runs/23801325662

@hyperion-project
Copy link
Copy Markdown

Hey @lougovsk I created a new link to your workflow artifacts:
🔗 https://github.com/hyperion-project/hyperion.ng/actions/runs/25749473787

@Lord-Grey
Copy link
Copy Markdown
Collaborator

@lougovsk Thank you for your contribution and PR issues. This is well received and appreciated.

I did some smaller updates for consistency with the overall project:

  1. There is an existing KelvinToRgb function already in place based on the same algorithm.
    I removed the duplicate code and updated the related code.

Question: Is there a specific reason why the Temperature default is 5000 and not 6600 which is the neutral one?
Is this e131 device related?

  1. I updated the json to have it aligned with the standard that units are appended to the elements rather showing in the label.

  2. I got the AI review feedback as following and pushed a proposed fix 1f6c1be.

The finding was as following

The new white-channel scaling in RgbToRgbw.cpp:150 and RgbToRgbw.cpp:151 is inconsistent with the rest of the RGBW model and breaks the main use case of this feature. The code derives the white output from ratio × sumW, then caps it at 255 and recomputes the subtraction ratio from that capped value. For common white temperatures this means even an exact chromaticity match does not collapse to the white channel. I checked the math directly: at 6500 K, input 255/255/255 produces white 255 but still leaves RGB 169/170/171; at 2000 K, an input equal to the computed white basis still leaves RGB 94/50/5. That means sub_ktemp_white cannot actually subtract the configured white cleanly before merge. The fix should scale the white channel as a 0..255 scalar of the chosen white basis, then subtract that same scalar fraction from RGB, instead of normalizing by the sum of the basis channels.

Maybe you can check, if it is in line with your intend.
If the fix does not make sense we can roll it back....

@hyperion-project
Copy link
Copy Markdown

Hey @lougovsk I created a new link to your workflow artifacts:
🔗 https://github.com/hyperion-project/hyperion.ng/actions/runs/25750568415

@Lord-Grey Lord-Grey moved this from Backlog to In Progress in Output Devices May 12, 2026
@Lord-Grey Lord-Grey assigned lougovsk and unassigned lougovsk May 12, 2026
@lougovsk
Copy link
Copy Markdown
Contributor Author

@lougovsk Thank you for your contribution and PR issues. This is well received and appreciated.
Hi @Lord-Grey thanks a lot! And sorry for a delayed reply.

I did some smaller updates for consistency with the overall project:

  1. There is an existing KelvinToRgb function already in place based on the same algorithm.
    I removed the duplicate code and updated the related code.
    Ah... Sorry, I didn't check the code base properly, so implemented the duplication. Really appreciate, that you've taken care of removing it.

Question: Is there a specific reason why the Temperature default is 5000 and not 6600 which is the neutral one? Is this e131 device related?
No specific reason. Just picked some mid range temperature as a default, not too warm, not too cold. Any reasonable default should be fine.

  1. I updated the json to have it aligned with the standard that units are appended to the elements rather showing in the label.
    Thanks a lot!
  2. I got the AI review feedback as following and pushed a proposed fix 1f6c1be.

The finding was as following

The new white-channel scaling in RgbToRgbw.cpp:150 and RgbToRgbw.cpp:151 is inconsistent with the rest of the RGBW model and breaks the main use case of this feature. The code derives the white output from ratio × sumW, then caps it at 255 and recomputes the subtraction ratio from that capped value. For common white temperatures this means even an exact chromaticity match does not collapse to the white channel. I checked the math directly: at 6500 K, input 255/255/255 produces white 255 but still leaves RGB 169/170/171; at 2000 K, an input equal to the computed white basis still leaves RGB 94/50/5. That means sub_ktemp_white cannot actually subtract the configured white cleanly before merge. The fix should scale the white channel as a 0..255 scalar of the chosen white basis, then subtract that same scalar fraction from RGB, instead of normalizing by the sum of the basis channels.

Maybe you can check, if it is in line with your intend. If the fix does not make sense we can roll it back....
I will take a look into it ASAP.

@lougovsk
Copy link
Copy Markdown
Contributor Author

lougovsk commented May 25, 2026

@Lord-Grey now regarding what is the right subtraction algorithm. BTH I don't know. I think a simple plausibility test would be:

  1. We have a white of 6,600 Kelvin on our LED strip (255, 255, 253)
  2. We have the same power output on all LEDs of a RGBW (e.g. 0.3W RGB and 0.1W White) -> full RGB (255,255,255) equals 3x White channel (255)
  3. input = (255,255,255)
  4. output color ordering = (R,G,B,W)

Now both algos:

  1. sumW = 765; fRatio = 1;fWhiteDrive = qBound(0, 1*765, 255) = 255; fActualRatio = 1/3; output = (255 - 255/3, 255 - 255/3, 255 - 255/3 , 255) -> this result kinda makes sense as we expect RGB (255,255,255) to be 3x brighter than W(255)
  2. whiteLevel = 1; output = (255 - 2551,255 - 2551,255 - 255*1, 255) = (0,0,0,255) -> basically I guess will 3x dimmer than RBG(255,255,255)

So the algo originally implemented was trying to account for brightness difference of 3 RGB LEDs vs a single white LED. But this also quite a massive assumption, tbh.

@Lord-Grey
Copy link
Copy Markdown
Collaborator

How does the output look like in practice with both algorithms?

@lougovsk
Copy link
Copy Markdown
Contributor Author

I will check out in the evening when it gets dark.

@lougovsk
Copy link
Copy Markdown
Contributor Author

lougovsk commented May 25, 2026

@Lord-Grey I've done a short round of A/B testing. TBH I didn't much of a difference.

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

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants