| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- # -*- coding: utf-8 -*-
- """DoTouchAI app icon generator — 3 variants, Huawei store spec.
- Outputs 1024x1024 / 216x216 PNG (<3MB) and WEBP (<100KB).
- """
- from PIL import Image, ImageDraw, ImageFilter
- import math, os
- OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dotouchai_logo")
- os.makedirs(OUT, exist_ok=True)
- SS = 3 # supersample factor
- BASE = 1024
- S = BASE * SS # render size
- def bilinear_bg(size, c_tl, c_tr, c_bl, c_br):
- img = Image.new('RGB', (2, 2))
- img.putpixel((0, 0), c_tl); img.putpixel((1, 0), c_tr)
- img.putpixel((0, 1), c_bl); img.putpixel((1, 1), c_br)
- return img.resize((size, size), Image.BILINEAR)
- def solid_bg(size, color):
- return Image.new('RGB', (size, size), color)
- def vgradient(size, c_top, c_bot):
- img = Image.new('RGB', (1, 256))
- for i in range(256):
- t = i / 255
- img.putpixel((0, i), tuple(round(a + (b - a) * t) for a, b in zip(c_top, c_bot)))
- return img.resize((size, size), Image.BILINEAR)
- def radial_glow(size, cx, cy, rx, ry, color, alpha, blur):
- layer = Image.new('RGBA', (size, size), (0, 0, 0, 0))
- d = ImageDraw.Draw(layer)
- d.ellipse([cx - rx, cy - ry, cx + rx, cy + ry], fill=color + (alpha,))
- return layer.filter(ImageFilter.GaussianBlur(blur))
- # ---- mark geometry (in BASE units, scaled by SS) ----
- PX, PY = 320, 512 # ripple origin (on the stem)
- STEM_W = 84
- RADII_Y = [140, 225, 310] # concentric arcs — spacing 85 > stroke 76
- RX_SCALE = 1.25
- STROKE = 76
- DOT_R = 62
- NODE_R = 40
- NODE_RING_R = 56
- NODE_ANGLES = (-40, 0, 40) # degrees on outer arc
- def draw_D_ripple(mark_mask, over_mask, under_mask, ring_mask):
- dm = ImageDraw.Draw(mark_mask)
- dov = ImageDraw.Draw(over_mask)
- dr = ImageDraw.Draw(ring_mask)
- px, py = PX * SS, PY * SS
- sw, st = STEM_W * SS, STROKE * SS
- r3y = RADII_Y[-1] * SS
- # stem (left bar of the D)
- dm.rounded_rectangle([px - sw // 2, py - r3y, px + sw // 2, py + r3y],
- radius=sw // 2, fill=255)
- # concentric arcs (right bulge of the D = touch ripples)
- for ry in RADII_Y:
- rxs, rys = int(ry * RX_SCALE * SS), ry * SS
- dm.arc([px - rxs, py - rys, px + rxs, py + rys],
- start=270, end=90, fill=255, width=st)
- # touch point (accent dot at ripple origin)
- dr_ = DOT_R * SS
- dov.ellipse([px - dr_, py - dr_, px + dr_, py + dr_], fill=255)
- # aggregation nodes on the outer arc
- rx3, ry3 = int(RADII_Y[-1] * RX_SCALE * SS), RADII_Y[-1] * SS
- for ang in NODE_ANGLES:
- t = math.radians(ang)
- nx = px + rx3 * math.cos(t)
- ny = py + ry3 * math.sin(t)
- nr, rr = NODE_R * SS, NODE_RING_R * SS
- dr.ellipse([nx - rr, ny - rr, nx + rr, ny + rr], fill=255)
- dov.ellipse([nx - nr, ny - nr, nx + nr, ny + nr], fill=255)
- def draw_D_touch(mark_mask, over_mask, under_mask, ring_mask):
- """Variant C: D ring + touch emitter (dot + ripple rings) inside."""
- dm = ImageDraw.Draw(mark_mask)
- dov = ImageDraw.Draw(over_mask)
- dun = ImageDraw.Draw(under_mask)
- px, py = PX * SS, PY * SS
- st = 96 * SS
- r3y = RADII_Y[-1] * SS
- rx3 = int(RADII_Y[-1] * RX_SCALE * SS)
- cx, cy = px + 140 * SS, py # emitter center inside counter
- # ripple rings — drawn UNDER the D stroke, clipped at the stem
- for r in (130, 195):
- rs = r * SS
- dun.ellipse([cx - rs, cy - rs, cx + rs, cy + rs],
- outline=255, width=26 * SS)
- # clip: nothing left of the stem's right edge
- dun.rectangle([0, 0, px + st // 2, S], fill=0)
- # emitter dot (over everything)
- dr_ = 66 * SS
- dov.ellipse([cx - dr_, cy - dr_, cx + dr_, cy + dr_], fill=255)
- # D outline: stem + outer arc
- dm.rounded_rectangle([px - st // 2, py - r3y, px + st // 2, py + r3y],
- radius=st // 2, fill=255)
- dm.arc([px - rx3, py - r3y, px + rx3, py + r3y],
- start=270, end=90, fill=255, width=st)
- def render(name, bg, glow, mark_fill, over_fill, under_fill, draw_fn):
- img = bg(S).convert('RGBA')
- if glow:
- img.alpha_composite(glow(S))
- mark_mask = Image.new('L', (S, S), 0)
- over_mask = Image.new('L', (S, S), 0)
- under_mask = Image.new('L', (S, S), 0)
- ring_mask = Image.new('L', (S, S), 0)
- draw_fn(mark_mask, over_mask, under_mask, ring_mask)
- def fill_layer(fill):
- return fill(S) if callable(fill) else Image.new('RGBA', (S, S), fill)
- # 1. node separator rings (punch a clean gap in the arc)
- if ring_mask.getbbox():
- img.paste(Image.new('RGBA', (S, S), (255, 255, 255, 255)),
- (0, 0), ring_mask)
- # 2. under-accent (ripples beneath the D stroke)
- if under_mask.getbbox():
- img.paste(fill_layer(under_fill), (0, 0), under_mask)
- # 3. main mark
- img.paste(fill_layer(mark_fill), (0, 0), mark_mask)
- # 4. over-accent (touch point + nodes)
- img.paste(fill_layer(over_fill), (0, 0), over_mask)
- master = img.convert('RGB').resize((BASE, BASE), Image.LANCZOS)
- small = master.resize((216, 216), Image.LANCZOS)
- p1024 = os.path.join(OUT, f"DoTouchAI_icon_{name}_1024.png")
- p216 = os.path.join(OUT, f"DoTouchAI_icon_{name}_216.png")
- pwebp = os.path.join(OUT, f"DoTouchAI_icon_{name}_1024.webp")
- master.save(p1024, optimize=True)
- small.save(p216, optimize=True)
- master.save(pwebp, 'WEBP', quality=88, method=6)
- for p in (p1024, p216, pwebp):
- print(f"{os.path.basename(p):42s} {os.path.getsize(p)/1024:8.1f} KB")
- # ---------- Variant A: deep navy, white ripple-D, cyan accent ----------
- render(
- "A",
- bg=lambda s: bilinear_bg(s, (10, 18, 48), (16, 31, 82),
- (13, 27, 74), (30, 47, 110)),
- glow=lambda s: radial_glow(s, int(500 * SS), int(512 * SS),
- int(430 * SS), int(390 * SS),
- (46, 107, 255), 70, 130 * SS),
- mark_fill=(244, 248, 255, 255),
- over_fill=lambda s: vgradient(s, (63, 224, 255), (46, 123, 255)),
- under_fill=None,
- draw_fn=draw_D_ripple,
- )
- # ---------- Variant B: clean light, blue-violet gradient mark ----------
- render(
- "B",
- bg=lambda s: solid_bg(s, (247, 249, 255)),
- glow=lambda s: radial_glow(s, int(500 * SS), int(500 * SS),
- int(470 * SS), int(430 * SS),
- (218, 228, 255), 120, 110 * SS),
- mark_fill=lambda s: vgradient(s, (43, 107, 246), (124, 58, 237)),
- over_fill=(38, 50, 116, 255),
- under_fill=None,
- draw_fn=draw_D_ripple,
- )
- # ---------- Variant C: dark violet, D-ring with touch emitter ----------
- render(
- "C",
- bg=lambda s: bilinear_bg(s, (11, 16, 48), (26, 20, 78),
- (18, 16, 66), (42, 26, 104)),
- glow=lambda s: radial_glow(s, int(520 * SS), int(512 * SS),
- int(430 * SS), int(390 * SS),
- (124, 92, 255), 64, 130 * SS),
- mark_fill=(244, 246, 255, 255),
- over_fill=lambda s: vgradient(s, (56, 224, 255), (46, 123, 255)),
- under_fill=lambda s: vgradient(s, (157, 92, 255), (86, 150, 255)),
- draw_fn=draw_D_touch,
- )
- print("done ->", OUT)
|