diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..80bcc6f
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 8ce9a65..68802ec 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
new file mode 100644
index 0000000..6d75fa7
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..1d8c17d
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index c1ce60e..b435e57 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
new file mode 100644
index 0000000..f1e2143
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..5c2b709
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index b02f16d..955c20b 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..d16a2d9
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..a1645b0
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 550d1e9..c8ecf3e 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..55f87a5
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..886b77c
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 9db70c1..fd2e8c0 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..2663317
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..2e73d94
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/assets/icon/app_icon_1024.png b/assets/icon/app_icon_1024.png
index 6f6ea0c..2a739df 100644
Binary files a/assets/icon/app_icon_1024.png and b/assets/icon/app_icon_1024.png differ
diff --git a/assets/icon/app_icon_256.png b/assets/icon/app_icon_256.png
index 9ff1b45..e620262 100644
Binary files a/assets/icon/app_icon_256.png and b/assets/icon/app_icon_256.png differ
diff --git a/assets/icon/app_icon_small_256.png b/assets/icon/app_icon_small_256.png
index d19c659..7c169fd 100644
Binary files a/assets/icon/app_icon_small_256.png and b/assets/icon/app_icon_small_256.png differ
diff --git a/assets/tray_icon.ico b/assets/tray_icon.ico
index c76ba64..d6168d9 100644
Binary files a/assets/tray_icon.ico and b/assets/tray_icon.ico differ
diff --git a/tool/make_icon.py b/tool/make_icon.py
index 46d9dc1..d54dca1 100644
--- a/tool/make_icon.py
+++ b/tool/make_icon.py
@@ -1,19 +1,24 @@
"""JJ_MKVMaker 아이콘 만들기 (1024px 원본 → .ico / .png)
+눈에 띄게: 빨강 → 주황 그라데이션 바탕에 큰 흰 재생(▶) 버튼, 위아래 필름 구멍 띠.
+(JJ 로 시작하는 다른 프로그램과 헷갈리지 않게 글자 대신 재생 버튼이 주인공)
+
사용법: python tool/make_icon.py
-결과: assets/icon/app_icon_1024.png, windows/runner/resources/app_icon.ico,
- assets/tray_icon.ico, android/app/src/main/res/mipmap-*/ic_launcher.png
+결과: assets/icon/app_icon_1024.png · app_icon_256.png · app_icon_small_256.png,
+ windows/runner/resources/app_icon.ico, assets/tray_icon.ico,
+ android/app/src/main/res/mipmap-*/ic_launcher.png (예전 Android),
+ mipmap-*/ic_launcher_foreground.png · ic_launcher_background.png + mipmap-anydpi-v26/ic_launcher.xml (Android 8 이상)
"""
from pathlib import Path
-from PIL import Image, ImageDraw, ImageFilter, ImageFont
+from PIL import Image, ImageDraw, ImageFilter
ROOT = Path(__file__).resolve().parent.parent
S = 1024 # 원본 크기
-FONT = 'C:/Windows/Fonts/seguibl.ttf' # Segoe UI Black
-TEAL = (0, 196, 204)
-VIOLET = (108, 92, 255)
+RED = (255, 45, 85) # 왼쪽 위
+ORANGE = (255, 149, 0) # 오른쪽 아래
+FILM = (25, 10, 30) # 필름 띠
def lerp(a, b, t):
@@ -26,51 +31,78 @@ def rounded_mask(size, radius):
return m
+def gradient(size):
+ """대각선 그라데이션 (왼쪽 위 빨강 → 오른쪽 아래 주황) + 왼쪽 위의 은은한 빛"""
+ g = Image.new('RGB', (size, size))
+ px = g.load()
+ for y in range(size):
+ for x in range(size):
+ px[x, y] = lerp(RED, ORANGE, (x + y) / (2 * (size - 1)))
+ glow = Image.new('L', (size, size), 0)
+ ImageDraw.Draw(glow).ellipse((-size * 0.4, -size * 0.5, size * 0.8, size * 0.45), fill=45)
+ glow = glow.filter(ImageFilter.GaussianBlur(size * 0.15))
+ return Image.composite(Image.new('RGB', (size, size), (255, 255, 255)), g, glow)
+
+
+def play(size, scale=1.0, cx=0.5, cy=0.5):
+ """흰 재생 버튼 (모서리가 둥근 굵은 삼각형 + 그림자) 만 있는 투명 그림"""
+ layer = Image.new('RGBA', (size, size), (0, 0, 0, 0))
+ h = size * 0.46 * scale # 삼각형 높이 (세로)
+ w = h * 0.92 # 너비
+ # 무게 중심이 가운데 오도록 약간 오른쪽으로
+ x0 = size * cx - w * 0.40
+ y0 = size * cy - h / 2
+ pts = [(x0, y0), (x0, y0 + h), (x0 + w, y0 + h / 2)]
+ r = size * 0.05 * scale # 모서리 둥글기 (선 두께로)
+
+ def draw(img, fill, off=(0, 0)):
+ d = ImageDraw.Draw(img)
+ p = [(x + off[0], y + off[1]) for x, y in pts]
+ d.polygon(p, fill=fill)
+ d.line(p + [p[0]], fill=fill, width=int(r * 2), joint='curve')
+ for x, y in p:
+ d.ellipse((x - r, y - r, x + r, y + r), fill=fill)
+
+ shadow = Image.new('RGBA', (size, size), (0, 0, 0, 0))
+ draw(shadow, (90, 0, 20, 140), off=(size * 0.012 * scale, size * 0.028 * scale))
+ shadow = shadow.filter(ImageFilter.GaussianBlur(size * 0.025 * scale))
+ layer = Image.alpha_composite(layer, shadow)
+ draw(layer, (255, 255, 255, 255))
+ return layer
+
+
+def film_bands(size, top, band, holes=6):
+ """위아래 필름 띠 (어두운 띠 + 흰 구멍)"""
+ layer = Image.new('RGBA', (size, size), (0, 0, 0, 0))
+ d = ImageDraw.Draw(layer)
+ for y0 in (top, size - top - band):
+ d.rectangle((0, y0, size, y0 + band), fill=FILM + (235,))
+ hw, hh = size / (holes * 2.2), band * 0.46
+ gap = size / holes
+ for i in range(holes):
+ cx = gap * (i + 0.5)
+ cy = y0 + band / 2
+ d.rounded_rectangle((cx - hw / 2, cy - hh / 2, cx + hw / 2, cy + hh / 2), radius=hh * 0.3,
+ fill=(255, 255, 255, 230))
+ return layer
+
+
def make_master(small=False):
- """small=True: 16~32px 용 단순 버전 (재생 표시 없이 JJ 를 크게)"""
- # 대각선 그라데이션 (왼쪽 위 청록 → 오른쪽 아래 보라)
- grad = Image.new('RGB', (S, S))
- px = grad.load()
- for y in range(S):
- for x in range(S):
- px[x, y] = lerp(TEAL, VIOLET, (x + y) / (2 * (S - 1)))
-
- # 왼쪽 위에 아주 은은한 빛 (경계가 보이지 않게 크게 흐림)
- glow = Image.new('L', (S, S), 0)
- ImageDraw.Draw(glow).ellipse((-S * 0.5, -S * 0.6, S * 0.9, S * 0.5), fill=38)
- glow = glow.filter(ImageFilter.GaussianBlur(S * 0.16))
- grad = Image.composite(Image.new('RGB', (S, S), (255, 255, 255)), grad, glow)
-
+ """앱 아이콘 (둥근 사각형). small=True: 16~32px 용 (필름 띠 없이 재생 버튼을 크게)"""
icon = Image.new('RGBA', (S, S), (0, 0, 0, 0))
- icon.paste(grad, (0, 0), rounded_mask(S, int(S * 0.23)))
+ icon.paste(gradient(S), (0, 0), rounded_mask(S, int(S * 0.22)))
+ if not small:
+ bands = film_bands(S, top=int(S * 0.055), band=int(S * 0.13))
+ icon = Image.alpha_composite(icon, Image.composite(bands, Image.new('RGBA', (S, S)), rounded_mask(S, int(S * 0.22))))
+ return Image.alpha_composite(icon, play(S, scale=1.25 if small else 1.0))
- # "JJ" (그림자 + 흰 글자)
- font = ImageFont.truetype(FONT, int(S * (0.66 if small else 0.56)))
- text = 'JJ'
- d = ImageDraw.Draw(icon)
- l, t, r, b = d.textbbox((0, 0), text, font=font)
- x = (S - (r - l)) / 2 - l - (0 if small else S * 0.035)
- y = (S - (b - t)) / 2 - t - (0 if small else S * 0.02)
- shadow = Image.new('RGBA', (S, S), (0, 0, 0, 0))
- ImageDraw.Draw(shadow).text((x + S * 0.012, y + S * 0.022), text, font=font, fill=(20, 10, 60, 120))
- shadow = shadow.filter(ImageFilter.GaussianBlur(S * 0.018))
- icon = Image.alpha_composite(icon, shadow)
- ImageDraw.Draw(icon).text((x, y), text, font=font, fill=(255, 255, 255, 255))
- if small:
- return icon
-
- # 오른쪽 아래 재생(▶) 표시: 흰 원 + 보라 삼각형
- cx, cy, rad = S * 0.765, S * 0.765, S * 0.135
- badge = Image.new('RGBA', (S, S), (0, 0, 0, 0))
- bd = ImageDraw.Draw(badge)
- bd.ellipse((cx - rad * 1.12, cy - rad * 1.12, cx + rad * 1.12, cy + rad * 1.12), fill=(40, 20, 110, 90))
- badge = badge.filter(ImageFilter.GaussianBlur(S * 0.012))
- bd = ImageDraw.Draw(badge)
- bd.ellipse((cx - rad, cy - rad, cx + rad, cy + rad), fill=(255, 255, 255, 255))
- tri = rad * 0.52
- bd.polygon([(cx - tri * 0.62, cy - tri), (cx - tri * 0.62, cy + tri), (cx + tri * 0.95, cy)], fill=VIOLET + (255,))
- return Image.alpha_composite(icon, badge)
+def make_adaptive():
+ """Android 8 이상 (adaptive icon): 108dp 바탕 (그라데이션 + 필름 띠) · 앞 (재생 버튼, 안전 영역 66dp 안)"""
+ bg = gradient(S).convert('RGBA')
+ bg = Image.alpha_composite(bg, film_bands(S, top=int(S * 0.17), band=int(S * 0.1), holes=7))
+ fg = play(S, scale=0.62)
+ return bg, fg
def main():
@@ -80,7 +112,7 @@ def main():
master.save(out / 'app_icon_1024.png')
master.resize((256, 256), Image.LANCZOS).save(out / 'app_icon_256.png')
- # 작은 크기 (16~32px) 는 단순 버전, 큰 크기는 재생 표시 포함 버전
+ # 작은 크기 (16~32px) 는 단순 버전, 큰 크기는 필름 띠 포함 버전
simple = make_master(small=True)
simple.resize((256, 256), Image.LANCZOS).save(out / 'app_icon_small_256.png')
@@ -91,11 +123,37 @@ def main():
ico(ROOT / 'windows' / 'runner' / 'resources' / 'app_icon.ico', [16, 20, 24, 32, 40, 48, 64, 128, 256])
ico(ROOT / 'assets' / 'tray_icon.ico', [16, 20, 24, 32, 48])
- # Android 앱 아이콘 (이식 대비)
+ # Android: 예전 기기용 아이콘 + Android 8 이상 adaptive icon (기기마다 다른 모양 틀에 꽉 차게)
+ res = ROOT / 'android' / 'app' / 'src' / 'main' / 'res'
+ bg, fg = make_adaptive()
for name, px in {'mdpi': 48, 'hdpi': 72, 'xhdpi': 96, 'xxhdpi': 144, 'xxxhdpi': 192}.items():
- d = ROOT / 'android' / 'app' / 'src' / 'main' / 'res' / f'mipmap-{name}'
- if d.exists():
- master.resize((px, px), Image.LANCZOS).save(d / 'ic_launcher.png')
+ d = res / f'mipmap-{name}'
+ d.mkdir(parents=True, exist_ok=True)
+ master.resize((px, px), Image.LANCZOS).save(d / 'ic_launcher.png')
+ a = px * 108 // 48 # adaptive 는 108dp
+ bg.resize((a, a), Image.LANCZOS).save(d / 'ic_launcher_background.png')
+ fg.resize((a, a), Image.LANCZOS).save(d / 'ic_launcher_foreground.png')
+ any_dpi = res / 'mipmap-anydpi-v26'
+ any_dpi.mkdir(parents=True, exist_ok=True)
+ (any_dpi / 'ic_launcher.xml').write_text(
+ '\n'
+ '\n'
+ ' \n'
+ ' \n'
+ ' \n'
+ '\n', encoding='utf-8')
+
+ # 미리보기 (원형 마스크 · 둥근 사각형 마스크)
+ prev = Image.new('RGBA', (S * 3 + 80, S + 40), (40, 40, 48, 255))
+ prev.alpha_composite(master, (20, 20))
+ full = Image.alpha_composite(bg, fg)
+ crop = int(S * 18 / 108)
+ view = full.crop((crop, crop, S - crop, S - crop)).resize((S, S), Image.LANCZOS)
+ circle = Image.new('L', (S, S), 0)
+ ImageDraw.Draw(circle).ellipse((0, 0, S, S), fill=255)
+ prev.paste(view, (S + 40, 20), circle)
+ prev.paste(view, (S * 2 + 60, 20), rounded_mask(S, int(S * 0.3)))
+ prev.resize((prev.width // 4, prev.height // 4), Image.LANCZOS).save(out / 'preview.png')
print('아이콘 생성 완료:', out / 'app_icon_1024.png')
diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico
index f3a404e..155a8a0 100644
Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ