Skip to content

Commit

Permalink
-Backport ST3 features, such as showing color and selectors from color
Browse files Browse the repository at this point in the history
scheme at point
-Remove changelog because I never update it anyways
  • Loading branch information
facelessuser committed Oct 18, 2013
1 parent d8c4e52 commit 4ef8f87
Show file tree
Hide file tree
Showing 6 changed files with 469 additions and 57 deletions.
Empty file added ScopeHunterLib/__init__.py
Empty file.
178 changes: 178 additions & 0 deletions ScopeHunterLib/color_scheme_matcher.py
@@ -0,0 +1,178 @@
"""
Color Scheme Matcher (for sublime text)
Licensed under MIT
Copyright (c) 2013 Isaac Muse <isaacmuse@gmail.com>
"""
from __future__ import absolute_import
import sublime
import re
ST3 = int(sublime.version()) >= 3000
if not ST3:
from plistlib import readPlist
else:
from plistlib import readPlistFromBytes
from .rgba import RGBA
from os import path


def sublime_format_path(pth):
m = re.match(r"^([A-Za-z]{1}):(?:/|\\)(.*)", pth)
if sublime.platform() == "windows" and m != None:
pth = m.group(1) + "/" + m.group(2)
return pth.replace("\\", "/")


class ColorSchemeMatcher(object):
def __init__(self, scheme_file, strip_trans=False, ignore_gutter=False, track_dark_background=False, filter=None):
if filter is None:
filter = self.filter
self.color_scheme = path.normpath(scheme_file)
self.scheme_file = path.basename(self.color_scheme)
if ST3:
self.plist_file = filter(readPlistFromBytes(sublime.load_binary_resource(sublime_format_path(self.color_scheme))))
else:
self.plist_file = filter(readPlist(sublime.packages_path() + self.color_scheme.replace('Packages', '')))
self.scheme_file = scheme_file
self.strip_trans = strip_trans
self.ignore_gutter = ignore_gutter
self.track_dark_background = track_dark_background
self.dark_lumens = None
self.matched = {}

self.parse_scheme()

def filter(self, plist):
return plist

def parse_scheme(self):
color_settings = self.plist_file["settings"][0]["settings"]

# Get general theme colors from color scheme file
self.bground = self.strip_color(color_settings.get("background", '#FFFFFF'), simple_strip=True)
self.fground = self.strip_color(color_settings.get("foreground", '#000000'))
self.sbground = self.strip_color(color_settings.get("selection", self.fground))
self.sfground = self.strip_color(color_settings.get("selectionForeground", None))
self.gbground = self.strip_color(color_settings.get("gutter", self.bground)) if not self.ignore_gutter else self.bground
self.gfground = self.strip_color(color_settings.get("gutterForeground", self.fground)) if not self.ignore_gutter else self.fground

# Create scope colors mapping from color scheme file
self.colors = {}
for item in self.plist_file["settings"]:
name = item.get('name', None)
scope = item.get('scope', None)
color = None
style = []
if 'settings' in item:
color = item['settings'].get('foreground', None)
bgcolor = item['settings'].get('background', None)
if 'fontStyle' in item['settings']:
for s in item['settings']['fontStyle'].split(' '):
if s == "bold" or s == "italic": # or s == "underline":
style.append(s)

if scope != None and name != None and (color != None or bgcolor != None):
self.colors[scope] = {
"name": name,
"color": self.strip_color(color),
"bgcolor": self.strip_color(bgcolor),
"style": style
}

def strip_color(self, color, simple_strip=False):
if color is None:
return color
elif not self.strip_trans:
return color.replace(" ", "")
rgba = RGBA(color.replace(" ", ""))
if not simple_strip:
rgba.apply_alpha(self.bground if self.bground != "" else "#FFFFFF")
if self.track_dark_background:
lumens = rgba.luminance()
if self.dark_lumens is None or lumens < self.dark_lumens:
self.dark_lumens = lumens
return rgba.get_rgb()

def get_general_colors(self):
return self.bground, self.fground, self.sbground, self.sfground, self.gbground, self.gfground

def get_darkest_lumen(self):
return self.dark_lumens

def get_plist_file(self):
return self.plist_file

def get_scheme_file(self):
return self.scheme_file

def guess_color(self, view, pt, scope_key):
color = self.fground
bgcolor = self.bground
style = set([])
color_selector = "foreground"
style_selectors = {"bold": "", "italic": ""}
bg_selector = "background"
if scope_key in self.matched:
color = self.matched[scope_key]["color"]
style = self.matched[scope_key]["style"]
bgcolor = self.matched[scope_key]["bgcolor"]
selectors = self.matched[scope_key]["selectors"]
color_selector, bg_selector, style_selectors = selectors["color"], selectors["background"], selectors["style"]
else:
best_match_bg = 0
best_match_fg = 0
best_match_style = 0
for key in self.colors:
match = view.score_selector(pt, key)
if self.colors[key]["color"] is not None and match > best_match_fg:
best_match_fg = match
color = self.colors[key]["color"]
color_selector = self.colors[key]["name"]
if self.colors[key]["style"] is not None and match > best_match_style:
best_match_style = match
for s in self.colors[key]["style"]:
style.add(s)
if s == "bold":
style_selectors["bold"] = self.colors[key]["name"]
elif s == "italic":
style_selectors["italic"] = self.colors[key]["name"]
if self.colors[key]["bgcolor"] is not None and match > best_match_bg:
best_match_bg = match
bgcolor = self.colors[key]["bgcolor"]
bg_selector = self.colors[key]["name"]
self.matched[scope_key] = {
"color": color, "bgcolor": bgcolor,
"style": style, "selectors": {
"color": color_selector,
"background": bg_selector,
"style": style_selectors
}
}
if len(style) == 0:
style = "normal"
else:
style = ' '.join(style)
return color, style, bgcolor, color_selector, bg_selector, style_selectors

def shift_background_brightness(self, lumens_limit):
dlumen = self.get_darkest_lumen()
if dlumen is not None and dlumen < lumens_limit:
factor = 1 + ((lumens_limit - dlumen) / 255.0)
for k, v in self.colors.items():
fg, bg = v["color"], v["bgcolor"]
if v["color"] is not None:
self.colors[k]["color"] = self.apply_brightness(v["color"], factor)
if v["bgcolor"] is not None:
self.colors[k]["bgcolor"] = self.apply_brightness(v["bgcolor"], factor)
self.bground = self.apply_brightness(self.bground, factor)
self.fground = self.apply_brightness(self.fground, factor)
self.sbground = self.apply_brightness(self.sbground, factor)
if self.sfground is not None:
self.sfground = self.apply_brightness(self.sfground, factor)
self.gbground = self.apply_brightness(self.gbground, factor)
self.gfground = self.apply_brightness(self.gfground, factor)

def apply_brightness(self, color, shift_factor):
rgba = RGBA(color)
if shift_factor is not None:
rgba.brightness(shift_factor)
return rgba.get_rgb()
173 changes: 173 additions & 0 deletions ScopeHunterLib/rgba.py
@@ -0,0 +1,173 @@
"""
RGBA
Licensed under MIT
Copyright (c) 2012 Isaac Muse <isaacmuse@gmail.com>
"""

import re
from colorsys import rgb_to_hls, hls_to_rgb, rgb_to_hsv, hsv_to_rgb

RGB_CHANNEL_SCALE = 1.0 / 255.0
HUE_SCALE = 1.0 / 360.0


def clamp(value, mn, mx):
return max(min(value, mx), mn)


class RGBA(object):
r = None
g = None
b = None
a = None
color_pattern = re.compile(r"^#(?:([A-Fa-f\d]{6})([A-Fa-f\d]{2})?|([A-Fa-f\d]{3}))")

def __init__(self, s=None):
if s is None:
s = "#000000FF"
self.r, self.g, self.b, self.a = self._split_channels(s)

def _split_channels(self, s):
def alpha_channel(alpha):
return int(alpha, 16) if alpha else 0xFF

m = self.color_pattern.match(s)
assert(m is not None)
if m.group(1):
return int(s[1:3], 16), int(s[3:5], 16), int(s[5:7], 16), alpha_channel(m.group(2))
else:
return int(s[1] * 2, 16), int(s[2] * 2, 16), int(s[3] * 2, 16), 0xFF

def get_rgba(self):
return "#%02X%02X%02X%02X" % (self.r, self.g, self.b, self.a)

def get_rgb(self):
return "#%02X%02X%02X" % (self.r, self.g, self.b)

def apply_alpha(self, background="#000000AA"):
def tx_alpha(cf, af, cb, ab):
return abs(cf * af + cb * ab * (1 - af)) & 0xFF

if self.a < 0xFF:
r, g, b, a = self._split_channels(background)

self.r, self.g, self.b = (tx_alpha(self.r, self.a, r, a), tx_alpha(self.g, self.a, g, a), tx_alpha(self.b, self.a, b, a))

return self.get_rgb()

def luminance(self):
return clamp(int(round(0.299 * self.r + 0.587 * self.g + 0.114 * self.b)), 0, 255)

def tohsv(self):
return rgb_to_hsv(self.r * RGB_CHANNEL_SCALE, self.g * RGB_CHANNEL_SCALE, self.b * RGB_CHANNEL_SCALE)

def fromhsv(self, h, s, v):
r, g, b = hsv_to_rgb(h, s, v)
self.r = int(round(r * 255)) & 0xFF
self.g = int(round(g * 255)) & 0xFF
self.b = int(round(b * 255)) & 0xFF

def tohls(self):
return rgb_to_hls(self.r * RGB_CHANNEL_SCALE, self.g * RGB_CHANNEL_SCALE, self.b * RGB_CHANNEL_SCALE)

def fromhls(self, h, l, s):
r, g, b = hls_to_rgb(h, l, s)
self.r = int(round(r * 255)) & 0xFF
self.g = int(round(g * 255)) & 0xFF
self.b = int(round(b * 255)) & 0xFF

def colorize(self, deg):
h, l, s = self.tohls()
h = clamp(deg * HUE_SCALE, 0.0, 1.0)
self.fromhls(h, l, s)

def hue(self, deg):
d = deg * HUE_SCALE
h, l, s = self.tohls()
h = h + d
while h > 1.0:
h -= 1.0
while h < 0.0:
h += 1.0
self.fromhls(h, l, s)

def invert(self):
self.r ^= 0xFF
self.g ^= 0xFF
self.b ^= 0xFF

def saturation(self, factor):
h, l, s = self.tohls()
s = clamp(s * factor, 0.0, 1.0)
self.fromhls(h, l, s)

def grayscale(self):
luminance = self.luminance() & 0xFF
self.r = luminance
self.g = luminance
self.b = luminance

def sepia(self):
r = clamp(int((self.r * .393) + (self.g * .769) + (self.b * .189)), 0, 255) & 0xFF
g = clamp(int((self.r * .349) + (self.g * .686) + (self.b * .168)), 0, 255) & 0xFF
b = clamp(int((self.r * .272) + (self.g * .534) + (self.b * .131)), 0, 255) & 0xFF
self.r, self.g, self.b = r, g, b

def brightness(self, factor):
# Caculate brightness based on RGB luminance.
# Maybe HLS or HSV brightness adjustment is better?
def get_overage(c):
if c < 0.0:
o = 0.0 + c
c = 0.0
elif c > 255.0:
o = c - 255.0
c = 255.0
else:
o = 0.0
return o, c

def distribute_overage(c, o, s):
channels = len(s)
if channels == 0:
return c
parts = o / len(s)
if "r" in s and "g" in s:
c = c[0] + parts, c[1] + parts, c[2]
elif "r" in s and "b" in s:
c = c[0] + parts, c[1], c[2] + parts
elif "g" in s and "b" in s:
c = c[0], c[1] + parts, c[2] + parts
elif "r" in s:
c = c[0] + parts, c[1], c[2]
elif "g" in s:
c = c[0], c[1] + parts, c[2]
else: # "b" in s:
c = c[0], c[1], c[2] + parts
return c

channels = ["r", "g", "b"]
total_lumes = clamp(self.luminance() + (255.0 * factor) - 255.0, 0.0, 255.0)

if total_lumes == 255:
# white
self.r, self.g, self.b = 0xFF, 0xFF, 0xFF
elif total_lumes == 0:
# black
self.r, self.g, self.b = 0x00, 0x00, 0x00
else:
# Adjust Brightness
pts = (total_lumes - 0.299 * self.r - 0.587 * self.g - 0.114 * self.b)
slots = set(channels)
components = [float(self.r) + pts, float(self.g) + pts, float(self.b) + pts]
count = 0
for c in channels:
overage, components[count] = get_overage(components[count])
if overage:
slots.remove(c)
components = list(distribute_overage(components, overage, slots))
count += 1

self.r = int(round(components[0])) & 0xFF
self.g = int(round(components[1])) & 0xFF
self.b = int(round(components[2])) & 0xFF
23 changes: 1 addition & 22 deletions readme.md
Expand Up @@ -19,6 +19,7 @@ All features are configurable via the settings file
- Copy scope(s) to clipboard
- Multi-select support for all output except status bar
- Highlight scope extent
- Show color scheme colors and selectors (may not work under some linux distros)

# License

Expand All @@ -31,25 +32,3 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# Version 0.4.1
- Console logging is back

# Version 0.4.0
- Add highlight scope extent option
- Remove console logging

# Version 0.3.0
- Fix regression with on demand command

# Version 0.2.1
- Fix regression with on demand command

# Version 0.2.0
- Fix console setting not being checked
- Fix output format
- Do not log duplicate entries to console
- Ignore widget views

# Version 0.1.0
- First release

0 comments on commit 4ef8f87

Please sign in to comment.