From f81488cfc0bfbbdb2f78a511f43bb07d7fad652e Mon Sep 17 00:00:00 2001 From: Alex Cheema Date: Mon, 16 Feb 2026 05:35:29 -0800 Subject: [PATCH] fix: use Ollama-style DMG background (white, hand-drawn arrow, retina) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace procedural dark background with Ollama's MIT-licensed static background asset: white canvas, hand-drawn curved arrow, yellow bookmark accents. Window resized to 800×400 with app left / Applications right matching standard macOS DMG conventions. Co-Authored-By: Claude Opus 4.6 --- packaging/dmg/background.png | Bin 0 -> 15741 bytes packaging/dmg/create-dmg.sh | 12 +- packaging/dmg/generate-background.py | 221 ++------------------------- 3 files changed, 17 insertions(+), 216 deletions(-) create mode 100644 packaging/dmg/background.png diff --git a/packaging/dmg/background.png b/packaging/dmg/background.png new file mode 100644 index 0000000000000000000000000000000000000000..bbfc5879ffbc4ce92ceac7a9c51463b27d6601c3 GIT binary patch literal 15741 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 literal 0 HcmV?d00001 diff --git a/packaging/dmg/create-dmg.sh b/packaging/dmg/create-dmg.sh index e23730ed..4b6e965c 100755 --- a/packaging/dmg/create-dmg.sh +++ b/packaging/dmg/create-dmg.sh @@ -69,8 +69,8 @@ if [[ -n $BACKGROUND_PNG && -f $BACKGROUND_PNG ]]; then fi # ── Step 5: Configure window appearance via AppleScript ────────────────────── -# Window: 660×440, icons at 128px centered at y=130, app on left, Applications on right -# Text size set to 10 (Finder minimum) to minimize icon label visibility on dark background +# Window: 800×400, app icon on left, Applications on right (matches Ollama layout) +# Background image is 1600×740 (2× retina). Text size 12. APP_NAME="$(basename "$APP_PATH")" osascript < bytes: - """Build a single PNG chunk (type + data + CRC).""" - raw = chunk_type + data - return ( - struct.pack(">I", len(data)) - + raw - + struct.pack(">I", zlib.crc32(raw) & 0xFFFFFFFF) - ) - - -def _create_png( - width: int, height: int, pixels: list[list[tuple[int, int, int, int]]] -) -> bytes: - """Create a minimal RGBA PNG from pixel data.""" - signature = b"\x89PNG\r\n\x1a\n" - - # IHDR - ihdr_data = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0) # 8-bit RGBA - ihdr = _png_chunk(b"IHDR", ihdr_data) - - # IDAT — build raw scanlines then deflate - raw_lines = bytearray() - for row in pixels: - raw_lines.append(0) # filter: None - for r, g, b, a in row: - raw_lines.extend((r, g, b, a)) - idat = _png_chunk(b"IDAT", zlib.compress(bytes(raw_lines), 9)) - - # IEND - iend = _png_chunk(b"IEND", b"") - - return signature + ihdr + idat + iend - - -def _set_pixel( - pixels: list[list[tuple[int, int, int, int]]], - x: int, - y: int, - color: tuple[int, int, int], - alpha: float, -) -> None: - """Set a pixel with alpha blending.""" - height = len(pixels) - width = len(pixels[0]) if height > 0 else 0 - if not (0 <= x < width and 0 <= y < height): - return - a = max(0, min(255, int(alpha * 255))) - if a == 0: - return - bg = pixels[y][x] - if a == 255: - pixels[y][x] = (color[0], color[1], color[2], 255) - return - fa = a / 255.0 - ba = bg[3] / 255.0 - oa = fa + ba * (1 - fa) - if oa == 0: - return - r = int((color[0] * fa + bg[0] * ba * (1 - fa)) / oa) - g = int((color[1] * fa + bg[1] * ba * (1 - fa)) / oa) - b = int((color[2] * fa + bg[2] * ba * (1 - fa)) / oa) - pixels[y][x] = (r, g, b, int(oa * 255)) - - -def _draw_arrow( - pixels: list[list[tuple[int, int, int, int]]], - color: tuple[int, int, int], -) -> None: - """Draw a bold right-pointing arrow: thick shaft + solid filled triangle head. - - Arrow is positioned between the app icon (x=155) and Applications (x=505), - centered vertically at y=130 (icons moved up to reduce top space). - """ - # Arrow geometry - cy = 130 # vertical center — aligned with icon row - - # Shaft: solid rectangle - shaft_x1 = 250 - shaft_x2 = 380 - shaft_half_h = 3 # 6px thick shaft - - for y in range(cy - shaft_half_h, cy + shaft_half_h + 1): - for x in range(shaft_x1, shaft_x2 + 1): - # Anti-alias top and bottom edges - dist_from_edge = shaft_half_h - abs(y - cy) - if dist_from_edge >= 1: - _set_pixel(pixels, x, y, color, 1.0) - elif dist_from_edge > 0: - _set_pixel(pixels, x, y, color, dist_from_edge) - - # Arrowhead: filled triangle pointing right - # Vertices: left-top (375, 180), left-bottom (375, 220), tip (420, 200) - head_left = 375 - head_right = 420 - head_half_h = 20 # 40px tall triangle - - for x in range(head_left, head_right + 1): - # At this x, how tall is the triangle? - t = (x - head_left) / (head_right - head_left) # 0 at left, 1 at tip - half_height = head_half_h * (1.0 - t) - - y_top = cy - half_height - y_bot = cy + half_height - - for y in range(int(y_top) - 1, int(y_bot) + 2): - dist_top = y - y_top # positive = inside from top - dist_bot = y_bot - y # positive = inside from bottom - - if dist_top >= 1.0 and dist_bot >= 1.0: - _set_pixel(pixels, x, y, color, 1.0) - elif dist_top > 0 and dist_bot > 0: - alpha = min(dist_top, dist_bot) - _set_pixel(pixels, x, y, color, min(1.0, alpha)) - - -def _draw_text( - pixels: list[list[tuple[int, int, int, int]]], - x: int, - y: int, - text: str, - color: tuple[int, int, int], - scale: int = 1, -) -> None: - """Draw pixel text using a built-in 5x7 bitmap font.""" - glyphs: dict[str, list[str]] = { - "A": [" 111 ", "1 1", "1 1", "11111", "1 1", "1 1", "1 1"], - "D": ["1110 ", "1 01", "1 01", "1 01", "1 01", "1 01", "1110 "], - "E": ["11111", "1 ", "1 ", "1111 ", "1 ", "1 ", "11111"], - "O": [" 111 ", "1 1", "1 1", "1 1", "1 1", "1 1", " 111 "], - "X": ["1 1", " 1 1 ", " 1 ", " 1 1 ", "1 1", "1 1", " "], - "a": [" ", " ", " 111 ", " 01", " 1111", "1 01", " 1111"], - "c": [" ", " ", " 111 ", "1 ", "1 ", "1 ", " 111 "], - "g": [" ", " ", " 1111", "1 01", " 1111", " 01", " 110 "], - "i": [" ", " 1 ", " ", " 1 ", " 1 ", " 1 ", " 1 "], - "l": [" ", " 1 ", " 1 ", " 1 ", " 1 ", " 1 ", " 1 "], - "n": [" ", " ", "1 10 ", "11 01", "1 01", "1 01", "1 01"], - "o": [" ", " ", " 110 ", "1 01", "1 01", "1 01", " 110 "], - "p": [" ", " ", "1110 ", "1 01", "1110 ", "1 ", "1 "], - "r": [" ", " ", " 110 ", "1 ", "1 ", "1 ", "1 "], - "s": [" ", " ", " 111 ", "1 ", " 11 ", " 01", "111 "], - "t": [" ", " 1 ", "1111 ", " 1 ", " 1 ", " 1 ", " 11 "], - " ": [" ", " ", " ", " ", " ", " ", " "], - } - - cursor_x = x - for ch in text: - glyph = glyphs.get(ch) - if glyph is None: - cursor_x += 6 * scale - continue - for row_idx, row_str in enumerate(glyph): - for col_idx, pixel_ch in enumerate(row_str): - if pixel_ch == "1": - for sy in range(scale): - for sx in range(scale): - py = y + row_idx * scale + sy - px = cursor_x + col_idx * scale + sx - _set_pixel(pixels, px, py, color, 1.0) - cursor_x += (len(glyph[0]) + 1) * scale +BACKGROUND_PNG = Path(__file__).parent / "background.png" def generate_background(output_path: str) -> None: - """Generate the DMG background image.""" - width, height = 660, 440 - - # Solid dark background — clean, no gradients or vignettes - bg_color = (22, 22, 24, 255) # macOS dark mode surface - - pixels: list[list[tuple[int, int, int, int]]] = [ - [bg_color] * width for _ in range(height) - ] - - # Draw bold white right-pointing arrow between icon positions - # Arrow is vertically centered at icon row (y=130) - _draw_arrow(pixels, (255, 255, 255)) - - # Draw white icon labels (Finder's labels minimized via text size 10) - # Finder positions icons by center point: x=155 and x=505 - # Icon bottom edge is at y=130+64=194, labels start below at y=202 - label_color = (255, 255, 255) - label_scale = 2 - char_width = 6 * label_scale # 5px glyph + 1px spacing, scaled - # "EXO" — 3 chars, centered under icon at x=155 - exo_width = 3 * char_width - _draw_text(pixels, 155 - exo_width // 2, 202, "EXO", label_color, scale=label_scale) - # "Applications" — 12 chars, centered under icon at x=505 - apps_width = 12 * char_width - _draw_text( - pixels, - 505 - apps_width // 2, - 202, - "Applications", - label_color, - scale=label_scale, - ) - - # Draw "Drag to install" instruction — bright white, scale=3 for readability - # Text is ~15 chars × 18px/char (at scale=3) = ~270px wide - text_x = (width - 270) // 2 - _draw_text(pixels, text_x, 310, "Drag to install", (255, 255, 255), scale=3) - - # Write PNG - png_data = _create_png(width, height, pixels) - Path(output_path).write_bytes(png_data) + """Copy the static background image to the output path.""" + if not BACKGROUND_PNG.exists(): + print(f"Error: {BACKGROUND_PNG} not found", file=sys.stderr) + sys.exit(1) + shutil.copy2(BACKGROUND_PNG, output_path) if __name__ == "__main__":