master
John Lauer Laser mark by the planner (adom-aiflow 0.1.49): re-etched, outlines regenerated, GLB / hero / outline symbol rebuilt; l… 6827ebd 16d ago
"""Trace the Worldsemi logo mark (ring + WS, wordmark dropped) from world-semi.com/logo.png into polygons with holes.
Pixel-edge boundary tracing at 200 px across the ring (4.4 um steps at 0.875 mm), collinear points dropped.
Output JSON: rings as fractions of the ring's outer diameter, centred on the ring, y up; outers counter-clockwise."""
import json, sys
from collections import defaultdict
from PIL import Image
src, out = sys.argv[1], sys.argv[2]
im = Image.open(src).convert('RGBA'); W, H = im.size
im = im.crop((0, 0, W, 482)); px = im.load()                       # the wordmark starts below the ring
ink = lambda c: c[3] > 128 and c[2] - c[0] > 18 and c[0] < 235                    # the logo's blue
pts = [(x, y) for y in range(482) for x in range(W) if ink(px[x, y])]
x0, y0 = min(p[0] for p in pts), min(p[1] for p in pts); x1, y1 = max(p[0] for p in pts), max(p[1] for p in pts)
N = 200
im = im.crop((x0, y0, x1 + 1, y1 + 1)); D = max(im.size)
sq = Image.new('RGBA', (D, D), (255, 255, 255, 0)); sq.paste(im, ((D - im.size[0]) // 2, (D - im.size[1]) // 2))
sq = sq.resize((N, N), Image.LANCZOS); p = sq.load()
on = lambda x, y: 0 <= x < N and 0 <= y < N and ink(p[x, y])
# directed boundary edges in y-up coordinates (Y = N - y), filled side on the left
nxt = defaultdict(list)
for y in range(N):
    for x in range(N):
        if not on(x, y): continue
        X0, X1, Yt, Yb = x, x + 1, N - y, N - y - 1
        if not on(x, y + 1): nxt[(X0, Yb)].append((X1, Yb))         # bottom edge, left to right
        if not on(x + 1, y): nxt[(X1, Yb)].append((X1, Yt))         # right edge, upward
        if not on(x, y - 1): nxt[(X1, Yt)].append((X0, Yt))         # top edge, right to left
        if not on(x - 1, y): nxt[(X0, Yt)].append((X0, Yb))         # left edge, downward
rings = []
while any(nxt.values()):
    s = next(k for k, v in nxt.items() if v); ring = [s]; cur = s; prev = None
    while True:
        cands = nxt[cur]
        if prev is not None and len(cands) > 1:                      # pinch vertex: turn left first (keeps rings simple)
            dx, dy = cur[0] - prev[0], cur[1] - prev[1]
            left = (cur[0] - dy, cur[1] + dx)
            n = left if left in cands else cands[0]
        else:
            n = cands[0]
        cands.remove(n); prev, cur = cur, n
        if cur == s: break
        ring.append(cur)
    simp = [q for i, q in enumerate(ring) if not ((ring[i - 1][0] == q[0] == ring[(i + 1) % len(ring)][0]) or (ring[i - 1][1] == q[1] == ring[(i + 1) % len(ring)][1]))]
    rings.append(simp)
area = lambda r: sum(r[i - 1][0] * r[i][1] - r[i][0] * r[i - 1][1] for i in range(len(r))) / 2
def inside(pt, r):
    x, y = pt; c = False
    for i in range(len(r)):
        (xa, ya), (xb, yb) = r[i - 1], r[i]
        if (ya > y) != (yb > y) and x < xa + (y - ya) * (xb - xa) / (yb - ya): c = not c
    return c
outers = [r for r in rings if area(r) > 0]; holes = [r for r in rings if area(r) < 0]
polys = [[o] for o in outers]
for h in holes:
    t = (h[0][0] + 0.5, h[0][1] - 0.5) if False else (sum(q[0] for q in h[:2]) / 2 + 0.01, sum(q[1] for q in h[:2]) / 2 + 0.01)
    own = [i for i, o in enumerate(outers) if inside(h[0], o) or inside(t, o)]
    if own: polys[min(own, key=lambda i: abs(area(outers[i])))].append(h)
c = N / 2
norm = lambda r: [[round((x - c) / N, 5), round((y - c) / N, 5)] for x, y in r]
json.dump(dict(source='https://www.world-semi.com/logo.png (wordmark dropped)', unit='fraction of the ring outer diameter, centred, y up',
               polys=[[norm(r) for r in pl] for pl in polys]), open(out, 'w'))
print(json.dumps(dict(polys=len(polys), holes=len(holes), points=sum(len(r) for r in rings))))