ai-chatbot/lib/canvas/layout-replace-source-profile.ts

71 lines
2.1 KiB
TypeScript

import "server-only";
import { fetchImageBufferFromUrl } from "@/lib/ai/rmbg/service";
export type CanvasLayoutReplaceSourceProfile = {
boundingHeight: number;
boundingWidth: number;
silhouetteAspectRatio: number;
sourceHeight: number;
sourceWidth: number;
transparentPixelRatio: number;
};
export async function resolveCanvasLayoutReplaceSourceProfile(
sourceImageUrl: string,
): Promise<CanvasLayoutReplaceSourceProfile | null> {
try {
const sharp = (await import("sharp")).default;
const sourceBuffer = await fetchImageBufferFromUrl(sourceImageUrl);
const { data, info } = await sharp(sourceBuffer, { failOn: "none" })
.rotate()
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
const pixelCount = info.width * info.height;
if (pixelCount <= 0 || info.channels < 4) return null;
let transparentPixelCount = 0;
let minX = info.width;
let minY = info.height;
let maxX = -1;
let maxY = -1;
for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex += 1) {
const alpha = data[pixelIndex * info.channels + 3] || 0;
if (alpha < 250) transparentPixelCount += 1;
if (alpha < 24) continue;
const x = pixelIndex % info.width;
const y = Math.floor(pixelIndex / info.width);
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
const transparentPixelRatio = transparentPixelCount / pixelCount;
if (transparentPixelRatio < 0.01 || maxX < minX || maxY < minY) {
return null;
}
const boundingWidth = maxX - minX + 1;
const boundingHeight = maxY - minY + 1;
if (boundingWidth < 2 || boundingHeight < 2) return null;
return {
boundingHeight,
boundingWidth,
silhouetteAspectRatio: boundingWidth / boundingHeight,
sourceHeight: info.height,
sourceWidth: info.width,
transparentPixelRatio,
};
} catch (error) {
console.warn("[Canvas Layout Replace] Failed to resolve source profile:", {
error: error instanceof Error ? error.message : String(error),
});
return null;
}
}