Files
kosta2014p ec6f13ddcc PixelLauncher 2.1.0 — стабильная сборка
Офлайн-лаунчер Minecraft: аккаунты без Microsoft, установка версий и лоадеров
(Fabric, Quilt, Forge, NeoForge), каталог модпаков Modrinth, поиск и обновление
модов, разбор вылетов, системный трей, автозагрузка, плагины, прокси и зеркала.
2026-10-18 22:44:19 +07:00

277 lines
9.9 KiB
Python

# -*- 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)