This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/sequencer/intern/strip_transform.c
Richard Antalik 302b04a5a3 VSE: Improved Retiming system
Patch implements better way to control playback speed than it is
possible to do with speed effect. Speed factor property can be set in
Time panel.

There are 2 layers of control:

Option to retime movie to match scene FPS rate.
Custom speed factor to control playback rate.
Since playback rate is strip property, it is now possible to manipulate
strip as normal one even if it is retimed.

To facilitate manipulation, some functions need to consider speed factor
and apply necessary corrections to strip offset or strip start. These
corrections may need to be float numbers, so start and offsets must be
float as well.

Sound strips now use speed factor instead of pitch. This means, that
strips will change length to match usable length. In addition, it is
possible to group movie and sound strip and change speed of meta strip.
2022-06-29 12:48:34 +02:00

772 lines
27 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2001-2002 NaN Holding BV. All rights reserved.
* 2003-2009 Blender Foundation.
* 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de> */
/** \file
* \ingroup bke
*/
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "BKE_sound.h"
#include "SEQ_animation.h"
#include "SEQ_channels.h"
#include "SEQ_edit.h"
#include "SEQ_effects.h"
#include "SEQ_iterator.h"
#include "SEQ_relations.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
#include "sequencer.h"
#include "strip_time.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"seq.strip_transform"};
bool SEQ_transform_single_image_check(Sequence *seq)
{
return ((seq->len == 1) &&
(seq->type == SEQ_TYPE_IMAGE ||
((seq->type & SEQ_TYPE_EFFECT) && SEQ_effect_get_num_inputs(seq->type) == 0)));
}
bool SEQ_transform_seqbase_isolated_sel_check(ListBase *seqbase)
{
Sequence *seq;
/* is there more than 1 select */
bool ok = false;
for (seq = seqbase->first; seq; seq = seq->next) {
if (seq->flag & SELECT) {
ok = true;
break;
}
}
if (ok == false) {
return false;
}
/* test relationships */
for (seq = seqbase->first; seq; seq = seq->next) {
if ((seq->type & SEQ_TYPE_EFFECT) == 0) {
continue;
}
if (seq->flag & SELECT) {
if ((seq->seq1 && (seq->seq1->flag & SELECT) == 0) ||
(seq->seq2 && (seq->seq2->flag & SELECT) == 0) ||
(seq->seq3 && (seq->seq3->flag & SELECT) == 0)) {
return false;
}
}
else {
if ((seq->seq1 && (seq->seq1->flag & SELECT)) || (seq->seq2 && (seq->seq2->flag & SELECT)) ||
(seq->seq3 && (seq->seq3->flag & SELECT))) {
return false;
}
}
}
return true;
}
void SEQ_transform_fix_single_image_seq_offsets(const Scene *scene, Sequence *seq)
{
int left, start, offset;
if (!SEQ_transform_single_image_check(seq)) {
return;
}
/* make sure the image is always at the start since there is only one,
* adjusting its start should be ok */
left = SEQ_time_left_handle_frame_get(scene, seq);
start = seq->start;
if (start != left) {
offset = left - start;
SEQ_time_left_handle_frame_set(
scene, seq, SEQ_time_left_handle_frame_get(scene, seq) - offset);
SEQ_time_right_handle_frame_set(
scene, seq, SEQ_time_right_handle_frame_get(scene, seq) - offset);
seq->start += offset;
}
}
bool SEQ_transform_sequence_can_be_translated(Sequence *seq)
{
return !(seq->type & SEQ_TYPE_EFFECT) || (SEQ_effect_get_num_inputs(seq->type) == 0);
}
bool SEQ_transform_test_overlap_seq_seq(const Scene *scene, Sequence *seq1, Sequence *seq2)
{
return (seq1 != seq2 && seq1->machine == seq2->machine &&
((SEQ_time_right_handle_frame_get(scene, seq1) <=
SEQ_time_left_handle_frame_get(scene, seq2)) ||
(SEQ_time_left_handle_frame_get(scene, seq1) >=
SEQ_time_right_handle_frame_get(scene, seq2))) == 0);
}
bool SEQ_transform_test_overlap(const Scene *scene, ListBase *seqbasep, Sequence *test)
{
Sequence *seq;
seq = seqbasep->first;
while (seq) {
if (SEQ_transform_test_overlap_seq_seq(scene, test, seq)) {
return true;
}
seq = seq->next;
}
return false;
}
void SEQ_transform_translate_sequence(Scene *evil_scene, Sequence *seq, int delta)
{
if (delta == 0) {
return;
}
/* Meta strips requires special handling: their content is to be translated, and then frame range
* of the meta is to be updated for the updated content. */
if (seq->type == SEQ_TYPE_META) {
Sequence *seq_child;
for (seq_child = seq->seqbase.first; seq_child; seq_child = seq_child->next) {
SEQ_transform_translate_sequence(evil_scene, seq_child, delta);
}
/* Move meta start/end points. */
SEQ_time_left_handle_frame_set(
evil_scene, seq, SEQ_time_left_handle_frame_get(evil_scene, seq) + delta);
SEQ_time_right_handle_frame_set(
evil_scene, seq, SEQ_time_right_handle_frame_get(evil_scene, seq) + delta);
}
else { /* All other strip types. */
seq->start += delta;
/* Only to make files usable in older versions. */
seq->startdisp = SEQ_time_left_handle_frame_get(evil_scene, seq);
seq->enddisp = SEQ_time_right_handle_frame_get(evil_scene, seq);
}
SEQ_offset_animdata(evil_scene, seq, delta);
SEQ_time_update_meta_strip_range(evil_scene, seq_sequence_lookup_meta_by_seq(evil_scene, seq));
seq_time_update_effects_strip_range(evil_scene,
seq_sequence_lookup_effects_by_seq(evil_scene, seq));
}
bool SEQ_transform_seqbase_shuffle_ex(ListBase *seqbasep,
Sequence *test,
Scene *evil_scene,
int channel_delta)
{
const int orig_machine = test->machine;
BLI_assert(ELEM(channel_delta, -1, 1));
test->machine += channel_delta;
while (SEQ_transform_test_overlap(evil_scene, seqbasep, test)) {
if ((channel_delta > 0) ? (test->machine >= MAXSEQ) : (test->machine < 1)) {
break;
}
test->machine += channel_delta;
}
if (!SEQ_valid_strip_channel(test)) {
/* Blender 2.4x would remove the strip.
* nicer to move it to the end */
Sequence *seq;
int new_frame = SEQ_time_right_handle_frame_get(evil_scene, test);
for (seq = seqbasep->first; seq; seq = seq->next) {
if (seq->machine == orig_machine) {
new_frame = max_ii(new_frame, SEQ_time_right_handle_frame_get(evil_scene, seq));
}
}
test->machine = orig_machine;
new_frame = new_frame + (test->start - SEQ_time_left_handle_frame_get(
evil_scene, test)); /* adjust by the startdisp */
SEQ_transform_translate_sequence(evil_scene, test, new_frame - test->start);
return false;
}
return true;
}
bool SEQ_transform_seqbase_shuffle(ListBase *seqbasep, Sequence *test, Scene *evil_scene)
{
return SEQ_transform_seqbase_shuffle_ex(seqbasep, test, evil_scene, 1);
}
static bool shuffle_seq_test_overlap(const Scene *scene,
const Sequence *seq1,
const Sequence *seq2,
const int offset)
{
return (seq1 != seq2 && seq1->machine == seq2->machine &&
((SEQ_time_right_handle_frame_get(scene, seq1) + offset <=
SEQ_time_left_handle_frame_get(scene, seq2)) ||
(SEQ_time_left_handle_frame_get(scene, seq1) + offset >=
SEQ_time_right_handle_frame_get(scene, seq2))) == 0);
}
static int shuffle_seq_time_offset_get(const Scene *scene,
SeqCollection *strips_to_shuffle,
ListBase *seqbasep,
char dir)
{
int offset = 0;
Sequence *seq;
bool all_conflicts_resolved = false;
while (!all_conflicts_resolved) {
all_conflicts_resolved = true;
SEQ_ITERATOR_FOREACH (seq, strips_to_shuffle) {
LISTBASE_FOREACH (Sequence *, seq_other, seqbasep) {
if (!shuffle_seq_test_overlap(scene, seq, seq_other, offset)) {
continue;
}
if (SEQ_relation_is_effect_of_strip(seq_other, seq)) {
continue;
}
if (UNLIKELY(SEQ_collection_has_strip(seq_other, strips_to_shuffle))) {
CLOG_WARN(&LOG,
"Strip overlaps with itself or another strip, that is to be shuffled. "
"This should never happen.");
continue;
}
all_conflicts_resolved = false;
if (dir == 'L') {
offset = min_ii(offset,
SEQ_time_left_handle_frame_get(scene, seq_other) -
SEQ_time_right_handle_frame_get(scene, seq));
}
else {
offset = max_ii(offset,
SEQ_time_right_handle_frame_get(scene, seq_other) -
SEQ_time_left_handle_frame_get(scene, seq));
}
}
}
}
return offset;
}
bool SEQ_transform_seqbase_shuffle_time(SeqCollection *strips_to_shuffle,
SeqCollection *time_dependent_strips,
ListBase *seqbasep,
Scene *evil_scene,
ListBase *markers,
const bool use_sync_markers)
{
int offset_l = shuffle_seq_time_offset_get(evil_scene, strips_to_shuffle, seqbasep, 'L');
int offset_r = shuffle_seq_time_offset_get(evil_scene, strips_to_shuffle, seqbasep, 'R');
int offset = (-offset_l < offset_r) ? offset_l : offset_r;
if (offset) {
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, strips_to_shuffle) {
SEQ_transform_translate_sequence(evil_scene, seq, offset);
seq->flag &= ~SEQ_OVERLAP;
}
if (time_dependent_strips != NULL) {
SEQ_ITERATOR_FOREACH (seq, time_dependent_strips) {
SEQ_offset_animdata(evil_scene, seq, offset);
}
}
if (use_sync_markers && !(evil_scene->toolsettings->lock_markers) && (markers != NULL)) {
TimeMarker *marker;
/* affect selected markers - it's unlikely that we will want to affect all in this way? */
for (marker = markers->first; marker; marker = marker->next) {
if (marker->flag & SELECT) {
marker->frame += offset;
}
}
}
}
return offset ? false : true;
}
static SeqCollection *extract_standalone_strips(SeqCollection *transformed_strips)
{
SeqCollection *collection = SEQ_collection_create(__func__);
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, transformed_strips) {
if ((seq->type & SEQ_TYPE_EFFECT) == 0 || seq->seq1 == NULL) {
SEQ_collection_append_strip(seq, collection);
}
}
return collection;
}
/* Query strips positioned after left edge of transformed strips bound-box. */
static SeqCollection *query_right_side_strips(const Scene *scene,
ListBase *seqbase,
SeqCollection *transformed_strips,
SeqCollection *time_dependent_strips)
{
int minframe = MAXFRAME;
{
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, transformed_strips) {
minframe = min_ii(minframe, SEQ_time_left_handle_frame_get(scene, seq));
}
}
SeqCollection *collection = SEQ_collection_create(__func__);
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
if (SEQ_collection_has_strip(seq, time_dependent_strips)) {
continue;
}
if (SEQ_collection_has_strip(seq, transformed_strips)) {
continue;
}
if ((seq->flag & SELECT) == 0 && SEQ_time_left_handle_frame_get(scene, seq) >= minframe) {
SEQ_collection_append_strip(seq, collection);
}
}
return collection;
}
/* Offset all strips positioned after left edge of transformed strips bound-box by amount equal
* to overlap of transformed strips. */
static void seq_transform_handle_expand_to_fit(Scene *scene,
ListBase *seqbasep,
SeqCollection *transformed_strips,
SeqCollection *time_dependent_strips,
bool use_sync_markers)
{
ListBase *markers = &scene->markers;
SeqCollection *right_side_strips = query_right_side_strips(
scene, seqbasep, transformed_strips, time_dependent_strips);
/* Temporarily move right side strips beyond timeline boundary. */
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, right_side_strips) {
seq->machine += MAXSEQ * 2;
}
/* Shuffle transformed standalone strips. This is because transformed strips can overlap with
* strips on left side. */
SeqCollection *standalone_strips = extract_standalone_strips(transformed_strips);
SEQ_transform_seqbase_shuffle_time(
standalone_strips, time_dependent_strips, seqbasep, scene, markers, use_sync_markers);
SEQ_collection_free(standalone_strips);
/* Move temporarily moved strips back to their original place and tag for shuffling. */
SEQ_ITERATOR_FOREACH (seq, right_side_strips) {
seq->machine -= MAXSEQ * 2;
}
/* Shuffle again to displace strips on right side. Final effect shuffling is done in
* SEQ_transform_handle_overlap. */
SEQ_transform_seqbase_shuffle_time(
right_side_strips, NULL, seqbasep, scene, markers, use_sync_markers);
SEQ_collection_free(right_side_strips);
}
static SeqCollection *query_overwrite_targets(const Scene *scene,
ListBase *seqbasep,
SeqCollection *transformed_strips)
{
SeqCollection *collection = SEQ_query_unselected_strips(seqbasep);
Sequence *seq, *seq_transformed;
SEQ_ITERATOR_FOREACH (seq, collection) {
bool does_overlap = false;
SEQ_ITERATOR_FOREACH (seq_transformed, transformed_strips) {
/* Effects of transformed strips can be unselected. These must not be included. */
if (seq == seq_transformed) {
SEQ_collection_remove_strip(seq, collection);
}
if (SEQ_transform_test_overlap_seq_seq(scene, seq, seq_transformed)) {
does_overlap = true;
}
}
if (!does_overlap) {
SEQ_collection_remove_strip(seq, collection);
}
}
return collection;
}
typedef enum eOvelapDescrition {
/* No overlap. */
STRIP_OVERLAP_NONE,
/* Overlapping strip covers overlapped completely. */
STRIP_OVERLAP_IS_FULL,
/* Overlapping strip is inside overlapped. */
STRIP_OVERLAP_IS_INSIDE,
/* Partial overlap between 2 strips. */
STRIP_OVERLAP_LEFT_SIDE,
STRIP_OVERLAP_RIGHT_SIDE,
} eOvelapDescrition;
static eOvelapDescrition overlap_description_get(const Scene *scene,
const Sequence *transformed,
const Sequence *target)
{
if (SEQ_time_left_handle_frame_get(scene, transformed) <=
SEQ_time_left_handle_frame_get(scene, target) &&
SEQ_time_right_handle_frame_get(scene, transformed) >=
SEQ_time_right_handle_frame_get(scene, target)) {
return STRIP_OVERLAP_IS_FULL;
}
if (SEQ_time_left_handle_frame_get(scene, transformed) >
SEQ_time_left_handle_frame_get(scene, target) &&
SEQ_time_right_handle_frame_get(scene, transformed) <
SEQ_time_right_handle_frame_get(scene, target)) {
return STRIP_OVERLAP_IS_INSIDE;
}
if (SEQ_time_left_handle_frame_get(scene, transformed) <=
SEQ_time_left_handle_frame_get(scene, target) &&
SEQ_time_left_handle_frame_get(scene, target) <=
SEQ_time_right_handle_frame_get(scene, transformed)) {
return STRIP_OVERLAP_LEFT_SIDE;
}
if (SEQ_time_left_handle_frame_get(scene, transformed) <=
SEQ_time_right_handle_frame_get(scene, target) &&
SEQ_time_right_handle_frame_get(scene, target) <=
SEQ_time_right_handle_frame_get(scene, transformed)) {
return STRIP_OVERLAP_RIGHT_SIDE;
}
return STRIP_OVERLAP_NONE;
}
/* Split strip in 3 parts, remove middle part and fit transformed inside. */
static void seq_transform_handle_overwrite_split(Scene *scene,
ListBase *seqbasep,
const Sequence *transformed,
Sequence *target)
{
/* Because we are doing a soft split, bmain is not used in SEQ_edit_strip_split, so we can pass
* NULL here. */
Main *bmain = NULL;
Sequence *split_strip = SEQ_edit_strip_split(bmain,
scene,
seqbasep,
target,
SEQ_time_left_handle_frame_get(scene, transformed),
SEQ_SPLIT_SOFT,
NULL);
SEQ_edit_strip_split(bmain,
scene,
seqbasep,
split_strip,
SEQ_time_right_handle_frame_get(scene, transformed),
SEQ_SPLIT_SOFT,
NULL);
SEQ_edit_flag_for_removal(scene, seqbasep, split_strip);
SEQ_edit_remove_flagged_sequences(scene, seqbasep);
}
/* Trim strips by adjusting handle position.
* This is bit more complicated in case overlap happens on effect. */
static void seq_transform_handle_overwrite_trim(Scene *scene,
ListBase *seqbasep,
const Sequence *transformed,
Sequence *target,
const eOvelapDescrition overlap)
{
SeqCollection *targets = SEQ_query_by_reference(
target, scene, seqbasep, SEQ_query_strip_effect_chain);
/* Expand collection by adding all target's children, effects and their children. */
if ((target->type & SEQ_TYPE_EFFECT) != 0) {
SEQ_collection_expand(scene, seqbasep, targets, SEQ_query_strip_effect_chain);
}
/* Trim all non effects, that have influence on effect length which is overlapping. */
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, targets) {
if ((seq->type & SEQ_TYPE_EFFECT) != 0 && SEQ_effect_get_num_inputs(seq->type) > 0) {
continue;
}
if (overlap == STRIP_OVERLAP_LEFT_SIDE) {
SEQ_time_left_handle_frame_set(
scene, seq, SEQ_time_right_handle_frame_get(scene, transformed));
}
else {
BLI_assert(overlap == STRIP_OVERLAP_RIGHT_SIDE);
SEQ_time_right_handle_frame_set(
scene, seq, SEQ_time_left_handle_frame_get(scene, transformed));
}
}
SEQ_collection_free(targets);
}
static void seq_transform_handle_overwrite(Scene *scene,
ListBase *seqbasep,
SeqCollection *transformed_strips)
{
SeqCollection *targets = query_overwrite_targets(scene, seqbasep, transformed_strips);
SeqCollection *strips_to_delete = SEQ_collection_create(__func__);
Sequence *target;
Sequence *transformed;
SEQ_ITERATOR_FOREACH (target, targets) {
SEQ_ITERATOR_FOREACH (transformed, transformed_strips) {
if (transformed->machine != target->machine) {
continue;
}
const eOvelapDescrition overlap = overlap_description_get(scene, transformed, target);
if (overlap == STRIP_OVERLAP_IS_FULL) {
SEQ_collection_append_strip(target, strips_to_delete);
}
else if (overlap == STRIP_OVERLAP_IS_INSIDE) {
seq_transform_handle_overwrite_split(scene, seqbasep, transformed, target);
}
else if (ELEM(overlap, STRIP_OVERLAP_LEFT_SIDE, STRIP_OVERLAP_RIGHT_SIDE)) {
seq_transform_handle_overwrite_trim(scene, seqbasep, transformed, target, overlap);
}
}
}
SEQ_collection_free(targets);
/* Remove covered strips. This must be done in separate loop, because `SEQ_edit_strip_split()`
* also uses `SEQ_edit_remove_flagged_sequences()`. See T91096. */
if (SEQ_collection_len(strips_to_delete) > 0) {
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, strips_to_delete) {
SEQ_edit_flag_for_removal(scene, seqbasep, seq);
}
SEQ_edit_remove_flagged_sequences(scene, seqbasep);
}
SEQ_collection_free(strips_to_delete);
}
static void seq_transform_handle_overlap_shuffle(Scene *scene,
ListBase *seqbasep,
SeqCollection *transformed_strips,
SeqCollection *time_dependent_strips,
bool use_sync_markers)
{
ListBase *markers = &scene->markers;
/* Shuffle non strips with no effects attached. */
SeqCollection *standalone_strips = extract_standalone_strips(transformed_strips);
SEQ_transform_seqbase_shuffle_time(
standalone_strips, time_dependent_strips, seqbasep, scene, markers, use_sync_markers);
SEQ_collection_free(standalone_strips);
}
void SEQ_transform_handle_overlap(Scene *scene,
ListBase *seqbasep,
SeqCollection *transformed_strips,
SeqCollection *time_dependent_strips,
bool use_sync_markers)
{
const eSeqOverlapMode overlap_mode = SEQ_tool_settings_overlap_mode_get(scene);
switch (overlap_mode) {
case SEQ_OVERLAP_EXPAND:
seq_transform_handle_expand_to_fit(
scene, seqbasep, transformed_strips, time_dependent_strips, use_sync_markers);
break;
case SEQ_OVERLAP_OVERWRITE:
seq_transform_handle_overwrite(scene, seqbasep, transformed_strips);
break;
case SEQ_OVERLAP_SHUFFLE:
seq_transform_handle_overlap_shuffle(
scene, seqbasep, transformed_strips, time_dependent_strips, use_sync_markers);
break;
}
/* If any effects still overlap, we need to move them up.
* In some cases other strips can be overlapping still, see T90646. */
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, transformed_strips) {
if (SEQ_transform_test_overlap(scene, seqbasep, seq)) {
SEQ_transform_seqbase_shuffle(seqbasep, seq, scene);
}
seq->flag &= ~SEQ_OVERLAP;
}
}
void SEQ_transform_offset_after_frame(Scene *scene,
ListBase *seqbase,
const int delta,
const int timeline_frame)
{
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
if (SEQ_time_left_handle_frame_get(scene, seq) >= timeline_frame) {
SEQ_transform_translate_sequence(scene, seq, delta);
SEQ_relations_invalidate_cache_preprocessed(scene, seq);
}
}
if (!scene->toolsettings->lock_markers) {
LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
if (marker->frame >= timeline_frame) {
marker->frame += delta;
}
}
}
}
bool SEQ_transform_is_locked(ListBase *channels, Sequence *seq)
{
SeqTimelineChannel *channel = SEQ_channel_get_by_index(channels, seq->machine);
return seq->flag & SEQ_LOCK ||
(SEQ_channel_is_locked(channel) && ((seq->flag & SEQ_IGNORE_CHANNEL_LOCK) == 0));
}
void SEQ_image_transform_mirror_factor_get(const Sequence *seq, float r_mirror[2])
{
r_mirror[0] = 1.0f;
r_mirror[1] = 1.0f;
if ((seq->flag & SEQ_FLIPX) != 0) {
r_mirror[0] = -1.0f;
}
if ((seq->flag & SEQ_FLIPY) != 0) {
r_mirror[1] = -1.0f;
}
}
void SEQ_image_transform_origin_offset_pixelspace_get(const Scene *scene,
const Sequence *seq,
float r_origin[2])
{
float image_size[2];
StripElem *strip_elem = seq->strip->stripdata;
if (strip_elem == NULL) {
image_size[0] = scene->r.xsch;
image_size[1] = scene->r.ysch;
}
else {
image_size[0] = strip_elem->orig_width;
image_size[1] = strip_elem->orig_height;
}
const StripTransform *transform = seq->strip->transform;
r_origin[0] = (image_size[0] * transform->origin[0]) - (image_size[0] * 0.5f) + transform->xofs;
r_origin[1] = (image_size[1] * transform->origin[1]) - (image_size[1] * 0.5f) + transform->yofs;
const float viewport_pixel_aspect[2] = {scene->r.xasp / scene->r.yasp, 1.0f};
float mirror[2];
SEQ_image_transform_mirror_factor_get(seq, mirror);
mul_v2_v2(r_origin, mirror);
mul_v2_v2(r_origin, viewport_pixel_aspect);
}
static void seq_image_transform_quad_get_ex(const Scene *scene,
const Sequence *seq,
bool apply_rotation,
float r_quad[4][2])
{
StripTransform *transform = seq->strip->transform;
StripCrop *crop = seq->strip->crop;
int image_size[2] = {scene->r.xsch, scene->r.ysch};
if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) {
image_size[0] = seq->strip->stripdata->orig_width;
image_size[1] = seq->strip->stripdata->orig_height;
}
float transform_matrix[4][4];
float rotation_matrix[3][3];
axis_angle_to_mat3_single(rotation_matrix, 'Z', apply_rotation ? transform->rotation : 0.0f);
loc_rot_size_to_mat4(transform_matrix,
(const float[]){transform->xofs, transform->yofs, 0.0f},
rotation_matrix,
(const float[]){transform->scale_x, transform->scale_y, 1.0f});
const float origin[2] = {image_size[0] * transform->origin[0],
image_size[1] * transform->origin[1]};
const float pivot[3] = {origin[0] - (image_size[0] / 2), origin[1] - (image_size[1] / 2), 0.0f};
transform_pivot_set_m4(transform_matrix, pivot);
float quad_temp[4][3];
for (int i = 0; i < 4; i++) {
zero_v2(quad_temp[i]);
}
quad_temp[0][0] = (image_size[0] / 2) - crop->right;
quad_temp[0][1] = (image_size[1] / 2) - crop->top;
quad_temp[1][0] = (image_size[0] / 2) - crop->right;
quad_temp[1][1] = (-image_size[1] / 2) + crop->bottom;
quad_temp[2][0] = (-image_size[0] / 2) + crop->left;
quad_temp[2][1] = (-image_size[1] / 2) + crop->bottom;
quad_temp[3][0] = (-image_size[0] / 2) + crop->left;
quad_temp[3][1] = (image_size[1] / 2) - crop->top;
float mirror[2];
SEQ_image_transform_mirror_factor_get(seq, mirror);
const float viewport_pixel_aspect[2] = {scene->r.xasp / scene->r.yasp, 1.0f};
for (int i = 0; i < 4; i++) {
mul_m4_v3(transform_matrix, quad_temp[i]);
mul_v2_v2(quad_temp[i], mirror);
mul_v2_v2(quad_temp[i], viewport_pixel_aspect);
copy_v2_v2(r_quad[i], quad_temp[i]);
}
}
void SEQ_image_transform_quad_get(const Scene *scene,
const Sequence *seq,
bool apply_rotation,
float r_quad[4][2])
{
seq_image_transform_quad_get_ex(scene, seq, apply_rotation, r_quad);
}
void SEQ_image_transform_final_quad_get(const Scene *scene,
const Sequence *seq,
float r_quad[4][2])
{
seq_image_transform_quad_get_ex(scene, seq, true, r_quad);
}
void SEQ_image_preview_unit_to_px(const Scene *scene, const float co_src[2], float co_dst[2])
{
co_dst[0] = co_src[0] * scene->r.xsch;
co_dst[1] = co_src[1] * scene->r.ysch;
}
void SEQ_image_preview_unit_from_px(const Scene *scene, const float co_src[2], float co_dst[2])
{
co_dst[0] = co_src[0] / scene->r.xsch;
co_dst[1] = co_src[1] / scene->r.ysch;
}
void SEQ_image_transform_bounding_box_from_collection(
Scene *scene, SeqCollection *strips, bool apply_rotation, float r_min[2], float r_max[2])
{
Sequence *seq;
INIT_MINMAX2(r_min, r_max);
SEQ_ITERATOR_FOREACH (seq, strips) {
float quad[4][2];
SEQ_image_transform_quad_get(scene, seq, apply_rotation, quad);
for (int i = 0; i < 4; i++) {
minmax_v2v2_v2(r_min, r_max, quad[i]);
}
}
}