# -*- coding: utf-8 -*- """Генератор логотипа PixelLauncher — пиксель-арт на чистом stdlib. Рисует логотип на логическом поле 32x32 и масштабирует x8 -> 256x256 PNG. Запуск: python assets/make_logo.py # варианты land/gold/face -> превью python assets/make_logo.py land out.png # конкретный вариант в файл Варианты: land — пиксельная «P», сверху трава, снизу земля (буква-как-блок); gold — золотая «P» на тёмном фоне с травяной полосой; face — зелёный блок с тёмной «P» (родственник крипера). """ import os import struct import sys import zlib S = 32 # логическое поле K = 8 # масштаб -> 256 # палитра сайта/лаунчера DARK = (27, 31, 43) # #1b1f2b фон FRAME = (14, 16, 22) # тёмная рамка LINE = (51, 58, 77) # #333a4d GOLD = (255, 180, 84) # #ffb454 GOLD_D = (217, 171, 52) # #d9ab34 GOLD_L = (255, 217, 138) GRASS = (124, 179, 66) # rgb(124,179,66) GRASS_D = (90, 143, 50) GRASS_L = (148, 201, 95) DIRT = (125, 88, 54) # #7d5836 DIRT_D = (107, 74, 47) # #6b4a2f INK = (12, 13, 18) # пиксельная «P»: 6 шириной, 8 высотой? — см. LETTER (x: 6 колонок, 8 строк) LETTER = [ "XXXXXX", "X..XX.", # снизу-вверх перевернём: рисуем снизу вверх? см. ниже ] # настоящая «P» 6x8 (X = заливка) LETTER = [ "XXXXXX", "X..XX.", "X..XX.", "XXXXXX", "XX....", "XX....", "XX....", "XX....", ] # большая «P» 10x16 для 32x32 поля LETTER_BIG = [ "XXXXXXXXXX", "XX......XX", "XX......XX", "XX......XX", "XX......XX", "XX......XX", "XXXXXXXXXX", "XX........", "XX........", "XX........", "XX........", "XX........", "XX........", "XX........", "XX........", "XX........", ] def canvas(): return [[None] * S for _ in range(S)] def in_round_rect(x, y, x0, y0, x1, y1, r): """Пиксель внутри прямоугольника со скруглёнными углами (жёсткие углы).""" if not (x0 <= x <= x1 and y0 <= y <= y1): return False for cx, cy in ((x0 + r, y0 + r), (x1 - r, y0 + r), (x0 + r, y1 - r), (x1 - r, y1 - r)): if ((x < x0 + r or x > x1 - r) and (y < y0 + r or y > y1 - r)): dx = x - (x0 + r if x < x0 + r else x1 - r) dy = y - (y0 + r if y < y0 + r else y1 - r) if dx * dx + dy * dy > r * r: return False return True def fill_round(px, color, x0, y0, x1, y1, r=3): for y in range(S): for x in range(S): if in_round_rect(x, y, x0, y0, x1, y1, r): px[y][x] = color def speckle(px, region, tones, seed=1, every=5): """Базовый тон + редкие акценты (1 из every) — без шахматного шума.""" x0, y0, x1, y1 = region for y in range(y0, y1 + 1): for x in range(x0, x1 + 1): if px[y][x] is None: continue n = (x * 7 + y * 13 + seed) % every px[y][x] = tones[1] if n == 0 else tones[0] def hband(px, y0, y1, row_colors, r=3): """Горизонтальная полоса строго внутри внутреннего скругления.""" for y in range(y0, y1 + 1): c = row_colors[y - y0] for x in range(1, S - 1): if in_round_rect(x, y, 1, 1, S - 2, S - 2, r): px[y][x] = c def stamp_letter(px, gx, gy, colors, outline=None): """Рисует LETTER_BIG в точке (gx, gy); colors — список цветов по строкам буквы (1 цвет = весь ряд), outline — контур на 1 пиксель больше.""" h, w = len(LETTER_BIG), len(LETTER_BIG[0]) if outline: for dy in range(-1, h + 1): for dx in range(-1, w + 1): inside = 0 <= dy < h and 0 <= dx < w and \ LETTER_BIG[dy][dx] == "X" near = inside if not inside: for ey in (dy - 1, dy, dy + 1): for ex in (dx - 1, dx, dx + 1): if 0 <= ey < h and 0 <= ex < w and \ LETTER_BIG[ey][ex] == "X": near = True if near and not inside: yy, xx = gy + dy, gx + dx if 0 <= yy < S and 0 <= xx < S: px[yy][xx] = outline for dy, row in enumerate(LETTER_BIG): for dx, ch in enumerate(row): if ch == "X": yy, xx = gy + dy, gx + dx if 0 <= yy < S and 0 <= xx < S: px[yy][xx] = colors[min(dy, len(colors) - 1)] def variant_land(): """Буква-как-блок: трава сверху, земля снизу, тёмный фон.""" px = canvas() fill_round(px, FRAME, 0, 0, S - 1, S - 1, 4) fill_round(px, DARK, 1, 1, S - 2, S - 2, 3) # фоновая травяная полоса снизу (как на сайте) hband(px, S - 7, S - 5, [GRASS_D, GRASS, GRASS]) speckle(px, (2, S - 7, S - 3, S - 5), (GRASS, GRASS_L), seed=3, every=5) # буква: верх трава, низ земля, между ними тень-кромка gx, gy = (S - 10) // 2, 5 colors = ([GRASS_L, GRASS_L] + [GRASS] * 3 + [GRASS_D] + [DIRT] * 9 + [DIRT_D]) stamp_letter(px, gx, gy, colors, outline=INK) return px def variant_gold(): """Золотая «P» на тёмном слоте с травяной полосой.""" px = canvas() fill_round(px, FRAME, 0, 0, S - 1, S - 1, 4) fill_round(px, DARK, 1, 1, S - 2, S - 2, 3) gx, gy = (S - 10) // 2, 4 colors = [GOLD_L, GOLD_L] + [GOLD] * 11 + [GOLD_D] * 3 stamp_letter(px, gx, gy, colors, outline=INK) # травяной край снизу hband(px, S - 6, S - 4, [GRASS_D, GRASS, GRASS]) speckle(px, (2, S - 6, S - 3, S - 4), (GRASS, GRASS_L), seed=5, every=5) return px def variant_face(): """Зелёный блок с тёмной «P» — родственник крипера.""" px = canvas() fill_round(px, FRAME, 0, 0, S - 1, S - 1, 4) fill_round(px, GRASS, 1, 1, S - 2, S - 2, 3) speckle(px, (1, 1, S - 2, S - 2), (GRASS, GRASS_L), seed=7, every=6) # грунт снизу — как у травяного блока (кромка, земля, тень) hband(px, S - 9, S - 2, [GRASS_D] + [DIRT] * 6 + [DIRT_D]) speckle(px, (1, S - 9, S - 2, S - 2), (DIRT, DIRT_D), seed=11, every=4) gx, gy = (S - 10) // 2, 5 stamp_letter(px, gx, gy, [INK] * 16, outline=None) return px VARIANTS = {"land": variant_land, "gold": variant_gold, "face": variant_face} def rgba(c): """Всегда RGBA: полупрозрачность/прозрачность углов — 4 канала.""" return c if len(c) == 4 else (c[0], c[1], c[2], 255) def write_png(path, px, k=K): h = w = S * k rows = [] for y in range(S): row = [] for x in range(S): c = px[y][x] or (0, 0, 0, 0) row.append(rgba(c)) for _ in range(k): line = bytearray(b"\x00") for c in row: line += bytes(c) * k rows.append(bytes(line)) raw = b"".join(rows) def chunk(tag, data): return (struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data) & 0xffffffff)) ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0) with open(path, "wb") as f: f.write(b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b"")) return w, h def preview(name, out_dir): """Строка из трёх вариантов в 256px + миниатюра 32px рядом (nearest).""" tiles = [] for key in ("land", "gold", "face"): px = VARIANTS[key]() small = [[px[y][x] or (0, 0, 0, 0) for x in range(S)] for y in range(S)] tiles.append((px, small)) gap = 8 w = (S * K + gap + S + gap) * 3 + gap h = S * K + gap * 2 buf = [[(40, 44, 60, 255)] * w for _ in range(h)] ox = gap for px, small in tiles: for y in range(S * K): for x in range(S * K): c = px[y // K][x // K] or (0, 0, 0, 0) buf[gap + y][ox + x] = rgba(c) my = gap + (S * K - S) // 2 for y in range(S): for x in range(S): buf[my + y][ox + S * K + gap + x] = rgba(small[y][x]) ox += S * K + gap + S + gap rows = [b"\x00" + bytes(v for p in row for v in p) for row in buf] raw = b"".join(rows) def chunk(tag, data): return (struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data) & 0xffffffff)) ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0) with open(out_dir, "wb") as f: f.write(b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b"")) return out_dir if __name__ == "__main__": here = os.path.dirname(os.path.abspath(__file__)) if len(sys.argv) >= 3: px = VARIANTS[sys.argv[1]]() print(write_png(sys.argv[2], px)) else: tmp = os.path.join(os.environ.get("TEMP", here), "logo_preview.png") preview("all", tmp) for key in VARIANTS: out = os.path.join(os.environ.get("TEMP", here), "logo_%s.png" % key) print(key, write_png(out, VARIANTS[key]())) print("preview:", tmp)