diff --git a/README.md b/README.md
index 53167a7..d1bec9f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
diff --git a/README_CN.md b/README_CN.md
index 4a2cf02..9da5721 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -1,5 +1,5 @@
-
+
diff --git a/docs/assets/artemis-banner-cn.png b/docs/assets/artemis-banner-cn.png
index b8b1748..f0f7022 100644
Binary files a/docs/assets/artemis-banner-cn.png and b/docs/assets/artemis-banner-cn.png differ
diff --git a/docs/assets/artemis-banner-en.png b/docs/assets/artemis-banner-en.png
index f003fd8..f0f7022 100644
Binary files a/docs/assets/artemis-banner-en.png and b/docs/assets/artemis-banner-en.png differ
diff --git a/docs/assets/artemis-banner-raw.jpg b/docs/assets/artemis-banner-raw.jpg
new file mode 100644
index 0000000..3cc0e1a
Binary files /dev/null and b/docs/assets/artemis-banner-raw.jpg differ
diff --git a/docs/assets/artemis-banner.png b/docs/assets/artemis-banner.png
index f003fd8..f0f7022 100644
Binary files a/docs/assets/artemis-banner.png and b/docs/assets/artemis-banner.png differ
diff --git a/docs/assets/generate_banner.py b/docs/assets/generate_banner.py
index e2c4232..670e555 100644
--- a/docs/assets/generate_banner.py
+++ b/docs/assets/generate_banner.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,723 +13,187 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import os
-import subprocess
-from pathlib import Path
-
-
-def create_banner_html(lang="en"):
- html_template = """
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ARTEMIS
-
-
- __TAGLINE__
-
-
-
-
-
-
- > __PROMPT_TEXT__
-
-
-
-
-
-
-
-
-
-
-
-
-
- ✓
- __STEP_1__
-
-
- ✓
- __STEP_2__
-
-
-
-
-
-
- CPU < 5.0%
- 4.2% PASS
-
-
-
-
-
-
-
-
-
-
-
-
-
-
🎵
-
- Coldplay - Yellow
- Pixel Buds Pro
-
-
▶
-
-
-
-
-
-
- __MOCK_BTN__
-
-
-
-
-
-
-
-
-
-
-
+"""ARTEMIS Official Banner Generator.
+
+- Background: High-resolution celestial space background cropped to 1376x380 and
+ seamlessly extended on the right side to 1800x380 (+424px pure dark space)
+ to balance visual weight and provide ample breathing room for the crescent moon.
+- Typography: Montserrat Light (weight 300) with wide tracking (64px) at 98px size.
+- Centering: Exact pixel-level ink bounding box alignment, ensuring true mathematical
+ and optical center at exactly (width/2, height/2).
+- Sharpness: 3x vector supersampling followed by Lanczos downsampling for razor-sharp
+ edges without rasterization stair-stepping or blurry halation.
+- Lighting: Soft celestial ambient aura layered behind the pure white text.
+- Portability: Automatically downloads and caches the Montserrat font in /tmp
+ without requiring font binaries committed to the repository.
"""
- if lang == "cn":
- replacements = {
- "__TAGLINE__": "让 AI 助手与测试套件像人一样直接操作真机",
- "__PROMPT_LABEL__": "🎯 测试指令",
- "__PROMPT_STATUS__": "真机运行中",
- "__PROMPT_TEXT__": "连接蓝牙耳机播放音乐,断言 CPU 占用 < 5%",
- "__CONSOLE_TITLE__": "真机测试执行引擎",
- "__CONSOLE_BADGE__": "通过",
- "__STEP_1__": "连接蓝牙耳机",
- "__STEP_2__": "打开播放器并播放歌曲",
- "__STEP_3_TITLE__": "▶ 断言性能指标",
- "__STEP_3_TAG__": "实时验证",
- "__SCREEN_TITLE__": "正在播放",
- "__MOCK_BTN__": "音频输出控制",
- }
+
+import io
+import os
+import shutil
+import urllib.request
+import numpy as np
+from PIL import Image, ImageDraw, ImageFont, ImageFilter
+
+
+def get_font_path(weight: int = 300) -> str:
+ """Retrieve or download Montserrat font instantiated at the requested weight."""
+ cache_dir = "/tmp/artemis_banner_fonts"
+ os.makedirs(cache_dir, exist_ok=True)
+ target_font = os.path.join(cache_dir, f"Montserrat-wght{weight}.ttf")
+
+ if os.path.exists(target_font):
+ return target_font
+
+ var_url = "https://raw.githubusercontent.com/google/fonts/main/ofl/montserrat/Montserrat%5Bwght%5D.ttf"
+ print(f"Fetching Montserrat font from Google Fonts ({var_url}) ...")
+ try:
+ req = urllib.request.Request(var_url, headers={"User-Agent": "Mozilla/5.0"})
+ with urllib.request.urlopen(req, timeout=15) as resp:
+ font_data = resp.read()
+
+ from fontTools.ttLib import TTFont
+ from fontTools.varLib.instancer import instantiateVariableFont
+
+ tt = TTFont(io.BytesIO(font_data))
+ inst = instantiateVariableFont(tt, {"wght": weight})
+ inst.save(target_font)
+ print(f"Successfully cached Montserrat (wght {weight}) to: {target_font}")
+ return target_font
+ except Exception as e:
+ print(f"Warning: Failed to fetch online font: {e}. Falling back to default system font.")
+ return None
+
+
+def extend_canvas(canvas: Image.Image, extra_w: int = 424, blend_w: int = 80) -> Image.Image:
+ """Seamlessly extends the right edge of the canvas with matching space background."""
+ if extra_w <= 0:
+ return canvas
+ w, h = canvas.size
+ canvas_rgb = canvas.convert("RGB")
+ right_col = np.array(canvas_rgb)[:, -1:, :]
+ ext_part = np.repeat(right_col, extra_w + blend_w, axis=1)
+ canvas_arr = np.array(canvas_rgb)
+
+ alpha = np.linspace(0, 1, blend_w).reshape(1, blend_w, 1)
+ blend_zone = canvas_arr[:, w - blend_w : w, :] * (1 - alpha) + ext_part[:, :blend_w, :] * alpha
+ canvas_arr[:, w - blend_w : w, :] = blend_zone.astype(np.uint8)
+
+ full_arr = np.concatenate([canvas_arr, ext_part[:, blend_w:, :]], axis=1)
+ return Image.fromarray(full_arr.astype(np.uint8)).convert("RGBA")
+
+
+def generate_banner(
+ bg_path: str,
+ output_path: str,
+ text: str = "ARTEMIS",
+ font_size: int = 98,
+ tracking: int = 64,
+ font_weight: int = 300,
+ extra_w: int = 424,
+ offset_x: int = 160,
+ glow_mode: str = "ambient",
+ supersample: int = 3,
+):
+ """Generate the official ARTEMIS banner with high clarity and optical/geometric centering."""
+ if not os.path.exists(bg_path):
+ raise FileNotFoundError(f"Background image not found: {bg_path}")
+
+ # 1. Load raw background and crop canvas
+ raw_img = Image.open(bg_path)
+ w, h = raw_img.size
+ canvas = raw_img.crop((0, 15, w, 395)).convert("RGBA")
+
+ # 2. Seamlessly extend the right side
+ if extra_w > 0:
+ canvas = extend_canvas(canvas, extra_w=extra_w)
+ cw, ch = canvas.size
+
+ # 3. Supersampled text layout
+ scale = max(1, supersample)
+ sw, sh = cw * scale, ch * scale
+ sfsize = font_size * scale
+ strk = tracking * scale
+
+ font_path = get_font_path(weight=font_weight)
+ if font_path and os.path.exists(font_path):
+ font = ImageFont.truetype(font_path, sfsize)
else:
- replacements = {
- "__TAGLINE__": "Let AI assistants and test suites use real phones like a human",
- "__PROMPT_LABEL__": "🎯 TEST PROMPT",
- "__PROMPT_STATUS__": "RUNNING",
- "__PROMPT_TEXT__": "Connect Bluetooth earbuds, play song, and assert CPU < 5%",
- "__CONSOLE_TITLE__": "Test Runner",
- "__CONSOLE_BADGE__": "PASSED",
- "__STEP_1__": "Connect Bluetooth earbuds",
- "__STEP_2__": "Launch player & play song",
- "__STEP_3_TITLE__": "▶ Assert Performance SLA",
- "__STEP_3_TAG__": "VERIFYING",
- "__SCREEN_TITLE__": "Now Playing",
- "__MOCK_BTN__": "Audio Output",
- }
+ font = ImageFont.load_default()
- for key, val in replacements.items():
- html_template = html_template.replace(key, val)
- return html_template
+ t_layer = Image.new("RGBA", (sw * 2, sh * 2), (0, 0, 0, 0))
+ draw = ImageDraw.Draw(t_layer)
+ start_x = 400 * scale
+ start_y = 400 * scale
+ curr_x = start_x
+ for c in text:
+ draw.text((curr_x, start_y), c, font=font, fill=(255, 255, 255, 255))
+ curr_x += font.getlength(c) + strk
+ # 4. Extract ink bounds and downsample with Lanczos
+ ink_3x = t_layer.crop(t_layer.getbbox())
+ target_w = round(ink_3x.width / scale)
+ target_h = round(ink_3x.height / scale)
+ ink_1x = ink_3x.resize((target_w, target_h), Image.Resampling.LANCZOS)
+ iw, ih = ink_1x.size
-def render_image(html_str, output_path):
- temp_html = f"/tmp/banner_{os.path.basename(output_path)}.html"
- with open(temp_html, "w", encoding="utf-8") as f:
- f.write(html_str)
+ # 5. Exact pixel geometric / optical centering
+ px = (cw - iw) // 2 + offset_x
+ py = (ch - ih) // 2
- cmd = [
- "google-chrome",
- "--headless",
- "--disable-gpu",
- "--no-sandbox",
- "--disable-dev-shm-usage",
- "--window-size=1280,480",
- "--hide-scrollbars",
- f"--screenshot={output_path}",
- f"file://{temp_html}",
- ]
- res = subprocess.run(cmd, capture_output=True, text=True)
- if os.path.exists(output_path):
- print(f"Rendered {output_path} ({os.path.getsize(output_path)} bytes)")
- else:
- print(f"Error rendering {output_path}: {res.stderr}")
+ # 6. Lighting and composite
+ composite_img = canvas.copy()
+
+ if glow_mode == "ambient":
+ g = Image.new("RGBA", (cw, ch), (0, 0, 0, 0))
+ cyan = Image.new("RGBA", (iw, ih), (120, 215, 255, 55))
+ g_ink = Image.composite(cyan, Image.new("RGBA", (iw, ih), (0, 0, 0, 0)), ink_1x.split()[3])
+ g.paste(g_ink, (px, py), g_ink)
+ g = g.filter(ImageFilter.GaussianBlur(18))
+ composite_img = Image.alpha_composite(composite_img, g)
+
+ # Top crisp white text
+ text_overlay = Image.new("RGBA", (cw, ch), (0, 0, 0, 0))
+ text_overlay.paste(ink_1x, (px, py), ink_1x)
+ final_img = Image.alpha_composite(composite_img, text_overlay).convert("RGB")
+
+ os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
+ final_img.save(output_path, "PNG")
+
+ center_x = px + iw / 2.0
+ center_y = py + ih / 2.0
+ print(f"Banner successfully generated: {output_path} ({cw}x{ch})")
+ print(f"Ink dimensions: {iw}x{ih}px, Exact center: ({center_x:.1f}, {center_y:.1f}) [Canvas center: ({cw/2:.1f}, {ch/2:.1f}), Offset X: {offset_x:+d}px]")
if __name__ == "__main__":
- assets_dir = Path(__file__).resolve().parent
- os.makedirs(assets_dir, exist_ok=True)
+ import argparse
- # Generate English main banner and en banner
- en_html = create_banner_html(lang="en")
- render_image(en_html, str(assets_dir / "artemis-banner.png"))
- render_image(en_html, str(assets_dir / "artemis-banner-en.png"))
+ parser = argparse.ArgumentParser(description="ARTEMIS Official Banner Generator")
+ parser.add_argument("--offset-x", type=int, default=160, help="Horizontal pixel offset to the right from mathematical center (default: 160)")
+ parser.add_argument("--tracking", type=int, default=64, help="Character tracking spacing (default: 64)")
+ parser.add_argument("--font-size", type=int, default=98, help="Font size in pixels (default: 98)")
+ args = parser.parse_args()
- # Generate Chinese banner
- cn_html = create_banner_html(lang="cn")
- render_image(cn_html, str(assets_dir / "artemis-banner-cn.png"))
+ assets_dir = os.path.dirname(os.path.abspath(__file__))
+ bg_file = os.path.join(assets_dir, "artemis-banner-raw.jpg")
+ out_file = os.path.join(assets_dir, "artemis-banner.png")
+ out_file_cn = os.path.join(assets_dir, "artemis-banner-cn.png")
+ out_file_en = os.path.join(assets_dir, "artemis-banner-en.png")
+
+ generate_banner(
+ bg_path=bg_file,
+ output_path=out_file,
+ text="ARTEMIS",
+ font_size=args.font_size,
+ tracking=args.tracking,
+ font_weight=300,
+ extra_w=424,
+ offset_x=args.offset_x,
+ glow_mode="ambient",
+ )
+
+ if os.path.exists(out_file):
+ shutil.copyfile(out_file, out_file_cn)
+ shutil.copyfile(out_file, out_file_en)
+ print(f"Synchronized copy to: {out_file_cn} and {out_file_en}")