## Behavior
A new Cineon Converter opens with Gamma 1.7. Its Version parameter is 2 by default. A converter saved before Version existed opens as version 1, retaining its Gamma value, keyframes, and expression text and rendering on its original scale. Reset keeps the instance's Version.
In version 2:
- Log to Linear raises the exposure to `1 / gamma` before subtracting the black offset and normalizing: `(E^(1/g) - O^(1/g)) / (1 - O^(1/g))`, with `E = 10^((code - white) * 0.002 / 0.6)` and `O` the exposure at the black point. Gamma 1 gives linear output, and a larger Gamma lightens the midtones. Highlight Rolloff then applies as before, and Linear to Log is the inverse.
- Log to Log returns code values unchanged at Gamma 1 and raises the normalized code value to `1 / gamma` at other values. With custom black or white points, the code values first go through the Gamma 1 conversion.
The CPU and GPU paths match. Version 1 instances render as before, and Interpret Footage ▸ Cineon Settings keeps its existing formula, which agrees with the version 2 Converter at Gamma 1.
The public Cineon Converter documentation (https://helpx.adobe.com/after-effects/using/utility-effects.html) describes Gamma as a midtone control and gives no defaults or formula. The default, the Log to Linear curve and the Log to Log behavior come from running and scripting After Effects 26.5 on my own machine and reading back the rendered pixels. In After Effects, choosing Log to Log also sets Gamma to 1 and Highlight Rolloff to 0; EffectCraft does not do that yet, because no command sets one parameter from another.
Animation preset format 3 records `cineonVersion` for a saved Gamma property and stores effect key times relative to the first key. Presets in formats 1 and 2 without that tag contain version 1 Gamma values; format 2 already has relative effect key times. Applying a whole old Cineon Converter also gives it version 1. Exposed Essential Properties Gamma values convert to the target converter version where their target is unambiguous (the conversion keeps the linear point; other Gamma values follow each version's own curve); enabled expressions on another scale are skipped instead of rewriting their source.
A saved Gamma records the Version its converter has at the current time. Applied to a converter whose Version is keyframed, a Gamma preset keeps those keys: the Gamma is converted to the one version they hold, or skipped when they hold several or an expression sets the Version. A preset that also holds the Version sets it from that property.
The project schema remains 2. `docs/architecture.md` describes the behavior above.
## Validation
Effect tests check version 2 Log to Linear at Gamma 1.7 (codes 95 to 685) and Gamma 2.2 (codes 180 to 620), and Log to Log at Gamma 1 (Rolloff 0 and 20) and Gamma 1.7, against the measured 8-bit values within one or two codes, plus round trips, version 1 rendering and the Interpret Footage agreement at Gamma 1. GPU tests compare the version 2 paths with the CPU. Engine tests cover old projects, Reset, presets, keyframes, and Essential Properties.
Commands run on `74d398c2`:
- `cargo fmt --all -- --check`
- `cargo clippy -p effectcraft-effects -p effectcraft-gpu -p effectcraft-project -p effectcraft-engine --all-targets -- -D warnings`
- `cargo test -p effectcraft-effects`: 546 passed (including `effects_doc_is_current`, so `docs/effects.md` needs no change), 1 ignored, plus 6
- `cargo test -p effectcraft-project`: 47 passed
- `cargo test -p effectcraft-gpu`: 253 passed, 1 ignored
- `cargo test -p effectcraft-engine`: 1,007 passed, 5 ignored, plus 27
* Import: VectorCraft documents import through the PDF they carry
File > Import reads a .vectorcraft file through the PDF that VectorCraft
saves in it with Create PDF-Compatible File, one page per artboard, as
footage or as a composition with one layer per PDF layer. The PDF is read
with craft-doc from craft-libs (plain or compressed files, the PDF inline
or after the JSON). A PDF after the JSON may be as large as a PDF file
EffectCraft imports (512 MiB). A document saved without that option gives
an import error that names it.
craft-doc, craft-vectorcraft-format (tests only) and craft-ecproj-format
are pinned to craft-libs fa1a6574; craft-ecproj-format is unchanged
between ca1b5e2e and that commit.
* Import: show file errors returned by the import dialog
* Keep dropped-file import errors alongside local drop errors
* Import: preserve every file error across background jobs and UI notices
* Import: queue pending errors with bounded notification details
* Import: a damaged VectorCraft preview no longer stops the carried PDF
The importer read every rendition with craft_doc::renditions and then
picked the PDF, so a preview whose base64 was invalid or whose data lay
past the end of the file made the whole import fail. It now reads only
the PDF with craft_doc::rendition, from storytold/craft-libs#546, and
never decodes the preview. A document without a PDF still reports
NoCarriedPdf.
* Import: dismissing a picked import's error status acknowledges its details
A picked or menu import queued its error details with the status
report_import_errors set ("first (and 1 more)"), and show_result_errors
then replaced that status with "first; second". The two never matched,
so clicking the status left the details queued and they showed again.
Import commands now move the queued details to the status that replaces
it, also when After Effects project errors lead it. Other commands'
statuses never take over an import's details.
DialogAnswer.paths became a Result in #3304; three import tests merged
since still passed a plain Vec, so the test build of effectcraft-ui-egui
did not compile on main.
paint_pattern draws the tile content as a tree of its own on a fresh tile, so the
plain-clip state inherited from an enclosing group must not apply there: an inner
clip that blends against the tile was drawn as a plain clip and lost its backdrop.
Save and clear SurfaceBudget::plain around the tile draw, as apply_soft_mask does.
Upstream gained region-limited coverage, clip regions, a group nesting cap
in the PDF builder and a render budget. The clipping fast paths (canvas-covering
clips drawn without a surface, plain clip groups on region-sized surfaces) are
ported onto those; the crop-based shape/image/paint changes and the PDF
builder depth cap are superseded by upstream's.
* #3593: render_frame and render_preview tile `times` into one contact sheet
* contributors: credit andreaaazo by their GitHub profile name
* #3593: contact-sheet cells default to 320 px, sheets stay under 8000 px
With `times`, `max_side` now defaults to 320 per cell instead of 960, so
64 frames of a 16:9 comp make a 2560 x 1440 sheet instead of 7680 x 4320.
`sheet::MAX_SIDE` drops from 8192 to 7680. `labels` that isn't true or
false is a bad-arguments error, and a bridged cell keeps the time asked
for when the app's reply leaves `time` out.
* Text animators: fit one animator to a Per-character 3D setting
* Animation presets: save text animators whole and add them to the target layer (#2530)
* Animation presets: a preset that fits no layer is an error, not silence
* Tests: text animators in animation presets (#2530)
* Fix text preset review edge cases
* Keep existing animators consistent when presets turn 3D off
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
After Effects stores a static shape path that an expression draws (a text-box rig writes its
outline from the text's size) with no vertices. The importer dropped the whole path ('its path
has no vertices'), expression and all, so such shapes drew nothing. A path without vertices that
has an expression and no keys now arrives as an empty path with its expression; one without an
expression is still reported.
* Expressions: a text style's justification and direction use After Effects' names
After Effects reads a paragraph's justification as alignLeft, alignCenter,
alignRight, justifyLastLeft, justifyLastCenter, justifyLastRight or
justifyFull, and its direction as left-to-right or right-to-left; its
setters take those names. EffectCraft returned its own keys (left, ltr,
justifyAll...), and the document refused alignLeft/alignCenter/alignRight
and justifyFull. The getters now return After Effects' names and the
parser accepts them; EffectCraft's own names are still accepted.
* Expressions: every accepted justification spelling reads back as After Effects' name
setJustification('CENTER_JUSTIFY').justification returned CENTER_JUSTIFY.
The setter and getter now share one table of the spellings Justify::parse
accepts, looked up as own properties only, so an alias reads back as
alignCenter and an inherited name such as toString is never matched.
After Effects 26.3 numbers MULTIPLE_JUSTIFICATIONS 7412, LEFT_JUSTIFY 7413,
RIGHT_JUSTIFY 7414, CENTER_JUSTIFY 7415, and the justified variants with
the last line left, right, centred and full 7416-7419. EffectCraft had
centre before right (and last-line centre before last-line right), so a
script setting 7414 got a centred paragraph instead of a right-aligned
one, silently.