Skip to content

fix: Bevel highlight direction back to v1.0.2 algorithm; keep old files rendering unchanged - #3784

Open
036006 wants to merge 13 commits into
synfig:masterfrom
036006:fix_bevel_angle
Open

036006 wants to merge 13 commits into
synfig:masterfrom
036006:fix_bevel_angle

Conversation

@036006

@036006 036006 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #3769

The Bevel layer renders its highlight (Hi-Color) rotated ~30° CW compared to Synfig 1.0.2 — and has done so since ~v1.3.11 (bisect in #3769). This PR restores the original 1.0.2-era algorithm as Bevel v0.4, fixes two genuinely Cobra-era indexing defects, and keeps old project files rendering exactly as before.

Part 1. Bevel algorithm fixes (bevel.cpp → v0.4)

1. Highlight direction rotated ~30° CW (the main issue, #3769) — long-standing, not Cobra-era

  • Symptom: the highlight is rotated ~27–37° CW compared to Synfig 1.0.2, in old-renderer and Cobra builds alike.
  • Mechanism: the 6-tap gradient estimate takes central differences along d0 (angle a), d1 (a−45°) and d2 (perpendicular). As written, the two d2 taps make the sampling fan asymmetric — {θ−135°, θ−45°, θ} instead of {θ−45°, θ, θ+45°} — skewing the estimated bevel axis by ~30°.
  • Fix: swapped +=/-= on the two d2 lines, restoring the symmetric fan. Verified by the render matrix above: v0.4 matches 1.0.2.
  • Note: the d2 lines are textually unchanged since 1.0.2, so the regression's exact origin between 1.3.10 and 1.3.11 is not in the layer itself (no semantic changes in that window in bevel.cpp, blur.cpp, or renddesc). The render matrix is the ground truth this fix is validated against. (Commit message "correct highlight direction after Cobra port" misattributes the origin — the port only preserved the pre-existing behavior.)

2. Wrong indexing with target_min (Cobra-era)

  • Symptom: the bevel drifts or distorts in tiled rendering and for layers with shifted bounds.
  • Cause: TaskBevelSW::run started its loop at u = halfsizex + abs(offset_u) + target_min[0] (same for v). But blurred is indexed from zero relative to the expanded work surface, while target_min is the absolute origin of the target rectangle. The correct start at ix = target_min[0] is exactly halfsizex + abs(offset_u) — without target_min. The bug is invisible when target_rect.min == 0 (single full-canvas render), which is why it slipped through.
  • Fix: removed + target_min[0] / + target_min[1] from the u/v initialization.

3. Half-size mismatch for GAUSSIAN blur (Cobra-era)

  • Symptom: with Blur::GAUSSIAN the bevel was computed with wrong geometry.
  • Cause: set_coords_sub_tasks sized the buffer with the GAUSSIAN_ADJUSTMENT formula, but run() indexed it with the generic fabs(size*.5/pw)+3 formula. The half-size logic was duplicated in two places and drifted apart. (The default FASTGAUSSIAN type was unaffected — the formulas match there.)
  • Fix: extracted one helper bevel_calc_halfsize() used both to size the buffer and to index it. No duplication left, so they cannot drift again.

Part 2. Backward compatibility (loadcanvas.cpp + bevel_deprecated)

  • The 1.3.x–1.5.5 rendering is what existing artwork actually looks like, so it is preserved as a new layer type bevel_deprecated (an unchanged copy of the v0.3 layer); Bevel itself is bumped to v0.4 with the 1.0.2-era algorithm.
  • Migration on load: CanvasParser::parse_layer normalizes the layer type in one block (following the filled_rectanglerectangle pattern). A bevel of version 0.2–0.3 on canvas ≥ 1.1 is loaded as bevel_deprecated, preserving its old render result; otherwise it loads as the new bevel. This implements the plan from Bevel Layer renders incorrectly comparing to version 1.0.2 #3769: canvas ≥ 1.1 ⟺ Synfig ≥ 1.3.12, i.e. the era with the rotated highlight.
  • bevel_deprecated is hidden from the New Layer menu (CATEGORY_DO_NOT_USE, same as svg_layer) and from the CLI layer list, while remaining instantiable by name for loading and for the Upgrade action.
  • A warning is shown, mentioning that the layer can be updated via "Upgrade Layer" in the layer context menu.

Part 3. New "Upgrade Layer" action (synfig-studio)

  • A generic context-menu action (task upgrade), driven by a data map deprecated → current (currently bevel_deprecatedbevel). Adding a new pair is one line in get_target_layer_name().
  • Behavior: appears in the layer context menus when all selected layers are in the map. Carries over parameters (set_param_list — the vocab is identical), description (user-set only; a default one falls back to the new layer's name), active state, exclude_from_rendering, group membership (Canvas::insert syncs group_db_), and animated parameters (ValueNodes are shared, not cloned, so exported/animated links stay referenced). Full undo/redo via the action super-class.
  • Multi-select safety: layers are processed in descending depth order so index shifts never affect pending conversions.

Part 4. Icons

  • New action_layer_upgrade_icon (.sif asset) wired into known_icon_list (iconcontroller.cpp), so the upgrade task gets its stock icon; added to images/CMakeLists.txt and Makefile.am for the .sif → .png render.
  • The bevel_deprecated layer reuses the old bevel icon with an exclamation mark added, to visually mark it as deprecated.
image image image image

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@036006 036006 changed the title Fix bevel angle Fix Bevel highlight direction and indexing after Cobra port Jul 24, 2026
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 79 complexity · 43 duplication

Metric Results
Complexity 79
Duplication 43

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@036006 036006 changed the title Fix Bevel highlight direction and indexing after Cobra port fix: Bevel highlight direction and indexing after Cobra port Jul 24, 2026

@rodolforg rodolforg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing this!
(I didn't understand the 'indexing' word in the PR title.)

Comment on lines +74 to +76
halfsizex = (int)(std::fabs(size_x*.5/pw) + 3);
halfsizey = (int)(std::fabs(size_y*.5/ph) + 3);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines can be moved to inside the first (agglomerated) branch of the switch statement below.
These values are not used for Blur::GAUSSIAN

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks. The generic formula now lives only in the agglomerated branch.
I also added a default: label to that branch.

Comment on lines +7 to +8
src/modules/lyr_std/bevel_deprecated.cpp
src/modules/lyr_std/bevel_deprecated.h

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a new layer for solve this compatibility issue.
We could just use the layer (bad) version to use the TaskBevelDeprecated in Layer_Bevel::build_composite_fork_task_vfunc.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new layer was introduced, because the following reason:
If you load synfig file with old (broken) version, then Synfig will bump layer version.
So, when you load the file after saving it will behave as new (fixed) version, which might not be desirable for user.

As solution, when broken version id loaded we are converting it to Deprecated Bevel. This allows to keep compatibility.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding a new (invisible) parameter as I did for that broken gradient stuff?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with a separate layer type for two reasons:

  1. It quarantines the legacy code in one file pair that a future release
    can drop wholesale, without touching bevel.cpp. A hidden version flag
    leaves a legacy branch inside the current layer forever.
  2. Users get an explicit, undoable way to migrate old files to the fixed
    algorithm (Upgrade Layer in the layer menu). A hidden flag has no UI
    affordance — the only way to "upgrade" would be recreating the layer or
    hand-editing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. It quarantines the legacy code in one file pair that a future release
   can drop wholesale, without touching bevel.cpp. A hidden version flag
   leaves a legacy branch inside the current layer forever.

I don't think we could 'ever' remove the legacy code. We could have ourselves old animations that we don't touch for years and then, when we want to do so, Synfig would render it wrong - as MS Word does with its documents between versions :(

With the hidden flag would be not as simple, but it would be a search for "if (flag)" and wouldn't need to remove the deprecated filenames off the CMake/Autotools/POTFILE.in and include headers ;)

2. Users get an explicit, undoable way to migrate old files to the fixed
   algorithm (Upgrade Layer in the layer menu). A hidden flag has no UI
   affordance — the only way to "upgrade" would be recreating the layer or
   hand-editing.

Your great idea and work can ... work... even with the hidden flag too. Wouldn't be a simple change in get_target_layer_name()? It would have a layer name and its flag of deprecation instead?
Maybe this info could even be in a 'database' in Layer class. So loadcanvas and this new synfigapp action would be always in sync.

namespace lyr_std
{

class Layer_BevelDeprecated : public Layer_CompositeFork

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to keep this other version, maybe it should disappear of the Add Layer menu?
If so,

  • make it inherit the Layer_Invisible class too.
  • remove from the lyr_std/main.cpp
  • include this header directly in loadcanvas.cpp (as Layer_Mime is, as it shouldn't appear to final user)
    And take a look about the strategy I used in fix: inverted interpolation of gradient since 1.3.11 #3477 and see it fits.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to keep this other version, maybe it should disappear of the Add Layer menu?
Yes, it should disappear.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — hidden via CATEGORY_DO_NOT_USE, the same mechanism svg_layer uses (mod_svg/layer_svg.cpp:58).

I kept the registration and used the category instead: unregistered,
Layer::create("bevel_deprecated") would fall back to Layer_Mime and break
the migration and the Upgrade action. Layer_Invisible wouldn't help
either — stroboscope, timeloop and freetime inherit it and are still in
the menu; the filter is category-based.

Comment thread synfig-studio/src/synfigapp/actions/layerupgrade.cpp
private:
std::list<synfig::Layer::Handle> layers;

void prepare_upgrade_layer(const synfig::Layer::Handle &layer, const synfig::String &target_name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please prefer the code style that places the reference character & next to type, not near the variable name (C-style).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks — all references in layerupgrade now use Type& style.

@036006 036006 changed the title fix: Bevel highlight direction and indexing after Cobra port fix: Bevel highlight direction back to v1.0.2 algorithm; keep old files rendering unchanged Jul 28, 2026
@036006

036006 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a round of review fixes:

  • half-size helper: generic formula moved into the agglomerated switch
    branch (+ a default case to keep defined behavior for invalid types);
  • bevel_deprecated hidden from the New Layer menu / CLI list via
    CATEGORY_DO_NOT_USE;
  • code style: reference placement in layerupgrade.

Also corrected the origin story in the description: a render matrix against
the #3769 test file shows the highlight rotation predates the Cobra port
(present since ~1.3.11); the port preserved it faithfully, and v0.4 matches
the 1.0.2 algorithm.

@morevnaproject

Copy link
Copy Markdown
Member

I think we can merge this.

@rodolforg

Copy link
Copy Markdown
Contributor

I think we can merge this.

Please see this comment: #3784 (comment)

@rodolforg

Copy link
Copy Markdown
Contributor

@036006 @morevnaproject That's what I meant:
138da74

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.

Bevel Layer renders incorrectly comparing to version 1.0.2

3 participants