#!/usr/bin/python

from __future__ import nested_scopes

import math
import string

def huemod (hue):
    if hue is None: return None
    while hue < 0: hue = hue + 6
    while hue >= 6: hue = hue - 6
    return hue

def hueadd (h1, h2):
    if h1 is None or h2 is None: return None
    return huemod(h1+h2)

def rgb2xyz(rgb):
    (r,g,b) = rgb
    x = 0.412453 * r + 0.357580 * g + 0.180423 * b
    y = 0.212671 * r + 0.715160 * g + 0.072169 * b
    z = 0.019334 * r + 0.119193 * g + 0.950227 * b
    return (x,y,z)

def xyz2rgb(xyz):
    (x,y,z) = xyz
    r =  3.240479 * x - 1.537150 * y - 0.498535 * z
    g = -0.969256 * x + 1.875992 * y + 0.041556 * z
    b =  0.055648 * x - 0.204043 * y + 1.057311 * z
    return (r,g,b)

def xyz2yuv(xyz):
    (X,Y,Z) = xyz
    d = X+15*Y+3*Z
    if (d<=0.0): d=1.0
    return (Y, 4*X/d, 9*Y/d)

def yuv2xyz(yuv):
    (Y,u,v) = yuv
    if (v > 0): d = 9*Y/v
    else:       d = 0
    X = 0.25*d*u
    Z = (d-X-15*Y)/3
    return (X,Y,Z)

white = xyz2yuv(rgb2xyz((1,1,1)))

def xyz2luv(xyz):
    (Y,u,v) = xyz2yuv(xyz)
    if (Y > 0.008856):
        l_ = 116*math.pow((Y/white[0]),(1/3.))-16
    else:
        l_ = 903.29*Y
    u_ = 13*l_*(u-white[1])
    v_ = 13*l_*(v-white[2])
    return (l_,u_,v_)

def luv2lhc(luv):
    (l,u,v) = luv
    if (l > 0):
        h = huemod(math.atan2(v,u)/math.pi*3)
        c = math.sqrt(u*u+v*v)/l
    else:
        h = 0.0
        c = 0.0
    return (l,h,c)

def lhc2luv(lhc):
    (l,h,c) = lhc
    u = math.cos((h)/3*math.pi)*c*l
    v = math.sin((h)/3*math.pi)*c*l
    return (l,u,v)

def luv2xyz(luv):
    (l_,u_,v_) = luv
    if (l_>8):
        Y = (math.pow((l_+16)/116.0,3.0))*white[0]
    else:
        Y = l_/903.29;
    if l_ < 1:
        l_ = 1
    u = u_/(13*l_)+white[1]
    v = v_/(13*l_)+white[2]
    return yuv2xyz((Y,u,v))

def rotatehue(lhc,rot):
    return lhc[0],huemod(lhc[1]+rot),lhc[2]

def rgb2lhc(rgb):
    return luv2lhc(xyz2luv(rgb2xyz(rgb)))

def lhc2rgb(lhc):
    return xyz2rgb(luv2xyz(lhc2luv(lhc)))

def rotate(rgb,rot):
    return lhc2rgb(rotatehue(rgb2lhc(rgb),rot))

def dim(rgb,factor):
    lhc = rgb2lhc(rgb)
    return lhc2rgb(((lhc[0]*factor),lhc[1],lhc[2]))

def saturate(rgb,factor):
    lhc = rgb2lhc(rgb)
    return lhc2rgb((lhc[0],lhc[1],(lhc[2]*factor)))

def rgb2luv(rgb):
    return xyz2luv(rgb2xyz(rgb))

def luv2rgb(luv):
    return xyz2rgb(luv2xyz(luv))

def outofgamut(rgb):
    r,g,b = rgb
    if r < -0.5/255 or r > 1+0.5/255: return 1
    if g < -0.5/255 or g > 1+0.5/255: return 1
    if b < -0.5/255 or b > 1+0.5/255: return 1
    return None

def s255(x):
    x = int(x*255+0.5)
    if x < 0: x = 0
    if x > 255: x = 255
    return x

def rgb2hex (rgb):
    r,g,b = rgb
    r = s255(r)
    g = s255(g)
    b = s255(b)
    return "%02x%02x%02x" % (r,g,b)

def scaledrgb (rgb):
    r,g,b = rgb
    r = s255(r)
    g = s255(g)
    b = s255(b)
    return (r,g,b)

def index2lhc (index):
    if indexlhc.has_key(index):
        return indexlhc[index]
    lhc = rgb2lhc(index)
    indexlhc[index] = lhc
    return lhc

def huediff (h1, h2):
    d1 = abs(h1-h2)
    d2 = abs(h1-h2+6)
    d3 = abs(h1-h2-6)
    return min(d1,d2,d3)

def rgbblend(rgb1,rgb2,pct):
    rgb = (rgb1[0] * (1-pct) + rgb2[0] * pct,
           rgb1[1] * (1-pct) + rgb2[1] * pct,
           rgb1[2] * (1-pct) + rgb2[2] * pct)
    return rgb

huedata = [('red',       (1.00,0.00,0.00)),
           ('coral',     (1.00,0.75,0,00)),
           ('orange',    (1.00,0.50,0.00)),
           ('tangerine', (1.00,0.25,0.00)),
           ('yellow',    (1.00,1.00,0.00)),
           ('peridot',   (0.75,1.00,0.00)),
           ('spring',    (0.50,1.00,0.00)), # "lime"
           ('apple',     (0.25,1.00,0.00)),
           ('green',     (0.00,1.00,0.00)),
           ('leaf',      (0.00,1.00,0.25)),
           ('teal',      (0.00,1.00,0.50)), # "aquamarine"
           ('turquoise', (0.00,1.00,0.75)),
           ('cyan',      (0.00,1.00,1.00)),
           ('sky',       (0.00,0.75,1.00)),
           ('azure',     (0.00,0.50,1.00)), # "cobalt blue"
           ('indigo',    (0.00,0.25,1.00)),
           ('blue',      (0.00,0.00,1.00)), # "blue-violet"
           ('plum',      (0.25,0.00,1.00)),
           ('violet',    (0.50,0.00,1.00)), # "purple"
           ('mallow',    (0.75,0.00,1.00)),
           ('magenta',   (1.00,0.00,1.00)),
           ('rhodium',   (1.00,0.00,0.75)),
           ('pink',      (1.00,0.00,0.50)), # "raspberry"
           ('mulberry',  (1.00,0.00,0.25))]

hues = []
for h in huedata:
    lhc = rgb2lhc(h[1])
    hues.append((h[0],lhc))

def colorname (lhc):
    (val,hue,sat) = lhc
    return huename(hue)

def huename (hue):
    if hue is None: return "gray"
    best1, best2 = None, None
    for h in hues:
        hd = huediff(hue, h[1][1])
        if best1 is None or best1[0] > hd:
            best2 = best1
            best1 = (hd,h[0])
        if (best2 is None or best2[0] > hd) and best1[0] < hd:
            best2 = (hd,h[0])
    if best2[0] < 2*best1[0]:
        return best1[1] + '-' + best2[1]
    return best1[1]

def hex2rgb(s):
    try:
        if s[0:1] == '#': s = s[1:]
	rgb = map(lambda s: string.atoi(s,16), (s[0:2], s[2:4], s[4:6]))
        return (rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)
    except:
        for h in huedata:
            if h[0] == s: return h[1]
	return None

def dandc (func, x1, x2, diff):
    while (x2 - x1 > diff):
        x = (x2 + x1) / 2
        if func(x): x2 = x
        else: x1 = x
    return x1

def satcheck (L):
    return lambda x: outofgamut(lhc2rgb((L[0],L[1],x)))

def lumcheck (L):
    return lambda x: outofgamut(lhc2rgb((x,L[1],L[2])))

def maxsat (L):
    maxs = dandc (satcheck(L), 0.0, 8.0, 0.001)
    return (L[0],L[1],maxs)

def maxlum (L):
    maxl = dandc (lumcheck(L), 0.0, 125.0, 0.1)
    return (maxl,L[1],L[2])


    
