mirror of
https://github.com/google/pebble.git
synced 2025-03-15 16:51:21 +00:00
456 lines
16 KiB
Python
Executable file
456 lines
16 KiB
Python
Executable file
#!/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))
|