Upload via GUI (123 Dateien)

This commit is contained in:
2026-10-03 07:09:08 +00:00
parent bc68395437
commit 10ee45cbc2
11 changed files with 1148 additions and 16 deletions
+86
View File
@@ -0,0 +1,86 @@
"""
make-mc-textures.py – erzeugt die 16×16-Kacheln für das Minecraft-Design
(src/assets/design/*.png). Eigene, zufällig erzeugte Muster im Block-Stil –
keine Original-Texturen aus dem Spiel.
Aufruf: python scripts/make-mc-textures.py
"""
import os
import random
from PIL import Image
ZIEL = os.path.join(os.path.dirname(__file__), '..', 'src', 'assets', 'design')
os.makedirs(ZIEL, exist_ok=True)
def klemme(v):
return max(0, min(255, int(v)))
def rauschen(basis, streuung, seed, flecken=None):
"""Grundfarbe mit Pixelrauschen; flecken = (anteil, farbe, streuung)."""
rnd = random.Random(seed)
img = Image.new('RGB', (16, 16))
px = img.load()
for y in range(16):
for x in range(16):
d = rnd.randint(-streuung, streuung)
farbe = basis
if flecken and rnd.random() < flecken[0]:
farbe = flecken[1]
d = rnd.randint(-flecken[2], flecken[2])
px[x, y] = tuple(klemme(c + d) for c in farbe)
return img
def speichere(img, name):
img.save(os.path.join(ZIEL, name))
print(name)
# Hintergrund der Instanzliste: dunkle Erde (wie hinter Menüs im Spiel)
speichere(rauschen((44, 32, 23), 6, 11, (0.16, (31, 22, 15), 4)), 'erde-dunkel.png')
# Leisten (Werkzeugleiste, Seitenleiste, Statusleiste): dunkler Schiefer
schiefer = rauschen((46, 46, 50), 6, 23, (0.12, (34, 34, 38), 3))
px = schiefer.load()
rnd = random.Random(5)
for _ in range(5): # ein paar waagerechte Bruchkanten wie bei Tiefenschiefer
y = rnd.randrange(16)
x0 = rnd.randrange(16)
for x in range(x0, min(16, x0 + rnd.randint(3, 7))):
px[x, y] = (30, 30, 33)
speichere(schiefer, 'schiefer.png')
# Dialoge: Stein
stein = rauschen((78, 78, 80), 8, 37, (0.10, (60, 60, 62), 4))
speichere(stein, 'stein.png')
# Holzbretter (z. B. Kopf der Dialoge): vier Bretter zu je 4 Pixeln
bretter = Image.new('RGB', (16, 16))
px = bretter.load()
rnd = random.Random(41)
farben = [(122, 92, 56), (114, 86, 52), (128, 97, 60), (118, 89, 54)]
fugen = [3, 11, 7, 14]
for brett in range(4):
for y in range(brett * 4, brett * 4 + 4):
for x in range(16):
c = farben[brett]
d = rnd.randint(-5, 5)
if y == brett * 4 + 3: # dunkle Fuge unter jedem Brett
c, d = (74, 55, 33), rnd.randint(-2, 2)
elif x == fugen[brett]: # Stoßfuge
c, d = (88, 66, 40), 0
px[x, y] = tuple(klemme(v + d) for v in c)
speichere(bretter, 'bretter.png')
# Grasnarbe: oberer Rand der Werkzeugleiste (16×4, grün mit "Tropfen")
gras = Image.new('RGBA', (16, 4), (0, 0, 0, 0))
px = gras.load()
rnd = random.Random(53)
tiefe = [2, 3, 2, 2, 4, 3, 2, 3, 2, 2, 3, 4, 2, 3, 2, 3]
for x in range(16):
for y in range(tiefe[x]):
g = rnd.randint(-10, 10)
px[x, y] = (klemme(84 + g), klemme(140 + g), klemme(52 + g // 2), 255)
speichere(gras, 'gras.png')
+270
View File
@@ -0,0 +1,270 @@
'use strict';
/*
* make-pixelfont.js – erzeugt src/assets/aeropixel.ttf
* -----------------------------------------------------
* Eigene Pixel-Schrift für das Minecraft-Design (keine fremde Schrift, also
* keine Lizenzfragen). Jede Glyphe ist ein Raster aus '#' (Pixel) und ' ':
* Zeile 0 ist die Oberkante der Großbuchstaben, Zeile 6 sitzt auf der
* Grundlinie, Zeilen 7–8 sind Unterlängen (g, j, p, q, y, Komma).
* Fehlende Zeichen fallen im Launcher automatisch auf Segoe UI zurück.
*
* Aufruf: node scripts/make-pixelfont.js
*/
const fs = require('fs');
const path = require('path');
const P = 125; // ein Pixel in Font-Einheiten (8 Pixel = 1 em)
const EM = 1000;
const ASC = 1000; // 7 Zeilen Großbuchstaben + 1 Zeile Luft
const DESC = 250; // 2 Zeilen Unterlänge
const G = {
A: [' ### ', '# #', '# #', '#####', '# #', '# #', '# #'],
B: ['#### ', '# #', '# #', '#### ', '# #', '# #', '#### '],
C: [' ### ', '# #', '# ', '# ', '# ', '# #', ' ### '],
D: ['#### ', '# #', '# #', '# #', '# #', '# #', '#### '],
E: ['#####', '# ', '# ', '#### ', '# ', '# ', '#####'],
F: ['#####', '# ', '# ', '#### ', '# ', '# ', '# '],
G: [' ####', '# ', '# ', '# ##', '# #', '# #', ' ####'],
H: ['# #', '# #', '# #', '#####', '# #', '# #', '# #'],
I: ['###', ' # ', ' # ', ' # ', ' # ', ' # ', '###'],
J: [' #', ' #', ' #', ' #', ' #', '# #', ' ### '],
K: ['# #', '# # ', '# # ', '## ', '# # ', '# # ', '# #'],
L: ['# ', '# ', '# ', '# ', '# ', '# ', '#####'],
M: ['# #', '## ##', '# # #', '# #', '# #', '# #', '# #'],
N: ['# #', '## #', '# # #', '# ##', '# #', '# #', '# #'],
O: [' ### ', '# #', '# #', '# #', '# #', '# #', ' ### '],
P: ['#### ', '# #', '# #', '#### ', '# ', '# ', '# '],
Q: [' ### ', '# #', '# #', '# #', '# #', '# # ', ' ## #'],
R: ['#### ', '# #', '# #', '#### ', '# # ', '# #', '# #'],
S: [' ####', '# ', '# ', ' ### ', ' #', ' #', '#### '],
T: ['#####', ' # ', ' # ', ' # ', ' # ', ' # ', ' # '],
U: ['# #', '# #', '# #', '# #', '# #', '# #', ' ### '],
V: ['# #', '# #', '# #', '# #', '# #', ' # # ', ' # '],
W: ['# #', '# #', '# #', '# #', '# # #', '## ##', '# #'],
X: ['# #', '# #', ' # # ', ' # ', ' # # ', '# #', '# #'],
Y: ['# #', '# #', ' # # ', ' # ', ' # ', ' # ', ' # '],
Z: ['#####', ' #', ' # ', ' # ', ' # ', '# ', '#####'],
a: [' ', ' ', ' ### ', ' #', ' ####', '# #', ' ####'],
b: ['# ', '# ', '# ## ', '## #', '# #', '# #', '#### '],
c: [' ', ' ', ' ### ', '# #', '# ', '# #', ' ### '],
d: [' #', ' #', ' ## #', '# ##', '# #', '# #', ' ####'],
e: [' ', ' ', ' ### ', '# #', '#####', '# ', ' ####'],
f: [' ##', ' # ', '####', ' # ', ' # ', ' # ', ' # '],
g: [' ', ' ', ' ####', '# #', '# #', '# #', ' ####', ' #', ' ### '],
h: ['# ', '# ', '# ## ', '## #', '# #', '# #', '# #'],
i: ['#', ' ', '#', '#', '#', '#', '#'],
j: [' #', ' ', ' #', ' #', ' #', ' #', ' #', '# #', ' ## '],
k: ['# ', '# ', '# #', '# # ', '## ', '# # ', '# #'],
l: ['# ', '# ', '# ', '# ', '# ', '# ', ' #'],
m: [' ', ' ', '## # ', '# # #', '# # #', '# #', '# #'],
n: [' ', ' ', '#### ', '# #', '# #', '# #', '# #'],
o: [' ', ' ', ' ### ', '# #', '# #', '# #', ' ### '],
p: [' ', ' ', '# ## ', '## #', '# #', '# #', '#### ', '# ', '# '],
q: [' ', ' ', ' ## #', '# ##', '# #', '# #', ' ####', ' #', ' #'],
r: [' ', ' ', '# ## ', '## #', '# ', '# ', '# '],
s: [' ', ' ', ' ####', '# ', ' ### ', ' #', '#### '],
t: [' # ', ' # ', '###', ' # ', ' # ', ' # ', ' #'],
u: [' ', ' ', '# #', '# #', '# #', '# #', ' ####'],
v: [' ', ' ', '# #', '# #', '# #', ' # # ', ' # '],
w: [' ', ' ', '# #', '# #', '# # #', '# # #', ' ####'],
x: [' ', ' ', '# #', ' # # ', ' # ', ' # # ', '# #'],
y: [' ', ' ', '# #', '# #', '# #', '# #', ' ####', ' #', '#### '],
z: [' ', ' ', '#####', ' # ', ' # ', ' # ', '#####'],
0: [' ### ', '# #', '# ##', '# # #', '## #', '# #', ' ### '],
1: [' # ', ' ## ', ' # ', ' # ', ' # ', ' # ', '#####'],
2: [' ### ', '# #', ' #', ' ## ', ' # ', '# ', '#####'],
3: [' ### ', '# #', ' #', ' ## ', ' #', '# #', ' ### '],
4: [' ##', ' # #', ' # #', '# #', '#####', ' #', ' #'],
5: ['#####', '# ', '#### ', ' #', ' #', '# #', ' ### '],
6: [' ## ', ' # ', '# ', '#### ', '# #', '# #', ' ### '],
7: ['#####', '# #', ' #', ' # ', ' # ', ' # ', ' # '],
8: [' ### ', '# #', '# #', ' ### ', '# #', '# #', ' ### '],
9: [' ### ', '# #', '# #', ' ####', ' #', ' # ', ' ## '],
'Ä': ['# #', ' ### ', '# #', '# #', '#####', '# #', '# #'],
'Ö': ['# #', ' ### ', '# #', '# #', '# #', '# #', ' ### '],
'Ü': ['# #', ' ', '# #', '# #', '# #', '# #', ' ### '],
'ä': [' # # ', ' ', ' ### ', ' #', ' ####', '# #', ' ####'],
'ö': [' # # ', ' ', ' ### ', '# #', '# #', '# #', ' ### '],
'ü': [' # # ', ' ', '# #', '# #', '# #', '# #', ' ####'],
'ß': [' ### ', '# #', '# # ', '# # ', '# # ', '# #', '# ## '],
'é': [' # ', ' # ', ' ### ', '# #', '#####', '# ', ' ####'],
'.': [' ', ' ', ' ', ' ', ' ', ' ', '#'],
',': [' ', ' ', ' ', ' ', ' ', ' ', '#', '#'],
':': [' ', ' ', '#', ' ', ' ', ' ', '#'],
';': [' ', ' ', '#', ' ', ' ', ' ', '#', '#'],
'!': ['#', '#', '#', '#', '#', ' ', '#'],
'?': [' ### ', '# #', ' #', ' # ', ' # ', ' ', ' # '],
'-': [' ', ' ', ' ', '####', ' ', ' ', ' '],
'–': [' ', ' ', ' ', '#####', ' ', ' ', ' '],
'_': [' ', ' ', ' ', ' ', ' ', ' ', ' ', '#####'],
'(': [' #', ' # ', '# ', '# ', '# ', ' # ', ' #'],
')': ['# ', ' # ', ' #', ' #', ' #', ' # ', '# '],
'[': ['##', '# ', '# ', '# ', '# ', '# ', '##'],
']': ['##', ' #', ' #', ' #', ' #', ' #', '##'],
'/': [' #', ' # ', ' # ', ' # ', ' # ', ' # ', '# '],
'\\': ['# ', ' # ', ' # ', ' # ', ' # ', ' # ', ' #'],
'+': [' ', ' # ', ' # ', '#####', ' # ', ' # ', ' '],
'=': [' ', ' ', '#####', ' ', '#####', ' ', ' '],
'*': [' ', ' ', '# # #', ' ### ', '# # #', ' ', ' '],
'%': ['# #', ' #', ' # ', ' # ', ' # ', '# ', '# #'],
'&': [' ## ', '# # ', ' ## ', ' ## #', '# # ', '# # ', ' ## #'],
'#': [' # # ', '#####', ' # # ', ' # # ', ' # # ', '#####', ' # # '],
'<': [' #', ' # ', ' # ', '# ', ' # ', ' # ', ' #'],
'>': ['# ', ' # ', ' # ', ' #', ' # ', ' # ', '# '],
"'": ['#', '#'],
'"': ['# #', '# #'],
'|': ['#', '#', '#', '#', '#', '#', '#', '#', '#'],
'·': [' ', ' ', ' ', '#', ' ', ' ', ' '],
'…': [' ', ' ', ' ', ' ', ' ', ' ', '# # #'],
'@': [' ### ', '# #', '# ###', '# # #', '# ###', '# ', ' ####'],
};
// ---------- Glyphen in Umrisse umwandeln (waagerechte Pixel-Läufe als Rechtecke) ----------
function umrisse(zeilen) {
const konturen = [];
zeilen.forEach((zeile, r) => {
const yOben = (7 - r) * P;
const yUnten = yOben - P;
let x = 0;
while (x < zeile.length) {
if (zeile[x] !== '#') { x++; continue; }
let ende = x;
while (ende < zeile.length && zeile[ende] === '#') ende++;
// im Uhrzeigersinn (TrueType-Außenkontur)
konturen.push([[x * P, yUnten], [x * P, yOben], [ende * P, yOben], [ende * P, yUnten]]);
x = ende;
}
});
return konturen;
}
const glyphen = [{ name: '.notdef', code: null, breite: 4, konturen: [[[0, 0], [0, 875], [500, 875], [500, 0]], [[125, 125], [375, 125], [375, 750], [125, 750]]] },
{ name: 'space', code: 32, breite: 3, konturen: [] }];
for (const [zeichen, zeilen] of Object.entries(G)) {
const breite = Math.max(...zeilen.map((z) => z.length));
glyphen.push({ code: zeichen.codePointAt(0), breite, konturen: umrisse(zeilen) });
}
const sortiert = [glyphen[0], glyphen[1], ...glyphen.slice(2).sort((a, b) => a.code - b.code)];
// ---------- Hilfen zum Schreiben ----------
class Puffer {
constructor() { this.teile = []; this.laenge = 0; }
u8(v) { const b = Buffer.alloc(1); b.writeUInt8(v); return this.roh(b); }
u16(v) { const b = Buffer.alloc(2); b.writeUInt16BE(v & 0xffff); return this.roh(b); }
i16(v) { const b = Buffer.alloc(2); b.writeInt16BE(v); return this.roh(b); }
u32(v) { const b = Buffer.alloc(4); b.writeUInt32BE(v >>> 0); return this.roh(b); }
i64(v) { const b = Buffer.alloc(8); b.writeBigInt64BE(BigInt(v)); return this.roh(b); }
roh(b) { this.teile.push(b); this.laenge += b.length; return this; }
fertig() { return Buffer.concat(this.teile); }
}
const auf4 = (b) => (b.length % 4 ? Buffer.concat([b, Buffer.alloc(4 - (b.length % 4))]) : b);
function pruefsumme(b) {
const p = auf4(b);
let s = 0;
for (let i = 0; i < p.length; i += 4) s = (s + p.readUInt32BE(i)) >>> 0;
return s;
}
// ---------- glyf + loca + hmtx ----------
const glyf = new Puffer();
const loca = [];
const hmtx = new Puffer();
let maxPunkte = 0, maxKonturen = 0;
let gxMin = 0, gyMin = 0, gxMax = 0, gyMax = 0;
let maxVorschub = 0, minLsb = 1e9, minRsb = 1e9, maxXAusdehnung = 0;
for (const g of sortiert) {
loca.push(glyf.laenge);
const vorschub = (g.breite + 1) * P;
maxVorschub = Math.max(maxVorschub, vorschub);
if (!g.konturen.length) { hmtx.u16(vorschub).i16(0); continue; }
const punkte = g.konturen.flat();
const xs = punkte.map((p) => p[0]), ys = punkte.map((p) => p[1]);
const xMin = Math.min(...xs), xMax = Math.max(...xs), yMin = Math.min(...ys), yMax = Math.max(...ys);
gxMin = Math.min(gxMin, xMin); gyMin = Math.min(gyMin, yMin); gxMax = Math.max(gxMax, xMax); gyMax = Math.max(gyMax, yMax);
minLsb = Math.min(minLsb, xMin); minRsb = Math.min(minRsb, vorschub - xMax); maxXAusdehnung = Math.max(maxXAusdehnung, xMax);
maxPunkte = Math.max(maxPunkte, punkte.length); maxKonturen = Math.max(maxKonturen, g.konturen.length);
const b = new Puffer();
b.i16(g.konturen.length).i16(xMin).i16(yMin).i16(xMax).i16(yMax);
let ende = -1;
for (const k of g.konturen) { ende += k.length; b.u16(ende); }
b.u16(0); // keine Hinting-Anweisungen
for (let i = 0; i < punkte.length; i++) b.u8(0x01); // alle Punkte auf der Kurve, Koordinaten als int16
let px = 0, py = 0;
for (const [x] of punkte) { b.i16(x - px); px = x; }
for (const [, y] of punkte) { b.i16(y - py); py = y; }
glyf.roh(auf4(b.fertig()));
hmtx.u16(vorschub).i16(xMin);
}
loca.push(glyf.laenge);
const anzahl = sortiert.length;
const locaB = new Puffer();
for (const o of loca) locaB.u32(o);
// ---------- cmap (Format 4, ein Segment je Zeichen) ----------
const abbildung = sortiert.map((g, i) => [g.code, i]).filter(([c]) => c !== null).sort((a, b) => a[0] - b[0]);
const segmente = abbildung.map(([c, i]) => ({ start: c, ende: c, delta: (i - c) & 0xffff }));
segmente.push({ start: 0xffff, ende: 0xffff, delta: 1 });
const segX2 = segmente.length * 2;
const suchbereich = 2 * 2 ** Math.floor(Math.log2(segmente.length));
const sub = new Puffer();
sub.u16(4).u16(16 + segmente.length * 8).u16(0).u16(segX2).u16(suchbereich)
.u16(Math.log2(suchbereich / 2)).u16(segX2 - suchbereich);
for (const s of segmente) sub.u16(s.ende);
sub.u16(0);
for (const s of segmente) sub.u16(s.start);
for (const s of segmente) sub.u16(s.delta);
for (const s of segmente) sub.u16(0);
const cmap = new Puffer().u16(0).u16(1).u16(3).u16(1).u32(12).roh(sub.fertig());
// ---------- head / hhea / maxp / OS/2 / name / post ----------
const zeit = Math.floor(Date.UTC(2026, 9, 3) / 1000) + 2082844800; // Sekunden seit 1904
const head = new Puffer().u32(0x00010000).u32(0x00010000).u32(0).u32(0x5f0f3cf5).u16(0x000b).u16(EM)
.i64(zeit).i64(zeit).i16(gxMin).i16(gyMin).i16(gxMax).i16(gyMax).u16(0).u16(8).i16(2).i16(1).i16(0);
const hhea = new Puffer().u32(0x00010000).i16(ASC).i16(-DESC).i16(0).u16(maxVorschub).i16(minLsb).i16(minRsb)
.i16(maxXAusdehnung).i16(1).i16(0).i16(0).i16(0).i16(0).i16(0).i16(0).i16(0).u16(anzahl);
const maxp = new Puffer().u32(0x00010000).u16(anzahl).u16(maxPunkte).u16(maxKonturen).u16(0).u16(0)
.u16(2).u16(0).u16(0).u16(0).u16(0).u16(0).u16(0).u16(0).u16(0);
const codes = abbildung.map(([c]) => c);
const os2 = new Puffer().u16(4).i16(5 * P).u16(400).u16(5).u16(0)
.i16(650).i16(600).i16(0).i16(75).i16(650).i16(600).i16(0).i16(350).i16(P).i16(3 * P)
.i16(0).roh(Buffer.alloc(10)).u32(3).u32(0).u32(0).u32(0).roh(Buffer.from('AERO'))
.u16(0x40).u16(Math.min(...codes)).u16(Math.min(0xffff, Math.max(...codes)))
.i16(ASC).i16(-DESC).i16(0).u16(ASC).u16(DESC).u32(1).u32(0).i16(5 * P).i16(7 * P).u16(0).u16(32).u16(1);
const namen = { 1: 'AeroPixel', 2: 'Regular', 3: 'AeroPixel Regular', 4: 'AeroPixel', 5: 'Version 1.0', 6: 'AeroPixel-Regular' };
const nameDaten = Object.entries(namen).map(([id, text]) => [Number(id), Buffer.from(text, 'utf16le').swap16()]);
const name = new Puffer().u16(0).u16(nameDaten.length).u16(6 + nameDaten.length * 12);
let versatz = 0;
for (const [id, b] of nameDaten) { name.u16(3).u16(1).u16(0x409).u16(id).u16(b.length).u16(versatz); versatz += b.length; }
for (const [, b] of nameDaten) name.roh(b);
const post = new Puffer().u32(0x00030000).u32(0).i16(-P).i16(P).u32(0).u32(0).u32(0).u32(0).u32(0);
// ---------- zusammensetzen ----------
const tabellen = [
['OS/2', os2], ['cmap', cmap], ['glyf', glyf], ['head', head], ['hhea', hhea],
['hmtx', hmtx], ['loca', locaB], ['maxp', maxp], ['name', name], ['post', post],
].map(([tag, p]) => [tag, p.fertig()]);
const n = tabellen.length;
const sb = 16 * 2 ** Math.floor(Math.log2(n));
const kopf = new Puffer().u32(0x00010000).u16(n).u16(sb).u16(Math.log2(sb / 16)).u16(n * 16 - sb);
let offset = 12 + n * 16;
const verzeichnis = new Puffer();
for (const [tag, daten] of tabellen) {
verzeichnis.roh(Buffer.from(tag, 'latin1')).u32(pruefsumme(daten)).u32(offset).u32(daten.length);
offset += auf4(daten).length;
}
let schrift = Buffer.concat([kopf.fertig(), verzeichnis.fertig(), ...tabellen.map(([, d]) => auf4(d))]);
// checkSumAdjustment im head-Eintrag setzen
const headOffset = 12 + n * 16 + tabellen.slice(0, 3).reduce((s, [, d]) => s + auf4(d).length, 0);
schrift.writeUInt32BE((0xb1b0afba - pruefsumme(schrift)) >>> 0, headOffset + 8);
const ziel = path.join(__dirname, '..', 'src', 'assets', 'aeropixel.ttf');
fs.writeFileSync(ziel, schrift);
console.log(`${ziel}: ${anzahl} Glyphen, ${schrift.length} Bytes`);