mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
Take the circular SUB dragon logo and lock up an "OS" wordmark beneath the "SUB" letters, in the dragon's accent blue (#08B3FA) with the same oblique cut and black keyline, so the mark reads "SUB-OS" as one unit. Store the artwork in assets/ as the canonical source and regenerate the boot splash asset (init/logo_data.c) from it; the "OS" stays legible at the 160x160 boot resolution. Point mklogo.py's example at the new source. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
155 lines
5.4 KiB
Python
Executable File
155 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate the SUB-OS boot logo asset from a source image.
|
|
|
|
Emits init/logo_data.c: a 4-bit palette-indexed bitmap plus the ARGB palette
|
|
and an ASCII-art rendition used before the framebuffer is up.
|
|
|
|
python3 scripts/mklogo.py assets/logo_SUB_OS.png
|
|
|
|
Re-run this whenever the source artwork changes; init/logo_data.c is generated
|
|
and should not be hand-edited.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from PIL import Image
|
|
|
|
SIZE = 160 # Bitmap edge length in pixels
|
|
COLORS = 16 # Palette entries, one nibble per pixel
|
|
BACKGROUND = (11, 19, 43) # 0xFF0B132B, the desktop wallpaper's top gradient stop
|
|
ASCII_W = 46 # Text-mode logo width in characters
|
|
ASCII_RAMP = " .:-=+*#%@"
|
|
|
|
|
|
def build_bitmap(path):
|
|
img = Image.open(path).convert("RGBA")
|
|
|
|
# Composite onto the wallpaper colour so the logo's transparent surround
|
|
# blends into the splash instead of turning black.
|
|
canvas = Image.new("RGB", img.size, BACKGROUND)
|
|
canvas.paste(img, (0, 0), img)
|
|
|
|
canvas = canvas.resize((SIZE, SIZE), Image.LANCZOS)
|
|
|
|
# Dithering would scatter isolated palette indices through flat areas and
|
|
# look like noise at this size, so quantize with it disabled.
|
|
quant = canvas.quantize(colors=COLORS, dither=Image.NONE)
|
|
|
|
raw_palette = quant.getpalette()[: COLORS * 3]
|
|
palette = [tuple(raw_palette[i * 3 : i * 3 + 3]) for i in range(COLORS)]
|
|
indices = list(quant.getdata())
|
|
return palette, indices
|
|
|
|
|
|
def build_ascii(path):
|
|
img = Image.open(path).convert("RGBA")
|
|
canvas = Image.new("RGB", img.size, BACKGROUND)
|
|
canvas.paste(img, (0, 0), img)
|
|
|
|
# Console cells are about twice as tall as they are wide.
|
|
height = max(1, ASCII_W // 2)
|
|
small = canvas.convert("L").resize((ASCII_W, height), Image.LANCZOS)
|
|
|
|
pixels = list(small.getdata())
|
|
lo, hi = min(pixels), max(pixels)
|
|
span = max(1, hi - lo)
|
|
|
|
rows = []
|
|
for y in range(height):
|
|
row = []
|
|
for x in range(ASCII_W):
|
|
level = (pixels[y * ASCII_W + x] - lo) * (len(ASCII_RAMP) - 1) // span
|
|
row.append(ASCII_RAMP[level])
|
|
rows.append("".join(row).rstrip())
|
|
while rows and not rows[0]:
|
|
rows.pop(0)
|
|
while rows and not rows[-1]:
|
|
rows.pop()
|
|
return rows
|
|
|
|
|
|
def transparent_mask(palette):
|
|
"""Bitmask of palette entries close enough to the background to skip.
|
|
|
|
The source art has a transparent surround, which the compositing step turns
|
|
into the background colour. Quantization then spreads that flat area across
|
|
several near-identical entries, and drawing them would stamp a visible
|
|
rectangle over whatever the logo sits on. Marking them lets the renderer
|
|
skip those pixels so only the logo itself is painted.
|
|
"""
|
|
mask = 0
|
|
br, bg, bb = BACKGROUND
|
|
for i, (r, g, b) in enumerate(palette):
|
|
if abs(r - br) + abs(g - bg) + abs(b - bb) <= 12:
|
|
mask |= 1 << i
|
|
return mask
|
|
|
|
|
|
def emit(path, palette, indices, ascii_rows, out_path):
|
|
packed = []
|
|
for i in range(0, len(indices), 2):
|
|
hi = indices[i] & 0x0F
|
|
lo = indices[i + 1] & 0x0F if i + 1 < len(indices) else 0
|
|
packed.append((hi << 4) | lo)
|
|
|
|
lines = []
|
|
lines.append("// Generated by scripts/mklogo.py -- do not edit by hand.")
|
|
lines.append("// Source artwork: %s" % os.path.basename(path))
|
|
lines.append("")
|
|
lines.append('#include <init/bootlogo.h>')
|
|
lines.append("")
|
|
lines.append("const int boot_logo_width = %d;" % SIZE)
|
|
lines.append("const int boot_logo_height = %d;" % SIZE)
|
|
lines.append("")
|
|
lines.append("// Palette entries that are background rather than artwork; the renderer")
|
|
lines.append("// skips these so the logo composites onto whatever is behind it.")
|
|
lines.append("const uint16_t boot_logo_transparent_mask = 0x%04X;"
|
|
% transparent_mask(palette))
|
|
lines.append("")
|
|
lines.append("// 16-entry ARGB palette; index 0 is the wallpaper background.")
|
|
lines.append("const uint32_t boot_logo_palette[%d] = {" % COLORS)
|
|
for r, g, b in palette:
|
|
lines.append(" 0xFF%02X%02X%02X," % (r, g, b))
|
|
lines.append("};")
|
|
lines.append("")
|
|
lines.append("// %dx%d pixels, two 4-bit indices per byte, row major." % (SIZE, SIZE))
|
|
lines.append("const uint8_t boot_logo_bitmap[%d] = {" % len(packed))
|
|
for i in range(0, len(packed), 16):
|
|
chunk = ", ".join("0x%02X" % b for b in packed[i : i + 16])
|
|
lines.append(" %s," % chunk)
|
|
lines.append("};")
|
|
lines.append("")
|
|
lines.append("// ASCII rendition for the text console, before the framebuffer exists.")
|
|
lines.append("const char* const boot_logo_ascii[] = {")
|
|
for row in ascii_rows:
|
|
lines.append(' "%s",' % row.replace("\\", "\\\\").replace('"', '\\"'))
|
|
lines.append(" NULL")
|
|
lines.append("};")
|
|
lines.append("")
|
|
|
|
with open(out_path, "w") as fh:
|
|
fh.write("\n".join(lines))
|
|
|
|
return len(packed), len(ascii_rows)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(__doc__)
|
|
return 1
|
|
|
|
src = os.path.expanduser(sys.argv[1])
|
|
out = sys.argv[2] if len(sys.argv) > 2 else "init/logo_data.c"
|
|
|
|
palette, indices = build_bitmap(src)
|
|
ascii_rows = build_ascii(src)
|
|
nbytes, nrows = emit(src, palette, indices, ascii_rows, out)
|
|
|
|
print("%s: %dx%d bitmap, %d palette entries, %d bytes packed, %d ASCII rows"
|
|
% (out, SIZE, SIZE, len(palette), nbytes, nrows))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|