/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup edtransform */ #include #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_math.h" #include "BKE_context.h" #include "ED_screen.h" #include "ED_transform.h" #include "UI_view2d.h" #include "SEQ_channels.h" #include "SEQ_effects.h" #include "SEQ_iterator.h" #include "SEQ_relations.h" #include "SEQ_render.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" #include "transform.h" #include "transform_convert.h" #include "transform_snap.h" typedef struct TransSeqSnapData { int *source_snap_points; int *target_snap_points; int source_snap_point_count; int target_snap_point_count; } TransSeqSnapData; /* -------------------------------------------------------------------- */ /** \name Snap sources * \{ */ static int seq_get_snap_source_points_len(SeqCollection *snap_sources) { return SEQ_collection_len(snap_sources) * 2; } static void seq_snap_source_points_alloc(TransSeqSnapData *snap_data, SeqCollection *snap_sources) { const size_t point_count = seq_get_snap_source_points_len(snap_sources); snap_data->source_snap_points = MEM_callocN(sizeof(int) * point_count, __func__); memset(snap_data->source_snap_points, 0, sizeof(int)); snap_data->source_snap_point_count = point_count; } static int cmp_fn(const void *a, const void *b) { return (*(int *)a - *(int *)b); } static void seq_snap_source_points_build(const Scene *scene, TransSeqSnapData *snap_data, SeqCollection *snap_sources) { int i = 0; Sequence *seq; SEQ_ITERATOR_FOREACH (seq, snap_sources) { int left = 0, right = 0; if (seq->flag & SEQ_LEFTSEL) { left = right = SEQ_time_left_handle_frame_get(scene, seq); } else if (seq->flag & SEQ_RIGHTSEL) { left = right = SEQ_time_right_handle_frame_get(scene, seq); } else { left = SEQ_time_left_handle_frame_get(scene, seq); right = SEQ_time_right_handle_frame_get(scene, seq); } snap_data->source_snap_points[i] = left; snap_data->source_snap_points[i + 1] = right; i += 2; BLI_assert(i <= snap_data->source_snap_point_count); } qsort(snap_data->source_snap_points, snap_data->source_snap_point_count, sizeof(int), cmp_fn); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Snap targets * \{ */ /* Add effect strips directly or indirectly connected to `seq_reference` to `collection`. */ static void query_strip_effects_fn(const Scene *scene, Sequence *seq_reference, ListBase *seqbase, SeqCollection *collection) { if (!SEQ_collection_append_strip(seq_reference, collection)) { return; /* Strip is already in set, so all effects connected to it are as well. */ } /* Find all strips connected to `seq_reference`. */ LISTBASE_FOREACH (Sequence *, seq_test, seqbase) { if (SEQ_relation_is_effect_of_strip(seq_test, seq_reference)) { query_strip_effects_fn(scene, seq_test, seqbase, collection); } } } static SeqCollection *seq_collection_extract_effects(SeqCollection *collection) { SeqCollection *effects = SEQ_collection_create(__func__); Sequence *seq; SEQ_ITERATOR_FOREACH (seq, collection) { if (SEQ_effect_get_num_inputs(seq->type) > 0) { SEQ_collection_append_strip(seq, effects); } } return effects; } static SeqCollection *query_snap_targets(Scene *scene, SeqCollection *snap_sources, bool exclude_selected) { Editing *ed = SEQ_editing_get(scene); ListBase *seqbase = SEQ_active_seqbase_get(ed); ListBase *channels = SEQ_channels_displayed_get(ed); const short snap_flag = SEQ_tool_settings_snap_flag_get(scene); SeqCollection *snap_targets = SEQ_collection_create(__func__); LISTBASE_FOREACH (Sequence *, seq, seqbase) { if (exclude_selected && seq->flag & SELECT) { continue; /* Selected are being transformed. */ } if (SEQ_render_is_muted(channels, seq) && (snap_flag & SEQ_SNAP_IGNORE_MUTED)) { continue; } if (seq->type == SEQ_TYPE_SOUND_RAM && (snap_flag & SEQ_SNAP_IGNORE_SOUND)) { continue; } SEQ_collection_append_strip(seq, snap_targets); } /* Effects will always change position with strip to which they are connected and they don't have * to be selected. Remove such strips from `snap_targets` collection. */ SeqCollection *snap_sources_temp = SEQ_collection_duplicate(snap_sources); SEQ_collection_expand(scene, seqbase, snap_sources_temp, query_strip_effects_fn); SeqCollection *snap_sources_effects = seq_collection_extract_effects(snap_sources_temp); SEQ_collection_exclude(snap_targets, snap_sources_effects); SEQ_collection_free(snap_sources_temp); return snap_targets; } static int seq_get_snap_target_points_count(short snap_mode, SeqCollection *snap_targets) { int count = 2; /* Strip start and end are always used. */ if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) { count += 2; } count *= SEQ_collection_len(snap_targets); if (snap_mode & SEQ_SNAP_TO_CURRENT_FRAME) { count++; } return count; } static void seq_snap_target_points_alloc(short snap_mode, TransSeqSnapData *snap_data, SeqCollection *snap_targets) { const size_t point_count = seq_get_snap_target_points_count(snap_mode, snap_targets); snap_data->target_snap_points = MEM_callocN(sizeof(int) * point_count, __func__); memset(snap_data->target_snap_points, 0, sizeof(int)); snap_data->target_snap_point_count = point_count; } static void seq_snap_target_points_build(Scene *scene, short snap_mode, TransSeqSnapData *snap_data, SeqCollection *snap_targets) { int i = 0; if (snap_mode & SEQ_SNAP_TO_CURRENT_FRAME) { snap_data->target_snap_points[i] = scene->r.cfra; i++; } Sequence *seq; SEQ_ITERATOR_FOREACH (seq, snap_targets) { snap_data->target_snap_points[i] = SEQ_time_left_handle_frame_get(scene, seq); snap_data->target_snap_points[i + 1] = SEQ_time_right_handle_frame_get(scene, seq); i += 2; if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) { int content_start = min_ii(SEQ_time_right_handle_frame_get(scene, seq), seq->start); int content_end = max_ii(SEQ_time_left_handle_frame_get(scene, seq), seq->start + seq->len); /* Effects and single image strips produce incorrect content length. Skip these strips. */ if ((seq->type & SEQ_TYPE_EFFECT) != 0 || seq->len == 1) { content_start = SEQ_time_left_handle_frame_get(scene, seq); content_end = SEQ_time_right_handle_frame_get(scene, seq); } CLAMP(content_start, SEQ_time_left_handle_frame_get(scene, seq), SEQ_time_right_handle_frame_get(scene, seq)); CLAMP(content_end, SEQ_time_left_handle_frame_get(scene, seq), SEQ_time_right_handle_frame_get(scene, seq)); snap_data->target_snap_points[i] = content_start; snap_data->target_snap_points[i + 1] = content_end; i += 2; } } BLI_assert(i <= snap_data->target_snap_point_count); qsort(snap_data->target_snap_points, snap_data->target_snap_point_count, sizeof(int), cmp_fn); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Snap utilities * \{ */ static int seq_snap_threshold_get_frame_distance(const TransInfo *t) { const int snap_distance = SEQ_tool_settings_snap_distance_get(t->scene); const struct View2D *v2d = &t->region->v2d; return round_fl_to_int(UI_view2d_region_to_view_x(v2d, snap_distance) - UI_view2d_region_to_view_x(v2d, 0)); } /** \} */ TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) { if (t->data_type == &TransConvertType_SequencerImage) { return NULL; } Scene *scene = t->scene; TransSeqSnapData *snap_data = MEM_callocN(sizeof(TransSeqSnapData), __func__); ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene)); SeqCollection *snap_sources = SEQ_query_selected_strips(seqbase); SeqCollection *snap_targets = query_snap_targets(scene, snap_sources, true); if (SEQ_collection_len(snap_sources) == 0) { SEQ_collection_free(snap_targets); SEQ_collection_free(snap_sources); MEM_freeN(snap_data); return NULL; } /* Build arrays of snap points. */ seq_snap_source_points_alloc(snap_data, snap_sources); seq_snap_source_points_build(scene, snap_data, snap_sources); SEQ_collection_free(snap_sources); short snap_mode = t->tsnap.mode; seq_snap_target_points_alloc(snap_mode, snap_data, snap_targets); seq_snap_target_points_build(scene, snap_mode, snap_data, snap_targets); SEQ_collection_free(snap_targets); return snap_data; } void transform_snap_sequencer_data_free(TransSeqSnapData *data) { MEM_freeN(data->source_snap_points); MEM_freeN(data->target_snap_points); MEM_freeN(data); } bool transform_snap_sequencer_calc(TransInfo *t) { const TransSeqSnapData *snap_data = t->tsnap.seq_context; if (snap_data == NULL) { return false; } /* Prevent snapping when constrained to Y axis. */ if (t->con.mode & CON_APPLY && t->con.mode & CON_AXIS1) { return false; } int best_dist = MAXFRAME, best_target_frame = 0, best_source_frame = 0; for (int i = 0; i < snap_data->source_snap_point_count; i++) { int snap_source_frame = snap_data->source_snap_points[i] + round_fl_to_int(t->values[0]); for (int j = 0; j < snap_data->target_snap_point_count; j++) { int snap_target_frame = snap_data->target_snap_points[j]; int dist = abs(snap_target_frame - snap_source_frame); if (dist > best_dist) { continue; } best_dist = dist; best_target_frame = snap_target_frame; best_source_frame = snap_source_frame; } } if (best_dist > seq_snap_threshold_get_frame_distance(t)) { return false; } t->tsnap.snapPoint[0] = best_target_frame; t->tsnap.snapTarget[0] = best_source_frame; return true; } void transform_snap_sequencer_apply_translate(TransInfo *t, float *vec) { *vec += t->tsnap.snapPoint[0] - t->tsnap.snapTarget[0]; } static int transform_snap_sequencer_to_closest_strip_ex(TransInfo *t, int frame_1, int frame_2) { Scene *scene = t->scene; TransSeqSnapData *snap_data = MEM_callocN(sizeof(TransSeqSnapData), __func__); SeqCollection *empty_col = SEQ_collection_create(__func__); SeqCollection *snap_targets = query_snap_targets(scene, empty_col, false); SEQ_collection_free(empty_col); snap_data->source_snap_points = MEM_callocN(sizeof(int) * 2, __func__); snap_data->source_snap_point_count = 2; BLI_assert(frame_1 <= frame_2); snap_data->source_snap_points[0] = frame_1; snap_data->source_snap_points[1] = frame_2; short snap_mode = t->tsnap.mode; /* Build arrays of snap points. */ seq_snap_target_points_alloc(snap_mode, snap_data, snap_targets); seq_snap_target_points_build(scene, snap_mode, snap_data, snap_targets); SEQ_collection_free(snap_targets); t->tsnap.seq_context = snap_data; bool snap_success = transform_snap_sequencer_calc(t); transform_snap_sequencer_data_free(snap_data); t->tsnap.seq_context = NULL; float snap_offset = 0; if (snap_success) { t->tsnap.status |= (POINT_INIT | TARGET_INIT); transform_snap_sequencer_apply_translate(t, &snap_offset); } else { t->tsnap.status &= ~(POINT_INIT | TARGET_INIT); } return snap_offset; } bool ED_transform_snap_sequencer_to_closest_strip_calc(Scene *scene, ARegion *region, int frame_1, int frame_2, int *r_snap_distance, float *r_snap_frame) { TransInfo t; t.scene = scene; t.region = region; t.values[0] = 0; t.data_type = &TransConvertType_Sequencer; t.tsnap.mode = SEQ_tool_settings_snap_mode_get(scene); *r_snap_distance = transform_snap_sequencer_to_closest_strip_ex(&t, frame_1, frame_2); *r_snap_frame = t.tsnap.snapPoint[0]; return validSnap(&t); } void ED_draw_sequencer_snap_point(struct bContext *C, float snap_point) { /* Reuse the snapping drawing code from the transform system. */ TransInfo t; t.mode = TFM_SEQ_SLIDE; t.modifiers = MOD_SNAP; t.spacetype = SPACE_SEQ; t.tsnap.status = (POINT_INIT | TARGET_INIT); t.tsnap.snapPoint[0] = snap_point; drawSnapping(C, &t); }