rosalina: implement full color space correction (to sRGB)

Add option to make the 3DS screens look like sRGB monitors. While this
is not perfect (due to screen variance, and it may make dark tones
slightly darker), this significantly improves the color fidelity of
homebrew and custon HOME Menu themes, removing that "blueish, washed-out
look".

Do note that first-party party 3DS games are designed around the 3DS's
"washed-out" color curve, and may not need that adjustment.

(done in collaboration with @profi200)
This commit is contained in:
TuxSH 2024-07-31 22:58:44 +02:00
parent 7a259a5792
commit db4564a3f5
12 changed files with 1138 additions and 9 deletions

Binary file not shown.

View File

@ -501,6 +501,11 @@ static int configIniHandler(void* user, const char* section, const char* name, c
CHECK_PARSE_OPTION(parseBoolOption(&opt, value)); CHECK_PARSE_OPTION(parseBoolOption(&opt, value));
cfg->topScreenFilter.invert = opt; cfg->topScreenFilter.invert = opt;
return 1; return 1;
} else if (strcmp(name, "screen_filters_top_color_curve_adj") == 0) {
s64 opt;
CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 0, 2));
cfg->topScreenFilter.colorCurveCorrection = (u8)opt;
return 1;
} else if (strcmp(name, "screen_filters_bot_cct") == 0) { } else if (strcmp(name, "screen_filters_bot_cct") == 0) {
s64 opt; s64 opt;
CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 1000, 25100)); CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 1000, 25100));
@ -526,6 +531,11 @@ static int configIniHandler(void* user, const char* section, const char* name, c
CHECK_PARSE_OPTION(parseBoolOption(&opt, value)); CHECK_PARSE_OPTION(parseBoolOption(&opt, value));
cfg->bottomScreenFilter.invert = opt; cfg->bottomScreenFilter.invert = opt;
return 1; return 1;
} else if (strcmp(name, "screen_filters_bot_color_curve_adj") == 0) {
s64 opt;
CHECK_PARSE_OPTION(parseDecIntOption(&opt, value, 0, 2));
cfg->bottomScreenFilter.colorCurveCorrection = (u8)opt;
return 1;
} else { } else {
CHECK_PARSE_OPTION(-1); CHECK_PARSE_OPTION(-1);
} }
@ -666,6 +676,7 @@ static size_t saveLumaIniConfigToStr(char *out)
(int)cfg->ntpTzOffetMinutes, (int)cfg->ntpTzOffetMinutes,
(int)cfg->topScreenFilter.cct, (int)cfg->bottomScreenFilter.cct, (int)cfg->topScreenFilter.cct, (int)cfg->bottomScreenFilter.cct,
(int)cfg->topScreenFilter.colorCurveCorrection, (int)cfg->bottomScreenFilter.colorCurveCorrection,
topScreenFilterGammaStr, bottomScreenFilterGammaStr, topScreenFilterGammaStr, bottomScreenFilterGammaStr,
topScreenFilterContrastStr, bottomScreenFilterContrastStr, topScreenFilterContrastStr, bottomScreenFilterContrastStr,
topScreenFilterBrightnessStr, bottomScreenFilterBrightnessStr, topScreenFilterBrightnessStr, bottomScreenFilterBrightnessStr,

View File

@ -36,7 +36,7 @@
#define CONFIG_FILE "config.ini" #define CONFIG_FILE "config.ini"
#define CONFIG_VERSIONMAJOR 3 #define CONFIG_VERSIONMAJOR 3
#define CONFIG_VERSIONMINOR 12 #define CONFIG_VERSIONMINOR 13
#define BOOTCFG_NAND BOOTCONFIG(0, 1) #define BOOTCFG_NAND BOOTCONFIG(0, 1)
#define BOOTCFG_EMUINDEX BOOTCONFIG(1, 3) #define BOOTCFG_EMUINDEX BOOTCONFIG(1, 3)

View File

@ -64,6 +64,7 @@ typedef volatile s64 vs64;
typedef struct ScreenFiltersCfgData { typedef struct ScreenFiltersCfgData {
u16 cct; u16 cct;
bool invert; bool invert;
u8 colorCurveCorrection;
s64 gammaEnc; s64 gammaEnc;
s64 contrastEnc; s64 contrastEnc;
s64 brightnessEnc; s64 brightnessEnc;

View File

@ -131,6 +131,7 @@ extern void* (*kAlloc)(FcramDescriptor *fcramDesc, u32 nbPages, u32 alignment, u
typedef struct ScreenFiltersCfgData { typedef struct ScreenFiltersCfgData {
u16 cct; u16 cct;
bool invert; bool invert;
u8 colorCurveCorrection;
s64 gammaEnc; s64 gammaEnc;
s64 contrastEnc; s64 contrastEnc;
s64 brightnessEnc; s64 brightnessEnc;

View File

@ -43,7 +43,7 @@ Result GetSystemInfoHook(s64 *out, s32 type, s32 param)
s32 toCopy = (s32)sizeof(cfwInfo.launchedPath) - offset; s32 toCopy = (s32)sizeof(cfwInfo.launchedPath) - offset;
if (toCopy > 8) toCopy = 8; if (toCopy > 8) toCopy = 8;
memcpy(out, (u8*)cfwInfo.launchedPath + offset, (toCopy > 0) ? toCopy : 0); memcpy(out, (u8*)cfwInfo.launchedPath + offset, (toCopy > 0) ? toCopy : 0);
} }
else switch(param) else switch(param)
{ {
// Please do not use these, except 0, 1, and 0x200 // Please do not use these, except 0, 1, and 0x200
@ -120,6 +120,12 @@ Result GetSystemInfoHook(s64 *out, s32 type, s32 param)
case 0x10C: case 0x10C:
*out = (s64)cfwInfo.bottomScreenFilter.invert; *out = (s64)cfwInfo.bottomScreenFilter.invert;
break; break;
case 0x10D:
*out = (s64)cfwInfo.topScreenFilter.colorCurveCorrection;
break;
case 0x10E:
*out = (s64)cfwInfo.bottomScreenFilter.colorCurveCorrection;
break;
case 0x180: case 0x180:
*out = cfwInfo.pluginLoaderFlags; *out = cfwInfo.pluginLoaderFlags;
break; break;

View File

@ -62,7 +62,7 @@ typedef struct MenuItem {
typedef struct Menu { typedef struct Menu {
const char *title; const char *title;
MenuItem items[16]; MenuItem items[24];
} Menu; } Menu;
extern u32 menuCombo; extern u32 menuCombo;

View File

@ -33,6 +33,7 @@ extern Menu screenFiltersMenu;
typedef struct ScreenFilter { typedef struct ScreenFilter {
u16 cct; u16 cct;
bool invert; bool invert;
u8 colorCurveCorrection;
float gamma; float gamma;
float contrast; float contrast;
float brightness; float brightness;
@ -56,4 +57,7 @@ void ScreenFiltersMenu_SetWarmIncandescent(void); // 2300K
void ScreenFiltersMenu_SetCandle(void); // 1900K void ScreenFiltersMenu_SetCandle(void); // 1900K
void ScreenFiltersMenu_SetEmber(void); // 1200K void ScreenFiltersMenu_SetEmber(void); // 1200K
void ScreenFiltersMenu_SetSrgbColorCurves(void);
void ScreenFiltersMenu_RestoreColorCurves(void);
void ScreenFiltersMenu_AdvancedConfiguration(void); void ScreenFiltersMenu_AdvancedConfiguration(void);

File diff suppressed because it is too large Load Diff

View File

@ -180,6 +180,7 @@ static size_t LumaConfig_SaveLumaIniConfigToStr(char *out, const CfgData *cfg)
(int)cfg->ntpTzOffetMinutes, (int)cfg->ntpTzOffetMinutes,
(int)cfg->topScreenFilter.cct, (int)cfg->bottomScreenFilter.cct, (int)cfg->topScreenFilter.cct, (int)cfg->bottomScreenFilter.cct,
(int)cfg->topScreenFilter.colorCurveCorrection, (int)cfg->bottomScreenFilter.colorCurveCorrection,
topScreenFilterGammaStr, bottomScreenFilterGammaStr, topScreenFilterGammaStr, bottomScreenFilterGammaStr,
topScreenFilterContrastStr, bottomScreenFilterContrastStr, topScreenFilterContrastStr, bottomScreenFilterContrastStr,
topScreenFilterBrightnessStr, bottomScreenFilterBrightnessStr, topScreenFilterBrightnessStr, bottomScreenFilterBrightnessStr,

View File

@ -29,6 +29,7 @@
#include "memory.h" #include "memory.h"
#include "menu.h" #include "menu.h"
#include "menus/screen_filters.h" #include "menus/screen_filters.h"
#include "menus/screen_filters_srgb_tables.h"
#include "draw.h" #include "draw.h"
#include "redshift/colorramp.h" #include "redshift/colorramp.h"
@ -53,6 +54,7 @@ static inline bool ScreenFiltersMenu_IsDefaultSettingsFilter(const ScreenFilter
ok = ok && filter->gamma == 1.0f; ok = ok && filter->gamma == 1.0f;
ok = ok && filter->contrast == 1.0f; ok = ok && filter->contrast == 1.0f;
ok = ok && filter->brightness == 0.0f; ok = ok && filter->brightness == 0.0f;
ok = ok && filter->colorCurveCorrection == 0;
return ok; return ok;
} }
@ -89,7 +91,7 @@ static u8 ScreenFilterMenu_CalculatePolynomialColorLutComponent(const float coef
return (u8)CLAMP(levelInt, 0, 255); // clamp again just to be sure return (u8)CLAMP(levelInt, 0, 255); // clamp again just to be sure
} }
static void ScreenFilterMenu_WritePolynomialColorLut(bool top, const float coeffs[][3], bool invert, float gamma, u32 dim) static void ScreenFilterMenu_WritePolynomialColorLut(bool top, u8 curveCorrection, const float coeffs[][3], bool invert, float gamma, u32 dim)
{ {
if (top) if (top)
GPU_FB_TOP_COL_LUT_INDEX = 0; GPU_FB_TOP_COL_LUT_INDEX = 0;
@ -99,9 +101,15 @@ static void ScreenFilterMenu_WritePolynomialColorLut(bool top, const float coeff
for (int i = 0; i <= 255; i++) { for (int i = 0; i <= 255; i++) {
Pixel px; Pixel px;
int inLevel = invert ? 255 - i : i; int inLevel = invert ? 255 - i : i;
px.r = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 0, gamma, dim, inLevel); const u8 (*tbl)[3] = curveCorrection == 2 ? ctrToSrgbTableTop : ctrToSrgbTableBottom;
px.g = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 1, gamma, dim, inLevel);
px.b = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 2, gamma, dim, inLevel); u8 inLevelR = curveCorrection > 0 ? tbl[inLevel][0] : inLevel;
u8 inLevelG = curveCorrection > 0 ? tbl[inLevel][1] : inLevel;
u8 inLevelB = curveCorrection > 0 ? tbl[inLevel][2] : inLevel;
px.r = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 0, gamma, dim, inLevelR);
px.g = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 1, gamma, dim, inLevelG);
px.b = ScreenFilterMenu_CalculatePolynomialColorLutComponent(coeffs, 2, gamma, dim, inLevelB);
px.z = 0; px.z = 0;
if (top) if (top)
@ -127,7 +135,7 @@ static void ScreenFiltersMenu_ApplyColorSettings(bool top)
{ a * wp[0], a * wp[1], a * wp[2] }, // x^1 { a * wp[0], a * wp[1], a * wp[2] }, // x^1
}; };
ScreenFilterMenu_WritePolynomialColorLut(top, poly, inv, g, 1); ScreenFilterMenu_WritePolynomialColorLut(top, filter->colorCurveCorrection, poly, inv, g, 1);
} }
static void ScreenFiltersMenu_SetCct(u16 cct) static void ScreenFiltersMenu_SetCct(u16 cct)
@ -138,6 +146,30 @@ static void ScreenFiltersMenu_SetCct(u16 cct)
ScreenFiltersMenu_ApplyColorSettings(false); ScreenFiltersMenu_ApplyColorSettings(false);
} }
static void ScreenFiltersMenu_UpdateEntries(void)
{
if (topScreenFilter.colorCurveCorrection == 0 || bottomScreenFilter.colorCurveCorrection == 0)
{
screenFiltersMenu.items[10].title = "Adjust both screens color curve to sRGB";
screenFiltersMenu.items[10].method = &ScreenFiltersMenu_SetSrgbColorCurves;
}
else
{
screenFiltersMenu.items[10].title = "Restore both screens color curve";
screenFiltersMenu.items[10].method = &ScreenFiltersMenu_RestoreColorCurves;
}
}
static void ScreenFiltersMenu_SetColorCurveCorrection(bool top, u8 colorCurveCorrection)
{
if (top)
topScreenFilter.colorCurveCorrection = colorCurveCorrection;
else
bottomScreenFilter.colorCurveCorrection = colorCurveCorrection;
ScreenFiltersMenu_ApplyColorSettings(top);
ScreenFiltersMenu_UpdateEntries();
}
Menu screenFiltersMenu = { Menu screenFiltersMenu = {
"Screen filters menu", "Screen filters menu",
{ {
@ -151,7 +183,8 @@ Menu screenFiltersMenu = {
{ "[2300K] Warm Incandescent", METHOD, .method = &ScreenFiltersMenu_SetWarmIncandescent }, { "[2300K] Warm Incandescent", METHOD, .method = &ScreenFiltersMenu_SetWarmIncandescent },
{ "[1900K] Candle", METHOD, .method = &ScreenFiltersMenu_SetCandle }, { "[1900K] Candle", METHOD, .method = &ScreenFiltersMenu_SetCandle },
{ "[1200K] Ember", METHOD, .method = &ScreenFiltersMenu_SetEmber }, { "[1200K] Ember", METHOD, .method = &ScreenFiltersMenu_SetEmber },
{ "Advanced configuration", METHOD, .method = &ScreenFiltersMenu_AdvancedConfiguration }, { "Adjust both screen color curve to sRGB", METHOD, .method = &ScreenFiltersMenu_SetSrgbColorCurves },
{ "Advanced configuration...", METHOD, .method = &ScreenFiltersMenu_AdvancedConfiguration },
{}, {},
} }
}; };
@ -162,6 +195,12 @@ void ScreenFiltersMenu_Set##name(void)\
ScreenFiltersMenu_SetCct(temp);\ ScreenFiltersMenu_SetCct(temp);\
} }
#define DEF_SRGB_SETTER(top, profile, name)\
void ScreenFiltersMenu_##name(void)\
{\
ScreenFiltersMenu_SetColorCurveCorrection(top, profile);\
}
void ScreenFiltersMenu_RestoreSettings(void) void ScreenFiltersMenu_RestoreSettings(void)
{ {
// Precondition: menu has not been entered // Precondition: menu has not been entered
@ -214,6 +253,9 @@ void ScreenFiltersMenu_LoadConfig(void)
svcGetSystemInfo(&out, 0x10000, 0x107); svcGetSystemInfo(&out, 0x10000, 0x107);
topScreenFilter.invert = (bool)out; topScreenFilter.invert = (bool)out;
svcGetSystemInfo(&out, 0x10000, 0x10D);
topScreenFilter.colorCurveCorrection = (u8)out;
svcGetSystemInfo(&out, 0x10000, 0x108); svcGetSystemInfo(&out, 0x10000, 0x108);
bottomScreenFilter.cct = (u16)out; bottomScreenFilter.cct = (u16)out;
if (bottomScreenFilter.cct < 1000 || bottomScreenFilter.cct > 25100) if (bottomScreenFilter.cct < 1000 || bottomScreenFilter.cct > 25100)
@ -236,6 +278,11 @@ void ScreenFiltersMenu_LoadConfig(void)
svcGetSystemInfo(&out, 0x10000, 0x10C); svcGetSystemInfo(&out, 0x10000, 0x10C);
bottomScreenFilter.invert = (bool)out; bottomScreenFilter.invert = (bool)out;
svcGetSystemInfo(&out, 0x10000, 0x10E);
bottomScreenFilter.colorCurveCorrection = (u8)out;
ScreenFiltersMenu_UpdateEntries();
} }
DEF_CCT_SETTER(6500, Default) DEF_CCT_SETTER(6500, Default)
@ -250,6 +297,18 @@ DEF_CCT_SETTER(2300, WarmIncandescent)
DEF_CCT_SETTER(1900, Candle) DEF_CCT_SETTER(1900, Candle)
DEF_CCT_SETTER(1200, Ember) DEF_CCT_SETTER(1200, Ember)
void ScreenFiltersMenu_SetSrgbColorCurves(void)
{
ScreenFiltersMenu_SetColorCurveCorrection(true, 1);
ScreenFiltersMenu_SetColorCurveCorrection(false, 2);
}
void ScreenFiltersMenu_RestoreColorCurves(void)
{
ScreenFiltersMenu_SetColorCurveCorrection(true, 0);
ScreenFiltersMenu_SetColorCurveCorrection(false, 0);
}
static void ScreenFiltersMenu_ClampFilter(ScreenFilter *filter) static void ScreenFiltersMenu_ClampFilter(ScreenFilter *filter)
{ {
filter->cct = CLAMP(filter->cct, 1000, 25100); filter->cct = CLAMP(filter->cct, 1000, 25100);