fix: enforce imageSize in alibaba auto aspect mode
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-20 17:12:00 +08:00
parent 64502c9d4b
commit 9623f5365b

View File

@ -47,6 +47,79 @@ interface AlibabaImageResponse {
message?: string;
}
const ALIBABA_IMAGE_FETCH_TIMEOUT_MS = 20000;
const ALIBABA_MIN_SIZE = 512;
const ALIBABA_MAX_SIZE = 2048;
function normalizeImageSizeLabel(imageSize?: string): "1K" | "2K" | "4K" {
const normalized = String(imageSize || "").trim().toUpperCase();
if (normalized === "1K" || normalized === "2K" || normalized === "4K") return normalized;
return "2K";
}
function calculateAlibabaSizeByAspect(aspectValue: number, baseSize: number): string {
const safeAspect = Number.isFinite(aspectValue) && aspectValue > 0 ? aspectValue : 1;
const totalPixels = baseSize * baseSize;
let height = Math.round(Math.sqrt(totalPixels / safeAspect));
let width = Math.round(height * safeAspect);
if (width > ALIBABA_MAX_SIZE) {
const scale = ALIBABA_MAX_SIZE / width;
width = ALIBABA_MAX_SIZE;
height = Math.round(height * scale);
}
if (height > ALIBABA_MAX_SIZE) {
const scale = ALIBABA_MAX_SIZE / height;
height = ALIBABA_MAX_SIZE;
width = Math.round(width * scale);
}
if (width < ALIBABA_MIN_SIZE) {
const scale = ALIBABA_MIN_SIZE / width;
width = ALIBABA_MIN_SIZE;
height = Math.round(height * scale);
}
if (height < ALIBABA_MIN_SIZE) {
const scale = ALIBABA_MIN_SIZE / height;
height = ALIBABA_MIN_SIZE;
width = Math.round(width * scale);
}
width = Math.max(ALIBABA_MIN_SIZE, Math.min(ALIBABA_MAX_SIZE, width));
height = Math.max(ALIBABA_MIN_SIZE, Math.min(ALIBABA_MAX_SIZE, height));
width = Math.round(width / 2) * 2;
height = Math.round(height / 2) * 2;
return `${width}*${height}`;
}
async function inferAspectRatioFromImageUrl(imageUrl: string): Promise<number | null> {
if (!/^https?:\/\//i.test(imageUrl)) return null;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), ALIBABA_IMAGE_FETCH_TIMEOUT_MS);
try {
const response = await fetch(imageUrl, { signal: controller.signal });
if (!response.ok) {
console.warn("[Alibaba Image] Failed to fetch reference image for auto size:", response.status);
return null;
}
const buffer = Buffer.from(await response.arrayBuffer());
if (!buffer.length) return null;
const metadata = await sharp(buffer).metadata();
if (!metadata.width || !metadata.height || metadata.height <= 0) return null;
return metadata.width / metadata.height;
} catch (error) {
console.warn("[Alibaba Image] Failed to infer aspect ratio from reference image:", error);
return null;
} finally {
clearTimeout(timeoutId);
}
}
/**
* 创建缩略图
*/
@ -272,7 +345,19 @@ function getSizeLabel(size: string): string {
"1536*1536": "2K",
"2048*2048": "4K",
};
return sizeMap[size] || "2K";
if (sizeMap[size]) return sizeMap[size];
const matched = size.match(/^(\d+)\*(\d+)$/);
if (matched) {
const width = Number.parseInt(matched[1], 10);
const height = Number.parseInt(matched[2], 10);
const maxSide = Math.max(width, height);
if (maxSide >= 1900) return "4K";
if (maxSide >= 1450) return "2K";
if (maxSide >= 950) return "1K";
}
return "2K";
}
/**
@ -372,7 +457,7 @@ export function createAlibabaImage({ apiKey, baseURL }: AlibabaImageConfig) {
// 处理分辨率:结合 aspectRatio(宽高比)和 imageSize(分辨率等级)
const aspectRatio = options.imageParams?.aspectRatio || "auto";
const imageSize = options.imageParams?.imageSize || "2K";
const imageSize = normalizeImageSizeLabel(options.imageParams?.imageSize);
// 根据分辨率等级确定基准像素
const baseSizeMap: Record<string, number> = {
@ -385,12 +470,6 @@ export function createAlibabaImage({ apiKey, baseURL }: AlibabaImageConfig) {
// 根据宽高比计算实际分辨率
// 阿里云 API 要求宽高在 512-2048 之间
const calculateSize = (ratio: string, base: number): string | null => {
// 如果是 auto 模式,返回 null,让 API 根据输入图片自动决定
// 根据阿里云文档:默认(不指定时):总像素接近 1024*1024,宽高比与输入图(多图输入时为最后一张)一致
if (ratio === "auto") {
return null;
}
// 解析宽高比
const ratioMap: Record<string, [number, number]> = {
"1:1": [1, 1],
@ -405,70 +484,42 @@ export function createAlibabaImage({ apiKey, baseURL }: AlibabaImageConfig) {
"21:9": [21, 9],
};
const [w, h] = ratioMap[ratio] || [1, 1];
if (!ratioMap[ratio]) return null;
const [w, h] = ratioMap[ratio];
const aspectValue = w / h;
// 计算宽高,保持总像素接近 base*base
const totalPixels = base * base;
// height = sqrt(totalPixels / aspectValue)
// width = height * aspectValue
let height = Math.round(Math.sqrt(totalPixels / aspectValue));
let width = Math.round(height * aspectValue);
// 阿里云 API 限制:宽高在 512-2048 之间
const MIN_SIZE = 512;
const MAX_SIZE = 2048;
// 如果超出范围,按比例缩放,同时保持宽高比
if (width > MAX_SIZE) {
const scale = MAX_SIZE / width;
width = MAX_SIZE;
height = Math.round(height * scale);
}
if (height > MAX_SIZE) {
const scale = MAX_SIZE / height;
height = MAX_SIZE;
width = Math.round(width * scale);
}
if (width < MIN_SIZE) {
const scale = MIN_SIZE / width;
width = MIN_SIZE;
height = Math.round(height * scale);
}
if (height < MIN_SIZE) {
const scale = MIN_SIZE / height;
height = MIN_SIZE;
width = Math.round(width * scale);
}
// 最终 clamp 确保在范围内
width = Math.max(MIN_SIZE, Math.min(MAX_SIZE, width));
height = Math.max(MIN_SIZE, Math.min(MAX_SIZE, height));
// 确保是偶数(某些图像处理可能需要)
width = Math.round(width / 2) * 2;
height = Math.round(height / 2) * 2;
return `${width}*${height}`;
return calculateAlibabaSizeByAspect(aspectValue, base);
};
// 处理 size 参数
// 1. 如果用户直接指定了 size,使用用户指定的
// 2. 如果是 auto 模式,不传 size,让 API 根据输入图片自动决定比例
// 2. 如果是 auto 模式,按参考图宽高比计算 size,确保清晰度档位生效
// 3. 如果指定了具体比例,根据 aspectRatio 和 imageSize 计算
if (options.imageParams?.size) {
params.size = options.imageParams.size;
console.log(`[Alibaba Image] Using user-specified size: ${params.size}`);
} else {
const calculatedSize = calculateSize(aspectRatio, baseSize);
if (calculatedSize) {
params.size = calculatedSize;
console.log(`[Alibaba Image] Calculated size: aspectRatio=${aspectRatio}, imageSize=${imageSize} => size=${params.size}`);
if (aspectRatio === "auto") {
// 阿里文档在 auto 下默认接近 1024*1024,这会让 4K 选择失效。
// 这里改为根据参考图比例计算 size,确保 1K/2K/4K 档位真正生效。
const referenceImageUrl = imageUrls[imageUrls.length - 1];
const inferredAspectRatio = await inferAspectRatioFromImageUrl(referenceImageUrl);
params.size = inferredAspectRatio
? calculateAlibabaSizeByAspect(inferredAspectRatio, baseSize)
: `${baseSize}*${baseSize}`;
console.log(
`[Alibaba Image] Auto mode resolved size: imageSize=${imageSize}, inferredAspect=${inferredAspectRatio || "fallback-square"} => size=${params.size}`
);
} else {
// auto 模式:不设置 size,让 API 根据输入图片自动决定
// 阿里云会保持输入图片的宽高比,总像素接近 1024*1024
console.log(`[Alibaba Image] Auto mode: not setting size, API will use input image's aspect ratio`);
const calculatedSize = calculateSize(aspectRatio, baseSize);
if (calculatedSize) {
params.size = calculatedSize;
console.log(`[Alibaba Image] Calculated size: aspectRatio=${aspectRatio}, imageSize=${imageSize} => size=${params.size}`);
} else {
params.size = `${baseSize}*${baseSize}`;
console.log(
`[Alibaba Image] Unknown aspect ratio "${aspectRatio}", fallback size=${params.size}`
);
}
}
}
@ -536,4 +587,3 @@ export function createAlibabaImage({ apiKey, baseURL }: AlibabaImageConfig) {
}
export default createAlibabaImage;