def color_formatter(src="", language="", class_name=None, md=""): """Formatter wrapper.""" from coloraide import Color try: result = src.strip() try: console, colors = execute(result) if len(colors) != 1 or len(colors[0]) != 1: raise ValueError('Need one color only') color = colors[0][0].color result = colors[0][0].string except Exception: color = Color(result.strip()) el = Etree.Element('span') stops = [] if not color.in_gamut(WEBSPACE): color.fit(WEBSPACE, in_place=True) attributes = {'class': "swatch out-of-gamut", "title": result} sub_el = Etree.SubElement(el, 'span', attributes) stops.append(color.convert(WEBSPACE).to_string(hex=True, alpha=False)) if color.alpha < 1.0: stops[-1] += ' 50%' stops.append(color.convert(WEBSPACE).to_string(hex=True) + ' 50%') else: attributes = {'class': "swatch", "title": result} sub_el = Etree.SubElement(el, 'span', attributes) stops.append(color.convert(WEBSPACE).to_string(hex=True, alpha=False)) if color.alpha < 1.0: stops[-1] += ' 50%' stops.append(color.convert(WEBSPACE).to_string(hex=True) + ' 50%') if not stops: stops.extend(['transparent'] * 2) if len(stops) == 1: stops.append(stops[0]) Etree.SubElement( sub_el, 'span', { "class": "swatch-color", "style": "--swatch-stops: {};".format(','.join(stops)) } ) el.append(md.inlinePatterns['backtick'].handle_code('css-color', result)) except Exception: import traceback print(traceback.format_exc()) el = md.inlinePatterns['backtick'].handle_code('text', src) return el
def test_from_luv(self): """Test null from Luv conversion.""" c1 = Color('color(--luv 90% 0 0)') c2 = c1.convert('lchuv') self.assertColorEqual(c2, Color('color(--lchuv 90% 0 0)')) self.assertTrue(c2.is_nan('hue'))
def setup_image_border(self): """Setup_image_border.""" ch_settings = sublime.load_settings('color_helper.sublime-settings') border_color = ch_settings.get('image_border_color') if border_color is not None: try: border_color = Color(border_color, filters=util.SRGB_SPACES) border_color.fit("srgb", in_place=True) except Exception: border_color = None if border_color is None: # Calculate border color for images border_color = Color(self.view.style()['background'], filters=util.SRGB_SPACES).convert("hsl") border_color.lightness = border_color.lightness + ( 30 if border_color.luminance() < 0.5 else -30) self.default_border = border_color.convert("srgb").to_string(**HEX) self.out_of_gamut = Color("transparent", filters=util.SRGB_SPACES).to_string(**HEX) self.out_of_gamut_border = Color( self.view.style().get('redish', "red"), filters=util.SRGB_SPACES).to_string(**HEX)
def assert_round_trip(self, color, space): """Cycle through all the other colors and convert to them and back and check the results.""" c1 = Color(color).convert(space) for space in SPACES: # Print the color space to easily identify which color space broke. c2 = c1.convert(space) c2.convert(c1.space(), in_place=True) # Catch cases where we are really close to 360 which should wrap to 0 for c in (c1, c2): if isinstance(c._space, Cylindrical): if util.round_half_up(util.no_nan(c.hue), c.PRECISION) == 360: c.hue = 0 # Run rounded string back through parsing in case we hit something like a hue that needs normalization. str1 = Color(c1.to_string(color=True, fit=False)).to_string(color=True, fit=False) str2 = Color(c2.to_string(color=True, fit=False)).to_string(color=True, fit=False) # Print failing results for debug purposes if str1 != str2: print('----- Convert: {} <=> {} -----'.format( c1.space(), space)) print('Original: ', color.to_string(color=True, fit=False)) print(c1.space() + ': ', str1, c1.coords()) print(space + ': ', str2, c2.coords()) assert str1 == str2
def test_from_din99o(self): """Test null from DIN99o conversion.""" c1 = Color('color(--din99o 90% 0 0)') c2 = c1.convert('lch99o') self.assertColorEqual(c2, Color('color(--lch99o 90% 0 0)')) self.assertTrue(c2.is_nan('hue'))
def test_from_lab(self): """Test null from Lab conversion.""" c1 = Color('lab(90% 0 0)') c2 = c1.convert('lch') self.assertColorEqual(c2, Color('lch(90% 0 0)')) self.assertTrue(c2.is_nan('hue'))
def test_from_hsl(self): """Test null from Lab conversion.""" c1 = Color('hsl(0 0% 50%)') c2 = c1.convert('hsv') self.assertColorEqual(c2, Color('color(--hsv 0 0% 50%)')) self.assertTrue(c2.is_nan('hue'))
def test_from_jzazbz(self): """Test null from Lab conversion.""" c1 = Color('color(--jzazbz 90% 0 0)') c2 = c1.convert('jzczhz') self.assertColorEqual(c2, Color('color(--jzczhz 90% 0 0)')) self.assertTrue(c2.is_nan('hue'))
def test_achromatic_hue(self): """Test that all RGB-ish colors convert to LCHuv with a null hue.""" for space in ('srgb', 'display-p3', 'rec2020', 'a98-rgb', 'prophoto-rgb'): for x in range(0, 256): color = Color('color({space} {num:f} {num:f} {num:f})'.format( space=space, num=x / 255)) color2 = color.convert('lchuv') self.assertTrue(color2.is_nan('hue'))
def run(target, rgb, res): """Run.""" max_x = float('-inf') max_y = float('-inf') max_z = float('-inf') min_x = float('inf') min_y = float('inf') min_z = float('inf') print('-> Current:', end="") x = y = z = 0 color = Color(rgb, [0, 0, 0]) while True: color.update(rgb, [x / res, y / res, z / res]) print('\rCurrent: {}'.format(color.to_string(color=True)), end="") cx, cy, cz = color.convert(target).coords() if cx < min_x: min_x = cx if cy < min_y: min_y = cy if cz < min_z: min_z = cz if cx > max_x: max_x = cx if cy > max_y: max_y = cy if cz > max_z: max_z = cz if x == y == z == res: break elif y == z == res: x += 1 y = z = 0 elif z == res: y += 1 z = 0 else: z += 1 print('') chan_x, chan_y, chan_z = Color('white').convert( target)._space.CHANNEL_NAMES print('---- {} range in {} ----'.format(target, rgb)) print('{}: [{}, {}]'.format(chan_x, util.round_half_up(min_x, 3), util.round_half_up(max_x, 3))) print('{}: [{}, {}]'.format(chan_y, util.round_half_up(min_y, 3), util.round_half_up(max_y, 3))) print('{}: [{}, {}]'.format(chan_z, util.round_half_up(min_z, 3), util.round_half_up(max_z, 3))) return 0
def setup(self, color, mode, on_done, on_cancel): """Setup properties for rendering.""" self.on_done = on_done self.on_cancel = on_cancel self.template_vars = {} color = Color(color) self.setup_gamut_style() self.setup_image_border() self.setup_sizes() self.height_big = int(self.height + self.height / 4) self.setup_mode(color, mode) self.color = color.convert(self.mode, fit=True)
def initial_text(self): """Initial text.""" if self.color is not None: return self.color elif len(self.view.sel()) == 1: self.setup_color_class() text = self.view.substr(self.view.sel()[0]) if text: color = None try: color = self.custom_color_class(text, filters=self.filters) except Exception: pass if color is not None: color = Color(color) if color.space() not in util.SRGB_SPACES: color = color.convert("srgb", fit=True) return color.to_string(**util.DEFAULT) return ''
def plot_slice(space, channel0, channel1, channel2, gamut='srgb', resolution=500, dark=False, title=""): """Plot a slice.""" res = resolution if not dark: plt.style.use('seaborn-darkgrid') default_color = 'black' else: plt.style.use('dark_background') default_color = 'white' figure = plt.figure() # Create a color object based on the specified space. c = Color(space, []) # Parse the channel strings into actual values name0, value = [ c if i == 0 else float(c) for i, c in enumerate(channel0.split(":"), 0) ] name1, factor1, offset1 = [ c if i == 0 else float(c) for i, c in enumerate(channel1.split(':'), 0) ] name2, factor2, offset2 = [ c if i == 0 else float(c) for i, c in enumerate(channel2.split(':'), 0) ] # Get the actual indexes of the specified channels name0 = c._space.CHANNEL_ALIASES.get(name0, name0) index0 = c._space.CHANNEL_NAMES.index(name0) name1 = c._space.CHANNEL_ALIASES.get(name1, name1) index1 = c._space.CHANNEL_NAMES.index(name1) name2 = c._space.CHANNEL_ALIASES.get(name2, name2) index2 = c._space.CHANNEL_NAMES.index(name2) # Arrays for data points to plot c_map = [] xaxis = [] yaxis = [] # Track minimum and maximum values c1_mn = float('inf') c1_mx = float('-inf') c2_mn = float('inf') c2_mx = float('-inf') # Track the edge of the graphed shape. edge_map = {} # Iterate through the two specified channels for c1, c2 in itertools.product( ((x / res * factor1) + offset1 for x in range(0, res + 1)), ((x / res * factor2) + offset2 for x in range(0, res + 1))): # Set the appropriate channels and update the color object coords = [NaN] * 3 coords[index0] = value coords[index1] = c1 coords[index2] = c2 c.update(space, coords) # Only process colors within the gamut of sRGB. if c.in_gamut(gamut, tolerance=0) and not needs_lchuv_workaround(c): # Get the absolute min and max value plotted if c1 < c1_mn: c1_mn = c1 if c1 > c1_mx: c1_mx = c1 if c2 < c2_mn: c2_mn = c2 if c2 > c2_mx: c2_mx = c2 # Create an edge map so we can draw an outline if c1 not in edge_map: mn = mx = c2 else: mn, mx = edge_map[c1] if c2 < mn: mn = c2 elif c2 > mx: mx = c2 edge_map[c1] = [mn, mx] # Save the points xaxis.append(c1) yaxis.append(c2) c_map.append(c.convert('srgb').to_string(hex=True)) # Create a border around the data xe = [] ye = [] xtemp = [] ytemp = [] for p1, edges in edge_map.items(): xe.append(p1) ye.append(edges[0]) xtemp.append(p1) ytemp.append(edges[1]) xe.extend(reversed(xtemp)) ye.extend(reversed(ytemp)) xe.append(xe[0]) ye.append(ye[0]) ax = plt.axes(xlabel='{}: {} - {}'.format(name1, c1_mn, c1_mx), ylabel='{}: {} - {}'.format(name2, c2_mn, c2_mx)) ax.set_aspect('auto') figure.add_axes(ax) if not title: title = "A Slice of '{}' and '{}' in the {} Color Space".format( name1, name2, space) title += '\n{}: {}'.format(name0, value) plt.title(title) plt.plot(xe, ye, color=default_color, marker="", linewidth=2, markersize=0, antialiased=True) plt.scatter(xaxis, yaxis, marker="o", color=c_map, s=2)
def get_color_map_square(self): """Get a square variant of the color map.""" global color_map global color_map_size global line_height global default_border global color_scale global last_saturation s = self.color.convert("hsl").saturation # Only update if the last time we rendered we changed # something that would require a new render. if (color_map is None or s != last_saturation or self.graphic_size != color_map_size or self.graphic_scale != color_scale or self.line_height != line_height or self.default_border != default_border): color_map_size = self.graphic_size color_scale = self.graphic_scale line_height = self.line_height default_border = self.default_border html_colors = [] # Generate the colors with each row being dark than the last. # Each column will progress through hues. color = Color("hsl(0 {}% 90%)".format(s), filters=util.SRGB_SPACES) hfac = 24.0 lfac = 8.0 check_size = self.check_size(self.height) for y in range(0, 11): html_colors.append([self.get_spacer(width=5)]) for x in range(0, 15): value = color.convert("srgb").to_string(**HEX) kwargs = { "border_size": BORDER_SIZE, "height": self.height, "width": self.width, "check_size": check_size } if y == 0 and x == 0: border_map = colorbox.TOP | colorbox.LEFT elif y == 0 and x == 14: border_map = colorbox.TOP | colorbox.RIGHT elif y == 0: border_map = colorbox.TOP elif y == 10 and x == 0: border_map = colorbox.BOTTOM | colorbox.LEFT elif y == 10 and x == 14: border_map = colorbox.BOTTOM | colorbox.RIGHT elif y == 10: border_map = colorbox.BOTTOM elif x == 0: border_map = colorbox.LEFT elif x == 14: border_map = colorbox.RIGHT else: border_map = 0 kwargs["border_map"] = border_map html_colors[-1].append('<a href="{}">{}</a>'.format( color.to_string(**COLOR_FULL_PREC), mdpopups.color_box([value], self.default_border, **kwargs))) color.hue = color.hue + hfac color.hue = 0.0 color.lightness = color.lightness - lfac # Generate a grayscale bar. lfac = 10.0 color = Color('hsl(0 0% 100%)', filters=util.SRGB_SPACES) check_size = self.check_size(self.height) for y in range(0, 11): value = color.convert("srgb").to_string(**HEX) kwargs = { "border_size": BORDER_SIZE, "height": self.height, "width": self.width, "check_size": check_size } if y == 0: border_map = 0xb elif y == 10: border_map = 0xe else: border_map = 0xa kwargs["border_map"] = border_map html_colors[y].append('<a href="{}">{}</a>'.format( color.to_string(**COLOR_FULL_PREC), mdpopups.color_box([value], self.default_border, **kwargs))) color.lightness = color.lightness - lfac color_map = (''.join([ '<span>{}</span><br>'.format(''.join([y1 for y1 in x1])) for x1 in html_colors ]) + '\n\n') self.template_vars['color_picker'] = color_map
def do_search(self, force=False): """ Perform the search for the highlighted word. TODO: This function is a big boy. We should look into breaking it up. With that said, this is low priority. """ # Since the plugin has been reloaded, force update. global reload_flag settings = self.view.settings() colors = [] # Allow per view scan override option = settings.get("color_helper.scan_override", None) if option in ("Force enable", "Force disable"): override = option == "Force enable" else: override = None # Get the rules and use them to get the needed scopes. # The scopes will be used to get the searchable regions. rules = util.get_rules(self.view) # Bail if this if this view has no valid rule or scanning is disabled. if (rules is None or not rules.get("enabled", False) or (not rules.get("allow_scanning", True) and not override) or override is False): self.erase_phantoms() return if reload_flag: reload_flag = False force = True # Calculate size of preview boxes box_height = self.calculate_box_size() check_size = int((box_height - 2) / 4) if check_size < 2: check_size = 2 # If desired preview boxes are different than current, # we need to reload the boxes. old_box_height = int(settings.get('color_helper.box_height', 0)) current_color_scheme = settings.get('color_scheme') if (force or old_box_height != box_height or current_color_scheme != settings.get( 'color_helper.color_scheme', '') or settings.get('color_helper.refresh')): self.erase_phantoms() settings.set('color_helper.color_scheme', current_color_scheme) settings.set('color_helper.box_height', box_height) force = True # If we don't need to force previews, # quit if visible region is the same as last time visible_region = self.view.visible_region() if not force and self.previous_region == visible_region: return self.previous_region = visible_region # Setup "preview on select" preview_on_select = ch_settings.get("preview_on_select", False) show_preview = True sel = None if preview_on_select and len(self.view.sel()) != 1: show_preview = False elif preview_on_select: sel = self.view.sel()[0] # Get the scan scopes scanning = rules.get("scanning") classes = rules.get("color_class", "css-level-4") if show_preview and visible_region.size() and scanning and classes: # Get out of gamut related options self.setup_gamut_options() # Get triggers that identify where colors are likely color_trigger = re.compile( rules.get("color_trigger", util.RE_COLOR_START)) # Find source content in the visible region. # We will return consecutive content, but if the lines are too wide # horizontally, they will be clipped and returned as separate chunks. for src_region in self.source_iter(visible_region): source = self.view.substr(src_region) start = 0 # Find colors in this source chunk. for m in color_trigger.finditer(source): # Test if we have found a valid color start = m.start() src_start = src_region.begin() + start # Check if the first point within the color matches our scope rules # and load up the appropriate color class color_class, filters = self.get_color_class( src_start, classes) if color_class is None: continue # Check if scope matches for scanning try: value = self.view.score_selector(src_start, scanning) if not value: continue except Exception: continue obj = color_class.match(source, start=start, filters=filters) if obj is not None: # Calculate true start and end of the color source src_end = src_region.begin() + obj.end region = sublime.Region(src_start, src_end) # If "preview on select" is enabled, only show preview if within a selection # or if the selection as no width and the color comes right after. if (preview_on_select and not (sel.empty() and sel.begin() == region.begin()) and not region.intersects(sel)): continue else: continue # Calculate point at which we which to insert preview position_on_left = preview_is_on_left() pt = src_start if position_on_left else src_end if str(region.begin()) in self.previews: # Already exists continue # Calculate a reasonable border color for our image at this location and get color strings hsl = Color(mdpopups.scope2style( self.view, self.view.scope_name(pt))['background'], filters=util.SRGB_SPACES).convert("hsl") hsl.lightness = hsl.lightness + ( 30 if hsl.luminance() < 0.5 else -30) preview_border = hsl.convert( "srgb", fit=True).to_string(**util.HEX) color = Color(obj.color) title = '' if not color.in_gamut("srgb"): title = ' title="Out of gamut"' if self.show_out_of_gamut_preview: srgb = color.convert("srgb", fit=True) preview1 = srgb.to_string(**util.HEX_NA) preview2 = srgb.to_string(**util.HEX) else: preview1 = self.out_of_gamut preview2 = self.out_of_gamut preview_border = self.out_of_gamut_border else: srgb = color.convert("srgb") preview1 = srgb.to_string(**util.HEX_NA) preview2 = srgb.to_string(**util.HEX) # Create preview unique_id = str(time()) + str(region) html = PREVIEW_IMG.format( unique_id, title, mdpopups.color_box([preview1, preview2], preview_border, height=box_height, width=box_height, border_size=PREVIEW_BORDER_SIZE, check_size=check_size)) colors.append( (html, pt, region.begin(), region.end(), unique_id)) # Add all previews self.add_phantoms(colors) # The phantoms may have altered the viewable region, # so set previous region to the current viewable region self.previous_region = sublime.Region( self.previous_region.begin(), self.view.visible_region().end())