Browse Source

light: fix temperature scale and white channel mode

make use of our apply() objects for value replacement in-place
also revert the white channel mode change that ignores rgb output factor
pull/2558/head
Maxim Prokhorov 1 year ago
parent
commit
33453117a4
2 changed files with 130 additions and 76 deletions
  1. +2
    -2
      code/espurna/build.cpp
  2. +128
    -74
      code/espurna/light.cpp

+ 2
- 2
code/espurna/build.cpp View File

@ -263,9 +263,9 @@ PROGMEM_STRING(BuildTime, __TIME__);
// > This macro expands to a string constant that describes the date on which the preprocessor is being run.
// > The string constant contains eleven characters and looks like "Feb 12 1996". If the day of the month is less than 10,
// > it is padded with a space on the left.
// >
// >
// > If GCC cannot determine the current date, it will emit a warning message (once per compilation) and __DATE__ will expand to "??? ?? ????".
//
//
// __TIME__
// > This macro expands to a string constant that describes the time at which the preprocessor is being run.
// > The string constant contains eight characters and looks like "23:59:01".


+ 128
- 74
code/espurna/light.cpp View File

@ -504,6 +504,15 @@ struct Pointers {
return _data[4];
}
template <typename ...Args>
void maybeApply(Args&&... args) const {
for (auto ptr : _data) {
if (ptr) {
(*ptr).apply(std::forward<Args>(args)...);
}
}
}
private:
void reset(LightChannels& channels);
@ -876,6 +885,8 @@ long _lightBrightnessPercent() {
// After the channel value was updated through the API (i.e. through changing the `inputValue`),
// these functions are expected to be called. Which one is chosen is based on the current settings values.
// Basic brightness application; default when all other processing options are disabled
void _lightValuesWithBrightness(LightChannels& channels) {
const auto Brightness = _light_brightness;
for (auto& channel : channels) {
@ -883,12 +894,7 @@ void _lightValuesWithBrightness(LightChannels& channels) {
}
}
template <typename... Args>
void _lightChannelMaybeApply(LightChannel* ptr, Args&&... args) {
if (ptr) {
(*ptr).apply(std::forward<Args>(args)...);
}
}
// Maintain compatibility with older versions, limit brightness application to the RGB when using 'color mode'.
void _lightValuesWithBrightnessExceptWhite(LightChannels& channels) {
auto ptr = espurna::light::Pointers(channels);
@ -898,47 +904,76 @@ void _lightValuesWithBrightnessExceptWhite(LightChannels& channels) {
(*ptr.green()).apply(Brightness);
(*ptr.blue()).apply(Brightness);
_lightChannelMaybeApply(ptr.warm());
_lightChannelMaybeApply(ptr.cold());
if (ptr.warm()) {
(*ptr.warm()).apply();
}
if (ptr.cold()) {
(*ptr.cold()).apply();
}
}
// Reset inputValue directly in the expression
// Ignores all previous values, should only be used at the beginning
struct LightResetInput {
LightResetInput() = delete;
explicit LightResetInput(long value) :
_value(value)
{}
long operator()(long) const {
return _value;
}
// 0.0 is the 'coldest', 1.0 is the 'warmest'
static LightResetInput forWarm(float factor) {
return LightResetInput(factor * espurna::light::ValueMax);
}
// opposite value of `forWarm` for the given factor
static LightResetInput forCold(float factor) {
return LightResetInput((1.0f - factor) * espurna::light::ValueMax);
}
private:
long _value;
};
// With `useCCT`, balance the value between Warm and Cold channels based on the current `mireds`.
struct LightScaledWhite {
static auto constexpr Default = espurna::light::build::WhiteFactor;
LightScaledWhite() = default;
explicit LightScaledWhite(float factor) :
_factor(factor)
{}
static LightScaledWhite with(float factor) {
return LightScaledWhite{factor * Default};
}
long operator()(long input) const {
return std::lround(
static_cast<float>(input)
* _factor
* espurna::light::build::WhiteFactor);
return std::lround(static_cast<float>(input) * _factor);
}
private:
float _factor { 1.0f };
float _factor { Default };
};
void _lightValuesWithCct(LightChannels& channels) {
const auto Brightness = _light_brightness;
auto ptr = espurna::light::Pointers(channels);
_lightChannelMaybeApply(ptr.red(), Brightness);
_lightChannelMaybeApply(ptr.green(), Brightness);
_lightChannelMaybeApply(ptr.blue(), Brightness);
const auto Factor = _light_temperature.factor();
const auto CctFactor = _light_temperature.factor();
const auto White = LightScaledWhite();
auto& warm = *ptr.warm();
warm = std::lround(Factor * espurna::light::ValueMax);
warm.apply(White, Brightness);
auto ptr = espurna::light::Pointers(channels);
(*ptr.warm()).apply(
LightResetInput::forWarm(CctFactor), White);
(*ptr.cold()).apply(
LightResetInput::forCold(CctFactor), White);
auto& cold = *ptr.cold();
cold = std::lround((1.0f - Factor) * espurna::light::ValueMax);
cold.apply(White, Brightness);
const auto Brightness = _light_brightness;
ptr.maybeApply(Brightness);
}
// To handle both 4 and 5 channels, allow to 'adjust' internal factor calculation after construction
@ -952,9 +987,14 @@ void _lightValuesWithCct(LightChannels& channels) {
struct LightRgbWithoutWhite {
LightRgbWithoutWhite() = delete;
explicit LightRgbWithoutWhite(espurna::light::Rgb rgb) :
_common(makeCommon(rgb)),
_factor(makeFactor(_common)),
_luminance(makeLuminance(_common))
{}
explicit LightRgbWithoutWhite(const LightChannels& channels) :
_common(makeCommon(makeRgb(channels))),
_factor(makeFactor(_common))
LightRgbWithoutWhite{makeRgb(channels)}
{}
long operator()(long input) const {
@ -968,6 +1008,7 @@ struct LightRgbWithoutWhite {
std::forward<Args>(args)...
});
_factor = makeFactor(_common);
_luminance = makeLuminance(_common);
}
long inputMin() const {
@ -978,6 +1019,10 @@ struct LightRgbWithoutWhite {
return _factor;
}
float luminance() const {
return _luminance;
}
private:
struct Common {
long inputMin;
@ -985,7 +1030,12 @@ private:
long outputMax;
};
static float makeFactor(const Common& common) {
static float makeLuminance(Common common) {
const auto raw = (common.inputMin + common.inputMax) / 2;
return static_cast<float>(raw) / espurna::light::ValueMax;
}
static float makeFactor(Common common) {
const auto inputMax = static_cast<float>(common.inputMax);
const auto outputMax = static_cast<float>(common.outputMax);
return (outputMax > 0.0f)
@ -1016,6 +1066,7 @@ private:
Common _common;
float _factor;
float _luminance;
};
// When `useWhite` is enabled, white channels are 'detached' from the processing and their value depends on the RGB ones.
@ -1035,16 +1086,25 @@ void _lightValuesWithRgbWhite(LightChannels& channels) {
(*ptr.green()).apply(rgb, Brightness);
(*ptr.blue()).apply(rgb, Brightness);
auto& warm = *ptr.warm();
warm = rgb.inputMin();
warm.apply(LightScaledWhite{rgb.factor()}, Brightness);
(*ptr.warm()).apply(
LightResetInput{rgb.inputMin()},
LightScaledWhite::with(rgb.factor()),
Brightness);
_lightChannelMaybeApply(ptr.cold());
if (ptr.cold()) {
(*ptr.cold()).apply();
}
}
// Kelvin to RGB approximation algorithm by Tanner Helland
// * https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html
// Original code for RGB lights from AiLight library by Sacha Telgenhof (@
// * https://github.com/stelgenhof/AiLight/blob/develop/lib/AiLight/AiLight.cpp
// Instead of the above, use `mireds` value as a range for warm and cold channels, based on the calculated rgb common values
// Every value is also scaled by `brightness` after applying all of the previous steps
// Notice that we completely ignore inputs and reset them to either kelvin'ized or MAX value as the first step
// Notice that we completely ignore inputs and reset them to either kelvin'ized or hardcoded ValueMin or ValueMax
// (also, RED **always** stays at ValueMax b/c we never go above 6.6k kelvin)
espurna::light::Rgb _lightKelvinRgb(espurna::light::Kelvin kelvin) {
kelvin.value /= 100;
@ -1065,37 +1125,31 @@ espurna::light::Rgb _lightKelvinRgb(espurna::light::Kelvin kelvin) {
void _lightValuesWithRgbCct(LightChannels& channels) {
const auto Temperature = _light_temperature;
const auto Factor = Temperature.factor();
const auto Warm = std::lround(Factor * espurna::light::ValueMax);
const auto Cold = std::lround((1.0f - Factor) * espurna::light::ValueMax);
const auto RgbFromKelvin = _lightKelvinRgb(Temperature.kelvin());
auto ptr = espurna::light::Pointers(channels);
const auto Brightness = _light_brightness;
auto& red = *ptr.red();
red = RgbFromKelvin.red();
red.apply(Brightness);
auto& green = *ptr.green();
green = RgbFromKelvin.green();
green.apply(Brightness);
auto& blue = *ptr.blue();
blue = RgbFromKelvin.blue();
blue.apply(Brightness);
const auto White = LightScaledWhite();
auto rgb = LightRgbWithoutWhite{RgbFromKelvin};
rgb.adjustOutput(rgb.inputMin());
auto& warm = *ptr.warm();
warm = Warm;
warm.apply(White, Brightness);
const auto Brightness = _light_brightness;
auto ptr = espurna::light::Pointers(channels);
auto& cold = *ptr.cold();
cold = Cold;
cold.apply(White, Brightness);
(*ptr.red()).apply(
LightResetInput{RgbFromKelvin.red()},
rgb, Brightness);
(*ptr.green()).apply(
LightResetInput{RgbFromKelvin.green()},
rgb, Brightness);
(*ptr.blue()).apply(
LightResetInput{RgbFromKelvin.blue()},
rgb, Brightness);
const auto White = LightScaledWhite(rgb.factor());
(*ptr.warm()).apply(
LightResetInput::forWarm(Temperature.factor()),
White, Brightness);
(*ptr.cold()).apply(
LightResetInput::forCold(Temperature.factor()),
White, Brightness);
}
// UI hints about channel distribution
@ -3393,6 +3447,8 @@ void _lightConfigure() {
espurna::light::settings::color(false);
}
_light_use_rgb = espurna::light::settings::rgb();
const auto has_warm_white = (Channels >= 4) || (Channels >= 1);
_light_has_warm_white = has_warm_white;
@ -3411,18 +3467,6 @@ void _lightConfigure() {
espurna::light::settings::cct(false);
}
const auto last_process_input_values = _light_process_input_values;
_light_process_input_values =
(_light_use_color) ? (
(_light_use_cct) ? _lightValuesWithRgbCct :
(_light_use_white) ? _lightValuesWithRgbWhite :
_lightValuesWithBrightnessExceptWhite) :
(_light_use_cct) ?
_lightValuesWithCct :
_lightValuesWithBrightness;
_light_use_rgb = espurna::light::settings::rgb();
_light_temperature.range(
espurna::light::TemperatureRange{
espurna::light::settings::miredsCold(),
@ -3445,6 +3489,16 @@ void _lightConfigure() {
_light_channels[index].gamma = (_light_has_color && _light_use_gamma) && _lightUseGamma(Channels, index);
}
const auto last_process_input_values = _light_process_input_values;
_light_process_input_values =
(_light_use_color) ? (
(_light_use_cct) ? _lightValuesWithRgbCct :
(_light_use_white) ? _lightValuesWithRgbWhite :
_lightValuesWithBrightnessExceptWhite) :
(_light_use_cct) ?
_lightValuesWithCct :
_lightValuesWithBrightness;
if (!_light_update && (last_process_input_values != _light_process_input_values)) {
lightUpdate(false);
}


Loading…
Cancel
Save