mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 00:16:41 +02:00
feat(core): PDF-Export — Akzentfarbe dynamisch aus Branding-Logo extrahiert
- extractDominantColor(): 20x20 Resize via sharp, Alpha gegen Weiss flatten, alle gesaettigten Pixel (nicht weiss/schwarz/grau, range > 35) mitteln - Ergebnis wird als accentColor fuer Timeline-Linien, Ueberschriften, Skill-Chips usw. verwendet - Fallback auf #009688 wenn kein Logo hinterlegt oder keine Farbe extrahierbar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f5d83dc1c3
commit
382beab9c3
1 changed files with 48 additions and 0 deletions
|
|
@ -218,6 +218,13 @@ export class ProfileExportService {
|
|||
}
|
||||
}
|
||||
} catch { /* Einstellungen nicht verfügbar */ }
|
||||
|
||||
// Akzentfarbe dynamisch aus Logo extrahieren
|
||||
if (platformLogo) {
|
||||
const extracted = await this.extractDominantColor(platformLogo);
|
||||
if (extracted) accentColor = extracted;
|
||||
}
|
||||
|
||||
const fullName = `${data.firstName} ${data.lastName}`;
|
||||
const { firstName, lastName } = data;
|
||||
|
||||
|
|
@ -953,4 +960,45 @@ export class ProfileExportService {
|
|||
const base64Data = dataUrl.includes(',') ? dataUrl.split(',')[1] : dataUrl;
|
||||
return Buffer.from(base64Data, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert die dominante Akzentfarbe aus einem Logo-Bild.
|
||||
* Strategie: 20x20 Pixel resizen, transparenten Alpha gegen Weiss flatten,
|
||||
* dann alle "bunten" Pixel (nicht weiss/schwarz/grau) mitteln.
|
||||
* Gibt null zurück wenn keine ausreichend gesättigte Farbe gefunden wird.
|
||||
*/
|
||||
private async extractDominantColor(imageBuffer: Buffer): Promise<string | null> {
|
||||
try {
|
||||
const { data, info } = await sharp(imageBuffer)
|
||||
.resize(20, 20, { fit: 'fill' })
|
||||
.flatten({ background: { r: 255, g: 255, b: 255 } })
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
let rSum = 0, gSum = 0, bSum = 0, count = 0;
|
||||
for (let i = 0; i < info.width * info.height; i++) {
|
||||
const r = data[i * 3]!;
|
||||
const g = data[i * 3 + 1]!;
|
||||
const b = data[i * 3 + 2]!;
|
||||
const avg = (r + g + b) / 3;
|
||||
const range = Math.max(r, g, b) - Math.min(r, g, b);
|
||||
// Nur gesättigte Pixel: nicht zu hell, nicht zu dunkel, genug Farbabstand
|
||||
if (avg > 25 && avg < 225 && range > 35) {
|
||||
rSum += r;
|
||||
gSum += g;
|
||||
bSum += b;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count === 0) return null;
|
||||
|
||||
const r = Math.round(rSum / count);
|
||||
const g = Math.round(gSum / count);
|
||||
const b = Math.round(bSum / count);
|
||||
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue