fix: enforce imageSize in alibaba auto aspect mode
This commit is contained in:
parent
64502c9d4b
commit
9623f5365b
@ -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;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user