@astrofoundry/pi-astro 0.6.4 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "./models.ts";
|
|
21
21
|
import { parseTweaks } from "./parseTweaks.ts";
|
|
22
22
|
import { estimateCost, formatUsd, hasPricing } from "./pricing.ts";
|
|
23
|
+
import { formatResolution, resolveResolution } from "./resolution.ts";
|
|
23
24
|
|
|
24
25
|
const DEFAULT_SAVE_SUBDIR = ".gemini-images";
|
|
25
26
|
|
|
@@ -233,12 +234,16 @@ function saveImages(images: DecodedImage[], dir: string, prompt: string): string
|
|
|
233
234
|
|
|
234
235
|
function renderPreview(model: ImageModel, p: Params, estimate: { estimatedUsd: number; breakdown: string }): string {
|
|
235
236
|
const nImages = isImagen(model) ? p.number_of_images ?? 1 : 1;
|
|
237
|
+
const aspect = p.aspect_ratio ?? "1:1";
|
|
238
|
+
const size = p.image_size ?? "1K";
|
|
239
|
+
const resolution = formatResolution(resolveResolution(model, aspect, size));
|
|
236
240
|
const lines: string[] = [];
|
|
237
241
|
lines.push(`Model: ${model}`);
|
|
238
242
|
lines.push(`Prompt: "${p.prompt}"`);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
lines.push(`
|
|
243
|
+
lines.push(`Aspect: ${aspect}${p.aspect_ratio ? "" : " (default)"}`);
|
|
244
|
+
lines.push(`Size: ${size}${p.image_size ? "" : " (default)"}`);
|
|
245
|
+
lines.push(`Resolution: ${resolution}`);
|
|
246
|
+
lines.push(`Images: ${nImages}${p.number_of_images === undefined && isImagen(model) ? " (default)" : ""}`);
|
|
242
247
|
if (p.negative_prompt) lines.push(`Negative: "${p.negative_prompt.slice(0, 80)}"`);
|
|
243
248
|
if (p.seed !== undefined) lines.push(`Seed: ${p.seed}`);
|
|
244
249
|
if (p.person_generation) lines.push(`People: ${p.person_generation}`);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { formatResolution, resolveResolution } from "./resolution.ts";
|
|
3
|
+
|
|
4
|
+
describe("resolveResolution", () => {
|
|
5
|
+
it("defaults to 1:1 1K = 1024×1024", () => {
|
|
6
|
+
const r = resolveResolution("gemini-2.5-flash-image", undefined, undefined);
|
|
7
|
+
expect(r.width).toBe(1024);
|
|
8
|
+
expect(r.height).toBe(1024);
|
|
9
|
+
expect(r.approximate).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("scales 2K as 2x", () => {
|
|
13
|
+
const r = resolveResolution("gemini-2.5-flash-image", "1:1", "2K");
|
|
14
|
+
expect(r.width).toBe(2048);
|
|
15
|
+
expect(r.height).toBe(2048);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("scales 4K as 4x", () => {
|
|
19
|
+
const r = resolveResolution("gemini-3-pro-image-preview", "1:1", "4K");
|
|
20
|
+
expect(r.width).toBe(4096);
|
|
21
|
+
expect(r.height).toBe(4096);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("16:9 at 1K → 1344×768", () => {
|
|
25
|
+
const r = resolveResolution("gemini-2.5-flash-image", "16:9", "1K");
|
|
26
|
+
expect(r.width).toBe(1344);
|
|
27
|
+
expect(r.height).toBe(768);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("21:9 at 1K → 1536×672", () => {
|
|
31
|
+
const r = resolveResolution("gemini-2.5-flash-image", "21:9", "1K");
|
|
32
|
+
expect(r.width).toBe(1536);
|
|
33
|
+
expect(r.height).toBe(672);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("Imagen flagged as approximate", () => {
|
|
37
|
+
const r = resolveResolution("imagen-4.0-generate-001", "1:1", "1K");
|
|
38
|
+
expect(r.approximate).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("unknown aspect falls back to 1:1", () => {
|
|
42
|
+
const r = resolveResolution("gemini-2.5-flash-image", "7:3", "1K");
|
|
43
|
+
expect(r.width).toBe(1024);
|
|
44
|
+
expect(r.height).toBe(1024);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("formatResolution", () => {
|
|
49
|
+
it("exact Gemini is shown without tilde", () => {
|
|
50
|
+
expect(formatResolution({ width: 1024, height: 1024, approximate: false })).toBe("1024×1024");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("approximate Imagen gets tilde prefix", () => {
|
|
54
|
+
expect(formatResolution({ width: 1024, height: 1024, approximate: true })).toBe("~1024×1024");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the expected output resolution for a given (model, aspect_ratio, image_size).
|
|
3
|
+
*
|
|
4
|
+
* Base table is the documented Gemini 2.5 Flash Image matrix. 2K/4K scale the
|
|
5
|
+
* base by 2x/4x. Imagen's published resolutions vary per model and aren't in
|
|
6
|
+
* the public docs at the same granularity, so we use the same base and flag
|
|
7
|
+
* the result as approximate.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isImagen, type ImageModel } from "./models.ts";
|
|
11
|
+
|
|
12
|
+
type AspectRatio =
|
|
13
|
+
| "1:1"
|
|
14
|
+
| "2:3"
|
|
15
|
+
| "3:2"
|
|
16
|
+
| "3:4"
|
|
17
|
+
| "4:3"
|
|
18
|
+
| "4:5"
|
|
19
|
+
| "5:4"
|
|
20
|
+
| "9:16"
|
|
21
|
+
| "16:9"
|
|
22
|
+
| "21:9";
|
|
23
|
+
|
|
24
|
+
// Gemini 2.5 Flash Image 1K dimensions, per ai.google.dev/gemini-api/docs/image-generation.
|
|
25
|
+
const BASE_1K: Record<AspectRatio, [number, number]> = {
|
|
26
|
+
"1:1": [1024, 1024],
|
|
27
|
+
"2:3": [832, 1248],
|
|
28
|
+
"3:2": [1248, 832],
|
|
29
|
+
"3:4": [864, 1184],
|
|
30
|
+
"4:3": [1184, 864],
|
|
31
|
+
"4:5": [896, 1152],
|
|
32
|
+
"5:4": [1152, 896],
|
|
33
|
+
"9:16": [768, 1344],
|
|
34
|
+
"16:9": [1344, 768],
|
|
35
|
+
"21:9": [1536, 672],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export interface ResolutionInfo {
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
approximate: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveResolution(
|
|
45
|
+
model: ImageModel,
|
|
46
|
+
aspectRatio: string | undefined,
|
|
47
|
+
imageSize: "1K" | "2K" | "4K" | undefined,
|
|
48
|
+
): ResolutionInfo {
|
|
49
|
+
const ratio = (aspectRatio ?? "1:1") as AspectRatio;
|
|
50
|
+
const base = BASE_1K[ratio] ?? BASE_1K["1:1"];
|
|
51
|
+
const size = imageSize ?? "1K";
|
|
52
|
+
const multiplier = size === "4K" ? 4 : size === "2K" ? 2 : 1;
|
|
53
|
+
return {
|
|
54
|
+
width: base[0] * multiplier,
|
|
55
|
+
height: base[1] * multiplier,
|
|
56
|
+
approximate: isImagen(model),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatResolution(info: ResolutionInfo): string {
|
|
61
|
+
return `${info.approximate ? "~" : ""}${info.width}×${info.height}`;
|
|
62
|
+
}
|