Skip to content

Fix target_dimensions=0 corrupting pad_to_maximum alignment on the non-driving axis - #56

Open
giardiello wants to merge 4 commits into
ascmitc:hotfix/imaging_fixesfrom
giardiello:hotfix/target-dims-alignment-fix-v2
Open

Fix target_dimensions=0 corrupting pad_to_maximum alignment on the non-driving axis#56
giardiello wants to merge 4 commits into
ascmitc:hotfix/imaging_fixesfrom
giardiello:hotfix/target-dims-alignment-fix-v2

Conversation

@giardiello

Copy link
Copy Markdown

Summary

  • target_dimensions on the axis that does not drive the scale ratio (e.g. height under fit_method: "width") may legitimately be left "not predefined by the user" per spec 7.4.4/7.4.7, calculated by the application instead. The schema had no legal way to express that: dimensions_int requires both width/height to be > 0, so producers are forced to submit an arbitrary placeholder (observed in the wild as 1, since 0 was schema-invalid).
  • That literal placeholder was fed straight into alignment_shift() for pad_to_maximum templates, corrupting the pad/alignment math on the non-driving axis — e.g. TopLeft/BottomLeft pad_to_maximum templates producing large, wrong vertical shifts instead of the expected center-then-align-within-max behavior.
  • Adds a new target_dimensions_int schema def (minimum: 0, only for target_dimensions) so 0 is now the legal, explicit sentinel for "not predefined, calculate automatically." Every other dimensions field (maximum_dimensions, etc.) is untouched and still requires > 0.
  • fdl_template.cpp resolves that 0 sentinel to the scaled, preserve-extended canvas size (scaled_bounding_box) on the non-driving axis before calling alignment_shift(), gated on that axis padding rather than cropping — crop windows (e.g. fill overflowing the non-driving axis) keep target_dimensions fully literal and load-bearing, since alignment there genuinely selects which part of the overflow is visible.
  • Applies identically to fit_all/fill: they're not a distinct sizing model, just an automatic choice of which axis drives the scale (reusing calculate_scale_ratio()'s already-computed result rather than re-deriving it).
  • Any nonzero target_dimensions value continues to be used exactly as authored — this only changes behavior for the explicit 0 sentinel.

Test plan

  • ctest in native/core/build: 134/134 passed (regenerated template_vectors.json for the new behavior)
  • pytest native/bindings/python/tests: 457 passed, 4 skipped
  • pytest packages/fdl_imaging/tests: 59/59 passed
  • Regenerated official Scen_16/Scen_17/Scen_32 Results/*.fdl + *.exr goldens (self-generated by this same reference implementation, so regeneration after an intentional behavior change is expected)
  • Manually rendered a 12-case sweep of fit_method (width/fit_all) × pad_to_maximum × target height (0, 1, 20, 4000, ...) × alignment (top/center/bottom) confirming: target=0 centers correctly; nonzero targets (including small ones like 20) are respected as literal virtual boundaries that alignment flushes against; fit_all and explicit width produce identical results once the driving axis matches.

Per spec 7.4.7, for fit_method "width"/"height" the non-fit axis of
target_dimensions "should be automatically calculated by the
application and not predefined by the user." The reference
implementation ignored this for the alignment/pad step: it fed the
raw, literal target_dimensions value for that axis straight into
fdl_alignment_shift() unconditionally, so any producer submitting a
placeholder (0, 1, or any non-matching value) for "the axis I'm not
fitting by" corrupted alignment/padding whenever alignment_method
wasn't "center" (center algebraically cancels the term already).

Resolve that axis to the actual output extent (out_w/out_h) before
alignment_shift runs, except when preserve_from_source_canvas
diverges from fit_source — per spec 7.4.9, in that case
target_dimensions is a genuine, load-bearing relative-target
rectangle on both axes, not a placeholder, and must be used as
literally authored (this preserves Scen_32-shape templates, e.g.
"eff_tl_to_br", where target intentionally differs from
maximum_dimensions to control how much preserved padding is added).

Verified: 134/134 native CTest, 457/457 Python binding tests
(including all 53 official Scenarios_For_Implementers parameterized
cases), 59/59 fdl_imaging pixel-level render tests all still pass.
Additionally confirmed a zero/placeholder target_dimensions height
(fit_method=width, non-center alignment, pad_to_maximum=true) now
produces byte-identical output to a correctly-authored control,
matching independently-verified correct behavior (FLAPI's engine).
…out_w/out_h

Correction to the previous commit. Per spec 7.4.11, pad_to_maximum always
center-aligns the "virtual" target rectangle within maximum_dimensions,
and per 7.4.8 alignment_method only positions fit_source *inside* that
virtual rectangle — it does not move the virtual rectangle itself against
the padded canvas edge. My first fix incorrectly resolved the non-fit axis
to the padded output size (out_w/out_h), which made alignment_method
behave as if it aligned against the outer canvas edge — wrong per the
literal spec text, and it required a special-case carve-out for templates
where preserve_from_source_canvas diverges from fit_source (e.g. Scen_32)
to avoid regressing them.

The correct "virtual" size for the non-fit axis is the scaled,
preserve-extended canvas immediately after scaling and before padding —
already captured in this function as `scaled_bounding_box`
(== geometry.canvas_dims post-scale, pre-pad). It equals the scaled
fit_source size when preserve is absent/non-diverging, and the larger
preserve-extended size when it diverges (spec 7.4.9), so no case-split is
needed: the same one-line resolution is correct for every
preserve/fit_source combination.

Practical effect: for fit_method width/height with a non-center
alignment_method and pad_to_maximum=true, alignment_method now has no
effect at all unless preserve_from_source_canvas genuinely extends beyond
fit_source (the only way a real gap exists between the virtual rectangle
and the fit content) — content is otherwise always centered within
maximum_dimensions, regardless of a placeholder or literal value in
target_dimensions on that axis. This matches FLAPI's independently-computed
behavior exactly, verified for Scen_16, Scen_17, and Scen_32 templates.

Regenerated (all self-referential / non-independently-verified, produced
by calling this same reference implementation):
 - native/core/tests/vectors/template/template_vectors.json
   (left_top_alignment, right_bottom_alignment)
 - resources/FDL/Scenarios_For_Implementers/Scen_{16,17}.../Results/*
   (A/B/C variants)
 - resources/FDL/Scenarios_For_Implementers/Scen_32.../Results/*
   (Scen_32's expected fixture actually lives under
   resources/FDL/EdgeCases/alignment_combos/ — a Netflix-fork addition,
   not an ASC-upstream artifact — only its Results/ output is filed under
   the Scenarios_For_Implementers/Scen_32... folder)

Verified: 134/134 native CTest, 457/457 Python binding tests (including
all 53 parameterized scenarios), 59/59 fdl_imaging pixel-level render
tests all pass against the regenerated goldens.
fit_all/fill are not a distinct sizing model, just an automatic choice of
which axis drives the scale ratio (calculate_scale_ratio's w_ratio/h_ratio
comparison). Once that axis is chosen, the other axis's target_dimensions
should be exactly as non-authoritative as it would be had the user picked
that axis's fit_method explicitly — so the target_dims_resolved logic from
the previous commit needed to apply there too, not just to explicit
width/height.

However, naively applying it unconditionally broke fill's crop-alignment
behavior (Scen_12/13 and camera variants): fill deliberately overflows the
non-driving axis past target_dimensions, and that scenario is a genuine
crop, not a pad. In a crop, target_dimensions is the real, load-bearing
crop-window size — alignment_method determines which portion of the
overflow is kept — so replacing it with the (overflowing) scaled size
collapses the gap to zero and silently disables alignment for e.g.
"right"/"bottom" (they degenerate to flush-left/top).

Fix: only resolve the non-driving axis to scaled_bounding_box when that
axis is NOT overflowing maximum_dimensions (i.e. it would pad, or need no
adjustment) — leave it as the literal, authored value when it overflows
(i.e. it would crop). This is symmetric with fit_method width/height too:
an explicit width/height fit can also cause the other axis to overflow
maximum_dimensions given a large enough preserve_from_source_canvas
extension, and that case needs the same literal-value treatment.

Verified: 134/134 CTest, 457/457 Python binding tests (all scenarios,
including fill/fit_all crop cases restored to their original correct
behavior with no golden regeneration needed, and Scen_16/17/32's
padding-case fix from the previous commit unaffected).
…etection

Make target_dimensions.{width,height} == 0 a schema-legal sentinel for
"not predefined by the user, calculated by the application" (spec
7.4.4/7.4.7), instead of the schema forbidding 0 outright. A new
target_dimensions_int def (minimum: 0) replaces the shared dimensions_int
(exclusiveMinimum: 0) for target_dimensions only; every other dimensions
field is untouched.

fdl_template.cpp resolves the 0 sentinel to the scaled, preserve-extended
canvas size (scaled_bounding_box) on the non-driving axis before calling
alignment_shift(), gated on that axis padding rather than cropping (crop
windows keep target_dimensions literal and load-bearing). Nonzero values
are always used literally.

Also simplifies width_drives_scale to reuse calculate_scale_ratio()'s
already-computed result for fit_all/fill instead of re-deriving the
w_ratio/h_ratio comparison.

Regenerates template_vectors.json and the official Scen_16/17/32 goldens,
which are self-generated by this same reference implementation.
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