rendered paste bodyimport Image
import math
def angle_normalize(angle):
return(angle % 360) # uhh, DUH
#while angle >= 360: angle -= 360
#while angle < 0: angle += 360
#return(angle)
# Fixme: add/multiply functions?
# in the interest of speed, we do not calculate HSV values at construction
class RGB:
# Probably works best for r,g,b in [0,1]
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
def blend(self, color2, amount):
r = (1-amount)*self.r + amount*color2.r
g = (1-amount)*self.g + amount*color2.g
b = (1-amount)*self.b + amount*color2.b
def toRGB(self): return self
def __add__(self, color2):
return RGB(self.r + color2.r, self.g + color2.g, self.b + color2.b)
def __repr__(self):
return "RGB<%0.2f %.2f %.2f>" % (self.r, self.g, self.b)
def __sub__(self, color2):
return RGB(self.r - color2.r, self.g - color2.g, self.b - color2.b)
def __mul__(self, scalar):
return RGB(self.r * scalar, self.g * scalar, self.b * scalar)
def __div__(self, scalar):
return RGB(self.r / scalar, self.g / scalar, self.b / scalar)
def inverse(self):
return RGB(1-self.r,1-self.g,1-self.b)
# http://en.wikipedia.org/wiki/HSV_color_space
def toHSV(self):
max_c = max(self.r, self.g, self.b)
min_c = min(self.r, self.g, self.b)
if (self.r == max_c): h = 60.0*(self.g - self.b) / (max_c - min_c)
elif (self.g == max_c): h =60.0*(2 + (self.b - self.r) / (max_c - min_c))
elif (self.b == max_c): h = 60.0*(4 + (self.r - self.g) / (max_c - min_c))
s = (max_c - min_c) / max_c
v = max_c
return HSV(h,s,v)
def discrete_tuple(self, max_sample):
return(int(self.r*max_sample), int(self.g*max_sample), int(self.b*max_sample))
# Fixme: add/multiply functions?
class HSV:
def __init__(self, h, s, v):
self.h = h
self.s = s
self.v = v
# http://en.wikipedia.org/wiki/HSV_color_space
# not quite as readable as my homebrew algorithm
def toRGB(self):
if (self.s == 0.0): return RGB(self.v, self.v, self.v)
else:
h = angle_normalize(self.h)
h_i = math.floor(h / 60.0)
f = h/60.0 - h_i
p = self.v * (1 - self.s)
q = self.v * (1 - f * self.s)
t = self.v * (1 - (1 - f) * self.s)
if h_i == 0: return RGB(self.v, t, p)
elif h_i == 1: return RGB(q, self.v, p)
elif h_i == 2: return RGB(p, self.v, t)
elif h_i == 3: return RGB(p, q, self.v)
elif h_i == 4: return RGB(t, p, self.v)
elif h_i == 5: return RGB(self.v, p, q)
def toHSV(self): return self
def complement(self):
return HSV(angle_normalize(180 + self.h), self.s, self.v)
def __repr__(self):
return "HSV<%ideg %.2f %.2f>" % (self.h, self.s, self.v)
def __add__(self, color2):
return (self.toRGB() + color2.toRGB()).toHSV()
def __sub__(self, color2):
return (self.toRGB() - color2.toRGB()).toHSV()
def __mul__(self, scalar):
return (self.toRGB() * scalar).toHSV()
def __div__(self, scalar):
return (self.toRGB() / scalar).toHSV()
def inverse(self):
return (self.toRGB().inverse()).toHSV()
def hsv_add(self, color2):
return HSV(angle_normalize(self.h + color2.h), self.s + color2.s, self.v + color2.v)
def hsv_mult(self, scalar):
return HSV(angle_normalize(self.h*scalar), self.s*scalar, self.v*scalar)
# Fixme - issues if it's blending, say, 0 and 359 degrees
# def blend(self, color2, n):
# return(HSV(self.h*(1-n) + color2.h*n, self
# (independent, dependent)
class Map:
def __init__(self, start, end):
self.slope = (float(end.y) - start.y) / (end.x - start.x)
self.start = start
self.end = end
def fn(self, hue):
return self.slope*(hue - start.x) + start.y
class Map2:
def __init__(self, start_x, end_x, start_y, end_y):
self.start_x = start_x
self.end_x = end_x
self.start_y = start_y
self.end_y = end_y
self.slope = (float(start_y - end_y) / (start_x - end_x))
def fn(self, x):
return self.slope * (x - self.start_x) + self.start_y
# This is so much more coherent....
# Define a map, add items with .add(x1, x2, y1, y2) - it will handle overlaps for you.
class MapConstruct:
def __init__(self):
self.maps = []
def add(self, start_x, end_x, start_y, end_y):
mapped = False
for old_map in self.maps:
# (1) Add the new map. It overlaps, but we're fixing that.
# (2) Add another map which starts right where (1) ends, and
# ends where the old map (the one we're on top of) ends.
# (3) Change the old map underneath everything so that it ends
# where (1) begins.
if (start_x >= old_map.start_x) and (start_x < old_map.end_x):
mapped = True
self.maps.append(Map2(start_x, end_x, start_y, end_y))
self.maps.append( Map2(end_x, old_map.end_x, old_map.fn(end_x), old_map.fn(old_map.end_x)) )
old_map.end_x = start_x
break
if (not mapped): self.maps.append(Map2(start_x, end_x, start_y, end_y))
def fn(self, x):
found = False
for map in self.maps:
if (x >= map.start_x) and (x < map.end_x):
return map.fn(x)
break
if(not found):
print "Not found: %s" % x
return 0
# For speed, these maps are very simple and not too tolerant of users trying to taunt them.
# If they overlap, it's anyone's guess which one wins.
class Hue_Map:
def __init__(self, maps, default_hue = 0):
self.maps = maps
self.parts = len(maps)
self.default_hue = default_hue # shouldn't be needed...
def fn(self, color):
mapped = color.toHSV()
mapped.h = angle_normalize(mapped.h)
new_hue = self.default_hue
for i in self.maps:
if (mapped.h >= i.start.x) and (mapped.h < i.end.x):
new_hue = i.slope*(mapped.h - i.start.x) + i.start.y
break
return HSV(new_hue, mapped.s, mapped.v)