# Implementation of module {palette}.
# Last edited on 2021-02-18 21:26:40 by jstolfi

import palette
import color
import rn
import sys
from math import sqrt, hypot, log, exp, sin, cos, floor, ceil, inf, nan, pi

def smooth_single(x, x0,RGB0, x1,RGB1, blog):
  dtot = x1 - x0
  assert dtot != 0, "end values mut be different"
  r = (x - x0)/dtot
  if r <= 0:
    RGB = RGB0
  elif r >= 1:
    RGB = RGB1
  else:
    if blog != 0:
      # Use log scale:
      runit = abs(blog/dtot)
      if r <= runit:
        r = 0
      else:
        r = log(runit/r)/log(runit)
        assert 0 <= r and r <= 1
    # Interpolate with ratio {r}:
    RGB = color.interp_vis_RGB(r, RGB0, RGB1)
  return RGB
  # ----------------------------------------------------------------------

def smooth_double(x, xmin, RGBmin, xzer, eps, RGBzer, xmax, RGBmax, ulog):
  assert xmin <= xzer-eps
  assert xmax >= xzer+eps
  if abs(x-xzer) < eps:
    RGB = RGBzer
  elif x <= xmin:
    RGB = RGBmin
  elif x >= xmax:
    RGB = RGBmax
  else:
    # Must interpolate. 
    assert x != xzer
    # Define the endpoint away from {xzer}, and sign {sgn} of {x-xzer}
    if x < xzer:
      x1 = xmin; RGB1 = RGBmin; sgn = -1
    elif x > xzer:
      x1 = xmax; RGB1 = RGBmax; sgn = +1
    # Define the endpoint near {xzer}
    RGB0 = RGBzer;
    if ulog:
      x0 = xzer; blog = eps
    else:
      x0 = xzer + sgn*eps; blog = 0
    RGB = smooth_single(x, x0,RGB0, x1,RGB1, blog)
  return RGB
  # ----------------------------------------------------------------------

def discrete(k, Ymin, Ymax, Smin, Smax):
  assert 0 <= Ymin and Ymin <= Ymax and Ymax <= 1.000, "invalid {Ymin,Ymax}"
  assert 0 <= Smin and Smin <= Smax and Smax <= 1.000, "invalid {Smin,Smax}"

  # Point on the torus, hopefully far away from other colors:
  phi = (sqrt(5)-1)/2
  azm = k*phi*2*pi    # Azimuth, longitude: angle on the medial circle.
  ele = k             # Elevation, latitude: angle on meridian circles.
  
  # Compute the {Y} of the ideal color, in log scale:
  rYi = (1 + sin(ele))/2
  Ybias = 0.010
  Yi = exp((1-rYi)*log(Ymin+Ybias) + rYi*log(Ymax+Ybias)) - Ybias
  Yi = min(1, max(0, Yi)) # Just in case.
  assert Ymin <= Yi and Yi <= Ymax

  # Compute the {Y} and {S} of the ideal color, in log scale:
  rSi = (1 + cos(ele))/2
  Sbias = 0.010
  Si = exp((1-rSi)*log(Smin+Sbias) + rSi*log(Smax+Sbias)) - Sbias
  Si = max(0, Si) # Just in case.

  # Compute the ideal color, {YUV} and {RGB} coords:
  YUVi = (Yi, Si*cos(azm), Si*sin(azm))
  RGBi = color.RGB_from_YUV(YUVi)
  
  # Computes the color {YUVz,RGBz} with same {Y} and hue but with max saturation:
  Sz = Smax
  YUVz = (Yi, Sz*cos(azm), Sz*sin(azm))
  RGBz = color.RGB_from_YUV(YUVz)
  
  # Compute the scaling factor {f} to bring {YUVz} into the unit {RGB} cube:
  f = color.sat_clip_factor(RGBz)
  
  # Now use {f} to correct the ideal color:
  RGB = tuple( min(1, max(0, Yi + f*(RGBi[i] - Yi))) for i in range(3) )
  return RGB
  # ----------------------------------------------------------------------
 
