155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import sharp from "sharp";
|
|
import {
|
|
buildReplacementInputBoundingBoxList,
|
|
compositeReplacementWithReferenceLocks,
|
|
expandReplacementSubjectBoundingBoxForGeneration,
|
|
parseReplacementLayoutLockRegions,
|
|
} from "@/lib/ai/replacement-layout-lock";
|
|
|
|
test("parses normalized subject and protected text regions", () => {
|
|
const result = parseReplacementLayoutLockRegions(`
|
|
\`\`\`json
|
|
{
|
|
"subjectBoundingBox": [200, 250, 800, 900],
|
|
"textBoundingBoxes": [[100, 80, 700, 160], [20, 20, 20, 40], [-20, 900, 1100, 980]]
|
|
}
|
|
\`\`\`
|
|
`);
|
|
|
|
expect(result).toEqual({
|
|
subjectBoundingBox: [200, 250, 800, 900],
|
|
textBoundingBoxes: [
|
|
[100, 80, 700, 160],
|
|
[0, 900, 1000, 980],
|
|
],
|
|
analyzed: true,
|
|
});
|
|
});
|
|
|
|
test("marks incomplete layout analysis for a safe fallback", () => {
|
|
expect(
|
|
parseReplacementLayoutLockRegions({
|
|
subjectBoundingBox: [200, 250, 800, 900],
|
|
}),
|
|
).toEqual({
|
|
subjectBoundingBox: [200, 250, 800, 900],
|
|
textBoundingBoxes: [],
|
|
analyzed: false,
|
|
});
|
|
});
|
|
|
|
test("expands the replacement subject horizontally and aligns it with the layout input", () => {
|
|
expect(
|
|
expandReplacementSubjectBoundingBoxForGeneration([379, 240, 628, 917]),
|
|
).toEqual([354, 230, 653, 927]);
|
|
expect(
|
|
buildReplacementInputBoundingBoxList({
|
|
imageUrls: [
|
|
"https://example.com/product.png",
|
|
"https://example.com/layout.png",
|
|
],
|
|
referenceImageUrl: "https://example.com/layout.png",
|
|
subjectBoundingBox: [379, 240, 628, 917],
|
|
}),
|
|
).toEqual([[], [[354, 230, 653, 927]]]);
|
|
});
|
|
|
|
test("keeps the reference outside the subject and inside protected text", async () => {
|
|
const width = 100;
|
|
const height = 100;
|
|
const reference = await sharp({
|
|
create: {
|
|
width,
|
|
height,
|
|
channels: 3,
|
|
background: { r: 255, g: 0, b: 0 },
|
|
},
|
|
})
|
|
.png()
|
|
.toBuffer();
|
|
const generated = await sharp({
|
|
create: {
|
|
width,
|
|
height,
|
|
channels: 3,
|
|
background: { r: 0, g: 0, b: 255 },
|
|
},
|
|
})
|
|
.png()
|
|
.toBuffer();
|
|
|
|
const result = await compositeReplacementWithReferenceLocks({
|
|
generatedBuffer: generated,
|
|
referenceBuffer: reference,
|
|
width,
|
|
height,
|
|
preserveReferenceOutsideSubject: true,
|
|
subjectBoundingBox: [300, 300, 700, 700],
|
|
textBoundingBoxes: [[450, 450, 550, 550]],
|
|
});
|
|
const { data, info } = await sharp(result)
|
|
.removeAlpha()
|
|
.raw()
|
|
.toBuffer({ resolveWithObject: true });
|
|
const pixel = (x: number, y: number) =>
|
|
Array.from(
|
|
data.subarray(
|
|
(y * width + x) * info.channels,
|
|
(y * width + x) * info.channels + 3,
|
|
),
|
|
);
|
|
|
|
expect(pixel(5, 5)).toEqual([255, 0, 0]);
|
|
expect(pixel(35, 50)).toEqual([0, 0, 255]);
|
|
expect(pixel(50, 50)).toEqual([255, 0, 0]);
|
|
});
|
|
|
|
test("restores original text pixels while retaining a regenerated scene", async () => {
|
|
const width = 100;
|
|
const height = 100;
|
|
const reference = await sharp({
|
|
create: {
|
|
width,
|
|
height,
|
|
channels: 3,
|
|
background: { r: 255, g: 0, b: 0 },
|
|
},
|
|
})
|
|
.png()
|
|
.toBuffer();
|
|
const generated = await sharp({
|
|
create: {
|
|
width,
|
|
height,
|
|
channels: 3,
|
|
background: { r: 0, g: 0, b: 255 },
|
|
},
|
|
})
|
|
.png()
|
|
.toBuffer();
|
|
|
|
const result = await compositeReplacementWithReferenceLocks({
|
|
generatedBuffer: generated,
|
|
referenceBuffer: reference,
|
|
width,
|
|
height,
|
|
preserveReferenceOutsideSubject: false,
|
|
textBoundingBoxes: [[400, 400, 600, 600]],
|
|
});
|
|
const { data, info } = await sharp(result)
|
|
.removeAlpha()
|
|
.raw()
|
|
.toBuffer({ resolveWithObject: true });
|
|
const pixel = (x: number, y: number) =>
|
|
Array.from(
|
|
data.subarray(
|
|
(y * width + x) * info.channels,
|
|
(y * width + x) * info.channels + 3,
|
|
),
|
|
);
|
|
|
|
expect(pixel(5, 5)).toEqual([0, 0, 255]);
|
|
expect(pixel(50, 50)).toEqual([255, 0, 0]);
|
|
});
|