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);
}