def test_setter_alpha(self): """Color conversion from rgb to rgba""" color = Color('#ff0102') self.assertEqual(color.space, 'rgb') self.assertEqual(color.alpha, 1.0) color.alpha = 0.5 self.assertEqual(color.space, 'rgba') self.assertEqual(color.alpha, 0.5)
def test_namedcolor(self): """Named Color""" self.assertEqual(Color('red'), [255, 0, 0]) self.assertEqual(str(Color('red')), 'red') color = Color('red') color[0] = 41 self.assertEqual(str(color), '#290000') color = Color("#ff0000").to_named() self.assertEqual(str(color), "red")
def test_errors(self): """Color parsing errors""" self.assertRaises(ColorError, Color, {}) self.assertRaises(ColorError, Color, ["#id"]) self.assertRaises(ColorError, Color, "#badhex") self.assertRaises(ColorIdError, Color, "url(#someid)") self.assertRaises(ColorError, Color, [0, 0, 0, 0]) self.assertRaises(ColorError, Color(None, space='nop').to_rgb) self.assertRaises(ColorError, Color(None, space='nop').to_hsl) self.assertRaises(ColorError, Color([1], space='nop').__str__)
def colorsort(stroke_value ): #this function applies to stroke or fill (hex colors) if self.options.parsecolors == "hexval": return float(int(stroke_value[1:], 16)) elif self.options.parsecolors == "hue": return float(Color(stroke_value).to_hsl()[0]) elif self.options.parsecolors == "saturation": return float(Color(stroke_value).to_hsl()[1]) elif self.options.parsecolors == "luminance": return float(Color(stroke_value).to_hsl()[2]) return None
def test_setter_hsl(self): """Color HSL units can be set on RGB color""" color = Color('#ff0102') color.hue = 100 self.assertEqual(color.space, 'rgb') self.assertEqual(color.hue, 100) color.saturation = 100 self.assertEqual(color.space, 'rgb') self.assertEqual(color.saturation, 99) color.lightness = 100 self.assertEqual(color.space, 'rgb') self.assertEqual(color.lightness, 99) # No sure, bad conversion?
def test_rgb_hex(self): """RGB Hex Color""" color = Color('#ff0102') self.assertEqual(color, [255, 1, 2]) self.assertEqual(str(color), '#ff0102') self.assertEqual(color.red, 255) self.assertEqual(color.green, 1) self.assertEqual(color.blue, 2) color = Color(' #ff0102') self.assertEqual(color.to_hsl(), [254, 255, 128]) self.assertEqual(color.hue, 254) self.assertEqual(color.saturation, 255) self.assertEqual(color.lightness, 128) self.assertEqual(color.alpha, 1.0)
class StopTests(ElementTestCase): """Color stop tests""" black = Color('#000000') grey50 = Color('#080808') white = Color('#111111') def test_interpolate(self): """Interpolate colours""" stl1 = Style({'stop-color': self.black, 'stop-opacity': 0.0}) stop1 = Stop(offset='0.0', style=str(stl1)) stl2 = Style({'stop-color': self.white, 'stop-opacity': 1.0}) stop2 = Stop(offset='1.0', style=str(stl2)) stop3 = stop1.interpolate(stop2, 0.5) assert stop3.style['stop-color'] == str(self.grey50) assert float(stop3.style['stop-opacity']) == pytest.approx(0.5, 1e-3)
def test_interpolate(self): """Test interpolation method.""" stl1 = Style({ 'stroke-width': '0px', 'fill-opacity': 1.0, 'fill': Color((200, 0, 0)) }) stl2 = Style({ 'stroke-width': '1pc', 'fill-opacity': 0.0, 'fill': Color((100, 0, 100)) }) stl3 = stl1.interpolate(stl2, 0.5) assert stl3['fill-opacity'] == pytest.approx(0.5, 1e-3) assert stl3['fill'] == [150, 0, 50] assert stl3['stroke-width'] == '8px'
def test_hsl_color(self): """Parse HSL colors""" color = Color('hsl(4.0, 128, 99)') self.assertEqual(color, [4, 128, 99]) self.assertEqual(str(color), 'hsl(4, 128, 99)') self.assertEqual(color.hue, 4) self.assertEqual(color.saturation, 128) self.assertEqual(color.lightness, 99)
def read_stop_gradient(gradient): stop_data = {"id": gradient.attrib.get("id"), "stops": []} for stop in gradient: offset = stop.attrib.get("offset") style = Style(Style.parse_str(stop.attrib['style'])) color = Color.parse_str(style.get("stop-color"))[1] opacity = style.get("stop-opacity") stop_data.get("stops").append( tuple([float(offset)] + [x / 256.0 for x in color] + [float(opacity)])) return stop_data
def test_empty(self): """Empty color (black)""" self.assertEqual(Color(), []) self.assertEqual(Color().to_rgb(), [0, 0, 0]) self.assertEqual(Color().to_rgba(), [0, 0, 0, 1.0]) self.assertEqual(Color().to_hsl(), [0, 0, 0]) self.assertEqual(str(Color(None)), 'none') self.assertEqual(str(Color('none')), 'none')
def test_rgba_color(self): """Parse RGBA colours""" self.assertEqual(Color('rgba(45,50,55,1.0)'), [45, 50, 55, 1.0]) self.assertEqual(Color('rgba(45,50,55,1.5)'), [45, 50, 55, 1.0]) self.assertEqual(Color('rgba(66.667%,0%,6.667%,0.5)'), [170, 0, 17, 0.5]) color = Color('rgba(255,127,255,0.5)') self.assertEqual(str(color), 'rgba(255, 127, 255, 0.5)') self.assertEqual(str(color.to_rgb()), '#ff7fff') self.assertEqual(color.to_rgb().to_rgba(0.75), [255, 127, 255, 0.75]) color[3] = 1.0 self.assertEqual(str(color), 'rgb(255, 127, 255)')
def test_int_color(self): """Colours from arg parser""" color = Color(1364325887) self.assertEqual(str(color), '#5151f5') color = Color('1364325887') self.assertEqual(str(color), '#5151f5') color = Color(0xffffffff) self.assertEqual(str(color), '#ffffff') color = Color(0xffffff00) self.assertEqual(str(color), 'rgba(255, 255, 255, 0)') self.assertEqual(int(Color('#808080')), 0x808080ff) self.assertEqual(int(Color('rgba(128, 128, 128, 0.2)')), 2155905075)
def test_setter_rgb(self): """Color RGB units can be set""" color = Color('red') color.red = 127 self.assertEqual(color.red, 127) color.green = 5 self.assertEqual(color.green, 5) color.blue = 15 self.assertEqual(color.blue, 15) color.blue = 5.1 self.assertEqual(color.blue, 5) self.assertEqual(str(color), '#7f0505')
def get_compiled_gradient(self, new_id): # compiling gradient stops root = etree.Element("linearGradient", id=new_id) for idx, stop in enumerate(self.gradient_data["stops"]): stop_id = self.get_name() + str(idx) offset = stop[0] color = Color([stop[1], stop[2], stop[3]], "rgb") opacity = stop[4] tmp_stops = { "id": stop_id, "offset": str(offset), "style": Style({ "stop-color": str(color), "stop-opacity": str(opacity) }).to_str() } current_stop = etree.SubElement(root, "stop", attrib=tmp_stops) return root
class GradientTests(ElementTestCase): """Gradient testing""" black = Color('#000000') grey50 = Color('#080808') white = Color('#111111') whiteop1 = Style({'stop-color': white, 'stop-opacity': 1.0}) blackop1 = Style({'stop-color': black, 'stop-opacity': 1.0}) whiteop0 = Style({'stop-color': white, 'stop-opacity': 0.0}) blackop0 = Style({'stop-color': black, 'stop-opacity': 0.0}) translate11 = Transform('translate(1.0, 1.0)') translate22 = Transform('translate(2.0, 2.0)') def test_parse(self): """Gradients parsed from XML""" values = [ (LinearGradient, {'x1': '0px', 'y1': '1px', 'x2': '2px', 'y2': '3px'}, {'x1': 0.0, 'y1': 1.0, 'x2': 2.0, 'y2': 3.0}, ), (RadialGradient, {'cx': '0px', 'cy': '1px', 'fx': '2px', 'fy': '3px', 'r': '4px'}, {'cx': 0.0, 'cy': 1.0, 'fx': 2.0, 'fy': 3.0} )] for classname, attributes, expected in values: grad = classname(attrib=attributes) grad.apply_transform() # identity transform for key, value in expected.items(): assert float(grad.get(key)) == pytest.approx(value, 1e-3) grad = classname(attrib=attributes) grad = grad.interpolate(grad, 0.0) for key, value in expected.items(): assert float(grad.get(key)) == pytest.approx(value, 1e-3) def test_apply_transform(self): """Transform gradients""" values = [ (LinearGradient, {'x1': 0.0, 'y1': 0.0, 'x2': 1.0, 'y2': 1.0}, {'x1': 1.0, 'y1': 1.0, 'x2': 2.0, 'y2': 2.0}), (RadialGradient, {'cx': 0.0, 'cy': 0.0, 'fx': 1.0, 'fy': 1.0, 'r': 1.0}, {'cx': 1.0, 'cy': 1.0, 'fx': 2.0, 'fy': 2.0, 'r': 1.0} )] for classname, orientation, expected in values: grad = classname().update(**orientation) grad.gradientTransform = self.translate11 grad.apply_transform() val = grad.get('gradientTransform') assert val is None for key, value in expected.items(): assert float(grad.get(key)) == pytest.approx(value, 1e-3) def test_stops(self): """Gradients have stops""" for classname in [LinearGradient, RadialGradient]: grad = classname() stops = [ Stop().update(offset=0.0, style=self.whiteop0), Stop().update(offset=1.0, style=self.blackop1)] grad.add(*stops) assert [s1.tostring() == s2.tostring() for s1, s2 in zip(grad.stops, stops)] def test_stop_styles(self): """Gradients have styles""" for classname in [LinearGradient, RadialGradient]: grad = classname() stops = [ Stop().update(offset=0.0, style=self.whiteop0), Stop().update(offset=1.0, style=self.blackop1)] grad.add(*stops) assert [str(s1) == str(s2.style) for s1, s2 in zip(grad.stop_styles, stops)] def test_get_stop_offsets(self): """Gradients stop offsets""" for classname in [LinearGradient, RadialGradient]: grad = classname() stops = [ Stop().update(offset=0.0, style=self.whiteop0), Stop().update(offset=1.0, style=self.blackop1)] grad.add(*stops) for stop1, stop2 in zip(grad.stop_offsets, stops): self.assertEqual(float(stop1), pytest.approx(float(stop2.offset), 1e-3)) def test_interpolate(self): """Gradients can be interpolated""" values = [ (LinearGradient, {'x1': 0, 'y1': 0, 'x2': 1, 'y2': 1}, {'x1': 2, 'y1': 2, 'x2': 1, 'y2': 1}, {'x1': 1.0, 'y1': 1.0, 'x2': 1.0, 'y2': 1.0}), (RadialGradient, {'cx': 0, 'cy': 0, 'fx': 1, 'fy': 1, 'r': 0}, {'cx': 2, 'cy': 2, 'fx': 1, 'fy': 1, 'r': 1}, {'cx': 1.0, 'cy': 1.0, 'fx': 1.0, 'fy': 1.0, 'r': 0.5}) ] for classname, orientation1, orientation2, expected in values: # gradient 1 grad1 = classname() stops1 = [ Stop().update(offset=0.0, style=self.whiteop0), Stop().update(offset=1.0, style=self.blackop1)] grad1.add(*stops1) grad1.update(gradientTransform=self.translate11) grad1.update(**orientation1) # gradient 2 grad2 = classname() stops2 = [ Stop().update(offset=0.0, style=self.blackop1), Stop().update(offset=1.0, style=self.whiteop0)] grad2.add(*stops2) grad2.update(gradientTransform=self.translate22) grad2.update(**orientation2) grad = grad1.interpolate(grad2, 0.5) comp = Style({'stop-color': self.grey50, 'stop-opacity': 0.5}) self.assertEqual(str(grad.stops[0].style), str(Style(comp))) self.assertEqual(str(grad.stops[1].style), str(Style(comp))) self.assertEqual(str(grad.gradientTransform), 'translate(1.5, 1.5)') for key, value in expected.items(): self.assertEqual(float(grad.get(key)), pytest.approx(value, 1e-3))
def test_rgb_percent(self): """RGB Percent Color""" self.assertEqual(Color('rgb(100%,100%,100%)'), [255, 255, 255]) self.assertEqual(Color('rgb(50%,0%,1%)'), [127, 0, 2]) self.assertEqual(Color('rgb(66.667%,0%,6.667%)'), [170, 0, 17])
def test_hsl_to_rgb(self): """Convert HSL to RGB""" color = Color('hsl(172, 131, 128)') self.assertEqual(color.to_rgb(), [68, 62, 193]) self.assertEqual(str(color.to_rgb()), '#443ec1') self.assertEqual(color.red, 68) self.assertEqual(color.green, 62) self.assertEqual(color.blue, 193) color = Color('hsl(172, 131, 10)') self.assertEqual(color.to_rgb(), [5, 4, 15]) color = Color('hsl(0, 131, 10)') self.assertEqual(color.to_rgb(), [15, 4, 4]) self.assertEqual(color.to_rgba(), [15, 4, 4, 1.0])
def effect(self): def colorsort(stroke_value ): #this function applies to stroke or fill (hex colors) if self.options.parsecolors == "hexval": return float(int(stroke_value[1:], 16)) elif self.options.parsecolors == "hue": return float(Color(stroke_value).to_hsl()[0]) elif self.options.parsecolors == "saturation": return float(Color(stroke_value).to_hsl()[1]) elif self.options.parsecolors == "luminance": return float(Color(stroke_value).to_hsl()[2]) return None applyTransformAvailable = False # at first we apply external extension try: import applytransform applyTransformAvailable = True except Exception as e: # inkex.utils.debug(e) inkex.utils.debug( "Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ..." ) layer_name = None layerNodeList = [ ] #list with layer, neutral_value, element and self.options.separateby type selected = [] #list of items to parse if len(self.svg.selected) == 0: for element in self.document.getroot().iter("*"): selected.append(element) else: selected = self.svg.selected.values() for element in selected: # additional option to apply transformations. As we clear up some groups to form new layers, we might lose translations, rotations, etc. if self.options.apply_transformations is True and applyTransformAvailable is True: applytransform.ApplyTransform().recursiveFuseTransform(element) if isinstance( element, inkex.ShapeElement ): # Elements which have a visible representation on the canvas (even without a style attribute but by their type); if we do not use that ifInstance Filter we provokate unkown InkScape fatal crashes style = element.get('style') if style is not None: #if no style attributes or stroke/fill are set as extra attribute stroke = element.get('stroke') stroke_width = element.get('stroke-width') stroke_opacity = element.get('stroke-opacity') fill = element.get('fill') fill_opacity = element.get('fill-opacity') # possible values for fill are #HEXCOLOR (like #000000), color name (like purple, black, red) or gradients (URLs) neutral_value = None #we will use this value to slice the filter result into sub layers (threshold) if fill is not None: style = 'fill:' + fill + ";" if stroke is not None: style = style + 'stroke:' + stroke + ";" #we don't want to destroy elements with gradients (they contain svg:stop elements which have a style too) and we don't want to mess with tspans (text) #the Styles to Layers extension still might brick the gradients (some tests failed) if style and element.tag != inkex.addNS( 'stop', 'svg') and element.tag != inkex.addNS( 'tspan', 'svg'): if self.options.separateby == "stroke": stroke = re.search('(;|^)stroke:(.*?)(;|$)', style) if stroke is not None: stroke = stroke[0] stroke_value = stroke.split( "stroke:")[1].split(";")[0] if stroke_value != "none": stroke_converted = str( Color(stroke_value).to_rgb() ) #the color can be hex code or clear name. we handle both the same neutral_value = colorsort(stroke_converted) layer_name = "stroke-" + self.options.parsecolors + "-" + stroke_converted else: layer_name = "stroke-" + self.options.parsecolors + "-none" elif self.options.separateby == "stroke_width": stroke_width = re.search('stroke-width:(.*?)(;|$)', style) if stroke_width is not None: stroke_width = stroke_width[0] neutral_value = self.svg.unittouu( stroke_width.split("stroke-width:") [1].split(";")[0]) layer_name = stroke_width else: layer_name = "stroke-width-none" elif self.options.separateby == "stroke_hairline": stroke_hairline = re.search( '-inkscape-stroke:hairline(;|$)', style) if stroke_hairline is not None: neutral_value = 1 layer_name = "stroke-hairline-yes" else: neutral_value = 0 layer_name = "stroke-hairline-no" elif self.options.separateby == "stroke_opacity": stroke_opacity = re.search( 'stroke-opacity:(.*?)(;|$)', style) if stroke_opacity is not None: stroke_opacity = stroke_opacity[0] neutral_value = float( stroke_opacity.split("stroke-opacity:") [1].split(";")[0]) layer_name = stroke_opacity else: layer_name = "stroke-opacity-none" elif self.options.separateby == "fill": fill = re.search('fill:(.*?)(;|$)', style) if fill is not None: fill = fill[0] fill_value = fill.split("fill:")[1].split( ";")[0] #check if the fill color is a real color or a gradient. if it's a gradient we skip the element if fill_value != "none" and "url" not in fill_value: fill_converted = str( Color(fill_value).to_rgb() ) #the color can be hex code or clear name. we handle both the same neutral_value = colorsort(fill_converted) layer_name = "fill-" + self.options.parsecolors + "-" + fill_converted elif "url" in fill_value: #okay we found a gradient. we put it to some group layer_name = "fill-" + self.options.parsecolors + "-gradient" else: layer_name = "fill-" + self.options.parsecolors + "-none" elif self.options.separateby == "fill_opacity": fill_opacity = re.search('fill-opacity:(.*?)(;|$)', style) if fill_opacity is not None: fill_opacity = fill_opacity[0] neutral_value = float( fill_opacity.split("fill-opacity:") [1].split(";")[0]) layer_name = fill_opacity else: layer_name = "fill-opacity-none" else: inkex.utils.debug("No proper option selected.") exit(1) if neutral_value is not None: #apply decimals filter neutral_value = float( round(neutral_value, self.options.decimals)) if layer_name is not None: layer_name = layer_name.split( ";" )[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon currentLayer = self.findLayer(layer_name) if currentLayer is None: #layer does not exist, so create a new one layerNodeList.append([ self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby ]) else: layerNodeList.append( [ currentLayer, neutral_value, element, self.options.separateby ] ) #layer is existent. append items to this later else: #if no style attribute in element and not a group if isinstance(element, inkex.Group) is False: if self.options.show_info: inkex.utils.debug( element.get('id') + ' has no style attribute') if self.options.put_unfiltered: layer_name = 'without-style-attribute' currentLayer = self.findLayer(layer_name) if currentLayer is None: #layer does not exist, so create a new one layerNodeList.append([ self.createLayer(layerNodeList, layer_name), None, element, None ]) else: layerNodeList.append( [currentLayer, None, element, None] ) #layer is existent. append items to this later contentlength = 0 #some counter to track if there are layers inside or if it is just a list with empty children for layerNode in layerNodeList: try: #some nasty workaround. Make better code layerNode[0].append( layerNode[2]) #append element to created layer if layerNode[1] is not None: contentlength += 1 #for each found layer we increment +1 except: continue # we do some cosmetics with layers. Sometimes it can happen that one layer includes another. We don't want that. We move all layers to the top level for newLayerNode in layerNodeList: self.document.getroot().append(newLayerNode[0]) # Additionally if threshold was defined re-arrange the previously created layers by putting them into sub layers if self.options.subdividethreshold > 1 and contentlength > 0: #check if we need to subdivide and if there are items we could rearrange into sub layers #disabled sorting because it can return NoneType values which will kill the algorithm #layerNodeList.sort(key=itemgetter(1)) #sort by neutral values from min to max to put them with ease into parent layers topLevelLayerNodeList = [ ] #list with new layers and sub layers (mapping) minmax_range = [] for layerNode in layerNodeList: if layerNode[1] is not None: if layerNode[1] not in minmax_range: minmax_range.append(layerNode[1]) #get neutral_value if len( minmax_range ) >= 3: #if there are less than 3 distinct values a sub-layering will make no sense #adjust the subdividethreshold if there are less layers than division threshold value dictates if len(minmax_range) - 1 < self.options.subdividethreshold: self.options.subdividethreshold = len(minmax_range) - 1 #calculate the sub layer slices (sub ranges) minval = min(minmax_range) maxval = max(minmax_range) sliceinterval = (maxval - minval) / self.options.subdividethreshold #inkex.utils.debug("neutral values (sorted) = " + str(minmax_range)) #inkex.utils.debug("min neutral_value = " + str(minval)) #inkex.utils.debug("max neutral_value = " + str(maxval)) #inkex.utils.debug("slice value (divide step size) = " + str(sliceinterval)) #inkex.utils.debug("subdivides (parent layers) = " + str(self.options.subdividethreshold)) for layerNode in layerNodeList: for x in range( 0, self.options.subdividethreshold ): #loop through the sorted neutral_values and determine to which layer they should belong if layerNode[1] is None: layer_name = str( layerNode[3]) + "#parent:unfilterable" currentLayer = self.findLayer(layer_name) if currentLayer is None: #layer does not exist, so create a new one topLevelLayerNodeList.append([ self.createLayer(topLevelLayerNodeList, layer_name), layerNode[0] ]) else: topLevelLayerNodeList.append( [currentLayer, layerNode[0]] ) #layer is existent. append items to this later break else: layer_name = str( layerNode[3]) + "#parent" + str(x + 1) currentLayer = self.findLayer(layer_name) #value example for arranging: #min neutral_value = 0.07 #max neutral_value = 2.50 #slice value = 0.81 #subdivides = 3 # #that finally should generate: # layer #1: (from 0.07) to (0.07 + 0.81 = 0.88) # layer #2: (from 0.88) to (0.88 + 0.81 = 1.69) # layer #3: (from 1.69) to (1.69 + 0.81 = 2.50) # #now check layerNode[1] (neutral_value) and sort it into the correct layer if (layerNode[1] >= minval + sliceinterval * x ) and (layerNode[1] <= minval + sliceinterval + sliceinterval * x): if currentLayer is None: #layer does not exist, so create a new one topLevelLayerNodeList.append([ self.createLayer( topLevelLayerNodeList, layer_name), layerNode[0] ]) else: topLevelLayerNodeList.append( [currentLayer, layerNode[0]] ) #layer is existent. append items to this later break #finally append the sublayers to the slices #for layer in topLevelLayerNodeList: #inkex.utils.debug(layer[0].get('inkscape:label')) #inkex.utils.debug(layer[1]) for newLayerNode in topLevelLayerNodeList: newLayerNode[0].append( newLayerNode[1]) #append newlayer to layer if self.options.cleanup == True: try: import cleangroups cleangroups.CleanGroups.effect(self) except: inkex.utils.debug( "Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ..." )
def test_hsl_grey(self): """Parse HSL Grey""" color = Color('hsl(172, 0, 128)') self.assertEqual(color.to_rgb(), [128, 128, 128]) color = Color('rgb(128, 128, 128)') self.assertEqual(color.to_hsl(), [0, 0, 128])
def test_rgb_short_hex(self): """RGB Short Hex Color""" self.assertEqual(Color('#fff'), [255, 255, 255]) self.assertEqual(str(Color('#fff')), '#ffffff')
def test_rgb_to_hsl(self): """RGB to HSL Color""" self.assertEqual(Color('#ff7c7d').to_hsl(), [254, 255, 189]) self.assertEqual(Color('#7e7c7d').to_hsl(), [233, 2, 125]) self.assertEqual(Color('#7e7cff').to_hsl(), [170, 255, 189]) self.assertEqual(Color('#7eff7d').to_hsl(), [84, 255, 190])
def test_interpolate(self): black = Color('#000000') grey50 = Color('#080808') white = Color('#111111') val = black.interpolate(white, 0.5) assert val == grey50
def test_rgb_int(self): """RGB Integer Color""" self.assertEqual(Color('rgb(255,255,255)'), [255, 255, 255])