| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
import math |
| 17 |
import struct |
| 18 |
import sys |
| 19 |
|
| 20 |
CYAN, BLUE, WHITE, TILE = (0x00, 0xF0, 0xFF), (0x00, 0x72, 0xFF), (0xFF, 0xFF, 0xFF), (0x07, 0x0C, 0x18) |
| 21 |
LIGHT, EDGE = (0xF2, 0xF7, 0xFF), (0xC9, 0xDC, 0xF5) |
| 22 |
K = 0.5522847498 |
| 23 |
|
| 24 |
|
| 25 |
def coord(v): |
| 26 |
if v == int(v) and -32 <= v <= 95: |
| 27 |
return bytes([int(v) + 32]) |
| 28 |
n = int(round((v + 128.0) * 102.0)) |
| 29 |
return bytes([(n >> 8) | 0x80, n & 0xFF]) |
| 30 |
|
| 31 |
|
| 32 |
def float24(v): |
| 33 |
if v == 0: |
| 34 |
return b"\0\0\0" |
| 35 |
bits = struct.unpack(">I", struct.pack(">f", v))[0] |
| 36 |
sign, exp, man = bits >> 31, ((bits >> 23) & 0xFF) - 127, bits & 0x7FFFFF |
| 37 |
if not -32 <= exp < 32: |
| 38 |
raise ValueError(f"{v} does not fit a float24") |
| 39 |
n = (sign << 23) | ((exp + 32) << 17) | (man >> 6) |
| 40 |
return n.to_bytes(3, "big") |
| 41 |
|
| 42 |
|
| 43 |
def solid(rgb): |
| 44 |
return bytes([3, *rgb]) |
| 45 |
|
| 46 |
|
| 47 |
def linear(p1, p2, stops): |
| 48 |
|
| 49 |
dx, dy = (p2[0] - p1[0]) / 128.0, (p2[1] - p1[1]) / 128.0 |
| 50 |
mx, my = (p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2 |
| 51 |
out = bytes([2, 0, (1 << 1) | (1 << 2), len(stops)]) |
| 52 |
for v in (dx, dy, -dy, dx, mx, my): |
| 53 |
out += float24(v) |
| 54 |
for off, rgb in stops: |
| 55 |
out += bytes([off, *rgb]) |
| 56 |
return out |
| 57 |
|
| 58 |
|
| 59 |
def line(a, b): |
| 60 |
return bytes([1 << 3, 2]) + coord(a[0]) + coord(a[1]) + coord(b[0]) + coord(b[1]) |
| 61 |
|
| 62 |
|
| 63 |
def curve_path(points, closed=True): |
| 64 |
|
| 65 |
out = bytes([(1 << 1) if closed else 0, len(points)]) |
| 66 |
for p in points: |
| 67 |
for v in p: |
| 68 |
out += coord(v) |
| 69 |
return out |
| 70 |
|
| 71 |
|
| 72 |
def circle(cx, cy, r): |
| 73 |
k = r * K |
| 74 |
pts = [(cx, cy - r, cx - k, cy - r, cx + k, cy - r), (cx + r, cy, cx + r, cy - k, cx + r, cy + k), |
| 75 |
(cx, cy + r, cx + k, cy + r, cx - k, cy + r), (cx - r, cy, cx - r, cy + k, cx - r, cy - k)] |
| 76 |
return curve_path(pts) |
| 77 |
|
| 78 |
|
| 79 |
def rounded(x, y, w, h, r): |
| 80 |
k = r * (1 - K) |
| 81 |
x2, y2 = x + w, y + h |
| 82 |
pts = [ |
| 83 |
(x + r, y, x + k, y, x + r, y), (x2 - r, y, x2 - r, y, x2 - k, y), |
| 84 |
(x2, y + r, x2, y + k, x2, y + r), (x2, y2 - r, x2, y2 - r, x2, y2 - k), |
| 85 |
(x2 - r, y2, x2 - k, y2, x2 - r, y2), (x + r, y2, x + r, y2, x + k, y2), |
| 86 |
(x, y2 - r, x, y2 - k, x, y2 - r), (x, y + r, x, y + r, x, y + k), |
| 87 |
] |
| 88 |
return curve_path(pts) |
| 89 |
|
| 90 |
|
| 91 |
def shape(style, paths, stroke=None, cap=0): |
| 92 |
out = bytes([10, style, len(paths), *paths]) |
| 93 |
if stroke is None: |
| 94 |
return out + bytes([0]) |
| 95 |
|
| 96 |
return out + bytes([1 << 4, 1, 23, int(round(stroke)) + 128, (cap << 4) | 2, 4]) |
| 97 |
|
| 98 |
|
| 99 |
def mark(scale, ox, oy): |
| 100 |
|
| 101 |
p = lambda x, y: (ox + x * scale, oy + y * scale) |
| 102 |
return { |
| 103 |
"top": (p(-11, -6), p(11, -6)), "bottom": (p(-11, 6), p(11, 6)), |
| 104 |
"left": (p(-11, 0), 10 * scale), "right": (p(11, 0), 10 * scale), |
| 105 |
"dot": 3.5 * scale, "stroke": 3 * scale, "grad": (p(-22, -11), p(22, 11)), |
| 106 |
} |
| 107 |
|
| 108 |
|
| 109 |
def hvif(dark): |
| 110 |
m = mark(1.0, 32, 32) |
| 111 |
styles = [linear(*m["grad"], [(0, CYAN), (255, BLUE)]), solid(CYAN), solid(WHITE if dark else BLUE)] |
| 112 |
paths = [line(*m["top"]), line(*m["bottom"]), circle(*m["left"][0], m["left"][1]), |
| 113 |
circle(*m["right"][0], m["right"][1]), circle(*m["left"][0], m["dot"]), circle(*m["right"][0], m["dot"])] |
| 114 |
if dark: |
| 115 |
styles.append(solid(TILE)) |
| 116 |
paths.append(rounded(0, 0, 64, 64, 14)) |
| 117 |
tile = [shape(3, [6])] |
| 118 |
else: |
| 119 |
|
| 120 |
styles += [solid(EDGE), solid(LIGHT)] |
| 121 |
paths += [rounded(0, 0, 64, 64, 14), rounded(1.5, 1.5, 61, 61, 12.5)] |
| 122 |
tile = [shape(3, [6]), shape(4, [7])] |
| 123 |
shapes = tile + [ |
| 124 |
shape(0, [0], m["stroke"], cap=2), shape(0, [1], m["stroke"], cap=2), |
| 125 |
shape(0, [2], m["stroke"]), shape(0, [3], m["stroke"]), |
| 126 |
shape(1, [4]), shape(2, [5]), |
| 127 |
] |
| 128 |
out = b"ncif" |
| 129 |
for section in (styles, paths, shapes): |
| 130 |
out += bytes([len(section)]) + b"".join(section) |
| 131 |
return out |
| 132 |
|
| 133 |
|
| 134 |
def svg(dark): |
| 135 |
if dark: |
| 136 |
bg, dot = '<rect width="64" height="64" rx="14" fill="#070c18"/>', "#FFFFFF" |
| 137 |
else: |
| 138 |
bg = '<rect width="64" height="64" rx="14" fill="#C9DCF5"/><rect x="1.5" y="1.5" width="61" height="61" rx="12.5" fill="#F2F7FF"/>' |
| 139 |
dot = "#0072FF" |
| 140 |
return f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"> |
| 141 |
<defs> |
| 142 |
<linearGradient id="g" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11"> |
| 143 |
<stop offset="0%" stop-color="#00F0FF"/> |
| 144 |
<stop offset="100%" stop-color="#0072FF"/> |
| 145 |
</linearGradient> |
| 146 |
</defs> |
| 147 |
{bg} |
| 148 |
<g transform="translate(32 32)"> |
| 149 |
<path d="M -11 -6 L 11 -6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/> |
| 150 |
<path d="M -11 6 L 11 6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/> |
| 151 |
<circle cx="-11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/> |
| 152 |
<circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/> |
| 153 |
<circle cx="11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/> |
| 154 |
<circle cx="11" cy="0" r="3.5" fill="{dot}"/> |
| 155 |
</g> |
| 156 |
</svg> |
| 157 |
''' |
| 158 |
|
| 159 |
|
| 160 |
out = sys.argv[1] |
| 161 |
for name, dark in (("oxy", False), ("oh", True)): |
| 162 |
open(f"{out}/{name}.hvif", "wb").write(hvif(dark)) |
| 163 |
open(f"{out}/{name}.svg", "w").write(svg(dark)) |