#!/usr/bin/env python3 """Erzeugt das HoneyOS-Wappen aus einem Textraster. Die Biene steht **hier** und nirgends sonst: als 16 × 16 Zeichen, ein Zeichen je Pixel. Daraus entstehen alle Fassungen, die HoneyOS braucht — SVG für die beiden Leisten, PNG für Dokumentation und Vorschau, und eine Fassung aus Schriftzeichen fürs System-Fenster und den Startlauf. python3 tools/biene.py Kein Pillow, keine Abhängigkeit: Ein PNG ist ein Kopfteil, ein zlib-Strom aus Bildzeilen (jede mit einem Filterbyte davor) und eine Prüfsumme — das kann die Standardbibliothek. Der Vorteil gegenüber einem Bildprogramm ist nicht die Bequemlichkeit, sondern dass die **Quelle Text bleibt**: Ein Pixel ändern heißt einen Buchstaben ändern, danach stimmen wieder alle Fassungen überein. """ import json import pathlib import struct import zlib WURZEL = pathlib.Path(__file__).resolve().parent.parent # Die Farben nach dem Vorbild: Die Streifen sind **braun**. Schwarz sind nur # Augen, Beine und Fühler — das ist der Unterschied zwischen warm und hart. FARBEN = { "Y": (242, 179, 61), # Honiggelb, der Körper "L": (255, 208, 96), # Oberkante, eine Spur heller "y": (210, 149, 42), # Unterseite im Schatten "S": (92, 58, 28), # Streifen "s": (70, 43, 20), # Streifen an der Unterkante "E": (24, 20, 16), # Augenfeld und Beine "B": (99, 199, 192), # der Lichtpunkt im Auge "D": (59, 36, 18), # Fühler "W": (245, 242, 231), # Flügel "w": (214, 209, 190), # Flügel, untere Lage } # Von der Seite: Stachel hinten, Kopf in Laufrichtung. Diese Ansicht ist es # geworden, weil ihre Streifen **senkrecht** laufen — deshalb bleibt sie auch # als Text lesbar, wo die Vorderansicht in Streifen zerfällt. BIENE = [ "....WW...WW.....", "..WWWWW.WWWWW...", "..WWWWWWWWWWW.D.", "...WWWWWWWWW.DD.", "...LLLLLLLLLLL..", "..YYSSYYSSYYYYY.", ".YYYSSYYSSYYEBY.", "SYYYSSYYSSYYEEY.", ".YYYSSYYSSYYYYY.", "..yssyyssyyyyyy.", "...EE..EE..EE...", "...EE..EE..EE...", "................", "................", "................", "................", ] # ── Die 3/4-Ansicht ────────────────────────────────────────────────────── # # Nur für den Anmeldebildschirm: groß, einmal am Tag, mit Zeit zum Hinsehen. # Unter 64 px zerfällt sie — zu viele Kanten auf zu wenig Pixeln. # # Ein Würfel hat in Pixelkunst die Steigung 2:1: zwei Pixel nach rechts, einer # nach unten. Von Hand wird das krumm, also werden die drei sichtbaren Flächen # gerechnet — Deckel als Raute, darunter zwei Wangen. Wichtig ist, **zeilenweise** # zu rechnen: Wer die Raute Punkt für Punkt setzt, lässt Löcher, weil zwei # Schritte nach rechts nur einen nach unten kosten. def wuerfel(breite, wand): hoehe = breite // 2 mitte = breite / 2 deckel = { (x, y) for y in range(hoehe) for x in range(breite) if abs(x + 0.5 - mitte) / 2 + abs(y + 0.5 - hoehe / 2) <= hoehe / 2 } unterkante = {} for (x, y) in deckel: unterkante[x] = max(unterkante.get(x, -1), y) flaechen = {q: "T" for q in deckel} for x, yb in unterkante.items(): for t in range(1, wand + 1): flaechen[(x, yb + t)] = "V" if x < mitte else "R" # vorn / rechts return flaechen, unterkante, mitte def schraeg(breite=22, wand=10, dx=5, dy=9, n=32, band=5): flaechen, unterkante, mitte = wuerfel(breite, wand) g = [["." for _ in range(n)] for _ in range(n)] def setz(x, y, c): if 0 <= x + dx < n and 0 <= y + dy < n: g[y + dy][x + dx] = c # Die Streifen laufen parallel zur linken Deckelkante (u = x - 2y) und # setzen sich auf der rechten Wange als senkrechte Bänder fort: dieselbe # Rechnung, ausgewertet an der Kante, an der die Wange beginnt. Deshalb # passen sie über den Knick hinweg zusammen. streifig = lambda x, y: ((x - 2 * y) // band) % 2 == 0 for (x, y), art in sorted(flaechen.items()): if art == "T": setz(x, y, "S" if streifig(x, y) else "L") elif art == "R": setz(x, y, "s" if streifig(x, unterkante[x]) else "y") else: setz(x, y, "Y") # Augen: Rechtecke in Flächenkoordinaten — auf dem Schirm werden daraus # Parallelogramme, genau wie beim Vorbild. for ax in (2, 7): for x in range(ax, ax + 3): if x in unterkante: for t in (3, 4, 5): setz(x, unterkante[x] + t, "E") if ax + 1 in unterkante: setz(ax + 1, unterkante[ax + 1] + 4, "B") for k in range(3): # zwei Flügel in derselben Schräge for b in range(6): setz(3 + 2 * k + b, -2 + k, "W") setz(10 + 2 * k + b, -4 + k, "w") for k in range(2): # kurze Fühler am Deckelrand setz(int(mitte) - 5 - k, -3 - k, "D") setz(int(mitte) + 6 + k, -5 - k, "D") return ["".join(z) for z in g] def svg(raster): """Waagerechte Läufe gleicher Farbe werden ein Rechteck — dieselbe Idee wie bei den Pixelsymbolen in web/js/glyphs.js, nur mit Farbe statt currentColor. Warum SVG und nicht PNG für die Leisten: Die HoneyBar läuft in *fremden* Apps. Ein holt dort eine Datei von HoneyOS, und ob das erlaubt ist, entscheidet die CSP der fremden App. Markup kann niemand blockieren.""" teile = [] for y, zeile in enumerate(raster): x = 0 while x < len(zeile): z = zeile[x] if z not in FARBEN: x += 1 continue lauf = 1 while x + lauf < len(zeile) and zeile[x + lauf] == z: lauf += 1 r, g, b = FARBEN[z] teile.append(f'') x += lauf n = len(raster) return (f'") def png(raster, faktor, ziel): """Vergrößert wird um ganze Faktoren, damit jedes Pixel ein Quadrat bleibt.""" n = len(raster) roh = bytearray() for zeile in raster: pixel = bytearray() for zeichen in zeile: r, g, b = FARBEN.get(zeichen, (0, 0, 0)) pixel += bytes((r, g, b, 255 if zeichen in FARBEN else 0)) * faktor for _ in range(faktor): roh += b"\x00" + pixel # Filterbyte 0: keine Vorhersage def block(art, daten): return (struct.pack(">I", len(daten)) + art + daten + struct.pack(">I", zlib.crc32(art + daten) & 0xFFFFFFFF)) kopf = struct.pack(">IIBBBBB", n * faktor, n * faktor, 8, 6, 0, 0, 0) ziel.write_bytes(b"\x89PNG\r\n\x1a\n" + block(b"IHDR", kopf) + block(b"IDAT", zlib.compress(bytes(roh), 9)) + block(b"IEND", b"")) # Ein Zeichen ist etwa halb so breit wie hoch — **ein Pixel sind zwei Zeichen**, # sonst steht die Biene hochkant. Blockzeichen (█ ▓ ▒) wären naheliegend, # kacheln aber nicht: Zwischen den Zellen bleiben Fugen, und der Körper wird zur # Waffel. Gewöhnliche Schriftzeichen behaupten gar nicht erst, eine Fläche zu # sein — deshalb sehen sie besser aus. ZEICHEN = {"Y": "#", "L": "#", "y": "#", "S": ":", "s": ":", "E": ":", "D": ":", "W": "-", "B": "o"} def text(raster): zeilen = ["".join(ZEICHEN.get(c, " ") * 2 for c in z).rstrip() for z in raster] while zeilen and not zeilen[0]: zeilen.pop(0) while zeilen and not zeilen[-1]: zeilen.pop() return "\n".join(zeilen) def main(): bild = svg(BIENE) zeichen = text(BIENE) (WURZEL / "web/img/biene.svg").write_text(bild + "\n") # Für die Anmeldung: einmal groß, in Schrägsicht. drei = schraeg() (WURZEL / "web/img/biene-3d.svg").write_text(svg(drei) + "\n") png(drei, 8, WURZEL / "web/img/biene-3d-256.png") png(BIENE, 16, WURZEL / "web/img/biene-256.png") png(BIENE, 2, WURZEL / "web/img/biene-32.png") # Für die Oberfläche als Modul: Das Wappen im Markup (die Leiste holt kein # Bild nach) und die Zeichenfassung fürs System-Fenster. (WURZEL / "web/js/biene.js").write_text( "/* Erzeugt von tools/biene.py — nicht von Hand ändern.\n" " *\n" " * Das Wappen als Markup statt als Bilddatei: So kommt es ohne zweite\n" " * Anfrage aus und lässt sich mit dem Text daneben ausrichten.\n" " */\n" # Einfache Anführungszeichen: So steht das Markup wörtlich da und ist # mit web/img/biene.svg vergleichbar — ein Test hält beides gleich. f"export const BIENE_SVG = '{bild}';\n\n" f"export const BIENE_ASCII = {json.dumps(zeichen)};\n" ) print(text(BIENE)) print(f"\nweb/img/biene.svg ({len(bild)} Zeichen), biene-3d.svg, drei PNG, web/js/biene.js") if __name__ == "__main__": main()