import 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)