159 lines
5.8 KiB
TypeScript
159 lines
5.8 KiB
TypeScript
import Color from 'color';
|
|
|
|
type ColorScheme = {
|
|
name: string;
|
|
colors: {
|
|
primary: { background: string; foreground: string };
|
|
normal: {
|
|
black: string; red: string; green: string; yellow: string;
|
|
blue: string; magenta: string; cyan: string; white: string;
|
|
};
|
|
bright: {
|
|
black: string; red: string; green: string; yellow: string;
|
|
blue: string; magenta: string; cyan: string; white: string;
|
|
};
|
|
};
|
|
};
|
|
|
|
import knownSchemesData from '../../formatted_themes.json';
|
|
|
|
const knownSchemes: ColorScheme[] = knownSchemesData;
|
|
|
|
function generateRandomColor(): string {
|
|
return '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
|
|
}
|
|
|
|
function generateCreativeName(colors: { [key: string]: string }): string {
|
|
const allColors = Object.values(colors);
|
|
const dominantColor = getDominantColor(allColors);
|
|
const mood = getMood(allColors);
|
|
const theme = getTheme(dominantColor);
|
|
|
|
const nameComponents = [mood, theme].filter(Boolean);
|
|
if (nameComponents.length === 1) {
|
|
nameComponents.push(getRandomAdjective(dominantColor));
|
|
}
|
|
|
|
return nameComponents.join(' ');
|
|
}
|
|
|
|
function getDominantColor(colors: string[]): Color {
|
|
return colors.reduce((dominant, color) => {
|
|
const current = Color(color);
|
|
return current.luminosity() > dominant.luminosity() ? current : dominant;
|
|
}, Color(colors[0]));
|
|
}
|
|
|
|
function getMood(colors: string[]): string {
|
|
const avgSaturation = colors.reduce((sum, color) => sum + Color(color).saturationl(), 0) / colors.length;
|
|
const avgLightness = colors.reduce((sum, color) => sum + Color(color).lightness(), 0) / colors.length;
|
|
|
|
if (avgSaturation > 50 && avgLightness > 50) return 'Vibrant';
|
|
if (avgSaturation < 30 && avgLightness < 40) return 'Muted';
|
|
if (avgLightness > 70) return 'Bright';
|
|
if (avgLightness < 30) return 'Dark';
|
|
return '';
|
|
}
|
|
|
|
function getTheme(dominantColor: Color): string {
|
|
const hue = dominantColor.hue();
|
|
const themes = [
|
|
{ name: 'Sunset', range: [0, 60] },
|
|
{ name: 'Citrus', range: [45, 90] },
|
|
{ name: 'Forest', range: [90, 150] },
|
|
{ name: 'Ocean', range: [150, 210] },
|
|
{ name: 'Twilight', range: [210, 270] },
|
|
{ name: 'Lavender', range: [270, 330] },
|
|
{ name: 'Berry', range: [330, 360] },
|
|
];
|
|
|
|
const theme = themes.find(t => hue >= t.range[0] && hue < t.range[1]);
|
|
return theme ? theme.name : '';
|
|
}
|
|
|
|
function getRandomAdjective(color: Color): string {
|
|
const adjectives = {
|
|
warm: ['Cozy', 'Toasty', 'Snug'],
|
|
cool: ['Crisp', 'Fresh', 'Breezy'],
|
|
neutral: ['Balanced', 'Harmonious', 'Zen'],
|
|
bright: ['Radiant', 'Luminous', 'Gleaming'],
|
|
dark: ['Mysterious', 'Enigmatic', 'Shadowy'],
|
|
};
|
|
|
|
const hue = color.hue();
|
|
const lightness = color.lightness();
|
|
|
|
let category: keyof typeof adjectives;
|
|
if (hue < 60 || hue > 300) category = 'warm';
|
|
else if (hue >= 60 && hue <= 300) category = 'cool';
|
|
else if (lightness > 70) category = 'bright';
|
|
else if (lightness < 30) category = 'dark';
|
|
else category = 'neutral';
|
|
|
|
return adjectives[category][Math.floor(Math.random() * adjectives[category].length)];
|
|
}
|
|
|
|
function generateRandomScheme(): ColorScheme {
|
|
const colors = {
|
|
primary: { background: generateRandomColor(), foreground: generateRandomColor() },
|
|
normal: {
|
|
black: generateRandomColor(), red: generateRandomColor(), green: generateRandomColor(), yellow: generateRandomColor(),
|
|
blue: generateRandomColor(), magenta: generateRandomColor(), cyan: generateRandomColor(), white: generateRandomColor()
|
|
},
|
|
bright: {
|
|
black: generateRandomColor(), red: generateRandomColor(), green: generateRandomColor(), yellow: generateRandomColor(),
|
|
blue: generateRandomColor(), magenta: generateRandomColor(), cyan: generateRandomColor(), white: generateRandomColor()
|
|
}
|
|
};
|
|
|
|
colors.primary.background = colors.normal.black;
|
|
colors.primary.foreground = colors.bright.white;
|
|
|
|
return {
|
|
name: generateCreativeName({ ...colors.normal, ...colors.bright }),
|
|
colors: colors
|
|
};
|
|
}
|
|
|
|
function mutateColor(color: string): string {
|
|
const c = Color(color);
|
|
const hue = (c.hue() + Math.random() * 30 - 15 + 360) % 360;
|
|
const saturation = Math.max(0, Math.min(100, c.saturationl() + Math.random() * 20 - 10));
|
|
const lightness = Math.max(0, Math.min(100, c.lightness() + Math.random() * 20 - 10));
|
|
return c.hsl(hue, saturation, lightness).hex();
|
|
}
|
|
|
|
function generateSchemeFromGeneticAlgorithm(likedSchemes: ColorScheme[], dislikedSchemes: ColorScheme[]): ColorScheme {
|
|
if (likedSchemes.length === 0) {
|
|
return generateRandomScheme();
|
|
}
|
|
|
|
const parentScheme = likedSchemes[Math.floor(Math.random() * likedSchemes.length)];
|
|
const newScheme: ColorScheme = JSON.parse(JSON.stringify(parentScheme)); // Deep copy
|
|
|
|
// Mutate colors
|
|
Object.keys(newScheme.colors).forEach((colorGroup: keyof typeof newScheme.colors) => {
|
|
Object.keys(newScheme.colors[colorGroup]).forEach((colorName: string) => {
|
|
if (Math.random() < 0.3) { // 30% chance of mutation
|
|
(newScheme.colors[colorGroup] as any)[colorName] = mutateColor((newScheme.colors[colorGroup] as any)[colorName]);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Avoid similarities with disliked schemes
|
|
dislikedSchemes.forEach(dislikedScheme => {
|
|
Object.keys(newScheme.colors).forEach((colorGroup: keyof typeof newScheme.colors) => {
|
|
Object.keys(newScheme.colors[colorGroup]).forEach((colorName: string) => {
|
|
if ((newScheme.colors[colorGroup] as any)[colorName] === (dislikedScheme.colors[colorGroup] as any)[colorName]) {
|
|
(newScheme.colors[colorGroup] as any)[colorName] = mutateColor((newScheme.colors[colorGroup] as any)[colorName]);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
newScheme.name = generateCreativeName({ ...newScheme.colors.normal, ...newScheme.colors.bright });
|
|
return newScheme;
|
|
}
|
|
|
|
export type { ColorScheme };
|
|
export { knownSchemes, generateRandomScheme, generateSchemeFromGeneticAlgorithm }; |