diff --git a/src/server/services/image.service.ts b/src/server/services/image.service.ts index 249f2d9..1369307 100644 --- a/src/server/services/image.service.ts +++ b/src/server/services/image.service.ts @@ -1,4 +1,5 @@ import { randomUUID } from "node:crypto"; +import sharp from "sharp"; import { uploadImage } from "./storage.service"; const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"]; @@ -8,6 +9,7 @@ const FETCH_TIMEOUT = 10_000; // 10 seconds interface FetchImageResult { filename: string; sourceUrl: string; + dominantColor: string | null; } export async function fetchImageFromUrl( @@ -75,5 +77,29 @@ export async function fetchImageFromUrl( // Upload to object storage await uploadImage(Buffer.from(buffer), filename, contentType); - return { filename, sourceUrl: url }; + const dominantColor = await extractDominantColor(buffer); + + return { filename, sourceUrl: url, dominantColor }; +} + +/** + * Extract the dominant color from an image buffer. + * Resizes to 1x1 pixel for a perceptually weighted average. + * Returns hex string like '#a3b2c1' or null on failure. + */ +export async function extractDominantColor( + buffer: Buffer | ArrayBuffer, +): Promise { + try { + const { data } = await sharp(Buffer.from(buffer)) + .resize(1, 1) + .raw() + .toBuffer({ resolveWithObject: true }); + const r = data[0]; + const g = data[1]; + const b = data[2]; + return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; + } catch { + return null; + } }