Fix #140669: Graph Editor Gaussian smoothing causing artifacts for keys on subframes

This adds to the fix done with #110059.
With the changes of this patch, the smoothing still happens ONLY on full frames.
Any subframe data is linearly interpolated to reduce the stepping seen before this PR.

As a side effect, the operator now has to store the original y values of the keys in question.
This is needed for correct blending, whereas before it was assumed that the samples
contain the original y values.

No changes to the butterworth filter, because that already has a property to increase the
sample rate for sub-frame data.

Pull Request: https://projects.blender.org/blender/blender/pulls/140928
This commit is contained in:
Christoph Lendenfeld
2025-07-01 16:02:49 +02:00
committed by Christoph Lendenfeld
parent cac2806ee2
commit 8da357f1ea
3 changed files with 54 additions and 10 deletions

View File

@@ -635,27 +635,46 @@ void ED_ANIM_get_1d_gauss_kernel(const float sigma, const int kernel_size, doubl
void smooth_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
const float *original_values,
float *samples,
const int sample_count,
const float factor,
const int kernel_size,
const double *kernel)
{
const int segment_end_index = segment->start_index + segment->length;
const float segment_start_x = fcu->bezt[segment->start_index].vec[1][0];
for (int i = segment->start_index; i < segment_end_index; i++) {
/* Using round() instead of (int). The latter would create stepping on x-values that are just
* below a full frame. */
const int sample_index = round(fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size;
float *filtered_samples = static_cast<float *>(MEM_dupallocN(samples));
for (int i = kernel_size; i < sample_count - kernel_size; i++) {
/* Apply the kernel. */
double filter_result = samples[sample_index] * kernel[0];
double filter_result = samples[i] * kernel[0];
for (int j = 1; j <= kernel_size; j++) {
const double kernel_value = kernel[j];
filter_result += samples[sample_index + j] * kernel_value;
filter_result += samples[sample_index - j] * kernel_value;
filter_result += samples[i + j] * kernel_value;
filter_result += samples[i - j] * kernel_value;
}
const float key_y_value = interpf(float(filter_result), samples[sample_index], factor);
filtered_samples[i] = filter_result;
}
for (int i = segment->start_index; i < segment_end_index; i++) {
const float sample_index_f = (fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size;
/* Using round() instead of (int). The latter would create stepping on x-values that are just
* below a full frame. */
const int sample_index = round(sample_index_f);
/* Sampling the two closest indices to support subframe keys. This can end up being the same
* index as sample_index, in which case the interpolation will happen between two identical
* values. */
const int secondary_index = clamp_i(
sample_index + signum_i(sample_index_f - sample_index), 0, sample_count - 1);
const float filter_result = interpf(filtered_samples[secondary_index],
filtered_samples[sample_index],
std::abs(sample_index_f - sample_index));
const float key_y_value = interpf(
filter_result, original_values[i - segment->start_index], factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
MEM_freeN(filtered_samples);
}
/* ---------------- */

View File

@@ -479,7 +479,9 @@ void butterworth_smooth_fcurve_segment(FCurve *fcu,
ButterworthCoefficients *bw_coeff);
void smooth_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
const float *original_values,
float *samples,
const int sample_count,
float factor,
int kernel_size,
const double *kernel);

View File

@@ -1790,10 +1790,25 @@ struct tFCurveSegmentLink {
tFCurveSegmentLink *next, *prev;
FCurve *fcu;
FCurveSegment *segment;
float *samples; /* Array of y-values of the FCurve segment. */
/* Array of y-values. The length of the array equals the length of the
* segment. */
float *original_y_values;
/* Array of y-values of the FCurve segment at regular intervals. */
float *samples;
int sample_count;
};
/* Allocates data that has to be freed after. */
static float *back_up_key_y_values(const FCurveSegment *segment, const FCurve *fcu)
{
float *original_y_values = MEM_calloc_arrayN<float>(segment->length,
"Smooth FCurve original values");
for (int i = 0; i < segment->length; i++) {
original_y_values[i] = fcu->bezt[i + segment->start_index].vec[1][1];
}
return original_y_values;
}
static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
const int filter_width,
const float sigma)
@@ -1816,6 +1831,7 @@ static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
tFCurveSegmentLink *segment_link = MEM_callocN<tFCurveSegmentLink>("FCurve Segment Link");
segment_link->fcu = fcu;
segment_link->segment = segment;
segment_link->original_y_values = back_up_key_y_values(segment, fcu);
BezTriple left_bezt = fcu->bezt[segment->start_index];
BezTriple right_bezt = fcu->bezt[segment->start_index + segment->length - 1];
const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) +
@@ -1824,6 +1840,7 @@ static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
blender::animrig::sample_fcurve_segment(
fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
segment_link->samples = samples;
segment_link->sample_count = sample_count;
BLI_addtail(&segment_links, segment_link);
}
}
@@ -1839,6 +1856,7 @@ static void gaussian_smooth_free_operator_data(void *operator_data)
LISTBASE_FOREACH (tFCurveSegmentLink *, segment_link, &gauss_data->segment_links) {
MEM_freeN(segment_link->samples);
MEM_freeN(segment_link->segment);
MEM_freeN(segment_link->original_y_values);
}
MEM_freeN(gauss_data->kernel);
BLI_freelistN(&gauss_data->segment_links);
@@ -1865,7 +1883,9 @@ static void gaussian_smooth_modal_update(bContext *C, wmOperator *op)
LISTBASE_FOREACH (tFCurveSegmentLink *, segment, &operator_data->segment_links) {
smooth_fcurve_segment(segment->fcu,
segment->segment,
segment->original_y_values,
segment->samples,
segment->sample_count,
factor,
filter_width,
operator_data->kernel);
@@ -1923,10 +1943,13 @@ static void gaussian_smooth_graph_keys(bAnimContext *ac,
const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) +
(filter_width * 2 + 1);
float *samples = MEM_calloc_arrayN<float>(sample_count, "Smooth FCurve Op Samples");
float *original_y_values = back_up_key_y_values(segment, fcu);
blender::animrig::sample_fcurve_segment(
fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
smooth_fcurve_segment(fcu, segment, samples, factor, filter_width, kernel);
smooth_fcurve_segment(
fcu, segment, original_y_values, samples, sample_count, factor, filter_width, kernel);
MEM_freeN(samples);
MEM_freeN(original_y_values);
}
BLI_freelistN(&segments);