Skip to content

Commit 0d6da8c

Browse files
committed
Improved album art generation
1 parent c1235a7 commit 0d6da8c

File tree

1 file changed

+110
-87
lines changed

1 file changed

+110
-87
lines changed

src/app/album-art/route.tsx

Lines changed: 110 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,110 +4,120 @@ import 'server-only';
44
import { fetchArtists, fetchShowByUUID } from '@/app/queries';
55
import { ImageResponse } from 'next/og';
66
import { NextRequest } from 'next/server';
7-
import React from 'react';
7+
import { notFound } from 'next/navigation';
88

99
export const runtime = 'edge';
1010

11-
const notFound = () =>
12-
new Response('Not Found', {
13-
status: 404,
14-
});
15-
1611
// Function to generate a color based on artist UUID
1712
const getArtistColor = (uuid: string) => {
18-
// Generate a consistent color based on the UUID
13+
// Generate a consistent hash from the UUID
1914
const hash = Array.from(uuid).reduce((acc, char) => char.charCodeAt(0) + ((acc << 5) - acc), 0);
2015

21-
// Map to a curated set of Tailwind colors
22-
const tailwindColors = [
23-
'#3b82f6', // blue-500
24-
'#10b981', // emerald-500
25-
'#8b5cf6', // violet-500
26-
'#ef4444', // red-500
27-
'#f59e0b', // amber-500
28-
'#06b6d4', // cyan-500
29-
'#f97316', // orange-500
30-
'#8b5cf6', // violet-500
31-
'#0ea5e9', // sky-500
32-
'#22c55e', // green-500
33-
'#dc2626', // red-600
34-
'#0d9488', // teal-600
35-
];
16+
// Use HSL color model for more systematic and beautiful variations
17+
// Hue: 0-360 (full color spectrum)
18+
// Saturation: 60-80% (more vibrant than before)
19+
// Lightness: 55-75% (slightly darker for more vibrant appearance)
20+
const hue = Math.abs(hash) % 360;
21+
const saturation = 60 + (Math.abs(hash >> 8) % 20); // Increased from 40-60% to 60-80%
22+
const lightness = 55 + (Math.abs(hash >> 16) % 20); // Adjusted from 65-85% to 55-75%
3623

37-
return tailwindColors[Math.abs(hash) % tailwindColors.length];
24+
// Return the color in HSL format
25+
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
3826
};
3927

4028
// Function to generate a gradient based on artist UUID
4129
const getArtistGradient = (uuid: string) => {
30+
// Generate a base color using HSL for better control
4231
const baseColor = getArtistColor(uuid);
4332

44-
// Create complementary color pairs for beautiful gradients using Tailwind colors
45-
const gradientPairs = {
46-
'#3b82f6': '#8b5cf6', // blue-500 to violet-500
47-
'#10b981': '#06b6d4', // emerald-500 to cyan-500
48-
'#8b5cf6': '#3b82f6', // violet-500 to blue-500
49-
'#ef4444': '#f97316', // red-500 to orange-500
50-
'#f59e0b': '#ef4444', // amber-500 to red-500
51-
'#06b6d4': '#10b981', // cyan-500 to emerald-500
52-
'#f97316': '#f59e0b', // orange-500 to amber-500
53-
// '#8b5cf6': '#a855f7', // violet-500 to purple-500
54-
'#0ea5e9': '#3b82f6', // sky-500 to blue-500
55-
'#22c55e': '#10b981', // green-500 to emerald-500
56-
'#dc2626': '#ef4444', // red-600 to red-500
57-
'#0d9488': '#06b6d4', // teal-600 to cyan-500
58-
};
59-
60-
const secondColor = gradientPairs[baseColor] || '#8b5cf6'; // Default to violet-500
61-
62-
return `linear-gradient(135deg, ${baseColor}, ${secondColor})`;
63-
};
64-
65-
export async function GET(request: NextRequest) {
66-
try {
67-
const { searchParams } = new URL(request.url);
33+
// Create a second hash for the complementary color
34+
const secondHash = Array.from(uuid).reduce(
35+
(acc, char, i) => char.charCodeAt(0) + ((acc << ((i % 5) + 3)) - acc),
36+
0
37+
);
38+
39+
// Generate complementary colors using color theory
40+
// Options: analogous (±30°), complementary (180°), triadic (120°), split-complementary (±150°)
41+
const colorSchemes = [
42+
30, // analogous 1
43+
-30, // analogous 2
44+
60, // harmonious 1
45+
-60, // harmonious 2
46+
120, // triadic 1
47+
-120, // triadic 2
48+
// 150, // split-complementary 1
49+
// -150, // split-complementary 2
50+
180, // complementary (used less frequently for pastels)
51+
];
6852

69-
const showUuid = searchParams.get('showUuid');
70-
if (!showUuid) return notFound();
53+
// Extract HSL values from the base color
54+
const baseHSLMatch = baseColor.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/);
55+
const baseHue = baseHSLMatch ? parseInt(baseHSLMatch[1]) : 0;
56+
const baseSaturation = baseHSLMatch ? parseInt(baseHSLMatch[2]) : 60;
57+
const baseLightness = baseHSLMatch ? parseInt(baseHSLMatch[3]) : 55;
7158

72-
const artists = await fetchArtists();
73-
const show = await fetchShowByUUID(showUuid);
59+
// Select a color scheme based on the hash
60+
const schemeOffset = colorSchemes[Math.abs(secondHash) % colorSchemes.length];
7461

75-
if (!show || !show.sources?.length) return notFound();
62+
// Create a complementary hue and adjust saturation/lightness for vibrant effect
63+
const secondHue = (baseHue + schemeOffset + 360) % 360;
64+
// Higher saturation but still balanced
65+
const secondSaturation = Math.min(Math.max(baseSaturation - (secondHash % 10), 75), 75); // Increased from 35-55% to 55-75%
66+
const secondLightness = Math.min(Math.max(baseLightness + (secondHash % 10), 60), 75); // Adjusted from 70-85% to 60-75%
7667

77-
// Get params
78-
const artist = artists.find((artist) => artist.uuid === show.artist_uuid);
79-
const artistName = artist?.name ?? 'Unknown Artist';
68+
// Create second color in HSL format
69+
const secondColor = `hsl(${secondHue}, ${secondSaturation}%, ${secondLightness}%)`;
8070

81-
// Generate dynamic background color and pattern based on artist UUID
82-
const bgGradient = getArtistGradient(show.artist_uuid || '');
71+
// Determine if we should use a third color (about 1/3 of the time)
8372

84-
// Generate a pattern of circles for the background based on artist UUID
85-
const patternSeed = parseInt(show.artist_uuid?.replace(/\D/g, '').slice(0, 8) || '0', 10);
86-
const patternElements: React.ReactNode[] = [];
73+
const gradientString = `linear-gradient(135deg, ${baseColor}, ${secondColor})`;
8774

88-
for (let i = 0; i < 5; i++) {
89-
const size = 100 + (patternSeed % 200);
90-
const x = (patternSeed * (i + 1)) % 900;
91-
const y = (patternSeed * (i + 2)) % 900;
92-
const opacity = 0.1 + i * 0.05;
75+
return gradientString;
76+
};
9377

94-
patternElements.push(
95-
<div
96-
key={i}
97-
tw="absolute rounded-full"
98-
style={{
99-
width: size,
100-
height: size,
101-
left: x,
102-
top: y,
103-
background: 'white',
104-
opacity: opacity,
105-
filter: 'blur(40px)',
106-
}}
107-
/>
108-
);
78+
const generatePixelatedSVG = (opacity = 0.4) => {
79+
// Create a pixelated pattern with large squares
80+
const squareSize = 45; // Size of each pixel square
81+
const gridSize = 10; // Number of squares in each row/column
82+
const borderWidth = 1; // Reduced width of the border between pixels
83+
const borderColor = 'rgba(0, 0, 0, 0.1)'; // More transparent black for subtler borders
84+
85+
let squares = '';
86+
for (let y = 0; y < gridSize; y++) {
87+
for (let x = 0; x < gridSize; x++) {
88+
// TODO: get rid of the randomness here.
89+
const squareOpacity = (Math.random() * 0.3 + 0.1) * opacity;
90+
91+
squares += `<rect
92+
x="${x * squareSize}"
93+
y="${y * squareSize}"
94+
width="${squareSize}"
95+
height="${squareSize}"
96+
fill="white"
97+
fill-opacity="${squareOpacity}"
98+
stroke="${borderColor}"
99+
stroke-width="${borderWidth}"
100+
/>`;
109101
}
110-
const [fontReg, fontBold, fontMegaBold] = await Promise.all([
102+
}
103+
104+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${squareSize * gridSize}" height="${squareSize * gridSize}" viewBox="0 0 ${squareSize * gridSize} ${squareSize * gridSize}">
105+
${squares}
106+
</svg>`;
107+
108+
return `url("data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}")`;
109+
};
110+
111+
export async function GET(request: NextRequest) {
112+
try {
113+
const { searchParams } = new URL(request.url);
114+
115+
const showUuid = searchParams.get('showUuid');
116+
if (!showUuid) return notFound();
117+
118+
const [artists, show, fontReg, fontBold, fontMegaBold] = await Promise.all([
119+
fetchArtists(),
120+
fetchShowByUUID(showUuid),
111121
fetch(
112122
new URL('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.ttf'),
113123
{ next: { revalidate: 60 * 60 * 24 * 30 } } // Cache for 30 days
@@ -122,26 +132,39 @@ export async function GET(request: NextRequest) {
122132
).then((res) => res.arrayBuffer()),
123133
]);
124134

135+
if (!show || !show.sources?.length) return notFound();
136+
137+
// Get params
138+
const artist = artists.find((artist) => artist.uuid === show.artist_uuid);
139+
const artistName = artist?.name ?? 'Unknown Artist';
140+
141+
// Generate dynamic background color and pattern based on artist UUID
142+
const bgGradient = getArtistGradient(show.artist_uuid || '');
143+
// Generate pixelated background pattern
144+
const pixelatedSVG = generatePixelatedSVG(0.8);
145+
125146
return new ImageResponse(
126147
(
127148
<div
128149
tw="flex h-full w-full flex-col items-center justify-center p-10 text-white relative overflow-hidden"
129-
style={{ background: bgGradient }}
150+
style={{
151+
backgroundImage: `${pixelatedSVG}, ${bgGradient}`,
152+
backgroundBlendMode: 'overlay',
153+
}}
130154
>
131-
<div tw="flex w-full max-w-[800px] flex-col items-center justify-center rounded-2xl bg-black/20 p-12 backdrop-blur-sm shadow-2xl">
132-
<div tw="mb-4 text-center text-7xl font-extrabold tracking-tight">{artistName}</div>
133-
<div tw="mb-6 text-center text-6xl font-bold">{show.display_date}</div>
155+
<div tw="flex w-full max-w-[800px] flex-col items-center justify-center rounded-2xl bg-black/25 p-12 relative">
156+
<div tw="mb-2 text-center text-7xl font-extrabold tracking-tight">{artistName}</div>
157+
<div tw="mb-2 text-center text-6xl font-bold">{show.display_date}</div>
134158
<div tw="flex items-center justify-center" style={{ gap: 8 }}>
135159
{show.venue?.name && (
136-
<div tw="rounded-xl bg-white/25 px-6 py-3 text-4xl flex text-center backdrop-blur-sm">
160+
<div tw="rounded-xl bg-white/20 px-6 py-3 text-4xl flex text-center">
137161
{show.venue?.name} {show.venue?.location ? `• ${show.venue.location}` : ''}
138162
</div>
139163
)}
140164
</div>
141165
</div>
142166

143167
<div tw="absolute bottom-6 right-6 text-4xl font-bold">Relisten.net</div>
144-
{patternElements}
145168
</div>
146169
),
147170
{

0 commit comments

Comments
 (0)