Commit 5bd9d67b0 for imagemagick.org

commit 5bd9d67b078c57a4460794abfb0bac0377426cb3
Author: Cristy <urban-warrior@imagemagick.org>
Date:   Tue Mar 3 19:22:20 2026 -0500

    https://github.com/ImageMagick/ImageMagick/issues/8589

diff --git a/MagickCore/color.c b/MagickCore/color.c
index 20fdd6ce6..651695012 100644
--- a/MagickCore/color.c
+++ b/MagickCore/color.c
@@ -1191,6 +1191,8 @@ MagickExport void ConcatenateColorComponent(const PixelInfo *pixel,
         scale=100.0;
       if (pixel->colorspace == XYZColorspace)
         color/=2.55;
+      if (pixel->colorspace == OklchColorspace)
+        scale=1.0;
       break;
     }
     case GreenPixelChannel:
@@ -1203,6 +1205,8 @@ MagickExport void ConcatenateColorComponent(const PixelInfo *pixel,
         color-=QuantumRange/2.0;
       if (pixel->colorspace == XYZColorspace)
         color/=2.55;
+      if (pixel->colorspace == OklchColorspace)
+        scale=1.0;
       break;
     }
     case BluePixelChannel:
@@ -1218,6 +1222,8 @@ MagickExport void ConcatenateColorComponent(const PixelInfo *pixel,
         color*=360.0/255.0;
       if (pixel->colorspace == XYZColorspace)
         color/=2.55;
+      if (pixel->colorspace == OklchColorspace)
+        scale=1.0;
       break;
     }
     case AlphaPixelChannel:
@@ -1240,13 +1246,17 @@ MagickExport void ConcatenateColorComponent(const PixelInfo *pixel,
     default:
       break;
   }
-  if ((scale != 100.0) ||
-      (IsLabCompatibleColorspace(pixel->colorspace) != MagickFalse))
+  if ((pixel->colorspace == OklchColorspace) && (channel == BluePixelChannel))
     (void) FormatLocaleString(component,MagickPathExtent,"%.*g",
-      GetMagickPrecision(),scale*QuantumScale*color);
+      GetMagickPrecision(),RadiansToDegrees(scale*QuantumScale*color));
   else
-    (void) FormatLocaleString(component,MagickPathExtent,"%.*g%%",
-      GetMagickPrecision(),scale*QuantumScale*color);
+    if ((scale != 100.0) ||
+        (IsLabCompatibleColorspace(pixel->colorspace) != MagickFalse))
+      (void) FormatLocaleString(component,MagickPathExtent,"%.*g",
+        GetMagickPrecision(),scale*QuantumScale*color);
+      else
+        (void) FormatLocaleString(component,MagickPathExtent,"%.*g%%",
+          GetMagickPrecision(),scale*QuantumScale*color);
   (void) ConcatenateMagickString(tuple,component,MagickPathExtent);
 }

@@ -2509,29 +2519,42 @@ MagickExport MagickBooleanType QueryColorCompliance(const char *name,
           colorname=DestroyString(colorname);
           return(status);
         }
-      if (IsLabCompatibleColorspace(color->colorspace) != MagickFalse)
-        {
-          color->red=(MagickRealType) ClampToQuantum((MagickRealType)
-            ((double) QuantumRange*geometry_info.rho/100.0));
-          if ((flags & SigmaValue) != 0)
-            color->green=(MagickRealType) ClampToQuantum((MagickRealType)
-              (scale*geometry_info.sigma+((double) QuantumRange+1)/2.0));
-          if ((flags & XiValue) != 0)
-            color->blue=(MagickRealType) ClampToQuantum((MagickRealType)
-              (scale*geometry_info.xi+((double) QuantumRange+1)/2.0));
-        }
+      if (color->colorspace == OklchColorspace)
+         {
+           if ((flags & RhoValue) != 0)
+             color->red=(MagickRealType) ClampToQuantum((double) QuantumRange*
+                geometry_info.rho);
+           if ((flags & SigmaValue) != 0)
+             color->green=(MagickRealType) ClampToQuantum((double) QuantumRange*
+                geometry_info.sigma);
+           if ((flags & XiValue) != 0)
+             color->blue=(MagickRealType) ClampToQuantum((double) QuantumRange*
+                DegreesToRadians(geometry_info.xi));
+         }
       else
-        {
-          if ((flags & RhoValue) != 0)
-            color->red=(double) ClampToQuantum((MagickRealType) (scale*
-              geometry_info.rho));
-          if ((flags & SigmaValue) != 0)
-            color->green=(double) ClampToQuantum((MagickRealType) (scale*
-              geometry_info.sigma));
-          if ((flags & XiValue) != 0)
-            color->blue=(double) ClampToQuantum((MagickRealType) (scale*
-              geometry_info.xi));
-        }
+        if (IsLabCompatibleColorspace(color->colorspace) != MagickFalse)
+          {
+            color->red=(MagickRealType) ClampToQuantum((MagickRealType)
+              ((double) QuantumRange*geometry_info.rho/100.0));
+            if ((flags & SigmaValue) != 0)
+              color->green=(MagickRealType) ClampToQuantum((MagickRealType)
+                (scale*geometry_info.sigma+((double) QuantumRange+1)/2.0));
+            if ((flags & XiValue) != 0)
+              color->blue=(MagickRealType) ClampToQuantum((MagickRealType)
+                (scale*geometry_info.xi+((double) QuantumRange+1)/2.0));
+          }
+        else
+          {
+            if ((flags & RhoValue) != 0)
+              color->red=(double) ClampToQuantum((MagickRealType) (scale*
+                geometry_info.rho));
+            if ((flags & SigmaValue) != 0)
+              color->green=(double) ClampToQuantum((MagickRealType) (scale*
+                geometry_info.sigma));
+            if ((flags & XiValue) != 0)
+              color->blue=(double) ClampToQuantum((MagickRealType) (scale*
+                geometry_info.xi));
+          }
       if ((flags & AlphaValue) != 0)
         color->alpha_trait=BlendPixelTrait;
       color->alpha=(double) OpaqueAlpha;
diff --git a/MagickCore/colorspace-private.h b/MagickCore/colorspace-private.h
index 3eb9d6d11..b51fd107a 100644
--- a/MagickCore/colorspace-private.h
+++ b/MagickCore/colorspace-private.h
@@ -1541,15 +1541,29 @@ static inline void ConvertOklabToRGB(const double L,const double a,
 }

 static inline void ConvertOklchToRGB(const double L,const double C,
-  const double h,double *red,double *green,double *blue)
+  const double H,double *red,double *green,double *blue)
 {
-  double
-    a,
-    b;
+  /* OKLCH → OKLab */
+  const double a = C*cos(H);
+  const double b_ = C*sin(H);
+
+  /* OKLab → nonlinear LMS */
+  const double l_ = L+Oklab_la*a+Oklab_lb*b_;
+  const double m_ = L+Oklab_ma*a+Oklab_mb*b_;
+  const double s_ = L+Oklab_sa*a+Oklab_sb*b_;
+
+  /* Undo nonlinear transform */
+  const double l = l_*l_*l_;
+  const double m = m_*m_*m_;
+  const double s = s_*s_*s_;

-  a=C*cos(2.0*MagickPI*h);
-  b=C*sin(2.0*MagickPI*h);
-  ConvertOklabToRGB(L,a,b,red,green,blue);
+  /* LMS → linear RGB */
+  *red=EncodePixelGamma((double) QuantumRange*(Oklab_Rl*l+Oklab_Rm*m+
+    Oklab_Rs*s));
+  *green=EncodePixelGamma((double) QuantumRange*(Oklab_Gl*l+Oklab_Gm*m+
+    Oklab_Gs*s));
+  *blue=EncodePixelGamma((double) QuantumRange*(Oklab_Bl*l+Oklab_Bm*m+
+    Oklab_Bs*s));
 }

 static inline void ConvertRGBToOklab(const double red,const double green,
@@ -1615,15 +1629,38 @@ static inline void ConvertRGBToOklab(const double red,const double green,
 }

 static inline void ConvertRGBToOklch(const double red,const double green,
-  const double blue,double *L,double *C,double *h)
-{
-  double
-    a,
-    b;
-
-  ConvertRGBToOklab(red,green,blue,L,&a,&b);
-  *C=sqrt(a*a+b*b);
-  *h=0.5+0.5*atan2(-b,-a)/MagickPI;
+  const double blue,double *L,double *C,double *H)
+{
+  /* Linear RGB → LMS */
+  const double l = Oklab_lR*QuantumScale*DecodePixelGamma(red)+
+    Oklab_lG*QuantumScale*DecodePixelGamma(green)+
+    Oklab_lB*QuantumScale*DecodePixelGamma(blue);
+  const double m = Oklab_mR*QuantumScale*DecodePixelGamma(red)+
+    Oklab_mG*QuantumScale*DecodePixelGamma(green)+
+    Oklab_mB*QuantumScale*DecodePixelGamma(blue);
+  const double s = Oklab_sR*QuantumScale*DecodePixelGamma(red)+
+    Oklab_sG*QuantumScale*DecodePixelGamma(green)+
+    Oklab_sB*QuantumScale*DecodePixelGamma(blue);
+
+  /* Nonlinear transform */
+  const double l_ = cbrt(l);
+  const double m_ = cbrt(m);
+  const double s_ = cbrt(s);
+
+  /* LMS → OKLab */
+  const double L_ok = Oklab_Ll*l_+Oklab_Lm*m_+Oklab_Ls*s_;
+  const double a_ok = Oklab_al*l_+Oklab_am*m_+Oklab_as*s_;
+  const double b_ok = Oklab_bl*l_+Oklab_bm*m_+Oklab_bs*s_;
+
+  /* OKLab → OKLCH */
+  const double C_ok = sqrt(a_ok*a_ok+b_ok*b_ok);
+  double H_ok = atan2(b_ok,a_ok);  /* radians */
+
+  if (H_ok < 0.0)
+    H_ok+=2.0*M_PI;
+  *L=L_ok;
+  *C=C_ok;
+  *H=H_ok;
 }

 static inline void ConvertRGBToYDbDr(const double red,const double green,
@@ -1817,7 +1854,7 @@ static inline MagickBooleanType IsLabCompatibleColorspace(
 {
   if ((colorspace == LabColorspace) || (colorspace == LCHColorspace) ||
       (colorspace == LCHabColorspace) || (colorspace == LCHuvColorspace) ||
-      (colorspace == OklabColorspace) || (colorspace == OklchColorspace))
+      (colorspace == OklabColorspace))
     return(MagickTrue);
   return(MagickFalse);
 }