91 lines
2.8 KiB
JavaScript
91 lines
2.8 KiB
JavaScript
import sharp from 'sharp';
|
|
import { join } from 'path';
|
|
import { UPLOADS_DIR } from './runtime-paths.mjs';
|
|
|
|
const uploadsDir = UPLOADS_DIR;
|
|
|
|
export async function prepareForAI(filenames, maxSize = 1024) {
|
|
const images = [];
|
|
for (const filename of filenames) {
|
|
const filepath = join(uploadsDir, filename);
|
|
const buffer = await sharp(filepath)
|
|
.resize(maxSize, maxSize, { fit: 'inside', withoutEnlargement: true })
|
|
.jpeg({ quality: 85 })
|
|
.toBuffer();
|
|
images.push({ base64: buffer.toString('base64'), mediaType: 'image/jpeg' });
|
|
}
|
|
return images;
|
|
}
|
|
|
|
export async function removeBackground(filename) {
|
|
const filepath = join(uploadsDir, filename);
|
|
const newName = filename.replace(/(\.\w+)$/, '_nobg.png');
|
|
const outPath = join(uploadsDir, newName);
|
|
|
|
const image = sharp(filepath);
|
|
const { width, height } = await image.metadata();
|
|
|
|
const { data, info } = await image
|
|
.ensureAlpha()
|
|
.raw()
|
|
.toBuffer({ resolveWithObject: true });
|
|
|
|
const corners = [
|
|
getPixel(data, 0, 0, info.width),
|
|
getPixel(data, info.width - 1, 0, info.width),
|
|
getPixel(data, 0, info.height - 1, info.width),
|
|
getPixel(data, info.width - 1, info.height - 1, info.width),
|
|
];
|
|
|
|
const bgColor = avgColor(corners);
|
|
const tolerance = 60;
|
|
const result = Buffer.from(data);
|
|
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const dr = Math.abs(data[i] - bgColor.r);
|
|
const dg = Math.abs(data[i + 1] - bgColor.g);
|
|
const db = Math.abs(data[i + 2] - bgColor.b);
|
|
if (dr + dg + db < tolerance * 3) {
|
|
result[i + 3] = 0;
|
|
}
|
|
}
|
|
|
|
await sharp(result, { raw: { width: info.width, height: info.height, channels: 4 } })
|
|
.png()
|
|
.toFile(outPath);
|
|
|
|
return newName;
|
|
}
|
|
|
|
function getPixel(data, x, y, width) {
|
|
const idx = (y * width + x) * 4;
|
|
return { r: data[idx], g: data[idx + 1], b: data[idx + 2] };
|
|
}
|
|
|
|
function avgColor(pixels) {
|
|
const sum = pixels.reduce((a, p) => ({ r: a.r + p.r, g: a.g + p.g, b: a.b + p.b }), { r: 0, g: 0, b: 0 });
|
|
const n = pixels.length;
|
|
return { r: Math.round(sum.r / n), g: Math.round(sum.g / n), b: Math.round(sum.b / n) };
|
|
}
|
|
|
|
export async function addWatermark(filename, text = 'Vendoo') {
|
|
const filepath = join(uploadsDir, filename);
|
|
const newName = filename.replace(/(\.\w+)$/, '_wm$1');
|
|
const outPath = join(uploadsDir, newName);
|
|
|
|
const image = sharp(filepath);
|
|
const { width, height } = await image.metadata();
|
|
|
|
const fontSize = Math.max(16, Math.round(width * 0.04));
|
|
const svg = `<svg width="${width}" height="${height}">
|
|
<style>.wm { fill: rgba(255,255,255,0.4); font-size: ${fontSize}px; font-family: sans-serif; font-weight: bold; }</style>
|
|
<text x="${width - 10}" y="${height - 10}" text-anchor="end" class="wm">${text}</text>
|
|
</svg>`;
|
|
|
|
await image
|
|
.composite([{ input: Buffer.from(svg), top: 0, left: 0 }])
|
|
.toFile(outPath);
|
|
|
|
return newName;
|
|
}
|