mirror of
https://github.com/google/pebble.git
synced 2025-05-20 10:24:58 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
456
tools/snowy_colors.py
Executable file
456
tools/snowy_colors.py
Executable file
|
@ -0,0 +1,456 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# coding=utf-8
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
from math import sqrt
|
||||
import re
|
||||
import urllib2
|
||||
import sys
|
||||
from os.path import basename, splitext
|
||||
|
||||
COLORS_JSON_PREFIX = splitext(basename(__file__))[0]
|
||||
COLORLOVERS_COLORS_JSON = COLORS_JSON_PREFIX + "_colorlovers.json"
|
||||
WIKIPEDIA_COLORS_JSON = COLORS_JSON_PREFIX + "_wikipedia.json"
|
||||
|
||||
def download_values_from_color_lovers(r, g, b):
|
||||
"""
|
||||
Returns values for a single color from colourlovers.com
|
||||
|
||||
NOTE: does a single HTTP request per call, please call wisely
|
||||
"""
|
||||
|
||||
url = "http://www.colourlovers.com/api/color/%02x%02x%02x?format=json" % (r, g, b)
|
||||
opener = urllib2.build_opener()
|
||||
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
|
||||
response = opener.open(url)
|
||||
s = response.read()
|
||||
values = json.loads(s)
|
||||
print "r: %03d, g:%03d, b:%03d: %s" % (r, g, b, values)
|
||||
return values
|
||||
|
||||
|
||||
def download_all_colors_from_color_lovers():
|
||||
"""
|
||||
Requests and caches into colorlovers_colors.json all 64 colors
|
||||
"""
|
||||
colors = []
|
||||
for r2 in range(0, 4):
|
||||
for g2 in range(0, 4):
|
||||
for b2 in range(0, 4):
|
||||
colors += download_values_from_color_lovers(r2*85, g2*85, b2*85)
|
||||
|
||||
with open(COLORLOVERS_COLORS_JSON, "w") as f:
|
||||
json.dump({"colors": colors}, f, indent=2)
|
||||
|
||||
def load_cached_colorlovers_colors():
|
||||
"""
|
||||
Loads cached color values from colourlovers.com and converts them to the expected format
|
||||
"""
|
||||
with open(COLORLOVERS_COLORS_JSON) as f:
|
||||
colors = json.load(f)["colors"]
|
||||
return [{"r": c["rgb"]["red"], "g": c["rgb"]["green"], "b": c["rgb"]["blue"], "name": c["title"], "source": c["url"]} for c in colors]
|
||||
|
||||
|
||||
def parse_colors_from_wikipedia_html(html):
|
||||
"""
|
||||
Requests and return all color values from a single, paginated wikipedia "list of colors" page
|
||||
"""
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
url_base = "http://en.wikipedia.org"
|
||||
colors = []
|
||||
soup = BeautifulSoup(html)
|
||||
for tr in soup.find("table").find_all("tr"):
|
||||
tds = tr.find_all("td")
|
||||
if len(tds) == 9:
|
||||
hex = tds[0].text.strip()
|
||||
r = int(hex[1:1+2], 16)
|
||||
g = int(hex[3:3+2], 16)
|
||||
b = int(hex[5:5+2], 16)
|
||||
color = {"r": r, "g": g, "b": b}
|
||||
|
||||
th_a = tr.find("th").find("a")
|
||||
if th_a is None:
|
||||
color["name"] = tr.find("th").text.strip()
|
||||
color["url"] = "http://en.wikipedia.org/wiki/List_of_colors"
|
||||
else:
|
||||
color["name"] = th_a.text.strip()
|
||||
color["url"] = url_base + th_a["href"]
|
||||
|
||||
print color
|
||||
colors.append(color)
|
||||
|
||||
return colors
|
||||
|
||||
|
||||
def download_and_parse_colors_from_wikipedia():
|
||||
"""
|
||||
Requests and caches into wikipedia_colors.json all available colors from wikipedias "list of colors" pages
|
||||
"""
|
||||
import wikipedia
|
||||
|
||||
colors = []
|
||||
for title in ["List of colors: A-F", "List of colors: G-M", "List of colors: N-Z"]:
|
||||
colors += parse_colors_from_wikipedia_html(wikipedia.page(title).html())
|
||||
|
||||
with open(WIKIPEDIA_COLORS_JSON, "w") as f:
|
||||
json.dump({"colors": colors}, f, indent=2)
|
||||
return colors
|
||||
|
||||
|
||||
def load_cached_wikipedia_colors():
|
||||
"""
|
||||
loads all previously cached colors from wikipedia
|
||||
"""
|
||||
with open(WIKIPEDIA_COLORS_JSON) as f:
|
||||
return [copy.copy(c) for c in json.load(f)["colors"]]
|
||||
|
||||
|
||||
def hardwired_colors():
|
||||
"""
|
||||
creates a set of hard-wired colors. Used to overrule any other source.
|
||||
"""
|
||||
result = []
|
||||
|
||||
result.append({'r': 0, 'g': 85, 'b': 255, 'name': u'Blue Moon', 'url': 'http://en.wikipedia.org/wiki/Blue_Moon_(beer)'})
|
||||
result.append({'r': 0, 'g': 170, 'b': 85, 'name': u'Jaeger Green', 'url': 'http://en.wikipedia.org/wiki/Jägermeister'})
|
||||
|
||||
result.append({'r': 0, 'g': 255, 'b': 0, 'name': u'Green', 'url': 'http://en.wikipedia.org/wiki/Green'})
|
||||
result.append({'r': 0, 'g': 255, 'b': 255, 'name': u'Cyan', 'url': 'http://en.wikipedia.org/wiki/Cyan'})
|
||||
result.append({'r': 255, 'g': 0, 'b': 255, 'name': u'Magenta', 'url': 'http://en.wikipedia.org/wiki/Magenta'})
|
||||
result.append({'r': 255, 'g': 0, 'b': 170, 'name': u'Fashion Magenta', 'url': 'http://en.wikipedia.org/wiki/Fuchsia_(color)#Fashion_fuchsia'})
|
||||
result.append({'r': 255, 'g': 255, 'b': 0, 'name': u'Yellow', 'url': 'http://en.wikipedia.org/wiki/Yellow'})
|
||||
result.append({'r': 255, 'g': 85, 'b': 0, 'name': u'Orange', 'url': 'http://en.wikipedia.org/wiki/Orange_(colour)'}) # verify with display
|
||||
result.append({'r': 170, 'g': 0, 'b': 170, 'name': u'Purple', 'url': 'http://en.wikipedia.org/wiki/Purple'}) # verify with display
|
||||
# TODO: find brown value, core graphics says: r:0.6,g:0.4,b:0.2
|
||||
|
||||
# colors to match CoreGraphics names
|
||||
result.append({'r': 85, 'g': 85, 'b': 85, 'name': u'Dark Gray', 'url': 'http://en.wikipedia.org/wiki/Shades_of_gray#Dark_medium_gray_.28dark_gray_.28X11.29.29'})
|
||||
result.append({'r': 170, 'g': 170, 'b': 170, 'name': u'Light Gray', 'url': 'http://en.wikipedia.org/wiki/Shades_of_gray#Light_gray'})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def color_dist(a, b):
|
||||
# TODO: use YUV dist
|
||||
sum = 0
|
||||
for c in ["r", "g", "b"]:
|
||||
sum += abs(a[c] - b[c]) ** 2
|
||||
return sqrt(sum)
|
||||
|
||||
|
||||
def closest_color(c, colors):
|
||||
min_dist = None
|
||||
min_value = None
|
||||
for candidate in colors:
|
||||
dist = color_dist(c, candidate)
|
||||
if min_dist is None or dist < min_dist:
|
||||
min_dist = dist
|
||||
min_value = candidate
|
||||
if dist == 0:
|
||||
break
|
||||
return min_value
|
||||
|
||||
|
||||
def enhanced_color(color):
|
||||
"""
|
||||
Add additional, derived data to a color used for json output, c header file generation, etc.
|
||||
"""
|
||||
result = copy.copy(color)
|
||||
result["identifier"] = re.sub(r"\([^\)]+\)|[\s_'-]", " ", color["name"]).title().replace(" ", "")
|
||||
result["name"] = result["name"].title()
|
||||
r = result["r"]
|
||||
g = result["g"]
|
||||
b = result["b"]
|
||||
r2 = r / 85
|
||||
g2 = g / 85
|
||||
b2 = b / 85
|
||||
|
||||
c_identifier = "GColor%s" % result["identifier"]
|
||||
result["c_identifier"] = c_identifier
|
||||
result["c_value_identifier"] = "GColor%sARGB8" % result["identifier"]
|
||||
hex_value = "0x%0.2X%0.2X%0.2X" % (r, g, b)
|
||||
html_value = "#%0.2X%0.2X%0.2X" % (r, g, b)
|
||||
result["html"] = html_value
|
||||
binary = "0b11{0:02b}{1:02b}{2:02b}".format(r2, g2, b2)
|
||||
result["binary"] = binary
|
||||
|
||||
result["literals"] = [
|
||||
{"id": "define", "description": "SDK Constant", "value": c_identifier},
|
||||
{"id": "rgb", "description": "Code (RGB)", "value": "GColorFromRGB(%d, %d, %d)" % (r, g, b)},
|
||||
{"id": "hex", "description": "Code (Hex)", "value": "GColorFromHEX(%s)" % hex_value},
|
||||
{"id": "html", "description": "HTML code", "value": html_value},
|
||||
{"id": "gcolor_argb", "description": "GColor (argb)", "value": "(GColor){.argb=%s}" % binary},
|
||||
{"id": "gcolor_fields", "description": "GColor (components)", "value": "(GColor){{.a=0b11, .r=0b{0:02b}, .g=0b{1:02b}, .b=0b{2:02b}}}".format(r2, g2, b2)},
|
||||
]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def validate_colors(colors):
|
||||
"""
|
||||
Some sanity checks on the set of colors.
|
||||
"""
|
||||
if len(colors) != 64:
|
||||
raise Exception("Number of derived colors (%d) is different from expectation (64)", len(colors))
|
||||
|
||||
for c in colors:
|
||||
if len([cc for cc in colors if cc["identifier"] == c["identifier"]]) != 1:
|
||||
raise Exception("duplicate identifier name: %s and %s", c["name"])
|
||||
|
||||
|
||||
def all_colors_with_names():
|
||||
"""
|
||||
Will construct a set of our 64 colors with the closest colors from a hard-wired set of sources.
|
||||
"""
|
||||
candidates = []
|
||||
candidates += hardwired_colors()
|
||||
try:
|
||||
candidates += load_cached_wikipedia_colors()
|
||||
# for now, we only look at colors from wikipedia
|
||||
# color lovers code can be deleted as soon as we agreed on final color names
|
||||
# candidates += load_colorlovers_colors()
|
||||
except IOError, e:
|
||||
raise IOError("%s\n\n%s" % (e, "make sure you called --download_wikipedia once"))
|
||||
|
||||
result = []
|
||||
for r2 in range(0, 4):
|
||||
for g2 in range(0, 4):
|
||||
for b2 in range(0, 4):
|
||||
c = {"r": r2 * 85, "g": g2 * 85, "b": b2 * 85}
|
||||
closest = closest_color(c, candidates)
|
||||
dist = color_dist(c, closest)
|
||||
c["dist"] = dist
|
||||
c["closest"] = closest
|
||||
for k in ["name", "url"]:
|
||||
if k in closest:
|
||||
c[k] = closest[k]
|
||||
result.append(enhanced_color(c))
|
||||
|
||||
validate_colors(result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def render_header(colors):
|
||||
"""
|
||||
produces the contents of color_definitions.h
|
||||
"""
|
||||
color_value_maxlen = max([len(c["c_value_identifier"]) for c in colors])
|
||||
color_value_defines = []
|
||||
color_value_defines.append("//%s AARRGGBB" % "".ljust(color_value_maxlen + len("#define (uint_8_t)")))
|
||||
for c in colors:
|
||||
identifier = c["c_value_identifier"]
|
||||
color_value_defines.append(
|
||||
"#define %s ((uint8_t)%s)" % (identifier.ljust(color_value_maxlen), c["binary"])
|
||||
)
|
||||
|
||||
color_define_maxlen = max([len(c["c_identifier"]) for c in colors])
|
||||
color_defines = []
|
||||
for c in colors:
|
||||
identifier = c["c_identifier"]
|
||||
value_identifier = c["c_value_identifier"]
|
||||
hex_value = "#%0.2X%0.2X%0.2X" % (c["r"], c["g"], c["b"])
|
||||
color_defines.append("")
|
||||
color_defines.append(
|
||||
"//! <span class=\"gcolor_sample\" style=\"background-color: %s;\"></span> <a href=\"https://developer.getpebble.com/tools/color-picker/%s\">%s</a>"
|
||||
% (hex_value, hex_value, identifier))
|
||||
color_defines.append(
|
||||
"#define %s (GColor8){.argb=%s}" % (identifier.ljust(color_define_maxlen), value_identifier)
|
||||
)
|
||||
|
||||
file_content = """#pragma once
|
||||
|
||||
// @%s
|
||||
// THIS FILE HAS BEEN GENERATED, PLEASE DON'T MODIFY ITS CONTENT MANUALLY
|
||||
// USE <TINTIN_ROOT>/tools/%s TO MAKE CHANGES
|
||||
|
||||
//! @addtogroup Graphics
|
||||
//! @{
|
||||
|
||||
//! @addtogroup GraphicsTypes
|
||||
//! @{
|
||||
|
||||
//! Convert RGBA to GColor.
|
||||
//! @param red Red value from 0 - 255
|
||||
//! @param green Green value from 0 - 255
|
||||
//! @param blue Blue value from 0 - 255
|
||||
//! @param alpha Alpha value from 0 - 255
|
||||
//! @return GColor created from the RGBA values
|
||||
#define GColorFromRGBA(red, green, blue, alpha) ((GColor8){ \\
|
||||
.a = (uint8_t)(alpha) >> 6, \\
|
||||
.r = (uint8_t)(red) >> 6, \\
|
||||
.g = (uint8_t)(green) >> 6, \\
|
||||
.b = (uint8_t)(blue) >> 6, \\
|
||||
})
|
||||
|
||||
//! Convert RGB to GColor.
|
||||
//! @param red Red value from 0 - 255
|
||||
//! @param green Green value from 0 - 255
|
||||
//! @param blue Blue value from 0 - 255
|
||||
//! @return GColor created from the RGB values
|
||||
#define GColorFromRGB(red, green, blue) \\
|
||||
GColorFromRGBA(red, green, blue, 255)
|
||||
|
||||
|
||||
//! Convert hex integer to GColor.
|
||||
//! @param v Integer hex value (e.g. 0x64ff46)
|
||||
//! @return GColor created from the hex value
|
||||
#define GColorFromHEX(v) GColorFromRGB(((v) >> 16) & 0xff, ((v) >> 8) & 0xff, ((v) & 0xff))
|
||||
|
||||
//! @addtogroup ColorDefinitions Color Definitions
|
||||
//! A list of all of the named colors available with links to the color map on the Pebble Developer website.
|
||||
//! @{
|
||||
|
||||
// 8bit color values of all natively supported colors
|
||||
%s
|
||||
|
||||
// GColor values of all natively supported colors
|
||||
%s
|
||||
|
||||
// Additional 8bit color values
|
||||
#define GColorClearARGB8 ((uint8_t)0b00000000)
|
||||
|
||||
// Additional GColor values
|
||||
#define GColorClear ((GColor8){.argb=GColorClearARGB8})
|
||||
|
||||
//! @} // group ColorDefinitions
|
||||
|
||||
//! @} // group GraphicsTypes
|
||||
|
||||
//! @} // group Graphics
|
||||
|
||||
""" % ("generated", basename(__file__), "\n".join(color_value_defines), "\n".join(color_defines))
|
||||
|
||||
return file_content
|
||||
|
||||
|
||||
def render_html(colors):
|
||||
"""
|
||||
renders a HTML file for debugging purposes. Not even close to the awesome Pebble color picker(tm)
|
||||
"""
|
||||
html = '<table style="border-spacing:0"><thead>'
|
||||
html += '<tr><th colspan="4">Closest</th><th colspan="7">Actual Color</th></tr>'
|
||||
html += "<tr>%s</tr>" % "".join(["<th>%s</th>" % s for s in ["r", "g", "b", "color", "color", "Δ", "r", "g", "b", "c code", "name", "identifier"]])
|
||||
html += "</thead><tbody>"
|
||||
for c in colors:
|
||||
def rgb(c):
|
||||
return '<td>%d</td><td>%d</td><td>%d</td>' % (c["r"], c["g"], c["b"])
|
||||
|
||||
def color(c):
|
||||
return '<td style="background-color:rgb(%d,%d,%d); width:4em;"></td>' % (c["r"], c["g"], c["b"])
|
||||
|
||||
def c_code(c):
|
||||
return "(GColor){{.rgba=0b{:02b}{:02b}{:02b}11}}".format(c["r"] / 64, c["g"] / 64, c["b"] / 64)
|
||||
|
||||
html += '<tr>'
|
||||
html += rgb(c["closest"])
|
||||
html += color(c["closest"])
|
||||
html += color(c)
|
||||
html += '<td><strong>%d</strong></td>' % c["dist"]
|
||||
html += rgb(c)
|
||||
html += '<td><pre>'+c_code(c)+'</pre></td>'
|
||||
|
||||
html += '<td>'
|
||||
if "url" in c:
|
||||
html += '<a href="%s">%s</a>' % (c["url"], c["name"])
|
||||
else:
|
||||
html += c["name"]
|
||||
html += '<td>%s</td>' % c["identifier"]
|
||||
|
||||
html += "</tr>"
|
||||
|
||||
|
||||
html += "</tbody></table>"
|
||||
|
||||
return html
|
||||
|
||||
def render_json(colors):
|
||||
"""
|
||||
JSON as being used by the awesome Pebble color picker(tm)
|
||||
"""
|
||||
obj = {}
|
||||
for c in colors:
|
||||
color_attr = "#%0.2X%0.2X%0.2X" % (c["r"], c["g"], c["b"])
|
||||
obj[color_attr] = c
|
||||
|
||||
return json.dumps(obj, indent=2)
|
||||
|
||||
|
||||
def render_svg(colors=None):
|
||||
"""
|
||||
renders all 64 colors, ignores provided colors (only there to share same signature with other functions
|
||||
"""
|
||||
|
||||
polygons = []
|
||||
|
||||
dd = 300
|
||||
for r in range(4):
|
||||
yy = r * dd
|
||||
xx = -742-dd
|
||||
for g in range(4):
|
||||
for b in range(4):
|
||||
xx += dd
|
||||
points = [(850,75), (958,137.5), (958,262.5), (850,325), (742,262.6), (742,137.5)]
|
||||
points = [(p[0]+xx, p[1]+yy) for p in points]
|
||||
|
||||
points_attr = " ".join(["%f,%f" % (p[0], p[1]) for p in points])
|
||||
color_attr = "#%0.2X%0.2X%0.2X" % (r * 85, g * 85, b * 85)
|
||||
polygon = """<polygon fill="%s" stroke="black" stroke-width=".1" points="%s" />""" % (color_attr, points_attr)
|
||||
polygons.append(polygon)
|
||||
|
||||
|
||||
xml = """<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg viewBox="0 0 4000 4000"
|
||||
xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
%s
|
||||
</svg>""" % "\n".join(polygons)
|
||||
|
||||
return xml
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# e.g. --download_wikipedia --json snowy_colors.json --header ../src/fw/applib/graphics/gcolor_definitions.h
|
||||
parser = argparse.ArgumentParser(description="Generate various files that contain Snowy's 64 colors")
|
||||
parser.add_argument("--download_wikipedia", action='store_true', help="loads and caches colors from wikipedia")
|
||||
parser.add_argument("--download_colorlovers", action='store_true', help="loads and caches colors from colourlovers.com")
|
||||
parser.add_argument("--html", help="generates HTML file for test purposes")
|
||||
parser.add_argument("--json", help="generates JSON used by awesome Pebble color picker(tm)")
|
||||
parser.add_argument("--header", help="generates C header file that can replace color_definitions.h")
|
||||
parser.add_argument("--svg", help="generates SVG file with hexagons of all supported colors")
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
parser.print_usage()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.download_wikipedia:
|
||||
download_and_parse_colors_from_wikipedia()
|
||||
if args.download_colorlovers:
|
||||
download_all_colors_from_color_lovers()
|
||||
|
||||
colors = all_colors_with_names()
|
||||
for k, v in {"html": render_html, "json": render_json, "header": render_header, "svg": render_svg}.items():
|
||||
file_name = getattr(args, k)
|
||||
if file_name is not None:
|
||||
with open(file_name, "w") as f:
|
||||
f.write(v(colors))
|
Loading…
Add table
Add a link
Reference in a new issue