Commits

Robert Kern committed 3ea5ebe

Colorspace conversion cleanup.

Comments (0)

Files changed (1)

colormap_explorer/conversion.py

 
 #### Conversion routines #######################################################
 
-def xyz2lab(xyz, axis=-1, wp=whitepoints['D65'][-1], doclip=True):
+def xyz2lab(xyz, axis=-1, wp=whitepoints['D65'][-1]):
     """ Convert XYZ tristimulus values to CIE L*a*b*.
 
     Parameters
     L = 116*fy - 16
     a = 500*(fx - fy)
     b = 200*(fy - fz)
-    if doclip:
-        L = np.clip(L, 0.0, 100.0)
-        a = np.clip(a, -500.0, 500.0)
-        b = np.clip(b, -200.0, 200.0)
 
     return join_colors(L,a,b,axis)
 
     denominator = (x + 15*y + 3*z)
     zeros = (denominator == 0.0)
     denominator = np.where(zeros, 1.0, denominator)
-    # I'm not entirely sure about these defaults when X=Y=Z=0.
-    u_numerator = np.where(zeros, 4.0, 4*x)
+    u_numerator = np.where(zeros, 4.0, 4 * x)
     v_numerator = np.where(zeros, 9.0, 9 * y)
 
     return u_numerator/denominator, v_numerator/denominator
     unp, vnp = _uv(*wp)
     small_mask = (Ls <= 903.3 * 0.008856)
     y = wp[1] * ((Ls + 16.0) / 116.0) ** 3
-    y[small_mask] = Ls * wp[1] / 903.0
-    up = us / (13*Ls) + us
-    vp = vs / (13*Ls) + vs
+    y[small_mask] = Ls[small_mask] * wp[1] / 903.0
+    # Where L==0, X=Y=Z=0. Avoid the nans and infs in the meantime.
+    black = (Ls == 0)
+    Ls[black] = 1.0
+
+    up = us / (13*Ls) + unp
+    vp = vs / (13*Ls) + vnp
     x = 9.0 * y * up / (4.0 * vp)
     z = -x / 3.0 - 5.0 * y + 3.0 * y/vp
 
+    x[black] = 0.0
+    y[black] = 0.0
+    z[black] = 0.0
+
     return join_colors(x, y, z, axis)
 
 
+# HCL (Hue - Chroma - Luminance) is the cylindrical-coordinate form of Luv (or
+# Lab, but for the purpose of this app, Luv is more suitable).
+
+def xyz2hcl(xyz, axis=-1, wp=whitepoints['D65'][-1], luvlab='luv'):
+    """ Convert XYZ tristimulus values to HCL.
+
+    Parameters
+    ----------
+    xyz : float array
+        XYZ values.
+    axis : int, optional
+        The axis of the XYZ values.
+    wp : list of 3 floats, optional
+        The XYZ tristimulus values of the whitepoint.
+    luvlab : 'luv' or 'lab', optional
+        Whether to use the L*u*v* or L*a*b*.
+
+    Returns
+    -------
+    hcl : float array
+        The HCL colors.
+    """
+    if luvlab == 'luv':
+        xyz2lxx = xyz2luv
+    else:
+        xyz2lxx = xyz2lab
+
+    Ls, x1, x2, axis = separate_colors(xyz2lxx(xyz, axis=axis, wp=wp), axis)
+    Cs = np.hypot(x1, x2)
+    Hs = (180. / np.pi) * np.arctan2(x2, x1)
+
+    return join_colors(Hs, Cs, Ls, axis)
+
+
+def hcl2xyz(hcl, axis=-1, wp=whitepoints['D65'][-1], luvlab='luv'):
+    """ Convert HCL values to XYZ tristimulus values.
+
+    Parameters
+    ----------
+    hcl : float array
+        The HCL colors.
+    axis : int, optional
+        The axis of the XYZ values.
+    wp : list of 3 floats, optional
+        The XYZ tristimulus values of the whitepoint.
+    luvlab : 'luv' or 'lab', optional
+        Whether to use the L*u*v* or L*a*b*.
+
+    Returns
+    -------
+    xyz : float array
+        XYZ values.
+    """
+    if luvlab == 'luv':
+        lxx2xyz = luv2xyz
+    else:
+        lxx2xyz = lab2xyz
+
+    Hs, Cs, Ls, axis = separate_colors(hcl, axis)
+    theta = Hs * (np.pi / 180)
+    x1 = Cs * np.cos(theta)
+    x2 = Cs * np.sin(theta)
+
+    return lxx2xyz(join_colors(Ls, x1, x2, axis), axis=axis, wp=wp)
+
+
 #  RGB values that will be displayed on a screen are always nonlinear
 #  R'G'B' values.  To get the XYZ value of the color that will be
 #  displayed you need a calibrated monitor with a profile.