Skip to content

Commit

Permalink
Match MC bezier interpolation for degenerate cases
Browse files Browse the repository at this point in the history
Don't preform handle scaling and clamping
Add tests for keyframe interpolation
  • Loading branch information
austinwitherspoon authored and markreidvfx committed Mar 24, 2024
1 parent 408eb8d commit aeea03b
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 16 deletions.
35 changes: 19 additions & 16 deletions src/aaf2/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,26 +147,29 @@ def scale_handle(p0, p1, p2):
y = (p1[1] - p0[1]) * (p2[0] - p0[0]) / (p1[0] - p0[0])
return [p2[0], p0[1] + y]

def bezier_interpolate(p0, p1, p2, p3, x):
def bezier_interpolate(p0, p1, p2, p3, x, scale_handles=False):

# degenerate cases 1
# p0 is after p3
if p0[0] >= p3[0]:
return p[1]

# degenerate cases 2
# p1 after p3 or before p0
if p1[0] > p3[0]:
p1 = scale_handle(p0, p1, p3)
elif p1[0] < p0[0]:
p1 = [p0[0], p1[1]]

# degenerate cases 3
# p2 before p0 or after p3
if p2[0] < p0[0]:
p2 = scale_handle(p3, p2, p0)
elif p2[0] > p3[0]:
p2 = [p3[0], p2[1]]
return p0[1]

# Media Composer doesn't perform any scaling or clamping of handles
# if their x positions are out not between p0.x and p3.x.
if scale_handles:
# degenerate cases 2
# p1 after p3 or before p0
if p1[0] > p3[0]:
p1 = scale_handle(p0, p1, p3)
elif p1[0] < p0[0]:
p1 = [p0[0], p1[1]]

# degenerate cases 3
# p2 before p0 or after p3
if p2[0] < p0[0]:
p2 = scale_handle(p3, p2, p0)
elif p2[0] > p3[0]:
p2 = [p3[0], p2[1]]

# offset points so x is the x axis
pa = p0[0] - x
Expand Down
Binary file not shown.
Binary file added tests/test_files/keyframes/normal_bezier.aaf
Binary file not shown.
102 changes: 102 additions & 0 deletions tests/test_interpolation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
import os
import aaf2
import common
import unittest

KEYFRAME_VALUE_MARGIN_OF_ERROR = .1


class TestKeyframeInterpolation(unittest.TestCase):
def test_normal_bezier(self):
file = os.path.join(common.test_files_dir(), 'keyframes/normal_bezier.aaf')
# these values are transcribed by hand from media composer
expected_result = {
0: 0.0,
1: 147.50,
2: 257.19,
3: 343.74,
4: 413.91,
5: 471.47,
6: 518.72,
7: 557.20,
8: 587.92,
9: 611.60,
10: 628.70,
11: 639.51,
12: 644.18,
13: 642.68,
14: 634.87,
15: 620.42,
16: 598.80,
17: 569.19,
18: 530.32,
19: 480.22,
20: 415.59,
21: 330.32,
22: 210.33,
23: 0.0,
}
with aaf2.open(file) as f:
# Grab the Y position parameter from this AAF
op_group = next(f.content.compositionmobs()).slots[8].segment.components[1]
param_y_pos = next(
param for param in op_group.parameters
if isinstance(param, aaf2.misc.VaryingValue)
and param.name == "DVE_POS_Y_U"
)
self.compare_interpolated_values(param_y_pos, expected_result)

def test_handle_past_last_keyframe(self):
file = os.path.join(common.test_files_dir(), 'keyframes/bezier_handle_past_last_keyframe.aaf')
# these values are transcribed by hand from media composer
expected_result = {
0: 0.0,
1: 40.2,
2: 79.92,
3: 119.09,
4: 157.63,
5: 195.45,
6: 232.43,
7: 268.39,
8: 303.14,
9: 336.38,
10: 367.72,
11: 396.57,
12: 422.01,
13: 442.48,
14: 454.99,
15: 452.45,
16: 409.92,
17: 227.30,
18: 107.82,
19: 55.0,
20: 26.25,
21: 10.24,
22: 2.3,
23: 0.0,
}
with aaf2.open(file) as f:
# Grab the Y position parameter from this AAF
op_group = next(f.content.compositionmobs()).slots[8].segment.components[1]
param_y_pos = next(
param for param in op_group.parameters
if isinstance(param, aaf2.misc.VaryingValue)
and param.name == "DVE_POS_Y_U"
)
self.compare_interpolated_values(param_y_pos, expected_result)

def compare_interpolated_values(self, parameter, expected_values):
# Generate the interpolated values
for frame_num, expected_value in expected_values.items():
actual_value = parameter.value_at(frame_num)
difference = abs(actual_value - expected_value)
assert difference < KEYFRAME_VALUE_MARGIN_OF_ERROR, \
"Expected value of {:.02f} for frame {} but got {:.02f}. Difference: {:.02f}".format(
expected_value, frame_num, actual_value, difference
)

0 comments on commit aeea03b

Please sign in to comment.