From b615e9941fa295798e01dc3445b7bc8c97f9e2c7 Mon Sep 17 00:00:00 2001 From: averne Date: Sat, 12 Oct 2024 22:21:14 +0200 Subject: [PATCH] Implement hue rotation control --- Makefile | 2 +- application/src/gfx.cpp | 3 +++ application/src/gui.cpp | 7 +++++++ common/include/color.hpp | 1 + common/include/config.hpp | 1 + common/include/fizeau.h | 1 + common/include/types.h | 5 +++++ common/src/color.cpp | 19 +++++++++++++++++++ common/src/config.cpp | 7 +++++++ common/src/config_parse.cpp | 2 ++ misc/default.ini | 7 +++++++ overlay/src/gui.cpp | 21 +++++++++++++++++++++ overlay/src/gui.hpp | 3 ++- sysmodule/src/nvdisp.cpp | 3 +++ sysmodule/src/profile.cpp | 1 + 15 files changed, 81 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ea1ed45..bf3053b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export FZ_VERSION = 2.5.5 +export FZ_VERSION = 2.6.0 export FZ_COMMIT = $(shell git rev-parse --short HEAD) export FZ_TID = 0100000000000F12 diff --git a/application/src/gfx.cpp b/application/src/gfx.cpp index c9d4e69..b8fd274 100644 --- a/application/src/gfx.cpp +++ b/application/src/gfx.cpp @@ -350,6 +350,9 @@ void render_preview(FizeauSettings &settings, int width, int height, int src_ima // Apply saturation coeffs = dot(coeffs, saturation_matrix(settings.saturation)); + // Apply hue rotation + coeffs = dot(coeffs, hue_matrix(settings.hue)); + auto colormatrix = glm::mat4( coeffs[0], coeffs[1], coeffs[2], 0, coeffs[3], coeffs[4], coeffs[5], 0, diff --git a/application/src/gui.cpp b/application/src/gui.cpp index ad3385e..fd6a793 100644 --- a/application/src/gui.cpp +++ b/application/src/gui.cpp @@ -227,6 +227,13 @@ Result draw_color_tab(Config &ctx) { im::Separator(); + // Saturation sliders + im::TextUnformatted("Hue"); + ctx.is_editing_day_profile |= new_slider("Day:", "##hued", ctx.profile.day_settings.hue, MIN_HUE, MAX_HUE, "%.2f"); + ctx.is_editing_night_profile |= new_slider("Night:", "##huen", ctx.profile.night_settings.hue, MIN_HUE, MAX_HUE, "%.2f"); + + im::Separator(); + // Filter combos im::TextUnformatted("Filter"); ctx.is_editing_day_profile |= new_combo("Day:", "##filterd", ctx.profile.day_settings.filter, filters_names); diff --git a/common/include/color.hpp b/common/include/color.hpp index de17451..042c661 100644 --- a/common/include/color.hpp +++ b/common/include/color.hpp @@ -37,6 +37,7 @@ constexpr ColorMatrix dot(const ColorMatrix &r, const ColorMatrix &l) { ColorMatrix filter_matrix(ColorFilter filter); std::tuple whitepoint(Temperature temperature); +ColorMatrix hue_matrix(Hue hue); ColorMatrix saturation_matrix(Saturation sat); float degamma(float x, Gamma gamma); diff --git a/common/include/config.hpp b/common/include/config.hpp index d0d069f..14a3452 100644 --- a/common/include/config.hpp +++ b/common/include/config.hpp @@ -31,6 +31,7 @@ class Config { constexpr static FizeauSettings default_settings = { .temperature = DEFAULT_TEMP, .gamma = DEFAULT_GAMMA, + .hue = DEFAULT_HUE, .saturation = DEFAULT_SAT, .luminance = DEFAULT_LUMA, .range = DEFAULT_RANGE, diff --git a/common/include/fizeau.h b/common/include/fizeau.h index 2241151..b6a1490 100644 --- a/common/include/fizeau.h +++ b/common/include/fizeau.h @@ -55,6 +55,7 @@ typedef enum { typedef struct { Temperature temperature; Gamma gamma; + Hue hue; Saturation saturation; Luminance luminance; ColorRange range; diff --git a/common/include/types.h b/common/include/types.h index 4eaf14b..63e255a 100644 --- a/common/include/types.h +++ b/common/include/types.h @@ -46,6 +46,11 @@ typedef float Luminance; #define MAX_LUMA 1.0f #define DEFAULT_LUMA 0.0f +typedef float Hue; +#define MIN_HUE -1.0f +#define MAX_HUE 1.0f +#define DEFAULT_HUE 0.0f + typedef float Saturation; #define MIN_SAT 0.0f #define MAX_SAT 2.0f diff --git a/common/src/color.cpp b/common/src/color.cpp index 7143d4b..f6ae970 100644 --- a/common/src/color.cpp +++ b/common/src/color.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -80,7 +81,25 @@ std::tuple whitepoint(Temperature temperature) { }; } +ColorMatrix hue_matrix(Hue hue) { + if (hue == DEFAULT_HUE) // Fast path + return { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + + auto angle = hue * std::numbers::pi_v; + auto c1 = (1.0f + 2.0f * std::cos(angle)) / 3.0f, + c2 = (1.0f - std::cos(angle)) / 3.0f, + c3 = std::sin(angle) / std::numbers::sqrt3_v; + return { + c1, c2 - c3, c2 + c3, + c2 + c3, c1, c2 - c3, + c2 - c3, c2 + c3, c1, + }; +} + ColorMatrix saturation_matrix(Saturation sat) { + if (sat == DEFAULT_SAT) // Fast path + return { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + return { (1.0f - sat) * 0.2126f + sat, (1.0f - sat) * 0.7152f , (1.0f - sat) * 0.0722f, (1.0f - sat) * 0.2126f , (1.0f - sat) * 0.7152f + sat, (1.0f - sat) * 0.0722f, diff --git a/common/src/config.cpp b/common/src/config.cpp index b82f695..506a3e5 100644 --- a/common/src/config.cpp +++ b/common/src/config.cpp @@ -88,6 +88,9 @@ void Config::sanitize_profile() { sanitize_minmax(this->profile.day_settings .saturation, MIN_SAT, MAX_SAT); sanitize_minmax(this->profile.night_settings.saturation, MIN_SAT, MAX_SAT); + sanitize_minmax(this->profile.day_settings .hue, MIN_HUE, MAX_HUE); + sanitize_minmax(this->profile.night_settings.hue, MIN_HUE, MAX_HUE); + sanitize_minmax(this->profile.day_settings .luminance, MIN_LUMA, MAX_LUMA); sanitize_minmax(this->profile.night_settings.luminance, MIN_LUMA, MAX_LUMA); @@ -167,6 +170,9 @@ std::string Config::make() { str += "saturation_day = " + std::to_string(this->profile.day_settings.saturation) + '\n'; str += "saturation_night = " + std::to_string(this->profile.night_settings.saturation) + '\n'; + str += "hue_day = " + std::to_string(this->profile.day_settings.hue) + '\n'; + str += "hue_night = " + std::to_string(this->profile.night_settings.hue) + '\n'; + str += "luminance_day = " + std::to_string(this->profile.day_settings.luminance) + '\n'; str += "luminance_night = " + std::to_string(this->profile.night_settings.luminance) + '\n'; @@ -216,6 +222,7 @@ Result Config::apply() { Result Config::reset() { this->profile.day_settings.temperature = DEFAULT_TEMP, this->profile.night_settings.temperature = DEFAULT_TEMP; this->profile.day_settings.gamma = DEFAULT_GAMMA, this->profile.night_settings.gamma = DEFAULT_GAMMA; + this->profile.day_settings.hue = DEFAULT_HUE, this->profile.night_settings.hue = DEFAULT_HUE; this->profile.day_settings.saturation = DEFAULT_SAT, this->profile.night_settings.saturation = DEFAULT_SAT; this->profile.day_settings.luminance = DEFAULT_LUMA, this->profile.night_settings.luminance = DEFAULT_LUMA; this->profile.day_settings.range = DEFAULT_RANGE, this->profile.night_settings.range = DEFAULT_RANGE; diff --git a/common/src/config_parse.cpp b/common/src/config_parse.cpp index d133e95..73787d2 100644 --- a/common/src/config_parse.cpp +++ b/common/src/config_parse.cpp @@ -156,6 +156,8 @@ int Config::ini_handler(void *user, const char *section, const char *name, const MATCH_SET(name, "gamma_night", p.night_settings.gamma) || MATCH_SET(name, "saturation_day", p.day_settings .saturation) || MATCH_SET(name, "saturation_night", p.night_settings.saturation) || + MATCH_SET(name, "hue_day", p.day_settings .hue) || + MATCH_SET(name, "hue_night", p.night_settings.hue) || MATCH_SET(name, "luminance_day", p.day_settings .luminance) || MATCH_SET(name, "luminance_night", p.night_settings.luminance) ) { diff --git a/misc/default.ini b/misc/default.ini index b6124b6..3f56bab 100644 --- a/misc/default.ini +++ b/misc/default.ini @@ -35,6 +35,11 @@ gamma_night = 2.4 saturation_day = 1.0 saturation_night = 1.0 +; Hue +; Value has to be >=-1.0, and <=1.0 +hue_day = 0.0 +hue_night = 0.0 + ; Luminance ; Value has to be >=-1.0, and <=1.0 luminance_day = 0.0 @@ -63,6 +68,8 @@ gamma_day = 2.4 gamma_night = 2.4 saturation_day = 1.0 saturation_night = 1.0 +hue_day = 0.0 +hue_night = 0.0 luminance_day = 0.0 luminance_night = -0.3 range_day = 0.0-1.0 diff --git a/overlay/src/gui.cpp b/overlay/src/gui.cpp index 1adc88a..85b84c6 100644 --- a/overlay/src/gui.cpp +++ b/overlay/src/gui.cpp @@ -157,6 +157,22 @@ tsl::elm::Element *FizeauOverlayGui::createUI() { val * (MAX_SAT - MIN_SAT) / 100 + MIN_SAT; }); + this->hue_slider = new tsl::elm::TrackBar(""); + this->hue_slider->setProgress(((this->is_day ? this->config.profile.day_settings.hue : this->config.profile.night_settings.hue) - MIN_HUE) + * 100 / (MAX_HUE - MIN_HUE)); + this->hue_slider->setClickListener([this](std::uint64_t keys) { + if (keys & HidNpadButton_Y) { + this->hue_slider->setProgress((DEFAULT_HUE - MIN_HUE) * 100 / (MAX_HUE - MIN_HUE)); + (this->is_day ? this->config.profile.day_settings.hue : this->config.profile.night_settings.hue) = DEFAULT_HUE; + return true; + } + return false; + }); + this->hue_slider->setValueChangedListener([this](std::uint8_t val) { + (this->is_day ? this->config.profile.day_settings.hue : this->config.profile.night_settings.hue) = + val * (MAX_HUE - MIN_HUE) / 100 + MIN_HUE; + }); + this->luma_slider = new tsl::elm::TrackBar(""); this->luma_slider->setProgress(((this->is_day ? this->config.profile.day_settings.luminance : this->config.profile.night_settings.luminance) - MIN_LUMA) * 100 / (MAX_LUMA - MIN_LUMA)); @@ -206,6 +222,7 @@ tsl::elm::Element *FizeauOverlayGui::createUI() { this->filter_header = new tsl::elm::CategoryHeader("Filter"); this->gamma_header = new tsl::elm::CategoryHeader(""); this->sat_header = new tsl::elm::CategoryHeader(""); + this->hue_header = new tsl::elm::CategoryHeader(""); this->luma_header = new tsl::elm::CategoryHeader(""); auto *frame = new tsl::elm::OverlayFrame("Fizeau", VERSION "-" COMMIT); @@ -218,6 +235,8 @@ tsl::elm::Element *FizeauOverlayGui::createUI() { list->addItem(this->temp_slider); list->addItem(this->sat_header); list->addItem(this->sat_slider); + list->addItem(this->hue_header); + list->addItem(this->hue_slider); list->addItem(this->gamma_header); list->addItem(this->gamma_slider); @@ -242,6 +261,8 @@ void FizeauOverlayGui::update() { this->is_day ? this->config.profile.day_settings.gamma : this->config.profile.night_settings.gamma)); this->sat_header->setText(format("Saturation: %.2f", this->is_day ? this->config.profile.day_settings.saturation : this->config.profile.night_settings.saturation)); + this->hue_header->setText(format("Hue: %.2f", + this->is_day ? this->config.profile.day_settings.hue : this->config.profile.night_settings.hue)); this->luma_header->setText(format("Luminance: %.2f", this->is_day ? this->config.profile.day_settings.luminance : this->config.profile.night_settings.luminance)); } diff --git a/overlay/src/gui.hpp b/overlay/src/gui.hpp index b8af028..ebda336 100644 --- a/overlay/src/gui.hpp +++ b/overlay/src/gui.hpp @@ -64,11 +64,12 @@ class FizeauOverlayGui: public tsl::Gui { tsl::elm::TrackBar *brightness_slider; tsl::elm::TrackBar *gamma_slider; tsl::elm::TrackBar *sat_slider; + tsl::elm::TrackBar *hue_slider; tsl::elm::TrackBar *luma_slider; tsl::elm::ListItem *range_button; tsl::elm::CategoryHeader *temp_header, *filter_header, - *brightness_header, *gamma_header, *sat_header, *luma_header; + *brightness_header, *gamma_header, *sat_header, *hue_header, *luma_header; }; } // namespace fz diff --git a/sysmodule/src/nvdisp.cpp b/sysmodule/src/nvdisp.cpp index ba5c44e..b9ac794 100644 --- a/sysmodule/src/nvdisp.cpp +++ b/sysmodule/src/nvdisp.cpp @@ -42,6 +42,9 @@ Cmu calculate_cmu(FizeauSettings &settings) { // Apply saturation coeffs = dot(coeffs, saturation_matrix(settings.saturation)); + // Apply hue rotation + coeffs = dot(coeffs, hue_matrix(settings.hue)); + // Copy color matrix to cmu format std::transform(coeffs.begin(), coeffs.end(), &cmu.krr, [](float c) -> QS18 { return c; }); diff --git a/sysmodule/src/profile.cpp b/sysmodule/src/profile.cpp index 2a6f1d5..84c31fd 100644 --- a/sysmodule/src/profile.cpp +++ b/sysmodule/src/profile.cpp @@ -43,6 +43,7 @@ FizeauSettings interpolate_profile(FizeauProfile &in, float factor, bool from_da out = { .temperature = static_cast(std::lerp(from.temperature, to.temperature, factor)), .gamma = std::lerp(from.gamma, to.gamma, factor), + .hue = std::lerp(from.hue, to.hue, factor), .saturation = std::lerp(from.saturation, to.saturation, factor), .luminance = std::lerp(from.luminance, to.luminance, factor), .range = {