# -*- coding: utf-8 -*- """ paint servers """ # imports #################################################################### # from ..opengl import gl as _gl # from ..opengl.utils import (get_opengl_version, set_uniform) # from ..opengl.utils import (create_shader, create_program) from ._common import _Element from .transform import Translate, Matrix, shrink # from .transform import Ortho, product # shaders #################################################################### _ATTRIB_LOCATIONS = { b"vertex": 0, } _VERT_SHADER = """ #version %(GLSL_VERSION)s #if __VERSION__ >= 150 #define attribute in #define varying out #endif attribute vec2 vertex; uniform vec3 color; uniform float alpha; uniform mat3 projection_transform; uniform mat3 modelview_transform; uniform mat3 paint_transform; uniform mat3 mask_transform; varying vec4 front_color; varying vec2 paint_coord; varying vec2 mask_coord; void main() { front_color = vec4(color, alpha); vec3 pixel_position = modelview_transform * vec3(vertex, 1.); paint_coord = (paint_transform * vec3(vertex, 1.)).xy; mask_coord = (mask_transform * pixel_position).xy; gl_Position = vec4((projection_transform * pixel_position).xy, 0., 1.); } """ _MAIN_FRAG_SHADER = """ #version %(GLSL_VERSION)s #if __VERSION__ >= 150 #define varying in #define texture2D texture #define gl_FragColor fragment_color out vec4 fragment_color; #endif uniform bool masking; uniform sampler2D mask; const vec4 luminance = vec4(.2125, .7154, .0721, 0.); varying vec4 front_color; varying vec2 mask_coord; vec4 color(); // filling color vec4 frag_color() { vec4 color = color(); if(masking) { color.a *= dot(luminance, texture2D(mask, mask_coord)); } return front_color * color; } void main() { gl_FragColor = frag_color(); } """ # filling fragment shaders ################################################### _SOLID_FRAG_SHADER = """ #version %(GLSL_VERSION)s vec4 color() { return vec4(1., 1., 1., 1.); } """ _TEXTURE_FRAG_SHADER = """ #version %(GLSL_VERSION)s #if __VERSION__ >= 150 #define varying in #define texture2D texture #endif uniform sampler2D texture; varying vec2 paint_coord; vec4 color() { return texture2D(texture, paint_coord); } """ MAX_STOPS = 21 _GRADIENT_FRAG_SHADER = """ #version %(GLSL_VERSION)s #if __VERSION__ >= 150 #define varying in #endif const int N = %(MAX_STOPS)s; uniform int n; uniform float os[N]; uniform vec4 colors[N]; uniform int spread; varying vec2 paint_coord; float o(vec2 p); // offset at point p in the gradient float s(float o) { // offset according to spread method float pad = o; float repeat = fract(o); float reflect = mod(o, 2.); if(reflect > 1.) reflect = 2.-reflect; return vec3(pad, reflect, repeat)[spread]; } vec4 gradient(float o) { // color at offset o in the gradient float oa, oi = os[0]; vec4 ca, ci = colors[0]; for(int i = 0; i < n; i++) { oa = os[i]; ca = colors[i]; if(o <= oa) break; oi = oa; ci = ca; } float ki = (oi == oa) ? 1. : (oa-o)/(oa-oi); return ki*ci + (1.-ki)*ca; } vec4 color() { float o = o(paint_coord); float s = s(o); return gradient(s); } """ _LINEAR_OFFSET_FRAG_SHADER = """ #version %(GLSL_VERSION)s uniform vec2 p1; uniform vec2 p2; float o(vec2 p) { // offset at point p in the linear gradient vec2 u = p - p1; vec2 v = p2 - p1; return dot(u, v)/dot(v, v); } """ _RADIAL_OFFSET_FRAG_SHADER = """ #version %(GLSL_VERSION)s uniform vec2 c; uniform float r; uniform vec2 f; float o(vec2 p) { // offset at point p in the radial gradient vec2 u = p - f; vec2 v = c - f; float a2 = dot(v, v) - r*r; float a1 = dot(u, v); float a0 = dot(u, u); if(a2 == 0.) { return .5*a0/a1; // at first order sqrt(1+x) == 1+x/2 } else { return (a1-sqrt(a1*a1-a2*a0))/a2; } } """ _PATTERN_FRAG_SHADER = """ #version %(GLSL_VERSION)s #if __VERSION__ >= 150 #define varying in #endif uniform vec2 origin; uniform vec2 period; varying vec2 paint_coord; vec4 color() { vec2 uv = mod(paint_coord + origin, period); if((uv.x-period.x/2.)*(uv.y-period.y/2.) < 0.) { return vec4(.75, .75, .75, 1.); } else { return vec4(1., 1., 1., 1.); } } """ # _shaders = { # "solid_color": [ # (_gl.VERTEX_SHADER, _VERT_SHADER), # (_gl.FRAGMENT_SHADER, _SOLID_FRAG_SHADER), # (_gl.FRAGMENT_SHADER, _MAIN_FRAG_SHADER), # ], # "texture": [ # (_gl.VERTEX_SHADER, _VERT_SHADER), # (_gl.FRAGMENT_SHADER, _TEXTURE_FRAG_SHADER), # (_gl.FRAGMENT_SHADER, _MAIN_FRAG_SHADER), # ], # "linear_gradient": [ # (_gl.VERTEX_SHADER, _VERT_SHADER), # (_gl.FRAGMENT_SHADER, _GRADIENT_FRAG_SHADER), # (_gl.FRAGMENT_SHADER, _LINEAR_OFFSET_FRAG_SHADER), # (_gl.FRAGMENT_SHADER, _MAIN_FRAG_SHADER), # ], # "radial_gradient": [ # (_gl.VERTEX_SHADER, _VERT_SHADER), # (_gl.FRAGMENT_SHADER, _GRADIENT_FRAG_SHADER), # (_gl.FRAGMENT_SHADER, _RADIAL_OFFSET_FRAG_SHADER), # (_gl.FRAGMENT_SHADER, _MAIN_FRAG_SHADER), # ], # "pattern": [ # (_gl.VERTEX_SHADER, _VERT_SHADER), # (_gl.FRAGMENT_SHADER, _PATTERN_FRAG_SHADER), # (_gl.FRAGMENT_SHADER, _MAIN_FRAG_SHADER), # ] # } # programs ################################################################### # _programs = {} # # # def _program(name): # global _programs # try: # program = _programs[name] # except KeyError: # shaders = list(create_shader(*shader, MAX_STOPS=MAX_STOPS) for shader in _shaders[name]) # program = _programs[name] = create_program(shaders, attrib_locations=_ATTRIB_LOCATIONS) # return program _current_kwargs = {} _current_program = None def _create(name, enable_sample_shading=True, **default_uniforms): # def set_sample_shading(): # """en/dis-able the sample shading # # this function specializes itself on first call to avoid checking opengl # version at each use. # """ # nonlocal set_sample_shading # if get_opengl_version() >= (4, 0): # if enable_sample_shading: # set_sample_shading = lambda: _gl.Enable(_gl.SAMPLE_SHADING) # else: # set_sample_shading = lambda: _gl.Disable(_gl.SAMPLE_SHADING) # else: # set_sample_shading = lambda: None # set_sample_shading() def _use(**kwargs): raise NotImplementedError # global _current_program, _current_uniforms # program = _program(name) # if _current_program != program: # _gl.UseProgram(program) # _current_program = program # _current_uniforms = {} # uniforms = dict(default_uniforms) # uniforms.update(kwargs) # uniforms["mask_transform"] = _MaskContext.transforms[-1].uniform() # uniforms["masking"] = [len(_MaskContext.textures) > 1] # for k in uniforms: # v = uniforms[k] # if v != _current_uniforms.get(k, None): # set_uniform(program, k, v) # _current_uniforms = uniforms # set_sample_shading() return _use _use_solid_color = _create("solid_color", mask=[1]) _use_texture = _create("texture", texture=[0], mask=[1], enable_sample_shading=False) _use_linear_gradient = _create("linear_gradient", mask=[1]) _use_radial_gradient = _create("radial_gradient", mask=[1]) _use_pattern = _create("pattern", mask=[1]) # painting ################################################################## # def _stencil_op(n, op): # _gl.StencilOp(_gl.KEEP, _gl.KEEP, op) # _gl.DrawArrays(_gl.TRIANGLE_STRIP, 0, n) # # def _make_stencil(op): # def _stencil(n): # _stencil_op(n, op) # return _stencil # # _stencil_one = _make_stencil(_gl.INCR) # _stencil_evenodd = _make_stencil(_gl.INVERT) # _stencil_replace = _make_stencil(_gl.REPLACE) # # def _stencil_nonzero(n): # _gl.Enable(_gl.CULL_FACE) # for cull, op in [(_gl.BACK, _gl.INCR_WRAP), # (_gl.FRONT, _gl.DECR_WRAP)]: # _gl.CullFace(cull) # _stencil_op(n, op) # _gl.Disable(_gl.CULL_FACE) # # # def _make_paint(_stencil): # def paint(color, alpha, data, transform, context, origin, bbox): # paint_transform = product(*color.transform).inverse() * \ # color.units(origin, bbox) # projection_transform = Ortho(*context.orthos[-1]) # color._use_program(color=[color.rgb], alpha=[float(alpha)], # modelview_transform=transform.uniform(), # paint_transform=paint_transform.uniform(), # projection_transform=projection_transform.uniform()) # n, vbo_id = data # _gl.BindBuffer(_gl.ARRAY_BUFFER, vbo_id) # _gl.VertexAttribPointer(_ATTRIB_LOCATIONS[b"vertex"], 2, _gl.FLOAT, # False, 0, None) # # for mask, func, stencil in [(_gl.FALSE, _gl.ALWAYS, _stencil), # (_gl.TRUE, _gl.NOTEQUAL, _stencil_replace)]: # _gl.ColorMask(mask, mask, mask, mask) # _gl.StencilFunc(func, 0, -1) # stencil(n) # return paint # paint base class ########################################################### def _object_bbox(origin, bbox): (x_min, y_min), (x_max, y_max) = bbox try: return shrink(x_min, y_min, x_max - x_min, y_max - y_min) except ZeroDivisionError: return Matrix() def _user_space(origin, bbox): return Translate(*origin) _UNITS = { "objectBoundingBox": _object_bbox, "userSpaceOnUse": _user_space, } class _Paint(_Element): _r, _g, _b = 1., 1., 1. units = staticmethod(_UNITS["objectBoundingBox"]) transform = [] def get_rgb(self): return self._r, self._g, self._b def set_rgb(self, rgb): self._r, self._g, self._b = rgb rgb = property(get_rgb, set_rgb) # paint_one = _make_paint(_stencil_one) # paint_evenodd = _make_paint(_stencil_evenodd) # paint_nonzero = _make_paint(_stencil_nonzero) # solid color ################################################################ _BASE = 255. def _float(i, base=_BASE): """convert byte color components to float""" return float(i / base if isinstance(i, int) else i) class Color(_Paint): tag = "solidColor" none = None current = "currentColor" _state_attributes = _Paint._state_attributes + [ "_r", "_g", "_b", ] def __init__(self, r=0., g=None, b=None, name=""): self._r = _float(r) self._g = _float(g) if g is not None else self._r self._b = _float(b) if b is not None else self._r self._name = name def _use_program(self, **kwargs): _use_solid_color(**kwargs) def _xml_attr(self, defs): return self._name or \ "#%02x%02x%02x" % tuple(int(v * _BASE) for v in self.rgb) # texture #################################################################### class _Texture(_Paint): def __init__(self, texture_id=0, color=Color(1., 1., 1.)): self.texture_id = texture_id self.get_rgb, self.set_rgb = color.get_rgb, color.set_rgb def _use_program(self, **kwargs): pass # _gl.BindTexture(_gl.TEXTURE_2D, self.texture_id) # _use_texture(**kwargs) class _MaskContext: textures = [0] transforms = [Matrix()] def __init__(self, origin, size, texture_id): x, y = origin width, height = size self.transform = shrink(x, y, width, height) self.texture_id = texture_id def __enter__(self): self.textures.append(self.texture_id) self.transforms.append(self.transform) # _gl.ActiveTexture(_gl.TEXTURE1) # _gl.BindTexture(_gl.TEXTURE_2D, self.texture_id) # _gl.ActiveTexture(_gl.TEXTURE0) def __exit__(self, *args): assert self.textures.pop() == self.texture_id assert self.transforms.pop() == self.transform # _gl.ActiveTexture(_gl.TEXTURE1) # _gl.BindTexture(_gl.TEXTURE_2D, self.textures[-1]) # _gl.ActiveTexture(_gl.TEXTURE0) # pserver #################################################################### class _PaintServer(_Paint): _state_attributes = _Paint._state_attributes + [ "parent", ] def __init__(self, parent=None): self.parent = parent def __getattr__(self, attribute): try: return getattr(self.parent, attribute) except AttributeError: pass if attribute in self._DEFAULTS: return self._DEFAULTS[attribute] return super(_PaintServer, self).__getattribute__(attribute) @property def id(self): return "%X" % id(self) @property def href(self): return self.parent @property def attributes(self): attributes = ["id"] attributes += list(k for k in self._DEFAULTS if k in dir(self)) if self.parent: attributes += ["href"] return attributes # gradients ################################################################## _SPREADS = { "pad": 0, "reflect": 1, "repeat": 2, } def _stop(o, c, a=1.): """add optional alpha to gradient stop definitions""" if c is Color.none: c = Color.black a = 0. elif c is Color.current: # TODO: should be fixed c = Color.black a = 0. return (float(o), (c._r, c._g, c._b, a)) class _Gradient(_PaintServer): _DEFAULTS = { "stops": [(0., Color.none)], "gradientTransform": [], "gradientUnits": "objectBoundingBox", "spreadMethod": "pad", } _state_attributes = _PaintServer._state_attributes + list(_DEFAULTS) def __init__(self, parent=None, stops=None, spreadMethod=None, gradientUnits=None, gradientTransform=None): super(_Gradient, self).__init__(parent) if stops is not None: self.stops = stops if spreadMethod is not None: self.spreadMethod = spreadMethod if gradientUnits is not None: self.gradientUnits = gradientUnits if gradientTransform is not None: self.gradientTransform = gradientTransform @property def units(self): return _UNITS[self.gradientUnits] @property def transform(self): return self.gradientTransform def _use_gradient(self, n, os, colors, spread, **kwargs): raise NotImplementedError def _use_program(self, **kwargs): n = len(self.stops) assert n <= MAX_STOPS, "too much stops in gradient" os, colors = zip(*(_stop(*stop) for stop in self.stops)) spread = _SPREADS[self.spreadMethod] self._use_gradient(n, os, colors, spread, **kwargs) @property def attributes(self): attributes = super(_Gradient, self).attributes if "stops" in attributes: attributes.remove("stops") return attributes def _xml_content(self, defs): if "stops" not in dir(self): return "" stops = [] def _stop(offset, color, opacity=None): if color is Color.none: color = "none" elif color == Color.current: pass else: color = color._xml_attr(defs) return offset, color, opacity for stop in self.stops: offset, color, opacity = _stop(*stop) if opacity is None: stop = "" % (offset, color) else: stop = "" % (offset, color, opacity) stops.append(stop) return "\n".join(stops) class LinearGradient(_Gradient): tag = "linearGradient" _DEFAULTS = { "x1": 0., "y1": 0., "x2": 1., "y2": 0., } _state_attributes = _Gradient._state_attributes + list(_DEFAULTS) _DEFAULTS.update(_Gradient._DEFAULTS) def __init__(self, parent=None, stops=None, spreadMethod=None, gradientUnits=None, gradientTransform=None, x1=None, y1=None, x2=None, y2=None): super(LinearGradient, self).__init__(parent, stops, spreadMethod, gradientUnits, gradientTransform) if x1 is not None: self.x1 = x1 if y1 is not None: self.y1 = y1 if x2 is not None: self.x2 = x2 if y2 is not None: self.y2 = y2 def _use_gradient(self, n, os, colors, spread, **kwargs): _use_linear_gradient(p1=[(float(self.x1), float(self.y1))], p2=[(float(self.x2), float(self.y2))], n=[n], os=list(os), colors=list(colors), spread=[spread], **kwargs) class RadialGradient(_Gradient): tag = "radialGradient" _DEFAULTS = { "cx": .5, "cy": .5, "r": .5, "fx": None, "fy": None, } _state_attributes = _Gradient._state_attributes + list(_DEFAULTS) _DEFAULTS.update(_Gradient._DEFAULTS) def __init__(self, parent=None, stops=None, spreadMethod=None, gradientUnits=None, gradientTransform=None, cx=None, cy=None, r=None, fx=None, fy=None): super(RadialGradient, self).__init__(parent, stops, spreadMethod, gradientUnits, gradientTransform) if cx is not None: self.cx = cx if cy is not None: self.cy = cy if r is not None: self.r = r if fx is not None: self.fx = fx if fy is not None: self.fy = fy def _use_gradient(self, n, os, colors, spread, **kwargs): fx, fy = self.fx, self.fy if fx is None: fx = self.cx if fy is None: fy = self.cy _use_radial_gradient(c=[(float(self.cx), float(self.cy))], r=[float(self.r)], f=[(float(fx), float(fy))], n=[n], os=list(os), colors=list(colors), spread=[spread], **kwargs) # pattern #################################################################### class Pattern(_PaintServer): tag = "pattern" _r, _g, _b = 1., 1., 1. _DEFAULTS = { "patternUnits": "objectBoundingBox", "patternContentUnits": "userSpaceOnUse", "patternTransform": [], "x": 0., "y": 0., "width": 0., "height": 0., "viewBox": None, } _state_attributes = _Paint._state_attributes + list(_DEFAULTS) + [ "pattern", ] _DEFAULTS.update(_Gradient._DEFAULTS) def __init__(self, parent=None, pattern=None, patternUnits=None, patternContentUnits=None, patternTransform=None, x=None, y=None, width=None, height=None, viewBox=None, **kwargs): super(Pattern, self).__init__(parent) self.pattern = pattern if patternUnits is not None: self.patternUnits = patternUnits if patternContentUnits is not None: self.patternContentUnits = patternContentUnits if patternTransform is not None: self.patternTransform = patternTransform if x is not None: self.x = x if y is not None: self.y = y if width is not None: self.width = width if height is not None: self.height = height self.viewBox = viewBox @property def units(self): return _UNITS[self.patternUnits] @property def transform(self): return self.patternTransform def _use_program(self, **kwargs): _use_pattern(origin=[(float(self.x), float(self.y))], period=[(float(self.width), float(self.height))], **kwargs) def _xml_content(self, defs): return self.pattern._xml_content(defs) # colors by name ############################################################# # see _color_keywords = { "aliceblue": (240, 248, 255), "antiquewhite": (250, 235, 215), "aqua": ( 0, 255, 255), "aquamarine": (127, 255, 212), "azure": (240, 255, 255), "beige": (245, 245, 220), "bisque": (255, 228, 196), "black": ( 0, 0, 0), "blanchedalmond": (255, 235, 205), "blue": ( 0, 0, 255), "blueviolet": (138, 43, 226), "brown": (165, 42, 42), "burlywood": (222, 184, 135), "cadetblue": ( 95, 158, 160), "chartreuse": (127, 255, 0), "chocolate": (210, 105, 30), "coral": (255, 127, 80), "cornflowerblue": (100, 149, 237), "cornsilk": (255, 248, 220), "crimson": (220, 20, 60), "cyan": ( 0, 255, 255), "darkblue": ( 0, 0, 139), "darkcyan": ( 0, 139, 139), "darkgoldenrod": (184, 134, 11), "darkgray": (169, 169, 169), "darkgreen": ( 0, 100, 0), "darkgrey": (169, 169, 169), "darkkhaki": (189, 183, 107), "darkmagenta": (139, 0, 139), "darkolivegreen": ( 85, 107, 47), "darkorange": (255, 140, 0), "darkorchid": (153, 50, 204), "darkred": (139, 0, 0), "darksalmon": (233, 150, 122), "darkseagreen": (143, 188, 143), "darkslateblue": ( 72, 61, 139), "darkslategray": ( 47, 79, 79), "darkslategrey": ( 47, 79, 79), "darkturquoise": ( 0, 206, 209), "darkviolet": (148, 0, 211), "deeppink": (255, 20, 147), "deepskyblue": ( 0, 191, 255), "dimgray": (105, 105, 105), "dimgrey": (105, 105, 105), "dodgerblue": ( 30, 144, 255), "firebrick": (178, 34, 34), "floralwhite": (255, 250, 240), "forestgreen": ( 34, 139, 34), "fuchsia": (255, 0, 255), "gainsboro": (220, 220, 220), "ghostwhite": (248, 248, 255), "gold": (255, 215, 0), "goldenrod": (218, 165, 32), "gray": (128, 128, 128), "grey": (128, 128, 128), "green": ( 0, 128, 0), "greenyellow": (173, 255, 47), "honeydew": (240, 255, 240), "hotpink": (255, 105, 180), "indianred": (205, 92, 92), "indigo": ( 75, 0, 130), "ivory": (255, 255, 240), "khaki": (240, 230, 140), "lavender": (230, 230, 250), "lavenderblush": (255, 240, 245), "lawngreen": (124, 252, 0), "lemonchiffon": (255, 250, 205), "lightblue": (173, 216, 230), "lightcoral": (240, 128, 128), "lightcyan": (224, 255, 255), "lightgoldenrodyellow": (250, 250, 210), "lightgray": (211, 211, 211), "lightgreen": (144, 238, 144), "lightgrey": (211, 211, 211), "lightpink": (255, 182, 193), "lightsalmon": (255, 160, 122), "lightseagreen": ( 32, 178, 170), "lightskyblue": (135, 206, 250), "lightslategray": (119, 136, 153), "lightslategrey": (119, 136, 153), "lightsteelblue": (176, 196, 222), "lightyellow": (255, 255, 224), "lime": ( 0, 255, 0), "limegreen": ( 50, 205, 50), "linen": (250, 240, 230), "magenta": (255, 0, 255), "maroon": (128, 0, 0), "mediumaquamarine": (102, 205, 170), "mediumblue": ( 0, 0, 205), "mediumorchid": (186, 85, 211), "mediumpurple": (147, 112, 219), "mediumseagreen": ( 60, 179, 113), "mediumslateblue": (123, 104, 238), "mediumspringgreen": ( 0, 250, 154), "mediumturquoise": ( 72, 209, 204), "mediumvioletred": (199, 21, 133), "midnightblue": ( 25, 25, 112), "mintcream": (245, 255, 250), "mistyrose": (255, 228, 225), "moccasin": (255, 228, 181), "navajowhite": (255, 222, 173), "navy": ( 0, 0, 128), "oldlace": (253, 245, 230), "olive": (128, 128, 0), "olivedrab": (107, 142, 35), "orange": (255, 165, 0), "orangered": (255, 69, 0), "orchid": (218, 112, 214), "palegoldenrod": (238, 232, 170), "palegreen": (152, 251, 152), "paleturquoise": (175, 238, 238), "palevioletred": (219, 112, 147), "papayawhip": (255, 239, 213), "peachpuff": (255, 218, 185), "peru": (205, 133, 63), "pink": (255, 192, 203), "plum": (221, 160, 221), "powderblue": (176, 224, 230), "purple": (128, 0, 128), "red": (255, 0, 0), "rosybrown": (188, 143, 143), "royalblue": ( 65, 105, 225), "saddlebrown": (139, 69, 19), "salmon": (250, 128, 114), "sandybrown": (244, 164, 96), "seagreen": ( 46, 139, 87), "seashell": (255, 245, 238), "sienna": (160, 82, 45), "silver": (192, 192, 192), "skyblue": (135, 206, 235), "slateblue": (106, 90, 205), "slategray": (112, 128, 144), "slategrey": (112, 128, 144), "snow": (255, 250, 250), "springgreen": ( 0, 255, 127), "steelblue": ( 70, 130, 180), "tan": (210, 180, 140), "teal": ( 0, 128, 128), "thistle": (216, 191, 216), "tomato": (255, 99, 71), "turquoise": ( 64, 224, 208), "violet": (238, 130, 238), "wheat": (245, 222, 179), "white": (255, 255, 255), "whitesmoke": (245, 245, 245), "yellow": (255, 255, 0), "yellowgreen": (154, 205, 50), } for name in _color_keywords: r, g, b = _color_keywords[name] color = Color(r=r, g=g, b=b, name=name) setattr(Color, name, color)