*/}}
Browse Source

Color management

Yiming Wu 2 years ago
parent
commit
b354b05a3c
8 changed files with 220 additions and 29 deletions
  1. 10 10
      la_interface.h
  2. 17 2
      la_kernel.c
  3. 7 0
      la_tns.h
  4. 39 3
      la_tns_kernel.c
  5. 8 0
      resources/la_properties.c
  6. 14 12
      resources/la_templates.c
  7. 113 1
      resources/la_tns_shaders.cpp
  8. 12 1
      resources/la_widgets.c

+ 10 - 10
la_interface.h

@@ -499,20 +499,17 @@ STRUCTURE(laWindow){
     Window* win;
     GLXContext glc;
 
-    int X;
-    int Y;
-    int W;
-    int H;
-    int CW;
-    int CH;
+    int X,Y,W,H;
+    int CW,CH;
+    int Activated;
+    int Shown;
+    int IsDocking;
+    int OutputColorSpace;
 
     laPropPack PP;
     laPropStep FakePS;
 
     laSafeString *Title;
-    int Activated;
-    int Shown;
-    int IsDocking;
     laWindow* DockingFrom;
 
     laListHandle EventList;
@@ -887,6 +884,7 @@ STRUCTURE(laUiTemplate){
     laUiDefineFunc          Define;
     laUiDefineFunc          Header;
     laPanelDetachedPropFunc PropFunc;
+    int DefaultGLFormat;
     laKeyMapper KeyMap;
 };
 
@@ -957,6 +955,7 @@ STRUCTURE(laWidget){
 #define LA_UI_COLLECTION_NO_HIGHLIGHT LA_TEXT_ALIGN_LEFT
 #define LA_TEXT_ONE_LINE       (1<<23)
 #define LA_UI_FLAGS_NO_SCROLL_INACTIVE (1<<24)
+#define LA_UI_FLAGS_COLOR_SPACE_CLAY (1<<25)
 
 #define LA_UI_FLAGS_INT_ICON  (LA_UI_FLAGS_NO_DECAL|LA_UI_FLAGS_NO_EVENT|LA_UI_FLAGS_ICON)
 #define LA_UI_FLAGS_PLAIN     (LA_UI_FLAGS_NO_DECAL|LA_UI_FLAGS_NO_EVENT)
@@ -1073,6 +1072,7 @@ STRUCTURE(laPanel){
     //int         PanX, PanY;
 
     int Show;
+    int DefaultGLFormat;
     short Refresh;
     unsigned int FrameDistinguish;
 
@@ -1850,7 +1850,7 @@ laUiTemplate *laFindUiTemplate(char *Identifier);
 
 void la_DestroyUiTemplate(laUiTemplate* uit);
 void la_DestroyCanvasTemplate(laCanvasTemplate* uit);
-laUiTemplate *laRegisterUiTemplate(char *Identifier, char* Title, laUiDefineFunc func,laPanelDetachedPropFunc PropFunc, laUiDefineFunc header, char* NewCategory);
+laUiTemplate *laRegisterUiTemplate(char *Identifier, char* Title, laUiDefineFunc func,laPanelDetachedPropFunc PropFunc, laUiDefineFunc header, char* NewCategory, int DefaultGLFormat);
 laCanvasTemplate *laRegisterCanvasTemplate(char *Identifier, char *ForContainer, laModalFunc ExtraModal, laCanvasDrawFunc Func, laUiDrawFunc SecondDraw, laUiInitFunc CustomInit, laUiDestroyFunc CustomDestroy);
 
 void laGet2DViewRange(laCanvasExtra *e, int *L, int *R, int *U, int *B);

+ 17 - 2
la_kernel.c

@@ -1150,6 +1150,9 @@ void la_PanelBackgroundInit(laPanel *p, laBoxedTheme *bt){
 void la_PanelDrawToWindow(laPanel *p, laWindow *w){
     real Color[] = {1, 1, 1, 1};
     real L, W, U, H;
+
+    tnsUseImmShader(); tnsEnableShaderv(T->immShader); tnsUniformOutputColorSpace(T->immShader,w->OutputColorSpace);
+
     switch (p->AnimationMode){
     case 0:
         tnsDraw2DTextureDirectly(p->OffScr->pColor[0], p->X, p->Y, p->W, p->H);
@@ -1234,11 +1237,13 @@ void la_PanelDrawToWindow(laPanel *p, laWindow *w){
     }
 
     tnsFlush();
+
+    tnsUniformOutputColorSpace(T->immShader,0);
 }
 void la_PanelDrawToOffsceen(laPanel *p, laUiList *uil){
     laEnsurePanelInBound(p,p->MenuRefer?p->MenuRefer:&p->UI);
     if (!p->OffScr){
-        p->OffScr = tnsCreate2DOffscreen(GL_RGBA8, p->W, p->H, MAIN.PanelMultisample, 0);
+        p->OffScr = tnsCreate2DOffscreen(p->DefaultGLFormat?p->DefaultGLFormat:GL_RGBA8, p->W, p->H, MAIN.PanelMultisample, 0);
     }
     tnsDrawToOffscreen(p->OffScr, 1, 0);
 }
@@ -1506,6 +1511,8 @@ void la_BlockDefDrawSelf(laBlock *b, int CH){
     int tw = 0;
     int L = LA_RH+LA_SEAM_W;
 
+    tnsUseImmShader(); tnsEnableShaderv(T->immShader); tnsUniformOutputColorSpace(T->immShader,MAIN.CurrentWindow->OutputColorSpace);
+
     for (p = b->Panels.pFirst; p; p = p->Item.pNext){
         tw += p->TitleWidth + LA_SEAM_W*2;
     }
@@ -1565,10 +1572,14 @@ void la_BlockDefDrawSelf(laBlock *b, int CH){
     tnsDrawStringAuto("🔻",laThemeColor(bt,LA_BT_BORDER), b->X+LA_SEAM_W,b->X+b->W, CH-b->Y, LA_TEXT_REVERT_Y);
 
     tnsFlush();
+
+    tnsUniformOutputColorSpace(T->immShader,0);
 }
 void la_BlockDefDrawSelfEmpty(laBlock *b, int CH){
     laBoxedTheme *bt = _LA_THEME_PANEL;
     real tv[8];
+
+    tnsUseImmShader(); tnsEnableShaderv(T->immShader); tnsUniformOutputColorSpace(T->immShader,MAIN.CurrentWindow->OutputColorSpace);
     tnsUseNoTexture();
     tnsMakeQuad2d(tv, b->X, (CH - b->Y),
                       b->X + b->W, (CH - b->Y),
@@ -1581,6 +1592,8 @@ void la_BlockDefDrawSelfEmpty(laBlock *b, int CH){
     tnsDrawStringAuto("Dock some panels here.", laThemeColor(bt,LA_BT_TEXT), b->X+LA_SEAM_W,b->X+b->W-LA_SEAM_W, CH-b->Y-b->H/2+LA_RH2,
         LA_TEXT_ALIGN_CENTER|LA_TEXT_REVERT_Y|LA_TEXT_USE_NEWLINE|LA_TEXT_LINE_WRAP);
     tnsFlush();
+
+    tnsUniformOutputColorSpace(T->immShader,0);
 }
 void la_BlockDefDrawRecursive(laWindow *w, laBoxedTheme *bt, laBlock *b){
     if (b->B1){
@@ -2065,6 +2078,7 @@ laPanel *laCreatePanelT(laBlock *b, laUiTemplate* uit){
 
     strSafeSet(&p->Title, uit->Title->Ptr);
     p->PanelTemplate = uit;
+    p->DefaultGLFormat=uit->DefaultGLFormat?uit->DefaultGLFormat:GL_RGBA8;
 
     p->Show = 1;
     p->PP.EndInstance = p;
@@ -3878,7 +3892,7 @@ void la_DestroyCanvasTemplate(laCanvasTemplate* uit){
     laKeyMapItem* kmi; while(kmi=lstPopItem(&uit->KeyMapper.Items)){ la_FreeKeyMapItem(kmi); }
     memFree(uit);
 }
-laUiTemplate *laRegisterUiTemplate(char *Identifier, char* Title, laUiDefineFunc func,laPanelDetachedPropFunc PropFunc, laUiDefineFunc header, char* NewCategory){
+laUiTemplate *laRegisterUiTemplate(char *Identifier, char* Title, laUiDefineFunc func,laPanelDetachedPropFunc PropFunc, laUiDefineFunc header, char* NewCategory, int DefaultGLFormat){
     laUiTemplate *uit = memAcquire(sizeof(laUiTemplate));
     strSafeSet(&uit->Identifier, Identifier);
     strSafeSet(&uit->Title, Title);
@@ -3886,6 +3900,7 @@ laUiTemplate *laRegisterUiTemplate(char *Identifier, char* Title, laUiDefineFunc
     uit->Define = func;
     uit->Header = header;
     uit->PropFunc = PropFunc;
+    uit->DefaultGLFormat=DefaultGLFormat;
     if(MAIN.InitDone) lstAppendItem(&MAIN.PanelTemplates, uit); else lstAppendItem(&MAIN.InitPanelTemplates, uit);
     la_UDFAppendSharedTypePointer(Identifier, uit);
     return uit;

+ 7 - 0
la_tns.h

@@ -14,6 +14,9 @@ extern const char* TNS_SHADER_COLOR_COMMON;
 #define deg(r) r / TNS_PI * 180.0
 #define rad(d) d *TNS_PI / 180.0
 
+#define TNS_COLOR_SPACE_SRGB 0
+#define TNS_COLOR_SPACE_CLAY 1
+
 //typedef real tnsMatrix33d[9];
 
 typedef float tnsMatrix44f[16];
@@ -66,6 +69,7 @@ struct _tnsShader{
     int iMultiplyColor, StateMultiplyColor;
     int iTextureMode, StateTextureMode, iColorMode;
     int iSampleAmount, StateSampleAmount;
+    int iInputColorSpace, iOutputColorSpace, iShowStripes;
 
     int uViewDir,uViewPos,uFOV;
 };
@@ -1216,6 +1220,9 @@ void tnsBindTexture(tnsTexture *t);
 void tnsUnbindTexture();
 void tnsUniformUseTexture(tnsShader* s, int mode, int sample);
 void tnsUniformColorMode(tnsShader* s, int mode);
+void tnsUniformInputColorSpace(tnsShader* s, int ColorSpace);
+void tnsUniformOutputColorSpace(tnsShader* s, int ColorSpace);
+void tnsUniformShowColorOverflowStripes(tnsShader* s, int Show);
 void tnsUniformUseMultiplyColor(tnsShader* s, int enable);
 void tnsUseMaskTexture(tnsTexture *t);
 void tnsUseTexture(tnsTexture *t);

+ 39 - 3
la_tns_kernel.c

@@ -121,6 +121,9 @@ uniform int ColorMode;\n\
 uniform int MultiplyColor;\n\
 uniform int SampleAmount;\n\
 uniform int UseNormal;\n\
+uniform int InputColorSpace;\n\
+uniform int OutputColorSpace;\n\
+uniform int ShowStripes;\n\
 uniform vec3 uViewPos;\n\
 in vec4 fColor;\n\
 in vec2 fUV;\n\
@@ -130,6 +133,28 @@ layout(location = 0) out vec4 outColor;\n\
 layout(location = 1) out vec3 outNormal;\n\
 layout(location = 2) out vec3 outGPos;\n\
 #with TNS_SHADER_COLOR_COMMON\n\
+vec3 ConvertColorSpace(vec3 color){\n\
+    if(InputColorSpace!=OutputColorSpace){\n\
+        if(ColorMode==0){\n\
+            if(InputColorSpace==0) color=to_linear_srgb(color);\n\
+            else if(InputColorSpace==1) color=to_linear_clay(color);\n\
+        } \n\
+        vec3 xyz; if(ColorMode==1){ color=okhsl_to_linear_srgb(color); }\n\
+        if(InputColorSpace==1){ xyz=Clay2XYZ(color); }\n\
+        if(InputColorSpace==0){ xyz=sRGB2XYZ(color); }\n\
+        if(OutputColorSpace==0){ color=to_log_srgb(XYZ2sRGB(xyz)); }\n\
+        if(OutputColorSpace==1){ color=to_log_clay(XYZ2Clay(xyz)); }\n\
+    }else{\n\
+        if(ColorMode==1){ return okhsl_to_srgb(color); }\n\
+        if(ColorMode==0){ return color; }\n\
+        if(OutputColorSpace==0){ color=to_log_srgb(color); }\n\
+        if(OutputColorSpace==1){ color=to_log_clay(color); }\n\
+    }\n\
+    if(ShowStripes!=0){\n\
+        if(color.r>1.00001||color.g>1.00001||color.b>1.00001||color.r<0||color.g<0||color.b<0){ color=mix(color,vec3(0.5,0.5,0.5),(sin((gl_FragCoord.x+gl_FragCoord.y)/2)>0)?1:0); }\n\
+    }\n\
+    return color;\n\
+}\n\
 void main(){\n\
     vec4 color=vec4(1,0,1,1);\n\
     if(TextureMode==0){ color = fColor;}\n\
@@ -153,9 +178,8 @@ void main(){\n\
         vec3 oNormal=fNormal; if(view<0){ oNormal=-fNormal; }\n\
         outNormal = oNormal;\n\
     }\n\
-    if(ColorMode==1){ color.rgb=okhsl_to_srgb(color.rgb); }\n\
-    else if(ColorMode==2){ color.rgb=to_log_srgb(color.rgb); }\n\
-    outColor = color;\n\
+    color=vec4(ConvertColorSpace(color.rgb),color.a);\n\
+    outColor = color; \n\
     outGPos = fGPos;\n\
 }";
 const char LA_RAY_VERTEX_SHADER[] = "#version 330\n\
@@ -483,6 +507,9 @@ void tnsShaderMakeIndex(tnsShader *tns){
     tns->iColorMode = glGetUniformLocation(program, "ColorMode");
     tns->iSampleAmount = glGetUniformLocation(program, "SampleAmount");
     tns->iUseNormal = glGetUniformLocation(program, "UseNormal");
+    tns->iOutputColorSpace=glGetUniformLocation(program, "OutputColorSpace");
+    tns->iInputColorSpace=glGetUniformLocation(program, "InputColorSpace");
+    tns->iShowStripes=glGetUniformLocation(program, "ShowStripes");
     if(tns->iTexColor>=0){glUniform1i(tns->iTexColor, 0);}
     if(tns->iTexColorMS>=0){glUniform1i(tns->iTexColorMS, 1);}
 
@@ -1989,6 +2016,15 @@ void tnsUniformUseMultiplyColor(tnsShader* s, int enable){
 void tnsUniformColorMode(tnsShader* s, int mode){
     glUniform1i(s->iColorMode,mode);
 }
+void tnsUniformInputColorSpace(tnsShader* s, int ColorSpace){
+    glUniform1i(s->iInputColorSpace,ColorSpace);
+}
+void tnsUniformOutputColorSpace(tnsShader* s, int ColorSpace){
+    glUniform1i(s->iOutputColorSpace,ColorSpace);
+}
+void tnsUniformShowColorOverflowStripes(tnsShader* s, int Show){
+    glUniform1i(s->iShowStripes,Show);
+}
 
 void tnsDraw2DTextureDirectly(tnsTexture *t, real x, real y, real w, real h){
     real Verts[8];

+ 8 - 0
resources/la_properties.c

@@ -487,6 +487,10 @@ void* laget_CurrentRackPage(laRackPageCollection* c){
     return c->CurrentPage;
 }
 
+void laset_WindowColorSpace(laWindow* w, int space){
+    w->OutputColorSpace=space; laRedrawCurrentWindow();
+}
+
 void *tnsget_TnsMain(void *unused,void *unused2){
     return T;
 }
@@ -1083,6 +1087,10 @@ void la_RegisterInternalProps(){
             laAddIntProperty(p, "size", "Size", "The Size Of A Window", 0, "W,H", "px", 0, 0, 0, 0, 0, offsetof(laWindow, W), 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,LA_READ_ONLY);
             laAddIntProperty(p, "client_size", "Client Area Size", "Display Canvans Size Of A Window", 0, "W,H", "px", 0, 0, 1, 0, 0, offsetof(laWindow, CW), 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,LA_READ_ONLY);
             laAddStringProperty(p, "operator_hints", "Operator Hints", "Operator hints if there's any", LA_WIDGET_STRING_PLAIN, 0, 0, 0, 1, offsetof(laWindow, OperatorHints), 0, 0, 0, 0, LA_READ_ONLY);
+            ep = laAddEnumProperty(p, "output_color_space", "Output Color Space", "Output color space of this window, set this to the monitor's color space to get accurate result", 0, 0, 0, 0, 0, offsetof(laWindow, OutputColorSpace), 0, laset_WindowColorSpace, 0, 0, 0, 0, 0, 0, 0, 0);{
+                laAddEnumItemAs(ep, "SRGB", "sRGB", "Standard sRGB diplay", TNS_COLOR_SPACE_SRGB, 0);
+                laAddEnumItemAs(ep, "CLAY", "Clay", "Clay color space (AdobeRGB 1998 compatible)", TNS_COLOR_SPACE_CLAY, 0);
+            }
         }
 
         // UI LAYOUT ========================================================================================

+ 14 - 12
resources/la_templates.c

@@ -20,10 +20,10 @@ void laui_DefaultPropDetails(laUiList *uil, laPropPack *This, laPropPack *Extra,
             laProp* p=This->LastPs->p; laPropContainer* pc=p->Container; void* inst=This->LastPs->UseInstance; 
             char FullPath[256]={0}; la_GetPropPackFullPath(This, FullPath);
             strSafePrint(&s,"%s", FullPath); if(ui->ExtraInstructions){ strSafePrint(&s,"(%s)", ui->ExtraInstructions->Ptr); }
-            laShowLabel(uil,c,p->Description,0,0);
+            laShowLabel(uil,c,p->Description,0,0)->Flags|=LA_TEXT_LINE_WRAP;
             laShowLabel(uil,c,s->Ptr,0,0)->Flags|=LA_TEXT_MONO|LA_UI_FLAGS_DISABLED;
         }else{
-            laShowLabel(uil,c,ui->AT->Description,0,0);
+            laShowLabel(uil,c,ui->AT->Description,0,0)->Flags|=LA_TEXT_LINE_WRAP;
             strSafePrint(&s,"%s", ui->AT->Identifier); if(ui->ExtraInstructions){ strSafePrint(&s,"(%s)", ui->ExtraInstructions->Ptr); }
             laShowLabel(uil,c,s->Ptr,0,0)->Flags|=LA_TEXT_MONO|LA_UI_FLAGS_DISABLED;
         }
@@ -33,10 +33,10 @@ void laui_DefaultPropDetails(laUiList *uil, laPropPack *This, laPropPack *Extra,
         char FullPath[256]={0}; la_GetPropPackFullPath(This, FullPath);
         laProp* p=This->LastPs->p; laPropContainer* pc=p->Container; void* inst=This->LastPs->UseInstance;
 
-        laShowLabel(uil,c,p->Description,0,0);
+        laShowLabel(uil,c,p->Description,0,0)->Flags|=LA_TEXT_LINE_WRAP;
         if(p->PropertyType==(LA_PROP_ENUM)||(LA_PROP_ARRAY|LA_PROP_ENUM)){
             laEnumItem* ei=laGetEnumArrayIndexed(This,This->LastIndex);
-            if(ei){ strSafeSet(&s,0);strSafePrint(&s,"Current: %s", ei->Description);laShowLabel(uil,c,s->Ptr,0,0)->Flags|=LA_UI_FLAGS_HIGHLIGHT; }
+            if(ei){ strSafeSet(&s,0);strSafePrint(&s,"Current: %s", ei->Description);laShowLabel(uil,c,s->Ptr,0,0)->Flags|=LA_UI_FLAGS_HIGHLIGHT|LA_TEXT_LINE_WRAP; }
         }
         laShowLabel(uil,c,FullPath,0,0)->Flags|=LA_TEXT_MONO|LA_UI_FLAGS_DISABLED;
 
@@ -537,6 +537,8 @@ void laui_DefaultMenuBarActual(laUiList *uil, laPropPack *pp, laPropPack *actins
             }laEndCondition(uil, ui);
         }laEndCondition(uil,mui);
 
+        laShowItem(uil,c,0,"la.windows.output_color_space");
+
         if(MAIN.MenuExtras){ laShowSeparator(uil,c); MAIN.MenuExtras(uil,0,0,0,0); }
 
         laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(0,"la.windows.panels_hidden"));
@@ -1511,12 +1513,12 @@ void laui_Drivers(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *
 
 
 void la_RegisterBuiltinTemplates(){
-    laRegisterUiTemplate("LAUI_input_mapper","Input Mapper",laui_InputMapper,0,0,"Controlling");
-    laRegisterUiTemplate("LAUI_drivers","Drivers",laui_Drivers,lauidetached_Drivers,0,0);
-    laRegisterUiTemplate("LAUI_controllers", "Controllers", laui_GameController, lauidetached_GameController, 0,0);
-    laRegisterUiTemplate("LAUI_user_preferences", "User Preferences", laui_UserPreference, 0, 0, "System");
-    laRegisterUiTemplate("LAUI_about", "About", laui_About, 0, 0, 0);
-    laRegisterUiTemplate("LAUI_texture_inspector", "Texture Inspector", laui_TextureInspector, lauidetached_TextureInspector, 0, 0);
-    laRegisterUiTemplate("LAUI_data_manager", "Data Manager", laui_IdleDataManager, lauidetached_IdleDataManager, 0, 0);
-    laRegisterUiTemplate("LAUI_terminal", "Terminal", laui_terminal, 0, 0, 0);
+    laRegisterUiTemplate("LAUI_input_mapper","Input Mapper",laui_InputMapper,0,0,"Controlling", 0);
+    laRegisterUiTemplate("LAUI_drivers","Drivers",laui_Drivers,lauidetached_Drivers,0,0, 0);
+    laRegisterUiTemplate("LAUI_controllers", "Controllers", laui_GameController, lauidetached_GameController, 0,0, 0);
+    laRegisterUiTemplate("LAUI_user_preferences", "User Preferences", laui_UserPreference, 0, 0, "System", 0);
+    laRegisterUiTemplate("LAUI_about", "About", laui_About, 0, 0, 0, 0);
+    laRegisterUiTemplate("LAUI_texture_inspector", "Texture Inspector", laui_TextureInspector, lauidetached_TextureInspector, 0, 0, 0);
+    laRegisterUiTemplate("LAUI_data_manager", "Data Manager", laui_IdleDataManager, lauidetached_IdleDataManager, 0, 0, 0);
+    laRegisterUiTemplate("LAUI_terminal", "Terminal", laui_terminal, 0, 0, 0, 0);
 }

+ 113 - 1
resources/la_tns_shaders.cpp

@@ -16,6 +16,12 @@ vec3 to_log_srgb(vec3 color){
 vec3 to_linear_srgb(vec3 color){
 	return vec3(srgb_transfer_function_inv(color.r),srgb_transfer_function_inv(color.g),srgb_transfer_function_inv(color.b));
 }
+vec3 to_linear_clay(vec3 color){
+	return vec3(pow(color.r,2.19921875),pow(color.g,2.19921875),pow(color.b,2.19921875));
+}
+vec3 to_log_clay(vec3 color){
+	return vec3(pow(color.r,1.0/2.19921875),pow(color.g,1.0/2.19921875),pow(color.b,1.0/2.19921875));
+}
 vec3 linear_srgb_to_oklab(vec3 c){
 	float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
 	float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
@@ -42,6 +48,18 @@ vec3 oklab_to_linear_srgb(vec3 c){
 		-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s
 	);
 }
+vec3 oklab_to_xyz(vec3 c){
+	float l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;
+	float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;
+	float s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;
+	float l = l_ * l_ * l_;
+	float m = m_ * m_ * m_;
+	float s = s_ * s_ * s_;
+	mat3 mat=inverse(mat3(vec3(+0.8189330101,+0.0329845436,+0.0482003018),
+			              vec3(+0.3618667424,+0.9293118715,+0.2643662691),
+				          vec3(-0.1288597137,+0.0361456387,+0.6338517070)));
+	return mat*vec3(l,m,s);
+}
 float compute_max_saturation(float a, float b){	float k0, k1, k2, k3, k4, wl, wm, ws;
 	if (-1.88170328f * a - 0.80936493f * b > 1.f){		k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
 		wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
@@ -301,6 +319,76 @@ vec3 okhsl_to_srgb(vec3 hsl){
 		srgb_transfer_function(rgb.b)
 	);
 }
+vec3 okhsl_to_linear_srgb(vec3 hsl){
+	float h = hsl.x;
+	float s = hsl.y;
+	float l = hsl.z;
+	if (l == 1.0f){
+		return vec3( 1.f, 1.f, 1.f );
+	}
+	else if (l == 0.f){
+		return vec3( 0.f, 0.f, 0.f );
+	}
+	float a_ = cos(2.f * M_PI * h);
+	float b_ = sin(2.f * M_PI * h);
+	float L = toe_inv(l);
+	vec3 cs = get_Cs(L, a_, b_);
+	float C_0 = cs.x;
+	float C_mid = cs.y;
+	float C_max = cs.z;
+	float mid = 0.8f;
+	float mid_inv = 1.25f;
+	float C, t, k_0, k_1, k_2;
+	if (s < mid){
+		t = mid_inv * s;
+		k_1 = mid * C_0;
+		k_2 = (1.f - k_1 / C_mid);
+		C = t * k_1 / (1.f - k_2 * t);
+	}
+	else{
+		t = (s - mid)/ (1.f - mid);
+		k_0 = C_mid;
+		k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
+		k_2 = (1.f - (k_1) / (C_max - C_mid));
+		C = k_0 + t * k_1 / (1.f - k_2 * t);
+	}
+	return oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));
+}
+vec3 okhsl_to_xyz(vec3 hsl){
+	float h = hsl.x;
+	float s = hsl.y;
+	float l = hsl.z;
+	if (l == 1.0f){
+		return vec3( 1.f, 1.f, 1.f );
+	}
+	else if (l == 0.f){
+		return vec3( 0.f, 0.f, 0.f );
+	}
+	float a_ = cos(2.f * M_PI * h);
+	float b_ = sin(2.f * M_PI * h);
+	float L = toe_inv(l);
+	vec3 cs = get_Cs(L, a_, b_);
+	float C_0 = cs.x;
+	float C_mid = cs.y;
+	float C_max = cs.z;
+	float mid = 0.8f;
+	float mid_inv = 1.25f;
+	float C, t, k_0, k_1, k_2;
+	if (s < mid){
+		t = mid_inv * s;
+		k_1 = mid * C_0;
+		k_2 = (1.f - k_1 / C_mid);
+		C = t * k_1 / (1.f - k_2 * t);
+	}
+	else{
+		t = (s - mid)/ (1.f - mid);
+		k_0 = C_mid;
+		k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
+		k_2 = (1.f - (k_1) / (C_max - C_mid));
+		C = k_0 + t * k_1 / (1.f - k_2 * t);
+	}
+	return oklab_to_xyz(vec3( L, C * a_, C * b_ ));
+}
 vec3 srgb_to_okhsl(vec3 rgb){
 	vec3 lab = linear_srgb_to_oklab(vec3(
 		srgb_transfer_function_inv(rgb.r),
@@ -397,4 +485,28 @@ vec3 srgb_to_okhsv(vec3 rgb){
 	float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
 	return vec3 (h, s, v );
 }
-)";
+vec3 sRGB2XYZ(vec3 color){
+	mat3 mat=mat3(vec3(0.4124564,0.3575761,0.1804375),
+				  vec3(0.2126729,0.7151522,0.0721750),
+				  vec3(0.0193339,0.1191920,0.9503041));
+	return color*mat;
+}
+vec3 Clay2XYZ(vec3 color){
+	mat3 mat=mat3(vec3(0.5767309,0.1855540,0.1881852),
+				  vec3(0.2973769,0.6273491,0.0752741),
+				  vec3(0.0270343,0.0706872,0.9911085));
+	return color*mat;
+}
+vec3 XYZ2sRGB(vec3 xyz){
+	mat3 mat=mat3(vec3(3.2404542,-1.5371385,-0.4985314),
+				  vec3(-0.9692660,1.8760108,0.0415560),
+				  vec3(0.0556434,-0.2040259,1.0572252));
+	return xyz*mat;
+}
+vec3 XYZ2Clay(vec3 xyz){
+	mat3 mat=mat3(vec3(2.0413690,-0.5649464,-0.3446944),
+				  vec3(-0.9692660,1.8760108,0.0415560),
+				  vec3(0.0134474,-0.1183897,1.0154096));
+	return xyz*mat;
+}
+)";

+ 12 - 1
resources/la_widgets.c

@@ -781,7 +781,7 @@ void la_EnumSelectorDraw(laUiItem *ui, int h){
                 tnsDrawStringAuto(buf, laThemeColor(bt, LA_BT_TEXT|ExtraState), _L+bt->LM+(HasIcon?LA_RH:0), _R-bt->RM, _U, UseFlags);
             }
             if (!IsExpand && !IsVertical && !IconOnly && !IsCycle && !NoEvent){
-                tnsDrawIcon(L'', laThemeColor(bt, LA_BT_TEXT|ExtraState), _R-LA_RH, _R, _U, ui->Flags);
+                tnsDrawIcon(L'🔻', laThemeColor(bt, LA_BT_TEXT|ExtraState), _R-LA_RH, _R, _U, ui->Flags);
             }
         }
     }
@@ -1076,6 +1076,14 @@ void la_ColorCircleDrawHCY(laUiItem *ui, int h){
 
     tnsFlush();
 
+    tnsUniformShowColorOverflowStripes(T->immShader,1);
+
+    if(ui->Flags&LA_UI_FLAGS_COLOR_SPACE_CLAY){
+        tnsUniformInputColorSpace(T->immShader,TNS_COLOR_SPACE_CLAY);
+    }else{
+        tnsUniformInputColorSpace(T->immShader,TNS_COLOR_SPACE_SRGB);
+    }
+
     tnsUseNoTexture();
     tnsVectorSet4(&colors[576],1,1,hcy[2],1);
     tnsVectorSet4(&colors[580],1,0,hcy[2],1);
@@ -1141,6 +1149,9 @@ void la_ColorCircleDrawHCY(laUiItem *ui, int h){
         tnsDrawStringAuto("◿",laThemeColor(bt,LA_BT_BORDER),ui->R-LA_RH, ui->R, ui->B-bt->BM-LA_RH, LA_TEXT_ALIGN_CENTER);
 
     tnsFlush();
+
+    tnsUniformShowColorOverflowStripes(T->immShader,0);
+    tnsUniformInputColorSpace(T->immShader,0);
 }
 void la_MenuItemDraw(laUiItem *ui, int h){
     laBoxedTheme *bt = (*ui->Type->Theme);