Original-Logo (euer radio) als Icon, Installer-Bilder und README-Logo verwenden
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
# EuerChat - euer.tv IRC & Livestream Client (Windows 64-bit)
|
# EuerChat - euer.tv IRC & Livestream Client (Windows 64-bit)
|
||||||
|
|
||||||
<p align="center"><img src="assets/logo-wide.png" alt="euer.tv chat" width="560"></p>
|
<p align="center"><img src="assets/logo-original.png" alt="euer radio" width="560"></p>
|
||||||
|
|
||||||
Ein moderner, performanter und schlanker Desktop-Client für Windows 64-bit, gebaut mit **Rust + Tauri v2** und einem ansprechenden **Dark-Mode Web-Frontend**.
|
Ein moderner, performanter und schlanker Desktop-Client für Windows 64-bit, gebaut mit **Rust + Tauri v2** und einem ansprechenden **Dark-Mode Web-Frontend**.
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
|
||||||
<defs>
|
|
||||||
<radialGradient id="iorb" cx="50%" cy="32%" r="82%">
|
|
||||||
<stop offset="0" stop-color="#ff5a3d"/><stop offset=".36" stop-color="#e50b0b"/>
|
|
||||||
<stop offset=".76" stop-color="#8c0000"/><stop offset="1" stop-color="#3d0000"/>
|
|
||||||
</radialGradient>
|
|
||||||
<linearGradient id="iring" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset=".45" stop-color="#8a8a8a"/><stop offset="1" stop-color="#e9e9e9"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="ichrome" x1="0" y1="0" x2="1" y2="0">
|
|
||||||
<stop offset="0" stop-color="#555"/><stop offset=".2" stop-color="#bdbdbd"/><stop offset=".4" stop-color="#fff"/>
|
|
||||||
<stop offset=".58" stop-color="#cfcfcf"/><stop offset=".82" stop-color="#777"/><stop offset="1" stop-color="#454545"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="igloss" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#fff" stop-opacity=".8"/><stop offset="1" stop-color="#fff" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="ibubble" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#c3ccd3"/>
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="iorbClip"><circle cx="256" cy="256" r="214"/></clipPath>
|
|
||||||
<clipPath id="iheadClip"><rect x="198" y="118" width="116" height="172" rx="58"/></clipPath>
|
|
||||||
<filter id="ishadow" x="-40%" y="-30%" width="180%" height="170%">
|
|
||||||
<feDropShadow dx="3" dy="9" stdDeviation="7" flood-color="#000" flood-opacity=".6"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="iblur" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur stdDeviation="6"/></filter>
|
|
||||||
</defs>
|
|
||||||
<circle cx="256" cy="256" r="252" fill="url(#iring)"/>
|
|
||||||
<circle cx="256" cy="256" r="237" fill="#0b0b0b"/>
|
|
||||||
<circle cx="256" cy="256" r="214" fill="url(#iorb)"/>
|
|
||||||
<g clip-path="url(#iorbClip)">
|
|
||||||
<ellipse cx="256" cy="118" rx="172" ry="86" fill="url(#igloss)" opacity=".75"/>
|
|
||||||
<ellipse cx="256" cy="478" rx="200" ry="96" fill="#ff3a1c" opacity=".5"/>
|
|
||||||
</g>
|
|
||||||
<g filter="url(#ishadow)" transform="rotate(-10 256 260)">
|
|
||||||
<rect x="198" y="118" width="116" height="172" rx="58" fill="url(#ichrome)"/>
|
|
||||||
<g clip-path="url(#iheadClip)"><line x1="196" x2="316" y1="144" y2="144" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="150.5" y2="150.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="161" y2="161" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="167.5" y2="167.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="178" y2="178" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="184.5" y2="184.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="195" y2="195" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="201.5" y2="201.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="212" y2="212" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="218.5" y2="218.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="229" y2="229" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="235.5" y2="235.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="246" y2="246" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="252.5" y2="252.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="263" y2="263" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="269.5" y2="269.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/></g>
|
|
||||||
<rect x="198" y="118" width="116" height="172" rx="58" fill="none" stroke="#2c2c2c" stroke-width="5"/>
|
|
||||||
<rect x="214" y="288" width="84" height="26" rx="10" fill="url(#ichrome)" stroke="#2c2c2c" stroke-width="4"/>
|
|
||||||
<path d="M236 314 H276 L268 372 H244 Z" fill="url(#ichrome)" stroke="#2c2c2c" stroke-width="4" stroke-linejoin="round"/>
|
|
||||||
<rect x="230" y="372" width="52" height="26" rx="6" fill="#2a2a2a" stroke="#777" stroke-width="3"/>
|
|
||||||
</g>
|
|
||||||
<circle cx="296" cy="150" r="16" fill="#fff" opacity=".9" filter="url(#iblur)"/>
|
|
||||||
<path d="M296 122 L301 145 L324 150 L301 155 L296 178 L291 155 L268 150 L291 145 Z" fill="#fff"/>
|
|
||||||
<g filter="url(#ishadow)">
|
|
||||||
<path d="M322 392 L294 446 L362 396 Z" fill="url(#ibubble)" stroke="#0b0b0b" stroke-width="10" stroke-linejoin="round"/>
|
|
||||||
<rect x="288" y="296" width="146" height="102" rx="32" fill="url(#ibubble)" stroke="#0b0b0b" stroke-width="10"/>
|
|
||||||
<path d="M322 392 L318 400 L352 394 Z" fill="url(#ibubble)"/>
|
|
||||||
<circle cx="326" cy="347" r="12" fill="#d00000"/><circle cx="361" cy="347" r="12" fill="#d00000"/><circle cx="396" cy="347" r="12" fill="#d00000"/>
|
|
||||||
</g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 264 KiB |
@@ -1,82 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1911" height="560" viewBox="0 0 1911 560">
|
|
||||||
<defs>
|
|
||||||
|
|
||||||
<linearGradient id="wsilver" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#fff"/><stop offset=".5" stop-color="#9c9c9c"/><stop offset="1" stop-color="#f1f1f1"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="wletters" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset=".55" stop-color="#e6e6e6"/><stop offset="1" stop-color="#bcbcbc"/>
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="wtshadow" x="-10%" y="-20%" width="120%" height="160%">
|
|
||||||
<feDropShadow dx="0" dy="6" stdDeviation="6" flood-color="#000" flood-opacity=".5"/>
|
|
||||||
</filter>
|
|
||||||
<linearGradient id="wpillStroke" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ff5a45"/><stop offset=".4" stop-color="#e00000"/><stop offset="1" stop-color="#7d0000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="wpillFill" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#d9d9d9"/>
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="wpillShadow" x="-5%" y="-20%" width="110%" height="150%">
|
|
||||||
<feDropShadow dx="0" dy="10" stdDeviation="9" flood-color="#000" flood-opacity=".45"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<rect x="46" y="86" width="1819" height="388" rx="194" fill="url(#wpillFill)" stroke="url(#wpillStroke)" stroke-width="56" filter="url(#wpillShadow)"/>
|
|
||||||
<rect x="74" y="114" width="1763" height="332" rx="166" fill="none" stroke="#6d0000" stroke-opacity=".55" stroke-width="6"/>
|
|
||||||
|
|
||||||
<text x="468" y="335" font-family="'Segoe UI Black','Arial Black',Verdana,sans-serif" font-size="170" text-anchor="middle" textLength="596" lengthAdjust="spacingAndGlyphs" stroke-linejoin="round" fill="#1a1a1a" stroke="#1a1a1a" stroke-width="34" filter="url(#wtshadow)">euer.tv</text>
|
|
||||||
<text x="468" y="335" font-family="'Segoe UI Black','Arial Black',Verdana,sans-serif" font-size="170" text-anchor="middle" textLength="596" lengthAdjust="spacingAndGlyphs" stroke-linejoin="round" fill="none" stroke="url(#wsilver)" stroke-width="22.1">euer.tv</text>
|
|
||||||
<text x="468" y="335" font-family="'Segoe UI Black','Arial Black',Verdana,sans-serif" font-size="170" text-anchor="middle" textLength="596" lengthAdjust="spacingAndGlyphs" stroke-linejoin="round" fill="url(#wletters)" stroke="#5e5e5e" stroke-width="2.04">euer.tv</text>
|
|
||||||
|
|
||||||
<text x="1563.5" y="335" font-family="'Segoe UI Black','Arial Black',Verdana,sans-serif" font-size="170" text-anchor="middle" textLength="355" lengthAdjust="spacingAndGlyphs" stroke-linejoin="round" fill="#1a1a1a" stroke="#1a1a1a" stroke-width="34" filter="url(#wtshadow)">chat</text>
|
|
||||||
<text x="1563.5" y="335" font-family="'Segoe UI Black','Arial Black',Verdana,sans-serif" font-size="170" text-anchor="middle" textLength="355" lengthAdjust="spacingAndGlyphs" stroke-linejoin="round" fill="none" stroke="url(#wsilver)" stroke-width="22.1">chat</text>
|
|
||||||
<text x="1563.5" y="335" font-family="'Segoe UI Black','Arial Black',Verdana,sans-serif" font-size="170" text-anchor="middle" textLength="355" lengthAdjust="spacingAndGlyphs" stroke-linejoin="round" fill="url(#wletters)" stroke="#5e5e5e" stroke-width="2.04">chat</text>
|
|
||||||
<svg x="806" y="10" width="540" height="540" viewBox="0 0 512 512">
|
|
||||||
<defs>
|
|
||||||
<radialGradient id="worb" cx="50%" cy="32%" r="82%">
|
|
||||||
<stop offset="0" stop-color="#ff5a3d"/><stop offset=".36" stop-color="#e50b0b"/>
|
|
||||||
<stop offset=".76" stop-color="#8c0000"/><stop offset="1" stop-color="#3d0000"/>
|
|
||||||
</radialGradient>
|
|
||||||
<linearGradient id="wring" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset=".45" stop-color="#8a8a8a"/><stop offset="1" stop-color="#e9e9e9"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="wchrome" x1="0" y1="0" x2="1" y2="0">
|
|
||||||
<stop offset="0" stop-color="#555"/><stop offset=".2" stop-color="#bdbdbd"/><stop offset=".4" stop-color="#fff"/>
|
|
||||||
<stop offset=".58" stop-color="#cfcfcf"/><stop offset=".82" stop-color="#777"/><stop offset="1" stop-color="#454545"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="wgloss" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#fff" stop-opacity=".8"/><stop offset="1" stop-color="#fff" stop-opacity="0"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="wbubble" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#c3ccd3"/>
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="worbClip"><circle cx="256" cy="256" r="214"/></clipPath>
|
|
||||||
<clipPath id="wheadClip"><rect x="198" y="118" width="116" height="172" rx="58"/></clipPath>
|
|
||||||
<filter id="wshadow" x="-40%" y="-30%" width="180%" height="170%">
|
|
||||||
<feDropShadow dx="3" dy="9" stdDeviation="7" flood-color="#000" flood-opacity=".6"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="wblur" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur stdDeviation="6"/></filter>
|
|
||||||
</defs>
|
|
||||||
<circle cx="256" cy="256" r="252" fill="url(#wring)"/>
|
|
||||||
<circle cx="256" cy="256" r="237" fill="#0b0b0b"/>
|
|
||||||
<circle cx="256" cy="256" r="214" fill="url(#worb)"/>
|
|
||||||
<g clip-path="url(#worbClip)">
|
|
||||||
<ellipse cx="256" cy="118" rx="172" ry="86" fill="url(#wgloss)" opacity=".75"/>
|
|
||||||
<ellipse cx="256" cy="478" rx="200" ry="96" fill="#ff3a1c" opacity=".5"/>
|
|
||||||
</g>
|
|
||||||
<g filter="url(#wshadow)" transform="rotate(-10 256 260)">
|
|
||||||
<rect x="198" y="118" width="116" height="172" rx="58" fill="url(#wchrome)"/>
|
|
||||||
<g clip-path="url(#wheadClip)"><line x1="196" x2="316" y1="144" y2="144" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="150.5" y2="150.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="161" y2="161" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="167.5" y2="167.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="178" y2="178" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="184.5" y2="184.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="195" y2="195" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="201.5" y2="201.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="212" y2="212" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="218.5" y2="218.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="229" y2="229" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="235.5" y2="235.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="246" y2="246" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="252.5" y2="252.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/><line x1="196" x2="316" y1="263" y2="263" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/><line x1="204" x2="308" y1="269.5" y2="269.5" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/></g>
|
|
||||||
<rect x="198" y="118" width="116" height="172" rx="58" fill="none" stroke="#2c2c2c" stroke-width="5"/>
|
|
||||||
<rect x="214" y="288" width="84" height="26" rx="10" fill="url(#wchrome)" stroke="#2c2c2c" stroke-width="4"/>
|
|
||||||
<path d="M236 314 H276 L268 372 H244 Z" fill="url(#wchrome)" stroke="#2c2c2c" stroke-width="4" stroke-linejoin="round"/>
|
|
||||||
<rect x="230" y="372" width="52" height="26" rx="6" fill="#2a2a2a" stroke="#777" stroke-width="3"/>
|
|
||||||
</g>
|
|
||||||
<circle cx="296" cy="150" r="16" fill="#fff" opacity=".9" filter="url(#wblur)"/>
|
|
||||||
<path d="M296 122 L301 145 L324 150 L301 155 L296 178 L291 155 L268 150 L291 145 Z" fill="#fff"/>
|
|
||||||
<g filter="url(#wshadow)">
|
|
||||||
<path d="M322 392 L294 446 L362 396 Z" fill="url(#wbubble)" stroke="#0b0b0b" stroke-width="10" stroke-linejoin="round"/>
|
|
||||||
<rect x="288" y="296" width="146" height="102" rx="32" fill="url(#wbubble)" stroke="#0b0b0b" stroke-width="10"/>
|
|
||||||
<path d="M322 392 L318 400 L352 394 Z" fill="url(#wbubble)"/>
|
|
||||||
<circle cx="326" cy="347" r="12" fill="#d00000"/><circle cx="361" cy="347" r="12" fill="#d00000"/><circle cx="396" cy="347" r="12" fill="#d00000"/>
|
|
||||||
</g></svg>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 209 KiB |
@@ -1,5 +1,6 @@
|
|||||||
// Erzeugt das EuerChat-Logo (Icon, Wortmarke, Installer-Bilder) aus SVG und rendert es mit Edge (Headless).
|
// Erzeugt alle Icons und Installer-Bilder aus dem Original-Logo assets/logo-original.png (unveraendert verwendet).
|
||||||
// Aufruf: node tools/make-branding.mjs
|
// Quadratische Icons zeigen den runden Mikrofon-Ball aus dem Logo, breite Flaechen das komplette Logo.
|
||||||
|
// Aufruf: node tools/make-branding.mjs (rendert mit Microsoft Edge im Headless-Modus)
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
@@ -14,157 +15,139 @@ const EDGE = [
|
|||||||
].find(fs.existsSync);
|
].find(fs.existsSync);
|
||||||
if (!EDGE) throw new Error('Microsoft Edge nicht gefunden');
|
if (!EDGE) throw new Error('Microsoft Edge nicht gefunden');
|
||||||
|
|
||||||
const FONT = "'Segoe UI Black','Arial Black',Verdana,sans-serif";
|
const sourcePath = path.join(root, 'assets', 'logo-original.png');
|
||||||
|
const sourcePng = fs.readFileSync(sourcePath);
|
||||||
|
const dataUrl = 'data:image/png;base64,' + sourcePng.toString('base64');
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// SVG-Bausteine
|
// PNG lesen (nur 8 Bit RGB/RGBA ohne Interlacing, wie vom Logo und von Edge geliefert)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Rotes Orb-Symbol mit Chrom-Mikrofon und Chat-Blase (512x512). `p` macht die IDs eindeutig. */
|
function decodePng(buf) {
|
||||||
function iconInner(p) {
|
let pos = 8;
|
||||||
const slots = [];
|
let width = 0, height = 0, colorType = 0;
|
||||||
for (let y = 144; y <= 268; y += 17) {
|
const idat = [];
|
||||||
slots.push(`<line x1="196" x2="316" y1="${y}" y2="${y}" stroke="#1c1c1c" stroke-width="9" stroke-linecap="round"/>`);
|
while (pos < buf.length) {
|
||||||
slots.push(`<line x1="204" x2="308" y1="${y + 6.5}" y2="${y + 6.5}" stroke="#fff" stroke-opacity=".45" stroke-width="2" stroke-linecap="round"/>`);
|
const len = buf.readUInt32BE(pos);
|
||||||
|
const type = buf.toString('ascii', pos + 4, pos + 8);
|
||||||
|
const data = buf.subarray(pos + 8, pos + 8 + len);
|
||||||
|
if (type === 'IHDR') {
|
||||||
|
width = data.readUInt32BE(0); height = data.readUInt32BE(4); colorType = data[9];
|
||||||
|
if (data[8] !== 8 || data[12] !== 0 || (colorType !== 2 && colorType !== 6)) throw new Error('PNG-Format nicht unterstuetzt');
|
||||||
}
|
}
|
||||||
return `
|
if (type === 'IDAT') idat.push(data);
|
||||||
<defs>
|
pos += 12 + len;
|
||||||
<radialGradient id="${p}orb" cx="50%" cy="32%" r="82%">
|
}
|
||||||
<stop offset="0" stop-color="#ff5a3d"/><stop offset=".36" stop-color="#e50b0b"/>
|
const bpp = colorType === 6 ? 4 : 3;
|
||||||
<stop offset=".76" stop-color="#8c0000"/><stop offset="1" stop-color="#3d0000"/>
|
const raw = zlib.inflateSync(Buffer.concat(idat));
|
||||||
</radialGradient>
|
const stride = width * bpp;
|
||||||
<linearGradient id="${p}ring" x1="0" y1="0" x2="0" y2="1">
|
const px = Buffer.alloc(height * stride);
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset=".45" stop-color="#8a8a8a"/><stop offset="1" stop-color="#e9e9e9"/>
|
for (let y = 0; y < height; y++) {
|
||||||
</linearGradient>
|
const filter = raw[y * (stride + 1)];
|
||||||
<linearGradient id="${p}chrome" x1="0" y1="0" x2="1" y2="0">
|
for (let x = 0; x < stride; x++) {
|
||||||
<stop offset="0" stop-color="#555"/><stop offset=".2" stop-color="#bdbdbd"/><stop offset=".4" stop-color="#fff"/>
|
const v = raw[y * (stride + 1) + 1 + x];
|
||||||
<stop offset=".58" stop-color="#cfcfcf"/><stop offset=".82" stop-color="#777"/><stop offset="1" stop-color="#454545"/>
|
const a = x >= bpp ? px[y * stride + x - bpp] : 0;
|
||||||
</linearGradient>
|
const b = y > 0 ? px[(y - 1) * stride + x] : 0;
|
||||||
<linearGradient id="${p}gloss" x1="0" y1="0" x2="0" y2="1">
|
const c = x >= bpp && y > 0 ? px[(y - 1) * stride + x - bpp] : 0;
|
||||||
<stop offset="0" stop-color="#fff" stop-opacity=".8"/><stop offset="1" stop-color="#fff" stop-opacity="0"/>
|
let out;
|
||||||
</linearGradient>
|
if (filter === 0) out = v;
|
||||||
<linearGradient id="${p}bubble" x1="0" y1="0" x2="0" y2="1">
|
else if (filter === 1) out = v + a;
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#c3ccd3"/>
|
else if (filter === 2) out = v + b;
|
||||||
</linearGradient>
|
else if (filter === 3) out = v + ((a + b) >> 1);
|
||||||
<clipPath id="${p}orbClip"><circle cx="256" cy="256" r="214"/></clipPath>
|
else {
|
||||||
<clipPath id="${p}headClip"><rect x="198" y="118" width="116" height="172" rx="58"/></clipPath>
|
const p = a + b - c;
|
||||||
<filter id="${p}shadow" x="-40%" y="-30%" width="180%" height="170%">
|
const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
|
||||||
<feDropShadow dx="3" dy="9" stdDeviation="7" flood-color="#000" flood-opacity=".6"/>
|
out = v + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
|
||||||
</filter>
|
}
|
||||||
<filter id="${p}blur" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur stdDeviation="6"/></filter>
|
px[y * stride + x] = out & 255;
|
||||||
</defs>
|
}
|
||||||
<circle cx="256" cy="256" r="252" fill="url(#${p}ring)"/>
|
}
|
||||||
<circle cx="256" cy="256" r="237" fill="#0b0b0b"/>
|
return { width, height, bpp, px };
|
||||||
<circle cx="256" cy="256" r="214" fill="url(#${p}orb)"/>
|
|
||||||
<g clip-path="url(#${p}orbClip)">
|
|
||||||
<ellipse cx="256" cy="118" rx="172" ry="86" fill="url(#${p}gloss)" opacity=".75"/>
|
|
||||||
<ellipse cx="256" cy="478" rx="200" ry="96" fill="#ff3a1c" opacity=".5"/>
|
|
||||||
</g>
|
|
||||||
<g filter="url(#${p}shadow)" transform="rotate(-10 256 260)">
|
|
||||||
<rect x="198" y="118" width="116" height="172" rx="58" fill="url(#${p}chrome)"/>
|
|
||||||
<g clip-path="url(#${p}headClip)">${slots.join('')}</g>
|
|
||||||
<rect x="198" y="118" width="116" height="172" rx="58" fill="none" stroke="#2c2c2c" stroke-width="5"/>
|
|
||||||
<rect x="214" y="288" width="84" height="26" rx="10" fill="url(#${p}chrome)" stroke="#2c2c2c" stroke-width="4"/>
|
|
||||||
<path d="M236 314 H276 L268 372 H244 Z" fill="url(#${p}chrome)" stroke="#2c2c2c" stroke-width="4" stroke-linejoin="round"/>
|
|
||||||
<rect x="230" y="372" width="52" height="26" rx="6" fill="#2a2a2a" stroke="#777" stroke-width="3"/>
|
|
||||||
</g>
|
|
||||||
<circle cx="296" cy="150" r="16" fill="#fff" opacity=".9" filter="url(#${p}blur)"/>
|
|
||||||
<path d="M296 122 L301 145 L324 150 L301 155 L296 178 L291 155 L268 150 L291 145 Z" fill="#fff"/>
|
|
||||||
<g filter="url(#${p}shadow)">
|
|
||||||
<path d="M322 392 L294 446 L362 396 Z" fill="url(#${p}bubble)" stroke="#0b0b0b" stroke-width="10" stroke-linejoin="round"/>
|
|
||||||
<rect x="288" y="296" width="146" height="102" rx="32" fill="url(#${p}bubble)" stroke="#0b0b0b" stroke-width="10"/>
|
|
||||||
<path d="M322 392 L318 400 L352 394 Z" fill="url(#${p}bubble)"/>
|
|
||||||
<circle cx="326" cy="347" r="12" fill="#d00000"/><circle cx="361" cy="347" r="12" fill="#d00000"/><circle cx="396" cy="347" r="12" fill="#d00000"/>
|
|
||||||
</g>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function iconSvg(size) {
|
/** Findet den dunklen Ring um den roten Ball: senkrecht durch die Mitte (Durchmesser) und nahe dem oberen Rand (Mittelpunkt). */
|
||||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 512 512">${iconInner('i')}</svg>`;
|
function findOrb(logo) {
|
||||||
}
|
const { width, height, bpp, px } = logo;
|
||||||
|
const isDark = (x, y) => {
|
||||||
/** Chrom-Schriftzug: dunkle Kontur, silberner Rand, helle Fuellung. */
|
const i = (y * width + x) * bpp;
|
||||||
function chromeText(p, x, y, size, text, width) {
|
return (bpp === 4 ? px[i + 3] : 255) > 200 && Math.max(px[i], px[i + 1], px[i + 2]) < 70;
|
||||||
const common = `x="${x}" y="${y}" font-family="${FONT}" font-size="${size}" text-anchor="middle" textLength="${width}" lengthAdjust="spacingAndGlyphs" stroke-linejoin="round"`;
|
|
||||||
return `
|
|
||||||
<text ${common} fill="#1a1a1a" stroke="#1a1a1a" stroke-width="${size * 0.2}" filter="url(#${p}tshadow)">${text}</text>
|
|
||||||
<text ${common} fill="none" stroke="url(#${p}silver)" stroke-width="${size * 0.13}">${text}</text>
|
|
||||||
<text ${common} fill="url(#${p}letters)" stroke="#5e5e5e" stroke-width="${size * 0.012}">${text}</text>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function textDefs(p) {
|
|
||||||
return `
|
|
||||||
<linearGradient id="${p}silver" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#fff"/><stop offset=".5" stop-color="#9c9c9c"/><stop offset="1" stop-color="#f1f1f1"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="${p}letters" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset=".55" stop-color="#e6e6e6"/><stop offset="1" stop-color="#bcbcbc"/>
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="${p}tshadow" x="-10%" y="-20%" width="120%" height="160%">
|
|
||||||
<feDropShadow dx="0" dy="6" stdDeviation="6" flood-color="#000" flood-opacity=".5"/>
|
|
||||||
</filter>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Breites Logo im Stil des euer.radio-Logos: [euer.tv] (Orb) [chat] in einer roten Pille. */
|
|
||||||
function wideSvg(measure) {
|
|
||||||
const size = 170;
|
|
||||||
const wL = Math.round(measure.left);
|
|
||||||
const wR = Math.round(measure.right);
|
|
||||||
const orb = 540;
|
|
||||||
const gap = 60;
|
|
||||||
const padEnd = 150;
|
|
||||||
const width = padEnd + wL + gap + orb + gap + wR + padEnd;
|
|
||||||
const height = 560;
|
|
||||||
const cx = padEnd + wL + gap + orb / 2;
|
|
||||||
const baseline = 335;
|
|
||||||
return {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
||||||
<defs>
|
|
||||||
${textDefs('w')}
|
|
||||||
<linearGradient id="wpillStroke" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ff5a45"/><stop offset=".4" stop-color="#e00000"/><stop offset="1" stop-color="#7d0000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="wpillFill" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#d9d9d9"/>
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="wpillShadow" x="-5%" y="-20%" width="110%" height="150%">
|
|
||||||
<feDropShadow dx="0" dy="10" stdDeviation="9" flood-color="#000" flood-opacity=".45"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<rect x="46" y="86" width="${width - 92}" height="388" rx="194" fill="url(#wpillFill)" stroke="url(#wpillStroke)" stroke-width="56" filter="url(#wpillShadow)"/>
|
|
||||||
<rect x="74" y="114" width="${width - 148}" height="332" rx="166" fill="none" stroke="#6d0000" stroke-opacity=".55" stroke-width="6"/>
|
|
||||||
${chromeText('w', padEnd + wL / 2 + 20, baseline, size, 'euer.tv', wL)}
|
|
||||||
${chromeText('w', width - padEnd - wR / 2 - 20, baseline, size, 'chat', wR)}
|
|
||||||
<svg x="${cx - orb / 2}" y="${(height - orb) / 2}" width="${orb}" height="${orb}" viewBox="0 0 512 512">${iconInner('w')}</svg>
|
|
||||||
</svg>`,
|
|
||||||
};
|
};
|
||||||
|
const cxEst = Math.round(width / 2);
|
||||||
|
|
||||||
|
let top = -1, bottom = -1;
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
if (isDark(cxEst, y)) { if (top < 0) top = y; bottom = y; }
|
||||||
|
}
|
||||||
|
const r = (bottom - top) / 2;
|
||||||
|
const cy = (top + bottom) / 2;
|
||||||
|
|
||||||
|
// Die Messzeile nahe dem oberen Rand schneidet nur den Ring (links und rechts), nicht den Text.
|
||||||
|
let left = -1, right = -1;
|
||||||
|
for (let x = cxEst - 330; x <= cxEst + 330; x++) {
|
||||||
|
if (isDark(x, top + 40)) { if (left < 0) left = x; right = x; }
|
||||||
|
}
|
||||||
|
const cx = (left + right) / 2;
|
||||||
|
if (top < 0 || left < 0 || r < 200 || r > 300 || Math.abs(cx - cxEst) > 30) {
|
||||||
|
throw new Error(`Ball im Logo nicht erkannt (top=${top}, bottom=${bottom}, left=${left}, right=${right})`);
|
||||||
|
}
|
||||||
|
return { cx, cy, r };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hochformat fuer die Willkommensseite des Installers (164x314). */
|
// ---------------------------------------------------------------------------
|
||||||
function sidebarSvg() {
|
// HTML-Bausteine (werden von Edge gerendert)
|
||||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="164" height="314" viewBox="0 0 164 314">
|
// ---------------------------------------------------------------------------
|
||||||
<defs>
|
|
||||||
${textDefs('s')}
|
const logo = decodePng(sourcePng);
|
||||||
<linearGradient id="sbg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#2b0606"/><stop offset="1" stop-color="#050000"/></linearGradient>
|
const orb = findOrb(logo);
|
||||||
<radialGradient id="sglow" cx="50%" cy="34%" r="60%"><stop offset="0" stop-color="#ff2a10" stop-opacity=".55"/><stop offset="1" stop-color="#ff2a10" stop-opacity="0"/></radialGradient>
|
|
||||||
</defs>
|
// Im Original liegen Teile der Buchstaben ("r" von euer und "ra" von radio) ueber dem Ball.
|
||||||
<rect width="164" height="314" fill="url(#sbg)"/>
|
// Fuer die runden Icons werden sie im Textband senkrecht zwischen den sauberen Pixeln darueber und darunter aufgefuellt;
|
||||||
<rect width="164" height="314" fill="url(#sglow)"/>
|
// das Mikrofon bleibt unangetastet. Das breite Logo wird nie retuschiert.
|
||||||
<svg x="17" y="28" width="130" height="130" viewBox="0 0 512 512">${iconInner('s')}</svg>
|
const RETOUCH_JS = `(async () => {
|
||||||
${chromeText('s', 82, 214, 31, 'euer.tv', 126)}
|
const img = new Image(); img.src = ${JSON.stringify(dataUrl)}; await img.decode();
|
||||||
${chromeText('s', 82, 262, 40, 'chat', 92)}
|
const c = document.createElement('canvas'); c.width = img.width; c.height = img.height;
|
||||||
</svg>`;
|
const ctx = c.getContext('2d', { willReadFrequently: true }); ctx.drawImage(img, 0, 0);
|
||||||
|
const d = ctx.getImageData(0, 0, c.width, c.height); const p = d.data; const W = c.width;
|
||||||
|
const O = ${JSON.stringify(orb)}; const s = O.r / 271;
|
||||||
|
const YT = Math.round(O.cy - 68 * s), Y0 = YT + 6, YB = Math.round(O.cy + 98 * s), Y1 = YB - 6;
|
||||||
|
const micL = O.cx - 126 * s, micR = O.cx + 82 * s, clearX = O.cx + 114 * s;
|
||||||
|
const at = (x, y) => { const i = (y * W + x) * 4; return [p[i], p[i + 1], p[i + 2], p[i + 3]]; };
|
||||||
|
for (let x = Math.floor(O.cx - O.r) - 5; x <= Math.ceil(O.cx + O.r) + 5; x++) {
|
||||||
|
if (x >= micL && x < micR) continue;
|
||||||
|
const a = at(x >= micR && x < clearX ? Math.round(clearX) : x, YT), b = at(x, YB);
|
||||||
|
// Neben dem Mikrofonkopf beginnt der Buchstabe erst weiter unten; den Kopfrand nicht anschneiden.
|
||||||
|
const yFrom = x >= micR && x < clearX ? Math.round(O.cy - 38 * s) : Y0;
|
||||||
|
for (let y = yFrom; y <= Y1; y++) {
|
||||||
|
const dx = x - O.cx, dy = y - O.cy;
|
||||||
|
if (dx * dx + dy * dy > (O.r + 5) * (O.r + 5)) continue;
|
||||||
|
const t = (y - YT) / (YB - YT), i = (y * W + x) * 4;
|
||||||
|
for (let k = 0; k < 4; k++) p[i + k] = Math.round(a[k] + (b[k] - a[k]) * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.putImageData(d, 0, 0);
|
||||||
|
return c.toDataURL('image/png');
|
||||||
|
})()`;
|
||||||
|
|
||||||
|
// Quelle fuer die runden Icons; wird nach der Retusche ersetzt.
|
||||||
|
let orbUrl = dataUrl;
|
||||||
|
|
||||||
|
/** Runder Ausschnitt (Ball mit Mikrofon) in `size` Pixeln. */
|
||||||
|
function orbHtml(size, extra = '') {
|
||||||
|
const r = orb.r + 3;
|
||||||
|
const k = size / (2 * r);
|
||||||
|
return `<div style="position:relative;width:${size}px;height:${size}px;border-radius:50%;overflow:hidden;${extra}">
|
||||||
|
<img src="${orbUrl}" style="position:absolute;max-width:none;width:${logo.width * k}px;height:${logo.height * k}px;left:${-(orb.cx - r) * k}px;top:${-(orb.cy - r) * k}px"></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function headerSvg(wide) {
|
const wideHtml = (w, h, bg = 'transparent') => `<div style="width:${w}px;height:${h}px;background:${bg};display:flex;align-items:center;justify-content:center"><img src="${dataUrl}" style="width:100%;height:100%;object-fit:contain"></div>`;
|
||||||
const w = 150, h = 57;
|
|
||||||
const scale = Math.min(w / wide.width, h / wide.height);
|
const sidebarHtml = () => `<div style="position:relative;width:164px;height:314px;overflow:hidden;background:linear-gradient(#2b0606,#050000)">
|
||||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
<div style="position:absolute;inset:0;background:radial-gradient(circle at 50% 30%,rgba(255,40,16,.5),rgba(255,40,16,0) 62%)"></div>
|
||||||
<rect width="${w}" height="${h}" fill="#fff"/>
|
${orbHtml(116, 'position:absolute;left:24px;top:26px;box-shadow:0 6px 18px rgba(0,0,0,.6)')}
|
||||||
<svg x="${(w - wide.width * scale) / 2}" y="${(h - wide.height * scale) / 2}" width="${wide.width * scale}" height="${wide.height * scale}" viewBox="0 0 ${wide.width} ${wide.height}">${wide.svg.replace(/^<svg[^>]*>/, '').replace(/<\/svg>\s*$/, '')}</svg>
|
<img src="${dataUrl}" style="position:absolute;left:6px;top:166px;width:152px;height:auto">
|
||||||
</svg>`;
|
<div style="position:absolute;left:0;right:0;top:232px;text-align:center;font:900 25px 'Segoe UI Black','Arial Black',sans-serif;color:#fff;letter-spacing:.5px;text-shadow:0 0 10px rgba(255,50,30,.9),0 2px 3px #000">EuerChat</div>
|
||||||
}
|
<div style="position:absolute;left:0;right:0;top:266px;text-align:center;font:600 11px 'Segoe UI',sans-serif;color:#d8b8b8;letter-spacing:1.5px">IRC & LIVESTREAM</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Rendering mit Edge (Chrome DevTools Protocol)
|
// Rendering mit Edge (Chrome DevTools Protocol)
|
||||||
@@ -198,23 +181,21 @@ async function startEdge() {
|
|||||||
await send('Page.enable');
|
await send('Page.enable');
|
||||||
await send('Emulation.setDefaultBackgroundColorOverride', { color: { r: 0, g: 0, b: 0, a: 0 } });
|
await send('Emulation.setDefaultBackgroundColorOverride', { color: { r: 0, g: 0, b: 0, a: 0 } });
|
||||||
return {
|
return {
|
||||||
send,
|
async render(markup, width, height) {
|
||||||
async html(markup, width, height) {
|
|
||||||
await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
|
await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
|
||||||
const doc = `<!doctype html><html><body style="margin:0;background:transparent;overflow:hidden">${markup}</body></html>`;
|
const doc = `<!doctype html><html><body style="margin:0;background:transparent;overflow:hidden">${markup}</body></html>`;
|
||||||
await send('Page.navigate', { url: 'data:text/html;base64,' + Buffer.from(doc).toString('base64') });
|
await send('Page.navigate', { url: 'data:text/html;base64,' + Buffer.from(doc).toString('base64') });
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
await new Promise((r) => setTimeout(r, 400));
|
||||||
await send('Runtime.evaluate', { expression: 'document.fonts.ready.then(()=>1)', awaitPromise: true });
|
await send('Runtime.evaluate', { expression: 'Promise.all([document.fonts.ready,...[...document.images].map(i=>i.decode())]).then(()=>1)', awaitPromise: true });
|
||||||
},
|
|
||||||
async png() {
|
|
||||||
const r = await send('Page.captureScreenshot', { format: 'png', fromSurface: true });
|
const r = await send('Page.captureScreenshot', { format: 'png', fromSurface: true });
|
||||||
return Buffer.from(r.data, 'base64');
|
return Buffer.from(r.data, 'base64');
|
||||||
},
|
},
|
||||||
async eval(expression) {
|
|
||||||
const r = await send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true });
|
|
||||||
return r.result?.value;
|
|
||||||
},
|
|
||||||
close() { ws.close(); proc.kill(); },
|
close() { ws.close(); proc.kill(); },
|
||||||
|
async evaluate(expression) {
|
||||||
|
const r = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
|
||||||
|
if (r.exceptionDetails) throw new Error(JSON.stringify(r.exceptionDetails));
|
||||||
|
return r.result.value;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,45 +224,6 @@ function makeIco(frames) {
|
|||||||
return Buffer.concat([header, ...entries, ...sorted.map((f) => f.png)]);
|
return Buffer.concat([header, ...entries, ...sorted.map((f) => f.png)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodePng(buf) {
|
|
||||||
let pos = 8;
|
|
||||||
let width = 0, height = 0, colorType = 0;
|
|
||||||
const idat = [];
|
|
||||||
while (pos < buf.length) {
|
|
||||||
const len = buf.readUInt32BE(pos);
|
|
||||||
const type = buf.toString('ascii', pos + 4, pos + 8);
|
|
||||||
const data = buf.subarray(pos + 8, pos + 8 + len);
|
|
||||||
if (type === 'IHDR') { width = data.readUInt32BE(0); height = data.readUInt32BE(4); colorType = data[9]; }
|
|
||||||
if (type === 'IDAT') idat.push(data);
|
|
||||||
pos += 12 + len;
|
|
||||||
}
|
|
||||||
const bpp = colorType === 6 ? 4 : 3;
|
|
||||||
const raw = zlib.inflateSync(Buffer.concat(idat));
|
|
||||||
const stride = width * bpp;
|
|
||||||
const px = Buffer.alloc(height * stride);
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
const filter = raw[y * (stride + 1)];
|
|
||||||
for (let x = 0; x < stride; x++) {
|
|
||||||
const v = raw[y * (stride + 1) + 1 + x];
|
|
||||||
const a = x >= bpp ? px[y * stride + x - bpp] : 0;
|
|
||||||
const b = y > 0 ? px[(y - 1) * stride + x] : 0;
|
|
||||||
const c = x >= bpp && y > 0 ? px[(y - 1) * stride + x - bpp] : 0;
|
|
||||||
let out;
|
|
||||||
if (filter === 0) out = v;
|
|
||||||
else if (filter === 1) out = v + a;
|
|
||||||
else if (filter === 2) out = v + b;
|
|
||||||
else if (filter === 3) out = v + ((a + b) >> 1);
|
|
||||||
else {
|
|
||||||
const p = a + b - c;
|
|
||||||
const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
|
|
||||||
out = v + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
|
|
||||||
}
|
|
||||||
px[y * stride + x] = out & 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { width, height, bpp, px };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 24-Bit-BMP (NSIS kann nur BMP), Transparenz wird auf `bg` gemischt. */
|
/** 24-Bit-BMP (NSIS kann nur BMP), Transparenz wird auf `bg` gemischt. */
|
||||||
function pngToBmp(png, bg = [255, 255, 255]) {
|
function pngToBmp(png, bg = [255, 255, 255]) {
|
||||||
const { width, height, bpp, px } = decodePng(png);
|
const { width, height, bpp, px } = decodePng(png);
|
||||||
@@ -309,42 +251,30 @@ function pngToBmp(png, bg = [255, 255, 255]) {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
console.log(`Logo ${logo.width}x${logo.height}, Ball: Mitte (${orb.cx}, ${orb.cy}), Radius ${orb.r}`);
|
||||||
const edge = await startEdge();
|
const edge = await startEdge();
|
||||||
try {
|
try {
|
||||||
// Textbreiten der Wortmarke messen, damit nichts verzerrt wird
|
|
||||||
await edge.html(`<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><text id="a" font-family="${FONT}" font-size="170">euer.tv</text><text id="b" font-family="${FONT}" font-size="170">chat</text></svg>`, 400, 200);
|
|
||||||
const measure = {
|
|
||||||
left: await edge.eval("document.getElementById('a').getComputedTextLength()"),
|
|
||||||
right: await edge.eval("document.getElementById('b').getComputedTextLength()"),
|
|
||||||
};
|
|
||||||
console.log('Textbreiten:', measure);
|
|
||||||
|
|
||||||
const render = async (svg, w, h) => { await edge.html(svg, w, h); return edge.png(); };
|
|
||||||
const out = {
|
const out = {
|
||||||
icons: path.join(root, 'src-tauri', 'icons'),
|
icons: path.join(root, 'src-tauri', 'icons'),
|
||||||
assets: path.join(root, 'assets'),
|
|
||||||
installer: path.join(root, 'installer'),
|
installer: path.join(root, 'installer'),
|
||||||
ui: path.join(root, 'ui'),
|
ui: path.join(root, 'ui'),
|
||||||
};
|
};
|
||||||
for (const d of Object.values(out)) fs.mkdirSync(d, { recursive: true });
|
for (const d of Object.values(out)) fs.mkdirSync(d, { recursive: true });
|
||||||
|
|
||||||
|
orbUrl = await edge.evaluate(RETOUCH_JS);
|
||||||
|
|
||||||
const frames = [];
|
const frames = [];
|
||||||
for (const size of [256, 128, 64, 48, 32, 24, 16]) frames.push({ size, png: await render(iconSvg(size), size, size) });
|
for (const size of [256, 128, 64, 48, 32, 24, 16]) frames.push({ size, png: await edge.render(orbHtml(size), size, size) });
|
||||||
fs.writeFileSync(path.join(out.icons, 'icon.ico'), makeIco(frames));
|
|
||||||
const bySize = (s) => frames.find((f) => f.size === s).png;
|
const bySize = (s) => frames.find((f) => f.size === s).png;
|
||||||
|
fs.writeFileSync(path.join(out.icons, 'icon.ico'), makeIco(frames));
|
||||||
fs.writeFileSync(path.join(out.icons, '32x32.png'), bySize(32));
|
fs.writeFileSync(path.join(out.icons, '32x32.png'), bySize(32));
|
||||||
fs.writeFileSync(path.join(out.icons, '128x128.png'), bySize(128));
|
fs.writeFileSync(path.join(out.icons, '128x128.png'), bySize(128));
|
||||||
fs.writeFileSync(path.join(out.icons, 'icon.png'), await render(iconSvg(512), 512, 512));
|
fs.writeFileSync(path.join(out.icons, 'icon.png'), await edge.render(orbHtml(512), 512, 512));
|
||||||
fs.writeFileSync(path.join(out.ui, 'logo.png'), bySize(128));
|
fs.writeFileSync(path.join(out.ui, 'logo.png'), bySize(128));
|
||||||
|
|
||||||
const wide = wideSvg(measure);
|
fs.writeFileSync(path.join(out.installer, 'sidebar.bmp'), pngToBmp(await edge.render(sidebarHtml(), 164, 314), [5, 0, 0]));
|
||||||
fs.writeFileSync(path.join(out.assets, 'logo-wide.png'), await render(wide.svg, wide.width, wide.height));
|
fs.writeFileSync(path.join(out.installer, 'header.bmp'), pngToBmp(await edge.render(wideHtml(150, 57), 150, 57)));
|
||||||
fs.writeFileSync(path.join(out.assets, 'logo-icon.svg'), iconSvg(512));
|
console.log('Fertig.');
|
||||||
fs.writeFileSync(path.join(out.assets, 'logo-wide.svg'), wide.svg);
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(out.installer, 'sidebar.bmp'), pngToBmp(await render(sidebarSvg(), 164, 314), [5, 0, 0]));
|
|
||||||
fs.writeFileSync(path.join(out.installer, 'header.bmp'), pngToBmp(await render(headerSvg(wide), 150, 57)));
|
|
||||||
console.log('Fertig:', Object.values(out).join('\n '));
|
|
||||||
} finally {
|
} finally {
|
||||||
edge.close();
|
edge.close();
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |