import os import math from PIL import Image, ImageDraw, ImageFont, ImageFilter def create_super_sampled_icon(size=1024, supersample=4): ss_size = size * supersample img = Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # 1. Background Rounded Squircle with rich forest-emerald gradient # Gradient from top-left (#1F8A5B) to bottom-right (#0D452B) radius = int(ss_size * 0.22) # Create mask for squircle mask = Image.new("L", (ss_size, ss_size), 0) mask_draw = ImageDraw.Draw(mask) margin = int(ss_size * 0.04) mask_draw.rounded_rectangle( [margin, margin, ss_size - margin, ss_size - margin], radius=radius, fill=255 ) # Base gradient image gradient = Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)) c_tl = (38, 155, 102) # #269B66 c_br = (14, 66, 42) # #0E422A for y in range(ss_size): for x in range(0, ss_size, 4): t = (x + y) / (ss_size * 2) r = int(c_tl[0] * (1 - t) + c_br[0] * t) g = int(c_tl[1] * (1 - t) + c_br[1] * t) b = int(c_tl[2] * (1 - t) + c_br[2] * t) # fill horizontal run for dx in range(4): if x + dx < ss_size: gradient.putpixel((x + dx, y), (r, g, b, 255)) # Composite gradient with squircle mask bg = Image.composite(gradient, Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)), mask) # Inner soft glow ring inner_glow = Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)) glow_draw = ImageDraw.Draw(inner_glow) glow_draw.rounded_rectangle( [margin + 6 * supersample, margin + 6 * supersample, ss_size - margin - 6 * supersample, ss_size - margin - 6 * supersample], radius=radius - 6 * supersample, outline=(255, 255, 255, 45), width=int(3 * supersample) ) bg = Image.alpha_composite(bg, inner_glow) # 2. Main Speech Bubble + Sprout Motif # Center coordinates cx, cy = ss_size // 2, ss_size // 2 # A subtle companion speech bubble in the background (Mia AI partner) comp_bubble = Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)) comp_draw = ImageDraw.Draw(comp_bubble) comp_rect = [ int(cx + 40 * supersample), int(cy - 280 * supersample), int(cx + 340 * supersample), int(cy - 20 * supersample) ] comp_draw.rounded_rectangle( comp_rect, radius=int(60 * supersample), fill=(226, 243, 232, 180) # AppColors.softGreen with opacity ) # Companion bubble tail comp_tail = [ (int(cx + 300 * supersample), int(cy - 40 * supersample)), (int(cx + 360 * supersample), int(cy + 40 * supersample)), (int(cx + 250 * supersample), int(cy - 20 * supersample)), ] comp_draw.polygon(comp_tail, fill=(226, 243, 232, 180)) # Sound wave dots in companion bubble dot_color = (23, 107, 70, 220) for i in range(3): dx_dot = int(cx + (140 + i * 55) * supersample) dy_dot = int(cy - 150 * supersample) r_dot = int((10 + (1 if i==1 else 0)*4) * supersample) comp_draw.ellipse([dx_dot - r_dot, dy_dot - r_dot, dx_dot + r_dot, dy_dot + r_dot], fill=dot_color) # Composite companion bubble bg = Image.alpha_composite(bg, comp_bubble) # 3. Primary Speech Bubble (Clean Crisp White with subtle drop shadow) primary_shadow = Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)) sh_draw = ImageDraw.Draw(primary_shadow) b_left = int(cx - 320 * supersample) b_top = int(cy - 180 * supersample) b_right = int(cx + 180 * supersample) b_bottom = int(cy + 260 * supersample) b_radius = int(100 * supersample) sh_draw.rounded_rectangle( [b_left, b_top + 16 * supersample, b_right, b_bottom + 16 * supersample], radius=b_radius, fill=(0, 0, 0, 70) ) # Primary tail shadow p_tail_sh = [ (int(b_left + 80 * supersample), int(b_bottom + 10 * supersample)), (int(b_left - 30 * supersample), int(b_bottom + 120 * supersample)), (int(b_left + 190 * supersample), int(b_bottom + 10 * supersample)), ] sh_draw.polygon(p_tail_sh, fill=(0, 0, 0, 70)) primary_shadow = primary_shadow.filter(ImageFilter.GaussianBlur(radius=int(16 * supersample))) bg = Image.alpha_composite(bg, primary_shadow) # Draw actual Primary Bubble primary_bubble = Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)) pb_draw = ImageDraw.Draw(primary_bubble) pb_draw.rounded_rectangle( [b_left, b_top, b_right, b_bottom], radius=b_radius, fill=(255, 255, 255, 255) ) p_tail = [ (int(b_left + 80 * supersample), int(b_bottom - 10 * supersample)), (int(b_left - 30 * supersample), int(b_bottom + 95 * supersample)), (int(b_left + 190 * supersample), int(b_bottom - 10 * supersample)), ] pb_draw.polygon(p_tail, fill=(255, 255, 255, 255)) bg = Image.alpha_composite(bg, primary_bubble) # 4. Sprout & Dynamic Voice Wave Graphic inside Primary Bubble # Center of primary bubble pcx = (b_left + b_right) // 2 pcy = (b_top + b_bottom) // 2 - int(10 * supersample) icon_art = Image.new("RGBA", (ss_size, ss_size), (0, 0, 0, 0)) art_draw = ImageDraw.Draw(icon_art) # Sprout Stem & Leaves (Vibrant Emerald & Spring Green) # Main Leaf (Right): curving upwards with life leaf_r_points = [] steps = 40 # Center anchor of stem: (pcx - 30, pcy + 110) stem_x, stem_y = pcx - int(20 * supersample), pcy + int(110 * supersample) # Left leaf left_leaf = [ (stem_x, stem_y - int(30 * supersample)), (stem_x - int(120 * supersample), stem_y - int(60 * supersample)), (stem_x - int(150 * supersample), stem_y - int(150 * supersample)), (stem_x - int(60 * supersample), stem_y - int(140 * supersample)), (stem_x, stem_y - int(70 * supersample)) ] # Draw smooth left leaf art_draw.polygon(left_leaf, fill=(43, 174, 107, 255)) # Right bigger primary leaf right_leaf = [ (stem_x + int(10 * supersample), stem_y - int(40 * supersample)), (stem_x + int(70 * supersample), stem_y - int(60 * supersample)), (stem_x + int(160 * supersample), stem_y - int(170 * supersample)), (stem_x + int(130 * supersample), stem_y - int(220 * supersample)), (stem_x + int(40 * supersample), stem_y - int(190 * supersample)), (stem_x - int(10 * supersample), stem_y - int(100 * supersample)) ] art_draw.polygon(right_leaf, fill=(23, 107, 70, 255)) # Sprout Dewdrop / Energy Spark (Warm Sun Gold #F59E0B) spark_x = stem_x + int(145 * supersample) spark_y = stem_y - int(235 * supersample) spark_r = int(22 * supersample) art_draw.ellipse( [spark_x - spark_r, spark_y - spark_r, spark_x + spark_r, spark_y + spark_r], fill=(245, 158, 11, 255) ) # 3 Arched Voice Waves radiating from the sprout (representing speaking & pronunciation) wave_color = (23, 107, 70, 220) # Wave 1 (inner) w1_box = [ int(pcx - 180 * supersample), int(pcy - 160 * supersample), int(pcx - 20 * supersample), int(pcy + 0 * supersample) ] art_draw.arc(w1_box, start=140, end=270, fill=wave_color, width=int(14 * supersample)) # Wave 2 (middle) w2_box = [ int(pcx - 240 * supersample), int(pcy - 210 * supersample), int(pcx - 10 * supersample), int(pcy + 30 * supersample) ] art_draw.arc(w2_box, start=145, end=265, fill=(43, 174, 107, 200), width=int(14 * supersample)) # Wave 3 (outer) w3_box = [ int(pcx - 295 * supersample), int(pcy - 260 * supersample), int(pcx - 0 * supersample), int(pcy + 60 * supersample) ] art_draw.arc(w3_box, start=150, end=260, fill=(245, 158, 11, 230), width=int(13 * supersample)) # Composite artwork bg = Image.alpha_composite(bg, icon_art) # Downsample using high quality Lanczos filter for razor-sharp antialiasing final_icon = bg.resize((size, size), Image.Resampling.LANCZOS) return final_icon def create_brand_banner(icon_img): w, h = 1200, 600 banner = Image.new("RGBA", (w, h), (245, 247, 243, 255)) # AppColors.paper #F5F7F3 draw = ImageDraw.Draw(banner) # Background subtle decorative circles dec_draw = ImageDraw.Draw(banner) dec_draw.ellipse([800, -100, 1400, 500], fill=(226, 243, 232, 120)) dec_draw.ellipse([-100, 300, 400, 800], fill=(255, 240, 227, 120)) # Paste resized icon on the left icon_w = 340 icon_resized = icon_img.resize((icon_w, icon_w), Image.Resampling.LANCZOS) banner.paste(icon_resized, (100, (h - icon_w) // 2), icon_resized) # Typography on the right # Try finding available fonts or use default with clear layout font_path_candidates = [ "/System/Library/Fonts/PingFang.ttc", "/System/Library/Fonts/Hiragino Sans GB.ttc", "/System/Library/Fonts/Supplemental/Arial Bold.ttf", "/System/Library/Fonts/Supplemental/Arial.ttf" ] title_font = None en_font = None sub_font = None badge_font = None for p in font_path_candidates: if os.path.exists(p): try: title_font = ImageFont.truetype(p, 58, index=0) en_font = ImageFont.truetype(p, 30, index=0) sub_font = ImageFont.truetype(p, 24, index=0) badge_font = ImageFont.truetype(p, 18, index=0) break except Exception: continue if not title_font: title_font = ImageFont.load_default() en_font = ImageFont.load_default() sub_font = ImageFont.load_default() badge_font = ImageFont.load_default() tx = 490 ty = 135 # Tag / Pill: "AI 伴学 · 零基础开口" pill_w = 210 pill_h = 36 draw.rounded_rectangle([tx, ty, tx + pill_w, ty + pill_h], radius=18, fill=(226, 243, 232, 255)) draw.text((tx + 18, ty + 7), "🌱 AI 伴学 · 轻松开口", fill=(23, 107, 70, 255), font=badge_font) # Main Brand Name draw.text((tx, ty + 50), "芽说英语", fill=(25, 33, 27, 255), font=title_font) # English Name draw.text((tx + 270, ty + 72), "SpeakSprout", fill=(23, 107, 70, 255), font=en_font) # Slogan / Value Proposition draw.text((tx, ty + 145), "每一次开口,都是成长的萌芽", fill=(100, 114, 104, 255), font=sub_font) draw.text((tx, ty + 190), "• 真实场景 1v1 AI 语伴 Mia", fill=(25, 33, 27, 230), font=sub_font) draw.text((tx, ty + 235), "• 听 / 说 / 读 / 写 四维科学进阶", fill=(25, 33, 27, 230), font=sub_font) draw.text((tx, ty + 280), "• 本地离线高精语音识别 · 极速跟读", fill=(25, 33, 27, 230), font=sub_font) return banner if __name__ == "__main__": out_dir = "assets/branding" os.makedirs(out_dir, exist_ok=True) print("🎨 正在生成超采样高清 Logo (1024x1024)...") icon1024 = create_super_sampled_icon(size=1024, supersample=4) master_path = os.path.join(out_dir, "logo_master_1024.png") icon1024.save(master_path, "PNG") print(f"✅ 保存主图标: {master_path}") # 512x512 icon512 = icon1024.resize((512, 512), Image.Resampling.LANCZOS) p512 = os.path.join(out_dir, "logo_512.png") icon512.save(p512, "PNG") # Brand Banner print("🖼️ 正在生成品牌展示图 (1200x600)...") banner = create_brand_banner(icon1024) banner_path = os.path.join(out_dir, "brand_banner.png") banner.save(banner_path, "PNG") print(f"✅ 保存品牌展示图: {banner_path}") # Android launcher icons mipmaps = { "mipmap-mdpi": 48, "mipmap-hdpi": 72, "mipmap-xhdpi": 96, "mipmap-xxhdpi": 144, "mipmap-xxxhdpi": 192, } print("📱 正在更新 Android App 各分辨率图标...") for folder, sz in mipmaps.items(): dir_path = os.path.join("android/app/src/main/res", folder) os.makedirs(dir_path, exist_ok=True) out_f = os.path.join(dir_path, "ic_launcher.png") resized = icon1024.resize((sz, sz), Image.Resampling.LANCZOS) resized.save(out_f, "PNG") print(f" -> {out_f} ({sz}x{sz})") print("🎉 全部 Logo 和图标生成完成!")