feat(29-01): add dominant color extraction via Sharp
extractDominantColor() resizes image to 1x1 pixel for weighted average color. Integrated into fetchImageFromUrl to return dominantColor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
|
import sharp from "sharp";
|
||||||
import { uploadImage } from "./storage.service";
|
import { uploadImage } from "./storage.service";
|
||||||
|
|
||||||
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
|
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
|
||||||
@@ -8,6 +9,7 @@ const FETCH_TIMEOUT = 10_000; // 10 seconds
|
|||||||
interface FetchImageResult {
|
interface FetchImageResult {
|
||||||
filename: string;
|
filename: string;
|
||||||
sourceUrl: string;
|
sourceUrl: string;
|
||||||
|
dominantColor: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchImageFromUrl(
|
export async function fetchImageFromUrl(
|
||||||
@@ -75,5 +77,29 @@ export async function fetchImageFromUrl(
|
|||||||
// Upload to object storage
|
// Upload to object storage
|
||||||
await uploadImage(Buffer.from(buffer), filename, contentType);
|
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<string | null> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user