Skip to content

Add --aspect-ratio for backend-verified image framing - #21

Open
LingMoe404 wants to merge 3 commits into
jdmnk:mainfrom
LingMoe404:feature/aspect-ratio
Open

LingMoe404 wants to merge 3 commits into
jdmnk:mainfrom
LingMoe404:feature/aspect-ratio

Conversation

@LingMoe404

@LingMoe404 LingMoe404 commented Sep 26, 2026 •

Copy link
Copy Markdown

What this changes

Adds --aspect-ratio W:H to generate, edit, and batch.

The tested backend ignores the request's size field when framing an image. A request for --size 1024x1536 comes back square (1254x1254 on my account); --size 1024x1024 and 1536x1024 also both came back square, and --backend responses behaves the same way. What does control the frame is the prompt. --aspect-ratio exposes that directly instead of leaving users to discover it by trial and error.

codex-imagegen generate --prompt "A lighthouse at dusk" --out out.png --aspect-ratio 2:3

The option prepends explicit framing guidance to the prompt and then verifies the returned image against the requested ratio through the existing --size-policy:

The frame must be in 2:3 portrait format, taller than it is wide.

A lighthouse at dusk

Design notes

  • The ratio is prompt guidance, not a disguised size. The size field is left untouched (auto), and --aspect-ratio is mutually exclusive with --size, so the two cannot silently disagree.
  • The instruction leads the prompt. The published GPT-Image2 style-library templates put the ratio constraint before the scene ("must be written first, otherwise the model defaults to a phone 9:16"), and the ratio appears near the start of the prompt in most of that library's 544 cases. I A/B tested the two orders and measured no difference on this backend, including the conflict case of a portrait phone-screenshot prompt asked for 21:9 (both orders returned 1916x821), so the order follows the documented convention rather than a claimed advantage.
  • No resizing or cropping to hide a mismatch — consistent with the project boundary in CONTRIBUTING. When the ratio does not match, --size-policy warn (default) saves the original dimensions and warns; --size-policy error refuses to write.
  • Ratios outside 1:3..3:1 are rejected locally because the backend clamps them (a 4:1 request came back at 2172x724, i.e. 3:1) and would otherwise surface as a confusing mismatch after spending usage.
  • Tolerant to 2%: live runs came back within 0.1% of the requested ratio, while 2% still rejects a genuine framing error (requesting 2:3 and getting 9:16).

Verification

Live checks on the tested backend returned the requested ratio on every attempt, including edit:

Requested Returned Ratio
1:1 1254x1254 1.000
3:4 1086x1448 0.750
2:3 1024x1536 0.667
9:16 941x1672 0.563
1:2 887x1774 0.500
16:9 1672x941 1.777
21:9 1916x821 2.334
3:1 2172x724 3.000

Every row was produced with the --aspect-ratio flag itself. Re-checked after the ordering change with the same results. Returned frames follow the ratio at a roughly constant pixel budget (~1,572,864), which is why 2:3 and 3:2 land on exactly 1024x1536 and 1536x1024. The README documents this and notes that exact pixel sizes still require a local crop or pad, since the backend chooses the final dimensions.

Note --aspect-ratio is a framing control, not an exact-pixel control: it gets the orientation and ratio right, and the table above is what the backend returned on one account rather than a guaranteed mapping.

Tests

44 offline tests in tests/test_aspect_ratio.py covering parsing and limits, mutual exclusion with --size, prompt guidance and instruction ordering, mismatch detection, --size-policy warn/error behaviour (including preserving an existing output), that the size field stays auto, and that batch applies the flag to every job.

All pre-PR checks from CONTRIBUTING pass, on Python 3.10 / 3.12 / 3.14:

uv lock --check                      OK
uv run pytest                        141 passed, 2 skipped
uv run ruff check .                  All checks passed
uv run ruff format --check .         14 files already formatted
uv run python -m build --installer uv OK
uv run python -m twine check dist/*  PASSED

CODEX_IMAGEGEN_LIVE_TEST=1 uv run pytest -m live -v also passes (2 passed). I did not add an aspect-ratio case to the live suite, since CONTRIBUTING describes that suite as three image requests and the new logic is covered offline; live ratio behaviour is reported above instead.

Notes for review

  • --aspect-ratio is CLI-level for batch, matching how --size and --quality already work there; it is not a per-job JSONL field.
  • No changes to auth handling, request signing, or the size field's semantics.
  • Existing --size behaviour is unchanged; the mismatch branch is reused rather than duplicated.

The tested backend ignores the request's size field when framing an image:
a request for 1024x1536 comes back square, and both the native and
Responses paths behave the same way. Prompt wording is what actually
controls the frame, so expose that as a first-class option rather than
leaving users to discover it.

--aspect-ratio W:H appends explicit framing guidance to the prompt and
then verifies the returned image against the requested ratio through the
existing --size-policy. Requests outside 1:3..3:1 are rejected locally
because the backend clamps them and would otherwise surface as a
confusing mismatch.

The size field is left untouched: the ratio is prompt guidance, not a
disguised size. The returned image is never resized or cropped to hide a
mismatch, matching the project's existing boundary.

Covered by 43 offline tests. Live checks returned the requested ratio on
every attempt (2:3 -> 1024x1536, 16:9 -> 1672x941, 21:9 -> 1916x821,
1:1 -> 1254x1254, 9:18 -> 887x1774, 3:1 -> 2172x724), including edit mode.
The previous wording said a request beyond 3:1 "will be reported as a
mismatch", but --aspect-ratio rejects anything outside 1:3..3:1 during
argument parsing, so a 4:1 request never reaches the backend. Describe
what the CLI actually does, and keep the live observation that motivated
the local check.
Framing now precedes the scene description:

    The frame must be in 2:3 portrait format, taller than it is wide.

    A lighthouse at dusk

The published GPT-Image2 style-library templates put the ratio constraint
before the scene ("must be written first, otherwise the model defaults to
a phone 9:16"), and the ratio appears near the start of the prompt in most
of that library's 544 cases. A/B checks showed no behavioural difference
between the two orders on the tested backend, including the conflict case
of a portrait phone-screenshot prompt asked to render at 21:9, so order
follows the documented convention rather than a measured advantage.

Live re-checks after the change still returned the requested ratio on
every attempt (2:3 -> 1024x1536, 21:9 -> 1916x821, 16:9 -> 1672x941,
1:1 -> 1254x1254, and 2:3 on an edit).
foksa added a commit to foksa/codex-img that referenced this pull request Oct 1, 2026
Live tests on the direct route showed the request's size field has no
effect: 1024x1536 and 1536x1024 both came back at 1312x1199. A sentence
stating the ratio at the start of the prompt gave that ratio every time,
on generations and edits (a 2:3 input edited with 16:9 came back 16:9).

-a/--aspect W:H (1:3 to 3:1) leads the prompt with that sentence and
warns when the image comes back more than 2% off. It can't be combined
with --size. batch takes an aspect field; the Python fallback takes the
flag too. The docs now say -s is ignored, that edits reframe with -a,
that ratio prompts come back reported as quality low with fewer image
tokens, and that the model field isn't checked. Idea and pixel counts
from jdmnk/codex-imagegen-cli#21.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sc9Qaa9KWor3Didh9k9sxN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant