diff --git a/release/datafiles/icons/ops.sequencer.retime.dat b/release/datafiles/icons/ops.sequencer.retime.dat new file mode 100644 index 00000000000..58075c0409b Binary files /dev/null and b/release/datafiles/icons/ops.sequencer.retime.dat differ diff --git a/scripts/startup/bl_ui/space_sequencer.py b/scripts/startup/bl_ui/space_sequencer.py index 3976f503aa2..5fce60035a6 100644 --- a/scripts/startup/bl_ui/space_sequencer.py +++ b/scripts/startup/bl_ui/space_sequencer.py @@ -1878,7 +1878,7 @@ class SEQUENCER_PT_time(SequencerButtonsPanel, Panel): split.label(text="Channel") split.prop(strip, "channel", text="") - if not is_effect: + if strip.type == 'SOUND': split = layout.split(factor=0.5 + max_factor) split.alignment = 'RIGHT' split.label(text="Speed Factor") diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index bc640799352..9ec813f035f 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2562,6 +2562,18 @@ class _defs_sequencer_generic: options={'KEYMAP_FALLBACK'}, ) + @ToolDef.from_fn + def retime(): + return dict( + idname="builtin.retime", + label="Retime", + icon="ops.sequencer.retime", + widget="SEQUENCER_GGT_gizmo_retime", + operator=None, + keymap=None, + options={'KEYMAP_FALLBACK'}, + ) + @ToolDef.from_fn def sample(): return dict( @@ -3255,6 +3267,7 @@ class SEQUENCER_PT_tools_active(ToolSelectPanelHelper, Panel): 'SEQUENCER': [ *_tools_select, _defs_sequencer_generic.blade, + _defs_sequencer_generic.retime, ], 'SEQUENCER_PREVIEW': [ *_tools_select, diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 4a1c14bd430..59e4be70989 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -77,6 +77,7 @@ #include "SEQ_channels.h" #include "SEQ_iterator.h" +#include "SEQ_retiming.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" @@ -685,6 +686,25 @@ static bool seq_speed_factor_set(Sequence *seq, void *user_data) return true; } +static bool do_versions_sequencer_init_retiming_tool_data(Sequence *seq, void *user_data) +{ + const Scene *scene = static_cast(user_data); + + if (seq->speed_factor == 1 || !SEQ_retiming_is_allowed(seq)) { + return true; + } + + const int content_length = SEQ_time_strip_length_get(scene, seq); + + SEQ_retiming_data_ensure(scene, seq); + + SeqRetimingHandle *handle = &seq->retiming_handles[seq->retiming_handle_num - 1]; + handle->strip_frame_index = round_fl_to_int(content_length / seq->speed_factor); + seq->speed_factor = 0.0f; + + return true; +} + static void version_geometry_nodes_replace_transfer_attribute_node(bNodeTree *ntree) { using namespace blender; @@ -1205,6 +1225,16 @@ void do_versions_after_linking_300(Main *bmain, ReportList * /*reports*/) */ { /* Keep this block, even when empty. */ + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + Editing *ed = SEQ_editing_get(scene); + if (ed == nullptr) { + continue; + } + + SEQ_for_each_callback( + &scene->ed->seqbase, do_versions_sequencer_init_retiming_tool_data, scene); + } } } diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt index 0a8130ffb56..d2b91d2bcb1 100644 --- a/source/blender/editors/datafiles/CMakeLists.txt +++ b/source/blender/editors/datafiles/CMakeLists.txt @@ -867,6 +867,7 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES ops.sculpt.mask_by_color ops.sculpt.mesh_filter ops.sequencer.blade + ops.sequencer.retime ops.transform.bone_envelope ops.transform.bone_size ops.transform.edge_slide diff --git a/source/blender/editors/space_sequencer/CMakeLists.txt b/source/blender/editors/space_sequencer/CMakeLists.txt index 842ba8d7fe8..1a276ce2090 100644 --- a/source/blender/editors/space_sequencer/CMakeLists.txt +++ b/source/blender/editors/space_sequencer/CMakeLists.txt @@ -35,10 +35,13 @@ set(SRC sequencer_drag_drop.c sequencer_draw.c sequencer_edit.c + sequencer_gizmo_retime.cc + sequencer_gizmo_retime_type.cc sequencer_modifier.c sequencer_ops.c sequencer_preview.c sequencer_proxy.c + sequencer_retiming.cc sequencer_scopes.c sequencer_select.c sequencer_thumbnails.c diff --git a/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc b/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc new file mode 100644 index 00000000000..9dcedbfdc4d --- /dev/null +++ b/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spseq + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_context.h" + +#include "BLI_span.hh" + +#include "RNA_access.h" + +#include "UI_resources.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_gizmo_library.h" +#include "ED_gizmo_utils.h" + +#include "SEQ_iterator.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" + +/* Own include. */ +#include "sequencer_intern.h" + +typedef struct GizmoGroup_retime { + wmGizmo *add_handle_gizmo; + wmGizmo *move_handle_gizmo; + wmGizmo *remove_handle_gizmo; +} GizmoGroup_retime; + +static bool gizmogroup_retime_poll(const bContext *C, wmGizmoGroupType *gzgt) +{ + /* Needed to prevent drawing gizmos when retiming tool is not activated. */ + if (!ED_gizmo_poll_or_unlink_delayed_from_tool(C, gzgt)) { + return false; + } + + if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) { + return false; + } + + ScrArea *area = CTX_wm_area(C); + if (area == nullptr && area->spacetype != SPACE_SEQ) { + return false; + } + + const SpaceSeq *sseq = (SpaceSeq *)area->spacedata.first; + if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_TOOL)) { + return false; + } + + Editing *ed = SEQ_editing_get(CTX_data_scene(C)); + Sequence *seq = ed->act_seq; + + if (ed == nullptr || seq == nullptr || !SEQ_retiming_is_allowed(seq)) { + return false; + } + + return true; +} + +static void gizmogroup_retime_setup(const bContext * /* C */, wmGizmoGroup *gzgroup) +{ + GizmoGroup_retime *ggd = (GizmoGroup_retime *)MEM_callocN(sizeof(GizmoGroup_retime), __func__); + + /* Assign gizmos. */ + const wmGizmoType *gzt_add_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_add", true); + ggd->add_handle_gizmo = WM_gizmo_new_ptr(gzt_add_handle, gzgroup, nullptr); + const wmGizmoType *gzt_remove_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_remove", true); + ggd->remove_handle_gizmo = WM_gizmo_new_ptr(gzt_remove_handle, gzgroup, nullptr); + const wmGizmoType *gzt_move_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_move", true); + ggd->move_handle_gizmo = WM_gizmo_new_ptr(gzt_move_handle, gzgroup, nullptr); + gzgroup->customdata = ggd; + + /* Assign operators. */ + wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_move", true); + WM_gizmo_operator_set(ggd->move_handle_gizmo, 0, ot, nullptr); + ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_add", true); + WM_gizmo_operator_set(ggd->add_handle_gizmo, 0, ot, nullptr); + ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_remove", true); + WM_gizmo_operator_set(ggd->remove_handle_gizmo, 0, ot, nullptr); +} + +void SEQUENCER_GGT_gizmo_retime(wmGizmoGroupType *gzgt) +{ + gzgt->name = "Sequencer Transform Gizmo Retime"; + gzgt->idname = "SEQUENCER_GGT_gizmo_retime"; + + gzgt->flag = WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL; + + gzgt->gzmap_params.spaceid = SPACE_SEQ; + gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; + + gzgt->poll = gizmogroup_retime_poll; + gzgt->setup = gizmogroup_retime_setup; +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc new file mode 100644 index 00000000000..ac224c08d7f --- /dev/null +++ b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc @@ -0,0 +1,568 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spseq + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_span.hh" + +#include "DNA_anim_types.h" +#include "DNA_sequence_types.h" + +#include "BKE_context.h" +#include "BKE_fcurve.h" +#include "BKE_scene.h" + +#include "BLF_api.h" + +#include "GPU_batch.h" +#include "GPU_batch_utils.h" +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_select.h" +#include "GPU_state.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_gizmo_library.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "SEQ_iterator.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" + +/* Own include. */ +#include "sequencer_intern.h" + +using blender::MutableSpan; + +#define REMOVE_GIZMO_HEIGHT 14.0f * U.dpi_fac /* Pixels from bottom of strip. */ +#define RETIME_HANDLE_TRIANGLE_SIZE 14.0f * U.dpi_fac /* Size in pixels. */ +#define RETIME_HANDLE_MOUSEOVER_THRESHOLD 16.0f * U.dpi_fac /* Size in pixels. */ +#define RETIME_BUTTON_SIZE 0.6f /* Factor based on icon size. */ + +static float strip_y_rescale(const Sequence *seq, const float y_value) +{ + const float y_range = SEQ_STRIP_OFSTOP - SEQ_STRIP_OFSBOTTOM; + return (y_value * y_range) + seq->machine + SEQ_STRIP_OFSBOTTOM; +} + +static float handle_x_get(const Sequence *seq, const SeqRetimingHandle *handle) +{ + + const SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + const bool is_last_handle = (handle == last_handle); + + return SEQ_time_start_frame_get(seq) + handle->strip_frame_index + (is_last_handle ? 1 : 0); +} + +static const SeqRetimingHandle *mouse_over_handle_get(const Sequence *seq, + const View2D *v2d, + const int mval[2]) +{ + int best_distance = INT_MAX; + const SeqRetimingHandle *best_handle = nullptr; + + MutableSpan handles = SEQ_retiming_handles_get(seq); + for (const SeqRetimingHandle &handle : handles) { + int distance = round_fl_to_int( + fabsf(UI_view2d_view_to_region_x(v2d, handle_x_get(seq, &handle)) - mval[0])); + + if (distance < RETIME_HANDLE_MOUSEOVER_THRESHOLD && distance < best_distance) { + best_distance = distance; + best_handle = &handle; + } + } + + return best_handle; +} + +static float pixels_to_view_width(const bContext *C, const float width) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + float scale_x = UI_view2d_view_to_region_x(v2d, 1) - UI_view2d_view_to_region_x(v2d, 0.0f); + return width / scale_x; +} + +static float pixels_to_view_height(const bContext *C, const float height) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + float scale_y = UI_view2d_view_to_region_y(v2d, 1) - UI_view2d_view_to_region_y(v2d, 0.0f); + return height / scale_y; +} + +static float strip_start_screenspace_get(const bContext *C, const Sequence *seq) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + const Scene *scene = CTX_data_scene(C); + return UI_view2d_view_to_region_x(v2d, SEQ_time_left_handle_frame_get(scene, seq)); +} + +static float strip_end_screenspace_get(const bContext *C, const Sequence *seq) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + const Scene *scene = CTX_data_scene(C); + return UI_view2d_view_to_region_x(v2d, SEQ_time_right_handle_frame_get(scene, seq)); +} + +static Sequence *active_seq_from_context(const bContext *C) +{ + const Editing *ed = SEQ_editing_get(CTX_data_scene(C)); + return ed->act_seq; +} + +static rctf strip_box_get(const bContext *C, const Sequence *seq) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + rctf rect; + rect.xmin = strip_start_screenspace_get(C, seq); + rect.xmax = strip_end_screenspace_get(C, seq); + rect.ymin = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0)); + rect.ymax = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 1)); + return rect; +} + +static rctf remove_box_get(const bContext *C, const Sequence *seq) +{ + rctf rect = strip_box_get(C, seq); + rect.ymax = rect.ymin + REMOVE_GIZMO_HEIGHT; + return rect; +} + +static bool mouse_is_inside_box(const rctf *box, const int mval[2]) +{ + return mval[0] >= box->xmin && mval[0] <= box->xmax && mval[1] >= box->ymin && + mval[1] <= box->ymax; +} + +/* -------------------------------------------------------------------- */ +/** \name Retiming Add Handle Gizmo + * \{ */ + +typedef struct RetimeButtonGizmo { + wmGizmo gizmo; + int icon_id; + const Sequence *seq_under_mouse; + bool is_mouse_over_gizmo; +} RetimeButtonGizmo; + +typedef struct ButtonDimensions { + float height; + float width; + float x; + float y; +} ButtonDimensions; + +static ButtonDimensions button_dimensions_get(const bContext *C, const RetimeButtonGizmo *gizmo) +{ + const Scene *scene = CTX_data_scene(C); + const View2D *v2d = UI_view2d_fromcontext(C); + const Sequence *seq = active_seq_from_context(C); + + const float icon_height = UI_icon_get_height(gizmo->icon_id) * U.dpi_fac; + const float icon_width = UI_icon_get_width(gizmo->icon_id) * U.dpi_fac; + const float icon_x = UI_view2d_view_to_region_x(v2d, BKE_scene_frame_get(scene)) + + icon_width / 2; + const float icon_y = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0.5)) - + icon_height / 2; + const ButtonDimensions dimensions = {icon_height, icon_width, icon_x, icon_y}; + return dimensions; +} + +static rctf button_box_get(const bContext *C, const RetimeButtonGizmo *gizmo) +{ + ButtonDimensions button_dimensions = button_dimensions_get(C, gizmo); + + float x_range = button_dimensions.width; + float y_range = button_dimensions.height; + + rctf rect; + rect.xmin = button_dimensions.x; + rect.xmax = button_dimensions.x + x_range; + rect.ymin = button_dimensions.y; + rect.ymax = button_dimensions.y + y_range; + + return rect; +} + +static void gizmo_retime_handle_add_draw(const bContext *C, wmGizmo *gz) +{ + RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz; + + if (ED_screen_animation_playing(CTX_wm_manager(C))) { + return; + } + + const ButtonDimensions button = button_dimensions_get(C, gizmo); + const rctf strip_box = strip_box_get(C, active_seq_from_context(C)); + if (!BLI_rctf_isect_pt(&strip_box, button.x, button.y)) { + return; + } + + wmOrtho2_region_pixelspace(CTX_wm_region(C)); + GPU_blend(GPU_BLEND_ALPHA); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + const float alpha = gizmo->is_mouse_over_gizmo ? 1.0f : 0.6f; + + immUniformColor4f(0.2f, 0.2f, 0.2f, alpha); + imm_draw_circle_fill_2d(pos, + button.x + button.width / 2, + button.y + button.height / 2, + button.width * RETIME_BUTTON_SIZE, + 32); + immUnbindProgram(); + + GPU_polygon_smooth(false); + UI_icon_draw_alpha(button.x, button.y, gizmo->icon_id, alpha); + GPU_polygon_smooth(true); + + GPU_blend(GPU_BLEND_NONE); +} + +static int gizmo_retime_handle_add_test_select(bContext *C, wmGizmo *gz, const int mval[2]) +{ + RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz; + Sequence *seq = active_seq_from_context(C); + + Sequence *mouse_over_seq = nullptr; + gizmo->is_mouse_over_gizmo = false; + + /* Store strip under mouse cursor. */ + const rctf strip_box = strip_box_get(C, seq); + if (mouse_is_inside_box(&strip_box, mval)) { + mouse_over_seq = seq; + } + + if (gizmo->seq_under_mouse != mouse_over_seq) { + gizmo->seq_under_mouse = mouse_over_seq; + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, CTX_data_scene(C)); + } + + if (gizmo->seq_under_mouse == nullptr) { + return -1; + } + + const rctf button_box = button_box_get(C, gizmo); + if (!mouse_is_inside_box(&button_box, mval)) { + return -1; + } + + gizmo->is_mouse_over_gizmo = true; + const Scene *scene = CTX_data_scene(C); + wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0); + RNA_int_set(&op_elem->ptr, "timeline_frame", BKE_scene_frame_get(scene)); + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, CTX_data_scene(C)); + return 0; +} + +static void gizmo_retime_handle_add_setup(wmGizmo *gz) +{ + RetimeButtonGizmo *gizmo = (RetimeButtonGizmo *)gz; + gizmo->icon_id = ICON_ADD; +} + +void GIZMO_GT_retime_handle_add(wmGizmoType *gzt) +{ + /* Identifiers. */ + gzt->idname = "GIZMO_GT_retime_handle_add"; + + /* Api callbacks. */ + gzt->setup = gizmo_retime_handle_add_setup; + gzt->draw = gizmo_retime_handle_add_draw; + gzt->test_select = gizmo_retime_handle_add_test_select; + gzt->struct_size = sizeof(RetimeButtonGizmo); + + /* Currently only used for cursor display. */ + RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Move Handle Gizmo + * \{ */ + +typedef struct RetimeHandleMoveGizmo { + wmGizmo gizmo; + const Sequence *mouse_over_seq; + int mouse_over_handle_x; +} RetimeHandleMoveGizmo; + +static void retime_handle_draw(const bContext *C, + const RetimeHandleMoveGizmo *gizmo, + uint pos, + const Sequence *seq, + const SeqRetimingHandle *handle) +{ + const Scene *scene = CTX_data_scene(C); + const float handle_x = handle_x_get(seq, handle); + + if (handle_x == SEQ_time_left_handle_frame_get(scene, seq)) { + return; + } + if (handle_x == SEQ_time_right_handle_frame_get(scene, seq)) { + return; + } + + const View2D *v2d = UI_view2d_fromcontext(C); + const rctf strip_box = strip_box_get(C, seq); + if (!BLI_rctf_isect_x(&strip_box, UI_view2d_view_to_region_x(v2d, handle_x))) { + return; /* Handle out of strip bounds. */ + } + + const int ui_triangle_size = RETIME_HANDLE_TRIANGLE_SIZE; + const float bottom = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 0.0f)) + 2; + const float top = UI_view2d_view_to_region_y(v2d, strip_y_rescale(seq, 1.0f)) - 2; + const float handle_position = UI_view2d_view_to_region_x(v2d, handle_x); + + if (seq == gizmo->mouse_over_seq && handle_x == gizmo->mouse_over_handle_x) { + immUniformColor4f(1.0f, 1.0f, 1.0f, 1.0f); + } + else { + immUniformColor4f(0.65f, 0.65f, 0.65f, 1.0f); + } + + immBegin(GPU_PRIM_TRI_FAN, 3); + immVertex2f(pos, handle_position - ui_triangle_size / 2, bottom); + immVertex2f(pos, handle_position + ui_triangle_size / 2, bottom); + immVertex2f(pos, handle_position, bottom + ui_triangle_size); + + immEnd(); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, handle_position, bottom); + immVertex2f(pos, handle_position, top); + immEnd(); +} + +static void retime_speed_text_draw(const bContext *C, + const Sequence *seq, + const SeqRetimingHandle *handle) +{ + SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + if (handle == last_handle) { + return; + } + + const Scene *scene = CTX_data_scene(C); + const int start_frame = SEQ_time_left_handle_frame_get(scene, seq); + const int end_frame = SEQ_time_right_handle_frame_get(scene, seq); + + int next_handle_index = SEQ_retiming_handle_index_get(seq, handle) + 1; + const SeqRetimingHandle *next_handle = &SEQ_retiming_handles_get(seq)[next_handle_index]; + if (handle_x_get(seq, next_handle) < start_frame || handle_x_get(seq, handle) > end_frame) { + return; /* Label out of strip bounds. */ + } + + const float speed = SEQ_retiming_handle_speed_get(scene, seq, next_handle); + + char label_str[20]; + const size_t label_len = BLI_snprintf_rlen( + label_str, sizeof(label_str), "%d%%", round_fl_to_int(speed * 100.0f)); + + const float width = pixels_to_view_width(C, BLF_width(BLF_default(), label_str, label_len)); + + const float xmin = max_ff(SEQ_time_left_handle_frame_get(scene, seq), handle_x_get(seq, handle)); + const float xmax = min_ff(SEQ_time_right_handle_frame_get(scene, seq), + handle_x_get(seq, next_handle)); + + const float text_x = (xmin + xmax - width) / 2; + const float text_y = strip_y_rescale(seq, 0) + pixels_to_view_height(C, 5); + + if (width > xmax - xmin) { + return; /* Not enough space to draw label. */ + } + + const uchar col[4] = {255, 255, 255, 255}; + UI_view2d_text_cache_add(UI_view2d_fromcontext(C), text_x, text_y, label_str, label_len, col); +} + +static void gizmo_retime_handle_draw(const bContext *C, wmGizmo *gz) +{ + const RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz; + const View2D *v2d = UI_view2d_fromcontext(C); + + wmOrtho2_region_pixelspace(CTX_wm_region(C)); + GPU_blend(GPU_BLEND_ALPHA); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + Sequence *seq = active_seq_from_context(C); + SEQ_retiming_data_ensure(CTX_data_scene(C), seq); + MutableSpan handles = SEQ_retiming_handles_get(seq); + + for (const SeqRetimingHandle &handle : handles) { + retime_speed_text_draw(C, seq, &handle); + + if (&handle == handles.begin()) { + continue; /* Ignore first handle. */ + } + retime_handle_draw(C, gizmo, pos, seq, &handle); + } + + immUnbindProgram(); + GPU_blend(GPU_BLEND_NONE); + + UI_view2d_text_cache_draw(CTX_wm_region(C)); + UI_view2d_view_ortho(v2d); /* 'UI_view2d_text_cache_draw()' messes up current view. */ +} + +static int gizmo_retime_handle_test_select(bContext *C, wmGizmo *gz, const int mval[2]) +{ + RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz; + Scene *scene = CTX_data_scene(C); + + gizmo->mouse_over_seq = nullptr; + + Sequence *seq = active_seq_from_context(C); + SEQ_retiming_data_ensure(CTX_data_scene(C), seq); + const SeqRetimingHandle *handle = mouse_over_handle_get(seq, UI_view2d_fromcontext(C), mval); + const int handle_index = SEQ_retiming_handle_index_get(seq, handle); + + if (handle == nullptr) { + return -1; + } + + if (handle_x_get(seq, handle) == SEQ_time_left_handle_frame_get(scene, seq) || + handle_index == 0) { + return -1; + } + + rctf strip_box = strip_box_get(C, seq); + BLI_rctf_resize_x(&strip_box, BLI_rctf_size_x(&strip_box) + 2 * RETIME_HANDLE_TRIANGLE_SIZE); + if (!mouse_is_inside_box(&strip_box, mval)) { + return -1; + } + + gizmo->mouse_over_seq = seq; + gizmo->mouse_over_handle_x = handle_x_get(seq, handle); + + wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0); + RNA_int_set(&op_elem->ptr, "handle_index", handle_index); + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return 0; +} + +static int gizmo_retime_handle_cursor_get(wmGizmo *gz) +{ + if (RNA_boolean_get(gz->ptr, "show_drag")) { + return WM_CURSOR_EW_SCROLL; + } + return WM_CURSOR_DEFAULT; +} + +static void gizmo_retime_handle_setup(wmGizmo *gz) +{ + gz->flag = WM_GIZMO_DRAW_MODAL; +} + +void GIZMO_GT_retime_handle(wmGizmoType *gzt) +{ + /* Identifiers. */ + gzt->idname = "GIZMO_GT_retime_handle_move"; + + /* Api callbacks. */ + gzt->setup = gizmo_retime_handle_setup; + gzt->draw = gizmo_retime_handle_draw; + gzt->test_select = gizmo_retime_handle_test_select; + gzt->cursor_get = gizmo_retime_handle_cursor_get; + gzt->struct_size = sizeof(RetimeHandleMoveGizmo); + + /* Currently only used for cursor display. */ + RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Remove Handle Gizmo + * \{ */ + +static void gizmo_retime_remove_draw(const bContext * /* C */, wmGizmo * /* gz */) +{ + /* Pass. */ +} + +static int gizmo_retime_remove_test_select(bContext *C, wmGizmo *gz, const int mval[2]) +{ + Scene *scene = CTX_data_scene(C); + Sequence *seq = active_seq_from_context(C); + + SEQ_retiming_data_ensure(CTX_data_scene(C), seq); + const SeqRetimingHandle *handle = mouse_over_handle_get(seq, UI_view2d_fromcontext(C), mval); + const int handle_index = SEQ_retiming_handle_index_get(seq, handle); + + if (handle == nullptr) { + return -1; + } + + if (handle_x_get(seq, handle) == SEQ_time_left_handle_frame_get(scene, seq) || + handle_index == 0) { + return -1; /* Ignore first handle. */ + } + + SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + if (handle == last_handle) { + return -1; /* Last handle can not be removed. */ + } + + rctf box = remove_box_get(C, seq); + + BLI_rctf_resize_x(&box, BLI_rctf_size_x(&box) + 2 * RETIME_HANDLE_TRIANGLE_SIZE); + if (!mouse_is_inside_box(&box, mval)) { + return -1; + } + + wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0); + RNA_int_set(&op_elem->ptr, "handle_index", handle_index); + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return 0; +} + +static int gizmo_retime_remove_cursor_get(wmGizmo *gz) +{ + if (RNA_boolean_get(gz->ptr, "show_drag")) { + return WM_CURSOR_ERASER; + } + return WM_CURSOR_DEFAULT; +} + +void GIZMO_GT_retime_remove(wmGizmoType *gzt) +{ + /* Identifiers. */ + gzt->idname = "GIZMO_GT_retime_handle_remove"; + + /* Api callbacks. */ + gzt->draw = gizmo_retime_remove_draw; + gzt->test_select = gizmo_retime_remove_test_select; + gzt->cursor_get = gizmo_retime_remove_cursor_get; + gzt->struct_size = sizeof(wmGizmo); + + /* Currently only used for cursor display. */ + RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", ""); +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h index c6bc2bf7dec..d88ff6a5dff 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.h +++ b/source/blender/editors/space_sequencer/sequencer_intern.h @@ -10,11 +10,17 @@ #include "DNA_sequence_types.h" #include "RNA_access.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Internal exports only. */ struct ARegion; struct ARegionType; struct Depsgraph; +struct wmGizmoGroupType; +struct wmGizmoType; struct Main; struct Scene; struct SeqCollection; @@ -298,3 +304,21 @@ int sequencer_image_seq_get_minmax_frame(struct wmOperator *op, int *r_numdigits); void sequencer_image_seq_reserve_frames( struct wmOperator *op, struct StripElem *se, int len, int minframe, int numdigits); + +/* sequencer_retiming.c */ +void SEQUENCER_OT_retiming_reset(struct wmOperatorType *ot); +void SEQUENCER_OT_retiming_handle_move(struct wmOperatorType *ot); +void SEQUENCER_OT_retiming_handle_add(struct wmOperatorType *ot); +void SEQUENCER_OT_retiming_handle_remove(struct wmOperatorType *ot); + +/* sequencer_gizmo_retime.c */ +void SEQUENCER_GGT_gizmo_retime(struct wmGizmoGroupType *gzgt); + +/* sequencer_gizmo_retime_type.c */ +void GIZMO_GT_retime_handle_add(struct wmGizmoType *gzt); +void GIZMO_GT_retime_handle(struct wmGizmoType *gzt); +void GIZMO_GT_retime_remove(struct wmGizmoType *gzt); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/space_sequencer/sequencer_ops.c b/source/blender/editors/space_sequencer/sequencer_ops.c index e0e2cfa42cc..bd2d53c1fca 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.c +++ b/source/blender/editors/space_sequencer/sequencer_ops.c @@ -68,6 +68,12 @@ void sequencer_operatortypes(void) WM_operatortype_append(SEQUENCER_OT_cursor_set); WM_operatortype_append(SEQUENCER_OT_scene_frame_range_update); + /* sequencer_retiming.c */ + WM_operatortype_append(SEQUENCER_OT_retiming_reset); + WM_operatortype_append(SEQUENCER_OT_retiming_handle_move); + WM_operatortype_append(SEQUENCER_OT_retiming_handle_add); + WM_operatortype_append(SEQUENCER_OT_retiming_handle_remove); + /* sequencer_select.c */ WM_operatortype_append(SEQUENCER_OT_select_all); WM_operatortype_append(SEQUENCER_OT_select); diff --git a/source/blender/editors/space_sequencer/sequencer_retiming.cc b/source/blender/editors/space_sequencer/sequencer_retiming.cc new file mode 100644 index 00000000000..84708b74e77 --- /dev/null +++ b/source/blender/editors/space_sequencer/sequencer_retiming.cc @@ -0,0 +1,377 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup spseq + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" + +#include "BKE_context.h" +#include "BKE_report.h" +#include "BKE_scene.h" + +#include "SEQ_iterator.h" +#include "SEQ_relations.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" +#include "SEQ_transform.h" + +#include "WM_api.h" + +#include "RNA_define.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "DEG_depsgraph.h" + +/* Own include. */ +#include "sequencer_intern.h" + +using blender::MutableSpan; + +static bool retiming_poll(bContext *C) +{ + if (!sequencer_edit_poll(C)) { + return false; + } + + const Editing *ed = SEQ_editing_get(CTX_data_scene(C)); + Sequence *seq = ed->act_seq; + + if (seq != nullptr && !SEQ_retiming_is_allowed(seq)) { + CTX_wm_operator_poll_msg_set(C, "This strip type can not be retimed"); + return false; + } + return true; +} + +static void retiming_handle_overlap(Scene *scene, Sequence *seq) +{ + ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene)); + SeqCollection *strips = SEQ_collection_create(__func__); + SEQ_collection_append_strip(seq, strips); + SeqCollection *dependant = SEQ_collection_create(__func__); + SEQ_collection_expand(scene, seqbase, strips, SEQ_query_strip_effect_chain); + SEQ_collection_remove_strip(seq, dependant); + SEQ_transform_handle_overlap(scene, seqbase, strips, dependant, true); + SEQ_collection_free(strips); + SEQ_collection_free(dependant); +} + +/*-------------------------------------------------------------------- */ +/** \name Retiming Reset + * \{ */ + +static int sequencer_retiming_reset_exec(bContext *C, wmOperator * /* op */) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + SEQ_retiming_data_clear(seq); + + retiming_handle_overlap(scene, seq); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; +} + +void SEQUENCER_OT_retiming_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset Retiming"; + ot->description = "Reset strip retiming"; + ot->idname = "SEQUENCER_OT_retiming_reset"; + + /* api callbacks */ + ot->exec = sequencer_retiming_reset_exec; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Move Handle + * \{ */ + +static SeqRetimingHandle *closest_retiming_handle_get(const bContext *C, + const Sequence *seq, + const float mouse_x) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + int best_distance = INT_MAX; + SeqRetimingHandle *closest_handle = nullptr; + + const float distance_threshold = UI_view2d_region_to_view_x(v2d, 10); + const float mouse_x_view = UI_view2d_region_to_view_x(v2d, mouse_x); + + for (int i = 0; i < SEQ_retiming_handles_count(seq); i++) { + SeqRetimingHandle *handle = seq->retiming_handles + i; + const int distance = round_fl_to_int(fabsf(handle->strip_frame_index - mouse_x_view)); + + if (distance < distance_threshold && distance < best_distance) { + best_distance = distance; + closest_handle = handle; + } + } + return closest_handle; +} + +static int sequencer_retiming_handle_move_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + int handle_index = 0; + if (RNA_struct_property_is_set(op->ptr, "handle_index")) { + handle_index = RNA_int_get(op->ptr, "handle_index"); + } + + /* Ensure retiming handle at left handle position. This way user gets more predictable result + * when strips have offsets. */ + const int left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq); + if (SEQ_retiming_add_handle(seq, left_handle_frame) != nullptr) { + handle_index++; /* Advance index, because new handle was created. */ + } + + MutableSpan handles = SEQ_retiming_handles_get(seq); + SeqRetimingHandle *handle = nullptr; + if (RNA_struct_property_is_set(op->ptr, "handle_index")) { + BLI_assert(handle_index < handles.size()); + handle = &handles[handle_index]; + } + else { + handle = closest_retiming_handle_get(C, seq, event->mval[0]); + } + + if (handle == nullptr) { + BKE_report(op->reports, RPT_ERROR, "No handle available"); + return OPERATOR_CANCELLED; + } + + op->customdata = handle; + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int sequencer_retiming_handle_move_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + const ARegion *region = CTX_wm_region(C); + const View2D *v2d = ®ion->v2d; + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + switch (event->type) { + case MOUSEMOVE: { + float mouse_x = UI_view2d_region_to_view_x(v2d, event->mval[0]); + int offset = 0; + + SeqRetimingHandle *handle = (SeqRetimingHandle *)op->customdata; + SeqRetimingHandle *handle_prev = handle - 1; + + /* Limit retiming handle movement. */ + int xmin = SEQ_time_start_frame_get(seq) + handle_prev->strip_frame_index + 1; + mouse_x = max_ff(xmin, mouse_x); + offset = mouse_x - (SEQ_time_start_frame_get(seq) + handle->strip_frame_index); + + SEQ_retiming_offset_handle(scene, seq, handle, offset); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_RUNNING_MODAL; + } + case LEFTMOUSE: + case EVT_RETKEY: + case EVT_SPACEKEY: { + retiming_handle_overlap(scene, seq); + DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; + } + + case EVT_ESCKEY: + case RIGHTMOUSE: { + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_CANCELLED; + } + } + return OPERATOR_RUNNING_MODAL; +} + +void SEQUENCER_OT_retiming_handle_move(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Move Retiming Handle"; + ot->description = "Move retiming handle"; + ot->idname = "SEQUENCER_OT_retiming_handle_move"; + + /* api callbacks */ + ot->invoke = sequencer_retiming_handle_move_invoke; + ot->modal = sequencer_retiming_handle_move_modal; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, + "handle_index", + 0, + 0, + INT_MAX, + "Handle Index", + "Index of handle to be moved", + 0, + INT_MAX); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Add Handle + * \{ */ + +static int sequesequencer_retiming_handle_add_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + float timeline_frame; + if (RNA_struct_property_is_set(op->ptr, "timeline_frame")) { + timeline_frame = RNA_int_get(op->ptr, "timeline_frame"); + } + else { + timeline_frame = BKE_scene_frame_get(scene); + } + + bool inserted = false; + const float end_frame = seq->start + SEQ_time_strip_length_get(scene, seq); + if (seq->start < timeline_frame && end_frame > timeline_frame) { + SEQ_retiming_add_handle(seq, timeline_frame); + inserted = true; + } + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + + return inserted ? OPERATOR_FINISHED : OPERATOR_PASS_THROUGH; +} + +void SEQUENCER_OT_retiming_handle_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Retiming Handle"; + ot->description = "Add retiming Handle"; + ot->idname = "SEQUENCER_OT_retiming_handle_add"; + + /* api callbacks */ + ot->exec = sequesequencer_retiming_handle_add_exec; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, + "timeline_frame", + 0, + 0, + INT_MAX, + "Timeline Frame", + "Frame where handle will be added", + 0, + INT_MAX); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Remove Handle + * \{ */ + +static int sequencer_retiming_handle_remove_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + + SeqRetimingHandle *handle = (SeqRetimingHandle *)op->customdata; + SEQ_retiming_remove_handle(seq, handle); + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; +} + +static int sequencer_retiming_handle_remove_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + const Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + const Sequence *seq = ed->act_seq; + + if (seq == nullptr) { + return OPERATOR_CANCELLED; + } + + MutableSpan handles = SEQ_retiming_handles_get(seq); + SeqRetimingHandle *handle = nullptr; + + if (RNA_struct_property_is_set(op->ptr, "handle_index")) { + const int handle_index = RNA_int_get(op->ptr, "handle_index"); + BLI_assert(handle_index < handles.size()); + handle = &handles[handle_index]; + } + else { + handle = closest_retiming_handle_get(C, seq, event->mval[0]); + } + + if (handle == nullptr) { + BKE_report(op->reports, RPT_ERROR, "No handle available"); + return OPERATOR_CANCELLED; + } + + op->customdata = handle; + return sequencer_retiming_handle_remove_exec(C, op); +} + +void SEQUENCER_OT_retiming_handle_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Retiming Handle"; + ot->description = "Remove retiming handle"; + ot->idname = "SEQUENCER_OT_retiming_handle_remove"; + + /* api callbacks */ + ot->invoke = sequencer_retiming_handle_remove_invoke; + ot->exec = sequencer_retiming_handle_remove_exec; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, + "handle_index", + 0, + 0, + INT_MAX, + "Handle Index", + "Index of handle to be removed", + 0, + INT_MAX); +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 2f8ee37be2a..5661208dc15 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -494,10 +494,15 @@ static void sequencer_gizmos(void) wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure( &(const struct wmGizmoMapType_Params){SPACE_SEQ, RGN_TYPE_PREVIEW}); + WM_gizmotype_append(GIZMO_GT_retime_handle_add); + WM_gizmotype_append(GIZMO_GT_retime_handle); + WM_gizmotype_append(GIZMO_GT_retime_remove); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_translate); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_resize); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_rotate); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo_retime); WM_gizmogrouptype_append_and_link(gzmap_type, SEQUENCER_GGT_navigate); } @@ -645,6 +650,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params) case ND_SEQUENCER: case ND_RENDER_RESULT: ED_region_tag_redraw(region); + WM_gizmomap_tag_refresh(region->gizmo_map); break; } break; @@ -658,6 +664,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params) case NC_SPACE: if (wmn->data == ND_SPACE_SEQUENCER) { ED_region_tag_redraw(region); + WM_gizmomap_tag_refresh(region->gizmo_map); } break; case NC_ID: @@ -668,6 +675,7 @@ static void sequencer_main_region_listener(const wmRegionListenerParams *params) case NC_SCREEN: if (ELEM(wmn->data, ND_ANIMPLAY)) { ED_region_tag_redraw(region); + WM_gizmomap_tag_refresh(region->gizmo_map); } break; } @@ -1069,7 +1077,6 @@ void ED_spacetype_sequencer(void) art->on_view2d_changed = sequencer_main_region_view2d_changed; art->listener = sequencer_main_region_listener; art->message_subscribe = sequencer_main_region_message_subscribe; - /* NOTE: inclusion of #ED_KEYMAP_GIZMO is currently for scripts and isn't used by default. */ art->keymapflag = ED_KEYMAP_TOOL | ED_KEYMAP_GIZMO | ED_KEYMAP_VIEW2D | ED_KEYMAP_FRAMES | ED_KEYMAP_ANIMATION; BLI_addhead(&st->regiontypes, art); diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 47039c740c1..e64f3f1ab24 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -120,6 +120,12 @@ typedef struct Strip { ColorManagedColorspaceSettings colorspace_settings; } Strip; +typedef struct SeqRetimingHandle { + int strip_frame_index; + int _pad0[2]; + float retiming_factor; /* Value between 0-1 mapped to original content range. */ +} SeqRetimingHandle; + typedef struct SequenceRuntime { SessionUUID session_uuid; } SequenceRuntime; @@ -164,12 +170,12 @@ typedef struct Sequence { float startstill, endstill; /** Machine: the strip channel */ int machine; - int _pad3; + int _pad; /** Starting and ending points of the effect strip. Undefined for other strip types. */ int startdisp, enddisp; float sat; float mul; - float _pad; + float _pad1; short anim_preseek; /* UNUSED. */ /** Streamindex for movie or sound files with several streams. */ @@ -231,7 +237,7 @@ typedef struct Sequence { int8_t color_tag; char alpha_mode; - char _pad4[2]; + char _pad2[2]; int cache_flag; @@ -241,7 +247,7 @@ typedef struct Sequence { /* Multiview */ char views_format; - char _pad1[3]; + char _pad3[3]; struct Stereo3dFormat *stereo3d_format; struct IDProperty *prop; @@ -251,9 +257,13 @@ typedef struct Sequence { /* Playback rate of strip content in frames per second. */ float media_playback_rate; - /* Multiply strip playback speed. */ float speed_factor; + struct SeqRetimingHandle *retiming_handles; + void *_pad5; + int retiming_handle_num; + char _pad6[4]; + SequenceRuntime runtime; } Sequence; diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index a1f9bf776ae..fe143903f22 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -468,6 +468,7 @@ void RNA_api_region_view3d(struct StructRNA *srna); void RNA_api_texture(struct StructRNA *srna); void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop, bool metastrip); void RNA_api_sequence_elements(BlenderRNA *brna, PropertyRNA *cprop); +void RNA_api_sequence_retiming_handles(BlenderRNA *brna, PropertyRNA *cprop); void RNA_api_sound(struct StructRNA *srna); void RNA_api_vfont(struct StructRNA *srna); void RNA_api_workspace(struct StructRNA *srna); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index 55a71ceb14e..369cde9cc89 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -43,6 +43,7 @@ #include "SEQ_prefetch.h" #include "SEQ_proxy.h" #include "SEQ_relations.h" +#include "SEQ_retiming.h" #include "SEQ_select.h" #include "SEQ_sequencer.h" #include "SEQ_sound.h" @@ -272,7 +273,7 @@ static int rna_SequenceEditor_elements_length(PointerRNA *ptr) return (int)olen; } -static void rna_SequenceEditor_elements_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +static void rna_Sequence_elements_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { Sequence *seq = (Sequence *)ptr->data; rna_iterator_array_begin(iter, @@ -283,6 +284,85 @@ static void rna_SequenceEditor_elements_begin(CollectionPropertyIterator *iter, NULL); } +static int rna_Sequence_retiming_handles_length(PointerRNA *ptr) +{ + return SEQ_retiming_handles_count((Sequence *)ptr->data); +} + +static void rna_SequenceEditor_retiming_handles_begin(CollectionPropertyIterator *iter, + PointerRNA *ptr) +{ + Sequence *seq = (Sequence *)ptr->data; + rna_iterator_array_begin(iter, + (void *)seq->retiming_handles, + sizeof(SeqRetimingHandle), + SEQ_retiming_handles_count(seq), + 0, + NULL); +} + +static Sequence *strip_by_handle_find(Scene *scene, SeqRetimingHandle *handle) +{ + Editing *ed = SEQ_editing_get(scene); + SeqCollection *strips = SEQ_query_all_strips_recursive(&ed->seqbase); + + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, strips) { + const int retiming_handle_count = SEQ_retiming_handles_count(seq); + SeqRetimingHandle *first = seq->retiming_handles; + SeqRetimingHandle *last = seq->retiming_handles + retiming_handle_count - 1; + + if (handle >= first && handle <= last) { + return seq; + } + } + + return NULL; +} + +static void rna_Sequence_retiming_handle_remove(ID *id, SeqRetimingHandle *handle) +{ + Scene *scene = (Scene *)id; + Sequence *seq = strip_by_handle_find(scene, handle); + + if (seq == NULL) { + return; + } + + SEQ_retiming_remove_handle(seq, handle); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL); +} + +static int rna_Sequence_retiming_handle_frame_get(PointerRNA *ptr) +{ + SeqRetimingHandle *handle = (SeqRetimingHandle *)ptr->data; + Scene *scene = (Scene *)ptr->owner_id; + Sequence *seq = strip_by_handle_find(scene, handle); + + if (seq == NULL) { + return 0; + } + + return SEQ_time_start_frame_get(seq) + handle->strip_frame_index; +} + +static void rna_Sequence_retiming_handle_frame_set(PointerRNA *ptr, int value) +{ + SeqRetimingHandle *handle = (SeqRetimingHandle *)ptr->data; + Scene *scene = (Scene *)ptr->owner_id; + Sequence *seq = strip_by_handle_find(scene, handle); + + if (seq == NULL) { + return; + } + + const int offset = value - SEQ_time_start_frame_get(seq) + handle->strip_frame_index; + SEQ_retiming_offset_handle(scene, seq, handle, offset); + SEQ_relations_invalidate_cache_raw(scene, seq); +} + static void rna_Sequence_views_format_update(Main *bmain, Scene *scene, PointerRNA *ptr) { rna_Sequence_invalidate_raw_update(bmain, scene, ptr); @@ -1493,6 +1573,31 @@ static void rna_def_strip_element(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Orig FPS", "Original frames per second"); } +static void rna_def_retiming_handle(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "RetimingHandle", NULL); + RNA_def_struct_ui_text( + srna, + "Retiming Handle", + "Handle mapped to particular frame that can be moved to change playback speed"); + RNA_def_struct_sdna(srna, "SeqRetimingHandle"); + + prop = RNA_def_property(srna, "timeline_frame", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "strip_frame_index"); + RNA_def_property_int_funcs(prop, + "rna_Sequence_retiming_handle_frame_get", + "rna_Sequence_retiming_handle_frame_set", + NULL); + RNA_def_property_ui_text(prop, "Timeline Frame", "Position of retiming handle in timeline"); + + FunctionRNA *func = RNA_def_function(srna, "remove", "rna_Sequence_retiming_handle_remove"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID); + RNA_def_function_ui_description(func, "Remove retiming handle"); +} + static void rna_def_strip_crop(BlenderRNA *brna) { StructRNA *srna; @@ -2564,7 +2669,7 @@ static void rna_def_image(BlenderRNA *brna) RNA_def_property_struct_type(prop, "SequenceElement"); RNA_def_property_ui_text(prop, "Elements", ""); RNA_def_property_collection_funcs(prop, - "rna_SequenceEditor_elements_begin", + "rna_Sequence_elements_begin", "rna_iterator_array_next", "rna_iterator_array_end", "rna_iterator_array_get", @@ -2596,7 +2701,6 @@ static void rna_def_image(BlenderRNA *brna) rna_def_proxy(srna); rna_def_input(srna); rna_def_color_management(srna); - rna_def_speed_factor(srna); } static void rna_def_meta(BlenderRNA *brna) @@ -2628,7 +2732,6 @@ static void rna_def_meta(BlenderRNA *brna) rna_def_filter_video(srna); rna_def_proxy(srna); rna_def_input(srna); - rna_def_speed_factor(srna); } static void rna_def_scene(BlenderRNA *brna) @@ -2677,7 +2780,6 @@ static void rna_def_scene(BlenderRNA *brna) rna_def_proxy(srna); rna_def_input(srna); rna_def_movie_types(srna); - rna_def_speed_factor(srna); } static void rna_def_movie(BlenderRNA *brna) @@ -2705,7 +2807,7 @@ static void rna_def_movie(BlenderRNA *brna) RNA_def_property_struct_type(prop, "SequenceElement"); RNA_def_property_ui_text(prop, "Elements", ""); RNA_def_property_collection_funcs(prop, - "rna_SequenceEditor_elements_begin", + "rna_Sequence_elements_begin", "rna_iterator_array_next", "rna_iterator_array_end", "rna_iterator_array_get", @@ -2714,6 +2816,21 @@ static void rna_def_movie(BlenderRNA *brna) NULL, NULL); + prop = RNA_def_property(srna, "retiming_handles", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "retiming_handles", NULL); + RNA_def_property_struct_type(prop, "RetimingHandle"); + RNA_def_property_ui_text(prop, "Retiming Hndles", ""); + RNA_def_property_collection_funcs(prop, + "rna_SequenceEditor_retiming_handles_begin", + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_iterator_array_get", + "rna_Sequence_retiming_handles_length", + NULL, + NULL, + NULL); + RNA_api_sequence_retiming_handles(brna, prop); + prop = RNA_def_property(srna, "filepath", PROP_STRING, PROP_FILEPATH); RNA_def_property_ui_text(prop, "File", ""); RNA_def_property_string_funcs(prop, @@ -2761,7 +2878,6 @@ static void rna_def_movie(BlenderRNA *brna) rna_def_input(srna); rna_def_color_management(srna); rna_def_movie_types(srna); - rna_def_speed_factor(srna); } static void rna_def_movieclip(BlenderRNA *brna) @@ -2789,7 +2905,6 @@ static void rna_def_movieclip(BlenderRNA *brna) rna_def_filter_video(srna); rna_def_input(srna); rna_def_movie_types(srna); - rna_def_speed_factor(srna); } static void rna_def_mask(BlenderRNA *brna) @@ -2808,7 +2923,6 @@ static void rna_def_mask(BlenderRNA *brna) rna_def_filter_video(srna); rna_def_input(srna); - rna_def_speed_factor(srna); } static void rna_def_sound(BlenderRNA *brna) @@ -3613,6 +3727,7 @@ void RNA_def_sequencer(BlenderRNA *brna) rna_def_color_balance(brna); rna_def_strip_element(brna); + rna_def_retiming_handle(brna); rna_def_strip_proxy(brna); rna_def_strip_color_balance(brna); rna_def_strip_crop(brna); diff --git a/source/blender/makesrna/intern/rna_sequencer_api.c b/source/blender/makesrna/intern/rna_sequencer_api.c index e1ccffdd2c1..c12c3ffe6b0 100644 --- a/source/blender/makesrna/intern/rna_sequencer_api.c +++ b/source/blender/makesrna/intern/rna_sequencer_api.c @@ -44,6 +44,7 @@ # include "SEQ_effects.h" # include "SEQ_relations.h" # include "SEQ_render.h" +# include "SEQ_retiming.h" # include "SEQ_sequencer.h" # include "SEQ_time.h" @@ -637,6 +638,29 @@ static void rna_Sequence_invalidate_cache_rnafunc(ID *id, Sequence *self, int ty } } +static SeqRetimingHandle *rna_Sequence_retiming_handles_add(ID *id, + Sequence *seq, + int timeline_frame) +{ + Scene *scene = (Scene *)id; + + SeqRetimingHandle *handle = SEQ_retiming_add_handle(seq, timeline_frame); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL); + return handle; +} + +static void rna_Sequence_retiming_handles_reset(ID *id, Sequence *seq) +{ + Scene *scene = (Scene *)id; + + SEQ_retiming_data_clear(seq); + + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL); +} + #else void RNA_api_sequence_strip(StructRNA *srna) @@ -744,6 +768,30 @@ void RNA_api_sequence_elements(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } +void RNA_api_sequence_retiming_handles(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + + RNA_def_property_srna(cprop, "RetimingHandles"); + srna = RNA_def_struct(brna, "RetimingHandles", NULL); + RNA_def_struct_sdna(srna, "Sequence"); + RNA_def_struct_ui_text(srna, "RetimingHandles", "Collection of RetimingHandle"); + + FunctionRNA *func = RNA_def_function(srna, "add", "rna_Sequence_retiming_handles_add"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID); + RNA_def_int( + func, "timeline_frame", 0, -MAXFRAME, MAXFRAME, "Timeline Frame", "", -MAXFRAME, MAXFRAME); + RNA_def_function_ui_description(func, "Add retiming handle"); + /* return type */ + PropertyRNA *parm = RNA_def_pointer( + func, "retiming_handle", "RetimingHandle", "", "New RetimingHandle"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "reset", "rna_Sequence_retiming_handles_reset"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID); + RNA_def_function_ui_description(func, "Remove all retiming handles"); +} + void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop, const bool metastrip) { StructRNA *srna; diff --git a/source/blender/sequencer/CMakeLists.txt b/source/blender/sequencer/CMakeLists.txt index 76a3a060e19..7f1e38752c9 100644 --- a/source/blender/sequencer/CMakeLists.txt +++ b/source/blender/sequencer/CMakeLists.txt @@ -42,6 +42,8 @@ set(SRC SEQ_proxy.h SEQ_relations.h SEQ_render.h + SEQ_retiming.h + SEQ_retiming.hh SEQ_select.h SEQ_sequencer.h SEQ_sound.h @@ -76,6 +78,7 @@ set(SRC intern/strip_add.c intern/strip_edit.c intern/strip_relations.c + intern/strip_retiming.cc intern/strip_select.c intern/strip_time.c intern/strip_time.h diff --git a/source/blender/sequencer/SEQ_retiming.h b/source/blender/sequencer/SEQ_retiming.h new file mode 100644 index 00000000000..84254fda0ea --- /dev/null +++ b/source/blender/sequencer/SEQ_retiming.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2004 Blender Foundation. All rights reserved. */ + +#pragma once + +/** \file + * \ingroup sequencer + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct Scene; +struct Sequence; +struct SeqRetimingHandle; + +int SEQ_retiming_handles_count(const struct Sequence *seq); +bool SEQ_retiming_is_active(const struct Sequence *seq); +void SEQ_retiming_data_ensure(const struct Scene *scene, struct Sequence *seq); +void SEQ_retiming_data_clear(struct Sequence *seq); +bool SEQ_retiming_is_allowed(const struct Sequence *seq); + +/** + * Add new retiming handle. + * This function always reallocates memory, so when function is used all stored pointers will + * become invalid. + */ +struct SeqRetimingHandle *SEQ_retiming_add_handle(struct Sequence *seq, const int timeline_frame); +struct SeqRetimingHandle *SEQ_retiming_last_handle_get(const struct Sequence *seq); +void SEQ_retiming_remove_handle(struct Sequence *seq, struct SeqRetimingHandle *handle); +void SEQ_retiming_offset_handle(const struct Scene *scene, + struct Sequence *seq, + struct SeqRetimingHandle *handle, + const int offset); +float SEQ_retiming_handle_speed_get(const struct Scene *scene, + const struct Sequence *seq, + const struct SeqRetimingHandle *handle); +int SEQ_retiming_handle_index_get(const struct Sequence *seq, + const struct SeqRetimingHandle *handle); +#ifdef __cplusplus +} +#endif diff --git a/source/blender/sequencer/SEQ_retiming.hh b/source/blender/sequencer/SEQ_retiming.hh new file mode 100644 index 00000000000..128a3020534 --- /dev/null +++ b/source/blender/sequencer/SEQ_retiming.hh @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#pragma once + +/** \file + * \ingroup sequencer + */ + +#include "BLI_span.hh" + +struct Sequence; +struct SeqRetimingHandle; + +blender::MutableSpan SEQ_retiming_handles_get(const Sequence *seq); diff --git a/source/blender/sequencer/SEQ_time.h b/source/blender/sequencer/SEQ_time.h index 11dbe0dab0a..45ec3bbc8ae 100644 --- a/source/blender/sequencer/SEQ_time.h +++ b/source/blender/sequencer/SEQ_time.h @@ -136,7 +136,6 @@ void SEQ_time_start_frame_set(const struct Scene *scene, struct Sequence *seq, i * \note this function is currently only used internally and in versioning code. */ void SEQ_time_update_meta_strip_range(const struct Scene *scene, struct Sequence *seq_meta); - #ifdef __cplusplus } #endif diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index 25f633ef27c..478b3acda6a 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -18,6 +18,7 @@ #include "BLI_listbase.h" +#include "BKE_fcurve.h" #include "BKE_idprop.h" #include "BKE_lib_id.h" #include "BKE_sound.h" @@ -34,6 +35,7 @@ #include "SEQ_modifier.h" #include "SEQ_proxy.h" #include "SEQ_relations.h" +#include "SEQ_retiming.h" #include "SEQ_select.h" #include "SEQ_sequencer.h" #include "SEQ_sound.h" @@ -218,6 +220,12 @@ static void seq_sequence_free_ex(Scene *scene, SEQ_channels_free(&seq->channels); } + if (seq->retiming_handles != NULL) { + MEM_freeN(seq->retiming_handles); + seq->retiming_handles = NULL; + seq->retiming_handle_num = 0; + } + MEM_freeN(seq); } @@ -576,6 +584,11 @@ static Sequence *seq_dupli(const Scene *scene_src, } } + if (seq->retiming_handles != NULL) { + seqn->retiming_handles = MEM_dupallocN(seq->retiming_handles); + seqn->retiming_handle_num = seq->retiming_handle_num; + } + return seqn; } @@ -749,6 +762,12 @@ static bool seq_write_data_cb(Sequence *seq, void *userdata) LISTBASE_FOREACH (SeqTimelineChannel *, channel, &seq->channels) { BLO_write_struct(writer, SeqTimelineChannel, channel); } + + if (seq->retiming_handles != NULL) { + int size = SEQ_retiming_handles_count(seq); + BLO_write_struct_array(writer, SeqRetimingHandle, size, seq->retiming_handles); + } + return true; } @@ -818,6 +837,11 @@ static bool seq_read_data_cb(Sequence *seq, void *user_data) SEQ_modifier_blend_read_data(reader, &seq->modifiers); BLO_read_list(reader, &seq->channels); + + if (seq->retiming_handles != NULL) { + BLO_read_data_address(reader, &seq->retiming_handles); + } + return true; } void SEQ_blend_read(BlendDataReader *reader, ListBase *seqbase) diff --git a/source/blender/sequencer/intern/strip_edit.c b/source/blender/sequencer/intern/strip_edit.c index d0f7521bd10..b5d4f44cb39 100644 --- a/source/blender/sequencer/intern/strip_edit.c +++ b/source/blender/sequencer/intern/strip_edit.c @@ -275,9 +275,7 @@ static void seq_split_set_right_hold_offset(Main *bmain, /* Adjust within range of strip contents. */ else if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) { seq->endofs = 0; - float speed_factor = (seq->type == SEQ_TYPE_SOUND_RAM) ? - seq_time_media_playback_rate_factor_get(scene, seq) : - seq_time_playback_rate_factor_get(scene, seq); + float speed_factor = seq_time_media_playback_rate_factor_get(scene, seq); seq->anim_endofs += round_fl_to_int((content_end - timeline_frame) * speed_factor); } @@ -296,9 +294,7 @@ static void seq_split_set_left_hold_offset(Main *bmain, /* Adjust within range of strip contents. */ if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) { - float speed_factor = (seq->type == SEQ_TYPE_SOUND_RAM) ? - seq_time_media_playback_rate_factor_get(scene, seq) : - seq_time_playback_rate_factor_get(scene, seq); + float speed_factor = seq_time_media_playback_rate_factor_get(scene, seq); seq->anim_startofs += round_fl_to_int((timeline_frame - content_start) * speed_factor); seq->start = timeline_frame; seq->startofs = 0; diff --git a/source/blender/sequencer/intern/strip_retiming.cc b/source/blender/sequencer/intern/strip_retiming.cc new file mode 100644 index 00000000000..b3da8ad3f47 --- /dev/null +++ b/source/blender/sequencer/intern/strip_retiming.cc @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup bke + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_sequence_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_span.hh" + +#include "BKE_fcurve.h" +#include "BKE_movieclip.h" +#include "BKE_scene.h" +#include "BKE_sound.h" + +#include "DNA_anim_types.h" +#include "DNA_sound_types.h" + +#include "IMB_imbuf.h" + +#include "RNA_prototypes.h" + +#include "SEQ_channels.h" +#include "SEQ_iterator.h" +#include "SEQ_relations.h" +#include "SEQ_render.h" +#include "SEQ_retiming.h" +#include "SEQ_retiming.hh" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" +#include "SEQ_transform.h" + +#include "sequencer.h" +#include "strip_time.h" +#include "utils.h" + +using blender::MutableSpan; + +MutableSpan SEQ_retiming_handles_get(const Sequence *seq) +{ + blender::MutableSpan handles(seq->retiming_handles, seq->retiming_handle_num); + return handles; +} + +struct SeqRetimingHandle *SEQ_retiming_last_handle_get(const struct Sequence *seq) +{ + return seq->retiming_handles + seq->retiming_handle_num - 1; +} + +int SEQ_retiming_handle_index_get(const Sequence *seq, const SeqRetimingHandle *handle) +{ + return handle - seq->retiming_handles; +} + +static bool seq_retiming_is_last_handle(const Sequence *seq, const SeqRetimingHandle *handle) +{ + return SEQ_retiming_handle_index_get(seq, handle) == seq->retiming_handle_num - 1; +} + +static const SeqRetimingHandle *retiming_find_segment_start_handle(const Sequence *seq, + const int frame_index) +{ + const SeqRetimingHandle *start_handle = nullptr; + for (auto const &handle : SEQ_retiming_handles_get(seq)) { + if (seq_retiming_is_last_handle(seq, &handle)) { + break; + } + if (handle.strip_frame_index > frame_index) { + break; + } + + start_handle = &handle; + } + + return start_handle; +} + +int SEQ_retiming_handles_count(const Sequence *seq) +{ + return seq->retiming_handle_num; +} + +void SEQ_retiming_data_ensure(const Scene *scene, Sequence *seq) +{ + if (!SEQ_retiming_is_allowed(seq)) { + return; + } + + if (seq->retiming_handles != nullptr) { + return; + } + + seq->retiming_handles = (SeqRetimingHandle *)MEM_calloc_arrayN( + 2, sizeof(SeqRetimingHandle), __func__); + SeqRetimingHandle *handle = seq->retiming_handles + 1; + handle->strip_frame_index = seq_time_strip_original_content_length_get(scene, seq) - 1; + handle->retiming_factor = 1.0f; + seq->retiming_handle_num = 2; +} + +void SEQ_retiming_data_clear(Sequence *seq) +{ + seq->retiming_handles = nullptr; + seq->retiming_handle_num = 0; +} + +bool SEQ_retiming_is_active(const Sequence *seq) +{ + return seq->retiming_handle_num > 1; +} + +bool SEQ_retiming_is_allowed(const Sequence *seq) +{ + return ELEM(seq->type, + SEQ_TYPE_IMAGE, + SEQ_TYPE_META, + SEQ_TYPE_SCENE, + SEQ_TYPE_MOVIE, + SEQ_TYPE_MOVIECLIP, + SEQ_TYPE_MASK); +} + +float seq_retiming_evaluate(const Sequence *seq, const int frame_index) +{ + const SeqRetimingHandle *previous_handle = retiming_find_segment_start_handle(seq, frame_index); + const SeqRetimingHandle *next_handle = previous_handle + 1; + const int previous_handle_index = previous_handle - seq->retiming_handles; + + BLI_assert(previous_handle_index < seq->retiming_handle_num); + UNUSED_VARS_NDEBUG(previous_handle_index); + + if (next_handle == nullptr) { + return 1.0f; + } + + const int segment_length = next_handle->strip_frame_index - previous_handle->strip_frame_index; + const int segment_frame_index = frame_index - previous_handle->strip_frame_index; + const float segment_fac = segment_frame_index / (float)segment_length; + + const float target_diff = next_handle->retiming_factor - previous_handle->retiming_factor; + return previous_handle->retiming_factor + (target_diff * segment_fac); +} + +SeqRetimingHandle *SEQ_retiming_add_handle(Sequence *seq, const int timeline_frame) +{ + float frame_index = timeline_frame - SEQ_time_start_frame_get(seq); + float value = seq_retiming_evaluate(seq, frame_index); + + const SeqRetimingHandle *closest_handle = retiming_find_segment_start_handle(seq, frame_index); + if (closest_handle->strip_frame_index == frame_index) { + return nullptr; /* Retiming handle already exists. */ + } + + SeqRetimingHandle *handles = seq->retiming_handles; + size_t handle_count = SEQ_retiming_handles_count(seq); + const int new_handle_index = closest_handle - handles + 1; + BLI_assert(new_handle_index >= 0); + BLI_assert(new_handle_index < handle_count); + + SeqRetimingHandle *new_handles = (SeqRetimingHandle *)MEM_callocN( + (handle_count + 1) * sizeof(SeqRetimingHandle), __func__); + if (new_handle_index > 0) { + memcpy(new_handles, handles, new_handle_index * sizeof(SeqRetimingHandle)); + } + if (new_handle_index < handle_count) { + memcpy(new_handles + new_handle_index + 1, + handles + new_handle_index, + (handle_count - new_handle_index) * sizeof(SeqRetimingHandle)); + } + MEM_freeN(handles); + seq->retiming_handles = new_handles; + seq->retiming_handle_num++; + + SeqRetimingHandle *added_handle = (new_handles + new_handle_index); + added_handle->strip_frame_index = frame_index; + added_handle->retiming_factor = value; + + return added_handle; +} + +void SEQ_retiming_offset_handle(const Scene *scene, + Sequence *seq, + SeqRetimingHandle *handle, + const int offset) +{ + if (handle->strip_frame_index == 0) { + return; /* First handle can not be moved. */ + } + + MutableSpan handles = SEQ_retiming_handles_get(seq); + for (; handle < handles.end(); handle++) { + handle->strip_frame_index += offset; + } + + SEQ_time_update_meta_strip_range(scene, seq_sequence_lookup_meta_by_seq(scene, seq)); + seq_time_update_effects_strip_range(scene, seq_sequence_lookup_effects_by_seq(scene, seq)); +} + +void SEQ_retiming_remove_handle(Sequence *seq, SeqRetimingHandle *handle) +{ + SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + if (handle->strip_frame_index == 0 || handle == last_handle) { + return; /* First and last handle can not be removed. */ + } + + size_t handle_count = SEQ_retiming_handles_count(seq); + SeqRetimingHandle *handles = (SeqRetimingHandle *)MEM_callocN( + (handle_count - 1) * sizeof(SeqRetimingHandle), __func__); + + const int handle_index = handle - seq->retiming_handles; + memcpy(handles, seq->retiming_handles, (handle_index) * sizeof(SeqRetimingHandle)); + memcpy(handles + handle_index, + seq->retiming_handles + handle_index + 1, + (handle_count - handle_index - 1) * sizeof(SeqRetimingHandle)); + MEM_freeN(seq->retiming_handles); + seq->retiming_handles = handles; + seq->retiming_handle_num--; +} + +float SEQ_retiming_handle_speed_get(const Scene *scene, + const Sequence *seq, + const SeqRetimingHandle *handle) +{ + if (handle->strip_frame_index == 0) { + return 1.0f; + } + + const SeqRetimingHandle *handle_prev = handle - 1; + + const int frame_index_max = seq_time_strip_original_content_length_get(scene, seq) - 1; + const int frame_retimed_prev = round_fl_to_int(handle_prev->retiming_factor * frame_index_max); + const int frame_index_prev = handle_prev->strip_frame_index; + const int frame_retimed = round_fl_to_int(handle->retiming_factor * frame_index_max); + const int frame_index = handle->strip_frame_index; + + const int fragment_length_retimed = frame_retimed - frame_retimed_prev; + const int fragment_length_original = frame_index - frame_index_prev; + + const float speed = (float)fragment_length_retimed / (float)fragment_length_original; + return speed; +} diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c index 7ef3f0f5a47..84f01df8db8 100644 --- a/source/blender/sequencer/intern/strip_time.c +++ b/source/blender/sequencer/intern/strip_time.c @@ -7,22 +7,31 @@ * \ingroup bke */ +#include "MEM_guardedalloc.h" + #include "DNA_scene_types.h" #include "DNA_sequence_types.h" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BKE_fcurve.h" #include "BKE_movieclip.h" #include "BKE_scene.h" #include "BKE_sound.h" +#include "DNA_anim_types.h" #include "DNA_sound_types.h" + #include "IMB_imbuf.h" +#include "RNA_prototypes.h" + #include "SEQ_channels.h" #include "SEQ_iterator.h" +#include "SEQ_relations.h" #include "SEQ_render.h" +#include "SEQ_retiming.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" #include "SEQ_transform.h" @@ -44,9 +53,13 @@ float seq_time_media_playback_rate_factor_get(const Scene *scene, const Sequence return seq->media_playback_rate / scene_playback_rate; } -float seq_time_playback_rate_factor_get(const Scene *scene, const Sequence *seq) +int seq_time_strip_original_content_length_get(const Scene *scene, const Sequence *seq) { - return seq_time_media_playback_rate_factor_get(scene, seq) * seq->speed_factor; + if (seq->type == SEQ_TYPE_SOUND_RAM) { + return seq->len; + } + + return seq->len / seq_time_media_playback_rate_factor_get(scene, seq); } float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_frame) @@ -54,6 +67,7 @@ float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_fra float frame_index; float sta = SEQ_time_start_frame_get(seq); float end = SEQ_time_content_end_frame_get(scene, seq) - 1; + const float length = seq_time_strip_original_content_length_get(scene, seq); if (seq->type & SEQ_TYPE_EFFECT) { end = SEQ_time_right_handle_frame_get(scene, seq); @@ -70,9 +84,15 @@ float seq_give_frame_index(const Scene *scene, Sequence *seq, float timeline_fra frame_index = timeline_frame - sta; } - /* Clamp frame index to strip frame range. */ - frame_index = clamp_f(frame_index, 0, end - sta); - frame_index *= seq_time_playback_rate_factor_get(scene, seq); + if (SEQ_retiming_is_active(seq)) { + const float retiming_factor = seq_retiming_evaluate(seq, frame_index); + frame_index = retiming_factor * (length - 1); + } + else { + frame_index *= seq_time_media_playback_rate_factor_get(scene, seq); + /* Clamp frame index to strip frame range. */ + frame_index = clamp_f(frame_index, 0, end - sta); + } if (seq->strobe < 1.0f) { seq->strobe = 1.0f; @@ -484,7 +504,13 @@ int SEQ_time_strip_length_get(const Scene *scene, const Sequence *seq) return seq->len; } - return seq->len / seq_time_playback_rate_factor_get(scene, seq); + if (SEQ_retiming_is_active(seq)) { + SeqRetimingHandle *handle_start = seq->retiming_handles; + SeqRetimingHandle *handle_end = seq->retiming_handles + (SEQ_retiming_handles_count(seq) - 1); + return handle_end->strip_frame_index - handle_start->strip_frame_index + 1; + } + + return seq_time_strip_original_content_length_get(scene, seq); } float SEQ_time_start_frame_get(const Sequence *seq) diff --git a/source/blender/sequencer/intern/strip_time.h b/source/blender/sequencer/intern/strip_time.h index c8946c47710..fbd35dc6adf 100644 --- a/source/blender/sequencer/intern/strip_time.h +++ b/source/blender/sequencer/intern/strip_time.h @@ -43,7 +43,9 @@ void seq_time_update_effects_strip_range(const struct Scene *scene, struct SeqCo void seq_time_translate_handles(const struct Scene *scene, struct Sequence *seq, const int offset); float seq_time_media_playback_rate_factor_get(const struct Scene *scene, const struct Sequence *seq); -float seq_time_playback_rate_factor_get(const struct Scene *scene, const struct Sequence *seq); +int seq_time_strip_original_content_length_get(const struct Scene *scene, + const struct Sequence *seq); +float seq_retiming_evaluate(const struct Sequence *seq, const int frame_index); #ifdef __cplusplus }