"""重做字母 D — 用更干净的 polygon path。""" from PIL import Image, ImageDraw, ImageFont, ImageChops, ImageFilter from pathlib import Path OUT = Path(r'D:\selftools\diary-news\docs\android\assets') LOGO = OUT / 'logo' SPLASH = OUT / 'splash' WOOD_LIGHT = (245, 233, 208) WOOD_MID = (232, 212, 168) WOOD_DARK = (201, 168, 118) GRAIN_LINE = (168, 130, 90) LETTER_DARK = (62, 42, 30) LETTER_DARKER = (42, 27, 16) def lerp(a, b, t): return tuple(int(a[i] + (b[i] - a[i]) * t) for i in range(3)) def make_wood_gradient(w, h, top, mid, bot): img = Image.new('RGB', (w, h), top) px = img.load() for y in range(h): t = y / max(1, h - 1) c = lerp(top, mid, min(1.0, t * 2)) if t < 0.5 else lerp(mid, bot, (t - 0.5) * 2) for x in range(w): px[x, y] = c return img def add_wood_grain(img, spacing=10, opacity=40): w, h = img.size overlay = Image.new('RGBA', (w, h), (0, 0, 0, 0)) draw = ImageDraw.Draw(overlay) for y in range(0, h, spacing): op = max(15, opacity - (y % spacing) * 2) draw.line([(0, y), (w, y)], fill=(*GRAIN_LINE, op), width=1) return Image.alpha_composite(img.convert('RGBA'), overlay) def draw_letter_D_simple(canvas_size, box): """画一个干净的 D — 用 polygon 直接画出 D 的外形。 D 的几何:左竖条 + 上下凸出半圆(右半)+ 中间挖空。 """ img = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) draw = ImageDraw.Draw(img) x0, y0, x1, y1 = box bw, bh = x1 - x0, y1 - y0 cx = (x0 + x1) // 2 cy = (y0 + y1) // 2 # D 的外轮廓尺寸 dw = bw * 0.52 dh = bh * 0.68 L = int(cx - dw / 2) # 左 R = int(cx + dw / 2) # 右 T = int(cy - dh / 2) # 顶 B = int(cy + dh / 2) # 底 # 笔画粗细 bar_w = dw * 0.32 # 凸出(让 D 顶/底圆润) bulge = bar_w * 0.35 # 1) 画 D 的整体外形:左竖条 + 上下半圆 + 右半椭圆 # 用 polygon 拼一个 D D_path = [ # 左竖条顶 (L, T + bulge), # 左竖条底 (L, B - bulge), # 底弧起点 (L + bar_w * 0.4, B), # 右半圆底部 (R - bar_w * 0.3, B), # 右半圆弧最高点(顶部) (R, cy), # 右半圆底部 → 已经在 (R - bar_w*0.3, B) # 顶弧起点 (L + bar_w * 0.4, T), ] # 实际上我们直接用 pieslice 拼 # 顶半圆:从 (L, T+bulge) 到 (R-bar_w*0.3, T),画 pieslice 180°~360° # 实际更简单:画三个形状叠起来 # a) 左竖条(矩形) draw.rectangle( [L, T + bulge * 0.5, L + bar_w, B - bulge * 0.5], fill=LETTER_DARK, ) # b) 顶半圆(右半,从 180° 到 360°,中心点) # pieslice 接受 bbox + start/end angle(角度,3 点钟方向=0,逆时针为正) # Pillow 中 pieslice 是顺时针 0=3 点,90=6 点,180=9 点,270=12 点 # 我们要画右上 1/4 圆:从 270° 到 360°(即 12 点 → 3 点)不对 # 重新想:画 D 的右半外轮廓,是一个完整的椭圆右半 # 顶弧:从 (L+bar_w, T) 弧形向右下到 (R, cy) # 用 arc 描边粗一些,然后用 chord 实心填充 # 直接用 pieslice 实心填充 + ellipse 配合 # 简化方案: # - 画一个完整 ellipse fill DARK # - 再画一个稍小的 ellipse fill 木色(挖空内部) # - 用矩形覆盖椭圆左半,挖出左边的"竖条" # 这样视觉上就是 D # 整个 D 占的区域 full_ell = [L, T, R + int(bulge * 0.5), B] # 让椭圆稍微超出矩形一点,确保右半圆足够圆 inner_ell = [L + bar_w, T + bar_w * 1.05, R + int(bulge * 0.5) - bar_w * 0.55, B - bar_w * 1.05] # 1) 整个外轮廓 fill DARK draw.ellipse(full_ell, fill=LETTER_DARK) # 2) 内部挖空(fill 木色,让字母透出底) draw.ellipse(inner_ell, fill=WOOD_LIGHT) # 3) 用矩形盖住椭圆左半,形成 D 的竖条 # 矩形左边到 L+bar_w*0.9,右边到 inner_ell 的左侧+一点 rect_left = L rect_right = L + bar_w + (inner_ell[0] - (L + bar_w)) // 2 + 2 # 让矩形比椭圆略矮,保持椭圆上下凸出 rect_top = T + bar_w * 0.85 rect_bot = B - bar_w * 0.85 # 矩形 fill DARK(竖条) draw.rectangle([rect_left, rect_top, rect_right, rect_bot], fill=LETTER_DARK) # 4) 在矩形右侧挖一个米色矩形,让 D 中间真的空出来 # 计算 D 中间的"肚子"位置 mid_left = L + bar_w + 4 mid_right = R - bar_w * 0.4 mid_top = T + bar_w * 1.1 mid_bot = B - bar_w * 1.1 # 用椭圆 fill 米色 覆盖中间的"空腔" # 但这样会把竖条也覆盖,改成用 polygon # 实际上 inner_ell 已经挖空了椭圆内部,现在要把"竖条"也挖掉中间一部分 # 方法:用 ellipse 在竖条右侧挖一个椭圆洞 draw.ellipse( [mid_left, mid_top, mid_right, mid_bot], fill=WOOD_LIGHT, ) # 5) 用矩形 cover 椭圆左半(只留右边空腔) cover_left = mid_left - 5 cover_right = mid_left + (mid_right - mid_left) * 0.30 draw.rectangle( [cover_left, mid_top + (mid_bot - mid_top) * 0.05, cover_right, mid_bot - (mid_bot - mid_top) * 0.05], fill=WOOD_LIGHT, ) # 6) 描深色边 # 左竖条外缘 draw.line([(L, T + bulge * 0.5), (L, B - bulge * 0.5)], fill=LETTER_DARKER, width=3) # 顶弧右端 + 右半圆 + 底弧右端 draw.arc(full_ell, start=270, end=90, fill=LETTER_DARKER, width=3) return img def make_block_icon(size, safe_zone=False): canvas_size = (size, size) if safe_zone: pad = int(size * 0.22) else: pad = int(size * 0.08) box = (pad, pad, size - pad, size - pad) bw, bh = box[2] - box[0], box[3] - box[1] wood = make_wood_gradient(bw, bh, WOOD_LIGHT, WOOD_MID, WOOD_DARK) wood = add_wood_grain(wood, spacing=int(size * 0.025), opacity=50) wood_rgba = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) mask = Image.new('L', (bw, bh), 0) ImageDraw.Draw(mask).rounded_rectangle([0, 0, bw - 1, bh - 1], radius=int(size * 0.18), fill=255) wood_rgba.paste(wood, (box[0], box[1]), mask) edge = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) ed = ImageDraw.Draw(edge) ed.rounded_rectangle([box[0], box[1], box[2] - 1, box[3] - 1], radius=int(size * 0.18), outline=(107, 79, 48, 200), width=2) wood_rgba = Image.alpha_composite(wood_rgba, edge) shadow = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) sd = ImageDraw.Draw(shadow) sd.ellipse([int(size * 0.28), int(size * 0.93), int(size * 0.72), int(size * 1.02)], fill=(0, 0, 0, 50)) shadow = shadow.filter(ImageFilter.GaussianBlur(4)) hl = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) hd = ImageDraw.Draw(hl) hd.rounded_rectangle( [box[0], box[1], box[2] - 1, box[1] + bh // 2], radius=int(size * 0.18), fill=(255, 255, 255, 35), ) letter = draw_letter_D_simple(canvas_size, box) final = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) final = Image.alpha_composite(final, shadow) final = Image.alpha_composite(final, wood_rgba) final = Image.alpha_composite(final, hl) final = Image.alpha_composite(final, letter) return final def make_round_icon(size): canvas = Image.new('RGBA', (size, size), (0, 0, 0, 0)) wood = make_wood_gradient(size, size, WOOD_LIGHT, WOOD_MID, WOOD_DARK) wood = add_wood_grain(wood, spacing=int(size * 0.04), opacity=50) mask = Image.new('L', (size, size), 0) ImageDraw.Draw(mask).ellipse([0, 0, size - 1, size - 1], fill=255) canvas.paste(wood, (0, 0), mask) letter = draw_letter_D_simple( (size, size), (int(size * 0.20), int(size * 0.20), int(size * 0.80), int(size * 0.80)), ) canvas = Image.alpha_composite(canvas, letter) hl = Image.new('RGBA', (size, size), (0, 0, 0, 0)) hd = ImageDraw.Draw(hl) hd.ellipse([int(size * 0.1), int(size * 0.05), int(size * 0.9), int(size * 0.55)], fill=(255, 255, 255, 35)) hl_mask = Image.new('L', (size, size), 0) ImageDraw.Draw(hl_mask).ellipse([0, 0, size - 1, size - 1], fill=255) hl.putalpha(ImageChops.multiply(hl.split()[3], hl_mask)) canvas = Image.alpha_composite(canvas, hl) return canvas # === 重新生成 === print('[1] master 1024') make_block_icon(1024, safe_zone=False).save(LOGO / 'icon_master_1024.png') print('[2] adaptive icon 432') make_block_icon(432, safe_zone=True).save(LOGO / 'ic_launcher_foreground.png') bg = make_wood_gradient(432, 432, WOOD_LIGHT, WOOD_MID, WOOD_DARK) bg = add_wood_grain(bg, spacing=12, opacity=30) bg.save(LOGO / 'ic_launcher_background.png') print('[3] launcher icons 5 DPI') SIZES = {'mdpi': 48, 'hdpi': 72, 'xhdpi': 96, 'xxhdpi': 144, 'xxxhdpi': 192} icon_master = make_block_icon(1024, safe_zone=False) for dpi, sz in SIZES.items(): d = LOGO / f'mipmap-{dpi}' d.mkdir(exist_ok=True) icon_master.resize((sz, sz), Image.LANCZOS).save(d / 'ic_launcher.png') make_round_icon(sz).save(d / 'ic_launcher_round.png') print(f' {dpi}: {sz}x{sz}') print('[4] 启动屏 logo 512') make_block_icon(512, safe_zone=False).save(SPLASH / 'splash_logo.png') print('[5] 启动屏背景 1080x1920') bg_w, bg_h = 1080, 1920 bg_full = make_wood_gradient(bg_w, bg_h, WOOD_LIGHT, WOOD_MID, WOOD_DARK) bg_full = add_wood_grain(bg_full, spacing=20, opacity=35).convert('RGBA') logo_big = make_block_icon(512, safe_zone=False) logo_x = (bg_w - 512) // 2 logo_y = (bg_h - 512) // 2 - 100 bg_full.alpha_composite(logo_big, (logo_x, logo_y)) draw = ImageDraw.Draw(bg_full) try: font_big = ImageFont.truetype('arial.ttf', 110) font_sm = ImageFont.truetype('arial.ttf', 38) except Exception: font_big = ImageFont.load_default() font_sm = ImageFont.load_default() name = 'Diary News' bb = draw.textbbox((0, 0), name, font=font_big) tw = bb[2] - bb[0] draw.text(((bg_w - tw) // 2, logo_y + 512 + 80), name, fill=LETTER_DARK, font=font_big) sub = 'Your Private News Diary' bb2 = draw.textbbox((0, 0), sub, font=font_sm) tw2 = bb2[2] - bb2[0] draw.text(((bg_w - tw2) // 2, logo_y + 512 + 230), sub, fill=(90, 65, 40), font=font_sm) bg_full.save(SPLASH / 'splash_bg_full.png') print(' OK') print('[6] 启动屏各 DPI logo') SPLASH_SIZES = {'mdpi': 192, 'hdpi': 288, 'xhdpi': 384, 'xxhdpi': 576, 'xxxhdpi': 768} for dpi, sz in SPLASH_SIZES.items(): d = SPLASH / f'drawable-{dpi}' d.mkdir(exist_ok=True, parents=True) make_block_icon(sz, safe_zone=False).save(d / 'ic_splash_logo.png') print(f' {dpi}: {sz}x{sz}') print('\n=== 完成 ===')