fix(image): 图片生成 - 打通分辨率选择并补充回归测试
This commit is contained in:
@@ -516,6 +516,57 @@ func TestParameterizedOpenAIAspectPresetSystemChannelPersistsPixelExecutionSize(
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameterizedOpenAIPixelSizeSelectionAcceptsDeclaredSizeAndResolution(t *testing.T) {
|
||||
profile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceOpenAIImage), "GPT Image 2.5")
|
||||
minCount, maxCount := 1.0, 5.0
|
||||
profile.Image.Parameters = []protocol.ImageParameter{
|
||||
{
|
||||
Name: "size", Label: "画幅 / 尺寸", Type: "string",
|
||||
Values: []any{"auto", "1824x1024", "2752x1536", "3840x2160"}, Default: "1824x1024",
|
||||
Pricing: true, Binding: "aspectRatio", Encoding: "pixels",
|
||||
},
|
||||
{
|
||||
Name: "imageResolution", Label: "分辨率", Type: "string",
|
||||
Values: []any{"1k", "2k", "4k"}, Default: "1k", Pricing: true, Binding: "resolution",
|
||||
},
|
||||
{Name: "count", Label: "生成数量", Type: "integer", Min: &minCount, Max: &maxCount, Default: 1, Pricing: true, Binding: "imageCount"},
|
||||
{Name: "transparentBackground", Label: "透明背景", Type: "boolean", Default: false, Binding: "transparentBackground"},
|
||||
}
|
||||
profile.Image.Size = ImageSizeConfig{
|
||||
Parameter: "size", Values: []string{"auto", "1824x1024", "2752x1536", "3840x2160"}, Default: "1824x1024",
|
||||
Presets: []ImageSizePreset{
|
||||
{Tier: "1k", Ratio: "16:9", Size: "1824x1024", Width: 1824, Height: 1024},
|
||||
{Tier: "2k", Ratio: "16:9", Size: "2752x1536", Width: 2752, Height: 1536},
|
||||
{Tier: "4k", Ratio: "16:9", Size: "3840x2160", Width: 3840, Height: 2160},
|
||||
},
|
||||
}
|
||||
if err := projectImageParameters(profile.Image); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc, _, channel, channelModel := createSystemChannelSelectionFixture(t, "image", model.ChannelInterfaceOpenAIImage, profile, []model.ChannelModelPriceTier{
|
||||
newSelectionPriceTier("tier-1k", `{"imageResolution":"1k"}`, "provider-1k", "fixed_request"),
|
||||
newSelectionPriceTier("tier-2k", `{"imageResolution":"2k"}`, "provider-2k", "fixed_request"),
|
||||
newSelectionPriceTier("tier-4k", `{"imageResolution":"4k"}`, "provider-4k", "fixed_request"),
|
||||
})
|
||||
input := map[string]any{
|
||||
"mode": "image",
|
||||
"config": map[string]any{
|
||||
"channelId": channel.ID, "model": channelModel.ModelKey,
|
||||
"size": "1824x1024", "imageResolution": "1k",
|
||||
"imageParameters": map[string]any{"size": "1824x1024", "imageResolution": "1k", "count": 1, "transparentBackground": false},
|
||||
},
|
||||
}
|
||||
resolved, err := svc.resolveSystemChannelModelSelection(input, "canvas_image", "")
|
||||
if err != nil {
|
||||
t.Fatalf("resolve error = %v", err)
|
||||
}
|
||||
config := resolved["config"].(map[string]any)
|
||||
parameters := config["imageParameters"].(map[string]any)
|
||||
if parameters["size"] != "1824x1024" || parameters["imageResolution"] != "1k" || config["priceTierId"] != "tier-1k" {
|
||||
t.Fatalf("resolved parameters = %#v, config = %#v", parameters, config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIAspectPresetSystemChannelPersistsPixelExecutionSize(t *testing.T) {
|
||||
profile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceOpenAIImage), "gpt-image-2.5-4k")
|
||||
profile.Image.Size = ImageSizeConfig{
|
||||
|
||||
@@ -356,12 +356,16 @@ func publicWorkbenchParameters(kind string, snapshot, input map[string]any) map[
|
||||
if kind == model.WorkbenchKindImage {
|
||||
copyWorkbenchTextParameter(result, "size", parameters["size"])
|
||||
copyWorkbenchTextParameter(result, "quality", parameters["quality"])
|
||||
copyWorkbenchTextParameter(result, "imageResolution", parameters["imageResolution"])
|
||||
if result["size"] == nil {
|
||||
copyWorkbenchTextParameter(result, "size", config["size"])
|
||||
}
|
||||
if result["quality"] == nil {
|
||||
copyWorkbenchTextParameter(result, "quality", config["quality"])
|
||||
}
|
||||
if result["imageResolution"] == nil {
|
||||
copyWorkbenchTextParameter(result, "imageResolution", config["imageResolution"])
|
||||
}
|
||||
if value, ok := workbenchInteger(parameters["count"], 20); ok {
|
||||
result["count"] = value
|
||||
} else if value, ok := workbenchInteger(config["count"], 20); ok {
|
||||
|
||||
@@ -59,6 +59,22 @@ func TestTaskOutputIncludesOnlySafeWorkbenchRestoreData(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskOutputRestoresWorkbenchImageResolution(t *testing.T) {
|
||||
task := model.Task{InputJSON: `{"mode":"image","metadata":{"source":"workbench","workbenchKind":"image","workbench":{"kind":"image","parameters":{"size":"1824x1024","imageResolution":"2k","quality":"auto","count":1}}},"config":{"size":"1824x1024","imageResolution":"2k","quality":"auto","count":"1"}}`}
|
||||
var output map[string]any
|
||||
if err := json.Unmarshal([]byte(publicTaskInputJSON(task.InputJSON)), &output); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
workbench, ok := output["workbench"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("workbench restore data missing: %#v", output)
|
||||
}
|
||||
parameters, ok := workbench["parameters"].(map[string]any)
|
||||
if !ok || parameters["imageResolution"] != "2k" {
|
||||
t.Fatalf("image resolution not preserved for restore: %#v", workbench)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskMediaPreviewUsesSafeMediaURLs(t *testing.T) {
|
||||
previewURL, previewKind := taskMediaPreview(`{"images":["data:image/png;base64,AAAA","/api/resources/resource-1/file"],"video":"https://cdn.example.com/output.mp4"}`, "video")
|
||||
if previewURL != "/api/resources/resource-1/file" || previewKind != "image" {
|
||||
|
||||
@@ -152,7 +152,7 @@ func (s *Service) WorkbenchTasks(userID, kind string, activeOnly bool, limit int
|
||||
func (s *Service) resolveWorkbenchProviderConfig(kind, modelName, logicalModelID, channelID string, parameters map[string]any, input map[string]any) (map[string]any, error) {
|
||||
config := map[string]any{"mode": kind}
|
||||
if kind == WorkbenchKindImage {
|
||||
for _, key := range []string{"size", "quality", "count"} {
|
||||
for _, key := range []string{"size", "quality", "imageResolution", "count"} {
|
||||
if value, exists := parameters[key]; exists {
|
||||
config[key] = providerConfigOptionValue(value)
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ func TestResolveWorkbenchProviderConfigMapsWorkbenchParameters(t *testing.T) {
|
||||
service := &Service{}
|
||||
|
||||
image, err := service.resolveWorkbenchProviderConfig(WorkbenchKindImage, "image-model", "logical-image", "", map[string]any{
|
||||
"size": "1024x1024", "quality": "high", "count": 2,
|
||||
"size": "1024x1024", "quality": "high", "imageResolution": "2k", "count": 2,
|
||||
}, map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, want := range map[string]any{"model": "image-model", "size": "1024x1024", "quality": "high", "count": "2"} {
|
||||
for key, want := range map[string]any{"model": "image-model", "size": "1024x1024", "quality": "high", "imageResolution": "2k", "count": "2"} {
|
||||
if image[key] != want {
|
||||
t.Errorf("image config[%q] = %#v, want %#v", key, image[key], want)
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ func TestWorkbenchTaskRejectsUnknownParametersAndClientConfig(t *testing.T) {
|
||||
|
||||
func TestWorkbenchTaskCanBeCreatedWithoutTool(t *testing.T) {
|
||||
s, _, _ := newWorkbenchTest(t)
|
||||
request := TaskInput{Kind: KindImage, Prompt: "A glass greenhouse at sunrise", Model: "image-model", LogicalModelID: "logical", Parameters: map[string]any{"size": "1024x1024", "count": 1}}
|
||||
request := TaskInput{Kind: KindImage, Prompt: "A glass greenhouse at sunrise", Model: "image-model", LogicalModelID: "logical", Parameters: map[string]any{"size": "1024x1024", "imageResolution": "2k", "count": 1}}
|
||||
resolved, err := s.ResolveTask("user", request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -174,7 +174,8 @@ func TestWorkbenchTaskCanBeCreatedWithoutTool(t *testing.T) {
|
||||
t.Fatalf("tool-less task contains a tool id: %#v", metadata)
|
||||
}
|
||||
snapshot := metadata["workbench"].(map[string]any)
|
||||
if snapshot["kind"] != KindImage || snapshot["parameters"].(map[string]any)["count"] != 1 {
|
||||
parameters := snapshot["parameters"].(map[string]any)
|
||||
if snapshot["kind"] != KindImage || parameters["count"] != 1 || parameters["imageResolution"] != "2k" {
|
||||
t.Fatalf("workbench parameters not snapshotted: %#v", snapshot)
|
||||
}
|
||||
if resolved.TaskType != "canvas_image" || resolved.Operation != "image" {
|
||||
|
||||
@@ -194,7 +194,7 @@ func (s *Service) ResolveTask(userID string, req TaskInput) (*TaskResolution, er
|
||||
|
||||
func normalizeTaskParameters(kind string, params map[string]any) (map[string]any, error) {
|
||||
allowed := map[string]map[string]bool{
|
||||
KindImage: {"size": true, "quality": true, "count": true},
|
||||
KindImage: {"size": true, "quality": true, "imageResolution": true, "count": true},
|
||||
KindVideo: {"seconds": true, "ratio": true, "resolution": true, "generateAudio": true, "watermark": true},
|
||||
}[kind]
|
||||
if allowed == nil {
|
||||
@@ -206,7 +206,7 @@ func normalizeTaskParameters(kind string, params map[string]any) (map[string]any
|
||||
return nil, kernel.BadAuthRequest("生成参数包含不支持字段: " + key)
|
||||
}
|
||||
switch key {
|
||||
case "size", "quality", "ratio", "resolution":
|
||||
case "size", "quality", "imageResolution", "ratio", "resolution":
|
||||
text, ok := value.(string)
|
||||
text = strings.TrimSpace(text)
|
||||
if !ok || len(text) > 64 || strings.Contains(text, "://") || strings.HasPrefix(strings.ToLower(text), "data:") {
|
||||
|
||||
@@ -210,6 +210,7 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
const [settingsReady, setSettingsReady] = useState(false);
|
||||
const [ratio, setRatio] = useState(kind === "image" ? "1:1" : "16:9");
|
||||
const [quality, setQuality] = useState("auto");
|
||||
const [imageResolution, setImageResolution] = useState("auto");
|
||||
const [count, setCount] = useState("1");
|
||||
const [seconds, setSeconds] = useState("6");
|
||||
const [videoQuality, setVideoQuality] = useState("720");
|
||||
@@ -246,8 +247,8 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
[references],
|
||||
);
|
||||
const composerConfig = useMemo(
|
||||
() => (kind === "video" && selectedModel ? creationVideoConfig(config, selectedModel, { ratio, seconds, videoQuality }) : { ...config, model: selectedModel, imageModel: selectedModel, size: ratio, quality, count }),
|
||||
[config, count, kind, quality, ratio, seconds, selectedModel, videoQuality],
|
||||
() => (kind === "video" && selectedModel ? creationVideoConfig(config, selectedModel, { ratio, seconds, videoQuality }) : { ...config, model: selectedModel, imageModel: selectedModel, size: ratio, quality, imageResolution, count }),
|
||||
[config, count, imageResolution, kind, quality, ratio, seconds, selectedModel, videoQuality],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -281,10 +282,12 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
const normalized = normalizeImageValue(imageProfile, {
|
||||
size: saved.image?.ratio || imageProfile.size.default,
|
||||
quality: saved.image?.quality || imageProfile.quality.default,
|
||||
imageResolution: saved.image?.imageResolution || imageProfile.resolution.default,
|
||||
count: saved.image?.count || "1",
|
||||
});
|
||||
setRatio(normalized.size);
|
||||
setQuality(normalized.quality);
|
||||
setImageResolution(normalized.imageResolution || imageProfile.resolution.default || "auto");
|
||||
setCount(normalized.count);
|
||||
} else {
|
||||
const normalized = normalizeVideoValue(videoProfile, {
|
||||
@@ -297,7 +300,7 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
setVideoQuality(normalized.resolution.replace(/p$/i, ""));
|
||||
}
|
||||
setSettingsReady(true);
|
||||
}, [imageProfile, kind, preferencesHydrated, settingsReady, videoProfile]);
|
||||
}, [imageProfile, kind, preferencesHydrated, selectedModel, settingsReady, videoProfile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!settingsReady || kind !== "image" || !imageProfile) return;
|
||||
@@ -305,10 +308,12 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
const normalized = normalizeImageValue(imageProfile, {
|
||||
size: saved?.ratio || imageProfile.size.default,
|
||||
quality: saved?.quality || imageProfile.quality.default,
|
||||
imageResolution: saved?.imageResolution || imageProfile.resolution.default,
|
||||
count: saved?.count || count,
|
||||
});
|
||||
setRatio(normalized.size);
|
||||
setQuality(normalized.quality);
|
||||
setImageResolution(normalized.imageResolution || imageProfile.resolution.default || "auto");
|
||||
setCount(normalized.count);
|
||||
}, [imageProfile, kind, selectedModel, settingsReady]);
|
||||
|
||||
@@ -335,9 +340,9 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
audioCount: references.filter((reference) => reference.kind === "audio").length,
|
||||
characterCount: 0,
|
||||
},
|
||||
...(kind === "image" ? { imageSize: ratio, options: { size: ratio, quality, count: selectedCount } } : { videoSeconds: seconds, options: { size: ratio, videoSeconds: Number(seconds), vquality: videoQuality } }),
|
||||
...(kind === "image" ? { imageSize: ratio, options: { size: ratio, quality, ...(imageProfile?.resolution.supported ? { imageResolution } : {}), count: selectedCount } } : { videoSeconds: seconds, options: { size: ratio, videoSeconds: Number(seconds), vquality: videoQuality } }),
|
||||
}),
|
||||
[capability, kind, prompt, quality, ratio, references, seconds, selectedCount, videoQuality],
|
||||
[capability, imageProfile, imageResolution, kind, prompt, quality, ratio, references, seconds, selectedCount, videoQuality],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -447,6 +452,7 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
setCount("1");
|
||||
setRatio(kind === "image" ? imageProfile?.size.default || "1:1" : videoProfile?.defaultRatio || "16:9");
|
||||
setQuality(imageProfile?.quality.default || "auto");
|
||||
setImageResolution(imageProfile?.resolution.default || "auto");
|
||||
setSeconds(String(videoProfile?.duration.default || 6));
|
||||
setVideoQuality((videoProfile?.defaultResolution || "720p").replace(/p$/i, ""));
|
||||
const params = new URLSearchParams(searchParams);
|
||||
@@ -496,13 +502,17 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
setPresetPending(null);
|
||||
};
|
||||
|
||||
const updateImageSettings = (size: string, nextQuality?: string) => {
|
||||
const updateImageSettings = (size: string, nextQuality?: string, nextResolution?: string) => {
|
||||
setRatio(size);
|
||||
rememberImageSettings({ ratio: size });
|
||||
if (nextQuality) {
|
||||
setQuality(nextQuality);
|
||||
rememberImageSettings({ quality: nextQuality });
|
||||
}
|
||||
if (nextResolution) {
|
||||
setImageResolution(nextResolution);
|
||||
rememberImageSettings({ imageResolution: nextResolution });
|
||||
}
|
||||
};
|
||||
|
||||
const addFiles = async (files: FileList | File[], targetSlotKey = uploadSlotKey) => {
|
||||
@@ -582,10 +592,10 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
const imageRequest = kind === "image" && imageProfile ? normalizeImageValue(imageProfile, { size: ratio, quality, count, transparentBackground: config.transparentBackground }) : null;
|
||||
const imageRequest = kind === "image" && imageProfile ? normalizeImageValue(imageProfile, { size: ratio, quality, imageResolution, imageParameters: config.imageParameters, count, transparentBackground: config.transparentBackground }) : null;
|
||||
const videoRequest = kind === "video" && videoProfile ? normalizeVideoValue(videoProfile, { seconds, ratio, resolution: videoQuality }) : null;
|
||||
const parameters = imageRequest
|
||||
? { size: imageRequest.size, quality: imageRequest.quality, count: Number(imageRequest.count) }
|
||||
? { size: imageRequest.size, quality: imageRequest.quality, imageResolution: imageRequest.imageResolution, count: Number(imageRequest.count) }
|
||||
: {
|
||||
seconds: Number(videoRequest?.seconds || seconds),
|
||||
ratio: videoRequest?.ratio || ratio,
|
||||
@@ -596,7 +606,7 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
const generationConfig = {
|
||||
...composerConfig,
|
||||
model: selectedModel,
|
||||
...(imageRequest ? { imageModel: selectedModel, size: imageRequest.size, quality: imageRequest.quality, count: imageRequest.count } : {}),
|
||||
...(imageRequest ? { imageModel: selectedModel, size: imageRequest.size, quality: imageRequest.quality, imageResolution: imageRequest.imageResolution, count: imageRequest.count } : {}),
|
||||
...(videoRequest ? { videoModel: selectedModel, size: videoRequest.ratio, videoSeconds: videoRequest.seconds, vquality: videoRequest.resolution.replace(/p$/i, "") } : {}),
|
||||
};
|
||||
const readyReferences = references.filter((reference) => reference.resourceId);
|
||||
@@ -698,11 +708,13 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
if (kind === "image") {
|
||||
const restoredRatio = typeof parameters.size === "string" ? parameters.size : ratio;
|
||||
const restoredQuality = typeof parameters.quality === "string" ? parameters.quality : quality;
|
||||
const restoredResolution = typeof parameters.imageResolution === "string" ? parameters.imageResolution : imageResolution;
|
||||
const restoredCount = parameters.count != null ? String(parameters.count) : count;
|
||||
setRatio(restoredRatio);
|
||||
setQuality(restoredQuality);
|
||||
setImageResolution(restoredResolution);
|
||||
setCount(restoredCount);
|
||||
rememberImageSettings({ ratio: restoredRatio, quality: restoredQuality, count: restoredCount });
|
||||
rememberImageSettings({ ratio: restoredRatio, quality: restoredQuality, imageResolution: restoredResolution, count: restoredCount });
|
||||
} else {
|
||||
const restoredSeconds = parameters.seconds != null ? String(parameters.seconds) : seconds;
|
||||
const restoredRatio = typeof parameters.ratio === "string" ? parameters.ratio : ratio;
|
||||
@@ -812,7 +824,7 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
const settingsPanel =
|
||||
kind === "image" && imageProfile ? (
|
||||
<div className="workbench-settings-content">
|
||||
<ImageSizePicker profile={imageProfile} size={ratio} quality={quality} onChange={updateImageSettings} />
|
||||
<ImageSizePicker profile={imageProfile} size={ratio} quality={quality} imageResolution={imageResolution} onChange={updateImageSettings} />
|
||||
{imageProfile.quality.supported && !imageResolutionUsesQuality(imageProfile) ? (
|
||||
<label className="workbench-setting-row">
|
||||
<span>图片质量</span>
|
||||
@@ -1190,7 +1202,7 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
<section className="workbench-parameters" aria-label="生成参数">
|
||||
<header>
|
||||
<strong>{kind === "image" ? "图片参数" : "视频参数"}</strong>
|
||||
<span>{kind === "image" ? "尺寸、质量和数量" : "时长、画幅、清晰度和音频"}</span>
|
||||
<span>{kind === "image" ? "尺寸、分辨率、质量和数量" : "时长、画幅、清晰度和音频"}</span>
|
||||
</header>
|
||||
{selectedModel ? settingsPanel : <p className="workbench-list-empty">先选择模型,再调整尺寸、数量或时长。</p>}
|
||||
</section>
|
||||
@@ -1210,7 +1222,7 @@ export function WorkbenchPage({ kind }: { kind: WorkbenchKind }) {
|
||||
audioCount: references.filter((reference) => reference.kind === "audio").length,
|
||||
characterCount: 0,
|
||||
},
|
||||
...(kind === "image" ? { imageSize: ratio, options: { size: ratio, quality, count: selectedCount } } : { videoSeconds: seconds, options: { size: ratio, videoSeconds: Number(seconds), vquality: videoQuality } }),
|
||||
...(kind === "image" ? { imageSize: ratio, options: { size: ratio, quality, ...(imageProfile?.resolution.supported ? { imageResolution } : {}), count: selectedCount } } : { videoSeconds: seconds, options: { size: ratio, videoSeconds: Number(seconds), vquality: videoQuality } }),
|
||||
}}
|
||||
variant="creation"
|
||||
fullWidth
|
||||
|
||||
@@ -83,6 +83,27 @@ describe("image resolution tiers", () => {
|
||||
expect(profile.references.maskSupported).toBe(false);
|
||||
});
|
||||
|
||||
test("声明式图片模型保留独立分辨率选择", () => {
|
||||
const profile = defaultImageCapabilityConfig("openai-image", "gpt-image-2.5");
|
||||
profile.parameters = [
|
||||
{ name: "size", label: "画幅 / 尺寸", type: "string", values: ["auto", "1824x1024", "2752x1536"], default: "1824x1024", encoding: "pixels" },
|
||||
{ name: "imageResolution", label: "分辨率", type: "string", values: ["1k", "2k"], default: "1k" },
|
||||
];
|
||||
profile.resolution = { supported: true, values: ["1k", "2k"], default: "1k" };
|
||||
profile.size = {
|
||||
parameter: "size",
|
||||
values: ["auto", "1824x1024", "2752x1536"],
|
||||
default: "1824x1024",
|
||||
allowCustom: false,
|
||||
presets: [
|
||||
{ tier: "1k", ratio: "16:9", size: "1824x1024", width: 1824, height: 1024 },
|
||||
{ tier: "2k", ratio: "16:9", size: "2752x1536", width: 2752, height: 1536 },
|
||||
],
|
||||
};
|
||||
|
||||
expect(normalizeImageValue(profile, { size: "2752x1536", imageResolution: "2k" })).toMatchObject({ size: "2752x1536", imageResolution: "2k" });
|
||||
});
|
||||
|
||||
test("识别适合全景图的 2:1 自定义尺寸", () => {
|
||||
const options = buildImageResolutionOptions(["1440x720", "2048x1024", "3840x1920"]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user