Add Chromium-only Blender WebEngine parity work
This commit is contained in:
153
blender-5.2.0/source/blender/sequencer/CMakeLists.txt
Normal file
153
blender-5.2.0/source/blender/sequencer/CMakeLists.txt
Normal file
@@ -0,0 +1,153 @@
|
||||
# SPDX-FileCopyrightText: 2011-2024 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
PUBLIC .
|
||||
intern
|
||||
../compositor
|
||||
../compositor/algorithms
|
||||
../compositor/cached_resources
|
||||
../compositor/derived_resources
|
||||
../editors/include
|
||||
../animrig
|
||||
../makesrna
|
||||
|
||||
# RNA_prototypes.hh
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
SEQ_add.hh
|
||||
SEQ_animation.hh
|
||||
SEQ_channels.hh
|
||||
SEQ_connect.hh
|
||||
SEQ_edit.hh
|
||||
SEQ_effects.hh
|
||||
SEQ_iterator.hh
|
||||
SEQ_modifier.hh
|
||||
SEQ_modifiertypes.hh
|
||||
SEQ_offscreen.hh
|
||||
SEQ_prefetch.hh
|
||||
SEQ_preview_cache.hh
|
||||
SEQ_proxy.hh
|
||||
SEQ_relations.hh
|
||||
SEQ_render.hh
|
||||
SEQ_retiming.hh
|
||||
SEQ_select.hh
|
||||
SEQ_sequencer.hh
|
||||
SEQ_sound.hh
|
||||
SEQ_thumbnail_cache.hh
|
||||
SEQ_time.hh
|
||||
SEQ_transform.hh
|
||||
SEQ_utils.hh
|
||||
|
||||
intern/animation.cc
|
||||
intern/cache/compositor_cache.cc
|
||||
intern/cache/compositor_cache.hh
|
||||
intern/cache/final_image_cache.cc
|
||||
intern/cache/final_image_cache.hh
|
||||
intern/cache/intra_frame_cache.cc
|
||||
intern/cache/intra_frame_cache.hh
|
||||
intern/cache/preview_cache.cc
|
||||
intern/cache/source_image_cache.cc
|
||||
intern/cache/source_image_cache.hh
|
||||
intern/cache/thumbnail_cache.cc
|
||||
intern/channels.cc
|
||||
intern/compositor.cc
|
||||
intern/compositor.hh
|
||||
intern/effects/effect_add_sub_mul.cc
|
||||
intern/effects/effect_adjustment.cc
|
||||
intern/effects/effect_blend.cc
|
||||
intern/effects/effect_compositor.cc
|
||||
intern/effects/effect_cross.cc
|
||||
intern/effects/effect_gaussian_blur.cc
|
||||
intern/effects/effect_glow.cc
|
||||
intern/effects/effect_multi_camera.cc
|
||||
intern/effects/effect_solid_color.cc
|
||||
intern/effects/effect_speed.cc
|
||||
intern/effects/effect_text.cc
|
||||
intern/effects/effect_wipe.cc
|
||||
intern/effects/effects.cc
|
||||
intern/effects/effects.hh
|
||||
intern/modifiers/MOD_brightness_contrast.cc
|
||||
intern/modifiers/MOD_color_balance.cc
|
||||
intern/modifiers/MOD_compositor.cc
|
||||
intern/modifiers/MOD_curves.cc
|
||||
intern/modifiers/MOD_hue_correct.cc
|
||||
intern/modifiers/MOD_mask.cc
|
||||
intern/modifiers/MOD_none.cc
|
||||
intern/modifiers/MOD_sound_echo.cc
|
||||
intern/modifiers/MOD_sound_equalizer.cc
|
||||
intern/modifiers/MOD_sound_pitch.cc
|
||||
intern/modifiers/MOD_tonemap.cc
|
||||
intern/modifiers/MOD_white_balance.cc
|
||||
intern/modifiers/modifier.cc
|
||||
intern/modifiers/modifier.hh
|
||||
intern/iterator.cc
|
||||
intern/media_presence.cc
|
||||
intern/multiview.cc
|
||||
intern/multiview.hh
|
||||
intern/prefetch.cc
|
||||
intern/prefetch.hh
|
||||
intern/proxy.cc
|
||||
intern/proxy.hh
|
||||
intern/proxy_job.cc
|
||||
intern/render.cc
|
||||
intern/render.hh
|
||||
intern/sequencer.cc
|
||||
intern/sequencer.hh
|
||||
intern/sound.cc
|
||||
intern/strip_add.cc
|
||||
intern/strip_connect.cc
|
||||
intern/strip_edit.cc
|
||||
intern/strip_lookup.cc
|
||||
intern/strip_relations.cc
|
||||
intern/strip_retiming.cc
|
||||
intern/strip_select.cc
|
||||
intern/strip_time.cc
|
||||
intern/strip_time.hh
|
||||
intern/strip_transform.cc
|
||||
intern/utils.cc
|
||||
intern/utils.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenfont
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::blenloader
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::draw
|
||||
PRIVATE bf::extern::xxhash
|
||||
PRIVATE bf::gpu
|
||||
PRIVATE bf::imbuf
|
||||
PRIVATE bf::imbuf::movie
|
||||
PRIVATE bf::intern::atomic
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::intern::profile
|
||||
PRIVATE bf::nodes
|
||||
PRIVATE bf::render
|
||||
PRIVATE bf::windowmanager
|
||||
bf_compositor
|
||||
PRIVATE bf::dependencies::optional::audaspace
|
||||
PRIVATE bf::dependencies::optional::fftw3
|
||||
)
|
||||
|
||||
if(WITH_AUDASPACE)
|
||||
if(WITH_FFTW3)
|
||||
add_definitions(-DWITH_CONVOLUTION)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_sequencer "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_library(bf::sequencer ALIAS bf_sequencer)
|
||||
|
||||
# RNA_prototypes.hh
|
||||
add_dependencies(bf_sequencer bf_rna)
|
||||
186
blender-5.2.0/source/blender/sequencer/SEQ_add.hh
Normal file
186
blender-5.2.0/source/blender/sequencer/SEQ_add.hh
Normal file
@@ -0,0 +1,186 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_enum_flags.hh"
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_scene_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
struct Mask;
|
||||
struct MovieClip;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct Stereo3dFormat;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/** #SeqLoadData.flags */
|
||||
enum eLoadFlags {
|
||||
SEQ_LOAD_SOUND_CACHE = (1 << 1),
|
||||
SEQ_LOAD_SOUND_MONO = (1 << 2),
|
||||
SEQ_LOAD_MOVIE_SYNC_FPS = (1 << 3),
|
||||
SEQ_LOAD_SET_VIEW_TRANSFORM = (1 << 4),
|
||||
};
|
||||
ENUM_OPERATORS(eLoadFlags)
|
||||
|
||||
/** API for adding new strips. If multiple strips are added, data is updated for each one. */
|
||||
struct LoadData {
|
||||
int start_frame;
|
||||
int channel;
|
||||
short stream_index; /* Index within the source for files with multiple video/audio streams. */
|
||||
char name[64]; /* Strip name, including file extension. */
|
||||
/** Typically a `filepath` but may reference any kind of path. */
|
||||
char path[/*FILE_MAX*/ 1024];
|
||||
struct {
|
||||
int count; /* Number of images in this strip, 1 if not an image sequence. */
|
||||
int length;
|
||||
} image; /* Only for image strips. */
|
||||
Scene *scene; /* Only for scene strips. */
|
||||
MovieClip *clip; /* Only for clip strips. */
|
||||
Mask *mask; /* Only for mask strips. */
|
||||
struct {
|
||||
StripType type;
|
||||
int length;
|
||||
Strip *input1;
|
||||
Strip *input2;
|
||||
} effect; /* Only for effect strips. */
|
||||
eLoadFlags flags;
|
||||
eSeqImageFitMethod fit_method;
|
||||
bool use_multiview;
|
||||
eImageFormat_ViewsFormat views_format;
|
||||
Stereo3dFormat *stereo3d_format;
|
||||
bool allow_invalid_file; /* Used by RNA API to create placeholder strips. */
|
||||
double video_stream_start; /* For AV synchronization. Set by `seq::add_movie_strip`. */
|
||||
bool adjust_playback_rate;
|
||||
bool allow_overlap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize common SeqLoadData members
|
||||
*
|
||||
* \param load_data: SeqLoadData to be initialized
|
||||
* \param name: strip name (can be NULL)
|
||||
* \param path: path to file that is used as strip input (can be NULL)
|
||||
* \param start_frame: timeline frame where strip will be created
|
||||
* \param channel: timeline channel where strip will be created
|
||||
*/
|
||||
void add_load_data_init(
|
||||
LoadData *load_data, const char *name, const char *path, int start_frame, int channel);
|
||||
/**
|
||||
* Add image strip.
|
||||
* \note Use #SEQ_add_image_set_directory() and #SEQ_add_image_load_file() to load image sequences
|
||||
*
|
||||
* \param bmain: Main reference
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_image_strip(Main *bmain, Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
/**
|
||||
* Add sound strip.
|
||||
* \note Use SEQ_add_image_set_directory() and SEQ_add_image_load_file() to load image sequences
|
||||
*
|
||||
* \param bmain: Main reference
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_sound_strip(Main *bmain, Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
|
||||
/**
|
||||
* Add meta strip.
|
||||
*
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_meta_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
/**
|
||||
* Add movie strip.
|
||||
*
|
||||
* NOTE: This function can change the scene FPS if `SEQ_LOAD_MOVIE_SYNC_FPS` is set, take caution!
|
||||
*
|
||||
* \param bmain: Main reference
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_movie_strip(Main *bmain, Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
/**
|
||||
* Add scene strip.
|
||||
*
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_scene_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
/**
|
||||
* Add movieclip strip.
|
||||
*
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_movieclip_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
/**
|
||||
* Add mask strip.
|
||||
*
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_mask_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
/**
|
||||
* Add effect strip.
|
||||
*
|
||||
* \param scene: Scene where strips will be added
|
||||
* \param seqbase: List where strips will be added
|
||||
* \param load_data: SeqLoadData with information necessary to create strip
|
||||
* \return created strip
|
||||
*/
|
||||
Strip *add_effect_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data);
|
||||
/**
|
||||
* Set directory used by image strip.
|
||||
*
|
||||
* \param strip: image strip to be changed
|
||||
* \param dirpath: directory path
|
||||
*/
|
||||
void add_image_set_directory(Strip *strip, const char *dirpath);
|
||||
/**
|
||||
* Set directory used by image strip.
|
||||
*
|
||||
* \param strip: image strip to be changed
|
||||
* \param strip_frame: frame index of strip to be changed
|
||||
* \param filename: image filename (only filename, not complete path)
|
||||
*/
|
||||
void add_image_load_file(Scene *scene, Strip *strip, size_t strip_frame, const char *filename);
|
||||
/**
|
||||
* Set image strip alpha mode
|
||||
*
|
||||
* \param strip: image strip to be changed
|
||||
*/
|
||||
void add_image_init_alpha_mode(Main *bmain, Scene *scene, Strip *strip);
|
||||
void add_reload_new_file(Main *bmain, Scene *scene, Strip *strip, bool lock_range);
|
||||
void add_movie_reload_if_needed(
|
||||
Main *bmain, Scene *scene, Strip *strip, bool *r_was_reloaded, bool *r_can_produce_frames);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
49
blender-5.2.0/source/blender/sequencer/SEQ_animation.hh
Normal file
49
blender-5.2.0/source/blender/sequencer/SEQ_animation.hh
Normal file
@@ -0,0 +1,49 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
struct SeqAnimationBackup;
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
namespace seq {
|
||||
|
||||
bool animation_keyframes_exist(const Scene *scene);
|
||||
bool animation_drivers_exist(Scene *scene);
|
||||
void free_animdata(Scene *scene, Strip *strip);
|
||||
void offset_animdata(const Scene *scene, Strip *strip, float ofs);
|
||||
|
||||
/**
|
||||
* Return whether the fcurve targets the given strip.
|
||||
*/
|
||||
bool fcurve_matches(const Strip &strip, const FCurve &fcurve);
|
||||
|
||||
struct AnimationBackup {
|
||||
animrig::Channelbag channelbag;
|
||||
ListBaseT<FCurve> drivers;
|
||||
};
|
||||
/**
|
||||
* Move all F-Curves and drivers from `scene` to `backup`.
|
||||
*/
|
||||
void animation_backup_original(Scene *scene, AnimationBackup *backup);
|
||||
/**
|
||||
* Move all F-Curves and drivers from `backup` to `scene`.
|
||||
*/
|
||||
void animation_restore_original(Scene *scene, AnimationBackup *backup);
|
||||
/**
|
||||
* Duplicate F-Curves and drivers used by `strip` from `backup` to `scene`.
|
||||
*/
|
||||
void animation_duplicate_backup_to_scene(Scene *scene, Strip *strip, AnimationBackup *backup);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
39
blender-5.2.0/source/blender/sequencer/SEQ_channels.hh
Normal file
39
blender-5.2.0/source/blender/sequencer/SEQ_channels.hh
Normal file
@@ -0,0 +1,39 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Editing;
|
||||
struct SeqTimelineChannel;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/** The active displayed channels list, either from the root sequence or from a meta-strip. */
|
||||
ListBaseT<SeqTimelineChannel> *channels_displayed_get(const Editing *ed);
|
||||
void channels_ensure(ListBaseT<SeqTimelineChannel> *channels);
|
||||
void channels_duplicate(ListBaseT<SeqTimelineChannel> *channels_dst,
|
||||
ListBaseT<SeqTimelineChannel> *channels_src);
|
||||
void channels_free(ListBaseT<SeqTimelineChannel> *channels);
|
||||
|
||||
/**
|
||||
* Returns SeqTimelineChannel by index
|
||||
* NOTE: `Strip::channel` and `SeqTimelineChannel::index` are both counted from 0, but index of 0
|
||||
* is never used. Therefore, it is valid to call `SeqTimelineChannel(channels, strip->channel)` to
|
||||
* get channel corresponding to strip position.
|
||||
*/
|
||||
SeqTimelineChannel *channel_get_by_index(const ListBaseT<SeqTimelineChannel> *channels,
|
||||
int channel_index);
|
||||
ListBaseT<SeqTimelineChannel> *get_channels_by_strip(Editing *ed, const Strip *strip);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
63
blender-5.2.0/source/blender/sequencer/SEQ_connect.hh
Normal file
63
blender-5.2.0/source/blender/sequencer/SEQ_connect.hh
Normal file
@@ -0,0 +1,63 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_vector_set.hh"
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Strip;
|
||||
struct StripConnection;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void connections_duplicate(ListBaseT<StripConnection> *connections_dst,
|
||||
ListBaseT<StripConnection> *connections_src);
|
||||
|
||||
/**
|
||||
* Disconnect the strip(s) from any connections with other strips. This function also
|
||||
* frees the allocated memory as necessary. Returns false if any of the strips were not already
|
||||
* connected.
|
||||
*/
|
||||
bool disconnect(Strip *strip);
|
||||
bool disconnect(VectorSet<Strip *> &strip_list);
|
||||
|
||||
/**
|
||||
* Ensure that the strip has only bidirectional connections (expected behavior).
|
||||
*/
|
||||
void cut_one_way_connections(Strip *strip);
|
||||
|
||||
/**
|
||||
* Connect strips so that they may be selected together. Any connections the
|
||||
* strips already have will be severed before reconnection.
|
||||
*/
|
||||
void connect(Strip *strip1, Strip *strip2);
|
||||
void connect(VectorSet<Strip *> &strip_list);
|
||||
|
||||
/**
|
||||
* Returns a list of strips that the `strip` is connected to.
|
||||
* NOTE: This does not include `strip` itself.
|
||||
* This list is empty if `strip` is not connected.
|
||||
*/
|
||||
VectorSet<Strip *> connected_strips_get(const Strip *strip);
|
||||
|
||||
/**
|
||||
* Check whether a strip has any connections.
|
||||
*/
|
||||
bool is_strip_connected(const Strip *strip);
|
||||
|
||||
/**
|
||||
* Check whether the list of strips are a single connection "group", that is, they are all
|
||||
* connected to each other and there are no outside connections.
|
||||
*/
|
||||
bool are_strips_connected_together(VectorSet<Strip *> &strip_list);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
94
blender-5.2.0/source/blender/sequencer/SEQ_edit.hh
Normal file
94
blender-5.2.0/source/blender/sequencer/SEQ_edit.hh
Normal file
@@ -0,0 +1,94 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
struct Editing;
|
||||
struct Main;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
bool edit_strip_swap(Scene *scene, Strip *strip_a, Strip *strip_b, const char **r_error_str);
|
||||
/**
|
||||
* Move strip to seqbase.
|
||||
*
|
||||
* \param scene: Scene containing the editing
|
||||
* \param seqbase: seqbase where `strip` is located
|
||||
* \param strip: Strip to move
|
||||
* \param dst_seqbase: Target seqbase
|
||||
*/
|
||||
bool edit_move_strip_to_seqbase(Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
Strip *strip,
|
||||
ListBaseT<Strip> *dst_seqbase);
|
||||
/**
|
||||
* Move strip to meta-strip.
|
||||
*
|
||||
* \param scene: Scene containing the editing
|
||||
* \param src_strip: Strip to move
|
||||
* \param dst_stripm: Target meta-strip
|
||||
* \param r_error_str: Error message
|
||||
*/
|
||||
bool edit_move_strip_to_meta(Scene *scene,
|
||||
Strip *src_strip,
|
||||
Strip *dst_stripm,
|
||||
const char **r_error_str);
|
||||
/**
|
||||
* Flag strip and its users (effects) for removal.
|
||||
*/
|
||||
void edit_flag_for_removal(Scene *scene, ListBaseT<Strip> *seqbase, Strip *strip);
|
||||
/**
|
||||
* Remove all flagged strips, return true if strip is removed.
|
||||
*/
|
||||
void edit_remove_flagged_strips(Scene *scene, ListBaseT<Strip> *seqbase);
|
||||
void edit_update_muting(Editing *ed);
|
||||
|
||||
enum eSplitMethod {
|
||||
SPLIT_SOFT,
|
||||
SPLIT_HARD,
|
||||
};
|
||||
|
||||
/**
|
||||
* Split Strip at timeline_frame in two.
|
||||
*
|
||||
* \param strip: Strip to be split
|
||||
* \param timeline_frame: frame at which strip is split.
|
||||
* \param method: affects type of offset to be applied to resize Strip
|
||||
* \return The newly created strip. This is always the Strip on the right side.
|
||||
*/
|
||||
Strip *edit_strip_split(Main *bmain,
|
||||
Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
Strip *strip,
|
||||
int timeline_frame,
|
||||
eSplitMethod method,
|
||||
bool ignore_connections,
|
||||
const char **r_error);
|
||||
/**
|
||||
* Find gap after initial_frame and move strips on right side to close the gap
|
||||
*
|
||||
* \param scene: Scene in which strips are located
|
||||
* \param seqbase: List in which strips are located
|
||||
* \param initial_frame: frame on timeline from where gaps are searched for
|
||||
* \param remove_all_gaps: remove all gaps instead of one gap
|
||||
* \return true if gap is removed, otherwise false
|
||||
*/
|
||||
bool edit_remove_gaps(Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
int initial_frame,
|
||||
bool remove_all_gaps);
|
||||
void edit_strip_name_set(Scene *scene, Strip *strip, const char *new_name);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
77
blender-5.2.0/source/blender/sequencer/SEQ_effects.hh
Normal file
77
blender-5.2.0/source/blender/sequencer/SEQ_effects.hh
Normal file
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_vec_types.h"
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
struct Strip;
|
||||
struct VFont;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct RenderData;
|
||||
|
||||
void effect_ensure_initialized(Strip *strip);
|
||||
void effect_free(Strip *strip);
|
||||
|
||||
/* Returns the minimum number of inputs needed by the effect type.
|
||||
* Note: some effects (compositor) will return zero; they can
|
||||
* take variable number of inputs. */
|
||||
int effect_type_get_min_num_inputs(StripType type);
|
||||
bool strip_type_is_effect(StripType type);
|
||||
bool effect_is_transition(StripType type);
|
||||
|
||||
void effect_text_font_set(Strip *strip, VFont *font);
|
||||
bool effects_can_render_text(const Strip *strip);
|
||||
void text_effect_update_runtime(const RenderData *context, TextVars &text, const int2 image_size);
|
||||
std::recursive_mutex &text_runtime_mutex_get();
|
||||
|
||||
struct CharInfo {
|
||||
/** Character offset within text buffer. */
|
||||
int index = 0;
|
||||
/** Byte offset within text buffer. */
|
||||
int offset = 0;
|
||||
/** Size of the character in bytes. */
|
||||
int byte_length = 0;
|
||||
/** Pixel offset of character origin. */
|
||||
float2 position{0.0f, 0.0f};
|
||||
/** FreeType pixel offset for drawing next character after this one. */
|
||||
int advance_x = 0;
|
||||
/** Indicate that the next character after this one should be on a new line. */
|
||||
bool do_wrap = false;
|
||||
};
|
||||
|
||||
struct LineInfo {
|
||||
Vector<CharInfo> characters;
|
||||
/** Pixel width. */
|
||||
int width;
|
||||
};
|
||||
|
||||
struct TextVarsRuntime {
|
||||
Vector<LineInfo> lines;
|
||||
|
||||
int2 image_size;
|
||||
rcti text_boundbox; /* Bound-box used for box drawing and selection. */
|
||||
int line_height;
|
||||
int font_descender;
|
||||
int character_count;
|
||||
int font;
|
||||
bool editing_is_active; /* UI uses this to differentiate behavior. */
|
||||
};
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
170
blender-5.2.0/source/blender/sequencer/SEQ_iterator.hh
Normal file
170
blender-5.2.0/source/blender/sequencer/SEQ_iterator.hh
Normal file
@@ -0,0 +1,170 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
struct SeqTimelineChannel;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/**
|
||||
* Callback format for the for_each function below.
|
||||
*/
|
||||
using ForEachFunc = bool (*)(Strip *strip, void *user_data);
|
||||
|
||||
/**
|
||||
* Utility function to recursively iterate through all sequence strips in a `seqbase` list.
|
||||
* Uses callback to do operations on each element.
|
||||
* The callback can stop the iteration if needed.
|
||||
*
|
||||
* \param seqbase: List of sequences to be iterated over.
|
||||
* \param callback: query function callback, returns false if iteration should stop.
|
||||
* \param user_data: pointer to user data that can be used in the callback function.
|
||||
*/
|
||||
void foreach_strip(ListBaseT<Strip> *seqbase, ForEachFunc callback, void *user_data);
|
||||
|
||||
/** Same as above, but using a more modern FunctionRef as callback. */
|
||||
void foreach_strip(ListBaseT<Strip> *seqbase, FunctionRef<bool(Strip *)> callback);
|
||||
|
||||
/**
|
||||
* Expand set by running `strip_query_func()` for each strip, which will be used as reference.
|
||||
* Results of these queries will be merged into provided collection.
|
||||
*
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \param strips: set of strips to be expanded
|
||||
* \param strip_query_func: query function callback
|
||||
*/
|
||||
void iterator_set_expand(ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &strips,
|
||||
void strip_query_func(Strip *strip_reference,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &strips));
|
||||
/**
|
||||
* Query strips from seqbase. strip_reference is used by query function as filter condition.
|
||||
*
|
||||
* \param strip_reference: reference strip for query function
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \param strip_query_func: query function callback
|
||||
* \return set of strips
|
||||
*/
|
||||
VectorSet<Strip *> query_by_reference(Strip *strip_reference,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
void strip_query_func(Strip *strip_reference,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &strips));
|
||||
/**
|
||||
* Query all selected strips in seqbase.
|
||||
*
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \return set of strips
|
||||
*/
|
||||
VectorSet<Strip *> query_selected_strips(ListBaseT<Strip> *seqbase);
|
||||
/**
|
||||
* Query all unselected strips in seqbase.
|
||||
*
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \return set of strips
|
||||
*/
|
||||
VectorSet<Strip *> query_unselected_strips(ListBaseT<Strip> *seqbase);
|
||||
/**
|
||||
* Query all strips in seqbase. This does not include strips nested in meta strips.
|
||||
*
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \return set of strips
|
||||
*/
|
||||
VectorSet<Strip *> query_all_strips(ListBaseT<Strip> *seqbase);
|
||||
/**
|
||||
* Query all strips in seqbase and nested meta strips.
|
||||
*
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \return set of strips
|
||||
*/
|
||||
VectorSet<Strip *> query_all_strips_recursive(const ListBaseT<Strip> *seqbase);
|
||||
|
||||
/**
|
||||
* Query strips at \a timeline_frame in seqbase and nested meta strips.
|
||||
*
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \param timeline_frame: viewed frame
|
||||
* \return set of strips
|
||||
*/
|
||||
VectorSet<Strip *> query_strips_recursive_at_frame(const Scene *scene,
|
||||
const ListBaseT<Strip> *seqbase,
|
||||
int timeline_frame);
|
||||
|
||||
/**
|
||||
* Recursively query the entire chain of effect strips directly or indirectly
|
||||
* attached to a given reference \a strip, placing result in return parameter \a r_strips.
|
||||
* This includes all effects of \a strip, strips used by another inputs and their effects,
|
||||
* so that whole chain is fully independent of other strips.
|
||||
*
|
||||
* \param strip: reference strip
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \param r_strips: set of strips to be filled
|
||||
*/
|
||||
void query_strip_effect_chain(Strip *strip,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &r_strips);
|
||||
|
||||
/**
|
||||
* Recursively query the entire chain of connected and effect strips directly or indirectly
|
||||
* attached to a given reference \a strip, placing result in return parameter \a r_strips.
|
||||
*
|
||||
* \param strip: reference strip
|
||||
* \param seqbase: List in which strips are queried
|
||||
* \param r_strips: set of strips to be filled
|
||||
*/
|
||||
void query_strip_connected_and_effect_chain(Strip *strip,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &r_strips);
|
||||
|
||||
/**
|
||||
* Query strips that will be rendered at \a timeline_frame on all channels less than
|
||||
* or equal to \a displayed_channel. This does not recurse into meta-strips or sequencer-type scene
|
||||
* strips.
|
||||
*
|
||||
* \note This only returns strips that are directly rendered in the strip stack. Other strips'
|
||||
* content may still be indirectly rendered, such as effect inputs, even though they are not
|
||||
* included in the returned `VectorSet`. See #must_render_strip.
|
||||
* \note Pass \a displayed_channel of 0 to consider all channels.
|
||||
*/
|
||||
VectorSet<Strip *> query_rendered_strips(const Scene *scene,
|
||||
ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
int timeline_frame,
|
||||
int displayed_channel);
|
||||
|
||||
/**
|
||||
* Strips are sorted from lowest to highest channel.
|
||||
* \copydoc #query_rendered_strips
|
||||
*/
|
||||
Vector<Strip *> query_rendered_strips_sorted(const Scene *scene,
|
||||
ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
int timeline_frame,
|
||||
int chanshown);
|
||||
|
||||
/**
|
||||
* Check to see whether we cannot skip rendering this strip.
|
||||
* Some strips do not need to be directly rendered since they are already indirectly rendered as
|
||||
* part of other strips' renders (such as effect strip inputs). These should be skipped to avoid
|
||||
* unnecessary re-rendering.
|
||||
*
|
||||
* \note: Take care when changing the logic of this function since order matters.
|
||||
* */
|
||||
bool must_render_strip(const VectorSet<Strip *> &strip_stack, Strip *target_strip);
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
122
blender-5.2.0/source/blender/sequencer/SEQ_modifier.hh
Normal file
122
blender-5.2.0/source/blender/sequencer/SEQ_modifier.hh
Normal file
@@ -0,0 +1,122 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_sound_types.hh"
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ARegionType;
|
||||
struct BlendDataReader;
|
||||
struct BlendWriter;
|
||||
struct Strip;
|
||||
struct StripModifierData;
|
||||
struct ID;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct ModifierApplyContext;
|
||||
|
||||
struct StripModifierTypeInfo {
|
||||
/**
|
||||
* A unique identifier for this modifier. Used to generate the panel id type name.
|
||||
* See #seq::modifier_type_panel_id.
|
||||
*/
|
||||
char idname[/*MAX_NAME*/ 64];
|
||||
|
||||
/* default name for the modifier */
|
||||
char name[/*MAX_NAME*/ 64];
|
||||
|
||||
/* DNA structure name used on load/save filed */
|
||||
char struct_name[/*MAX_NAME*/ 64];
|
||||
|
||||
/* size of modifier data structure, used by allocation */
|
||||
int struct_size;
|
||||
|
||||
/* data initialization */
|
||||
void (*init_data)(StripModifierData *smd);
|
||||
|
||||
/* free data used by modifier,
|
||||
* only modifier-specific data should be freed, modifier descriptor would
|
||||
* be freed outside of this callback
|
||||
*/
|
||||
void (*free_data)(StripModifierData *smd);
|
||||
|
||||
/* copy data from one modifier to another */
|
||||
void (*copy_data)(StripModifierData *smd, StripModifierData *target);
|
||||
|
||||
/* Apply modifier on an image buffer. */
|
||||
void (*apply)(ModifierApplyContext &context, StripModifierData *smd);
|
||||
|
||||
/** Register the panel types for the modifier's UI. */
|
||||
void (*panel_register)(ARegionType *region_type);
|
||||
|
||||
/* Callback to read custom strip modifier data. */
|
||||
void (*blend_write)(BlendWriter *writer, const StripModifierData *smd);
|
||||
|
||||
/* Callback to write custom strip modifier data. */
|
||||
void (*blend_read)(BlendDataReader *reader, StripModifierData *smd);
|
||||
};
|
||||
|
||||
struct StripModifierDataRuntime {
|
||||
/* Reference parameters for optimizing updates. Sound modifiers can store parameters, sound
|
||||
* inputs and outputs. When all existing parameters do match new ones, the update can be skipped
|
||||
* and old sound handle may be returned. This is to prevent audio glitches, see #141595 */
|
||||
|
||||
/* Reference sound handles (may be used by any sound modifier). */
|
||||
AUD_Sound last_sound_in;
|
||||
AUD_Sound last_sound_out;
|
||||
|
||||
/* Hash to detect change in modifier state. */
|
||||
uint64_t params_hash = 0;
|
||||
|
||||
eStripModifierFlag flag = STRIP_MODIFIER_FLAG_NONE;
|
||||
};
|
||||
|
||||
void modifiers_init();
|
||||
|
||||
const StripModifierTypeInfo *modifier_type_info_get(eStripModifierType type);
|
||||
StripModifierData *modifier_new(Strip *strip, const char *name, eStripModifierType type);
|
||||
bool modifier_remove(Strip *strip, StripModifierData *smd);
|
||||
void modifier_clear(Strip *strip);
|
||||
void modifier_free(StripModifierData *smd);
|
||||
void modifier_unique_name(Strip *strip, StripModifierData *smd);
|
||||
StripModifierData *modifier_find_by_name(Strip *strip, const char *name);
|
||||
/**
|
||||
* Copy the `mod_src` modifier and add it to `strip_dst`.
|
||||
* \param flag: The flag used for copying the system ID properties.
|
||||
*/
|
||||
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src, int flag);
|
||||
void modifier_list_copy(Strip *strip_new, Strip *strip, int flag);
|
||||
bool strip_supports_modifiers(const Strip *strip);
|
||||
|
||||
void modifier_blend_write(BlendWriter *writer, ListBaseT<StripModifierData> *modbase);
|
||||
void modifier_blend_read_data(BlendDataReader *reader, ListBaseT<StripModifierData> *lb);
|
||||
void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd);
|
||||
|
||||
bool modifier_move_to_index(Strip *strip, StripModifierData *smd, int new_index);
|
||||
|
||||
StripModifierData *modifier_get_active(const Strip *strip);
|
||||
void modifier_set_active(Strip *strip, StripModifierData *smd);
|
||||
|
||||
static constexpr char STRIP_MODIFIER_TYPE_PANEL_PREFIX[] = "STRIPMOD_PT_";
|
||||
void modifier_type_panel_id(eStripModifierType type, char *r_idname);
|
||||
|
||||
/* Iterate over all the modifiers and call the callback function for every referenced ID. */
|
||||
void foreach_strip_modifier_id(Strip *strip, const FunctionRef<void(ID *)> fn);
|
||||
|
||||
void compositor_nodes_update_interface(Scene &sequencer_scene,
|
||||
SequencerCompositorModifierData &cmd);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
30
blender-5.2.0/source/blender/sequencer/SEQ_modifiertypes.hh
Normal file
30
blender-5.2.0/source/blender/sequencer/SEQ_modifiertypes.hh
Normal file
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* ****************** Type structures for all modifiers ****************** */
|
||||
|
||||
extern StripModifierTypeInfo seqModifierType_None;
|
||||
extern StripModifierTypeInfo seqModifierType_BrightContrast;
|
||||
extern StripModifierTypeInfo seqModifierType_ColorBalance;
|
||||
extern StripModifierTypeInfo seqModifierType_Compositor;
|
||||
extern StripModifierTypeInfo seqModifierType_Curves;
|
||||
extern StripModifierTypeInfo seqModifierType_HueCorrect;
|
||||
extern StripModifierTypeInfo seqModifierType_Mask;
|
||||
extern StripModifierTypeInfo seqModifierType_SoundEqualizer;
|
||||
extern StripModifierTypeInfo seqModifierType_Pitch;
|
||||
extern StripModifierTypeInfo seqModifierType_Echo;
|
||||
extern StripModifierTypeInfo seqModifierType_Tonemap;
|
||||
extern StripModifierTypeInfo seqModifierType_WhiteBalance;
|
||||
|
||||
} // namespace blender::seq
|
||||
44
blender-5.2.0/source/blender/sequencer/SEQ_offscreen.hh
Normal file
44
blender-5.2.0/source/blender/sequencer/SEQ_offscreen.hh
Normal file
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_object_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_view3d_enums.h"
|
||||
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct GPUOffScreen;
|
||||
struct GPUViewport;
|
||||
struct ImBuf;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
struct View3DShading;
|
||||
|
||||
namespace seq {
|
||||
using DrawViewFn = ImBuf *(*)(Depsgraph *,
|
||||
Scene *,
|
||||
View3DShading *,
|
||||
eDrawType,
|
||||
Object *,
|
||||
int,
|
||||
int,
|
||||
ImBufFlags,
|
||||
eV3DOffscreenDrawFlag,
|
||||
int,
|
||||
const char *,
|
||||
GPUOffScreen *,
|
||||
GPUViewport *,
|
||||
char *);
|
||||
extern DrawViewFn view3d_fn;
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
27
blender-5.2.0/source/blender/sequencer/SEQ_prefetch.hh
Normal file
27
blender-5.2.0/source/blender/sequencer/SEQ_prefetch.hh
Normal file
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct Scene;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void prefetch_stop_all();
|
||||
/**
|
||||
* Use also to update scene and context changes
|
||||
* This function should almost always be called by cache invalidation, not directly.
|
||||
*/
|
||||
void prefetch_stop(Scene *scene);
|
||||
bool prefetch_need_redraw(const bContext *C, Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
42
blender-5.2.0/source/blender/sequencer/SEQ_preview_cache.hh
Normal file
42
blender-5.2.0/source/blender/sequencer/SEQ_preview_cache.hh
Normal file
@@ -0,0 +1,42 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Additionally, this holds GPU textures representing the current frame.
|
||||
* This is to avoid the same GPU texture needing to get re-created if there
|
||||
* are multiple preview areas open (e.g. with scopes).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
|
||||
namespace gpu {
|
||||
class Texture;
|
||||
}
|
||||
|
||||
namespace seq {
|
||||
|
||||
gpu::Texture *preview_cache_get_gpu_texture(
|
||||
Scene *scene, int timeline_frame, int display_channel, int width, int height);
|
||||
void preview_cache_set_gpu_texture(Scene *scene,
|
||||
int timeline_frame,
|
||||
int display_channel,
|
||||
gpu::Texture *texture);
|
||||
gpu::Texture *preview_cache_get_gpu_scope_texture(
|
||||
Scene *scene, int timeline_frame, int display_channel, int width, int height);
|
||||
void preview_cache_set_gpu_scope_texture(Scene *scene,
|
||||
int timeline_frame,
|
||||
int display_channel,
|
||||
gpu::Texture *texture);
|
||||
|
||||
void preview_cache_invalidate(Scene *scene);
|
||||
void preview_cache_destroy(Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
71
blender-5.2.0/source/blender/sequencer/SEQ_proxy.hh
Normal file
71
blender-5.2.0/source/blender/sequencer/SEQ_proxy.hh
Normal file
@@ -0,0 +1,71 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_space_enums.h"
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "IMB_imbuf_enums.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct bContext;
|
||||
struct wmJob;
|
||||
struct wmJobWorkerStatus;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct ProxyBuildContext;
|
||||
struct RenderData;
|
||||
|
||||
/*
|
||||
* Initializes proxy (re)build for the given input strip.
|
||||
* The actual proxy builders, if needed, are added to the
|
||||
* `r_queue` output vector (there can be more than one
|
||||
* for multi-view videos/images).
|
||||
*/
|
||||
bool proxy_build_start(Main *bmain,
|
||||
Scene *scene,
|
||||
Strip *strip,
|
||||
Set<std::string> *processed_paths,
|
||||
bool build_only_on_bad_performance,
|
||||
Vector<ProxyBuildContext *> &r_queue);
|
||||
|
||||
/* Processes a proxy (re)build request in given `context`. */
|
||||
void proxy_build_process(ProxyBuildContext *context,
|
||||
const bool *should_stop,
|
||||
bool *has_updated,
|
||||
FunctionRef<void(float progress)> set_progress_fn);
|
||||
|
||||
/* Cleans up and deallocates the proxy build context. */
|
||||
void proxy_build_finish(ProxyBuildContext *context);
|
||||
|
||||
void proxy_set(Strip *strip, bool value);
|
||||
bool can_use_proxy(const RenderData *context, const Strip *strip, IMB_Proxy_Size psize);
|
||||
IMB_Proxy_Size rendersize_to_proxysize(eSpaceSeq_Proxy_RenderSize render_size);
|
||||
float rendersize_to_scale_factor(eSpaceSeq_Proxy_RenderSize render_size);
|
||||
|
||||
struct ProxyJob {
|
||||
Main *main = nullptr;
|
||||
Scene *scene = nullptr;
|
||||
Vector<ProxyBuildContext *> queue;
|
||||
int stop = 0;
|
||||
};
|
||||
|
||||
wmJob *ED_seq_proxy_wm_job_get(const bContext *C);
|
||||
ProxyJob *ED_seq_proxy_job_get(const bContext *C, wmJob *wm_job);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
132
blender-5.2.0/source/blender/sequencer/SEQ_relations.hh
Normal file
132
blender-5.2.0/source/blender/sequencer/SEQ_relations.hh
Normal file
@@ -0,0 +1,132 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
#include "BLI_enum_flags.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
struct MovieClip;
|
||||
struct ReportList;
|
||||
struct bNodeTree;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/**
|
||||
* Check if one strip is input to the other.
|
||||
*/
|
||||
bool relation_is_effect_of_strip(const Strip *effect, const Strip *input);
|
||||
/**
|
||||
* Free currently open movie strip readers.
|
||||
*/
|
||||
void strip_free_movie_readers(Strip *strip);
|
||||
bool relations_check_scene_recursion(Scene *scene, ReportList *reports);
|
||||
/**
|
||||
* Check if "strip_main" (indirectly) uses strip "strip".
|
||||
*/
|
||||
bool relations_render_loop_check(Strip *strip_main, Strip *strip);
|
||||
void relations_free_imbuf(Scene *scene, ListBaseT<Strip> *seqbase, bool for_render);
|
||||
|
||||
/**
|
||||
* Invalidates various caches related to a given strip:
|
||||
* - Final cached frames over the length of the strip,
|
||||
* - Intra-frame caches of the current frame,
|
||||
* - Source/raw caches of the meta strip that contains this strip, if any,
|
||||
* - Media presence cache of the strip,
|
||||
* - Rebuilds speed index map if this is a speed effect strip,
|
||||
* - Tags DEG for strip recalculation,
|
||||
* - Stops prefetching job, if any.
|
||||
*/
|
||||
void relations_invalidate_cache(Scene *scene, Strip *strip);
|
||||
|
||||
/**
|
||||
* Does everything #relations_invalidate_cache does, plus invalidates cached raw source
|
||||
* images of the strip.
|
||||
*/
|
||||
void relations_invalidate_cache_raw(Scene *scene, Strip *strip);
|
||||
void relations_invalidate_scene_strips(const Main *bmain, const Scene *scene_target);
|
||||
|
||||
/**
|
||||
* Sync sequencer scene strips that reference a view layer by name.
|
||||
* This updates `strip->scene_view_layer_name` from \a old_name to \a new_name for any matching
|
||||
* strip across all sequencer scenes that use \a scene as input.
|
||||
*
|
||||
* NOTE: If a view layer is deleted, `new_name = nullptr` should be passed to clear the strips'
|
||||
* `scene_view_layer_name` to the default view layer. In this case, the function will also clear
|
||||
* the caches of these matching strips to evict stale frames.
|
||||
*/
|
||||
void relations_update_view_layer_scene_strips(Main *bmain,
|
||||
Scene *scene,
|
||||
const char *old_name,
|
||||
const char *new_name);
|
||||
|
||||
/**
|
||||
* Invalidates the cache for all strips that uses the given compositor node tree.
|
||||
*/
|
||||
void relations_invalidate_compositor_users(const Main *bmain, const bNodeTree *node_tree);
|
||||
|
||||
void relations_invalidate_movieclip_strips(Main *bmain, MovieClip *clip_target);
|
||||
/**
|
||||
* Release FFmpeg handles of strips that are not currently displayed to minimize memory usage.
|
||||
*/
|
||||
void relations_free_all_anim_ibufs(Scene *scene, int timeline_frame);
|
||||
/**
|
||||
* A debug and development function which checks whether strips have unique UIDs.
|
||||
* Errors will be reported to the console.
|
||||
*/
|
||||
void relations_check_uids_unique_and_report(const Scene *scene);
|
||||
/**
|
||||
* Generate new UID for the given strip.
|
||||
*/
|
||||
void relations_session_uid_generate(Strip *strip);
|
||||
|
||||
enum class CacheCleanup {
|
||||
FinalImage = (1 << 0),
|
||||
SourceImage = (1 << 1),
|
||||
Thumbnails = (1 << 2),
|
||||
IntraFrame = (1 << 3),
|
||||
|
||||
/* All cache types. */
|
||||
All = FinalImage | SourceImage | Thumbnails | IntraFrame,
|
||||
|
||||
/* Typical "what gets rendered" cache types: final frame
|
||||
* cache, plus various intra-frame cached things. */
|
||||
FinalAndIntra = FinalImage | IntraFrame,
|
||||
};
|
||||
ENUM_OPERATORS(CacheCleanup);
|
||||
|
||||
void cache_cleanup(Scene *scene, CacheCleanup mode);
|
||||
|
||||
void cache_settings_changed(Scene *scene);
|
||||
bool is_cache_full(const Scene *scene);
|
||||
bool evict_caches_if_full(Scene *scene);
|
||||
|
||||
void source_image_cache_iterate(Scene *scene,
|
||||
void *userdata,
|
||||
void callback_iter(void *userdata,
|
||||
const Strip *strip,
|
||||
int timeline_frame));
|
||||
void final_image_cache_iterate(Scene *scene,
|
||||
void *userdata,
|
||||
void callback_iter(void *userdata, int timeline_frame));
|
||||
|
||||
size_t source_image_cache_calc_memory_size(const Scene *scene);
|
||||
size_t final_image_cache_calc_memory_size(const Scene *scene);
|
||||
|
||||
bool exists_in_seqbase(const Strip *strip, const ListBaseT<Strip> *seqbase);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
102
blender-5.2.0/source/blender/sequencer/SEQ_render.hh
Normal file
102
blender-5.2.0/source/blender/sequencer/SEQ_render.hh
Normal file
@@ -0,0 +1,102 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_space_enums.h"
|
||||
#include "GPU_context.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct GPUOffScreen;
|
||||
struct GPUViewport;
|
||||
struct ImBuf;
|
||||
struct Main;
|
||||
struct Render;
|
||||
struct Scene;
|
||||
struct SeqTimelineChannel;
|
||||
struct Strip;
|
||||
struct StripElem;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct RenderData {
|
||||
Main *bmain = nullptr;
|
||||
Depsgraph *depsgraph = nullptr;
|
||||
Scene *scene = nullptr;
|
||||
int rectx = 0;
|
||||
int recty = 0;
|
||||
eSpaceSeq_Proxy_RenderSize preview_render_size = SEQ_RENDER_SIZE_SCENE;
|
||||
bool use_proxies = false;
|
||||
bool ignore_missing_media = false;
|
||||
int motion_blur_samples = 0;
|
||||
float motion_blur_shutter = 0.0f;
|
||||
bool skip_cache = false;
|
||||
bool is_prefetch_render = false;
|
||||
bool is_playing = false;
|
||||
bool is_scrubbing = false;
|
||||
int view_id = 0;
|
||||
|
||||
/* Set when executing as part of a frame or animation render. */
|
||||
Render *render = nullptr;
|
||||
|
||||
/* special case for OpenGL render */
|
||||
GPUOffScreen *gpu_offscreen = nullptr;
|
||||
GPUViewport *gpu_viewport = nullptr;
|
||||
// int gpu_samples;
|
||||
// bool gpu_full_samples;
|
||||
|
||||
/* If GPU access is needed and this is set, use it
|
||||
* instead of regular GPU context. Primary case: prefetch job;
|
||||
* it is on another thread and can't use regular GPU context. */
|
||||
gpu::GPUSecondaryContextData gpu_context;
|
||||
};
|
||||
|
||||
/**
|
||||
* \return The image buffer or NULL.
|
||||
*
|
||||
* \note The returned #ImBuf has its reference increased, free after usage!
|
||||
*/
|
||||
ImBuf *render_give_ibuf(const RenderData *context, float timeline_frame, int chanshown);
|
||||
ImBuf *render_give_ibuf_direct(const RenderData *context, float timeline_frame, Strip *strip);
|
||||
void render_new_render_data(Main *bmain,
|
||||
Depsgraph *depsgraph,
|
||||
Scene *scene,
|
||||
int rectx,
|
||||
int recty,
|
||||
eSpaceSeq_Proxy_RenderSize preview_render_size,
|
||||
Render *render,
|
||||
RenderData *r_context);
|
||||
StripElem *render_give_stripelem(const Scene *scene, const Strip *strip, int timeline_frame);
|
||||
|
||||
/* Converts image into scene linear, if needed.
|
||||
* Note: if make_float is false and input image contains byte pixels, it is *NOT* modified. */
|
||||
void ensure_ibuf_is_linear_space(ImBuf *ibuf, bool make_float);
|
||||
|
||||
/**
|
||||
* Check if `strip` is muted for rendering.
|
||||
* This function also checks `SeqTimelineChannel` flag.
|
||||
*/
|
||||
bool render_is_muted(const ListBaseT<SeqTimelineChannel> *channels, const Strip *strip);
|
||||
|
||||
/**
|
||||
* Calculate render scale factor relative to full size. This can be due to render
|
||||
* scale setting in output settings, or preview proxy size.
|
||||
*/
|
||||
float get_render_scale_factor(eSpaceSeq_Proxy_RenderSize render_size, short scene_render_scale);
|
||||
float get_render_scale_factor(const RenderData &context);
|
||||
|
||||
/* Enables an appropriate GPU context if possible, if GPU rendering is not possible, the functions
|
||||
* returns false. The render_end_gpu function should be called to disable the context. */
|
||||
bool render_begin_gpu(const RenderData &rd);
|
||||
void render_end_gpu(const RenderData &rd);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
90
blender-5.2.0/source/blender/sequencer/SEQ_retiming.hh
Normal file
90
blender-5.2.0/source/blender/sequencer/SEQ_retiming.hh
Normal file
@@ -0,0 +1,90 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Editing;
|
||||
struct ReportList;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct SeqRetimingKey;
|
||||
|
||||
namespace seq {
|
||||
|
||||
MutableSpan<SeqRetimingKey> retiming_keys_get(const Strip *strip);
|
||||
int left_fake_key_frame_get(const Scene *scene, const Strip *strip);
|
||||
int right_fake_key_frame_get(const Scene *scene, const Strip *strip);
|
||||
Map<SeqRetimingKey *, Strip *> retiming_selection_get(const Editing *ed);
|
||||
int retiming_keys_count(const Strip *strip);
|
||||
bool retiming_has_keys(const Strip *strip);
|
||||
void retiming_data_ensure(Strip *strip);
|
||||
SeqRetimingKey *ensure_left_and_right_keys(const Scene *scene, Strip *strip);
|
||||
void realize_fake_keys(const Scene *scene, Strip *strip);
|
||||
SeqRetimingKey fake_retiming_key_init(const Scene *scene, const Strip *strip, int frame);
|
||||
void retiming_data_clear(Strip *strip);
|
||||
void retiming_reset(Scene *scene, Strip *strip);
|
||||
bool retiming_is_allowed(const Strip *strip);
|
||||
|
||||
SeqRetimingKey *retiming_key_add_new_for_strip(const Scene *scene,
|
||||
ReportList *reports,
|
||||
Strip *strip,
|
||||
const int frame);
|
||||
/**
|
||||
* Add new retiming key.
|
||||
* This function always reallocates memory, so when function is used all stored pointers will
|
||||
* become invalid.
|
||||
*/
|
||||
SeqRetimingKey *retiming_add_key(const Scene *scene, Strip *strip, int frame);
|
||||
SeqRetimingKey *retiming_add_transition(const Scene *scene,
|
||||
Strip *strip,
|
||||
SeqRetimingKey *key,
|
||||
float offset);
|
||||
SeqRetimingKey *retiming_add_freeze_frame(const Scene *scene,
|
||||
Strip *strip,
|
||||
SeqRetimingKey *key,
|
||||
const int offset);
|
||||
bool retiming_is_last_key(const Strip *strip, const SeqRetimingKey *key);
|
||||
SeqRetimingKey *retiming_last_key_get(const Strip *strip);
|
||||
void retiming_remove_key(Strip *strip, SeqRetimingKey *key);
|
||||
void retiming_transition_key_frame_set(const Scene *scene,
|
||||
const Strip *strip,
|
||||
SeqRetimingKey *key,
|
||||
int frame);
|
||||
float retiming_key_speed_get(const Strip *strip, const SeqRetimingKey *key);
|
||||
void retiming_key_speed_set(const Scene *scene, Strip *strip, SeqRetimingKey *key, float speed);
|
||||
int retiming_key_index_get(const Strip *strip, const SeqRetimingKey *key);
|
||||
SeqRetimingKey *retiming_key_get_by_frame(const Scene *scene, const Strip *strip, int frame);
|
||||
void retiming_sound_animation_data_set(const Scene *scene, const Strip *strip);
|
||||
/**
|
||||
* Get timeline frame of some `key` associated with `strip`.
|
||||
* This is absolute, not a frame index from the start of the `strip`.
|
||||
*/
|
||||
int retiming_key_frame_get(const Scene *scene, const Strip *strip, const SeqRetimingKey *key);
|
||||
void retiming_key_frame_set(const Scene *scene, Strip *strip, SeqRetimingKey *key, int frame);
|
||||
SeqRetimingKey *retiming_find_segment_start_key(const Strip *strip, float frame_index);
|
||||
bool retiming_key_is_transition_type(const SeqRetimingKey *key);
|
||||
bool retiming_key_is_transition_start(const SeqRetimingKey *key);
|
||||
SeqRetimingKey *retiming_transition_start_get(SeqRetimingKey *key);
|
||||
bool retiming_key_is_freeze_frame(const SeqRetimingKey *key);
|
||||
bool retiming_selection_clear(const Editing *ed);
|
||||
void retiming_selection_append(SeqRetimingKey *key);
|
||||
void retiming_selection_remove(SeqRetimingKey *key);
|
||||
void retiming_selection_copy(SeqRetimingKey *dst, const SeqRetimingKey *src);
|
||||
void retiming_remove_multiple_keys(Strip *strip, Vector<SeqRetimingKey *> &keys_to_remove);
|
||||
bool retiming_selection_contains(const Editing *ed, const SeqRetimingKey *key);
|
||||
bool retiming_selection_has_whole_transition(const Editing *ed, SeqRetimingKey *key);
|
||||
bool retiming_show_keys(const Strip *strip);
|
||||
bool retiming_keys_are_selected(const Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
24
blender-5.2.0/source/blender/sequencer/SEQ_select.hh
Normal file
24
blender-5.2.0/source/blender/sequencer/SEQ_select.hh
Normal file
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
Strip *select_active_get(const Scene *scene);
|
||||
bool select_active_get_pair(Scene *scene, Strip **r_strip_act, Strip **r_strip_other);
|
||||
void select_active_set(Scene *scene, Strip *strip);
|
||||
bool select_has_any(const Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
267
blender-5.2.0/source/blender/sequencer/SEQ_sequencer.hh
Normal file
267
blender-5.2.0/source/blender/sequencer/SEQ_sequencer.hh
Normal file
@@ -0,0 +1,267 @@
|
||||
/* SPDX-FileCopyrightText: 2004-2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_sound_types.hh"
|
||||
#include "BLI_enum_flags.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_session_uid_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct BlendDataReader;
|
||||
struct BlendWriter;
|
||||
struct Depsgraph;
|
||||
struct Editing;
|
||||
struct Main;
|
||||
struct MetaStack;
|
||||
struct MovieReader;
|
||||
struct Scene;
|
||||
struct SeqTimelineChannel;
|
||||
struct Strip;
|
||||
struct SequencerToolSettings;
|
||||
|
||||
namespace seq {
|
||||
|
||||
class CompositorCache;
|
||||
struct FinalImageCache;
|
||||
struct IntraFrameCache;
|
||||
struct MediaPresence;
|
||||
struct PrefetchJob;
|
||||
struct PreviewCache;
|
||||
struct SourceImageCache;
|
||||
struct StripLookup;
|
||||
struct ThumbnailCache;
|
||||
|
||||
constexpr int MAX_CHANNELS = 128;
|
||||
|
||||
/* RNA enums, just to be more readable */
|
||||
enum {
|
||||
SIDE_MOUSE = -1,
|
||||
SIDE_NONE = 0,
|
||||
SIDE_LEFT,
|
||||
SIDE_RIGHT,
|
||||
SIDE_BOTH,
|
||||
SIDE_NO_CHANGE,
|
||||
};
|
||||
|
||||
/* strip_duplicate' flags */
|
||||
enum class StripDuplicate : uint8_t {
|
||||
/* Note: Technically, the selected strips are duplicated when `All` is not set. */
|
||||
Selected = 0,
|
||||
/* Ensure strips have a unique name. */
|
||||
UniqueName = (1 << 0),
|
||||
/* Duplicate strips and the IDs they reference. */
|
||||
Data = (1 << 1),
|
||||
/* If this is set, duplicate all strips. If not set, duplicate selected strips. */
|
||||
All = (1 << 3),
|
||||
};
|
||||
ENUM_OPERATORS(StripDuplicate);
|
||||
|
||||
enum class StripRuntimeFlag {
|
||||
None = 0,
|
||||
ClampedLH = (1 << 0),
|
||||
ClampedRH = (1 << 1),
|
||||
Overlap = (1 << 2),
|
||||
MarkForDelete = (1 << 4),
|
||||
IgnoreChannelLock = (1 << 5), /* For #SEQUENCER_OT_duplicate_move macro. */
|
||||
ShowOffsets = (1 << 6), /* Set during #SEQUENCER_OT_slip. */
|
||||
};
|
||||
ENUM_OPERATORS(StripRuntimeFlag);
|
||||
|
||||
struct StripRuntime {
|
||||
~StripRuntime();
|
||||
|
||||
SessionUID session_uid = {};
|
||||
StripRuntimeFlag flag = StripRuntimeFlag::None;
|
||||
AUD_SequenceEntry scene_sound;
|
||||
AUD_Sound sound_time_stretch;
|
||||
float sound_time_stretch_fps = 0.0f;
|
||||
|
||||
Vector<MovieReader *, 1> movie_readers;
|
||||
/* To detect the removal of a sound modifier. */
|
||||
int sound_modifiers_count = 0;
|
||||
|
||||
[[nodiscard]] MovieReader *movie_reader_get(int64_t index = 0) const
|
||||
{
|
||||
if (index < 0 || index >= movie_readers.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return movie_readers[index];
|
||||
}
|
||||
|
||||
void clear_sound_time_stretch();
|
||||
void remove_scene_sound(Scene *scene);
|
||||
};
|
||||
|
||||
struct EditingRuntime {
|
||||
~EditingRuntime();
|
||||
|
||||
StripLookup *strip_lookup = nullptr;
|
||||
MediaPresence *media_presence = nullptr;
|
||||
ThumbnailCache *thumbnail_cache = nullptr;
|
||||
IntraFrameCache *intra_frame_cache = nullptr;
|
||||
SourceImageCache *source_image_cache = nullptr;
|
||||
FinalImageCache *final_image_cache = nullptr;
|
||||
PreviewCache *preview_cache = nullptr;
|
||||
PrefetchJob *prefetch_job = nullptr;
|
||||
CompositorCache *compositor_cache = nullptr;
|
||||
|
||||
/** Used for rendering a different frame using sequencer_draw_get_transform_preview from the box
|
||||
* blade tool. */
|
||||
int transform_preview_frame = 0;
|
||||
bool show_transform_preview = false;
|
||||
|
||||
CompositorCache &ensure_compositor_cache();
|
||||
};
|
||||
|
||||
SequencerToolSettings *tool_settings_init();
|
||||
SequencerToolSettings *tool_settings_ensure(Scene *scene);
|
||||
void tool_settings_free(SequencerToolSettings *tool_settings);
|
||||
eSeqImageFitMethod tool_settings_fit_method_get(Scene *scene);
|
||||
void tool_settings_fit_method_set(Scene *scene, eSeqImageFitMethod fit_method);
|
||||
short tool_settings_snap_flag_get(Scene *scene);
|
||||
short tool_settings_snap_mode_get(Scene *scene);
|
||||
int tool_settings_snap_distance_get(Scene *scene);
|
||||
eSeqOverlapMode tool_settings_overlap_mode_get(Scene *scene);
|
||||
int tool_settings_pivot_point_get(Scene *scene);
|
||||
SequencerToolSettings *tool_settings_copy(SequencerToolSettings *tool_settings);
|
||||
Editing *editing_get(const Scene *scene);
|
||||
Editing *editing_ensure(Scene *scene);
|
||||
void editing_free(Scene *scene, bool do_id_user);
|
||||
/**
|
||||
* Get seqbase that is being viewed currently. This can be main seqbase or meta strip seqbase
|
||||
*
|
||||
* \param ed: sequence editor data
|
||||
* \return pointer to active seqbase. returns NULL if ed is NULL
|
||||
*/
|
||||
ListBaseT<Strip> *active_seqbase_get(const Editing *ed);
|
||||
Strip *strip_alloc(ListBaseT<Strip> *lb, int timeline_frame, int channel, StripType type);
|
||||
void strip_free(Scene *scene, Strip *strip);
|
||||
/**
|
||||
* Get #MetaStack that corresponds to current level that is being viewed
|
||||
*
|
||||
* \return pointer to meta stack
|
||||
*/
|
||||
MetaStack *meta_stack_active_get(const Editing *ed);
|
||||
/**
|
||||
* Open Meta strip content for editing.
|
||||
*
|
||||
* \param scene: Scene containing the sequence editor data.
|
||||
* \param dst: meta strip or NULL for top level view
|
||||
*/
|
||||
void meta_stack_set(const Scene *scene, Strip *dst);
|
||||
/**
|
||||
* Close last Meta strip open for editing.
|
||||
*
|
||||
* \param ed: sequence editor data
|
||||
*/
|
||||
Strip *meta_stack_pop(Editing *ed);
|
||||
Strip *strip_duplicate_recursive(Main *bmain,
|
||||
const Scene *scene_src,
|
||||
Scene *scene_dst,
|
||||
ListBaseT<Strip> *seqbase_dst,
|
||||
Strip *strip,
|
||||
StripDuplicate dupe_flag);
|
||||
void seqbase_duplicate_recursive(Main *bmain,
|
||||
const Scene *scene_src,
|
||||
Scene *scene_dst,
|
||||
ListBaseT<Strip> *seqbase_dst,
|
||||
const ListBaseT<Strip> *seqbase_src,
|
||||
StripDuplicate dupe_flag,
|
||||
int copy_flag);
|
||||
bool is_valid_strip_channel(const Strip *strip);
|
||||
|
||||
/**
|
||||
* Read and Write functions for `.blend` file data.
|
||||
*/
|
||||
void blend_write(BlendWriter *writer, ListBaseT<Strip> *seqbase);
|
||||
void blend_read(BlendDataReader *reader, ListBaseT<Strip> *seqbase);
|
||||
|
||||
void doversion_250_sound_proxy_update(Main *bmain, Editing *ed);
|
||||
|
||||
/* Depsgraph update function. */
|
||||
|
||||
/**
|
||||
* Evaluate parts of strips which needs to be done as a part of a dependency graph evaluation.
|
||||
* This does NOT include actual rendering of the strips, but rather makes them up-to-date for
|
||||
* animation playback and makes them ready for the sequencer's rendering pipeline to render them.
|
||||
*/
|
||||
void eval_strips(Depsgraph *depsgraph, Scene *scene, ListBaseT<Strip> *seqbase);
|
||||
|
||||
/**
|
||||
* Find a strip with a given name.
|
||||
* If lookup hash doesn't exist, it will be created. If hash is tagged as invalid, it will be
|
||||
* rebuilt.
|
||||
*
|
||||
* \param ed: Editing that owns lookup hash
|
||||
* \param key: Strip name without SQ prefix (strip->name + 2)
|
||||
*
|
||||
* \return pointer to Strip
|
||||
*/
|
||||
Strip *lookup_strip_by_name(Editing *ed, const char *key);
|
||||
|
||||
/**
|
||||
* Find a strips using provided scene as input
|
||||
*
|
||||
* \param ed: Editing that owns lookup hash
|
||||
* \param key: Input Scene pointer
|
||||
*
|
||||
* \return Span of strips
|
||||
*/
|
||||
Span<Strip *> lookup_strips_by_scene(Editing *ed, const Scene *key);
|
||||
|
||||
/**
|
||||
* Returns Map of scenes to scene strips
|
||||
*
|
||||
* \param ed: Editing that owns lookup hash
|
||||
*/
|
||||
Map<const Scene *, VectorSet<Strip *>> &lookup_strips_by_scene_map_get(Editing *ed);
|
||||
|
||||
/**
|
||||
* Find all strips using provided compositor node tree
|
||||
*
|
||||
* \param ed: Editing that owns lookup hash
|
||||
* \param key: Node tree pointer
|
||||
*
|
||||
* \return Span of strips
|
||||
*/
|
||||
Span<Strip *> lookup_strips_by_compositor_node_group(Editing *ed, const bNodeTree *key);
|
||||
|
||||
/**
|
||||
* Find which meta strip the given timeline channel belongs to. Returns nullptr if it is a global
|
||||
* channel.
|
||||
*/
|
||||
Strip *lookup_strip_by_channel_owner(Editing *ed, const SeqTimelineChannel *channel);
|
||||
/**
|
||||
* Find meta strip, that contains strip `key`.
|
||||
* If lookup hash doesn't exist, it will be created. If hash is tagged as invalid, it will be
|
||||
* rebuilt.
|
||||
*
|
||||
* \param key: pointer to Strip inside of meta strip
|
||||
*
|
||||
* \return pointer to meta strip
|
||||
*/
|
||||
Strip *lookup_meta_by_strip(Editing *ed, const Strip *key);
|
||||
/**
|
||||
* Free lookup hash data.
|
||||
*/
|
||||
void strip_lookup_free(Editing *ed);
|
||||
|
||||
/**
|
||||
* Mark strip lookup as invalid (i.e. will need rebuilding).
|
||||
*/
|
||||
void strip_lookup_invalidate(const Editing *ed);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
82
blender-5.2.0/source/blender/sequencer/SEQ_sound.hh
Normal file
82
blender-5.2.0/source/blender/sequencer/SEQ_sound.hh
Normal file
@@ -0,0 +1,82 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_sound_types.hh"
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
struct EQCurveMappingData;
|
||||
struct Main;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct bSound;
|
||||
struct StripModifierData;
|
||||
struct BlendWriter;
|
||||
struct BlendDataReader;
|
||||
struct SoundEqualizerModifierData;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct SoundModifierWorkerInfo {
|
||||
int type;
|
||||
AUD_Sound (*recreator)(Strip *strip,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound,
|
||||
bool &needs_update);
|
||||
};
|
||||
|
||||
#define SOUND_EQUALIZER_DEFAULT_MIN_FREQ 30.0
|
||||
#define SOUND_EQUALIZER_DEFAULT_MAX_FREQ 20000.0
|
||||
#define SOUND_EQUALIZER_DEFAULT_MAX_DB 35.0
|
||||
#define SOUND_EQUALIZER_SIZE_CONVERSION 2048
|
||||
#define SOUND_EQUALIZER_SIZE_DEFINITION 1000
|
||||
|
||||
void sound_update_bounds_all(Scene *scene);
|
||||
void sound_update_bounds(Scene *scene, Strip *strip);
|
||||
void sound_update(Scene *scene, bSound *sound);
|
||||
void sound_update_length(Main *bmain, Scene *scene);
|
||||
float sound_pitch_get(const Scene *scene, const Strip *strip);
|
||||
EQCurveMappingData *sound_equalizer_add(SoundEqualizerModifierData *semd, float minX, float maxX);
|
||||
void sound_blend_write(BlendWriter *writer, ListBaseT<bSound> *soundbase);
|
||||
void sound_blend_read_data(BlendDataReader *reader, ListBaseT<bSound> *lb);
|
||||
|
||||
AUD_Sound sound_modifier_recreator(Strip *strip,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound,
|
||||
bool &needs_update);
|
||||
|
||||
void sound_equalizermodifier_init_data(StripModifierData *smd);
|
||||
void sound_equalizermodifier_free(StripModifierData *smd);
|
||||
void sound_equalizermodifier_copy_data(StripModifierData *target, StripModifierData *smd);
|
||||
AUD_Sound sound_equalizermodifier_recreator(Strip *strip,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound,
|
||||
bool &needs_update);
|
||||
void sound_equalizermodifier_set_graphs(SoundEqualizerModifierData *semd, int number);
|
||||
const SoundModifierWorkerInfo *sound_modifier_worker_info_get(int type);
|
||||
EQCurveMappingData *sound_equalizermodifier_add_graph(SoundEqualizerModifierData *semd,
|
||||
float min_freq,
|
||||
float max_freq);
|
||||
void sound_equalizermodifier_remove_graph(SoundEqualizerModifierData *semd,
|
||||
EQCurveMappingData *eqcmd);
|
||||
|
||||
AUD_Sound pitchmodifier_recreator(Strip * /*strip*/,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound,
|
||||
bool &needs_update);
|
||||
AUD_Sound echomodifier_recreator(Strip * /*strip*/,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound,
|
||||
bool &needs_update);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct ImBuf;
|
||||
struct rctf;
|
||||
struct Strip;
|
||||
struct Scene;
|
||||
|
||||
namespace seq {
|
||||
|
||||
static constexpr int THUMB_SIZE = 256;
|
||||
|
||||
/**
|
||||
* Get a thumbnail image for given `strip` at `timeline_frame`.
|
||||
*
|
||||
* The function can return null if a strip type does not have a thumbnail, a source media file is
|
||||
* not found, or the thumbnail has not been loaded yet.
|
||||
*
|
||||
* A "closest" thumbnail if there is no exact match can also be returned, e.g. for a movie strip
|
||||
* the closest frame that has a thumbnail already.
|
||||
*
|
||||
* When there is no exact match, a request to load a thumbnail will be internally added and
|
||||
* processed in the background. */
|
||||
ImBuf *thumbnail_cache_get(const bContext *C,
|
||||
Scene *scene,
|
||||
const Strip *strip,
|
||||
float timeline_frame);
|
||||
|
||||
/**
|
||||
* If total amount of resident thumbnails is too large, try to remove oldest-used ones to
|
||||
* keep the cache size in check.
|
||||
*/
|
||||
void thumbnail_cache_maintain_capacity(Scene *scene);
|
||||
|
||||
void thumbnail_cache_invalidate_strip(Scene *scene, const Strip *strip);
|
||||
|
||||
/**
|
||||
* Discard in-flight thumbnail loading requests that are outside of the given view (X coordinate:
|
||||
* timeline frames, Y coordinate: channels).
|
||||
*/
|
||||
void thumbnail_cache_discard_requests_outside(Scene *scene, const rctf &rect);
|
||||
|
||||
void thumbnail_cache_clear(Scene *scene);
|
||||
void thumbnail_cache_destroy(Scene *scene);
|
||||
|
||||
bool strip_can_have_thumbnail(const Scene *scene, const Strip *strip);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
73
blender-5.2.0/source/blender/sequencer/SEQ_time.hh
Normal file
73
blender-5.2.0/source/blender/sequencer/SEQ_time.hh
Normal file
@@ -0,0 +1,73 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct rctf;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/**
|
||||
* Initialize given rectangle with the Scene's timeline boundaries.
|
||||
*
|
||||
* \param scene: the Scene instance whose timeline boundaries are extracted from
|
||||
* \param r_rect: output parameter to be filled with timeline boundaries
|
||||
*/
|
||||
void timeline_init_boundbox(const Scene *scene, rctf *r_rect);
|
||||
/**
|
||||
* Stretch the given rectangle to include the given strips boundaries
|
||||
*
|
||||
* \param seqbase: List in which strips are located
|
||||
* \param rect: output parameter to be filled with strips' boundaries
|
||||
*/
|
||||
void timeline_expand_boundbox(const Scene *scene, const ListBaseT<Strip> *seqbase, rctf *rect);
|
||||
/**
|
||||
* Define boundary rectangle of sequencer timeline and fill in rect data
|
||||
*
|
||||
* \param scene: Scene in which strips are located
|
||||
* \param seqbase: List in which strips are located
|
||||
* \param r_rect: data structure describing rectangle, that will be filled in by this function
|
||||
*/
|
||||
void timeline_boundbox(const Scene *scene, const ListBaseT<Strip> *seqbase, rctf *r_rect);
|
||||
/**
|
||||
* Find start or end position of next or previous strip.
|
||||
* \param scene: Video editing scene
|
||||
* \param timeline_frame: reference frame for searching
|
||||
* \param side: direction of searching, `SIDE_LEFT`, `SIDE_RIGHT` or `SIDE_BOTH`.
|
||||
* \param do_center: find closest strip center if true, otherwise finds closest handle position.
|
||||
* \param do_unselected: only find closest position of unselected strip.
|
||||
*/
|
||||
int time_find_next_prev_edit(Scene *scene,
|
||||
int timeline_frame,
|
||||
short side,
|
||||
bool do_skip_mute,
|
||||
bool do_center,
|
||||
bool do_unselected);
|
||||
/* Convert timeline frame so strip frame index. */
|
||||
float give_frame_index(const Scene *scene, const Strip *strip, float timeline_frame);
|
||||
/**
|
||||
* Update meta strip content start and end, update sound playback range.
|
||||
* To be used after any contained strip length or position has changed.
|
||||
*
|
||||
* \note this function is currently only used internally and in versioning code.
|
||||
*/
|
||||
void time_update_meta_strip_range(const Scene *scene, Strip *strip_meta);
|
||||
/**
|
||||
* Move contents of a strip without moving the strip handles.
|
||||
*/
|
||||
void time_slip_strip(
|
||||
const Scene *scene, Strip *strip, int frame_delta, float subframe_delta, bool slip_keyframes);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
144
blender-5.2.0/source/blender/sequencer/SEQ_transform.hh
Normal file
144
blender-5.2.0/source/blender/sequencer/SEQ_transform.hh
Normal file
@@ -0,0 +1,144 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_bounds_types.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct SeqTimelineChannel;
|
||||
struct TimeMarker;
|
||||
|
||||
namespace seq {
|
||||
|
||||
bool transform_strip_can_be_translated(const Strip *strip);
|
||||
/**
|
||||
* Checks whether the strip functions as a single static display,
|
||||
* which means it has only one unique frame of content and does not draw holds.
|
||||
* This includes non-sequence image strips and all effect strips with no inputs (e.g. color, text).
|
||||
*/
|
||||
bool transform_single_image_check(const Strip *strip);
|
||||
bool transform_test_overlap(const Scene *scene, ListBaseT<Strip> *seqbasep, Strip *test);
|
||||
bool transform_test_overlap(const Scene *scene, Strip *strip1, Strip *strip2);
|
||||
void transform_translate_strip(Scene *evil_scene, Strip *strip, int delta);
|
||||
/**
|
||||
* \return 0 if there weren't enough space.
|
||||
*/
|
||||
bool transform_seqbase_shuffle_ex(ListBaseT<Strip> *seqbasep,
|
||||
Strip *test,
|
||||
Scene *evil_scene,
|
||||
int channel_delta);
|
||||
bool transform_seqbase_shuffle(ListBaseT<Strip> *seqbasep, Strip *test, Scene *evil_scene);
|
||||
bool transform_seqbase_shuffle_time(Span<Strip *> strips_to_shuffle,
|
||||
Span<Strip *> time_dependent_strips,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Scene *evil_scene,
|
||||
ListBaseT<TimeMarker> *markers,
|
||||
bool use_sync_markers);
|
||||
bool transform_seqbase_shuffle_time(Span<Strip *> strips_to_shuffle,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Scene *evil_scene,
|
||||
ListBaseT<TimeMarker> *markers,
|
||||
bool use_sync_markers);
|
||||
|
||||
void transform_handle_overlap(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips,
|
||||
Span<Strip *> time_dependent_strips,
|
||||
bool use_sync_markers);
|
||||
void transform_handle_overlap(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips,
|
||||
bool use_sync_markers);
|
||||
/**
|
||||
* Set strip channel. This value is clamped to valid values.
|
||||
*/
|
||||
void strip_channel_set(Strip *strip, int channel);
|
||||
/**
|
||||
* Move strips and markers (if not locked) that start after timeline_frame by delta frames
|
||||
*
|
||||
* \param scene: Scene in which strips are located
|
||||
* \param seqbase: List in which strips are located
|
||||
* \param delta: offset in frames to be applied
|
||||
* \param timeline_frame: frame on timeline from where strips are moved
|
||||
*/
|
||||
void transform_offset_after_frame(Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
int delta,
|
||||
int timeline_frame);
|
||||
|
||||
/**
|
||||
* Check if `strip` can be moved.
|
||||
* This function also checks `SeqTimelineChannel` flag.
|
||||
*/
|
||||
bool transform_is_locked(const ListBaseT<SeqTimelineChannel> *channels, const Strip *strip);
|
||||
|
||||
/* Image transformation. */
|
||||
|
||||
/**
|
||||
* Get per-axis mirror factors for a \a strip image.
|
||||
* \return float2 where each component is 1.0f (normal) or -1.0f (mirrored). */
|
||||
float2 image_transform_mirror_factor_get(const Strip *strip);
|
||||
|
||||
/**
|
||||
* Get the image-relative \a strip origin. This value lies in the range (0,0) to (1,1), where
|
||||
* (0,0) corresponds to the bottom left of the image, and (1,1) the top left.
|
||||
*
|
||||
* NOTE: Normally, you can get this value from `StripTransform::origin`; this function is only
|
||||
* needed for text strips, whose origin must be converted to be relative to the render size.
|
||||
*/
|
||||
float2 image_transform_origin_get(const Scene *scene, const Strip *strip);
|
||||
|
||||
/**
|
||||
* Get the \a strip origin's offset in view-space pixels from the preview's center.
|
||||
* NOTE: This function does not apply axis mirror.
|
||||
*/
|
||||
float2 image_transform_origin_preview_offset_get(const Scene *scene, const Strip *strip);
|
||||
|
||||
/**
|
||||
* Get the original size of the \a strip image without any scaling or transformation.
|
||||
* \return float2 with (width, height) in view-space pixels
|
||||
*/
|
||||
float2 image_transform_raw_size_get(const Scene *scene, const Strip *strip);
|
||||
|
||||
/**
|
||||
* Get 4 corner points of strip image. Corner vectors are in viewport space.
|
||||
* Indices correspond to following corners (assuming no rotation):
|
||||
* 3--0
|
||||
* | |
|
||||
* 2--1
|
||||
*
|
||||
* \param strip: Strip to calculate transformed image quad
|
||||
* \return array of four 2D points
|
||||
*/
|
||||
Array<float2> image_transform_quad_get(const Scene *scene, const Strip *strip);
|
||||
|
||||
float2 image_preview_unit_to_px(const Scene *scene, float2 co_src);
|
||||
float2 image_preview_unit_from_px(const Scene *scene, float2 co_src);
|
||||
|
||||
/**
|
||||
* Get viewport axis aligned bounding box from multiple strips.
|
||||
* \param strips: Span of strips to calculate the bounding box for
|
||||
*/
|
||||
Bounds<float2> image_transform_bounding_box_from_strips_get(Scene *scene, Span<Strip *> strips);
|
||||
|
||||
/**
|
||||
* Get \a strip image transformation matrix relative to its origin in view-space.
|
||||
*/
|
||||
float3x3 image_transform_matrix_get(const Scene *scene, const Strip *strip);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
101
blender-5.2.0/source/blender/sequencer/SEQ_utils.hh
Normal file
101
blender-5.2.0/source/blender/sequencer/SEQ_utils.hh
Normal file
@@ -0,0 +1,101 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bSound;
|
||||
struct Mask;
|
||||
struct Scene;
|
||||
struct SeqTimelineChannel;
|
||||
struct Strip;
|
||||
struct StripElem;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void strip_unique_name_set(Scene *scene, ListBaseT<Strip> *seqbasep, Strip *strip);
|
||||
const char *get_default_stripname_by_type(int type);
|
||||
const char *strip_give_name(const Strip *strip);
|
||||
ListBaseT<Strip> *get_seqbase_from_strip(Strip *strip,
|
||||
ListBaseT<SeqTimelineChannel> **r_channels,
|
||||
int *r_offset);
|
||||
const Strip *strip_topmost_get(const Scene *scene, int frame);
|
||||
/**
|
||||
* In cases where we don't know the strip's listbase.
|
||||
*/
|
||||
ListBaseT<Strip> *get_seqbase_by_strip(const Scene *scene, Strip *strip);
|
||||
/**
|
||||
* Only use as last resort when the StripElem is available but not the Strip.
|
||||
* (needed for RNA)
|
||||
*/
|
||||
Strip *strip_from_strip_elem(ListBaseT<Strip> *seqbase, StripElem *se);
|
||||
Strip *get_strip_by_name(ListBaseT<Strip> *seqbase, const char *name, bool recursive);
|
||||
Mask *active_mask_get(Scene *scene);
|
||||
void alpha_mode_from_file_extension(Strip *strip);
|
||||
|
||||
/**
|
||||
* Check if an input referenced by this strip is valid (e.g. scene for a scene strip).
|
||||
* Note that this only checks data block references, for missing media referenced
|
||||
* by paths use #media_presence_is_missing.
|
||||
*/
|
||||
bool strip_has_valid_data(const Strip *strip);
|
||||
|
||||
void set_scale_to_fit(const Strip *strip,
|
||||
int image_width,
|
||||
int image_height,
|
||||
int preview_width,
|
||||
int preview_height,
|
||||
eSeqImageFitMethod fit_method);
|
||||
/**
|
||||
* Ensure, that provided Strip has unique name. If animation data exists for this Strip, it
|
||||
* will be duplicated and mapped onto new name
|
||||
*
|
||||
* \param strip: Strip which name will be ensured to be unique
|
||||
* \param scene: Scene in which name must be unique
|
||||
*/
|
||||
void ensure_unique_name(Strip *strip, Scene *scene);
|
||||
|
||||
void fontmap_clear();
|
||||
|
||||
/**
|
||||
* Check whether a sequence strip has missing media.
|
||||
* Results of the query for this strip will be cached into #MediaPresence cache. The cache
|
||||
* will be created on demand.
|
||||
*
|
||||
* \param scene: Scene to query.
|
||||
* \param strip: Sequencer strip.
|
||||
* \return True if media file is missing.
|
||||
*/
|
||||
bool media_presence_is_missing(Scene *scene, const Strip *strip);
|
||||
|
||||
/**
|
||||
* Set or change the missing media cache value for a given strip.
|
||||
*/
|
||||
void media_presence_set_missing(Scene *scene, const Strip *strip, bool missing);
|
||||
|
||||
/**
|
||||
* Invalidate media presence cache for the given strip.
|
||||
*/
|
||||
void media_presence_invalidate_strip(Scene *scene, const Strip *strip);
|
||||
|
||||
/**
|
||||
* Invalidate media presence cache for the given sound.
|
||||
*/
|
||||
void media_presence_invalidate_sound(Scene *scene, const bSound *sound);
|
||||
|
||||
/**
|
||||
* Free media presence cache, if it was created.
|
||||
*/
|
||||
void media_presence_free(Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
203
blender-5.2.0/source/blender/sequencer/intern/animation.cc
Normal file
203
blender-5.2.0/source/blender/sequencer/intern/animation.cc
Normal file
@@ -0,0 +1,203 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_fcurve.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "SEQ_animation.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
bool animation_keyframes_exist(const Scene *scene)
|
||||
{
|
||||
return scene->adt != nullptr && scene->adt->action != nullptr &&
|
||||
scene->adt->action->wrap().has_keyframes(scene->adt->slot_handle);
|
||||
}
|
||||
|
||||
bool animation_drivers_exist(Scene *scene)
|
||||
{
|
||||
return scene->adt != nullptr && !scene->adt->drivers.is_empty();
|
||||
}
|
||||
|
||||
bool fcurve_matches(const Strip &strip, const FCurve &fcurve)
|
||||
{
|
||||
return animrig::fcurve_matches_collection_path(
|
||||
fcurve, "sequence_editor.strips_all[", strip.name + 2);
|
||||
}
|
||||
|
||||
void offset_animdata(const Scene *scene, Strip *strip, float ofs)
|
||||
{
|
||||
if (!animation_keyframes_exist(scene) || ofs == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<FCurve *> fcurves = animrig::fcurves_in_action_slot_filtered(
|
||||
scene->adt->action, scene->adt->slot_handle, [&](const FCurve &fcurve) {
|
||||
return fcurve_matches(*strip, fcurve);
|
||||
});
|
||||
|
||||
for (FCurve *fcu : fcurves) {
|
||||
uint i;
|
||||
if (fcu->bezt) {
|
||||
for (i = 0; i < fcu->totvert; i++) {
|
||||
BezTriple *bezt = &fcu->bezt[i];
|
||||
bezt->vec[0][0] += ofs;
|
||||
bezt->vec[1][0] += ofs;
|
||||
bezt->vec[2][0] += ofs;
|
||||
}
|
||||
}
|
||||
if (fcu->fpt) {
|
||||
for (i = 0; i < fcu->totvert; i++) {
|
||||
FPoint *fpt = &fcu->fpt[i];
|
||||
fpt->vec[0] += ofs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&scene->adt->action->id, ID_RECALC_ANIMATION);
|
||||
}
|
||||
|
||||
void free_animdata(Scene *scene, Strip *strip)
|
||||
{
|
||||
if (!animation_keyframes_exist(scene)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<FCurve *> fcurves = animrig::fcurves_in_action_slot_filtered(
|
||||
scene->adt->action, scene->adt->slot_handle, [&](const FCurve &fcurve) {
|
||||
return fcurve_matches(*strip, fcurve);
|
||||
});
|
||||
|
||||
animrig::Action &action = scene->adt->action->wrap();
|
||||
for (FCurve *fcu : fcurves) {
|
||||
action_fcurve_remove(action, *fcu);
|
||||
}
|
||||
}
|
||||
|
||||
void animation_backup_original(Scene *scene, AnimationBackup *backup)
|
||||
{
|
||||
if (animation_keyframes_exist(scene)) {
|
||||
animrig::Action &action = scene->adt->action->wrap();
|
||||
|
||||
assert_baklava_phase_1_invariants(action);
|
||||
|
||||
if (animrig::Channelbag *channelbag = animrig::channelbag_for_action_slot(
|
||||
action, scene->adt->slot_handle))
|
||||
{
|
||||
animrig::channelbag_fcurves_move(backup->channelbag, *channelbag);
|
||||
}
|
||||
}
|
||||
|
||||
if (animation_drivers_exist(scene)) {
|
||||
BLI_movelisttolist(&backup->drivers, &scene->adt->drivers);
|
||||
}
|
||||
}
|
||||
|
||||
void animation_restore_original(Scene *scene, AnimationBackup *backup)
|
||||
{
|
||||
if (!backup->channelbag.fcurves().is_empty()) {
|
||||
BLI_assert(scene->adt != nullptr && scene->adt->action != nullptr);
|
||||
|
||||
animrig::Action &action = scene->adt->action->wrap();
|
||||
|
||||
assert_baklava_phase_1_invariants(action);
|
||||
|
||||
animrig::Channelbag *channelbag = animrig::channelbag_for_action_slot(action,
|
||||
scene->adt->slot_handle);
|
||||
/* The channel bag should exist if we got here, because otherwise the
|
||||
* backup channel bag would have been empty. */
|
||||
BLI_assert(channelbag != nullptr);
|
||||
|
||||
animrig::channelbag_fcurves_move(*channelbag, backup->channelbag);
|
||||
}
|
||||
|
||||
if (!backup->drivers.is_empty()) {
|
||||
BLI_assert(scene->adt != nullptr);
|
||||
BLI_movelisttolist(&scene->adt->drivers, &backup->drivers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the animation in `src` that matches items in `strip` into `dst`.
|
||||
*/
|
||||
static void strip_animation_duplicate(Strip *strip,
|
||||
animrig::Action &dst,
|
||||
const animrig::slot_handle_t dst_slot_handle,
|
||||
AnimationBackup *src)
|
||||
{
|
||||
if (strip->type == STRIP_TYPE_META) {
|
||||
for (Strip &meta_child : strip->seqbase) {
|
||||
strip_animation_duplicate(&meta_child, dst, dst_slot_handle, src);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<FCurve *> fcurves = animrig::fcurves_in_span_filtered(
|
||||
src->channelbag.fcurves(),
|
||||
[&](const FCurve &fcurve) { return fcurve_matches(*strip, fcurve); });
|
||||
|
||||
for (const FCurve *fcu : fcurves) {
|
||||
FCurve *fcu_copy = BKE_fcurve_copy(fcu);
|
||||
|
||||
/* Handling groups properly requires more work, so we ignore them for now.
|
||||
*
|
||||
* Note that when legacy actions are deprecated, then we can handle channel
|
||||
* groups way more easily because we know they're stored in the
|
||||
* already-duplicated channelbag in `src`, and we therefore don't have to
|
||||
* worry that they might have already been freed. */
|
||||
fcu_copy->grp = nullptr;
|
||||
|
||||
animrig::action_fcurve_attach(dst, dst_slot_handle, *fcu_copy, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the drivers in `src` that matches items in `strip` into `dst`.
|
||||
*/
|
||||
static void strip_drivers_duplicate(Strip *strip, AnimData *dst, AnimationBackup *src)
|
||||
{
|
||||
if (strip->type == STRIP_TYPE_META) {
|
||||
for (Strip &meta_child : strip->seqbase) {
|
||||
strip_drivers_duplicate(&meta_child, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<FCurve *> fcurves = animrig::fcurves_in_listbase_filtered(
|
||||
src->drivers, [&](const FCurve &fcurve) { return fcurve_matches(*strip, fcurve); });
|
||||
|
||||
for (const FCurve *fcu : fcurves) {
|
||||
FCurve *fcu_cpy = BKE_fcurve_copy(fcu);
|
||||
BLI_addtail(&dst->drivers, fcu_cpy);
|
||||
}
|
||||
}
|
||||
|
||||
void animation_duplicate_backup_to_scene(Scene *scene, Strip *strip, AnimationBackup *backup)
|
||||
{
|
||||
BLI_assert(scene != nullptr);
|
||||
|
||||
if (!backup->channelbag.fcurves().is_empty()) {
|
||||
BLI_assert(scene->adt != nullptr);
|
||||
BLI_assert(scene->adt->action != nullptr);
|
||||
strip_animation_duplicate(strip, scene->adt->action->wrap(), scene->adt->slot_handle, backup);
|
||||
}
|
||||
|
||||
if (!backup->drivers.is_empty()) {
|
||||
BLI_assert(scene->adt != nullptr);
|
||||
strip_drivers_duplicate(strip, scene->adt, backup);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
56
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.cc
vendored
Normal file
56
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.cc
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_threads.h"
|
||||
#include "COM_result.hh"
|
||||
#include "DRW_engine.hh"
|
||||
|
||||
#include "compositor_cache.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
CompositorCache::~CompositorCache()
|
||||
{
|
||||
bool use_main_context = false;
|
||||
if (this->last_evaluation_used_gpu) {
|
||||
/* Free resources with GPU context enabled. Cleanup may happen from the main thread, and we
|
||||
* must use the main context there. */
|
||||
BLI_assert(BLI_thread_is_main() || this->secondary_gpu_context.ghost_context != nullptr);
|
||||
use_main_context = BLI_thread_is_main();
|
||||
if (use_main_context) {
|
||||
DRW_gpu_context_enable();
|
||||
}
|
||||
else {
|
||||
gpu::GPU_activate_secondary_context(this->secondary_gpu_context);
|
||||
}
|
||||
}
|
||||
|
||||
this->cache_manager.free();
|
||||
|
||||
/* See comment above on context enabling. */
|
||||
if (this->last_evaluation_used_gpu) {
|
||||
if (use_main_context) {
|
||||
DRW_gpu_context_disable();
|
||||
}
|
||||
else {
|
||||
gpu::GPU_deactivate_secondary_context(this->secondary_gpu_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorCache::recreate_if_needed(bool gpu,
|
||||
compositor::ResultPrecision precision,
|
||||
const gpu::GPUSecondaryContextData &gpu_context)
|
||||
{
|
||||
this->secondary_gpu_context = gpu ? gpu_context : gpu::GPUSecondaryContextData();
|
||||
|
||||
if (this->last_evaluation_used_gpu == gpu && this->last_evaluation_precision == precision) {
|
||||
return;
|
||||
}
|
||||
this->cache_manager.free();
|
||||
this->last_evaluation_used_gpu = gpu;
|
||||
this->last_evaluation_precision = precision;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
34
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.hh
vendored
Normal file
34
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.hh
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "COM_static_cache_manager.hh"
|
||||
#include "GPU_context.hh"
|
||||
|
||||
class GHOST_IContext;
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
class CompositorCache {
|
||||
private:
|
||||
compositor::StaticCacheManager cache_manager;
|
||||
bool last_evaluation_used_gpu = false;
|
||||
compositor::ResultPrecision last_evaluation_precision = compositor::ResultPrecision::Half;
|
||||
gpu::GPUSecondaryContextData secondary_gpu_context = {};
|
||||
|
||||
public:
|
||||
~CompositorCache();
|
||||
|
||||
compositor::StaticCacheManager &get_cache_manager()
|
||||
{
|
||||
return cache_manager;
|
||||
}
|
||||
|
||||
void recreate_if_needed(bool gpu,
|
||||
compositor::ResultPrecision precision,
|
||||
const gpu::GPUSecondaryContextData &gpu_context);
|
||||
};
|
||||
|
||||
} // namespace blender::seq
|
||||
293
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.cc
vendored
Normal file
293
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.cc
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_mutex.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
#include "final_image_cache.hh"
|
||||
#include "prefetch.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static Mutex final_image_cache_mutex;
|
||||
|
||||
struct FinalImageCache {
|
||||
struct Key {
|
||||
int timeline_frame;
|
||||
int view_id;
|
||||
int display_channel;
|
||||
int2 image_size;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(timeline_frame, view_id, display_channel, image_size);
|
||||
}
|
||||
|
||||
bool operator==(const Key &other) const
|
||||
{
|
||||
return timeline_frame == other.timeline_frame && view_id == other.view_id &&
|
||||
display_channel == other.display_channel && image_size == image_size;
|
||||
}
|
||||
};
|
||||
Map<Key, ImBuf *> map_;
|
||||
|
||||
~FinalImageCache()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (ImBuf *item : map_.values()) {
|
||||
IMB_freeImBuf(item);
|
||||
}
|
||||
map_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
static FinalImageCache *ensure_final_image_cache(Scene *scene)
|
||||
{
|
||||
FinalImageCache **cache = &scene->ed->runtime->final_image_cache;
|
||||
if (*cache == nullptr) {
|
||||
*cache = MEM_new<FinalImageCache>(__func__);
|
||||
}
|
||||
return *cache;
|
||||
}
|
||||
|
||||
static FinalImageCache *query_final_image_cache(const Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->final_image_cache;
|
||||
}
|
||||
|
||||
ImBuf *final_image_cache_get(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render)
|
||||
{
|
||||
if (is_render) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FinalImageCache::Key key = {
|
||||
int(math::round(timeline_frame)), view_id, display_channel, image_size};
|
||||
|
||||
ImBuf *res = nullptr;
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
res = cache->map_.lookup_default(key, nullptr);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
IMB_refImBuf(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void final_image_cache_put(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render,
|
||||
ImBuf *image)
|
||||
{
|
||||
if (is_render) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FinalImageCache::Key key = {
|
||||
int(math::round(timeline_frame)), view_id, display_channel, image_size};
|
||||
|
||||
IMB_refImBuf(image);
|
||||
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = ensure_final_image_cache(scene);
|
||||
|
||||
cache->map_.add_or_modify(
|
||||
key,
|
||||
[&](ImBuf **value) { *value = image; },
|
||||
[&](ImBuf **existing) {
|
||||
if (*existing) {
|
||||
IMB_freeImBuf(*existing);
|
||||
}
|
||||
*existing = image;
|
||||
});
|
||||
}
|
||||
|
||||
void final_image_cache_invalidate_frame_range(Scene *scene,
|
||||
const float timeline_frame_start,
|
||||
const float timeline_frame_end)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int key_start = int(math::floor(timeline_frame_start));
|
||||
const int key_end = int(math::ceil(timeline_frame_end));
|
||||
|
||||
for (auto it = cache->map_.items().begin(); it != cache->map_.items().end(); it++) {
|
||||
const int key = (*it).key.timeline_frame;
|
||||
if (key >= key_start && key <= key_end) {
|
||||
IMB_freeImBuf((*it).value);
|
||||
cache->map_.remove(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void final_image_cache_clear(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
scene->ed->runtime->final_image_cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void final_image_cache_destroy(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
BLI_assert(cache == scene->ed->runtime->final_image_cache);
|
||||
MEM_delete(scene->ed->runtime->final_image_cache);
|
||||
scene->ed->runtime->final_image_cache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void final_image_cache_iterate(Scene *scene,
|
||||
void *userdata,
|
||||
void callback_iter(void *userdata, int timeline_frame))
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const FinalImageCache::Key &frame_view : cache->map_.keys()) {
|
||||
callback_iter(userdata, frame_view.timeline_frame);
|
||||
}
|
||||
}
|
||||
|
||||
size_t final_image_cache_calc_memory_size(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = 0;
|
||||
for (ImBuf *frame : cache->map_.values()) {
|
||||
size += IMB_get_size_in_memory(frame);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t final_image_cache_get_image_count(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return cache->map_.size();
|
||||
}
|
||||
|
||||
bool final_image_cache_evict(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Find which entry to remove -- we pick the one that is furthest from the current frame,
|
||||
* biasing the ones that are behind the current frame.
|
||||
*
|
||||
* However, do not try to evict entries from the current prefetch job range -- we need to
|
||||
* be able to fully fill the cache from prefetching, and then actually stop the job when it
|
||||
* is full and no longer can evict anything. */
|
||||
int cur_prefetch_start = std::numeric_limits<int>::min();
|
||||
int cur_prefetch_end = std::numeric_limits<int>::min();
|
||||
if (scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT) {
|
||||
/* Only activate the prefetch guards if the cache is active. */
|
||||
seq_prefetch_get_time_range(scene, &cur_prefetch_start, &cur_prefetch_end);
|
||||
}
|
||||
const bool prefetch_loops_around = cur_prefetch_start > cur_prefetch_end;
|
||||
|
||||
const int timeline_start = scene->playback_start();
|
||||
const int timeline_end = scene->playback_end();
|
||||
/* If we wrap around, treat the timeline start as the playback head position.
|
||||
* This is to try to mitigate un-needed cache evictions. */
|
||||
const int cur_frame = prefetch_loops_around ? timeline_start : scene->r.cfra;
|
||||
|
||||
FinalImageCache::Key best_key = {};
|
||||
ImBuf *best_item = nullptr;
|
||||
int best_score = 0;
|
||||
for (const auto &item : cache->map_.items()) {
|
||||
const int item_frame = item.key.timeline_frame;
|
||||
if (prefetch_loops_around) {
|
||||
if (item_frame >= timeline_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
if (item_frame >= cur_prefetch_start && item_frame <= timeline_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
}
|
||||
else if (item_frame >= cur_prefetch_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
|
||||
/* Score for removal is distance to current frame; 2x that if behind current frame. */
|
||||
int score = 0;
|
||||
if (item_frame < cur_frame) {
|
||||
score = (cur_frame - item_frame) * 2;
|
||||
}
|
||||
else if (item_frame > cur_frame) {
|
||||
score = item_frame - cur_frame;
|
||||
}
|
||||
if (score > best_score) {
|
||||
best_key = item.key;
|
||||
best_item = item.value;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove if we found one. */
|
||||
if (best_item != nullptr) {
|
||||
IMB_freeImBuf(best_item);
|
||||
cache->map_.remove(best_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Did not find anything to remove. */
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
56
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.hh
vendored
Normal file
56
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.hh
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Cache of final rendered frames.
|
||||
* - Keyed by (timeline frame, view_id).
|
||||
* - When full, cache eviction policy is to remove frames furthest
|
||||
* from the current-frame, biasing towards removal of
|
||||
* frames behind the current-frame.
|
||||
* - Invalidated fairly often while editing, basically whenever any
|
||||
* strip overlapping that frame changes.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ImBuf;
|
||||
struct Strip;
|
||||
struct Scene;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void final_image_cache_put(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render,
|
||||
ImBuf *image);
|
||||
|
||||
ImBuf *final_image_cache_get(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render);
|
||||
|
||||
void final_image_cache_invalidate_frame_range(Scene *scene,
|
||||
const float timeline_frame_start,
|
||||
const float timeline_frame_end);
|
||||
|
||||
void final_image_cache_clear(Scene *scene);
|
||||
void final_image_cache_destroy(Scene *scene);
|
||||
|
||||
bool final_image_cache_evict(Scene *scene);
|
||||
|
||||
size_t final_image_cache_get_image_count(const Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
192
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.cc
vendored
Normal file
192
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.cc
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
#include "intra_frame_cache.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct StripImageMap {
|
||||
Map<const Strip *, SeqResult> map_;
|
||||
SeqResult get(const Strip *strip) const;
|
||||
void put(const Strip *strip, const SeqResult &result);
|
||||
void invalidate(const Strip *strip);
|
||||
void clear();
|
||||
};
|
||||
|
||||
struct IntraFrameCache {
|
||||
StripImageMap preprocessed;
|
||||
StripImageMap composite;
|
||||
float timeline_frame = -1.0f;
|
||||
int view_id = -1;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
bool is_render = false;
|
||||
|
||||
~IntraFrameCache()
|
||||
{
|
||||
preprocessed.clear();
|
||||
composite.clear();
|
||||
}
|
||||
};
|
||||
|
||||
static IntraFrameCache *query_intra_frame_cache(Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->intra_frame_cache;
|
||||
}
|
||||
|
||||
void intra_frame_cache_invalidate(Scene *scene)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->preprocessed.clear();
|
||||
cache->composite.clear();
|
||||
cache->timeline_frame = -1.0f;
|
||||
cache->view_id = -1;
|
||||
cache->width = -1;
|
||||
cache->height = -1;
|
||||
cache->is_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
void intra_frame_cache_invalidate(Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (strip == nullptr) {
|
||||
return;
|
||||
}
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->preprocessed.invalidate(strip);
|
||||
cache->composite.invalidate(strip);
|
||||
}
|
||||
}
|
||||
|
||||
void StripImageMap::invalidate(const Strip *strip)
|
||||
{
|
||||
/* Invalidate this strip, and all strips that are above it. */
|
||||
for (auto it = this->map_.items().begin(); it != this->map_.items().end(); it++) {
|
||||
const Strip *key = (*it).key;
|
||||
if (key == strip || key->channel >= strip->channel) {
|
||||
IMB_freeImBuf((*it).value.image);
|
||||
this->map_.remove(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SeqResult StripImageMap::get(const Strip *strip) const
|
||||
{
|
||||
SeqResult result = this->map_.lookup_default(strip, {});
|
||||
if (result.is_valid()) {
|
||||
IMB_refImBuf(result.image);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void StripImageMap::put(const Strip *strip, const SeqResult &result)
|
||||
{
|
||||
BLI_assert(strip != nullptr);
|
||||
if (!result.is_valid()) {
|
||||
return;
|
||||
}
|
||||
SeqResult existing = this->map_.lookup_default(strip, {});
|
||||
if (existing.is_valid()) {
|
||||
IMB_freeImBuf(existing.image);
|
||||
}
|
||||
this->map_.add_overwrite(strip, result);
|
||||
IMB_refImBuf(result.image);
|
||||
}
|
||||
|
||||
void StripImageMap::clear()
|
||||
{
|
||||
for (const auto &item : this->map_.items()) {
|
||||
IMB_freeImBuf(item.value.image);
|
||||
}
|
||||
this->map_.clear();
|
||||
}
|
||||
|
||||
SeqResult intra_frame_cache_get_preprocessed(Scene *scene, const Strip *strip)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (strip == nullptr || cache == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return cache->preprocessed.get(strip);
|
||||
}
|
||||
|
||||
SeqResult intra_frame_cache_get_composite(Scene *scene, const Strip *strip)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (strip == nullptr || cache == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return cache->composite.get(strip);
|
||||
}
|
||||
|
||||
void intra_frame_cache_put_preprocessed(Scene *scene, const Strip *strip, const SeqResult &result)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr || strip == nullptr || !result.is_valid()) {
|
||||
return;
|
||||
}
|
||||
IntraFrameCache *&cache = scene->ed->runtime->intra_frame_cache;
|
||||
if (cache == nullptr) {
|
||||
cache = MEM_new<IntraFrameCache>(__func__);
|
||||
}
|
||||
cache->preprocessed.put(strip, result);
|
||||
}
|
||||
|
||||
void intra_frame_cache_put_composite(Scene *scene, const Strip *strip, const SeqResult &result)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr || strip == nullptr || !result.is_valid()) {
|
||||
return;
|
||||
}
|
||||
IntraFrameCache *&cache = scene->ed->runtime->intra_frame_cache;
|
||||
if (cache == nullptr) {
|
||||
cache = MEM_new<IntraFrameCache>(__func__);
|
||||
}
|
||||
cache->composite.put(strip, result);
|
||||
}
|
||||
|
||||
void intra_frame_cache_destroy(Scene *scene)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
MEM_SAFE_DELETE(scene->ed->runtime->intra_frame_cache);
|
||||
}
|
||||
}
|
||||
|
||||
void intra_frame_cache_set_cur_frame(
|
||||
Scene *scene, float frame, int view_id, int width, int height, bool is_render)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
if (cache->timeline_frame != frame || cache->view_id != view_id || cache->width != width ||
|
||||
cache->height != height || cache->is_render != is_render)
|
||||
{
|
||||
cache->timeline_frame = frame;
|
||||
cache->view_id = view_id;
|
||||
cache->width = width;
|
||||
cache->height = height;
|
||||
cache->is_render = is_render;
|
||||
cache->preprocessed.clear();
|
||||
cache->composite.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
45
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.hh
vendored
Normal file
45
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.hh
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Cached intermediate images used while rendering one sequencer frame.
|
||||
* - For each strip, "preprocessed" (strip source, possibly
|
||||
* transformed, with modifiers applied) and "composite" (result of
|
||||
* blending this strip with image underneath) images are cached.
|
||||
* - Whenever going to a different frame, the cached content of previous
|
||||
* frame is cleared.
|
||||
* - Primary reason for having this cache at all, is when the whole frame
|
||||
* is a complex stack of things, and you want to tweak settings of one
|
||||
* of the involved strips. You don't want to be re-calculating all the
|
||||
* strips that are "below" your tweaked strip, for better interactivity.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Strip;
|
||||
struct Scene;
|
||||
|
||||
namespace seq {
|
||||
|
||||
SeqResult intra_frame_cache_get_preprocessed(Scene *scene, const Strip *strip);
|
||||
SeqResult intra_frame_cache_get_composite(Scene *scene, const Strip *strip);
|
||||
void intra_frame_cache_put_preprocessed(Scene *scene, const Strip *strip, const SeqResult &result);
|
||||
void intra_frame_cache_put_composite(Scene *scene, const Strip *strip, const SeqResult &result);
|
||||
|
||||
void intra_frame_cache_destroy(Scene *scene);
|
||||
|
||||
void intra_frame_cache_invalidate(Scene *scene, const Strip *strip);
|
||||
void intra_frame_cache_invalidate(Scene *scene);
|
||||
|
||||
void intra_frame_cache_set_cur_frame(
|
||||
Scene *scene, float frame, int view_id, int width, int height, bool is_render);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
215
blender-5.2.0/source/blender/sequencer/intern/cache/preview_cache.cc
vendored
Normal file
215
blender-5.2.0/source/blender/sequencer/intern/cache/preview_cache.cc
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "GPU_texture.hh"
|
||||
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct PreviewCacheItem {
|
||||
int64_t last_used = -1;
|
||||
int timeline_frame = -1;
|
||||
int display_channel = -1;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
|
||||
gpu::Texture *texture = nullptr;
|
||||
gpu::Texture *scope_texture = nullptr;
|
||||
|
||||
void clear()
|
||||
{
|
||||
last_used = -1;
|
||||
timeline_frame = -1;
|
||||
width = -1;
|
||||
height = -1;
|
||||
GPU_TEXTURE_FREE_SAFE(texture);
|
||||
GPU_TEXTURE_FREE_SAFE(scope_texture);
|
||||
}
|
||||
};
|
||||
|
||||
struct PreviewCache {
|
||||
static constexpr int cache_size = 4;
|
||||
PreviewCacheItem items[cache_size];
|
||||
int64_t tick_count = 0;
|
||||
|
||||
~PreviewCache()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (PreviewCacheItem &item : this->items) {
|
||||
item.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static PreviewCache *query_preview_cache(Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->preview_cache;
|
||||
}
|
||||
|
||||
static PreviewCache *ensure_preview_cache(Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
PreviewCache *&cache = scene->ed->runtime->preview_cache;
|
||||
if (cache == nullptr) {
|
||||
cache = MEM_new<PreviewCache>(__func__);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
gpu::Texture *preview_cache_get_gpu_texture(
|
||||
Scene *scene, int timeline_frame, int display_channel, int width, int height)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
cache->tick_count++;
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.timeline_frame == timeline_frame && item.display_channel == display_channel &&
|
||||
item.width == width && item.height == height && item.texture != nullptr)
|
||||
{
|
||||
item.last_used = cache->tick_count;
|
||||
return item.texture;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gpu::Texture *preview_cache_get_gpu_scope_texture(
|
||||
Scene *scene, int timeline_frame, int display_channel, int width, int height)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
cache->tick_count++;
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.timeline_frame == timeline_frame && item.display_channel == display_channel &&
|
||||
item.width == width && item.height == height && item.scope_texture != nullptr)
|
||||
{
|
||||
item.last_used = cache->tick_count;
|
||||
return item.scope_texture;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static PreviewCacheItem *find_slot(
|
||||
PreviewCache *cache, int timeline_frame, int display_channel, int width, int height)
|
||||
{
|
||||
cache->tick_count++;
|
||||
|
||||
/* Try to find an exact frame match. */
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.timeline_frame == timeline_frame && item.display_channel == display_channel &&
|
||||
item.width == width && item.height == height)
|
||||
{
|
||||
return &item;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find unused or least recently used slot. */
|
||||
PreviewCacheItem *best_slot = nullptr;
|
||||
int64_t best_score = -1;
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.texture == nullptr && item.scope_texture == nullptr) {
|
||||
return &item;
|
||||
}
|
||||
int64_t score = cache->tick_count - item.last_used;
|
||||
if (score >= best_score) {
|
||||
best_score = score;
|
||||
best_slot = &item;
|
||||
}
|
||||
}
|
||||
|
||||
return best_slot;
|
||||
}
|
||||
|
||||
void preview_cache_set_gpu_texture(Scene *scene,
|
||||
int timeline_frame,
|
||||
int display_channel,
|
||||
gpu::Texture *texture)
|
||||
{
|
||||
PreviewCache *cache = ensure_preview_cache(scene);
|
||||
if (cache == nullptr || texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
const int width = GPU_texture_width(texture);
|
||||
const int height = GPU_texture_height(texture);
|
||||
PreviewCacheItem *slot = find_slot(cache, timeline_frame, display_channel, width, height);
|
||||
if (slot == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot->timeline_frame = timeline_frame;
|
||||
slot->display_channel = display_channel;
|
||||
slot->width = width;
|
||||
slot->height = height;
|
||||
slot->last_used = cache->tick_count;
|
||||
GPU_TEXTURE_FREE_SAFE(slot->texture);
|
||||
/* Free the display-space texture of this slot too. */
|
||||
GPU_TEXTURE_FREE_SAFE(slot->scope_texture);
|
||||
slot->texture = texture;
|
||||
}
|
||||
|
||||
void preview_cache_set_gpu_scope_texture(Scene *scene,
|
||||
int timeline_frame,
|
||||
int display_channel,
|
||||
gpu::Texture *texture)
|
||||
{
|
||||
PreviewCache *cache = ensure_preview_cache(scene);
|
||||
if (cache == nullptr || texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
const int width = GPU_texture_width(texture);
|
||||
const int height = GPU_texture_height(texture);
|
||||
PreviewCacheItem *slot = find_slot(cache, timeline_frame, display_channel, width, height);
|
||||
if (slot == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot->timeline_frame = timeline_frame;
|
||||
slot->display_channel = display_channel;
|
||||
slot->width = width;
|
||||
slot->height = height;
|
||||
slot->last_used = cache->tick_count;
|
||||
GPU_TEXTURE_FREE_SAFE(slot->scope_texture);
|
||||
slot->scope_texture = texture;
|
||||
}
|
||||
|
||||
void preview_cache_invalidate(Scene *scene)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void preview_cache_destroy(Scene *scene)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
MEM_SAFE_DELETE(scene->ed->runtime->preview_cache);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
370
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.cc
vendored
Normal file
370
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.cc
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_mutex.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
|
||||
#include "prefetch.hh"
|
||||
#include "source_image_cache.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static Mutex source_image_cache_mutex;
|
||||
|
||||
struct SourceImageCache {
|
||||
struct FrameEntry {
|
||||
SeqResult image;
|
||||
/**
|
||||
* Frame in timeline, relative to strip start. Used to determine which
|
||||
* entries to evict (furthest from the play-head). Due to reversed
|
||||
* frames, playback rate, retiming the relationship between source frame
|
||||
* index and timeline frame is not a simple one.
|
||||
*/
|
||||
float strip_frame = 0;
|
||||
};
|
||||
|
||||
struct Key {
|
||||
float source_frame = 0.0f;
|
||||
int view_id = 0;
|
||||
eDrawType scene_draw_type = OB_SOLID;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(source_frame, view_id, scene_draw_type);
|
||||
}
|
||||
|
||||
friend bool operator==(const Key &a, const Key &b) = default;
|
||||
};
|
||||
|
||||
struct StripEntry {
|
||||
Map<Key, FrameEntry> frames;
|
||||
};
|
||||
|
||||
Map<const Strip *, StripEntry> map_;
|
||||
|
||||
~SourceImageCache()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (const auto &item : map_.items()) {
|
||||
for (const auto &frame : item.value.frames.values()) {
|
||||
IMB_freeImBuf(frame.image.image);
|
||||
}
|
||||
}
|
||||
map_.clear();
|
||||
}
|
||||
|
||||
void remove_entry(const Strip *strip)
|
||||
{
|
||||
StripEntry *entry = map_.lookup_ptr(strip);
|
||||
if (entry == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto &frame : entry->frames.values()) {
|
||||
IMB_freeImBuf(frame.image.image);
|
||||
}
|
||||
map_.remove_contained(strip);
|
||||
}
|
||||
};
|
||||
|
||||
static SourceImageCache *ensure_source_image_cache(Scene *scene)
|
||||
{
|
||||
SourceImageCache **cache = &scene->ed->runtime->source_image_cache;
|
||||
if (*cache == nullptr) {
|
||||
*cache = MEM_new<SourceImageCache>(__func__);
|
||||
}
|
||||
return *cache;
|
||||
}
|
||||
|
||||
static SourceImageCache *query_source_image_cache(const Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->source_image_cache;
|
||||
}
|
||||
|
||||
static float give_cache_frame_index(const Scene *scene, const Strip *strip, float timeline_frame)
|
||||
{
|
||||
float frame_index = give_frame_index(scene, strip, timeline_frame);
|
||||
if (strip->type != STRIP_TYPE_SCENE) {
|
||||
/* Scene strips that are slowed down need fractional frame index for animation interpolation;
|
||||
* for others use integer index for better cache hit rates. */
|
||||
frame_index = std::trunc(frame_index);
|
||||
}
|
||||
if (strip->type == STRIP_TYPE_MOVIE) {
|
||||
frame_index += strip->anim_startofs;
|
||||
}
|
||||
return frame_index;
|
||||
}
|
||||
|
||||
static SourceImageCache::Key get_key(const RenderData *context,
|
||||
const Scene *scene,
|
||||
const Strip *strip,
|
||||
float timeline_frame)
|
||||
{
|
||||
const float frame_index = give_cache_frame_index(scene, strip, timeline_frame);
|
||||
eDrawType draw_type = OB_RENDER;
|
||||
if (!context->render && strip->type == STRIP_TYPE_SCENE) {
|
||||
draw_type = eDrawType(scene->r.seq_prev_type);
|
||||
}
|
||||
return {frame_index, context->view_id, draw_type};
|
||||
}
|
||||
|
||||
SeqResult source_image_cache_get(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame)
|
||||
{
|
||||
if (context->skip_cache || strip == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
|
||||
timeline_frame = math::round(timeline_frame);
|
||||
const SourceImageCache::Key key = get_key(context, scene, strip, timeline_frame);
|
||||
|
||||
SeqResult res;
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return res;
|
||||
}
|
||||
|
||||
SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
|
||||
if (val == nullptr) {
|
||||
/* Nothing in cache for this strip yet. */
|
||||
return res;
|
||||
}
|
||||
/* Search entries for the frame we want. */
|
||||
SourceImageCache::FrameEntry *frame = val->frames.lookup_ptr(key);
|
||||
if (frame != nullptr) {
|
||||
res = frame->image;
|
||||
}
|
||||
|
||||
/* For effect, meta, and scene strips, check if the cached result matches our current
|
||||
* render resolution. If it does not, remove stale source entries for this strip. */
|
||||
if (res.is_valid() &&
|
||||
(strip->is_effect() || strip->type == STRIP_TYPE_SCENE || strip->type == STRIP_TYPE_META))
|
||||
{
|
||||
if (res.image->x != context->rectx || res.image->y != context->recty) {
|
||||
cache->remove_entry(strip);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.is_valid()) {
|
||||
IMB_refImBuf(res.image);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void source_image_cache_put(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame,
|
||||
const SeqResult &image)
|
||||
{
|
||||
if (context->skip_cache || strip == nullptr || !image.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
|
||||
timeline_frame = math::round(timeline_frame);
|
||||
const SourceImageCache::Key key = get_key(context, scene, strip, timeline_frame);
|
||||
|
||||
IMB_refImBuf(image.image);
|
||||
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = ensure_source_image_cache(scene);
|
||||
|
||||
SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
|
||||
|
||||
if (val == nullptr) {
|
||||
/* Nothing in cache for this strip yet. */
|
||||
cache->map_.add_new(strip, {});
|
||||
val = cache->map_.lookup_ptr(strip);
|
||||
}
|
||||
BLI_assert_msg(val != nullptr, "Source image cache value should never be null here");
|
||||
|
||||
SourceImageCache::FrameEntry &frame = val->frames.lookup_or_add_default(key);
|
||||
if (frame.image.is_valid()) {
|
||||
IMB_freeImBuf(frame.image.image);
|
||||
}
|
||||
frame.strip_frame = timeline_frame - strip->start;
|
||||
frame.image = image;
|
||||
}
|
||||
|
||||
void source_image_cache_invalidate_strip(Scene *scene, const Strip *strip)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->remove_entry(strip);
|
||||
}
|
||||
}
|
||||
|
||||
void source_image_cache_clear(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
scene->ed->runtime->source_image_cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void source_image_cache_destroy(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
BLI_assert(cache == scene->ed->runtime->source_image_cache);
|
||||
MEM_delete(scene->ed->runtime->source_image_cache);
|
||||
scene->ed->runtime->source_image_cache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void source_image_cache_iterate(Scene *scene,
|
||||
void *userdata,
|
||||
void callback_iter(void *userdata,
|
||||
const Strip *strip,
|
||||
int timeline_frame))
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &[strip, frames] : cache->map_.items()) {
|
||||
for (const auto &[frame_key, frame] : frames.frames.items()) {
|
||||
const float timeline_frame = strip->start + frame.strip_frame;
|
||||
callback_iter(userdata, strip, int(timeline_frame));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t source_image_cache_calc_memory_size(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = 0;
|
||||
for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
|
||||
for (const SourceImageCache::FrameEntry &frame : entry.frames.values()) {
|
||||
if (frame.image.is_valid()) {
|
||||
size += IMB_get_size_in_memory(frame.image.image);
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t source_image_cache_get_image_count(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t count = 0;
|
||||
for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
|
||||
count += entry.frames.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool source_image_cache_evict(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return false;
|
||||
}
|
||||
/* Find which entry to remove -- we pick the one that is furthest from the current frame,
|
||||
* biasing the ones that are behind the current frame.
|
||||
*
|
||||
* However, do not try to evict entries from the current prefetch job range -- we need to
|
||||
* be able to fully fill the cache from prefetching, and then actually stop the job when it
|
||||
* is full and no longer can evict anything. */
|
||||
int cur_prefetch_start = std::numeric_limits<int>::min();
|
||||
int cur_prefetch_end = std::numeric_limits<int>::min();
|
||||
if (scene->ed->cache_flag & SEQ_CACHE_STORE_RAW) {
|
||||
/* Only activate the prefetch guards if the cache is active. */
|
||||
seq_prefetch_get_time_range(scene, &cur_prefetch_start, &cur_prefetch_end);
|
||||
}
|
||||
const bool prefetch_loops_around = cur_prefetch_start > cur_prefetch_end;
|
||||
|
||||
const int timeline_start = scene->playback_start();
|
||||
const int timeline_end = scene->playback_end();
|
||||
/* If we wrap around, treat the timeline start as the playback head position.
|
||||
* This is to try to mitigate un-needed cache evictions. */
|
||||
const int cur_frame = prefetch_loops_around ? timeline_start : scene->r.cfra;
|
||||
|
||||
SourceImageCache::StripEntry *best_strip = nullptr;
|
||||
SourceImageCache::Key best_key;
|
||||
int best_score = 0;
|
||||
for (const auto &strip : cache->map_.items()) {
|
||||
for (const auto &entry : strip.value.frames.items()) {
|
||||
const int item_frame = int(strip.key->start + entry.value.strip_frame);
|
||||
if (prefetch_loops_around) {
|
||||
if (item_frame >= timeline_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
if (item_frame >= cur_prefetch_start && item_frame <= timeline_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
}
|
||||
else if (item_frame >= cur_prefetch_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
|
||||
/* Score for removal is distance to current frame; 2x that if behind current frame. */
|
||||
int score = 0;
|
||||
if (item_frame < cur_frame) {
|
||||
score = (cur_frame - item_frame) * 2;
|
||||
}
|
||||
else if (item_frame > cur_frame) {
|
||||
score = item_frame - cur_frame;
|
||||
}
|
||||
if (score > best_score) {
|
||||
best_strip = &strip.value;
|
||||
best_key = entry.key;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove if we found one. */
|
||||
if (best_strip != nullptr) {
|
||||
IMB_freeImBuf(best_strip->frames.lookup(best_key).image.image);
|
||||
best_strip->frames.remove(best_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
51
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.hh
vendored
Normal file
51
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.hh
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Cache source images for strips.
|
||||
* - Keyed by (strip + frame index within strip media + view ID + scene strip draw type).
|
||||
* - Caching is only done for strips that are independent of
|
||||
* any other strips (images, movies, no-input effect strips like
|
||||
* Text and Color).
|
||||
* - When full, cache eviction policy is to remove frames furthest
|
||||
* from the current-frame, biasing towards removal of
|
||||
* frames behind the current-frame.
|
||||
* - Invalidated fairly rarely, since the cached items only change
|
||||
* when the source content changes.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Strip;
|
||||
struct Scene;
|
||||
struct RenderData;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void source_image_cache_put(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame,
|
||||
const SeqResult &image);
|
||||
|
||||
SeqResult source_image_cache_get(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame);
|
||||
|
||||
void source_image_cache_invalidate_strip(Scene *scene, const Strip *strip);
|
||||
|
||||
void source_image_cache_clear(Scene *scene);
|
||||
void source_image_cache_destroy(Scene *scene);
|
||||
|
||||
bool source_image_cache_evict(Scene *scene);
|
||||
|
||||
size_t source_image_cache_get_image_count(const Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
646
blender-5.2.0/source/blender/sequencer/intern/cache/thumbnail_cache.cc
vendored
Normal file
646
blender-5.2.0/source/blender/sequencer/intern/cache/thumbnail_cache.cc
vendored
Normal file
@@ -0,0 +1,646 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_mutex.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_main.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_thumbnail_cache.hh"
|
||||
#include "SEQ_time.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static constexpr int MAX_THUMBNAILS = 5000;
|
||||
|
||||
// #define DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
|
||||
static Mutex thumb_cache_mutex;
|
||||
|
||||
/* Thumbnail cache is a map keyed by media file path, with values being
|
||||
* the various thumbnails that are loaded for it (mostly images would contain just
|
||||
* one thumbnail frame, but movies can contain multiple).
|
||||
*
|
||||
* File entries and individual frame entries also record the timestamp when they were
|
||||
* last accessed, so that when the cache is full, some of the old entries can be removed.
|
||||
*
|
||||
* Thumbnails that are requested but do not have an exact match in the cache, are added
|
||||
* to the "requests" set. The requests are processed in the background by a WM job. */
|
||||
struct ThumbnailCache {
|
||||
struct FrameEntry {
|
||||
int frame_index = 0; /* Frame index (for movies) or image index (for image sequences). */
|
||||
int stream_index = 0; /* Stream index (only for multi-stream movies). */
|
||||
ImBuf *thumb = nullptr;
|
||||
int64_t used_at = 0;
|
||||
};
|
||||
|
||||
struct FileEntry {
|
||||
Vector<FrameEntry> frames;
|
||||
int64_t used_at = 0;
|
||||
};
|
||||
|
||||
struct Request {
|
||||
explicit Request(const std::string &path,
|
||||
int frame,
|
||||
int stream,
|
||||
StripType type,
|
||||
int64_t logical_time,
|
||||
float time_frame,
|
||||
int ch,
|
||||
int width,
|
||||
int height)
|
||||
: file_path(path),
|
||||
frame_index(frame),
|
||||
stream_index(stream),
|
||||
strip_type(type),
|
||||
requested_at(logical_time),
|
||||
timeline_frame(time_frame),
|
||||
channel(ch),
|
||||
full_width(width),
|
||||
full_height(height)
|
||||
{
|
||||
}
|
||||
/* These determine request uniqueness (for equality/hash in a Set). */
|
||||
std::string file_path;
|
||||
int frame_index = 0; /* Frame index (for movies) or image index (for image sequences). */
|
||||
int stream_index = 0; /* Stream index (only for multi-stream movies). */
|
||||
StripType strip_type = STRIP_TYPE_IMAGE;
|
||||
|
||||
/* The following members are payload and do not contribute to uniqueness. */
|
||||
int64_t requested_at = 0;
|
||||
float timeline_frame = 0;
|
||||
int channel = 0;
|
||||
int full_width = 0;
|
||||
int full_height = 0;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(file_path, frame_index, stream_index, strip_type);
|
||||
}
|
||||
bool operator==(const Request &o) const
|
||||
{
|
||||
return frame_index == o.frame_index && stream_index == o.stream_index &&
|
||||
strip_type == o.strip_type && file_path == o.file_path;
|
||||
}
|
||||
};
|
||||
|
||||
Map<std::string, FileEntry> map_;
|
||||
Set<Request> requests_;
|
||||
int64_t logical_time_ = 0;
|
||||
|
||||
~ThumbnailCache()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (const auto &item : map_.items()) {
|
||||
for (const auto &thumb : item.value.frames) {
|
||||
IMB_freeImBuf(thumb.thumb);
|
||||
}
|
||||
}
|
||||
map_.clear();
|
||||
requests_.clear();
|
||||
logical_time_ = 0;
|
||||
}
|
||||
|
||||
void remove_entry(const std::string &path)
|
||||
{
|
||||
FileEntry *entry = map_.lookup_ptr(path);
|
||||
if (entry == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto &thumb : entry->frames) {
|
||||
IMB_freeImBuf(thumb.thumb);
|
||||
}
|
||||
map_.remove_contained(path);
|
||||
}
|
||||
};
|
||||
|
||||
static ThumbnailCache *ensure_thumbnail_cache(Scene *scene)
|
||||
{
|
||||
ThumbnailCache **cache = &scene->ed->runtime->thumbnail_cache;
|
||||
if (*cache == nullptr) {
|
||||
*cache = MEM_new<ThumbnailCache>(__func__);
|
||||
}
|
||||
return *cache;
|
||||
}
|
||||
|
||||
static ThumbnailCache *query_thumbnail_cache(Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->thumbnail_cache;
|
||||
}
|
||||
|
||||
bool strip_can_have_thumbnail(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr || strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!ELEM(strip->type, STRIP_TYPE_MOVIE, STRIP_TYPE_IMAGE)) {
|
||||
return false;
|
||||
}
|
||||
const StripElem *se = strip->data->stripdata;
|
||||
if (se->orig_height == 0 || se->orig_width == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string get_path_from_strip(Scene *scene, const Strip *strip, float timeline_frame)
|
||||
{
|
||||
char filepath[FILE_MAX];
|
||||
filepath[0] = 0;
|
||||
switch (strip->type) {
|
||||
case STRIP_TYPE_IMAGE: {
|
||||
const StripElem *s_elem = render_give_stripelem(scene, strip, timeline_frame);
|
||||
if (s_elem != nullptr) {
|
||||
BLI_path_join(filepath, sizeof(filepath), strip->data->dirpath, s_elem->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
||||
}
|
||||
} break;
|
||||
case STRIP_TYPE_MOVIE:
|
||||
BLI_path_join(
|
||||
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
static void image_size_to_thumb_size(int &r_width, int &r_height)
|
||||
{
|
||||
float aspect = float(r_width) / float(r_height);
|
||||
if (r_width > r_height) {
|
||||
r_width = THUMB_SIZE;
|
||||
r_height = round_fl_to_int(THUMB_SIZE / aspect);
|
||||
}
|
||||
else {
|
||||
r_height = THUMB_SIZE;
|
||||
r_width = round_fl_to_int(THUMB_SIZE * aspect);
|
||||
}
|
||||
}
|
||||
|
||||
static ImBuf *make_thumb_for_image(const Scene *scene, const ThumbnailCache::Request &request)
|
||||
{
|
||||
ImBuf *ibuf = IMB_thumb_load_image(
|
||||
request.file_path.c_str(), THUMB_SIZE, nullptr, IMBThumbLoadFlags::LoadLargeFiles);
|
||||
if (ibuf == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
/* Keep only float buffer if we have both byte & float. */
|
||||
if (ibuf->float_data() != nullptr && ibuf->byte_data() != nullptr) {
|
||||
IMB_free_byte_pixels(ibuf);
|
||||
}
|
||||
|
||||
ensure_ibuf_is_sequencer_space(scene, ibuf, false);
|
||||
return ibuf;
|
||||
}
|
||||
|
||||
static void scale_to_thumbnail_size(ImBuf *ibuf)
|
||||
{
|
||||
if (ibuf == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We only need byte thumbnails. */
|
||||
if (ibuf->float_data()) {
|
||||
if (ibuf->byte_data() == nullptr) {
|
||||
IMB_byte_from_float(ibuf);
|
||||
}
|
||||
IMB_free_float_pixels(ibuf);
|
||||
}
|
||||
|
||||
int width = ibuf->x;
|
||||
int height = ibuf->y;
|
||||
image_size_to_thumb_size(width, height);
|
||||
IMB_scale(ibuf, width, height, IMBScaleFilter::Nearest, false);
|
||||
}
|
||||
|
||||
/* Background job that processes in-flight thumbnail requests. */
|
||||
class ThumbGenerationJob {
|
||||
Scene *scene_ = nullptr;
|
||||
ThumbnailCache *cache_ = nullptr;
|
||||
|
||||
public:
|
||||
ThumbGenerationJob(Scene *scene, ThumbnailCache *cache) : scene_(scene), cache_(cache) {}
|
||||
|
||||
static void ensure_job(const bContext *C, ThumbnailCache *cache);
|
||||
|
||||
private:
|
||||
static void run_fn(void *customdata, wmJobWorkerStatus *worker_status);
|
||||
static void end_fn(void *customdata);
|
||||
static void free_fn(void *customdata);
|
||||
};
|
||||
|
||||
void ThumbGenerationJob::ensure_job(const bContext *C, ThumbnailCache *cache)
|
||||
{
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
wmWindow *win = CTX_wm_window(C);
|
||||
Scene *scene = CTX_data_sequencer_scene(C);
|
||||
wmJob *wm_job = WM_jobs_get(wm,
|
||||
win,
|
||||
scene,
|
||||
"Generating strip thumbnails...",
|
||||
eWM_JobFlag(0),
|
||||
WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
|
||||
if (!WM_jobs_is_running(wm_job)) {
|
||||
ThumbGenerationJob *tj = MEM_new<ThumbGenerationJob>("ThumbGenerationJob", scene, cache);
|
||||
WM_jobs_customdata_set(wm_job, tj, free_fn);
|
||||
WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_SEQUENCER, NC_SCENE | ND_SEQUENCER);
|
||||
WM_jobs_callbacks(wm_job, run_fn, nullptr, nullptr, end_fn);
|
||||
|
||||
WM_jobs_start(wm, wm_job);
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbGenerationJob::free_fn(void *customdata)
|
||||
{
|
||||
ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
|
||||
MEM_delete(job);
|
||||
}
|
||||
|
||||
void ThumbGenerationJob::run_fn(void *customdata, wmJobWorkerStatus *worker_status)
|
||||
{
|
||||
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
clock_t t0 = clock();
|
||||
std::atomic<int> total_thumbs = 0, total_images = 0, total_movies = 0;
|
||||
#endif
|
||||
|
||||
ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
|
||||
Vector<ThumbnailCache::Request> requests;
|
||||
while (!worker_status->stop) {
|
||||
/* Under cache mutex lock: copy all current requests into a vector for processing.
|
||||
* NOTE: keep the requests set intact! We don't want to add new requests for same
|
||||
* items while we are processing them. They will be removed from the set once
|
||||
* they are finished, one by one. */
|
||||
{
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
requests.clear();
|
||||
requests.reserve(job->cache_->requests_.size());
|
||||
for (const auto &request : job->cache_->requests_) {
|
||||
requests.append(request);
|
||||
}
|
||||
}
|
||||
|
||||
if (requests.is_empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Sort requests by file, stream and increasing frame index. */
|
||||
std::ranges::sort(requests,
|
||||
[](const ThumbnailCache::Request &a, const ThumbnailCache::Request &b) {
|
||||
if (a.file_path != b.file_path) {
|
||||
return a.file_path < b.file_path;
|
||||
}
|
||||
if (a.stream_index != b.stream_index) {
|
||||
return a.stream_index < b.stream_index;
|
||||
}
|
||||
return a.frame_index < b.frame_index;
|
||||
});
|
||||
|
||||
/* Note: we could process thumbnail cache requests somewhat in parallel,
|
||||
* but let's not do that so that UI responsiveness is not affected much.
|
||||
* Some of video/image loading code parts are multi-threaded internally already,
|
||||
* and that does provide some parallelism. */
|
||||
{
|
||||
/* Often the same movie file is chopped into multiple strips next to each other.
|
||||
* Since the requests are sorted by file path and frame index, we can reuse MovieReader
|
||||
* objects between them for performance. */
|
||||
MovieReader *cur_anim = nullptr;
|
||||
std::string cur_anim_path;
|
||||
int cur_stream = 0;
|
||||
IMB_Proxy_Size cur_proxy_size = IMB_PROXY_NONE;
|
||||
for (const ThumbnailCache::Request &request : requests) {
|
||||
if (worker_status->stop) {
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
++total_thumbs;
|
||||
#endif
|
||||
ImBuf *thumb = nullptr;
|
||||
if (request.strip_type == STRIP_TYPE_IMAGE) {
|
||||
/* Load thumbnail for an image. */
|
||||
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
++total_images;
|
||||
#endif
|
||||
thumb = make_thumb_for_image(job->scene_, request);
|
||||
}
|
||||
else if (request.strip_type == STRIP_TYPE_MOVIE) {
|
||||
/* Load thumbnail for an movie. */
|
||||
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
++total_movies;
|
||||
#endif
|
||||
|
||||
/* Are we switching to a different movie file / stream? */
|
||||
if (request.file_path != cur_anim_path || request.stream_index != cur_stream) {
|
||||
if (cur_anim != nullptr) {
|
||||
MOV_close(cur_anim);
|
||||
cur_anim = nullptr;
|
||||
}
|
||||
|
||||
cur_anim_path = request.file_path;
|
||||
cur_stream = request.stream_index;
|
||||
cur_anim = MOV_open_file(
|
||||
cur_anim_path.c_str(), ImBufFlags::Zero, cur_stream, true, nullptr);
|
||||
cur_proxy_size = IMB_PROXY_NONE;
|
||||
if (cur_anim != nullptr) {
|
||||
/* Find the lowest proxy resolution available.
|
||||
* `x & -x` leaves only the lowest bit set. */
|
||||
int proxies_mask = MOV_get_existing_proxies(cur_anim);
|
||||
cur_proxy_size = IMB_Proxy_Size(proxies_mask & -proxies_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/* Decode the movie frame. */
|
||||
if (cur_anim != nullptr) {
|
||||
thumb = MOV_decode_frame(cur_anim, request.frame_index, cur_proxy_size);
|
||||
if (thumb == nullptr && cur_proxy_size != IMB_PROXY_NONE) {
|
||||
/* Broken proxy file, switch to non-proxy. */
|
||||
cur_proxy_size = IMB_PROXY_NONE;
|
||||
thumb = MOV_decode_frame(cur_anim, request.frame_index, cur_proxy_size);
|
||||
}
|
||||
if (thumb != nullptr) {
|
||||
seq_imbuf_assign_spaces(job->scene_, thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
scale_to_thumbnail_size(thumb);
|
||||
|
||||
/* Add result into the cache (under cache mutex lock). */
|
||||
{
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache::FileEntry *val = job->cache_->map_.lookup_ptr(request.file_path);
|
||||
if (val != nullptr) {
|
||||
val->used_at = math::max(val->used_at, request.requested_at);
|
||||
val->frames.append(
|
||||
{request.frame_index, request.stream_index, thumb, request.requested_at});
|
||||
}
|
||||
else {
|
||||
IMB_freeImBuf(thumb);
|
||||
}
|
||||
/* Remove the request from original set. */
|
||||
job->cache_->requests_.remove(request);
|
||||
}
|
||||
|
||||
if (thumb) {
|
||||
worker_status->do_update = true;
|
||||
}
|
||||
}
|
||||
if (cur_anim != nullptr) {
|
||||
MOV_close(cur_anim);
|
||||
cur_anim = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
clock_t t1 = clock();
|
||||
printf("VSE thumb job: %i thumbs (%i img, %i movie) in %.3f sec\n",
|
||||
total_thumbs.load(),
|
||||
total_images.load(),
|
||||
total_movies.load(),
|
||||
double(t1 - t0) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ThumbGenerationJob::end_fn(void *customdata)
|
||||
{
|
||||
ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
|
||||
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, job->scene_);
|
||||
}
|
||||
|
||||
static ImBuf *query_thumbnail(ThumbnailCache &cache,
|
||||
const std::string &key,
|
||||
int frame_index,
|
||||
float timeline_frame,
|
||||
const bContext *C,
|
||||
const Strip *strip)
|
||||
{
|
||||
int64_t cur_time = cache.logical_time_;
|
||||
ThumbnailCache::FileEntry *val = cache.map_.lookup_ptr(key);
|
||||
|
||||
if (val == nullptr) {
|
||||
/* Nothing in cache for this path yet. */
|
||||
ThumbnailCache::FileEntry value;
|
||||
value.used_at = cur_time;
|
||||
cache.map_.add_new(key, value);
|
||||
val = cache.map_.lookup_ptr(key);
|
||||
}
|
||||
BLI_assert_msg(val != nullptr, "Thumbnail cache value should never be null here");
|
||||
|
||||
/* Search thumbnail entries of this file for closest match to the frame we want. */
|
||||
int64_t best_index = -1;
|
||||
int best_score = INT_MAX;
|
||||
for (int64_t index = 0; index < val->frames.size(); index++) {
|
||||
if (strip->streamindex != val->frames[index].stream_index) {
|
||||
continue; /* Different video stream than what we need, ignore. */
|
||||
}
|
||||
int score = math::abs(frame_index - val->frames[index].frame_index);
|
||||
if (score < best_score) {
|
||||
best_score = score;
|
||||
best_index = index;
|
||||
if (score == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_score > 0) {
|
||||
/* We do not have an exact frame match, add a thumb generation request. */
|
||||
const StripElem *se = strip->data->stripdata;
|
||||
int img_width = se->orig_width;
|
||||
int img_height = se->orig_height;
|
||||
ThumbnailCache::Request request(key,
|
||||
frame_index,
|
||||
strip->streamindex,
|
||||
strip->type,
|
||||
cur_time,
|
||||
timeline_frame,
|
||||
strip->channel,
|
||||
img_width,
|
||||
img_height);
|
||||
cache.requests_.add(request);
|
||||
ThumbGenerationJob::ensure_job(C, &cache);
|
||||
}
|
||||
|
||||
if (best_index < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Return the closest thumbnail fit we have so far. */
|
||||
val->used_at = math::max(val->used_at, cur_time);
|
||||
val->frames[best_index].used_at = math::max(val->frames[best_index].used_at, cur_time);
|
||||
return val->frames[best_index].thumb;
|
||||
}
|
||||
|
||||
ImBuf *thumbnail_cache_get(const bContext *C,
|
||||
Scene *scene,
|
||||
const Strip *strip,
|
||||
float timeline_frame)
|
||||
{
|
||||
if (!strip_can_have_thumbnail(scene, strip)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
timeline_frame = math::round(timeline_frame);
|
||||
|
||||
const std::string key = get_path_from_strip(scene, strip, timeline_frame);
|
||||
int frame_index = give_frame_index(scene, strip, timeline_frame);
|
||||
if (strip->type == STRIP_TYPE_MOVIE) {
|
||||
frame_index += strip->anim_startofs;
|
||||
}
|
||||
|
||||
ImBuf *res = nullptr;
|
||||
{
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache *cache = ensure_thumbnail_cache(scene);
|
||||
res = query_thumbnail(*cache, key, frame_index, timeline_frame, C, strip);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
IMB_refImBuf(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void thumbnail_cache_invalidate_strip(Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (!strip_can_have_thumbnail(scene, strip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
if (ELEM((strip)->type, STRIP_TYPE_MOVIE, STRIP_TYPE_IMAGE)) {
|
||||
const StripElem *elem = strip->data->stripdata;
|
||||
if (elem != nullptr) {
|
||||
int paths_count = 1;
|
||||
if (strip->type == STRIP_TYPE_IMAGE) {
|
||||
/* Image strip has array of file names. */
|
||||
paths_count = int(MEM_allocN_len(elem) / sizeof(*elem));
|
||||
}
|
||||
char filepath[FILE_MAX];
|
||||
const char *basepath = ID_BLEND_PATH_FROM_GLOBAL(&scene->id);
|
||||
for (int i = 0; i < paths_count; i++, elem++) {
|
||||
BLI_path_join(filepath, sizeof(filepath), strip->data->dirpath, elem->filename);
|
||||
BLI_path_abs(filepath, basepath);
|
||||
cache->remove_entry(filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void thumbnail_cache_maintain_capacity(Scene *scene)
|
||||
{
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->logical_time_++;
|
||||
|
||||
/* Count total number of thumbnails, and track which one is the least recently used file. */
|
||||
int64_t entries = 0;
|
||||
std::string oldest_file;
|
||||
/* Do not remove thumbnails for files used within last 10 updates. */
|
||||
int64_t oldest_time = cache->logical_time_ - 10;
|
||||
int64_t oldest_entries = 0;
|
||||
for (const auto &item : cache->map_.items()) {
|
||||
entries += item.value.frames.size();
|
||||
if (item.value.used_at < oldest_time) {
|
||||
oldest_file = item.key;
|
||||
oldest_time = item.value.used_at;
|
||||
oldest_entries = item.value.frames.size();
|
||||
}
|
||||
}
|
||||
|
||||
/* If we're beyond capacity and have a long-unused file, remove that. */
|
||||
if (entries > MAX_THUMBNAILS && !oldest_file.empty()) {
|
||||
cache->remove_entry(oldest_file);
|
||||
entries -= oldest_entries;
|
||||
}
|
||||
|
||||
/* If we're still beyond capacity, remove individual long-unused (but not within
|
||||
* last 100 updates) individual frames. */
|
||||
if (entries > MAX_THUMBNAILS) {
|
||||
for (const auto &item : cache->map_.items()) {
|
||||
for (int64_t i = 0; i < item.value.frames.size(); i++) {
|
||||
if (item.value.frames[i].used_at < cache->logical_time_ - 100) {
|
||||
IMB_freeImBuf(item.value.frames[i].thumb);
|
||||
item.value.frames.remove_and_reorder(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void thumbnail_cache_discard_requests_outside(Scene *scene, const rctf &rect)
|
||||
{
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->requests_.remove_if([&](const ThumbnailCache::Request &request) {
|
||||
return request.timeline_frame < rect.xmin || request.timeline_frame > rect.xmax ||
|
||||
request.channel < rect.ymin || request.channel > rect.ymax;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void thumbnail_cache_clear(Scene *scene)
|
||||
{
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
scene->ed->runtime->thumbnail_cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void thumbnail_cache_destroy(Scene *scene)
|
||||
{
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
BLI_assert(cache == scene->ed->runtime->thumbnail_cache);
|
||||
MEM_delete(scene->ed->runtime->thumbnail_cache);
|
||||
scene->ed->runtime->thumbnail_cache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
75
blender-5.2.0/source/blender/sequencer/intern/channels.cc
Normal file
75
blender-5.2.0/source/blender/sequencer/intern/channels.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "sequencer.hh"
|
||||
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
ListBaseT<SeqTimelineChannel> *channels_displayed_get(const Editing *ed)
|
||||
{
|
||||
return ed ? ed->current_channels() : nullptr;
|
||||
}
|
||||
|
||||
void channels_ensure(ListBaseT<SeqTimelineChannel> *channels)
|
||||
{
|
||||
/* Allocate channels. Channel 0 is never used, but allocated to prevent off by 1 issues. */
|
||||
for (int i = 0; i < MAX_CHANNELS + 1; i++) {
|
||||
SeqTimelineChannel *channel = MEM_new<SeqTimelineChannel>("seq timeline channel");
|
||||
SNPRINTF_UTF8(channel->name, DATA_("Channel %d"), i);
|
||||
channel->index = i;
|
||||
BLI_addtail(channels, channel);
|
||||
}
|
||||
}
|
||||
|
||||
void channels_duplicate(ListBaseT<SeqTimelineChannel> *channels_dst,
|
||||
ListBaseT<SeqTimelineChannel> *channels_src)
|
||||
{
|
||||
for (SeqTimelineChannel &channel : *channels_src) {
|
||||
SeqTimelineChannel *channel_duplicate = static_cast<SeqTimelineChannel *>(
|
||||
MEM_dupalloc(&channel));
|
||||
BLI_addtail(channels_dst, channel_duplicate);
|
||||
}
|
||||
}
|
||||
|
||||
void channels_free(ListBaseT<SeqTimelineChannel> *channels)
|
||||
{
|
||||
for (SeqTimelineChannel &channel : channels->items_mutable()) {
|
||||
MEM_delete(&channel);
|
||||
}
|
||||
}
|
||||
|
||||
SeqTimelineChannel *channel_get_by_index(const ListBaseT<SeqTimelineChannel> *channels,
|
||||
const int channel_index)
|
||||
{
|
||||
return static_cast<SeqTimelineChannel *>(BLI_findlink(channels, channel_index));
|
||||
}
|
||||
|
||||
ListBaseT<SeqTimelineChannel> *get_channels_by_strip(Editing *ed, const Strip *strip)
|
||||
{
|
||||
Strip *strip_owner = lookup_meta_by_strip(ed, strip);
|
||||
if (strip_owner != nullptr) {
|
||||
return &strip_owner->channels;
|
||||
}
|
||||
|
||||
return &ed->channels;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
256
blender-5.2.0/source/blender/sequencer/intern/compositor.cc
Normal file
256
blender-5.2.0/source/blender/sequencer/intern/compositor.cc
Normal file
@@ -0,0 +1,256 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "COM_algorithm_parallel_reduction.hh"
|
||||
#include "COM_ocio_color_space_conversion_shader.hh"
|
||||
#include "COM_realize_on_domain_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "GPU_state.hh"
|
||||
#include "GPU_texture_pool.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "compositor.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
compositor::ResultPrecision CompositorContext::get_precision() const
|
||||
{
|
||||
switch (this->render_data_.scene->r.compositor_precision) {
|
||||
case SCE_COMPOSITOR_PRECISION_AUTO:
|
||||
/* Auto uses full precision for final renders and half precision otherwise. */
|
||||
return this->render_data_.render ? compositor::ResultPrecision::Full :
|
||||
compositor::ResultPrecision::Half;
|
||||
case SCE_COMPOSITOR_PRECISION_FULL:
|
||||
return compositor::ResultPrecision::Full;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return compositor::ResultPrecision::Half;
|
||||
}
|
||||
|
||||
void CompositorContext::create_result_from_input(compositor::Result &result, ImBuf &input)
|
||||
{
|
||||
PRF_scope_with_name("SeqCreateCompInput", ProfileCategory::Draw);
|
||||
const bool gpu = this->use_gpu();
|
||||
const int2 size = int2(input.x, input.y);
|
||||
if (!gpu) {
|
||||
/* CPU path: ensure input is linear float. */
|
||||
ensure_ibuf_is_linear_space(&input, true);
|
||||
BLI_assert(input.float_data());
|
||||
result.share_data(input.float_data(), size);
|
||||
return;
|
||||
}
|
||||
|
||||
/* GPU path: do necessary color space conversions (if any) to linear space on the GPU. */
|
||||
const bool input_is_byte = input.float_data() == nullptr;
|
||||
const char *input_colorspace = input_is_byte ? IMB_colormanagement_get_byte_colorspace(&input) :
|
||||
IMB_colormanagement_get_float_colorspace(&input);
|
||||
const char *linear_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
||||
COLOR_ROLE_SCENE_LINEAR);
|
||||
|
||||
bool use_fallback = true;
|
||||
|
||||
if (input_is_byte || !STREQ(input_colorspace, linear_colorspace)) {
|
||||
/* Need to convert data format or colorspace: upload input into temporary texture,
|
||||
* convert into compositor result. */
|
||||
|
||||
/* Get the conversion shader. */
|
||||
compositor::OCIOColorSpaceConversionShader &ocio_shader =
|
||||
this->cache_manager().ocio_color_space_conversion_shaders.get(
|
||||
*this, input_colorspace, linear_colorspace);
|
||||
gpu::Shader *shader = ocio_shader.bind_shader_and_resources();
|
||||
if (shader) {
|
||||
|
||||
/* Upload input image into a GPU texture. */
|
||||
gpu::TexturePool &pool = gpu::TexturePool::get();
|
||||
gpu::Texture *input_tex = pool.acquire_texture_2d(size,
|
||||
1,
|
||||
input_is_byte ?
|
||||
gpu::TextureFormat::UNORM_8_8_8_8 :
|
||||
gpu::TextureFormat::SFLOAT_32_32_32_32,
|
||||
GPU_TEXTURE_USAGE_SHADER_READ,
|
||||
"seq_comp_input");
|
||||
if (input_tex) {
|
||||
if (input_is_byte) {
|
||||
GPU_texture_update(input_tex, GPU_DATA_UBYTE, input.byte_data());
|
||||
}
|
||||
else {
|
||||
GPU_texture_update(input_tex, GPU_DATA_FLOAT, input.float_data());
|
||||
}
|
||||
|
||||
/* Allocate compositor result texture. We use global compositor precision even
|
||||
* for byte inputs. In theory Half precision should be enough, but that leads to potential
|
||||
* small differences between CPU & GPU paths. */
|
||||
result.set_precision(get_precision());
|
||||
result.allocate_texture(size);
|
||||
|
||||
/* Convert input texture into the compositor result texture. */
|
||||
GPU_texture_bind(input_tex,
|
||||
GPU_shader_get_sampler_binding(shader, ocio_shader.input_sampler_name()));
|
||||
result.bind_as_image(shader, ocio_shader.output_image_name());
|
||||
|
||||
GPU_shader_uniform_1b(shader, "premultiply_output", input_is_byte);
|
||||
|
||||
compositor::compute_dispatch_threads_at_least(shader, size);
|
||||
|
||||
GPU_texture_unbind(input_tex);
|
||||
result.unbind_as_image();
|
||||
pool.release_texture(input_tex);
|
||||
|
||||
use_fallback = false;
|
||||
}
|
||||
ocio_shader.unbind_shader_and_resources();
|
||||
}
|
||||
}
|
||||
|
||||
/* Colorspace conversion was not needed or failed: upload input float data into
|
||||
* compositor result. */
|
||||
if (use_fallback) {
|
||||
/* This is a no-op if input is already linear float; otherwise this step might be needed
|
||||
* if conversion above has failed. */
|
||||
ensure_ibuf_is_linear_space(&input, true);
|
||||
result.allocate_texture(size);
|
||||
GPU_texture_update(result, GPU_DATA_FLOAT, input.float_data());
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorContext::write_viewer_impl(const compositor::Result &result, ImBuf &image)
|
||||
{
|
||||
using namespace compositor;
|
||||
|
||||
/* Realize the transforms if needed. */
|
||||
const InputDescriptor input_descriptor = {ResultType::Color,
|
||||
InputRealizationMode::OperationDomain};
|
||||
SimpleOperation *realization_operation = RealizeOnDomainOperation::construct_if_needed(
|
||||
*this, result, input_descriptor, result.domain());
|
||||
|
||||
if (realization_operation) {
|
||||
Result realize_input = this->create_result(ResultType::Color, result.precision());
|
||||
realize_input.share_data(result);
|
||||
realization_operation->map_input_to_result(&realize_input);
|
||||
realization_operation->evaluate();
|
||||
|
||||
Result &realized_viewer_result = realization_operation->get_result();
|
||||
this->write_output(realized_viewer_result, image);
|
||||
realized_viewer_result.release();
|
||||
viewer_was_written_ = true;
|
||||
delete realization_operation;
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_output(result, image);
|
||||
viewer_was_written_ = true;
|
||||
}
|
||||
|
||||
void CompositorContext::write_output(const compositor::Result &result, ImBuf &image)
|
||||
{
|
||||
/* Do not write the output if the viewer output was already written. */
|
||||
if (viewer_was_written_) {
|
||||
return;
|
||||
}
|
||||
|
||||
PRF_scope_with_name("SeqCompWriteOutput", ProfileCategory::Draw);
|
||||
|
||||
if (result.is_single_value()) {
|
||||
compositor::Color color = result.get_single_value<compositor::Color>();
|
||||
IMB_rectfill(&image, color);
|
||||
image.color_mode = color.a < 1.0f ? ImColorMode::RGBA : ImColorMode::RGB;
|
||||
return;
|
||||
}
|
||||
|
||||
result_translation_ = result.domain().transformation.location();
|
||||
const int output_size_x = result.domain().data_size.x;
|
||||
const int output_size_y = result.domain().data_size.y;
|
||||
if (output_size_x != image.x || output_size_y != image.y || !image.float_buffer.data) {
|
||||
/* Output size is different (e.g. image is blurred with expanded bounds);
|
||||
* need to allocate appropriately sized buffer. */
|
||||
IMB_free_all_data(&image);
|
||||
image.x = output_size_x;
|
||||
image.y = output_size_y;
|
||||
}
|
||||
|
||||
compositor::Color min_color = compositor::minimum_color(*this, result);
|
||||
image.color_mode = min_color.a < 1.0f ? ImColorMode::RGBA : ImColorMode::RGB;
|
||||
|
||||
if (this->use_gpu()) {
|
||||
PRF_scope_with_name("SeqCompositorGPUReadback", ProfileCategory::Draw);
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
IMB_alloc_float_pixels(&image, 4, false);
|
||||
GPU_texture_read(result.gpu_texture(), GPU_DATA_FLOAT, 0, image.float_data_for_write());
|
||||
}
|
||||
else if (result.sharing_info()) {
|
||||
image.channels = 4;
|
||||
image.float_buffer = ImBufFloatBuffer{
|
||||
.data = static_cast<const float *>(result.cpu_data().data()),
|
||||
.sharing_info = result.sharing_info(),
|
||||
.colorspace = nullptr};
|
||||
}
|
||||
else if (result.cpu_data().data() != image.float_data()) {
|
||||
IMB_alloc_float_pixels(&image, 4, false);
|
||||
std::memcpy(image.float_data_for_write(),
|
||||
result.cpu_data().data(),
|
||||
IMB_get_pixel_count(&image) * sizeof(float) * 4);
|
||||
}
|
||||
const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
||||
COLOR_ROLE_SCENE_LINEAR);
|
||||
IMB_colormanagement_assign_float_colorspace(&image, to_colorspace);
|
||||
}
|
||||
|
||||
void CompositorContext::write_outputs(const bNodeTree &node_group,
|
||||
compositor::NodeGroupOperation &node_group_operation,
|
||||
ImBuf &output_image)
|
||||
{
|
||||
using namespace compositor;
|
||||
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
|
||||
Result &output_result = node_group_operation.get_result(output_socket->identifier);
|
||||
if (!output_result.should_compute()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Realize the output transforms if needed. */
|
||||
const InputDescriptor input_descriptor = {ResultType::Color,
|
||||
InputRealizationMode::OperationDomain};
|
||||
SimpleOperation *realization_operation = RealizeOnDomainOperation::construct_if_needed(
|
||||
*this, output_result, input_descriptor, output_result.domain());
|
||||
if (realization_operation) {
|
||||
realization_operation->map_input_to_result(&output_result);
|
||||
realization_operation->evaluate();
|
||||
Result &realized_output_result = realization_operation->get_result();
|
||||
this->write_output(realized_output_result, output_image);
|
||||
realized_output_result.release();
|
||||
delete realization_operation;
|
||||
continue;
|
||||
}
|
||||
|
||||
this->write_output(output_result, output_image);
|
||||
output_result.release();
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorContext::set_output_refcount(const bNodeTree &node_group,
|
||||
compositor::NodeGroupOperation &node_group_operation)
|
||||
{
|
||||
using namespace compositor;
|
||||
/* Set the reference count for the outputs, only the first color output is actually needed,
|
||||
* while the rest are ignored. */
|
||||
node_group.ensure_interface_cache();
|
||||
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
|
||||
const bool is_first_output = output_socket == node_group.interface_outputs().first();
|
||||
Result &output_result = node_group_operation.get_result(output_socket->identifier);
|
||||
const bool is_color = output_result.type() == ResultType::Color;
|
||||
output_result.set_reference_count(is_first_output && is_color ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
91
blender-5.2.0/source/blender/sequencer/intern/compositor.hh
Normal file
91
blender-5.2.0/source/blender/sequencer/intern/compositor.hh
Normal file
@@ -0,0 +1,91 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_node_group_operation.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
class CompositorContext : public compositor::Context {
|
||||
protected:
|
||||
const RenderData &render_data_;
|
||||
const Strip *strip_ = nullptr;
|
||||
float2 result_translation_ = float2(0, 0);
|
||||
/* Identifies if the output of the viewer was written. */
|
||||
bool viewer_was_written_ = false;
|
||||
|
||||
/* True if GPU compute is supported and can be used, if false, we fallback to CPU. */
|
||||
bool gpu_supported_ = true;
|
||||
|
||||
public:
|
||||
CompositorContext(compositor::StaticCacheManager &cache_manager,
|
||||
const RenderData &render_data,
|
||||
const Strip &strip)
|
||||
: compositor::Context(cache_manager), render_data_(render_data), strip_(&strip)
|
||||
{
|
||||
}
|
||||
const Main &get_main() const override
|
||||
{
|
||||
return *render_data_.bmain;
|
||||
}
|
||||
const Scene &get_scene() const override
|
||||
{
|
||||
return *render_data_.scene;
|
||||
}
|
||||
bool treat_viewer_as_group_output() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const Strip *get_strip() const override
|
||||
{
|
||||
return strip_;
|
||||
}
|
||||
|
||||
void set_gpu_supported(const bool supported)
|
||||
{
|
||||
gpu_supported_ = supported;
|
||||
}
|
||||
|
||||
bool use_gpu() const override
|
||||
{
|
||||
return gpu_supported_ &&
|
||||
this->render_data_.scene->r.compositor_device == SCE_COMPOSITOR_DEVICE_GPU;
|
||||
}
|
||||
|
||||
compositor::ResultPrecision get_precision() const override;
|
||||
|
||||
float2 get_result_translation() const
|
||||
{
|
||||
return result_translation_;
|
||||
}
|
||||
|
||||
protected:
|
||||
compositor::NodeGroupOutputTypes needed_outputs() const
|
||||
{
|
||||
compositor::NodeGroupOutputTypes needed_outputs =
|
||||
compositor::NodeGroupOutputTypes::GroupOutputNode;
|
||||
if (!render_data_.render) {
|
||||
needed_outputs |= compositor::NodeGroupOutputTypes::ViewerNode;
|
||||
}
|
||||
return needed_outputs;
|
||||
}
|
||||
|
||||
void create_result_from_input(compositor::Result &result, ImBuf &input);
|
||||
void write_viewer_impl(const compositor::Result &result, ImBuf &image);
|
||||
void write_output(const compositor::Result &result, ImBuf &image);
|
||||
void write_outputs(const bNodeTree &node_group,
|
||||
compositor::NodeGroupOperation &node_group_operation,
|
||||
ImBuf &output_image);
|
||||
void set_output_refcount(const bNodeTree &node_group,
|
||||
compositor::NodeGroupOperation &node_group_operation);
|
||||
};
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,181 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Color Add Effect */
|
||||
|
||||
struct AddEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
const float fac = this->factor;
|
||||
int ifac = int(256.0f * fac);
|
||||
for (int64_t idx = 0; idx < size; idx++) {
|
||||
if constexpr (std::is_same_v<T, uchar>) {
|
||||
const int f = ifac * int(src2[3]);
|
||||
dst[0] = min_ii(src1[0] + ((f * src2[0]) >> 16), 255);
|
||||
dst[1] = min_ii(src1[1] + ((f * src2[1]) >> 16), 255);
|
||||
dst[2] = min_ii(src1[2] + ((f * src2[2]) >> 16), 255);
|
||||
}
|
||||
else {
|
||||
const float f = (1.0f - (src1[3] * (1.0f - fac))) * src2[3];
|
||||
dst[0] = src1[0] + f * src2[0];
|
||||
dst[1] = src1[1] + f * src2[1];
|
||||
dst[2] = src1[2] + f * src2[2];
|
||||
}
|
||||
dst[3] = src1[3];
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_add_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip * /*strip*/,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxAdd", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
AddEffectOp op;
|
||||
op.factor = fac;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
/* Destination uses alpha from src1 */
|
||||
dst.is_opaque_before_transform = !src1.image->can_contain_alpha();
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Color Subtract Effect */
|
||||
|
||||
struct SubEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
const float fac = this->factor;
|
||||
int ifac = int(256.0f * fac);
|
||||
for (int64_t idx = 0; idx < size; idx++) {
|
||||
if constexpr (std::is_same_v<T, uchar>) {
|
||||
const int f = ifac * int(src2[3]);
|
||||
dst[0] = max_ii(src1[0] - ((f * src2[0]) >> 16), 0);
|
||||
dst[1] = max_ii(src1[1] - ((f * src2[1]) >> 16), 0);
|
||||
dst[2] = max_ii(src1[2] - ((f * src2[2]) >> 16), 0);
|
||||
}
|
||||
else {
|
||||
const float f = (1.0f - (src1[3] * (1.0f - fac))) * src2[3];
|
||||
dst[0] = max_ff(src1[0] - f * src2[0], 0.0f);
|
||||
dst[1] = max_ff(src1[1] - f * src2[1], 0.0f);
|
||||
dst[2] = max_ff(src1[2] - f * src2[2], 0.0f);
|
||||
}
|
||||
dst[3] = src1[3];
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_sub_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip * /*strip*/,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxSub", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
SubEffectOp op;
|
||||
op.factor = fac;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
/* Destination uses alpha from src1 */
|
||||
dst.is_opaque_before_transform = !src1.image->can_contain_alpha();
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Multiply Effect */
|
||||
|
||||
struct MulEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
const float fac = this->factor;
|
||||
int ifac = int(256.0f * fac);
|
||||
for (int64_t idx = 0; idx < size; idx++) {
|
||||
/* Formula: `fac * (a * b) + (1-fac) * a => fac * a * (b - 1) + a` */
|
||||
if constexpr (std::is_same_v<T, uchar>) {
|
||||
dst[0] = src1[0] + ((ifac * src1[0] * (src2[0] - 255)) >> 16);
|
||||
dst[1] = src1[1] + ((ifac * src1[1] * (src2[1] - 255)) >> 16);
|
||||
dst[2] = src1[2] + ((ifac * src1[2] * (src2[2] - 255)) >> 16);
|
||||
dst[3] = src1[3] + ((ifac * src1[3] * (src2[3] - 255)) >> 16);
|
||||
}
|
||||
else {
|
||||
dst[0] = src1[0] + fac * src1[0] * (src2[0] - 1.0f);
|
||||
dst[1] = src1[1] + fac * src1[1] * (src2[1] - 1.0f);
|
||||
dst[2] = src1[2] + fac * src1[2] * (src2[2] - 1.0f);
|
||||
dst[3] = src1[3] + fac * src1[3] * (src2[3] - 1.0f);
|
||||
}
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_mul_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip * /*strip*/,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxMul", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
MulEffectOp op;
|
||||
op.factor = fac;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
return dst;
|
||||
}
|
||||
|
||||
void add_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.execute = do_add_effect;
|
||||
rval.early_out = early_out_mul_input2;
|
||||
}
|
||||
|
||||
void sub_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.execute = do_sub_effect;
|
||||
rval.early_out = early_out_mul_input2;
|
||||
}
|
||||
|
||||
void mul_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.execute = do_mul_effect;
|
||||
rval.early_out = early_out_mul_input2;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,92 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static StripEarlyOut early_out_adjustment(const Strip * /*strip*/, float /*fac*/)
|
||||
{
|
||||
return StripEarlyOut::NoInput;
|
||||
}
|
||||
|
||||
static SeqResult do_adjustment_impl(const RenderData *context,
|
||||
SeqRenderState *state,
|
||||
Strip *strip,
|
||||
float timeline_frame)
|
||||
{
|
||||
SeqResult out;
|
||||
Editing *ed = context->scene->ed;
|
||||
|
||||
ListBaseT<Strip> *seqbasep = get_seqbase_by_strip(context->scene, strip);
|
||||
ListBaseT<SeqTimelineChannel> *channels = get_channels_by_strip(ed, strip);
|
||||
|
||||
/* Clamp timeline_frame to strip range so it behaves as if it had "still frame" offset (last
|
||||
* frame is static after end of strip). This is how most strips behave. This way transition
|
||||
* effects that doesn't overlap or speed effect can't fail rendering outside of strip range. */
|
||||
timeline_frame = clamp_i(
|
||||
timeline_frame, strip->left_handle(), strip->right_handle(context->scene) - 1);
|
||||
|
||||
if (strip->channel > 1) {
|
||||
out = seq_render_give_ibuf_seqbase(
|
||||
context, state, timeline_frame, strip->channel - 1, channels, seqbasep);
|
||||
}
|
||||
|
||||
/* Found nothing? Then work our way up the meta-strip stack, as this adjustment strip might be
|
||||
* inside a nested meta-strip and affect strips below that meta-strip.
|
||||
*
|
||||
* NOTE: we should NOT walk past the stack level that the user is currently tabbed into,
|
||||
* otherwise the adjustment layer can leak content from outside the meta context. */
|
||||
|
||||
if (!out.is_valid()) {
|
||||
Strip *meta = lookup_meta_by_strip(ed, strip);
|
||||
if (meta && meta != ed->current_meta_strip) {
|
||||
out = do_adjustment_impl(context, state, meta, timeline_frame);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static SeqResult do_adjustment(const RenderData *context,
|
||||
SeqRenderState *state,
|
||||
Strip *strip,
|
||||
float timeline_frame,
|
||||
float /*fac*/,
|
||||
const SeqResult & /*ibuf1*/,
|
||||
const SeqResult & /*ibuf2*/)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxAdjustment", ProfileCategory::Draw);
|
||||
Editing *ed = context->scene->ed;
|
||||
if (!ed || state->strips_in_progress.contains(strip)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
state->strips_in_progress.add(strip);
|
||||
SeqResult out = do_adjustment_impl(context, state, strip, timeline_frame);
|
||||
state->strips_in_progress.remove(strip);
|
||||
return out;
|
||||
}
|
||||
|
||||
void adjustment_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.early_out = early_out_adjustment;
|
||||
rval.execute = do_adjustment;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,415 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_color_blend.h"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Alpha Over Effect */
|
||||
|
||||
static void init_alpha_over_or_under(Strip *strip)
|
||||
{
|
||||
Strip *input1 = strip->input1;
|
||||
Strip *input2 = strip->input2;
|
||||
|
||||
strip->input2 = input1;
|
||||
strip->input1 = input2;
|
||||
}
|
||||
|
||||
static bool alpha_opaque(uchar alpha)
|
||||
{
|
||||
return alpha == 255;
|
||||
}
|
||||
|
||||
static bool alpha_opaque(float alpha)
|
||||
{
|
||||
return alpha >= 1.0f;
|
||||
}
|
||||
|
||||
/* dst = src1 over src2 (alpha from src1) */
|
||||
struct AlphaOverEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
const float fac = this->factor;
|
||||
if (fac <= 0.0f) {
|
||||
memcpy(dst, src2, sizeof(T) * 4 * size);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int64_t idx = 0; idx < size; idx++) {
|
||||
if (std::is_same_v<T, uchar> && src1[3] == 0) {
|
||||
/* Optimization for fully transparent pixels: copy src2. Only do this for byte images;
|
||||
* in floats alpha=0 can still have pure emissive color. */
|
||||
memcpy(dst, src2, sizeof(T) * 4);
|
||||
}
|
||||
else if (fac == 1.0f && alpha_opaque(src1[3])) {
|
||||
/* No change to `src1` as `fac == 1` and fully opaque. */
|
||||
memcpy(dst, src1, sizeof(T) * 4);
|
||||
}
|
||||
else {
|
||||
float4 col1 = load_premul_pixel(src1);
|
||||
float mfac = 1.0f - fac * col1.w;
|
||||
float4 col2 = load_premul_pixel(src2);
|
||||
float4 col = fac * col1 + mfac * col2;
|
||||
store_premul_pixel(col, dst);
|
||||
}
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_alphaover_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip * /*strip*/,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxOver", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
AlphaOverEffectOp op;
|
||||
op.factor = fac;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Alpha Under Effect */
|
||||
|
||||
/* dst = src1 under src2 (alpha from src2) */
|
||||
struct AlphaUnderEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
const float fac = this->factor;
|
||||
if (fac <= 0.0f) {
|
||||
memcpy(dst, src2, sizeof(T) * 4 * size);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int64_t idx = 0; idx < size; idx++) {
|
||||
if (src2[3] <= 0.0f && fac >= 1.0f) {
|
||||
memcpy(dst, src1, sizeof(T) * 4);
|
||||
}
|
||||
else if (alpha_opaque(src2[3])) {
|
||||
memcpy(dst, src2, sizeof(T) * 4);
|
||||
}
|
||||
else {
|
||||
float4 col2 = load_premul_pixel(src2);
|
||||
float mfac = fac * (1.0f - col2.w);
|
||||
float4 col1 = load_premul_pixel(src1);
|
||||
float4 col = mfac * col1 + col2;
|
||||
store_premul_pixel(col, dst);
|
||||
}
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_alphaunder_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip * /*strip*/,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxUnder", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
AlphaUnderEffectOp op;
|
||||
op.factor = fac;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Blend Mode Effect */
|
||||
|
||||
/* blend_function has to be: void (T* dst, const T *src1, const T *src2) */
|
||||
template<typename T, typename Func>
|
||||
static void apply_blend_function(
|
||||
float fac, int64_t size, const T *src1, const T *src2, T *dst, Func blend_function)
|
||||
{
|
||||
for (int64_t i = 0; i < size; i++) {
|
||||
T achannel = src2[3];
|
||||
(static_cast<T *>(const_cast<T *>(src2)))[3] = T(achannel * fac);
|
||||
blend_function(dst, src1, src2);
|
||||
(static_cast<T *>(const_cast<T *>(src2)))[3] = achannel;
|
||||
dst[3] = src1[3];
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
|
||||
static void do_blend_effect_float(float fac,
|
||||
int64_t size,
|
||||
const float *rect1,
|
||||
const float *rect2,
|
||||
StripBlendMode btype,
|
||||
float *out)
|
||||
{
|
||||
switch (btype) {
|
||||
case STRIP_BLEND_ADD:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_add_float);
|
||||
break;
|
||||
case STRIP_BLEND_SUB:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_sub_float);
|
||||
break;
|
||||
case STRIP_BLEND_MUL:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_mul_float);
|
||||
break;
|
||||
case STRIP_BLEND_DARKEN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_darken_float);
|
||||
break;
|
||||
case STRIP_BLEND_COLOR_BURN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_burn_float);
|
||||
break;
|
||||
case STRIP_BLEND_LINEAR_BURN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearburn_float);
|
||||
break;
|
||||
case STRIP_BLEND_SCREEN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_screen_float);
|
||||
break;
|
||||
case STRIP_BLEND_LIGHTEN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_lighten_float);
|
||||
break;
|
||||
case STRIP_BLEND_DODGE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_dodge_float);
|
||||
break;
|
||||
case STRIP_BLEND_OVERLAY:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_overlay_float);
|
||||
break;
|
||||
case STRIP_BLEND_SOFT_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_softlight_float);
|
||||
break;
|
||||
case STRIP_BLEND_HARD_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hardlight_float);
|
||||
break;
|
||||
case STRIP_BLEND_PIN_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_pinlight_float);
|
||||
break;
|
||||
case STRIP_BLEND_LIN_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearlight_float);
|
||||
break;
|
||||
case STRIP_BLEND_VIVID_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_vividlight_float);
|
||||
break;
|
||||
case STRIP_BLEND_BLEND_COLOR:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_color_float);
|
||||
break;
|
||||
case STRIP_BLEND_HUE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hue_float);
|
||||
break;
|
||||
case STRIP_BLEND_SATURATION:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_saturation_float);
|
||||
break;
|
||||
case STRIP_BLEND_VALUE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_luminosity_float);
|
||||
break;
|
||||
case STRIP_BLEND_DIFFERENCE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_difference_float);
|
||||
break;
|
||||
case STRIP_BLEND_EXCLUSION:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_exclusion_float);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void do_blend_effect_byte(float fac,
|
||||
int64_t size,
|
||||
const uchar *rect1,
|
||||
const uchar *rect2,
|
||||
StripBlendMode btype,
|
||||
uchar *out)
|
||||
{
|
||||
switch (btype) {
|
||||
case STRIP_BLEND_ADD:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_add_byte);
|
||||
break;
|
||||
case STRIP_BLEND_SUB:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_sub_byte);
|
||||
break;
|
||||
case STRIP_BLEND_MUL:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_mul_byte);
|
||||
break;
|
||||
case STRIP_BLEND_DARKEN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_darken_byte);
|
||||
break;
|
||||
case STRIP_BLEND_COLOR_BURN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_burn_byte);
|
||||
break;
|
||||
case STRIP_BLEND_LINEAR_BURN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearburn_byte);
|
||||
break;
|
||||
case STRIP_BLEND_SCREEN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_screen_byte);
|
||||
break;
|
||||
case STRIP_BLEND_LIGHTEN:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_lighten_byte);
|
||||
break;
|
||||
case STRIP_BLEND_DODGE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_dodge_byte);
|
||||
break;
|
||||
case STRIP_BLEND_OVERLAY:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_overlay_byte);
|
||||
break;
|
||||
case STRIP_BLEND_SOFT_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_softlight_byte);
|
||||
break;
|
||||
case STRIP_BLEND_HARD_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hardlight_byte);
|
||||
break;
|
||||
case STRIP_BLEND_PIN_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_pinlight_byte);
|
||||
break;
|
||||
case STRIP_BLEND_LIN_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearlight_byte);
|
||||
break;
|
||||
case STRIP_BLEND_VIVID_LIGHT:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_vividlight_byte);
|
||||
break;
|
||||
case STRIP_BLEND_BLEND_COLOR:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_color_byte);
|
||||
break;
|
||||
case STRIP_BLEND_HUE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hue_byte);
|
||||
break;
|
||||
case STRIP_BLEND_SATURATION:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_saturation_byte);
|
||||
break;
|
||||
case STRIP_BLEND_VALUE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_luminosity_byte);
|
||||
break;
|
||||
case STRIP_BLEND_DIFFERENCE:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_difference_byte);
|
||||
break;
|
||||
case STRIP_BLEND_EXCLUSION:
|
||||
apply_blend_function(fac, size, rect1, rect2, out, blend_color_exclusion_byte);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct BlendModeEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
do_blend_effect_float(this->factor, size, src1, src2, this->blend_mode, dst);
|
||||
}
|
||||
else {
|
||||
do_blend_effect_byte(this->factor, size, src1, src2, this->blend_mode, dst);
|
||||
}
|
||||
}
|
||||
StripBlendMode blend_mode;
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_blend_mode_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip *strip,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxBlend", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
BlendModeEffectOp op;
|
||||
op.factor = fac;
|
||||
op.blend_mode = strip->blend_mode;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Color Mix Effect */
|
||||
|
||||
static void init_colormix_effect(Strip *strip)
|
||||
{
|
||||
ColorMixVars *data = MEM_new<ColorMixVars>("colormixvars");
|
||||
strip->effectdata = data;
|
||||
data->blend_effect = STRIP_BLEND_OVERLAY;
|
||||
data->factor = 1.0f;
|
||||
}
|
||||
|
||||
static void free_colormix_effect(Strip *strip, const bool /*do_id_user*/)
|
||||
{
|
||||
if (strip->effectdata) {
|
||||
MEM_delete(static_cast<ColorMixVars *>(strip->effectdata));
|
||||
strip->effectdata = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static SeqResult do_colormix_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip *strip,
|
||||
float /*timeline_frame*/,
|
||||
float /*fac*/,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxColorMix", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
const ColorMixVars *data = static_cast<const ColorMixVars *>(strip->effectdata);
|
||||
BlendModeEffectOp op;
|
||||
op.blend_mode = data->blend_effect;
|
||||
op.factor = data->factor;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
return dst;
|
||||
}
|
||||
|
||||
void blend_mode_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.execute = do_blend_mode_effect;
|
||||
rval.early_out = early_out_mul_input2;
|
||||
}
|
||||
|
||||
void color_mix_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_colormix_effect;
|
||||
rval.free = free_colormix_effect;
|
||||
rval.execute = do_colormix_effect;
|
||||
rval.early_out = early_out_mul_input2;
|
||||
}
|
||||
|
||||
void alpha_over_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_alpha_over_or_under;
|
||||
rval.execute = do_alphaover_effect;
|
||||
rval.early_out = early_out_mul_input1;
|
||||
}
|
||||
|
||||
void alpha_under_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_alpha_over_or_under;
|
||||
rval.execute = do_alphaunder_effect;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,204 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "COM_domain.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
#include "cache/compositor_cache.hh"
|
||||
#include "compositor.hh"
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
class CompositorEffectContext : public CompositorContext {
|
||||
bNodeTree *node_group_;
|
||||
|
||||
ImBuf *input_1_;
|
||||
ImBuf *input_2_;
|
||||
ImBuf *output_;
|
||||
float factor_;
|
||||
|
||||
public:
|
||||
CompositorEffectContext(compositor::StaticCacheManager &cache_manager,
|
||||
const RenderData &render_data,
|
||||
bNodeTree *node_tree,
|
||||
ImBuf *input_1,
|
||||
ImBuf *input_2,
|
||||
ImBuf *output,
|
||||
float factor,
|
||||
const Strip &strip)
|
||||
: CompositorContext(cache_manager, render_data, strip),
|
||||
node_group_(node_tree),
|
||||
input_1_(input_1),
|
||||
input_2_(input_2),
|
||||
output_(output),
|
||||
factor_(factor)
|
||||
{
|
||||
}
|
||||
|
||||
compositor::Domain get_compositing_domain() const override
|
||||
{
|
||||
return compositor::Domain(int2(this->output_->x, this->output_->y));
|
||||
}
|
||||
|
||||
void write_viewer(compositor::Result &viewer_result) override
|
||||
{
|
||||
write_viewer_impl(viewer_result, *this->output_);
|
||||
}
|
||||
|
||||
void evaluate()
|
||||
{
|
||||
using namespace compositor;
|
||||
const bNodeTree &node_group = *DEG_get_evaluated<bNodeTree>(render_data_.depsgraph,
|
||||
node_group_);
|
||||
const bke::DataBlockComputeContext compute_context(nullptr, this->get_scene().id);
|
||||
NodeGroupOperation node_group_operation(*this,
|
||||
node_group,
|
||||
this->needed_outputs(),
|
||||
node_group.active_viewer_key,
|
||||
bke::NODE_INSTANCE_KEY_BASE,
|
||||
compute_context);
|
||||
set_output_refcount(node_group, node_group_operation);
|
||||
|
||||
/* Map the inputs to the operation. */
|
||||
Vector<std::unique_ptr<Result>> inputs;
|
||||
int float_counter = 0;
|
||||
int color_counter = 0;
|
||||
for (const bNodeTreeInterfaceSocket *input_socket : node_group.interface_inputs()) {
|
||||
const bke::bNodeSocketType *typeinfo = input_socket->socket_typeinfo();
|
||||
Result *input_result = nullptr;
|
||||
if (typeinfo && typeinfo->type == SOCK_FLOAT && float_counter == 0) {
|
||||
/* First float input is factor. */
|
||||
input_result = new Result(this->create_result(ResultType::Float, ResultPrecision::Full));
|
||||
input_result->allocate_single_value();
|
||||
input_result->set_single_value(this->factor_);
|
||||
float_counter++;
|
||||
}
|
||||
else if (color_counter == 0 && this->input_1_) {
|
||||
/* First input image. */
|
||||
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
|
||||
create_result_from_input(*input_result, *this->input_1_);
|
||||
color_counter++;
|
||||
}
|
||||
else if (color_counter == 1 && this->input_2_) {
|
||||
/* Second input image. */
|
||||
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
|
||||
create_result_from_input(*input_result, *this->input_2_);
|
||||
color_counter++;
|
||||
}
|
||||
else {
|
||||
/* Unsupported sockets. */
|
||||
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
|
||||
node_group_operation.map_input_to_result(input_socket->identifier, input_result);
|
||||
inputs.append(std::unique_ptr<Result>(input_result));
|
||||
}
|
||||
|
||||
node_group_operation.evaluate();
|
||||
this->write_outputs(node_group, node_group_operation, *this->output_);
|
||||
}
|
||||
};
|
||||
|
||||
static SeqResult do_compositor_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip *strip,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxCompositor", ProfileCategory::Draw);
|
||||
const int x = context->rectx;
|
||||
const int y = context->recty;
|
||||
SeqResult out;
|
||||
out.image = IMB_allocImBuf(x, y, ImBufFlags::FloatData | ImBufFlags::UninitializedPixels);
|
||||
IMB_colormanagement_assign_float_colorspace(
|
||||
out.image, IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_SCENE_LINEAR));
|
||||
|
||||
CompositorEffectVars *data = static_cast<CompositorEffectVars *>(strip->effectdata);
|
||||
if (!data || !data->node_group) {
|
||||
IMB_rectfill(out.image, float4(0, 0, 0, 1));
|
||||
out.image->color_mode = ImColorMode::RGB;
|
||||
out.is_opaque_before_transform = true;
|
||||
}
|
||||
else {
|
||||
CompositorCache &com_cache = context->scene->ed->runtime->ensure_compositor_cache();
|
||||
CompositorEffectContext com_context(com_cache.get_cache_manager(),
|
||||
*context,
|
||||
data->node_group,
|
||||
src1.image,
|
||||
src2.image,
|
||||
out.image,
|
||||
fac,
|
||||
*strip);
|
||||
|
||||
if (com_context.use_gpu()) {
|
||||
com_context.set_gpu_supported(render_begin_gpu(*context));
|
||||
}
|
||||
com_cache.recreate_if_needed(
|
||||
com_context.use_gpu(), com_context.get_precision(), context->gpu_context);
|
||||
com_context.evaluate();
|
||||
com_context.cache_manager().reset();
|
||||
if (com_context.use_gpu()) {
|
||||
render_end_gpu(*context);
|
||||
}
|
||||
out.translation += com_context.get_result_translation();
|
||||
out.is_opaque_before_transform = !out.image->can_contain_alpha();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void init_compositor_effect(Strip *strip)
|
||||
{
|
||||
CompositorEffectVars *data = MEM_new<CompositorEffectVars>(__func__);
|
||||
strip->effectdata = data;
|
||||
}
|
||||
|
||||
static void free_compositor_effect(Strip *strip, const bool /*do_id_user*/)
|
||||
{
|
||||
if (strip->effectdata) {
|
||||
CompositorEffectVars *data = static_cast<CompositorEffectVars *>(strip->effectdata);
|
||||
MEM_delete(data);
|
||||
strip->effectdata = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static StripEarlyOut early_out_compositor(const Strip *strip, float /*fac*/)
|
||||
{
|
||||
/* No inputs: compositor generates the result. */
|
||||
if (strip->input1 == nullptr) {
|
||||
return StripEarlyOut::NoInput;
|
||||
}
|
||||
|
||||
/* One or two inputs: do the effect. */
|
||||
return StripEarlyOut::DoEffect;
|
||||
}
|
||||
|
||||
void compositor_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_compositor_effect;
|
||||
rval.free = free_compositor_effect;
|
||||
rval.execute = do_compositor_effect;
|
||||
rval.early_out = early_out_compositor;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,136 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct CrossEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
const float fac = this->factor;
|
||||
const float mfac = 1.0f - fac;
|
||||
const int ifac = int(256.0f * fac);
|
||||
const int imfac = 256 - ifac;
|
||||
for (int64_t idx = 0; idx < size; idx++) {
|
||||
if constexpr (std::is_same_v<T, uchar>) {
|
||||
dst[0] = (imfac * src1[0] + ifac * src2[0]) >> 8;
|
||||
dst[1] = (imfac * src1[1] + ifac * src2[1]) >> 8;
|
||||
dst[2] = (imfac * src1[2] + ifac * src2[2]) >> 8;
|
||||
dst[3] = (imfac * src1[3] + ifac * src2[3]) >> 8;
|
||||
}
|
||||
else {
|
||||
dst[0] = mfac * src1[0] + fac * src2[0];
|
||||
dst[1] = mfac * src1[1] + fac * src2[1];
|
||||
dst[2] = mfac * src1[2] + fac * src2[2];
|
||||
dst[3] = mfac * src1[3] + fac * src2[3];
|
||||
}
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_cross_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip * /*strip*/,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxCross", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
CrossEffectOp op;
|
||||
op.factor = fac;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
dst.is_opaque_before_transform = !src1.image->can_contain_alpha() &&
|
||||
!src2.image->can_contain_alpha();
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* One could argue that gamma cross should not be hardcoded to 2.0 gamma,
|
||||
* but instead either do proper input->linear conversion (often sRGB). Or
|
||||
* maybe not even that, but do interpolation in some perceptual color space
|
||||
* like OKLAB. But currently it is fixed to just 2.0 gamma. */
|
||||
|
||||
static float gammaCorrect(float c)
|
||||
{
|
||||
if (UNLIKELY(c < 0)) {
|
||||
return -(c * c);
|
||||
}
|
||||
return c * c;
|
||||
}
|
||||
|
||||
static float invGammaCorrect(float c)
|
||||
{
|
||||
return sqrtf_signed(c);
|
||||
}
|
||||
|
||||
struct GammaCrossEffectOp {
|
||||
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
|
||||
{
|
||||
const float fac = this->factor;
|
||||
const float mfac = 1.0f - fac;
|
||||
for (int64_t idx = 0; idx < size; idx++) {
|
||||
float4 col1 = load_premul_pixel(src1);
|
||||
float4 col2 = load_premul_pixel(src2);
|
||||
float4 col;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
col[c] = gammaCorrect(mfac * invGammaCorrect(col1[c]) + fac * invGammaCorrect(col2[c]));
|
||||
}
|
||||
store_premul_pixel(col, dst);
|
||||
src1 += 4;
|
||||
src2 += 4;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
float factor;
|
||||
};
|
||||
|
||||
static SeqResult do_gammacross_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip * /*strip*/,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &src1,
|
||||
const SeqResult &src2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxGammaCross", ProfileCategory::Draw);
|
||||
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
|
||||
GammaCrossEffectOp op;
|
||||
op.factor = fac;
|
||||
apply_effect_op(op, src1.image, src2.image, dst.image);
|
||||
dst.is_opaque_before_transform = !src1.image->can_contain_alpha() &&
|
||||
!src2.image->can_contain_alpha();
|
||||
return dst;
|
||||
}
|
||||
|
||||
void cross_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.execute = do_cross_effect;
|
||||
rval.early_out = early_out_fade;
|
||||
}
|
||||
|
||||
void gamma_cross_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.early_out = early_out_fade;
|
||||
rval.execute = do_gammacross_effect;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,227 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void init_gaussian_blur_effect(Strip *strip)
|
||||
{
|
||||
GaussianBlurVars *data = MEM_new<GaussianBlurVars>("gaussianblurvars");
|
||||
strip->effectdata = data;
|
||||
data->size_x = 9.0f;
|
||||
data->size_y = 9.0f;
|
||||
}
|
||||
|
||||
static void free_gaussian_blur_effect(Strip *strip, const bool /*do_id_user*/)
|
||||
{
|
||||
if (strip->effectdata) {
|
||||
GaussianBlurVars *data = static_cast<GaussianBlurVars *>(strip->effectdata);
|
||||
MEM_delete(data);
|
||||
strip->effectdata = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static StripEarlyOut early_out_gaussian_blur(const Strip *strip, float /*fac*/)
|
||||
{
|
||||
GaussianBlurVars *data = static_cast<GaussianBlurVars *>(strip->effectdata);
|
||||
if (data->size_x == 0.0f && data->size_y == 0) {
|
||||
return StripEarlyOut::UseInput1;
|
||||
}
|
||||
return StripEarlyOut::DoEffect;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void gaussian_blur_x(const Span<float> gaussian,
|
||||
int half_size,
|
||||
int start_line,
|
||||
int width,
|
||||
int height,
|
||||
int /*frame_height*/,
|
||||
const T *rect,
|
||||
T *dst)
|
||||
{
|
||||
dst += int64_t(start_line) * width * 4;
|
||||
for (int y = start_line; y < start_line + height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
float4 accum(0.0f);
|
||||
float accum_weight = 0.0f;
|
||||
|
||||
int xmin = math::max(x - half_size, 0);
|
||||
int xmax = math::min(x + half_size, width - 1);
|
||||
for (int nx = xmin, index = (xmin - x) + half_size; nx <= xmax; nx++, index++) {
|
||||
float weight = gaussian[index];
|
||||
int offset = (y * width + nx) * 4;
|
||||
accum += float4(rect + offset) * weight;
|
||||
accum_weight += weight;
|
||||
}
|
||||
accum *= (1.0f / accum_weight);
|
||||
if constexpr (math::is_math_float_type<T>) {
|
||||
dst[0] = accum[0];
|
||||
dst[1] = accum[1];
|
||||
dst[2] = accum[2];
|
||||
dst[3] = accum[3];
|
||||
}
|
||||
else {
|
||||
dst[0] = accum[0] + 0.5f;
|
||||
dst[1] = accum[1] + 0.5f;
|
||||
dst[2] = accum[2] + 0.5f;
|
||||
dst[3] = accum[3] + 0.5f;
|
||||
}
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void gaussian_blur_y(const Span<float> gaussian,
|
||||
int half_size,
|
||||
int start_line,
|
||||
int width,
|
||||
int height,
|
||||
int frame_height,
|
||||
const T *rect,
|
||||
T *dst)
|
||||
{
|
||||
dst += int64_t(start_line) * width * 4;
|
||||
for (int y = start_line; y < start_line + height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
float4 accum(0.0f);
|
||||
float accum_weight = 0.0f;
|
||||
int ymin = math::max(y - half_size, 0);
|
||||
int ymax = math::min(y + half_size, frame_height - 1);
|
||||
for (int ny = ymin, index = (ymin - y) + half_size; ny <= ymax; ny++, index++) {
|
||||
float weight = gaussian[index];
|
||||
int offset = (ny * width + x) * 4;
|
||||
accum += float4(rect + offset) * weight;
|
||||
accum_weight += weight;
|
||||
}
|
||||
accum *= (1.0f / accum_weight);
|
||||
if constexpr (math::is_math_float_type<T>) {
|
||||
dst[0] = accum[0];
|
||||
dst[1] = accum[1];
|
||||
dst[2] = accum[2];
|
||||
dst[3] = accum[3];
|
||||
}
|
||||
else {
|
||||
dst[0] = accum[0] + 0.5f;
|
||||
dst[1] = accum[1] + 0.5f;
|
||||
dst[2] = accum[2] + 0.5f;
|
||||
dst[3] = accum[3] + 0.5f;
|
||||
}
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SeqResult do_gaussian_blur_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip *strip,
|
||||
float /*timeline_frame*/,
|
||||
float /*fac*/,
|
||||
const SeqResult &ibuf1,
|
||||
const SeqResult & /*ibuf2*/)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxBlur", ProfileCategory::Draw);
|
||||
/* Create blur kernel weights. */
|
||||
const GaussianBlurVars *data = static_cast<const GaussianBlurVars *>(strip->effectdata);
|
||||
|
||||
const float size_scale = seq::get_render_scale_factor(*context);
|
||||
const float size_x = data->size_x * size_scale;
|
||||
const float size_y = data->size_y * size_scale;
|
||||
|
||||
const int half_size_x = int(size_x + 0.5f);
|
||||
const int half_size_y = int(size_y + 0.5f);
|
||||
Array<float> gaussian_x = make_gaussian_blur_kernel(size_x, half_size_x);
|
||||
Array<float> gaussian_y = make_gaussian_blur_kernel(size_y, half_size_y);
|
||||
|
||||
const int width = context->rectx;
|
||||
const int height = context->recty;
|
||||
const bool is_float = ibuf1.image->float_data();
|
||||
|
||||
/* Horizontal blur: create output, blur ibuf1 into it. */
|
||||
SeqResult out = prepare_effect_imbufs(context, ibuf1, {});
|
||||
threading::parallel_for(IndexRange(context->recty), 32, [&](const IndexRange y_range) {
|
||||
const int y_first = y_range.first();
|
||||
const int y_size = y_range.size();
|
||||
if (is_float) {
|
||||
gaussian_blur_x(gaussian_x,
|
||||
half_size_x,
|
||||
y_first,
|
||||
width,
|
||||
y_size,
|
||||
height,
|
||||
ibuf1.image->float_data(),
|
||||
out.image->float_data_for_write());
|
||||
}
|
||||
else {
|
||||
gaussian_blur_x(gaussian_x,
|
||||
half_size_x,
|
||||
y_first,
|
||||
width,
|
||||
y_size,
|
||||
height,
|
||||
ibuf1.image->byte_data(),
|
||||
out.image->byte_data_for_write());
|
||||
}
|
||||
});
|
||||
|
||||
/* Vertical blur: create output, blur previous output into it. */
|
||||
SeqResult vin = out;
|
||||
out = prepare_effect_imbufs(context, vin, {});
|
||||
threading::parallel_for(IndexRange(context->recty), 32, [&](const IndexRange y_range) {
|
||||
const int y_first = y_range.first();
|
||||
const int y_size = y_range.size();
|
||||
if (is_float) {
|
||||
gaussian_blur_y(gaussian_y,
|
||||
half_size_y,
|
||||
y_first,
|
||||
width,
|
||||
y_size,
|
||||
height,
|
||||
vin.image->float_data(),
|
||||
out.image->float_data_for_write());
|
||||
}
|
||||
else {
|
||||
gaussian_blur_y(gaussian_y,
|
||||
half_size_y,
|
||||
y_first,
|
||||
width,
|
||||
y_size,
|
||||
height,
|
||||
vin.image->byte_data(),
|
||||
out.image->byte_data_for_write());
|
||||
}
|
||||
});
|
||||
|
||||
/* Free the first output. */
|
||||
IMB_freeImBuf(vin.image);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void gaussian_blur_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_gaussian_blur_effect;
|
||||
rval.free = free_gaussian_blur_effect;
|
||||
rval.early_out = early_out_gaussian_blur;
|
||||
rval.execute = do_gaussian_blur_effect;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,246 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void glow_blur_bitmap(
|
||||
const float4 *src, float4 *map, int width, int height, float blur, int quality)
|
||||
{
|
||||
/* If we're not really blurring, bail out */
|
||||
if (blur <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If result would be no blurring, early out. */
|
||||
const int halfWidth = ((quality + 1) * blur);
|
||||
if (halfWidth == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array<float4> temp(width * height);
|
||||
|
||||
/* Initialize the gaussian filter.
|
||||
* TODO: use code from #filter_kernel_value. */
|
||||
Array<float> filter(halfWidth * 2);
|
||||
const float k = -1.0f / (2.0f * float(M_PI) * blur * blur);
|
||||
float weight = 0;
|
||||
for (int ix = 0; ix < halfWidth; ix++) {
|
||||
weight = exp(k * (ix * ix));
|
||||
filter[halfWidth - ix] = weight;
|
||||
filter[halfWidth + ix] = weight;
|
||||
}
|
||||
filter[0] = weight;
|
||||
/* Normalize the array */
|
||||
float fval = 0;
|
||||
for (int ix = 0; ix < halfWidth * 2; ix++) {
|
||||
fval += filter[ix];
|
||||
}
|
||||
for (int ix = 0; ix < halfWidth * 2; ix++) {
|
||||
filter[ix] /= fval;
|
||||
}
|
||||
|
||||
/* Blur the rows: read map, write temp */
|
||||
threading::parallel_for(IndexRange(height), 32, [&](const IndexRange y_range) {
|
||||
for (const int y : y_range) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
float4 curColor = float4(0.0f);
|
||||
int xmin = math::max(x - halfWidth, 0);
|
||||
int xmax = math::min(x + halfWidth, width);
|
||||
for (int nx = xmin, index = (xmin - x) + halfWidth; nx < xmax; nx++, index++) {
|
||||
curColor += map[nx + y * width] * filter[index];
|
||||
}
|
||||
temp[x + y * width] = curColor;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Blur the columns: read temp, write map */
|
||||
threading::parallel_for(IndexRange(width), 32, [&](const IndexRange x_range) {
|
||||
const float4 one = float4(1.0f);
|
||||
for (const int x : x_range) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
float4 curColor = float4(0.0f);
|
||||
int ymin = math::max(y - halfWidth, 0);
|
||||
int ymax = math::min(y + halfWidth, height);
|
||||
for (int ny = ymin, index = (ymin - y) + halfWidth; ny < ymax; ny++, index++) {
|
||||
curColor += temp[x + ny * width] * filter[index];
|
||||
}
|
||||
if (src != nullptr) {
|
||||
curColor = math::min(one, src[x + y * width] + curColor);
|
||||
}
|
||||
map[x + y * width] = curColor;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void blur_isolate_highlights(const float4 *in,
|
||||
float4 *out,
|
||||
int width,
|
||||
int height,
|
||||
float threshold,
|
||||
float boost,
|
||||
float clamp)
|
||||
{
|
||||
threading::parallel_for(IndexRange(height), 64, [&](const IndexRange y_range) {
|
||||
const float4 clampv = float4(clamp);
|
||||
for (const int y : y_range) {
|
||||
int index = y * width;
|
||||
for (int x = 0; x < width; x++, index++) {
|
||||
|
||||
/* Isolate the intensity */
|
||||
float intensity = (in[index].x + in[index].y + in[index].z - threshold);
|
||||
float4 val;
|
||||
if (intensity > 0) {
|
||||
val = math::min(clampv, in[index] * (boost * intensity));
|
||||
}
|
||||
else {
|
||||
val = float4(0.0f);
|
||||
}
|
||||
out[index] = val;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void init_glow_effect(Strip *strip)
|
||||
{
|
||||
GlowVars *data = MEM_new<GlowVars>("glowvars");
|
||||
strip->effectdata = data;
|
||||
data->fMini = 0.25f;
|
||||
data->fClamp = 1.0f;
|
||||
data->fBoost = 0.5f;
|
||||
data->dDist = 3.0f;
|
||||
data->dQuality = 3;
|
||||
data->bNoComp = 0;
|
||||
}
|
||||
|
||||
static void free_glow_effect(Strip *strip, const bool /*do_id_user*/)
|
||||
{
|
||||
if (strip->effectdata) {
|
||||
GlowVars *data = static_cast<GlowVars *>(strip->effectdata);
|
||||
MEM_delete(data);
|
||||
strip->effectdata = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void do_glow_effect_byte(Strip *strip,
|
||||
int render_size,
|
||||
float fac,
|
||||
int x,
|
||||
int y,
|
||||
const uchar *rect1,
|
||||
const uchar * /*rect2*/,
|
||||
uchar *out)
|
||||
{
|
||||
GlowVars *glow = static_cast<GlowVars *>(strip->effectdata);
|
||||
|
||||
Array<float4> inbuf(x * y);
|
||||
Array<float4> outbuf(x * y);
|
||||
|
||||
IMB_colormanagement_transform_byte_to_float(*inbuf.data(), rect1, x, y, 4, "sRGB", "sRGB");
|
||||
|
||||
blur_isolate_highlights(
|
||||
inbuf.data(), outbuf.data(), x, y, glow->fMini * 3.0f, glow->fBoost * fac, glow->fClamp);
|
||||
glow_blur_bitmap(glow->bNoComp ? nullptr : inbuf.data(),
|
||||
outbuf.data(),
|
||||
x,
|
||||
y,
|
||||
glow->dDist * (render_size / 100.0f),
|
||||
glow->dQuality);
|
||||
|
||||
threading::parallel_for(IndexRange(y), 64, [&](const IndexRange y_range) {
|
||||
size_t offset = y_range.first() * x;
|
||||
IMB_buffer_byte_from_float(
|
||||
out + offset * 4, *(outbuf.data() + offset), 4, 0.0f, true, x, y_range.size(), x);
|
||||
});
|
||||
}
|
||||
|
||||
static void do_glow_effect_float(Strip *strip,
|
||||
int render_size,
|
||||
float fac,
|
||||
int x,
|
||||
int y,
|
||||
const float *rect1,
|
||||
const float * /*rect2*/,
|
||||
float *out)
|
||||
{
|
||||
float4 *outbuf = reinterpret_cast<float4 *>(out);
|
||||
const float4 *inbuf = reinterpret_cast<const float4 *>(rect1);
|
||||
GlowVars *glow = static_cast<GlowVars *>(strip->effectdata);
|
||||
|
||||
blur_isolate_highlights(
|
||||
inbuf, outbuf, x, y, glow->fMini * 3.0f, glow->fBoost * fac, glow->fClamp);
|
||||
glow_blur_bitmap(glow->bNoComp ? nullptr : inbuf,
|
||||
outbuf,
|
||||
x,
|
||||
y,
|
||||
glow->dDist * (render_size / 100.0f),
|
||||
glow->dQuality);
|
||||
}
|
||||
|
||||
static SeqResult do_glow_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip *strip,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &ibuf1,
|
||||
const SeqResult & /*ibuf2*/)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxGlow", ProfileCategory::Draw);
|
||||
SeqResult out = prepare_effect_imbufs(context, ibuf1, {});
|
||||
|
||||
int render_size = 100 * context->rectx / context->scene->r.xsch;
|
||||
|
||||
if (out.image->float_data()) {
|
||||
do_glow_effect_float(strip,
|
||||
render_size,
|
||||
fac,
|
||||
context->rectx,
|
||||
context->recty,
|
||||
ibuf1.image->float_data(),
|
||||
nullptr,
|
||||
out.image->float_data_for_write());
|
||||
}
|
||||
else {
|
||||
do_glow_effect_byte(strip,
|
||||
render_size,
|
||||
fac,
|
||||
context->rectx,
|
||||
context->recty,
|
||||
ibuf1.image->byte_data(),
|
||||
nullptr,
|
||||
out.image->byte_data_for_write());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void glow_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_glow_effect;
|
||||
rval.free = free_glow_effect;
|
||||
rval.execute = do_glow_effect;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,65 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static StripEarlyOut early_out_multicam(const Strip * /*strip*/, float /*fac*/)
|
||||
{
|
||||
return StripEarlyOut::NoInput;
|
||||
}
|
||||
|
||||
static SeqResult do_multicam(const RenderData *context,
|
||||
SeqRenderState *state,
|
||||
Strip *strip,
|
||||
float timeline_frame,
|
||||
float /*fac*/,
|
||||
const SeqResult & /*ibuf1*/,
|
||||
const SeqResult & /*ibuf2*/)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxMultiCam", ProfileCategory::Draw);
|
||||
if (strip->multicam_source == 0 || strip->multicam_source >= strip->channel) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Editing *ed = context->scene->ed;
|
||||
if (!ed || state->strips_in_progress.contains(strip)) {
|
||||
return {};
|
||||
}
|
||||
ListBaseT<Strip> *seqbasep = get_seqbase_by_strip(context->scene, strip);
|
||||
ListBaseT<SeqTimelineChannel> *channels = get_channels_by_strip(ed, strip);
|
||||
if (!seqbasep) {
|
||||
return {};
|
||||
}
|
||||
|
||||
state->strips_in_progress.add(strip);
|
||||
SeqResult out = seq_render_give_ibuf_seqbase(
|
||||
context, state, timeline_frame, strip->multicam_source, channels, seqbasep);
|
||||
state->strips_in_progress.remove(strip);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void multi_camera_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.early_out = early_out_multicam;
|
||||
rval.execute = do_multicam;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,85 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void init_solid_color(Strip *strip)
|
||||
{
|
||||
SolidColorVars *data = MEM_new<SolidColorVars>("solidcolor");
|
||||
strip->effectdata = data;
|
||||
data->col[0] = data->col[1] = data->col[2] = 0.5;
|
||||
data->width = data->height = 1;
|
||||
}
|
||||
|
||||
static void free_solid_color(Strip *strip, const bool /*do_id_user*/)
|
||||
{
|
||||
if (strip->effectdata) {
|
||||
SolidColorVars *data = static_cast<SolidColorVars *>(strip->effectdata);
|
||||
MEM_delete(data);
|
||||
strip->effectdata = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static StripEarlyOut early_out_color(const Strip * /*strip*/, float /*fac*/)
|
||||
{
|
||||
return StripEarlyOut::NoInput;
|
||||
}
|
||||
|
||||
static SeqResult do_solid_color(const RenderData * /*context*/,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip *strip,
|
||||
float /*timeline_frame*/,
|
||||
float /*fac*/,
|
||||
const SeqResult & /*ibuf1*/,
|
||||
const SeqResult & /*ibuf2*/)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxColor", ProfileCategory::Draw);
|
||||
SeqResult out;
|
||||
|
||||
const SolidColorVars *cv = static_cast<const SolidColorVars *>(strip->effectdata);
|
||||
out.image = IMB_allocImBuf(cv->width, cv->height, ImBufFlags::ByteData);
|
||||
|
||||
uchar color[4];
|
||||
rgb_float_to_uchar(color, cv->col);
|
||||
color[3] = 255;
|
||||
|
||||
uchar *byte_data = out.image->byte_data_for_write();
|
||||
threading::parallel_for(IndexRange(out.image->y), 64, [&](const IndexRange y_range) {
|
||||
uchar *dst = byte_data + y_range.first() * out.image->x * 4;
|
||||
uchar *dst_end = dst + y_range.size() * out.image->x * 4;
|
||||
while (dst < dst_end) {
|
||||
memcpy(dst, color, sizeof(color));
|
||||
dst += 4;
|
||||
}
|
||||
});
|
||||
|
||||
out.image->color_mode = ImColorMode::RGB;
|
||||
out.is_opaque_before_transform = true;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void solid_color_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_solid_color;
|
||||
rval.free = free_solid_color;
|
||||
rval.early_out = early_out_color;
|
||||
rval.execute = do_solid_color;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,213 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_fcurve.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_time.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void init_speed_effect(Strip *strip)
|
||||
{
|
||||
SpeedControlVars *data = MEM_new<SpeedControlVars>("speedcontrolvars");
|
||||
strip->effectdata = data;
|
||||
data->speed_control_type = SEQ_SPEED_STRETCH;
|
||||
data->speed_fader = 1.0f;
|
||||
data->speed_fader_length = 0.0f;
|
||||
data->speed_fader_frame_number = 0.0f;
|
||||
}
|
||||
|
||||
static void free_speed_effect(Strip *strip, const bool /*do_id_user*/)
|
||||
{
|
||||
if (strip->effectdata) {
|
||||
SpeedControlVars *v = static_cast<SpeedControlVars *>(strip->effectdata);
|
||||
if (v->frameMap) {
|
||||
MEM_delete(v->frameMap);
|
||||
}
|
||||
MEM_delete(v);
|
||||
strip->effectdata = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_speed_effect(Strip *dst, const Strip *src, const int /*flag*/)
|
||||
{
|
||||
SpeedControlVars *v = MEM_dupalloc(static_cast<SpeedControlVars *>(src->effectdata));
|
||||
v->frameMap = nullptr;
|
||||
dst->effectdata = v;
|
||||
}
|
||||
|
||||
static StripEarlyOut early_out_speed(const Strip * /*strip*/, float /*fac*/)
|
||||
{
|
||||
return StripEarlyOut::DoEffect;
|
||||
}
|
||||
|
||||
static FCurve *strip_effect_speed_speed_factor_curve_get(Scene *scene, Strip *strip)
|
||||
{
|
||||
return id_data_find_fcurve(&scene->id, strip, RNA_Strip, "speed_factor", 0, nullptr);
|
||||
}
|
||||
|
||||
void strip_effect_speed_rebuild_map(Scene *scene, Strip *strip)
|
||||
{
|
||||
const int effect_strip_length = strip->right_handle(scene) - strip->left_handle();
|
||||
|
||||
if ((strip->input1 == nullptr) || (effect_strip_length < 1)) {
|
||||
return; /* Make COVERITY happy and check for (CID 598) input strip. */
|
||||
}
|
||||
|
||||
const FCurve *fcu = strip_effect_speed_speed_factor_curve_get(scene, strip);
|
||||
if (fcu == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
SpeedControlVars *v = static_cast<SpeedControlVars *>(strip->effectdata);
|
||||
if (v->frameMap) {
|
||||
MEM_delete(v->frameMap);
|
||||
}
|
||||
|
||||
v->frameMap = MEM_new_array_uninitialized<float>(size_t(effect_strip_length), __func__);
|
||||
v->frameMap[0] = 0.0f;
|
||||
|
||||
float target_frame = 0;
|
||||
for (int frame_index = 1; frame_index < effect_strip_length; frame_index++) {
|
||||
target_frame += evaluate_fcurve(fcu, strip->left_handle() + frame_index);
|
||||
const int target_frame_max = strip->input1->length(scene);
|
||||
CLAMP(target_frame, 0, target_frame_max);
|
||||
v->frameMap[frame_index] = target_frame;
|
||||
}
|
||||
}
|
||||
|
||||
static void strip_effect_speed_frame_map_ensure(Scene *scene, Strip *strip)
|
||||
{
|
||||
const SpeedControlVars *v = static_cast<SpeedControlVars *>(strip->effectdata);
|
||||
if (v->frameMap != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
strip_effect_speed_rebuild_map(scene, strip);
|
||||
}
|
||||
|
||||
float strip_speed_effect_target_frame_get(Scene *scene,
|
||||
Strip *strip_speed,
|
||||
float timeline_frame,
|
||||
int input)
|
||||
{
|
||||
if (strip_speed->input1 == nullptr) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
strip_effect_handle_get(strip_speed); /* Ensure, that data are initialized. */
|
||||
int frame_index = round_fl_to_int(give_frame_index(scene, strip_speed, timeline_frame));
|
||||
SpeedControlVars *s = static_cast<SpeedControlVars *>(strip_speed->effectdata);
|
||||
const Strip *source = strip_speed->input1;
|
||||
|
||||
float target_frame = 0.0f;
|
||||
switch (s->speed_control_type) {
|
||||
case SEQ_SPEED_STRETCH: {
|
||||
/* Only right handle controls effect speed! */
|
||||
const float target_content_length = source->length(scene) - source->startofs;
|
||||
const float speed_effetct_length = strip_speed->right_handle(scene) -
|
||||
strip_speed->left_handle();
|
||||
const float ratio = frame_index / speed_effetct_length;
|
||||
target_frame = target_content_length * ratio;
|
||||
break;
|
||||
}
|
||||
case SEQ_SPEED_MULTIPLY: {
|
||||
const FCurve *fcu = strip_effect_speed_speed_factor_curve_get(scene, strip_speed);
|
||||
if (fcu != nullptr) {
|
||||
strip_effect_speed_frame_map_ensure(scene, strip_speed);
|
||||
target_frame = s->frameMap[frame_index];
|
||||
}
|
||||
else {
|
||||
target_frame = frame_index * s->speed_fader;
|
||||
if (s->speed_fader < 0) {
|
||||
/* Treat `target_frame` as a negative offset from the last frame of the strip. */
|
||||
target_frame += source->length(scene);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SEQ_SPEED_LENGTH:
|
||||
target_frame = source->length(scene) * (s->speed_fader_length / 100.0f);
|
||||
break;
|
||||
case SEQ_SPEED_FRAME_NUMBER:
|
||||
target_frame = s->speed_fader_frame_number;
|
||||
break;
|
||||
}
|
||||
|
||||
CLAMP(target_frame, 0, source->length(scene));
|
||||
target_frame += strip_speed->start;
|
||||
|
||||
/* No interpolation. */
|
||||
if ((s->flags & SEQ_SPEED_USE_INTERPOLATION) == 0) {
|
||||
return target_frame;
|
||||
}
|
||||
|
||||
/* Interpolation is used, switch between current and next frame based on which input is
|
||||
* requested. */
|
||||
return input == 0 ? target_frame : ceil(target_frame);
|
||||
}
|
||||
|
||||
static float speed_effect_interpolation_ratio_get(Scene *scene,
|
||||
Strip *strip_speed,
|
||||
float timeline_frame)
|
||||
{
|
||||
const float target_frame = strip_speed_effect_target_frame_get(
|
||||
scene, strip_speed, timeline_frame, 0);
|
||||
return target_frame - floor(target_frame);
|
||||
}
|
||||
|
||||
static SeqResult do_speed_effect(const RenderData *context,
|
||||
SeqRenderState *state,
|
||||
Strip *strip,
|
||||
float timeline_frame,
|
||||
float fac,
|
||||
const SeqResult &ibuf1,
|
||||
const SeqResult &ibuf2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxSpeed", ProfileCategory::Draw);
|
||||
SeqResult out;
|
||||
|
||||
const SpeedControlVars *s = static_cast<SpeedControlVars *>(strip->effectdata);
|
||||
EffectHandle cross_effect = effect_handle_get(STRIP_TYPE_CROSS);
|
||||
|
||||
if (s->flags & SEQ_SPEED_USE_INTERPOLATION) {
|
||||
fac = speed_effect_interpolation_ratio_get(context->scene, strip, timeline_frame);
|
||||
/* Current frame is ibuf1, next frame is ibuf2. */
|
||||
out = cross_effect.execute(context, state, nullptr, timeline_frame, fac, ibuf1, ibuf2);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* No interpolation. */
|
||||
out.image = IMB_dupImBuf(ibuf1.image);
|
||||
out.is_opaque_before_transform = !ibuf1.image->can_contain_alpha();
|
||||
return out;
|
||||
}
|
||||
|
||||
void speed_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_speed_effect;
|
||||
rval.free = free_speed_effect;
|
||||
rval.copy = copy_speed_effect;
|
||||
rval.execute = do_speed_effect;
|
||||
rval.early_out = early_out_speed;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
1136
blender-5.2.0/source/blender/sequencer/intern/effects/effect_text.cc
Normal file
1136
blender-5.2.0/source/blender/sequencer/intern/effects/effect_text.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,253 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct WipeData {
|
||||
WipeData(const WipeVars *wipe, int width, int height, float fac)
|
||||
{
|
||||
this->type = wipe->wipetype;
|
||||
this->forward = wipe->forward != 0;
|
||||
this->size = float2(width, height);
|
||||
|
||||
if (this->type == SEQ_WIPE_SINGLE) {
|
||||
/* Position that the wipe line goes through: moves along
|
||||
* the image diagonal. The other diagonal when angle is negative. */
|
||||
this->pos = this->size * (this->forward ? fac : (1.0f - fac));
|
||||
if (wipe->angle < 0.0f) {
|
||||
this->pos.x = this->size.x - this->pos.x;
|
||||
}
|
||||
}
|
||||
if (this->type == SEQ_WIPE_DOUBLE) {
|
||||
/* For double blend, position goes from center of screen
|
||||
* along the diagonal. The other blend line position will be
|
||||
* a mirror of it. */
|
||||
float2 offset = this->size * (this->forward ? (1.0f - fac) : fac) * 0.5f;
|
||||
if (wipe->angle < 0.0f) {
|
||||
offset.x = -offset.x;
|
||||
}
|
||||
this->pos = this->size * 0.5f + offset;
|
||||
}
|
||||
|
||||
/* Line direction: (cos(a), sin(a)). Perpendicular: (-sin(a), cos(a)).
|
||||
* Angle is negative to match previous behavior. */
|
||||
this->normal.x = -sinf(-wipe->angle);
|
||||
this->normal.y = cosf(-wipe->angle);
|
||||
|
||||
/* Blend zone width. */
|
||||
float blend_width = wipe->edgeWidth * ((width + height) / 2.0f);
|
||||
if (ELEM(this->type, SEQ_WIPE_DOUBLE, SEQ_WIPE_IRIS)) {
|
||||
blend_width *= 0.5f;
|
||||
}
|
||||
/* For single/double wipes, make sure the blend zone goes to zero at start & end
|
||||
* of transition. */
|
||||
if (ELEM(this->type, SEQ_WIPE_SINGLE, SEQ_WIPE_DOUBLE)) {
|
||||
blend_width = std::min(blend_width, fac * this->size.y);
|
||||
blend_width = std::min(blend_width, this->size.y - fac * this->size.y);
|
||||
}
|
||||
this->blend_width_inv = math::safe_rcp(blend_width);
|
||||
|
||||
if (this->type == SEQ_WIPE_IRIS) {
|
||||
/* Distance to Iris circle at current factor. */
|
||||
float2 iris = this->size * 0.5f * (this->forward ? (1.0f - fac) : fac);
|
||||
this->iris_dist = math::length(iris);
|
||||
}
|
||||
|
||||
if (this->type == SEQ_WIPE_CLOCK) {
|
||||
float angle_cur = 2.0f * float(M_PI) * (this->forward ? (1.0f - fac) : fac);
|
||||
float angle_width = wipe->edgeWidth * float(M_PI);
|
||||
float delta_neg = angle_width * (this->forward ? fac : (1.0f - fac));
|
||||
float delta_pos = angle_width * (this->forward ? (1.0f - fac) : fac);
|
||||
this->clock_angles.x = std::max(angle_cur - delta_neg, 0.0f);
|
||||
this->clock_angles.y = std::min(angle_cur + delta_pos, 2.0f * float(M_PI));
|
||||
this->clock_angle_inv_dif = math::safe_rcp(this->clock_angles.y - this->clock_angles.x);
|
||||
}
|
||||
}
|
||||
|
||||
float2 size; /* Image size. */
|
||||
float2 pos; /* Position that wipe line goes through. */
|
||||
float2 normal; /* Normal vector to single/double wipe line. */
|
||||
float blend_width_inv = 0.0f;
|
||||
float iris_dist = 0.0f;
|
||||
float2 clock_angles; /* Min, max clock angles at current factor. */
|
||||
float clock_angle_inv_dif = 0.0f;
|
||||
eEffectWipeType type;
|
||||
bool forward = false;
|
||||
};
|
||||
|
||||
static float calc_wipe_band(float dist, float inv_width)
|
||||
{
|
||||
if (inv_width == 0.0f) {
|
||||
return dist < 0.0f ? 0.0f : 1.0f;
|
||||
}
|
||||
return dist * inv_width + 0.5f;
|
||||
}
|
||||
|
||||
static float calc_wipe_blend(const WipeData *data, int x, int y)
|
||||
{
|
||||
float output = 0.0f;
|
||||
switch (data->type) {
|
||||
case SEQ_WIPE_SINGLE: {
|
||||
/* Distance to line: dot(pixel_pos - line_pos, line_normal). */
|
||||
float dist = math::dot(float2(x, y) - data->pos, data->normal);
|
||||
output = calc_wipe_band(dist, data->blend_width_inv);
|
||||
} break;
|
||||
|
||||
case SEQ_WIPE_DOUBLE: {
|
||||
/* Distance to line: dot(pixel_pos - line_pos, line_normal).
|
||||
* For double wipe, we have two lines to calculate the distance to. */
|
||||
float2 pos1 = data->pos;
|
||||
float2 pos2 = data->size - data->pos;
|
||||
float dist1 = math::dot(float2(x, y) - pos1, -data->normal);
|
||||
float dist2 = math::dot(float2(x, y) - pos2, data->normal);
|
||||
float dist = std::min(dist1, dist2);
|
||||
output = calc_wipe_band(dist, data->blend_width_inv);
|
||||
} break;
|
||||
|
||||
case SEQ_WIPE_CLOCK: {
|
||||
float2 offset = float2(x, y) - data->size * 0.5f;
|
||||
if (math::length_squared(offset) < 1.0e-3f) {
|
||||
output = 0.0f;
|
||||
}
|
||||
else {
|
||||
float angle;
|
||||
angle = atan2f(offset.y, offset.x);
|
||||
if (angle < 0.0f) {
|
||||
angle += 2.0f * float(M_PI);
|
||||
}
|
||||
if (angle < data->clock_angles.x) {
|
||||
output = 1;
|
||||
}
|
||||
else if (angle > data->clock_angles.y) {
|
||||
output = 0;
|
||||
}
|
||||
else {
|
||||
output = (data->clock_angles.y - angle) * data->clock_angle_inv_dif;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case SEQ_WIPE_IRIS: {
|
||||
float dist = math::distance(float2(x, y), data->size * 0.5f);
|
||||
output = calc_wipe_band(data->iris_dist - dist, data->blend_width_inv);
|
||||
} break;
|
||||
}
|
||||
if (!data->forward) {
|
||||
output = 1.0f - output;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static void init_wipe_effect(Strip *strip)
|
||||
{
|
||||
strip->effectdata = MEM_new<WipeVars>("wipevars");
|
||||
}
|
||||
|
||||
static void free_wipe_effect(Strip *strip, const bool /*do_id_user*/)
|
||||
{
|
||||
if (strip->effectdata) {
|
||||
MEM_delete(static_cast<WipeVars *>(strip->effectdata));
|
||||
strip->effectdata = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void do_wipe_effect(
|
||||
const Strip *strip, float fac, int width, int height, const T *rect1, const T *rect2, T *out)
|
||||
{
|
||||
const WipeVars *wipe = static_cast<const WipeVars *>(strip->effectdata);
|
||||
|
||||
const WipeData data(wipe, width, height, fac);
|
||||
|
||||
threading::parallel_for(IndexRange(height), 64, [&](const IndexRange y_range) {
|
||||
const T *cp1 = rect1 + y_range.first() * width * 4;
|
||||
const T *cp2 = rect2 + y_range.first() * width * 4;
|
||||
T *rt = out + y_range.first() * width * 4;
|
||||
for (const int y : y_range) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
float blend = calc_wipe_blend(&data, x, y);
|
||||
if (blend <= 0.0f) {
|
||||
memcpy(rt, cp2, sizeof(T) * 4);
|
||||
}
|
||||
else if (blend >= 1.0f) {
|
||||
memcpy(rt, cp1, sizeof(T) * 4);
|
||||
}
|
||||
else {
|
||||
float4 col1 = load_premul_pixel(cp1);
|
||||
float4 col2 = load_premul_pixel(cp2);
|
||||
float4 col = col1 * blend + col2 * (1.0f - blend);
|
||||
store_premul_pixel(col, rt);
|
||||
}
|
||||
|
||||
rt += 4;
|
||||
cp1 += 4;
|
||||
cp2 += 4;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static SeqResult do_wipe_effect(const RenderData *context,
|
||||
SeqRenderState * /*state*/,
|
||||
Strip *strip,
|
||||
float /*timeline_frame*/,
|
||||
float fac,
|
||||
const SeqResult &ibuf1,
|
||||
const SeqResult &ibuf2)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxWipe", ProfileCategory::Draw);
|
||||
SeqResult out = prepare_effect_imbufs(context, ibuf1, ibuf2);
|
||||
|
||||
if (out.image->float_data()) {
|
||||
do_wipe_effect(strip,
|
||||
fac,
|
||||
context->rectx,
|
||||
context->recty,
|
||||
ibuf1.image->float_data(),
|
||||
ibuf2.image->float_data(),
|
||||
out.image->float_data_for_write());
|
||||
}
|
||||
else {
|
||||
do_wipe_effect(strip,
|
||||
fac,
|
||||
context->rectx,
|
||||
context->recty,
|
||||
ibuf1.image->byte_data(),
|
||||
ibuf2.image->byte_data(),
|
||||
out.image->byte_data_for_write());
|
||||
}
|
||||
|
||||
out.is_opaque_before_transform = !ibuf1.image->can_contain_alpha() &&
|
||||
!ibuf2.image->can_contain_alpha();
|
||||
return out;
|
||||
}
|
||||
|
||||
void wipe_effect_get_handle(EffectHandle &rval)
|
||||
{
|
||||
rval.init = init_wipe_effect;
|
||||
rval.free = free_wipe_effect;
|
||||
rval.early_out = early_out_fade;
|
||||
rval.execute = do_wipe_effect;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
383
blender-5.2.0/source/blender/sequencer/intern/effects/effects.cc
Normal file
383
blender-5.2.0/source/blender/sequencer/intern/effects/effects.cc
Normal file
@@ -0,0 +1,383 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2024 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_filter.hh"
|
||||
|
||||
#include "BKE_fcurve.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_metadata.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "effects.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
SeqResult prepare_effect_imbufs(const RenderData *context,
|
||||
const SeqResult &ibuf1,
|
||||
const SeqResult &ibuf2,
|
||||
bool uninitialized_pixels)
|
||||
{
|
||||
PRF_scope_with_name("SeqFxPrepareImbufs", ProfileCategory::Draw);
|
||||
SeqResult out;
|
||||
Scene *scene = context->scene;
|
||||
int x = context->rectx;
|
||||
int y = context->recty;
|
||||
ImBufFlags base_flags = uninitialized_pixels ? ImBufFlags::UninitializedPixels :
|
||||
ImBufFlags::Zero;
|
||||
|
||||
if (!ibuf1.is_valid() && !ibuf2.is_valid()) {
|
||||
out.image = IMB_allocImBuf(x, y, ImBufFlags::ByteData | base_flags);
|
||||
}
|
||||
else if ((ibuf1.is_valid() && ibuf1.image->float_data()) ||
|
||||
(ibuf2.is_valid() && ibuf2.image->float_data()))
|
||||
{
|
||||
/* if any inputs are float, output is float too */
|
||||
out.image = IMB_allocImBuf(x, y, ImBufFlags::FloatData | base_flags);
|
||||
}
|
||||
else {
|
||||
out.image = IMB_allocImBuf(x, y, ImBufFlags::ByteData | base_flags);
|
||||
}
|
||||
|
||||
if (out.image->float_data()) {
|
||||
if (ibuf1.is_valid()) {
|
||||
ensure_ibuf_is_sequencer_space(scene, ibuf1.image, true);
|
||||
}
|
||||
if (ibuf2.is_valid()) {
|
||||
ensure_ibuf_is_sequencer_space(scene, ibuf2.image, true);
|
||||
}
|
||||
IMB_colormanagement_assign_float_colorspace(out.image,
|
||||
scene->sequencer_colorspace_settings.name);
|
||||
}
|
||||
else {
|
||||
if (ibuf1.is_valid() && !ibuf1.image->byte_data()) {
|
||||
IMB_byte_from_float(ibuf1.image);
|
||||
}
|
||||
if (ibuf2.is_valid() && !ibuf2.image->byte_data()) {
|
||||
IMB_byte_from_float(ibuf2.image);
|
||||
}
|
||||
}
|
||||
|
||||
/* If effect only affecting a single channel, forward input's metadata to the output. */
|
||||
if (ibuf1.is_valid() && ibuf1.image == ibuf2.image) {
|
||||
IMB_metadata_copy(out.image, ibuf1.image);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Array<float> make_gaussian_blur_kernel(float rad, int size)
|
||||
{
|
||||
int n = 2 * size + 1;
|
||||
Array<float> gaussian(n);
|
||||
|
||||
float sum = 0.0f;
|
||||
float fac = (rad > 0.0f ? 1.0f / rad : 0.0f);
|
||||
for (int i = -size; i <= size; i++) {
|
||||
float val = math::filter_kernel_value(math::FilterKernel::Gauss, float(i) * fac);
|
||||
sum += val;
|
||||
gaussian[i + size] = val;
|
||||
}
|
||||
|
||||
float inv_sum = 1.0f / sum;
|
||||
for (int i = 0; i < n; i++) {
|
||||
gaussian[i] *= inv_sum;
|
||||
}
|
||||
|
||||
return gaussian;
|
||||
}
|
||||
|
||||
static void init_noop(Strip * /*strip*/) {}
|
||||
|
||||
static void copy_effect_default(Strip *dst, const Strip *src, const int /*flag*/)
|
||||
{
|
||||
dst->effectdata = MEM_dupalloc_void(src->effectdata);
|
||||
}
|
||||
|
||||
static StripEarlyOut early_out_noop(const Strip * /*strip*/, float /*fac*/)
|
||||
{
|
||||
return StripEarlyOut::DoEffect;
|
||||
}
|
||||
|
||||
StripEarlyOut early_out_fade(const Strip * /*strip*/, float fac)
|
||||
{
|
||||
if (fac == 0.0f) {
|
||||
return StripEarlyOut::UseInput1;
|
||||
}
|
||||
if (fac == 1.0f) {
|
||||
return StripEarlyOut::UseInput2;
|
||||
}
|
||||
return StripEarlyOut::DoEffect;
|
||||
}
|
||||
|
||||
StripEarlyOut early_out_mul_input2(const Strip * /*strip*/, float fac)
|
||||
{
|
||||
if (fac == 0.0f) {
|
||||
return StripEarlyOut::UseInput1;
|
||||
}
|
||||
return StripEarlyOut::DoEffect;
|
||||
}
|
||||
|
||||
StripEarlyOut early_out_mul_input1(const Strip * /*strip*/, float fac)
|
||||
{
|
||||
if (fac == 0.0f) {
|
||||
return StripEarlyOut::UseInput2;
|
||||
}
|
||||
return StripEarlyOut::DoEffect;
|
||||
}
|
||||
|
||||
void effect_ensure_initialized(Strip *strip)
|
||||
{
|
||||
if (strip->effectdata == nullptr) {
|
||||
EffectHandle h = strip_effect_handle_get(strip);
|
||||
if (h.init != nullptr) {
|
||||
h.init(strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void effect_free(Strip *strip)
|
||||
{
|
||||
EffectHandle h = strip_effect_handle_get(strip);
|
||||
if (h.free != nullptr) {
|
||||
h.free(strip, true);
|
||||
BLI_assert(strip->effectdata == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
EffectHandle effect_handle_get(StripType strip_type)
|
||||
{
|
||||
EffectHandle rval;
|
||||
|
||||
rval.init = init_noop;
|
||||
rval.free = nullptr;
|
||||
rval.early_out = early_out_noop;
|
||||
rval.execute = nullptr;
|
||||
rval.copy = copy_effect_default;
|
||||
|
||||
switch (strip_type) {
|
||||
case STRIP_TYPE_CROSS:
|
||||
cross_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_GAMCROSS:
|
||||
gamma_cross_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_COMPOSITOR:
|
||||
compositor_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_ADD:
|
||||
add_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_SUB:
|
||||
sub_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_MUL:
|
||||
mul_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_COLORMIX:
|
||||
color_mix_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_ALPHAOVER:
|
||||
alpha_over_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_ALPHAUNDER:
|
||||
alpha_under_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_WIPE:
|
||||
wipe_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_GLOW:
|
||||
glow_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_SPEED:
|
||||
speed_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_COLOR:
|
||||
solid_color_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_MULTICAM:
|
||||
multi_camera_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_ADJUSTMENT:
|
||||
adjustment_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_GAUSSIAN_BLUR:
|
||||
gaussian_blur_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_TYPE_TEXT:
|
||||
text_effect_get_handle(rval);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
static EffectHandle effect_handle_for_blend_mode_get(StripBlendMode blend)
|
||||
{
|
||||
EffectHandle rval;
|
||||
|
||||
rval.init = init_noop;
|
||||
rval.free = nullptr;
|
||||
rval.early_out = early_out_noop;
|
||||
rval.execute = nullptr;
|
||||
rval.copy = nullptr;
|
||||
|
||||
switch (blend) {
|
||||
case STRIP_BLEND_CROSS:
|
||||
cross_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_BLEND_ADD:
|
||||
add_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_BLEND_SUB:
|
||||
sub_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_BLEND_ALPHAOVER:
|
||||
alpha_over_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_BLEND_ALPHAUNDER:
|
||||
alpha_under_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_BLEND_GAMCROSS:
|
||||
gamma_cross_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_BLEND_MUL:
|
||||
mul_effect_get_handle(rval);
|
||||
break;
|
||||
case STRIP_BLEND_SCREEN:
|
||||
case STRIP_BLEND_LIGHTEN:
|
||||
case STRIP_BLEND_DODGE:
|
||||
case STRIP_BLEND_DARKEN:
|
||||
case STRIP_BLEND_COLOR_BURN:
|
||||
case STRIP_BLEND_LINEAR_BURN:
|
||||
case STRIP_BLEND_OVERLAY:
|
||||
case STRIP_BLEND_HARD_LIGHT:
|
||||
case STRIP_BLEND_SOFT_LIGHT:
|
||||
case STRIP_BLEND_PIN_LIGHT:
|
||||
case STRIP_BLEND_LIN_LIGHT:
|
||||
case STRIP_BLEND_VIVID_LIGHT:
|
||||
case STRIP_BLEND_HUE:
|
||||
case STRIP_BLEND_SATURATION:
|
||||
case STRIP_BLEND_VALUE:
|
||||
case STRIP_BLEND_BLEND_COLOR:
|
||||
case STRIP_BLEND_DIFFERENCE:
|
||||
case STRIP_BLEND_EXCLUSION:
|
||||
blend_mode_effect_get_handle(rval);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
EffectHandle strip_effect_handle_get(Strip *strip)
|
||||
{
|
||||
EffectHandle h = {};
|
||||
if (strip->is_effect()) {
|
||||
h = effect_handle_get(strip->type);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
EffectHandle strip_blend_mode_handle_get(Strip *strip)
|
||||
{
|
||||
EffectHandle h = {};
|
||||
if (strip->blend_mode != STRIP_BLEND_REPLACE) {
|
||||
h = effect_handle_for_blend_mode_get(strip->blend_mode);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static float transition_fader_calc(const Scene *scene, const Strip *strip, float timeline_frame)
|
||||
{
|
||||
float fac = float(timeline_frame - strip->left_handle());
|
||||
/* Compositor with no inputs can have strip->len not be updated,
|
||||
* since most of existing editing code assumes no-input effects never need the length.
|
||||
* So for the fader, just calculated it here directly. */
|
||||
if (strip->type == STRIP_TYPE_COMPOSITOR) {
|
||||
fac /= strip->enddisp - strip->startdisp;
|
||||
}
|
||||
else {
|
||||
fac /= strip->length(scene);
|
||||
}
|
||||
fac = math::clamp(fac, 0.0f, 1.0f);
|
||||
return fac;
|
||||
}
|
||||
|
||||
float effect_fader_calc(Scene *scene, Strip *strip, float timeline_frame)
|
||||
{
|
||||
if (strip->flag & SEQ_USE_EFFECT_DEFAULT_FADE) {
|
||||
if (effect_is_transition(strip->type)) {
|
||||
return transition_fader_calc(scene, strip, timeline_frame);
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const FCurve *fcu = id_data_find_fcurve(
|
||||
&scene->id, strip, RNA_Strip, "effect_fader", 0, nullptr);
|
||||
if (fcu) {
|
||||
return evaluate_fcurve(fcu, timeline_frame);
|
||||
}
|
||||
return strip->effect_fader;
|
||||
}
|
||||
|
||||
int effect_type_get_min_num_inputs(StripType type)
|
||||
{
|
||||
if (!strip_type_is_effect(type)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Zero input effects. Note: compositor is here too, but it supports
|
||||
* any input count. */
|
||||
if (ELEM(type,
|
||||
STRIP_TYPE_ADJUSTMENT,
|
||||
STRIP_TYPE_MULTICAM,
|
||||
STRIP_TYPE_COLOR,
|
||||
STRIP_TYPE_TEXT,
|
||||
STRIP_TYPE_COMPOSITOR))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* One input effects. */
|
||||
if (ELEM(type, STRIP_TYPE_GAUSSIAN_BLUR, STRIP_TYPE_GLOW, STRIP_TYPE_SPEED)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Others are two inputs. */
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool strip_type_is_effect(StripType type)
|
||||
{
|
||||
return (type >= STRIP_TYPE_CROSS && type <= STRIP_TYPE_COMPOSITOR) ||
|
||||
(type >= STRIP_TYPE_WIPE && type <= STRIP_TYPE_ADJUSTMENT) ||
|
||||
(type >= STRIP_TYPE_GAUSSIAN_BLUR && type <= STRIP_TYPE_COLORMIX);
|
||||
}
|
||||
|
||||
bool effect_is_transition(StripType type)
|
||||
{
|
||||
return ELEM(type, STRIP_TYPE_CROSS, STRIP_TYPE_GAMCROSS, STRIP_TYPE_WIPE, STRIP_TYPE_COMPOSITOR);
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
173
blender-5.2.0/source/blender/sequencer/intern/effects/effects.hh
Normal file
173
blender-5.2.0/source/blender/sequencer/intern/effects/effects.hh
Normal file
@@ -0,0 +1,173 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "IMB_imbuf_types.hh"
|
||||
#include "SEQ_effects.hh"
|
||||
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ImBuf;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct SeqRenderState;
|
||||
struct RenderData;
|
||||
|
||||
enum class StripEarlyOut {
|
||||
NoInput = -1, /* No input needed. */
|
||||
DoEffect = 0, /* No early out (do the effect). */
|
||||
UseInput1 = 1, /* Output = input1. */
|
||||
UseInput2 = 2, /* Output = input2. */
|
||||
};
|
||||
|
||||
struct EffectHandle {
|
||||
/* #init is only called on first creation, or when changing effect type. */
|
||||
void (*init)(Strip *strip);
|
||||
|
||||
/* duplicate */
|
||||
void (*copy)(Strip *dst, const Strip *src, int flag);
|
||||
|
||||
/* destruct */
|
||||
void (*free)(Strip *strip, bool do_id_user);
|
||||
|
||||
StripEarlyOut (*early_out)(const Strip *strip, float fac);
|
||||
|
||||
/* execute the effect */
|
||||
SeqResult (*execute)(const RenderData *context,
|
||||
SeqRenderState *state,
|
||||
Strip *strip,
|
||||
float timeline_frame,
|
||||
float fac,
|
||||
const SeqResult &input1,
|
||||
const SeqResult &input2);
|
||||
};
|
||||
|
||||
/** Get the effect handle for a given strip.
|
||||
* If `strip` is not an effect strip, returns empty `EffectHandle`. */
|
||||
EffectHandle strip_effect_handle_get(Strip *strip);
|
||||
|
||||
EffectHandle strip_blend_mode_handle_get(Strip *strip);
|
||||
/**
|
||||
* Build frame map when speed in mode #SEQ_SPEED_MULTIPLY is animated.
|
||||
* This is, because `target_frame` value is integrated over time.
|
||||
*/
|
||||
void strip_effect_speed_rebuild_map(Scene *scene, Strip *strip);
|
||||
/**
|
||||
* Override timeline_frame when rendering speed effect input.
|
||||
*/
|
||||
float strip_speed_effect_target_frame_get(Scene *scene,
|
||||
Strip *strip_speed,
|
||||
float timeline_frame,
|
||||
int input);
|
||||
|
||||
SeqResult prepare_effect_imbufs(const RenderData *context,
|
||||
const SeqResult &ibuf1,
|
||||
const SeqResult &ibuf2,
|
||||
bool uninitialized_pixels = true);
|
||||
|
||||
Array<float> make_gaussian_blur_kernel(float rad, int size);
|
||||
|
||||
inline float4 load_premul_pixel(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
straight_uchar_to_premul_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
inline float4 load_premul_pixel(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
inline void store_premul_pixel(const float4 &pix, uchar *dst)
|
||||
{
|
||||
premul_float_to_straight_uchar(dst, pix);
|
||||
}
|
||||
|
||||
inline void store_premul_pixel(const float4 &pix, float *dst)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(dst) = pix;
|
||||
}
|
||||
|
||||
StripEarlyOut early_out_mul_input1(const Strip * /*strip*/, float fac);
|
||||
StripEarlyOut early_out_mul_input2(const Strip * /*strip*/, float fac);
|
||||
StripEarlyOut early_out_fade(const Strip * /*strip*/, float fac);
|
||||
|
||||
EffectHandle effect_handle_get(StripType strip_type);
|
||||
|
||||
float effect_fader_calc(Scene *scene, Strip *strip, float timeline_frame);
|
||||
|
||||
void add_effect_get_handle(EffectHandle &rval);
|
||||
void adjustment_effect_get_handle(EffectHandle &rval);
|
||||
void alpha_over_effect_get_handle(EffectHandle &rval);
|
||||
void alpha_under_effect_get_handle(EffectHandle &rval);
|
||||
void blend_mode_effect_get_handle(EffectHandle &rval);
|
||||
void color_mix_effect_get_handle(EffectHandle &rval);
|
||||
void compositor_effect_get_handle(EffectHandle &rval);
|
||||
void cross_effect_get_handle(EffectHandle &rval);
|
||||
void gamma_cross_effect_get_handle(EffectHandle &rval);
|
||||
void gaussian_blur_effect_get_handle(EffectHandle &rval);
|
||||
void glow_effect_get_handle(EffectHandle &rval);
|
||||
void mul_effect_get_handle(EffectHandle &rval);
|
||||
void multi_camera_effect_get_handle(EffectHandle &rval);
|
||||
void solid_color_effect_get_handle(EffectHandle &rval);
|
||||
void speed_effect_get_handle(EffectHandle &rval);
|
||||
void sub_effect_get_handle(EffectHandle &rval);
|
||||
void text_effect_get_handle(EffectHandle &rval);
|
||||
void transform_effect_get_handle(EffectHandle &rval);
|
||||
void wipe_effect_get_handle(EffectHandle &rval);
|
||||
|
||||
/* Given `OpT` that implements an `apply` function:
|
||||
*
|
||||
* template <typename T>
|
||||
* void apply(const T *src1, const T *src2, T *dst, int64_t size) const;
|
||||
*
|
||||
* this function calls the apply() function in parallel
|
||||
* chunks of the image to process, and with uchar or float types
|
||||
* All images are expected to have 4 (RGBA) color channels. */
|
||||
template<typename OpT>
|
||||
static void apply_effect_op(const OpT &op, const ImBuf *src1, const ImBuf *src2, ImBuf *dst)
|
||||
{
|
||||
BLI_assert_msg(src1->channels == 0 || src1->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
BLI_assert_msg(src2->channels == 0 || src2->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
BLI_assert_msg(dst->channels == 0 || dst->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
float *dst_float_data = dst->float_data_for_write();
|
||||
uchar *dst_byte_data = dst->byte_data_for_write();
|
||||
threading::parallel_for(IndexRange(size_t(dst->x) * dst->y), 32 * 1024, [&](IndexRange range) {
|
||||
int64_t offset = range.first() * 4;
|
||||
if (dst_float_data) {
|
||||
const float *src1_ptr = src1->float_data() + offset;
|
||||
const float *src2_ptr = src2->float_data() + offset;
|
||||
float *dst_ptr = dst_float_data + offset;
|
||||
op.apply(src1_ptr, src2_ptr, dst_ptr, range.size());
|
||||
}
|
||||
else {
|
||||
const uchar *src1_ptr = src1->byte_data() + offset;
|
||||
const uchar *src2_ptr = src2->byte_data() + offset;
|
||||
uchar *dst_ptr = dst_byte_data + offset;
|
||||
op.apply(src1_ptr, src2_ptr, dst_ptr, range.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
326
blender-5.2.0/source/blender/sequencer/intern/iterator.cc
Normal file
326
blender-5.2.0/source/blender/sequencer/intern/iterator.cc
Normal file
@@ -0,0 +1,326 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2024 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "SEQ_connect.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static bool strip_for_each_recursive(ListBaseT<Strip> *seqbase,
|
||||
ForEachFunc callback,
|
||||
void *user_data)
|
||||
{
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (!callback(&strip, user_data)) {
|
||||
/* Callback signaled stop, return. */
|
||||
return false;
|
||||
}
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
if (!strip_for_each_recursive(&strip.seqbase, callback, user_data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool strip_for_each_recursive(ListBaseT<Strip> *seqbase,
|
||||
FunctionRef<bool(Strip *)> callback)
|
||||
{
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (!callback(&strip)) {
|
||||
/* Callback signaled stop, return. */
|
||||
return false;
|
||||
}
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
if (!strip_for_each_recursive(&strip.seqbase, callback)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void foreach_strip(ListBaseT<Strip> *seqbase, ForEachFunc callback, void *user_data)
|
||||
{
|
||||
strip_for_each_recursive(seqbase, callback, user_data);
|
||||
}
|
||||
|
||||
void foreach_strip(ListBaseT<Strip> *seqbase, FunctionRef<bool(Strip *)> callback)
|
||||
{
|
||||
strip_for_each_recursive(seqbase, callback);
|
||||
}
|
||||
|
||||
VectorSet<Strip *> query_by_reference(Strip *strip_reference,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
void strip_query_func(Strip *strip_reference,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &strips))
|
||||
{
|
||||
VectorSet<Strip *> strips;
|
||||
strip_query_func(strip_reference, seqbase, strips);
|
||||
return strips;
|
||||
}
|
||||
|
||||
void iterator_set_expand(ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &strips,
|
||||
void strip_query_func(Strip *strip,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &strips))
|
||||
{
|
||||
/* Collect expanded results for each sequence in provided VectorSet. */
|
||||
VectorSet<Strip *> query_matches;
|
||||
|
||||
for (Strip *strip : strips) {
|
||||
query_matches.add_multiple(query_by_reference(strip, seqbase, strip_query_func));
|
||||
}
|
||||
|
||||
/* Merge all expanded results in provided VectorSet. */
|
||||
strips.add_multiple(query_matches);
|
||||
}
|
||||
|
||||
static void query_all_strips_recursive(const ListBaseT<Strip> *seqbase, VectorSet<Strip *> &strips)
|
||||
{
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
query_all_strips_recursive(&strip.seqbase, strips);
|
||||
}
|
||||
strips.add(&strip);
|
||||
}
|
||||
}
|
||||
|
||||
VectorSet<Strip *> query_all_strips_recursive(const ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
VectorSet<Strip *> strips;
|
||||
query_all_strips_recursive(seqbase, strips);
|
||||
return strips;
|
||||
}
|
||||
|
||||
static void query_strips_recursive_at_frame(const Scene *scene,
|
||||
const ListBaseT<Strip> *seqbase,
|
||||
const int timeline_frame,
|
||||
VectorSet<Strip *> &strips)
|
||||
{
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (!strip.intersects_frame(scene, timeline_frame)) {
|
||||
continue;
|
||||
}
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
query_strips_recursive_at_frame(scene, &strip.seqbase, timeline_frame, strips);
|
||||
}
|
||||
strips.add(&strip);
|
||||
}
|
||||
}
|
||||
|
||||
VectorSet<Strip *> query_strips_recursive_at_frame(const Scene *scene,
|
||||
const ListBaseT<Strip> *seqbase,
|
||||
const int timeline_frame)
|
||||
{
|
||||
VectorSet<Strip *> strips;
|
||||
query_strips_recursive_at_frame(scene, seqbase, timeline_frame, strips);
|
||||
return strips;
|
||||
}
|
||||
|
||||
VectorSet<Strip *> query_all_strips(ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
VectorSet<Strip *> strips;
|
||||
for (Strip &strip : *seqbase) {
|
||||
strips.add(&strip);
|
||||
}
|
||||
return strips;
|
||||
}
|
||||
|
||||
VectorSet<Strip *> query_selected_strips(ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
VectorSet<Strip *> strips;
|
||||
for (Strip &strip : *seqbase) {
|
||||
if ((strip.flag & SEQ_SELECT) != 0) {
|
||||
strips.add(&strip);
|
||||
}
|
||||
}
|
||||
return strips;
|
||||
}
|
||||
|
||||
static VectorSet<Strip *> query_strips_at_frame(const Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
const int timeline_frame)
|
||||
{
|
||||
VectorSet<Strip *> strips;
|
||||
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (strip.intersects_frame(scene, timeline_frame)) {
|
||||
strips.add(&strip);
|
||||
}
|
||||
}
|
||||
return strips;
|
||||
}
|
||||
|
||||
static void collection_filter_channel_up_to_incl(VectorSet<Strip *> &strip_stack,
|
||||
const int channel)
|
||||
{
|
||||
strip_stack.remove_if([&](Strip *strip) { return strip->channel > channel; });
|
||||
}
|
||||
|
||||
bool must_render_strip(const VectorSet<Strip *> &strip_stack, Strip *target_strip)
|
||||
{
|
||||
bool strip_have_effect_in_stack = false;
|
||||
for (Strip *strip : strip_stack) {
|
||||
/* Strips below another strip with replace blending are never directly rendered. */
|
||||
if (strip->blend_mode == STRIP_BLEND_REPLACE && target_strip->channel < strip->channel) {
|
||||
return false;
|
||||
}
|
||||
if (strip->is_effect() && relation_is_effect_of_strip(strip, target_strip)) {
|
||||
/* Strips at the same channel or above their effect are rendered. */
|
||||
if (target_strip->channel >= strip->channel) {
|
||||
return true;
|
||||
}
|
||||
/* Mark that this strip has an effect in the stack that is above the strip. */
|
||||
strip_have_effect_in_stack = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* All effects with inputs are rendered assuming they pass the above checks. */
|
||||
if (target_strip->is_effect_with_inputs()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If strip has effects in stack, and all effects are above this strip, it is not rendered. */
|
||||
if (strip_have_effect_in_stack) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Remove strips we don't want to render from VectorSet. */
|
||||
static void collection_filter_rendered_strips(VectorSet<Strip *> &strip_stack,
|
||||
ListBaseT<SeqTimelineChannel> *channels)
|
||||
{
|
||||
/* Remove sound strips and muted strips from VectorSet, because these are not rendered.
|
||||
* Function #must_render_strip() don't have to check for these strips anymore. */
|
||||
strip_stack.remove_if([&](Strip *strip) {
|
||||
return strip->type == STRIP_TYPE_SOUND || render_is_muted(channels, strip);
|
||||
});
|
||||
|
||||
strip_stack.remove_if([&](Strip *strip) { return !must_render_strip(strip_stack, strip); });
|
||||
}
|
||||
|
||||
VectorSet<Strip *> query_rendered_strips(const Scene *scene,
|
||||
ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
const int timeline_frame,
|
||||
const int displayed_channel)
|
||||
{
|
||||
VectorSet strips = query_strips_at_frame(scene, seqbase, timeline_frame);
|
||||
if (displayed_channel != 0) {
|
||||
collection_filter_channel_up_to_incl(strips, displayed_channel);
|
||||
}
|
||||
collection_filter_rendered_strips(strips, channels);
|
||||
return strips;
|
||||
}
|
||||
|
||||
Vector<Strip *> query_rendered_strips_sorted(const Scene *scene,
|
||||
ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
const int timeline_frame,
|
||||
const int chanshown)
|
||||
{
|
||||
VectorSet strips = query_rendered_strips(scene, channels, seqbase, timeline_frame, chanshown);
|
||||
|
||||
Vector<Strip *> strips_vec = strips.extract_vector();
|
||||
/* Sort strips by channel. */
|
||||
std::ranges::sort(strips_vec,
|
||||
[](const Strip *a, const Strip *b) { return a->channel < b->channel; });
|
||||
return strips_vec;
|
||||
}
|
||||
|
||||
VectorSet<Strip *> query_unselected_strips(ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
VectorSet<Strip *> strips;
|
||||
for (Strip &strip : *seqbase) {
|
||||
if ((strip.flag & SEQ_SELECT) != 0) {
|
||||
continue;
|
||||
}
|
||||
strips.add(&strip);
|
||||
}
|
||||
return strips;
|
||||
}
|
||||
|
||||
void query_strip_effect_chain(Strip *strip,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &r_strips)
|
||||
{
|
||||
if (r_strips.contains(strip)) {
|
||||
return; /* Strip is already in set, so all effects connected to it are as well. */
|
||||
}
|
||||
|
||||
r_strips.add(strip);
|
||||
|
||||
/* Find all input strips for `strip`. */
|
||||
if (strip->is_effect()) {
|
||||
if (strip->input1) {
|
||||
query_strip_effect_chain(strip->input1, seqbase, r_strips);
|
||||
}
|
||||
if (strip->input2) {
|
||||
query_strip_effect_chain(strip->input2, seqbase, r_strips);
|
||||
}
|
||||
}
|
||||
|
||||
/* Find all effect strips that have `strip` as an input. */
|
||||
for (Strip &strip_test : *seqbase) {
|
||||
if (strip_test.input1 == strip || strip_test.input2 == strip) {
|
||||
query_strip_effect_chain(&strip_test, seqbase, r_strips);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void query_strip_connected_and_effect_chain(Strip *strip,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
VectorSet<Strip *> &r_strips)
|
||||
{
|
||||
|
||||
Vector<Strip *> pending;
|
||||
pending.append(strip);
|
||||
|
||||
while (!pending.is_empty()) {
|
||||
Strip *current = pending.pop_last();
|
||||
|
||||
if (r_strips.contains(current)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r_strips.add(current);
|
||||
|
||||
VectorSet<Strip *> connections = connected_strips_get(current);
|
||||
for (Strip *connection : connections) {
|
||||
if (!r_strips.contains(connection)) {
|
||||
pending.append(connection);
|
||||
}
|
||||
}
|
||||
|
||||
VectorSet<Strip *> effect_chain;
|
||||
query_strip_effect_chain(current, seqbase, effect_chain);
|
||||
for (Strip *effect_strip : effect_chain) {
|
||||
if (!r_strips.contains(effect_strip)) {
|
||||
pending.append(effect_strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
185
blender-5.2.0/source/blender/sequencer/intern/media_presence.cc
Normal file
185
blender-5.2.0/source/blender/sequencer/intern/media_presence.cc
Normal file
@@ -0,0 +1,185 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_mutex.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_sound_types.h"
|
||||
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_main.hh"
|
||||
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static Mutex presence_lock;
|
||||
|
||||
static bool check_sound_media_missing(const bSound *sound)
|
||||
{
|
||||
if (sound == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char filepath[FILE_MAX];
|
||||
STRNCPY(filepath, sound->filepath);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&sound->id));
|
||||
return !BLI_exists(filepath);
|
||||
}
|
||||
|
||||
static bool check_media_missing(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (strip == nullptr || strip->data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Images or movies. */
|
||||
if (ELEM((strip)->type, STRIP_TYPE_MOVIE, STRIP_TYPE_IMAGE)) {
|
||||
const StripElem *elem = strip->data->stripdata;
|
||||
if (elem != nullptr) {
|
||||
int paths_count = 1;
|
||||
if (strip->type == STRIP_TYPE_IMAGE) {
|
||||
/* Image strip has array of file names. */
|
||||
paths_count = int(MEM_allocN_len(elem) / sizeof(*elem));
|
||||
}
|
||||
char filepath[FILE_MAX];
|
||||
const char *basepath = ID_BLEND_PATH_FROM_GLOBAL(&scene->id);
|
||||
for (int i = 0; i < paths_count; i++, elem++) {
|
||||
BLI_path_join(filepath, sizeof(filepath), strip->data->dirpath, elem->filename);
|
||||
BLI_path_abs(filepath, basepath);
|
||||
if (!BLI_exists(filepath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Recurse into meta strips. */
|
||||
if (strip->type == STRIP_TYPE_META) {
|
||||
for (Strip &strip_n : strip->seqbase) {
|
||||
if (check_media_missing(scene, &strip_n)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing is missing. */
|
||||
return false;
|
||||
}
|
||||
|
||||
struct MediaPresence {
|
||||
Map<const void *, bool> map_seq;
|
||||
Map<const bSound *, bool> map_sound;
|
||||
};
|
||||
|
||||
static MediaPresence *get_media_presence_cache(Scene *scene)
|
||||
{
|
||||
MediaPresence **presence = &scene->ed->runtime->media_presence;
|
||||
if (*presence == nullptr) {
|
||||
*presence = MEM_new<MediaPresence>(__func__);
|
||||
}
|
||||
return *presence;
|
||||
}
|
||||
|
||||
bool media_presence_is_missing(Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (strip == nullptr || scene == nullptr || scene->ed == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(presence_lock);
|
||||
|
||||
MediaPresence *presence = get_media_presence_cache(scene);
|
||||
|
||||
bool missing = false;
|
||||
|
||||
/* Strips that reference another data block that has path to media
|
||||
* (e.g. sound strips) need to key the presence cache on that data
|
||||
* block. Since it can be used by multiple strips. */
|
||||
if (strip->type == STRIP_TYPE_SOUND) {
|
||||
const bSound *sound = strip->sound;
|
||||
if (sound && sound->packedfile != nullptr) {
|
||||
/* The sound file has been packed, don't look up the path. */
|
||||
return false;
|
||||
}
|
||||
const bool *val = presence->map_sound.lookup_ptr(sound);
|
||||
if (val != nullptr) {
|
||||
missing = *val;
|
||||
}
|
||||
else {
|
||||
missing = check_sound_media_missing(sound);
|
||||
presence->map_sound.add_new(sound, missing);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Regular strips that point to media directly. */
|
||||
const bool *val = presence->map_seq.lookup_ptr(strip);
|
||||
if (val != nullptr) {
|
||||
missing = *val;
|
||||
}
|
||||
else {
|
||||
missing = check_media_missing(scene, strip);
|
||||
presence->map_seq.add_new(strip, missing);
|
||||
}
|
||||
}
|
||||
|
||||
return missing;
|
||||
}
|
||||
|
||||
void media_presence_set_missing(Scene *scene, const Strip *strip, bool missing)
|
||||
{
|
||||
if (strip == nullptr || scene == nullptr || scene->ed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(presence_lock);
|
||||
|
||||
MediaPresence *presence = get_media_presence_cache(scene);
|
||||
|
||||
if (strip->type == STRIP_TYPE_SOUND) {
|
||||
const bSound *sound = strip->sound;
|
||||
presence->map_sound.add_overwrite(sound, missing);
|
||||
}
|
||||
else {
|
||||
presence->map_seq.add_overwrite(strip, missing);
|
||||
}
|
||||
}
|
||||
|
||||
void media_presence_invalidate_strip(Scene *scene, const Strip *strip)
|
||||
{
|
||||
std::scoped_lock lock(presence_lock);
|
||||
if (scene != nullptr && scene->ed != nullptr && scene->ed->runtime->media_presence != nullptr) {
|
||||
scene->ed->runtime->media_presence->map_seq.remove(strip);
|
||||
}
|
||||
}
|
||||
|
||||
void media_presence_invalidate_sound(Scene *scene, const bSound *sound)
|
||||
{
|
||||
std::scoped_lock lock(presence_lock);
|
||||
if (scene != nullptr && scene->ed != nullptr && scene->ed->runtime->media_presence != nullptr) {
|
||||
scene->ed->runtime->media_presence->map_sound.remove(sound);
|
||||
}
|
||||
}
|
||||
|
||||
void media_presence_free(Scene *scene)
|
||||
{
|
||||
std::scoped_lock lock(presence_lock);
|
||||
if (scene != nullptr && scene->ed != nullptr && scene->ed->runtime->media_presence != nullptr) {
|
||||
MEM_delete(scene->ed->runtime->media_presence);
|
||||
scene->ed->runtime->media_presence = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,128 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct BrightContrastApplyOp {
|
||||
float mul;
|
||||
float add;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
|
||||
float4 result;
|
||||
result = input * this->mul + this->add;
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void brightcontrast_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModBrightContrast", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const BrightContrastModifierData *bcmd = reinterpret_cast<BrightContrastModifierData *>(smd);
|
||||
|
||||
BrightContrastApplyOp op;
|
||||
|
||||
/* The algorithm is by Werner D. Streidt
|
||||
* (http://visca.com/ffactory/archives/5-99/msg00021.html)
|
||||
* Extracted from OpenCV `demhist.cpp`. */
|
||||
const float brightness = bcmd->bright / 100.0f;
|
||||
const float contrast = bcmd->contrast;
|
||||
float delta = contrast / 200.0f;
|
||||
|
||||
if (contrast > 0) {
|
||||
op.mul = 1.0f - delta * 2.0f;
|
||||
op.mul = 1.0f / max_ff(op.mul, FLT_EPSILON);
|
||||
op.add = op.mul * (brightness - delta);
|
||||
}
|
||||
else {
|
||||
delta *= -1;
|
||||
op.mul = max_ff(1.0f - delta * 2.0f, 0.0f);
|
||||
op.add = op.mul * brightness + delta;
|
||||
}
|
||||
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void brightcontrast_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
layout.prop(ptr, "bright", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout.prop(ptr, "contrast", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void brightcontrast_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_BrightContrast, brightcontrast_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_BrightContrast = {
|
||||
/*idname*/ "BrightContrast",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Brightness/Contrast"),
|
||||
/*struct_name*/ "BrightContrastModifierData",
|
||||
/*struct_size*/ sizeof(BrightContrastModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ brightcontrast_apply,
|
||||
/*panel_register*/ brightcontrast_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,396 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* Lift-Gamma-Gain math. NOTE: lift is actually (2-lift). */
|
||||
static float color_balance_lgg(
|
||||
float in, const float lift, const float gain, const float gamma, const float mul)
|
||||
{
|
||||
float x = (((in - 1.0f) * lift) + 1.0f) * gain;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, gamma) * mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Slope-Offset-Power (ASC CDL) math, see https://en.wikipedia.org/wiki/ASC_CDL */
|
||||
static float color_balance_sop(
|
||||
float in, const float slope, const float offset, const float power, float mul)
|
||||
{
|
||||
float x = in * slope + offset;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, power);
|
||||
x *= mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a larger lookup table than 256 possible byte values: due to alpha
|
||||
* pre-multiplication, dark values with low alphas might need more precision.
|
||||
*/
|
||||
static constexpr int CB_TABLE_SIZE = 1024;
|
||||
|
||||
static void make_cb_table_lgg(
|
||||
float lift, float gain, float gamma, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_lgg(x, lift, gain, gamma, mul);
|
||||
}
|
||||
}
|
||||
|
||||
static void make_cb_table_sop(
|
||||
float slope, float offset, float power, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_sop(x, slope, offset, power, mul);
|
||||
}
|
||||
}
|
||||
|
||||
struct ColorBalanceApplyOp {
|
||||
int method;
|
||||
float3 lift, gain, gamma;
|
||||
float3 slope, offset, power;
|
||||
float multiplier;
|
||||
float lut[3][CB_TABLE_SIZE];
|
||||
|
||||
/* Apply on a byte image via a table lookup. */
|
||||
template<typename MaskSampler>
|
||||
void apply(uchar *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
int p0 = int(input.x * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p1 = int(input.y * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p2 = int(input.z * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
result.x = this->lut[0][p0];
|
||||
result.y = this->lut[1][p1];
|
||||
result.z = this->lut[2][p2];
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply on a float image by doing full math. */
|
||||
template<typename MaskSampler>
|
||||
void apply(float *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
/* Lift/Gamma/Gain */
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_lgg(
|
||||
input.x, this->lift.x, this->gain.x, this->gamma.x, this->multiplier);
|
||||
result.y = color_balance_lgg(
|
||||
input.y, this->lift.y, this->gain.y, this->gamma.y, this->multiplier);
|
||||
result.z = color_balance_lgg(
|
||||
input.z, this->lift.z, this->gain.z, this->gamma.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
/* Slope/Offset/Power */
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_sop(
|
||||
input.x, this->slope.x, this->offset.x, this->power.x, this->multiplier);
|
||||
result.y = color_balance_sop(
|
||||
input.y, this->slope.y, this->offset.y, this->power.y, this->multiplier);
|
||||
result.z = color_balance_sop(
|
||||
input.z, this->slope.z, this->offset.z, this->power.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_lgg(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN);
|
||||
|
||||
this->lift = 2.0f - float3(data.lift);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_LIFT) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
/* tweak to give more subtle results
|
||||
* values above 1.0 are scaled */
|
||||
if (this->lift[c] > 1.0f) {
|
||||
this->lift[c] = powf(this->lift[c] - 1.0f, 2.0f) + 1.0f;
|
||||
}
|
||||
this->lift[c] = 2.0f - this->lift[c];
|
||||
}
|
||||
}
|
||||
|
||||
this->gain = float3(data.gain);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_GAIN) {
|
||||
this->gain = math::rcp(math::max(this->gain, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->gamma = float3(data.gamma);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_GAMMA)) {
|
||||
this->gamma = math::rcp(math::max(this->gamma, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init_sop(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER);
|
||||
|
||||
this->slope = float3(data.slope);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_SLOPE) {
|
||||
this->slope = math::rcp(math::max(this->slope, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->offset = float3(data.offset) - 1.0f;
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_OFFSET) {
|
||||
this->offset = -this->offset;
|
||||
}
|
||||
|
||||
this->power = float3(data.power);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_POWER)) {
|
||||
this->power = math::rcp(math::max(this->power, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init(const ColorBalanceModifierData &data, bool byte_image)
|
||||
{
|
||||
this->multiplier = data.color_multiply;
|
||||
this->method = data.color_balance.method;
|
||||
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
init_lgg(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_lgg(
|
||||
this->lift[c], this->gain[c], this->gamma[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
init_sop(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_sop(
|
||||
this->slope[c], this->offset[c], this->power[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void colorBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
ColorBalanceModifierData *cbmd = reinterpret_cast<ColorBalanceModifierData *>(smd);
|
||||
|
||||
cbmd->color_multiply = 1.0f;
|
||||
cbmd->color_balance.method = SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN;
|
||||
|
||||
for (int c = 0; c < 3; c++) {
|
||||
cbmd->color_balance.lift[c] = 1.0f;
|
||||
cbmd->color_balance.gamma[c] = 1.0f;
|
||||
cbmd->color_balance.gain[c] = 1.0f;
|
||||
cbmd->color_balance.slope[c] = 1.0f;
|
||||
cbmd->color_balance.offset[c] = 1.0f;
|
||||
cbmd->color_balance.power[c] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModColorBalance", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const ColorBalanceModifierData *cbmd = reinterpret_cast<const ColorBalanceModifierData *>(smd);
|
||||
|
||||
ColorBalanceApplyOp op;
|
||||
op.init(*cbmd, context.result.image->byte_data() != nullptr);
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
PointerRNA color_balance = RNA_pointer_get(ptr, "color_balance");
|
||||
const int correction_method = RNA_enum_get(&color_balance, "correction_method");
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
layout.prop(ptr, "color_multiply", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout.prop(&color_balance, "correction_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
ui::Layout &flow = layout.grid_flow(true, 0, true, false, false);
|
||||
flow.use_property_split_set(false);
|
||||
if (correction_method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
/* Split into separate scopes to be able to reuse "split" and "col" variable names. */
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Lift"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "lift", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_lift", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "lift", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Gamma"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "gamma", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_gamma", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "gamma", true, false, true, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Gain"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "gain", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_gain", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "gain", true, false, true, true);
|
||||
}
|
||||
}
|
||||
else if (correction_method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Offset"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "offset", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_offset", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "offset", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Power"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "power", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_power", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "power", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Slope"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "slope", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_slope", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "slope", true, false, false, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_ColorBalance, colorBalance_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_ColorBalance = {
|
||||
/*idname*/ "ColorBalance",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Color Balance"),
|
||||
/*struct_name*/ "ColorBalanceModifierData",
|
||||
/*struct_size*/ sizeof(ColorBalanceModifierData),
|
||||
/*init_data*/ colorBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ colorBalance_apply,
|
||||
/*panel_register*/ colorBalance_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,496 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_rotation.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_animsys.h"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_idprop.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "NOD_composite.hh"
|
||||
#include "NOD_compositor_nodes_caller_ui.hh"
|
||||
#include "NOD_compositor_nodes_srna.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_transform.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "cache/compositor_cache.hh"
|
||||
#include "compositor.hh"
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
void compositor_nodes_update_interface(Scene &sequencer_scene,
|
||||
SequencerCompositorModifierData &cmd)
|
||||
{
|
||||
if (!cmd.modifier.system_properties) {
|
||||
cmd.modifier.system_properties =
|
||||
bke::idprop::create_group("SequencerCompositorModifierProperties").release();
|
||||
}
|
||||
PointerRNA properties_ptr = RNA_pointer_create_discrete(
|
||||
&sequencer_scene.id, RNA_SequencerCompositorModifierProperties, &cmd);
|
||||
RNA_ensure_and_sync_system_properties(properties_ptr, *cmd.modifier.system_properties);
|
||||
|
||||
DEG_id_tag_update(&sequencer_scene.id, ID_RECALC_SEQUENCER_STRIPS);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void set_float_array(PointerRNA *input_props_ptr, compositor::Result &result)
|
||||
{
|
||||
T value;
|
||||
RNA_float_get_array(input_props_ptr, "value", value);
|
||||
result.set_single_value(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void set_int_array(PointerRNA *input_props_ptr, compositor::Result &result)
|
||||
{
|
||||
T value;
|
||||
RNA_int_get_array(input_props_ptr, "value", value);
|
||||
result.set_single_value(value);
|
||||
}
|
||||
|
||||
static void set_single_input_from_rna_value(PointerRNA *input_props_ptr,
|
||||
const eNodeSocketDatatype socket_type,
|
||||
compositor::Result &result,
|
||||
const std::optional<int> dimensions = {})
|
||||
{
|
||||
using namespace nodes;
|
||||
switch (socket_type) {
|
||||
case SOCK_FLOAT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const float value = RNA_float_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
switch (dimensions.value_or(3)) {
|
||||
case 2: {
|
||||
set_float_array<float2>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
set_float_array<float3>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
set_float_array<float4>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
ColorGeometry4f value;
|
||||
RNA_float_get_array(input_props_ptr, "value", value);
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_BOOLEAN: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const bool value = RNA_boolean_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const int value = RNA_int_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_ROTATION: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
float3 value_euler;
|
||||
RNA_float_get_array(input_props_ptr, "value", value_euler);
|
||||
math::Quaternion value_rotation = math::to_quaternion(math::EulerXYZ(value_euler));
|
||||
result.set_single_value(value_rotation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_MENU: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const MenuValue value = MenuValue(RNA_enum_get(input_props_ptr, "value"));
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_STRING: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const std::string value = RNA_string_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_INT_VECTOR: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
switch (dimensions.value_or(2)) {
|
||||
case 2: {
|
||||
set_int_array<int2>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
set_int_array<int3>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_OBJECT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
Object *value = RNA_pointer_get(input_props_ptr, "value").data_as<Object>();
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_FONT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
VFont *value = RNA_pointer_get(input_props_ptr, "value").data_as<VFont>();
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_IMAGE:
|
||||
case SOCK_COLLECTION:
|
||||
case SOCK_TEXTURE:
|
||||
case SOCK_MATERIAL:
|
||||
case SOCK_SCENE:
|
||||
case SOCK_TEXT_ID:
|
||||
case SOCK_MASK:
|
||||
case SOCK_SOUND:
|
||||
case SOCK_GEOMETRY:
|
||||
case SOCK_MATRIX:
|
||||
case SOCK_BUNDLE:
|
||||
case SOCK_CLOSURE:
|
||||
case SOCK_SHADER:
|
||||
case SOCK_CUSTOM:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<int> get_socket_dimension(const bNodeTreeInterfaceSocket *socket,
|
||||
const eNodeSocketDatatype socket_type)
|
||||
{
|
||||
if (socket_type == SOCK_VECTOR) {
|
||||
return static_cast<bNodeSocketValueVector *>(socket->socket_data)->dimensions;
|
||||
}
|
||||
else if (socket_type == SOCK_INT_VECTOR) {
|
||||
return static_cast<bNodeSocketValueIntVector *>(socket->socket_data)->dimensions;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
class CompositorModifierContext : public CompositorContext {
|
||||
private:
|
||||
const ModifierApplyContext &mod_context_;
|
||||
SequencerCompositorModifierData *modifier_data_;
|
||||
|
||||
ImBuf *image_buffer_;
|
||||
compositor::Result mask_;
|
||||
ImBuf *mask_buffer_ = nullptr;
|
||||
int timeline_frame_;
|
||||
bool owns_mask_ = false;
|
||||
PointerRNA properties_ptr_;
|
||||
|
||||
public:
|
||||
CompositorModifierContext(const ModifierApplyContext &mod_context,
|
||||
compositor::StaticCacheManager &cache_manager,
|
||||
SequencerCompositorModifierData *modifier_data)
|
||||
: CompositorContext(cache_manager, mod_context.render_data, mod_context.strip),
|
||||
mod_context_(mod_context),
|
||||
modifier_data_(modifier_data),
|
||||
image_buffer_(mod_context.result.image),
|
||||
mask_(*this, compositor::ResultType::Color, compositor::ResultPrecision::Full),
|
||||
timeline_frame_(mod_context.timeline_frame)
|
||||
{
|
||||
PointerRNA ptr = RNA_pointer_create_discrete(
|
||||
&mod_context.render_data.scene->id, RNA_SequencerCompositorModifierData, modifier_data);
|
||||
properties_ptr_ = RNA_pointer_get(&ptr, "properties");
|
||||
}
|
||||
|
||||
void free_resources()
|
||||
{
|
||||
IMB_freeImBuf(this->mask_buffer_);
|
||||
this->mask_buffer_ = nullptr;
|
||||
|
||||
if (this->owns_mask_) {
|
||||
this->mask_.release();
|
||||
this->owns_mask_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
compositor::Domain get_compositing_domain() const override
|
||||
{
|
||||
return compositor::Domain(int2(image_buffer_->x, image_buffer_->y));
|
||||
}
|
||||
|
||||
void write_viewer(compositor::Result &viewer_result) override
|
||||
{
|
||||
write_viewer_impl(viewer_result, *image_buffer_);
|
||||
}
|
||||
|
||||
void evaluate()
|
||||
{
|
||||
using namespace compositor;
|
||||
const StripModifierData &smd = this->modifier_data_->modifier;
|
||||
const bool is_mask_used = smd.mask_input_type == STRIP_MASK_INPUT_STRIP ?
|
||||
smd.mask_strip != nullptr :
|
||||
smd.mask_id != nullptr;
|
||||
|
||||
const bNodeTree &node_group = *DEG_get_evaluated<bNodeTree>(render_data_.depsgraph,
|
||||
modifier_data_->node_group);
|
||||
const bke::DataBlockComputeContext compute_context(nullptr, this->get_scene().id);
|
||||
NodeGroupOperation node_group_operation(*this,
|
||||
node_group,
|
||||
this->needed_outputs(),
|
||||
node_group.active_viewer_key,
|
||||
bke::NODE_INSTANCE_KEY_BASE,
|
||||
compute_context);
|
||||
set_output_refcount(node_group, node_group_operation);
|
||||
|
||||
node_group.ensure_topology_cache();
|
||||
PointerRNA inputs_ptr = RNA_pointer_get(&properties_ptr_, "inputs");
|
||||
BLI_assert(inputs_ptr.data != nullptr);
|
||||
|
||||
/* Map the inputs to the operation. */
|
||||
Vector<std::unique_ptr<Result>> inputs;
|
||||
const Span<const bNodeTreeInterfaceSocket *> interface_inputs = node_group.interface_inputs();
|
||||
for (const bNodeTreeInterfaceSocket *input_socket : interface_inputs) {
|
||||
bke::bNodeSocketType *typeinfo = input_socket->socket_typeinfo();
|
||||
const eNodeSocketDatatype socket_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
|
||||
const bool valid_socket_type = typeinfo && node_group.typeinfo->valid_socket_type(
|
||||
node_group.typeinfo, typeinfo);
|
||||
/* Fallback to ResultType::Float for invalid inputs. */
|
||||
const ResultType result_type = valid_socket_type ?
|
||||
compositor::get_node_interface_socket_result_type(
|
||||
*input_socket) :
|
||||
ResultType::Float;
|
||||
Result *input_result = new Result(this->create_result(result_type, ResultPrecision::Full));
|
||||
if (input_socket == interface_inputs[0]) {
|
||||
if (socket_type == SOCK_RGBA) {
|
||||
/* First socket is the image input. */
|
||||
create_result_from_input(*input_result, *image_buffer_);
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
}
|
||||
else if (is_mask_used && input_socket == interface_inputs[1]) {
|
||||
if (socket_type == SOCK_RGBA) {
|
||||
/* Second socket is the mask input. */
|
||||
render_mask_input(this->mod_context_, this->timeline_frame_);
|
||||
if (this->mask_.is_allocated()) {
|
||||
input_result->set_type(this->mask_.type());
|
||||
input_result->set_precision(this->mask_.precision());
|
||||
input_result->share_data(this->mask_);
|
||||
input_result->set_transformation(this->mod_context_.transform_comp_result);
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
}
|
||||
else if (valid_socket_type) {
|
||||
PointerRNA input_props_ptr = RNA_pointer_get(&inputs_ptr, input_socket->identifier);
|
||||
input_result->allocate_single_value();
|
||||
set_single_input_from_rna_value(&input_props_ptr,
|
||||
socket_type,
|
||||
*input_result,
|
||||
get_socket_dimension(input_socket, socket_type));
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
|
||||
node_group_operation.map_input_to_result(input_socket->identifier, input_result);
|
||||
inputs.append(std::unique_ptr<Result>(input_result));
|
||||
}
|
||||
|
||||
{
|
||||
PRF_scope_with_name("SeqCompositorEvaluate", ProfileCategory::Draw);
|
||||
node_group_operation.evaluate();
|
||||
}
|
||||
this->write_outputs(node_group, node_group_operation, *this->image_buffer_);
|
||||
}
|
||||
|
||||
/* Render mask - similar to #modifier_render_mask_input except for the Mask ID
|
||||
* path we do a more efficient approach than rendering into a full ImBuf. */
|
||||
void render_mask_input(const ModifierApplyContext &context, int timeline_frame)
|
||||
{
|
||||
PRF_scope_with_name("SeqRenderMaskInput", ProfileCategory::Draw);
|
||||
const StripModifierData &smd = this->modifier_data_->modifier;
|
||||
if (smd.mask_input_type == STRIP_MASK_INPUT_STRIP && smd.mask_strip) {
|
||||
this->mask_buffer_ = seq_render_strip(&context.render_data,
|
||||
&context.render_state,
|
||||
smd.mask_strip,
|
||||
timeline_frame)
|
||||
.image;
|
||||
if (this->mask_buffer_ != nullptr) {
|
||||
this->create_result_from_input(this->mask_, *this->mask_buffer_);
|
||||
this->owns_mask_ = true;
|
||||
}
|
||||
}
|
||||
else if (smd.mask_input_type == STRIP_MASK_INPUT_ID && smd.mask_id) {
|
||||
int frame_index = 0;
|
||||
if (smd.mask_time == STRIP_MASK_TIME_RELATIVE) {
|
||||
frame_index = smd.mask_id->sfra + timeline_frame - context.strip.start;
|
||||
}
|
||||
else if (smd.mask_time == STRIP_MASK_TIME_ABSOLUTE) {
|
||||
frame_index = timeline_frame;
|
||||
}
|
||||
|
||||
/* Mask is a grayscale value, similar to alpha, so conceptually it is already a
|
||||
* "linear" quantity. However, masks used to be turned into grayscale images and
|
||||
* interpreted as being in "sequencer working space" (default: sRGB), so keep at least
|
||||
* that behavior working as before -- if sequencer space is sRGB, convert value to
|
||||
* linear for the compositor. */
|
||||
const bool seq_space_is_srgb = IMB_colormanagement_space_name_is_srgb(
|
||||
context.render_data.scene->sequencer_colorspace_settings.name);
|
||||
|
||||
const int width = context.render_data.rectx;
|
||||
const int height = context.render_data.recty;
|
||||
this->mask_.set_type(compositor::ResultType::Float);
|
||||
this->mask_.share_data(
|
||||
this->cache_manager().cached_masks.get(*this,
|
||||
smd.mask_id,
|
||||
compositor::Domain(int2(width, height)),
|
||||
1.0f,
|
||||
true,
|
||||
frame_index,
|
||||
1,
|
||||
0.0f,
|
||||
seq_space_is_srgb));
|
||||
this->owns_mask_ = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void compositor_modifier_init_data(StripModifierData *strip_modifier_data)
|
||||
{
|
||||
SequencerCompositorModifierData *modifier_data =
|
||||
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
|
||||
modifier_data->node_group = nullptr;
|
||||
}
|
||||
|
||||
static void compositor_modifier_apply(ModifierApplyContext &context,
|
||||
StripModifierData *strip_modifier_data)
|
||||
{
|
||||
PRF_scope_with_name("SeqModCompositor", ProfileCategory::Draw);
|
||||
SequencerCompositorModifierData *modifier_data =
|
||||
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
|
||||
if (!modifier_data->node_group) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompositorCache &com_cache = context.render_data.scene->ed->runtime->ensure_compositor_cache();
|
||||
CompositorModifierContext com_mod_context(context, com_cache.get_cache_manager(), modifier_data);
|
||||
|
||||
if (com_mod_context.use_gpu()) {
|
||||
com_mod_context.set_gpu_supported(render_begin_gpu(context.render_data));
|
||||
}
|
||||
|
||||
com_cache.recreate_if_needed(
|
||||
com_mod_context.use_gpu(), com_mod_context.get_precision(), context.render_data.gpu_context);
|
||||
com_mod_context.evaluate();
|
||||
com_mod_context.cache_manager().reset();
|
||||
com_mod_context.free_resources();
|
||||
if (com_mod_context.use_gpu()) {
|
||||
render_end_gpu(context.render_data);
|
||||
}
|
||||
|
||||
context.result.translation += com_mod_context.get_result_translation();
|
||||
}
|
||||
|
||||
static PointerRNA *modifier_panel_get_property_pointers(Panel *panel)
|
||||
{
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
BLI_assert(!RNA_pointer_is_null(ptr));
|
||||
BLI_assert(RNA_struct_is_a(ptr->type, RNA_StripModifier));
|
||||
ui::panel_context_pointer_set(panel, "modifier", ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void compositor_modifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *modifier_ptr = modifier_panel_get_property_pointers(panel);
|
||||
nodes::draw_compositor_nodes_modifier_ui(*C, modifier_ptr, layout);
|
||||
}
|
||||
|
||||
static void compositor_modifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(
|
||||
region_type, eSeqModifierType_Compositor, compositor_modifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Compositor = {
|
||||
/*idname*/ "Compositor",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Compositor"),
|
||||
/*struct_name*/ "SequencerCompositorModifierData",
|
||||
/*struct_size*/ sizeof(SequencerCompositorModifierData),
|
||||
/*init_data*/ compositor_modifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ compositor_modifier_apply,
|
||||
/*panel_register*/ compositor_modifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,147 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_curve_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_c.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void curves_init_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_set_defaults(&cmd->curve_mapping, 4, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
}
|
||||
|
||||
static void curves_free_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_free_data(&cmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void curves_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
CurvesModifierData *cmd_target = reinterpret_cast<CurvesModifierData *>(target);
|
||||
|
||||
BKE_curvemapping_copy_data(&cmd_target->curve_mapping, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct CurvesApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
BKE_curvemapping_evaluate_premulRGBF(this->curve_mapping, result, input);
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void curves_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModCurves", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
|
||||
const float black[3] = {0.0f, 0.0f, 0.0f};
|
||||
const float white[3] = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
BKE_curvemapping_init(&cmd->curve_mapping);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, false);
|
||||
BKE_curvemapping_set_black_white(&cmd->curve_mapping, black, white);
|
||||
|
||||
CurvesApplyOp op;
|
||||
op.curve_mapping = &cmd->curve_mapping;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, true);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void curves_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
template_curve_mapping(&layout, ptr, "curve_mapping", 'c', false, false, false, true, false);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void curves_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Curves, curves_panel_draw);
|
||||
}
|
||||
|
||||
static void curves_write(BlendWriter *writer, const StripModifierData *smd)
|
||||
{
|
||||
const CurvesModifierData *cmd = reinterpret_cast<const CurvesModifierData *>(smd);
|
||||
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void curves_read(BlendDataReader *reader, StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Curves = {
|
||||
/*idname*/ "Curves",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Curves"),
|
||||
/*struct_name*/ "CurvesModifierData",
|
||||
/*struct_size*/ sizeof(CurvesModifierData),
|
||||
/*init_data*/ curves_init_data,
|
||||
/*free_data*/ curves_free_data,
|
||||
/*copy_data*/ curves_copy_data,
|
||||
/*apply*/ curves_apply,
|
||||
/*panel_register*/ curves_register,
|
||||
/*blend_write*/ curves_write,
|
||||
/*blend_read*/ curves_read,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,174 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_curve_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void hue_correct_init_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
int c;
|
||||
|
||||
BKE_curvemapping_set_defaults(&hcmd->curve_mapping, 1, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
hcmd->curve_mapping.preset = CURVE_PRESET_MID8;
|
||||
|
||||
for (c = 0; c < 3; c++) {
|
||||
CurveMap *cuma = &hcmd->curve_mapping.cm[c];
|
||||
BKE_curvemap_reset(
|
||||
cuma, &hcmd->curve_mapping.clipr, hcmd->curve_mapping.preset, CurveMapSlopeType::Positive);
|
||||
}
|
||||
/* use wrapping for all hue correct modifiers */
|
||||
hcmd->curve_mapping.flag |= CUMA_USE_WRAPPING;
|
||||
/* default to showing Saturation */
|
||||
hcmd->curve_mapping.cur = 1;
|
||||
}
|
||||
|
||||
static void hue_correct_free_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_free_data(&hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void hue_correct_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
HueCorrectModifierData *hcmd_target = reinterpret_cast<HueCorrectModifierData *>(target);
|
||||
|
||||
BKE_curvemapping_copy_data(&hcmd_target->curve_mapping, &hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct HueCorrectApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
|
||||
float3 hsv;
|
||||
rgb_to_hsv(input.x, input.y, input.z, &hsv.x, &hsv.y, &hsv.z);
|
||||
|
||||
/* adjust hue, scaling returned default 0.5 up to 1 */
|
||||
float f;
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 0, hsv.x);
|
||||
hsv.x += f - 0.5f;
|
||||
|
||||
/* adjust saturation, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 1, hsv.x);
|
||||
hsv.y *= (f * 2.0f);
|
||||
|
||||
/* adjust value, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 2, hsv.x);
|
||||
hsv.z *= (f * 2.0f);
|
||||
|
||||
hsv.x = hsv.x - floorf(hsv.x); /* mod 1.0 */
|
||||
hsv.y = math::clamp(hsv.y, 0.0f, 1.0f);
|
||||
|
||||
/* convert back to rgb */
|
||||
hsv_to_rgb(hsv.x, hsv.y, hsv.z, &result.x, &result.y, &result.z);
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void hue_correct_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModHueCorrect", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_init(&hcmd->curve_mapping);
|
||||
|
||||
HueCorrectApplyOp op;
|
||||
op.curve_mapping = &hcmd->curve_mapping;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void hue_correct_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
template_curve_mapping(&layout, ptr, "curve_mapping", 'h', false, false, false, false, false);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void hue_correct_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_HueCorrect, hue_correct_panel_draw);
|
||||
}
|
||||
|
||||
static void hue_correct_write(BlendWriter *writer, const StripModifierData *smd)
|
||||
{
|
||||
const HueCorrectModifierData *hmd = reinterpret_cast<const HueCorrectModifierData *>(smd);
|
||||
BKE_curvemapping_blend_write(writer, &hmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void hue_correct_read(BlendDataReader *reader, StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
BKE_curvemapping_blend_read(reader, &hmd->curve_mapping);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_HueCorrect = {
|
||||
/*idname*/ "HueCorrect",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Hue Correct"),
|
||||
/*struct_name*/ "HueCorrectModifierData",
|
||||
/*struct_size*/ sizeof(HueCorrectModifierData),
|
||||
/*init_data*/ hue_correct_init_data,
|
||||
/*free_data*/ hue_correct_free_data,
|
||||
/*copy_data*/ hue_correct_copy_data,
|
||||
/*apply*/ hue_correct_apply,
|
||||
/*panel_register*/ hue_correct_register,
|
||||
/*blend_write*/ hue_correct_write,
|
||||
/*blend_read*/ hue_correct_read,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,104 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct MaskApplyOp {
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float m = mask.load_mask_min();
|
||||
|
||||
if constexpr (std::is_same_v<ImageT, uchar>) {
|
||||
/* Byte buffer is straight, so only affect on alpha itself, this is
|
||||
* the only way to alpha-over byte strip after applying mask modifier. */
|
||||
image[3] = uchar(image[3] * m);
|
||||
}
|
||||
else if constexpr (std::is_same_v<ImageT, float>) {
|
||||
/* Float buffers are pre-multiplied, so need to pre-multiply color as well to make it
|
||||
* easy to alpha-over masked strip. */
|
||||
float4 pix(image);
|
||||
pix *= m;
|
||||
*reinterpret_cast<float4 *>(image) = pix;
|
||||
}
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void maskmodifier_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModMask", ProfileCategory::Draw);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
if (mask != nullptr && (mask->byte_data() != nullptr || mask->float_data() != nullptr)) {
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
|
||||
MaskApplyOp op;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
|
||||
/* Image has gained transparency. */
|
||||
context.result.image->color_mode = ImColorMode::RGBA;
|
||||
}
|
||||
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void maskmodifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
draw_mask_input_type_settings(C, layout, ptr);
|
||||
}
|
||||
|
||||
static void maskmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Mask, maskmodifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Mask = {
|
||||
/*idname*/ "Mask",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Mask"),
|
||||
/*struct_name*/ "SequencerMaskModifierData",
|
||||
/*struct_size*/ sizeof(SequencerMaskModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ maskmodifier_apply,
|
||||
/*panel_register*/ maskmodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
StripModifierTypeInfo seqModifierType_None = {
|
||||
/*idname*/ "None",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "None"),
|
||||
/*struct_name*/ "StripModifierData",
|
||||
/*struct_size*/ sizeof(StripModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ nullptr,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void echomodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
EchoModifierData *emd = reinterpret_cast<EchoModifierData *>(smd);
|
||||
|
||||
emd->delay = 1.0f;
|
||||
emd->feedback = 0.5f;
|
||||
emd->mix = 0.5f;
|
||||
}
|
||||
|
||||
static void echomodifier_draw(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
|
||||
col.prop(ptr, "delay", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "feedback", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "mix", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void echomodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Echo, echomodifier_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Echo = {
|
||||
/*idname*/ "Echo",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Echo"),
|
||||
/*struct_name*/ "EchoModifierData",
|
||||
/*struct_size*/ sizeof(EchoModifierData),
|
||||
/*init_data*/ echomodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ echomodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,96 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLO_read_write.hh"
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_sound.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void sound_equalizermodifier_draw(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &flow = layout.grid_flow(true, 0, true, false, false);
|
||||
RNA_BEGIN (ptr, sound_eq, "graphics") {
|
||||
PointerRNA curve_mapping = RNA_pointer_get(&sound_eq, "curve_mapping");
|
||||
const float clip_min_x = RNA_float_get(&curve_mapping, "clip_min_x");
|
||||
const float clip_max_x = RNA_float_get(&curve_mapping, "clip_max_x");
|
||||
|
||||
ui::Layout &col = flow.column(false);
|
||||
ui::Layout &split = col.split(0.4f, false);
|
||||
split.label(fmt::format("{:.2f}", clip_min_x), ICON_NONE);
|
||||
split.label("Hz", ICON_NONE);
|
||||
split.alignment_set(ui::LayoutAlign::Right);
|
||||
split.label(fmt::format("{:.2f}", clip_max_x), ICON_NONE);
|
||||
template_curve_mapping(&col, &sound_eq, "curve_mapping", 0, false, true, true, false, false);
|
||||
ui::Layout &row = col.row(false);
|
||||
row.alignment_set(ui::LayoutAlign::Center);
|
||||
row.label("dB", ICON_NONE);
|
||||
}
|
||||
RNA_END;
|
||||
}
|
||||
|
||||
static void sound_equalizermodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(
|
||||
region_type, eSeqModifierType_SoundEqualizer, sound_equalizermodifier_draw);
|
||||
}
|
||||
|
||||
static void sound_equalizermodifier_write(BlendWriter *writer, const StripModifierData *smd)
|
||||
{
|
||||
const SoundEqualizerModifierData *semd = reinterpret_cast<const SoundEqualizerModifierData *>(
|
||||
smd);
|
||||
for (EQCurveMappingData &eqcmd : semd->graphics) {
|
||||
writer->write_struct_by_name("EQCurveMappingData", &eqcmd);
|
||||
BKE_curvemapping_blend_write(writer, &eqcmd.curve_mapping);
|
||||
}
|
||||
}
|
||||
|
||||
static void sound_equalizermodifier_read(BlendDataReader *reader, StripModifierData *smd)
|
||||
{
|
||||
SoundEqualizerModifierData *semd = reinterpret_cast<SoundEqualizerModifierData *>(smd);
|
||||
BLO_read_struct_list(reader, EQCurveMappingData, &semd->graphics);
|
||||
for (EQCurveMappingData &eqcmd : semd->graphics) {
|
||||
BKE_curvemapping_blend_read(reader, &eqcmd.curve_mapping);
|
||||
}
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_SoundEqualizer = {
|
||||
/*idname*/ "SoundEqualizer",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Equalizer"),
|
||||
/*struct_name*/ "SoundEqualizerModifierData",
|
||||
/*struct_size*/ sizeof(SoundEqualizerModifierData),
|
||||
/*init_data*/ sound_equalizermodifier_init_data,
|
||||
/*free_data*/ sound_equalizermodifier_free,
|
||||
/*copy_data*/ sound_equalizermodifier_copy_data,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ sound_equalizermodifier_register,
|
||||
/*blend_write*/ sound_equalizermodifier_write,
|
||||
/*blend_read*/ sound_equalizermodifier_read,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,79 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void pitchmodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
PitchModifierData *pmd = reinterpret_cast<PitchModifierData *>(smd);
|
||||
pmd->mode = ePitchMode::PITCH_MODE_SEMITONES;
|
||||
pmd->semitones = 0;
|
||||
pmd->cents = 0;
|
||||
pmd->ratio = 1;
|
||||
pmd->preserve_formant = false;
|
||||
pmd->quality = ePitchQuality::PITCH_QUALITY_HIGH;
|
||||
}
|
||||
|
||||
static void pitchmodifier_draw(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
|
||||
col.prop(ptr, "mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
int mode = RNA_enum_get(ptr, "mode");
|
||||
if (mode == ePitchMode::PITCH_MODE_SEMITONES) {
|
||||
col.prop(ptr, "semitones", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "cents", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else if (mode == ePitchMode::PITCH_MODE_RATIO) {
|
||||
col.prop(ptr, "ratio", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
col.prop(ptr, "preserve_formant", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "quality", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void pitchmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Pitch, pitchmodifier_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Pitch = {
|
||||
/*idname*/ "Pitch",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Pitch"),
|
||||
/*struct_name*/ "PitchModifierData",
|
||||
/*struct_size*/ sizeof(PitchModifierData),
|
||||
/*init_data*/ pitchmodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ pitchmodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,377 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct AvgLogLum {
|
||||
const SequencerTonemapModifierData *tmmd;
|
||||
float al;
|
||||
float auto_key;
|
||||
float lav;
|
||||
float3 cav;
|
||||
float igm;
|
||||
};
|
||||
|
||||
static void tonemapmodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
SequencerTonemapModifierData *tmmd = reinterpret_cast<SequencerTonemapModifierData *>(smd);
|
||||
/* Same as tone-map compositor node. */
|
||||
tmmd->type = SEQ_TONEMAP_RD_PHOTORECEPTOR;
|
||||
tmmd->key = 0.18f;
|
||||
tmmd->offset = 1.0f;
|
||||
tmmd->gamma = 1.0f;
|
||||
tmmd->intensity = 0.0f;
|
||||
tmmd->contrast = 0.0f;
|
||||
tmmd->adaptation = 1.0f;
|
||||
tmmd->correction = 0.0f;
|
||||
}
|
||||
|
||||
/* Convert chunk of float image pixels to scene linear space, in-place. */
|
||||
static void pixels_to_scene_linear_float(const ColorSpace *colorspace,
|
||||
float4 *pixels,
|
||||
int64_t count)
|
||||
{
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
reinterpret_cast<float *>(pixels), int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
/* Convert chunk of byte image pixels to scene linear space, into a destination array. */
|
||||
static void pixels_to_scene_linear_byte(const ColorSpace *colorspace,
|
||||
const uchar *pixels,
|
||||
float4 *dst,
|
||||
int64_t count)
|
||||
{
|
||||
const uchar *bptr = pixels;
|
||||
float4 *dst_ptr = dst;
|
||||
for (int64_t i = 0; i < count; i++) {
|
||||
straight_uchar_to_premul_float(*dst_ptr, bptr);
|
||||
bptr += 4;
|
||||
dst_ptr++;
|
||||
}
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
reinterpret_cast<float *>(dst), int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
static void scene_linear_to_image_chunk_byte(float4 *src, ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
reinterpret_cast<float *>(src), int(range.size()), 1, 4, colorspace);
|
||||
const float4 *src_ptr = src;
|
||||
uchar *bptr = ibuf->byte_data_for_write();
|
||||
for (const int64_t idx : range) {
|
||||
premul_float_to_straight_uchar(bptr + idx * 4, *src_ptr);
|
||||
src_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
struct AreaLuminance {
|
||||
int64_t pixel_count = 0;
|
||||
double sum = 0.0f;
|
||||
float3 color_sum = {0, 0, 0};
|
||||
double log_sum = 0.0;
|
||||
float min = FLT_MAX;
|
||||
float max = -FLT_MAX;
|
||||
};
|
||||
|
||||
static void scene_linear_to_image_chunk_float(ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->float_buffer.colorspace;
|
||||
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_data_for_write());
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
reinterpret_cast<float *>(fptr + range.first()), int(range.size()), 1, 4, colorspace);
|
||||
}
|
||||
|
||||
template<typename MaskSampler>
|
||||
static void tonemap_simple(
|
||||
float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
|
||||
{
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = *scene_linear;
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz() * avg.al;
|
||||
float3 d = pixel + avg.tmmd->offset;
|
||||
pixel.x /= (d.x == 0.0f) ? 1.0f : d.x;
|
||||
pixel.y /= (d.y == 0.0f) ? 1.0f : d.y;
|
||||
pixel.z /= (d.z == 0.0f) ? 1.0f : d.z;
|
||||
const float igm = avg.igm;
|
||||
if (igm != 0.0f) {
|
||||
pixel.x = powf(math::max(pixel.x, 0.0f), igm);
|
||||
pixel.y = powf(math::max(pixel.y, 0.0f), igm);
|
||||
pixel.z = powf(math::max(pixel.z, 0.0f), igm);
|
||||
}
|
||||
|
||||
/* Apply mask. */
|
||||
float4 result(pixel.x, pixel.y, pixel.z, input.w);
|
||||
mask.apply_mask(input, result);
|
||||
*scene_linear = result;
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename MaskSampler>
|
||||
static void tonemap_rd_photoreceptor(
|
||||
float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
|
||||
{
|
||||
const float f = expf(-avg.tmmd->intensity);
|
||||
const float m = (avg.tmmd->contrast > 0.0f) ? avg.tmmd->contrast :
|
||||
(0.3f + 0.7f * powf(avg.auto_key, 1.4f));
|
||||
const float ic = 1.0f - avg.tmmd->correction, ia = 1.0f - avg.tmmd->adaptation;
|
||||
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = *scene_linear;
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz();
|
||||
const float L = IMB_colormanagement_get_luminance(pixel);
|
||||
float I_l = pixel.x + ic * (L - pixel.x);
|
||||
float I_g = avg.cav.x + ic * (avg.lav - avg.cav.x);
|
||||
float I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.x /= std::max(pixel.x + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.y + ic * (L - pixel.y);
|
||||
I_g = avg.cav.y + ic * (avg.lav - avg.cav.y);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.y /= std::max(pixel.y + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.z + ic * (L - pixel.z);
|
||||
I_g = avg.cav.z + ic * (avg.lav - avg.cav.z);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.z /= std::max(pixel.z + powf(f * I_a, m), 1.0e-30f);
|
||||
|
||||
/* Apply mask. */
|
||||
float4 result(pixel.x, pixel.y, pixel.z, input.w);
|
||||
mask.apply_mask(input, result);
|
||||
*scene_linear = result;
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TonemapApplyOp {
|
||||
AreaLuminance lum;
|
||||
AvgLogLum data;
|
||||
eModTonemapType type;
|
||||
ImBuf *ibuf;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
const IndexRange pixel_range(y_range.first() * image_x, y_range.size() * image_x);
|
||||
if constexpr (std::is_same_v<ImageT, float>) {
|
||||
/* Float pixels: no need for temporary storage. Luminance calculation already converted
|
||||
* data to scene linear. */
|
||||
float4 *pixels = (float4 *)(image + y_range.first() * image_x * 4);
|
||||
if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(pixels, mask, image_x, y_range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(pixels, mask, image_x, y_range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_float(this->ibuf, pixel_range);
|
||||
}
|
||||
else {
|
||||
/* Byte pixels: temporary storage for scene linear pixel values. */
|
||||
Array<float4> scene_linear(pixel_range.size());
|
||||
pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace,
|
||||
ibuf->byte_data() + pixel_range.first() * 4,
|
||||
scene_linear.data(),
|
||||
pixel_range.size());
|
||||
if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(scene_linear.data(), mask, image_x, y_range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(scene_linear.data(), mask, image_x, y_range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_byte(scene_linear.data(), this->ibuf, pixel_range);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void tonemap_calc_chunk_luminance(const int width,
|
||||
const IndexRange y_range,
|
||||
const float4 *scene_linear,
|
||||
AreaLuminance &r_lum)
|
||||
{
|
||||
for ([[maybe_unused]] const int y : y_range) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
float4 pixel = *scene_linear;
|
||||
r_lum.pixel_count++;
|
||||
float L = IMB_colormanagement_get_luminance(pixel);
|
||||
r_lum.sum += L;
|
||||
r_lum.color_sum.x += pixel.x;
|
||||
r_lum.color_sum.y += pixel.y;
|
||||
r_lum.color_sum.z += pixel.z;
|
||||
r_lum.log_sum += logf(math::max(L, 0.0f) + 1e-5f);
|
||||
r_lum.max = math::max(r_lum.max, L);
|
||||
r_lum.min = math::min(r_lum.min, L);
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AreaLuminance tonemap_calc_input_luminance(ImBuf *ibuf)
|
||||
{
|
||||
float *float_data = ibuf->float_data_for_write();
|
||||
AreaLuminance lum;
|
||||
lum = threading::parallel_reduce(
|
||||
IndexRange(ibuf->y),
|
||||
32,
|
||||
lum,
|
||||
/* Calculate luminance for a chunk. */
|
||||
[&](const IndexRange y_range, const AreaLuminance &init) {
|
||||
AreaLuminance lum = init;
|
||||
const int64_t chunk_size = y_range.size() * ibuf->x;
|
||||
/* For float images, convert to scene-linear in place. The rest
|
||||
* of tone-mapper can then continue with scene-linear values. */
|
||||
if (float_data != nullptr) {
|
||||
float4 *fptr = reinterpret_cast<float4 *>(float_data);
|
||||
fptr += y_range.first() * ibuf->x;
|
||||
pixels_to_scene_linear_float(ibuf->float_buffer.colorspace, fptr, chunk_size);
|
||||
tonemap_calc_chunk_luminance(ibuf->x, y_range, fptr, lum);
|
||||
}
|
||||
else {
|
||||
const uchar *bptr = ibuf->byte_data() + y_range.first() * ibuf->x * 4;
|
||||
Array<float4> scene_linear(chunk_size);
|
||||
pixels_to_scene_linear_byte(
|
||||
ibuf->byte_buffer.colorspace, bptr, scene_linear.data(), chunk_size);
|
||||
tonemap_calc_chunk_luminance(ibuf->x, y_range, scene_linear.data(), lum);
|
||||
}
|
||||
return lum;
|
||||
},
|
||||
/* Reduce luminance results. */
|
||||
[&](const AreaLuminance &a, const AreaLuminance &b) {
|
||||
AreaLuminance res;
|
||||
res.pixel_count = a.pixel_count + b.pixel_count;
|
||||
res.sum = a.sum + b.sum;
|
||||
res.color_sum = a.color_sum + b.color_sum;
|
||||
res.log_sum = a.log_sum + b.log_sum;
|
||||
res.min = math::min(a.min, b.min);
|
||||
res.max = math::max(a.max, b.max);
|
||||
return res;
|
||||
});
|
||||
return lum;
|
||||
}
|
||||
|
||||
static void tonemapmodifier_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModTonemap", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const SequencerTonemapModifierData *tmmd =
|
||||
reinterpret_cast<const SequencerTonemapModifierData *>(smd);
|
||||
|
||||
TonemapApplyOp op;
|
||||
op.type = tmmd->type;
|
||||
op.ibuf = context.result.image;
|
||||
op.lum = tonemap_calc_input_luminance(context.result.image);
|
||||
if (op.lum.pixel_count == 0) {
|
||||
return; /* Strip is zero size or off-screen. */
|
||||
}
|
||||
|
||||
op.data.tmmd = tmmd;
|
||||
op.data.lav = op.lum.sum / op.lum.pixel_count;
|
||||
op.data.cav.x = op.lum.color_sum.x / op.lum.pixel_count;
|
||||
op.data.cav.y = op.lum.color_sum.y / op.lum.pixel_count;
|
||||
op.data.cav.z = op.lum.color_sum.z / op.lum.pixel_count;
|
||||
float maxl = log(double(op.lum.max) + 1e-5f);
|
||||
float minl = log(double(op.lum.min) + 1e-5f);
|
||||
float avl = op.lum.log_sum / op.lum.pixel_count;
|
||||
op.data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f;
|
||||
float al = exp(double(avl));
|
||||
op.data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
|
||||
op.data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
|
||||
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
const int tonemap_type = RNA_enum_get(ptr, "tonemap_type");
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
col.prop(ptr, "tonemap_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
if (tonemap_type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
col.prop(ptr, "intensity", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "contrast", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "adaptation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "correction", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else if (tonemap_type == SEQ_TONEMAP_RH_SIMPLE) {
|
||||
col.prop(ptr, "key", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "gamma", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemapmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Tonemap, tonemapmodifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Tonemap = {
|
||||
/*idname*/ "Tonemap",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Tonemap"),
|
||||
/*struct_name*/ "SequencerTonemapModifierData",
|
||||
/*struct_size*/ sizeof(SequencerTonemapModifierData),
|
||||
/*init_data*/ tonemapmodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ tonemapmodifier_apply,
|
||||
/*panel_register*/ tonemapmodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,122 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void whiteBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
WhiteBalanceModifierData *cbmd = reinterpret_cast<WhiteBalanceModifierData *>(smd);
|
||||
copy_v3_fl(cbmd->white_value, 1.0f);
|
||||
}
|
||||
|
||||
struct WhiteBalanceApplyOp {
|
||||
float multiplier[3];
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
#if 0
|
||||
mul_v3_v3(result, multiplier);
|
||||
#else
|
||||
/* similar to division without the clipping */
|
||||
for (int i = 0; i < 3; i++) {
|
||||
/* Prevent pow argument from being negative. This whole math
|
||||
* breaks down overall with any HDR colors; would be good to
|
||||
* revisit and do something more proper. */
|
||||
float f = max_ff(1.0f - input[i], 0.0f);
|
||||
result[i] = 1.0f - powf(f, this->multiplier[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void whiteBalance_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModWhiteBalance", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const WhiteBalanceModifierData *data = reinterpret_cast<const WhiteBalanceModifierData *>(smd);
|
||||
|
||||
WhiteBalanceApplyOp op;
|
||||
op.multiplier[0] = (data->white_value[0] != 0.0f) ? 1.0f / data->white_value[0] : FLT_MAX;
|
||||
op.multiplier[1] = (data->white_value[1] != 0.0f) ? 1.0f / data->white_value[1] : FLT_MAX;
|
||||
op.multiplier[2] = (data->white_value[2] != 0.0f) ? 1.0f / data->white_value[2] : FLT_MAX;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void whiteBalance_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
layout.prop(ptr, "white_value", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void whiteBalance_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_WhiteBalance, whiteBalance_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_WhiteBalance = {
|
||||
/*idname*/ "WhiteBalance",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "White Balance"),
|
||||
/*struct_name*/ "WhiteBalanceModifierData",
|
||||
/*struct_size*/ sizeof(WhiteBalanceModifierData),
|
||||
/*init_data*/ whiteBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ whiteBalance_apply,
|
||||
/*panel_register*/ whiteBalance_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,731 @@
|
||||
/* SPDX-FileCopyrightText: 2012-2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_mask_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_idprop.hh"
|
||||
#include "BKE_screen.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_modifiertypes.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
static bool modifier_has_persistent_uid(const Strip &strip, int uid)
|
||||
{
|
||||
for (StripModifierData &smd : strip.modifiers) {
|
||||
if (smd.persistent_uid == uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd)
|
||||
{
|
||||
uint64_t hash = get_default_hash(StringRef(smd.name));
|
||||
RandomNumberGenerator rng{uint32_t(hash)};
|
||||
while (true) {
|
||||
const int new_uid = rng.get_int32();
|
||||
if (new_uid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (modifier_has_persistent_uid(strip, new_uid)) {
|
||||
continue;
|
||||
}
|
||||
smd.persistent_uid = new_uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool modifier_persistent_uids_are_valid(const Strip &strip)
|
||||
{
|
||||
Set<int> uids;
|
||||
int modifiers_num = 0;
|
||||
for (StripModifierData &smd : strip.modifiers) {
|
||||
if (smd.persistent_uid <= 0) {
|
||||
return false;
|
||||
}
|
||||
uids.add(smd.persistent_uid);
|
||||
modifiers_num++;
|
||||
}
|
||||
if (uids.size() != modifiers_num) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void modifier_ops_extra_draw(bContext *C, ui::Layout *layout, void *smd_v)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
Strip *strip = seq::select_active_get(sequencer_scene);
|
||||
if (!strip) {
|
||||
return;
|
||||
}
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_v);
|
||||
PointerRNA mod_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, RNA_StripModifier, smd);
|
||||
|
||||
PointerRNA op_ptr;
|
||||
/* Duplicate. */
|
||||
op_ptr = layout->op("SEQUENCER_OT_strip_modifier_duplicate",
|
||||
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Duplicate"),
|
||||
ICON_DUPLICATE);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
/* Copy to selected. */
|
||||
op_ptr = layout->op("SEQUENCER_OT_strip_modifier_copy",
|
||||
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"),
|
||||
0);
|
||||
RNA_enum_set(&op_ptr, "type", /*SEQ_MODIFIER_COPY_APPEND*/ 1);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
|
||||
layout->separator();
|
||||
|
||||
/* Move to first. */
|
||||
{
|
||||
ui::Layout &row = layout->row(false);
|
||||
op_ptr = row.op("SEQUENCER_OT_strip_modifier_move_to_index",
|
||||
IFACE_("Move to First"),
|
||||
ICON_TRIA_UP,
|
||||
wm::OpCallContext::InvokeDefault,
|
||||
UI_ITEM_NONE);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
RNA_int_set(&op_ptr, "index", 0);
|
||||
row.enabled_set(smd->prev != nullptr);
|
||||
}
|
||||
|
||||
/* Move to last. */
|
||||
{
|
||||
ui::Layout &row = layout->row(false);
|
||||
op_ptr = row.op("SEQUENCER_OT_strip_modifier_move_to_index",
|
||||
IFACE_("Move to Last"),
|
||||
ICON_TRIA_DOWN,
|
||||
wm::OpCallContext::InvokeDefault,
|
||||
UI_ITEM_NONE);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
RNA_int_set(&op_ptr, "index", strip->modifiers.count() - 1);
|
||||
row.enabled_set(smd->next != nullptr);
|
||||
}
|
||||
|
||||
if (smd->type == eSeqModifierType_Compositor) {
|
||||
layout->separator();
|
||||
layout->prop(&mod_ptr, "show_group_selector", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
static void modifier_panel_header(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
|
||||
/* Don't use #modifier_panel_get_property_pointers, we don't want to lock the header. */
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(ptr->data);
|
||||
|
||||
ui::panel_context_pointer_set(panel, "modifier", ptr);
|
||||
|
||||
/* Modifier Icon. */
|
||||
ui::Layout *sub = &layout.row(true);
|
||||
sub->emboss_set(ui::EmbossType::None);
|
||||
PointerRNA active_op_ptr = sub->op(
|
||||
"SEQUENCER_OT_strip_modifier_set_active", "", RNA_struct_ui_icon(ptr->type));
|
||||
RNA_string_set(&active_op_ptr, "modifier", smd->name);
|
||||
|
||||
ui::Layout &row = layout.row(true);
|
||||
|
||||
/* Modifier Name.
|
||||
* Count how many buttons are added to the header to check if there is enough space. */
|
||||
int buttons_number = 0;
|
||||
ui::Layout &name_row = row.row(true);
|
||||
|
||||
if (!smd->is_type_sound()) {
|
||||
sub = &row.row(true);
|
||||
sub->prop(ptr, "show_preview", UI_ITEM_NONE, "", ICON_NONE);
|
||||
buttons_number++;
|
||||
}
|
||||
|
||||
sub = &row.row(true);
|
||||
sub->prop(ptr, "enable", UI_ITEM_NONE, "", ICON_NONE);
|
||||
buttons_number++;
|
||||
|
||||
/* Extra operators menu. */
|
||||
row.menu_fn("", ICON_DOWNARROW_HLT, modifier_ops_extra_draw, smd);
|
||||
|
||||
/* Delete button. */
|
||||
sub = &row.row(false);
|
||||
sub->emboss_set(ui::EmbossType::None);
|
||||
PointerRNA remove_op_ptr = sub->op("SEQUENCER_OT_strip_modifier_remove", "", ICON_X);
|
||||
RNA_string_set(&remove_op_ptr, "name", smd->name);
|
||||
buttons_number++;
|
||||
|
||||
bool display_name = (panel->sizex / UI_UNIT_X - buttons_number > 5) || (panel->sizex == 0);
|
||||
if (display_name) {
|
||||
name_row.prop(ptr, "name", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
else {
|
||||
row.alignment_set(ui::LayoutAlign::Right);
|
||||
}
|
||||
|
||||
/* Extra padding for delete button. */
|
||||
layout.separator();
|
||||
}
|
||||
|
||||
void draw_mask_input_type_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
Editing *ed = seq::editing_get(sequencer_scene);
|
||||
|
||||
const int input_mask_type = RNA_enum_get(ptr, "input_mask_type");
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
ui::Layout *row = &col.row(true);
|
||||
row->prop(ptr, "input_mask_type", ui::ITEM_R_EXPAND, IFACE_("Type"), ICON_NONE);
|
||||
|
||||
if (input_mask_type == STRIP_MASK_INPUT_STRIP) {
|
||||
PointerRNA sequences_object = RNA_pointer_create_discrete(
|
||||
&sequencer_scene->id, RNA_SequenceEditor, ed);
|
||||
col.prop_search(
|
||||
ptr, "input_mask_strip", &sequences_object, "strips_all", IFACE_("Mask"), ICON_NONE);
|
||||
}
|
||||
else {
|
||||
col.prop(ptr, "input_mask_id", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
row = &col.row(true);
|
||||
row->prop(ptr, "mask_time", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
bool modifier_ui_poll(const bContext *C, PanelType * /*pt*/)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
if (!sequencer_scene) {
|
||||
return false;
|
||||
}
|
||||
Strip *active_strip = seq::select_active_get(sequencer_scene);
|
||||
return active_strip != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a modifier to the index it's moved to after a drag and drop.
|
||||
*/
|
||||
static void modifier_reorder(bContext *C, Panel *panel, const int new_index)
|
||||
{
|
||||
PointerRNA *smd_ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_ptr->data);
|
||||
|
||||
wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_strip_modifier_move_to_index", false);
|
||||
PointerRNA props_ptr = WM_operator_properties_create_ptr(ot);
|
||||
RNA_string_set(&props_ptr, "modifier", smd->name);
|
||||
RNA_int_set(&props_ptr, "index", new_index);
|
||||
WM_operator_name_call_ptr(C, ot, wm::OpCallContext::InvokeDefault, &props_ptr, nullptr);
|
||||
WM_operator_properties_free(&props_ptr);
|
||||
}
|
||||
|
||||
static short get_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
PointerRNA *smd_ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_ptr->data);
|
||||
return smd->ui_expand_flag;
|
||||
}
|
||||
|
||||
static void set_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel, short expand_flag)
|
||||
{
|
||||
PointerRNA *smd_ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_ptr->data);
|
||||
smd->ui_expand_flag = expand_flag;
|
||||
}
|
||||
|
||||
PanelType *modifier_panel_register(ARegionType *region_type,
|
||||
const eStripModifierType type,
|
||||
PanelDrawFn draw)
|
||||
{
|
||||
PanelType *panel_type = MEM_new_zeroed<PanelType>(__func__);
|
||||
|
||||
modifier_type_panel_id(type, panel_type->idname);
|
||||
STRNCPY_UTF8(panel_type->label, "");
|
||||
STRNCPY_UTF8(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
|
||||
STRNCPY_UTF8(panel_type->active_property, "is_active");
|
||||
STRNCPY_UTF8(panel_type->context, "strip_modifier");
|
||||
|
||||
panel_type->draw_header = modifier_panel_header;
|
||||
panel_type->draw = draw;
|
||||
panel_type->poll = modifier_ui_poll;
|
||||
|
||||
/* Give the panel the special flag that says it was built here and corresponds to a
|
||||
* modifier rather than a #PanelType. */
|
||||
panel_type->flag = PANEL_TYPE_HEADER_EXPAND | PANEL_TYPE_INSTANCED;
|
||||
panel_type->reorder = modifier_reorder;
|
||||
panel_type->get_list_data_expand_flag = get_strip_modifier_expand_flag;
|
||||
panel_type->set_list_data_expand_flag = set_strip_modifier_expand_flag;
|
||||
|
||||
BLI_addtail(®ion_type->paneltypes, panel_type);
|
||||
|
||||
return panel_type;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
float4 load_pixel_premul(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
straight_uchar_to_premul_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
float4 load_pixel_premul(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
void store_pixel_premul(float4 pix, uchar *ptr)
|
||||
{
|
||||
premul_float_to_straight_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
void store_pixel_premul(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
float4 load_pixel_raw(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
rgba_uchar_to_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
float4 load_pixel_raw(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
void store_pixel_raw(float4 pix, uchar *ptr)
|
||||
{
|
||||
rgba_float_to_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
void store_pixel_raw(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
ImBuf *modifier_render_mask_input(const ModifierApplyContext &context,
|
||||
const StripModifierData &smd)
|
||||
{
|
||||
ImBuf *mask = nullptr;
|
||||
|
||||
if (smd.mask_input_type == STRIP_MASK_INPUT_STRIP) {
|
||||
if (smd.mask_strip) {
|
||||
mask = seq_render_strip(&context.render_data,
|
||||
&context.render_state,
|
||||
smd.mask_strip,
|
||||
context.timeline_frame)
|
||||
.image;
|
||||
}
|
||||
}
|
||||
else if (smd.mask_input_type == STRIP_MASK_INPUT_ID) {
|
||||
int frame_offset = 0;
|
||||
if (smd.mask_time == STRIP_MASK_TIME_RELATIVE) {
|
||||
frame_offset = context.strip.start;
|
||||
}
|
||||
else if (smd.mask_time == STRIP_MASK_TIME_ABSOLUTE) {
|
||||
frame_offset = smd.mask_id ? smd.mask_id->sfra : 0;
|
||||
}
|
||||
|
||||
/* Note that we do not request mask to be float image: if it is that is
|
||||
* fine, but if it is a byte image then we also just take that without
|
||||
* extra memory allocations or conversions. All modifiers are expected
|
||||
* to handle mask being either type. */
|
||||
mask = seq_render_mask(context.render_data.depsgraph,
|
||||
context.render_data.rectx,
|
||||
context.render_data.recty,
|
||||
smd.mask_id,
|
||||
context.timeline_frame - frame_offset,
|
||||
false);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Public Modifier Functions
|
||||
* \{ */
|
||||
|
||||
static StripModifierTypeInfo *modifiersTypes[NUM_STRIP_MODIFIER_TYPES] = {nullptr};
|
||||
|
||||
static void modifier_types_init(StripModifierTypeInfo *types[])
|
||||
{
|
||||
#define INIT_TYPE(typeName) (types[eSeqModifierType_##typeName] = &seqModifierType_##typeName)
|
||||
INIT_TYPE(None);
|
||||
INIT_TYPE(BrightContrast);
|
||||
INIT_TYPE(ColorBalance);
|
||||
INIT_TYPE(Compositor);
|
||||
INIT_TYPE(Curves);
|
||||
INIT_TYPE(HueCorrect);
|
||||
INIT_TYPE(Mask);
|
||||
INIT_TYPE(SoundEqualizer);
|
||||
INIT_TYPE(Pitch);
|
||||
INIT_TYPE(Echo);
|
||||
INIT_TYPE(Tonemap);
|
||||
INIT_TYPE(WhiteBalance);
|
||||
#undef INIT_TYPE
|
||||
}
|
||||
|
||||
void modifiers_init()
|
||||
{
|
||||
modifier_types_init(modifiersTypes);
|
||||
}
|
||||
|
||||
const StripModifierTypeInfo *modifier_type_info_get(eStripModifierType type)
|
||||
{
|
||||
if (type <= 0 || type >= NUM_STRIP_MODIFIER_TYPES) {
|
||||
return nullptr;
|
||||
}
|
||||
return modifiersTypes[type];
|
||||
}
|
||||
|
||||
StripModifierData *modifier_new(Strip *strip, const char *name, eStripModifierType type)
|
||||
{
|
||||
StripModifierData *smd;
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(type);
|
||||
|
||||
smd = static_cast<StripModifierData *>(MEM_new_zeroed(smti->struct_size, "sequence modifier"));
|
||||
|
||||
smd->type = type;
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_EXPANDED | STRIP_MODIFIER_FLAG_SHOW_PREVIEW;
|
||||
smd->ui_expand_flag |= UI_PANEL_DATA_EXPAND_ROOT;
|
||||
smd->runtime = MEM_new<StripModifierDataRuntime>(__func__);
|
||||
|
||||
if (!name || !name[0]) {
|
||||
STRNCPY_UTF8(smd->name, CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name));
|
||||
}
|
||||
else {
|
||||
STRNCPY_UTF8(smd->name, name);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip->modifiers, smd);
|
||||
|
||||
if (ELEM(strip->type, STRIP_TYPE_SOUND, STRIP_TYPE_SOUND_HD)) {
|
||||
strip->runtime->sound_modifiers_count++;
|
||||
}
|
||||
|
||||
modifier_unique_name(strip, smd);
|
||||
|
||||
if (smti->init_data) {
|
||||
smti->init_data(smd);
|
||||
}
|
||||
|
||||
modifier_set_active(strip, smd);
|
||||
|
||||
return smd;
|
||||
}
|
||||
|
||||
bool modifier_remove(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
if (BLI_findindex(&strip->modifiers, smd) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
/* Prefer the next modifier but use the previous if this modifier is the last in the list. */
|
||||
if (smd->next != nullptr) {
|
||||
modifier_set_active(strip, smd->next);
|
||||
}
|
||||
else if (smd->prev != nullptr) {
|
||||
modifier_set_active(strip, smd->prev);
|
||||
}
|
||||
}
|
||||
|
||||
BLI_remlink(&strip->modifiers, smd);
|
||||
modifier_free(smd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void modifier_clear(Strip *strip)
|
||||
{
|
||||
StripModifierData *smd, *smd_next;
|
||||
|
||||
for (smd = static_cast<StripModifierData *>(strip->modifiers.first); smd; smd = smd_next) {
|
||||
smd_next = smd->next;
|
||||
modifier_free(smd);
|
||||
}
|
||||
|
||||
strip->modifiers.clear_no_delete();
|
||||
}
|
||||
|
||||
void modifier_free(StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
if (smti && smti->free_data) {
|
||||
smti->free_data(smd);
|
||||
}
|
||||
|
||||
if (smd->runtime) {
|
||||
MEM_delete(smd->runtime);
|
||||
}
|
||||
if (smd->system_properties != nullptr) {
|
||||
IDP_FreeProperty_ex(smd->system_properties, false);
|
||||
}
|
||||
|
||||
MEM_delete(smd);
|
||||
}
|
||||
|
||||
void modifier_unique_name(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
BLI_uniquename(&strip->modifiers,
|
||||
smd,
|
||||
CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name),
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(smd->name));
|
||||
}
|
||||
|
||||
StripModifierData *modifier_find_by_name(Strip *strip, const char *name)
|
||||
{
|
||||
return static_cast<StripModifierData *>(
|
||||
BLI_findstring(&(strip->modifiers), name, offsetof(StripModifierData, name)));
|
||||
}
|
||||
|
||||
static bool skip_modifier(Scene *scene, const StripModifierData *smd, int timeline_frame)
|
||||
{
|
||||
using namespace blender::seq;
|
||||
|
||||
if (smd->mask_strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const bool strip_has_ended_skip = smd->mask_input_type == STRIP_MASK_INPUT_STRIP &&
|
||||
smd->mask_time == STRIP_MASK_TIME_RELATIVE &&
|
||||
!smd->mask_strip->intersects_frame(scene, timeline_frame);
|
||||
const bool missing_data_skip = !strip_has_valid_data(smd->mask_strip) ||
|
||||
media_presence_is_missing(scene, smd->mask_strip);
|
||||
|
||||
return strip_has_ended_skip || missing_data_skip;
|
||||
}
|
||||
|
||||
void modifier_apply_stack(ModifierApplyContext &context)
|
||||
{
|
||||
if (context.strip.modifiers.first == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (StripModifierData &smd : context.strip.modifiers) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd.type);
|
||||
|
||||
/* could happen if modifier is being removed or not exists in current version of blender */
|
||||
if (!smti) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool show_preview = (smd.flag & STRIP_MODIFIER_FLAG_SHOW_PREVIEW) != 0;
|
||||
const bool show_render = (smd.flag & STRIP_MODIFIER_FLAG_MUTE) == 0;
|
||||
|
||||
if (context.render_data.render && !show_render) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!context.render_data.render && !show_preview) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (smti->apply && !skip_modifier(context.render_data.scene, &smd, context.timeline_frame)) {
|
||||
smti->apply(context, &smd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src, const int flag)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type);
|
||||
StripModifierData *mod_new = MEM_dupalloc(mod_src);
|
||||
|
||||
mod_new->system_properties = nullptr;
|
||||
if (mod_src->system_properties) {
|
||||
mod_new->system_properties = IDP_CopyProperty_ex(mod_src->system_properties, flag);
|
||||
}
|
||||
|
||||
mod_new->runtime = MEM_new<StripModifierDataRuntime>(__func__);
|
||||
|
||||
if (smti && smti->copy_data) {
|
||||
smti->copy_data(mod_new, mod_src);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip_dst.modifiers, mod_new);
|
||||
BLI_uniquename(&strip_dst.modifiers,
|
||||
mod_new,
|
||||
"Strip Modifier",
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(StripModifierData::name));
|
||||
return mod_new;
|
||||
}
|
||||
|
||||
void modifier_list_copy(Strip *strip_new, Strip *strip, const int flag)
|
||||
{
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
modifier_copy(*strip_new, &smd, flag);
|
||||
}
|
||||
}
|
||||
|
||||
bool strip_supports_modifiers(const Strip *strip)
|
||||
{
|
||||
return (strip->type != STRIP_TYPE_SOUND);
|
||||
}
|
||||
|
||||
bool modifier_move_to_index(Strip *strip, StripModifierData *smd, const int new_index)
|
||||
{
|
||||
const int current_index = BLI_findindex(&strip->modifiers, smd);
|
||||
return BLI_listbase_move_index(&strip->modifiers, current_index, new_index);
|
||||
}
|
||||
|
||||
StripModifierData *modifier_get_active(const Strip *strip)
|
||||
{
|
||||
/* In debug mode, check for only one active modifier. */
|
||||
#ifndef NDEBUG
|
||||
int active_count = 0;
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
if (smd.flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
active_count++;
|
||||
}
|
||||
}
|
||||
BLI_assert(ELEM(active_count, 0, 1));
|
||||
#endif
|
||||
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
if (smd.flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
return &smd;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void modifier_set_active(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
for (StripModifierData &smd_iter : strip->modifiers) {
|
||||
smd_iter.flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
|
||||
if (smd != nullptr) {
|
||||
BLI_assert(BLI_findindex(&strip->modifiers, smd) != -1);
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void modifier_type_panel_id(eStripModifierType type, char *r_idname)
|
||||
{
|
||||
const StripModifierTypeInfo *mti = modifier_type_info_get(type);
|
||||
BLI_string_join(
|
||||
r_idname, sizeof(PanelType::idname), STRIP_MODIFIER_TYPE_PANEL_PREFIX, mti->idname);
|
||||
}
|
||||
|
||||
void foreach_strip_modifier_id(Strip *strip, const FunctionRef<void(ID *)> fn)
|
||||
{
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
if (smd.mask_id) {
|
||||
fn(reinterpret_cast<ID *>(smd.mask_id));
|
||||
}
|
||||
if (smd.type == eSeqModifierType_Compositor) {
|
||||
auto *modifier_data = reinterpret_cast<SequencerCompositorModifierData *>(&smd);
|
||||
if (modifier_data->node_group) {
|
||||
fn(reinterpret_cast<ID *>(modifier_data->node_group));
|
||||
}
|
||||
}
|
||||
if (smd.system_properties) {
|
||||
IDP_foreach_property(smd.system_properties, IDP_TYPE_FILTER_ID, [&](IDProperty *id_prop) {
|
||||
fn((ID *)id_prop->data.pointer);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name .blend File I/O
|
||||
* \{ */
|
||||
|
||||
void modifier_blend_write(BlendWriter *writer, ListBaseT<StripModifierData> *modbase)
|
||||
{
|
||||
for (StripModifierData &smd : *modbase) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd.type);
|
||||
|
||||
if (smti) {
|
||||
if (smd.system_properties) {
|
||||
IDP_BlendWrite(writer, smd.system_properties);
|
||||
}
|
||||
|
||||
writer->write_struct_by_name(smti->struct_name, &smd);
|
||||
if (smti->blend_write) {
|
||||
smti->blend_write(writer, &smd);
|
||||
}
|
||||
}
|
||||
else {
|
||||
writer->write_struct(&smd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void modifier_blend_read_data(BlendDataReader *reader, ListBaseT<StripModifierData> *lb)
|
||||
{
|
||||
BLO_read_struct_list(reader, StripModifierData, lb);
|
||||
|
||||
for (StripModifierData &smd : *lb) {
|
||||
BLO_read_struct(reader, IDProperty, &smd.system_properties);
|
||||
IDP_BlendDataRead(reader, &smd.system_properties);
|
||||
|
||||
if (smd.mask_strip) {
|
||||
BLO_read_struct(reader, Strip, &smd.mask_strip);
|
||||
}
|
||||
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd.type);
|
||||
if (smti && smti->blend_read) {
|
||||
smti->blend_read(reader, &smd);
|
||||
}
|
||||
smd.runtime = MEM_new<StripModifierDataRuntime>(__func__);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,353 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_interp.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct ARegionType;
|
||||
struct ImBuf;
|
||||
struct Strip;
|
||||
struct Panel;
|
||||
struct PanelType;
|
||||
struct PointerRNA;
|
||||
|
||||
namespace ui {
|
||||
struct Layout;
|
||||
} // namespace ui
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct RenderData;
|
||||
struct SeqRenderState;
|
||||
struct SeqResult;
|
||||
|
||||
struct ModifierApplyContext {
|
||||
ModifierApplyContext(const RenderData &render_data,
|
||||
SeqRenderState &render_state,
|
||||
const Strip &strip,
|
||||
const float3x3 &transform,
|
||||
const float3x3 &transform_comp_result,
|
||||
const float timeline_frame,
|
||||
SeqResult &result)
|
||||
: render_data(render_data),
|
||||
render_state(render_state),
|
||||
strip(strip),
|
||||
transform(transform),
|
||||
transform_comp_result(transform_comp_result),
|
||||
timeline_frame(timeline_frame),
|
||||
result(result)
|
||||
{
|
||||
}
|
||||
const RenderData &render_data;
|
||||
SeqRenderState &render_state;
|
||||
const Strip &strip;
|
||||
|
||||
/* Transformation from strip image local pixel coordinates to the
|
||||
* full render area pixel coordinates.This is used to sample
|
||||
* modifier masks (since masks are in full render area space). */
|
||||
const float3x3 transform;
|
||||
/* Transformation to apply when sampling masks in compositor modifier. */
|
||||
const float3x3 transform_comp_result;
|
||||
/* Timeline frame at which the modifiers are being applied at. */
|
||||
const float timeline_frame;
|
||||
SeqResult &result;
|
||||
};
|
||||
|
||||
void modifier_apply_stack(ModifierApplyContext &context);
|
||||
|
||||
ImBuf *modifier_render_mask_input(const ModifierApplyContext &context,
|
||||
const StripModifierData &smd);
|
||||
|
||||
bool modifier_persistent_uids_are_valid(const Strip &strip);
|
||||
|
||||
void draw_mask_input_type_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr);
|
||||
|
||||
bool modifier_ui_poll(const bContext *C, PanelType *pt);
|
||||
|
||||
using PanelDrawFn = void (*)(const bContext *, Panel *);
|
||||
|
||||
PanelType *modifier_panel_register(ARegionType *region_type,
|
||||
const eStripModifierType type,
|
||||
PanelDrawFn draw);
|
||||
|
||||
float4 load_pixel_premul(const uchar *ptr);
|
||||
float4 load_pixel_premul(const float *ptr);
|
||||
void store_pixel_premul(const float4 pix, uchar *ptr);
|
||||
void store_pixel_premul(const float4 pix, float *ptr);
|
||||
float4 load_pixel_raw(const uchar *ptr);
|
||||
float4 load_pixel_raw(const float *ptr);
|
||||
void store_pixel_raw(const float4 pix, uchar *ptr);
|
||||
void store_pixel_raw(const float4 pix, float *ptr);
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: no mask is present. */
|
||||
struct MaskSamplerNone {
|
||||
void begin_row(int64_t /*y*/) {}
|
||||
void apply_mask(const float4 /*input*/, float4 & /*result*/) {}
|
||||
float load_mask_min()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: floating point mask,
|
||||
* same size as input, no transform. */
|
||||
struct MaskSamplerDirectFloat {
|
||||
MaskSamplerDirectFloat(const ImBuf *mask) : mask(mask)
|
||||
{
|
||||
BLI_assert(mask && mask->float_data());
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
BLI_assert(y >= 0 && y < mask->y);
|
||||
ptr = mask->float_data() + y * mask->x * 4;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float3 m(this->ptr);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->ptr += 4;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float r = std::min({this->ptr[0], this->ptr[1], this->ptr[2]});
|
||||
this->ptr += 4;
|
||||
return r;
|
||||
}
|
||||
const float *ptr = nullptr;
|
||||
const ImBuf *mask;
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: byte mask,
|
||||
* same size as input, no transform. */
|
||||
struct MaskSamplerDirectByte {
|
||||
MaskSamplerDirectByte(const ImBuf *mask) : mask(mask)
|
||||
{
|
||||
BLI_assert(mask && mask->byte_data());
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
BLI_assert(y >= 0 && y < mask->y);
|
||||
ptr = mask->byte_data() + y * mask->x * 4;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float3 m;
|
||||
rgb_uchar_to_float(m, this->ptr);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->ptr += 4;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float r = float(std::min({this->ptr[0], this->ptr[1], this->ptr[2]})) * (1.0f / 255.0f);
|
||||
this->ptr += 4;
|
||||
return r;
|
||||
}
|
||||
const uchar *ptr = nullptr;
|
||||
const ImBuf *mask;
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: floating point mask,
|
||||
* sample mask with a transform. */
|
||||
struct MaskSamplerTransformedFloat {
|
||||
MaskSamplerTransformedFloat(const ImBuf *mask, const float3x3 &transform)
|
||||
: mask(mask), transform(transform)
|
||||
{
|
||||
BLI_assert(mask && mask->float_data());
|
||||
start_uv = transform.location().xy();
|
||||
add_x = transform.x_axis().xy();
|
||||
add_y = transform.y_axis().xy();
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
this->cur_y = y;
|
||||
this->cur_x = 0;
|
||||
/* Sample at pixel centers. */
|
||||
this->cur_uv_row = this->start_uv + (y + 0.5f) * this->add_y + 0.5f * this->add_x;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
float4 m;
|
||||
math::interpolate_bilinear_border_fl(
|
||||
this->mask->float_data(), m, this->mask->x, this->mask->y, 4, uv.x, uv.y);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->cur_x++;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
float4 m;
|
||||
math::interpolate_bilinear_border_fl(
|
||||
this->mask->float_data(), m, this->mask->x, this->mask->y, 4, uv.x, uv.y);
|
||||
float r = std::min({m.x, m.y, m.z});
|
||||
this->cur_x++;
|
||||
return r;
|
||||
}
|
||||
int64_t cur_x = 0, cur_y = 0;
|
||||
const ImBuf *mask;
|
||||
const float3x3 transform;
|
||||
float2 start_uv, add_x, add_y;
|
||||
float2 cur_uv_row;
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: byte mask,
|
||||
* sample mask with a transform. */
|
||||
struct MaskSamplerTransformedByte {
|
||||
MaskSamplerTransformedByte(const ImBuf *mask, const float3x3 &transform)
|
||||
: mask(mask), transform(transform)
|
||||
{
|
||||
BLI_assert(mask && mask->byte_data());
|
||||
start_uv = transform.location().xy();
|
||||
add_x = transform.x_axis().xy();
|
||||
add_y = transform.y_axis().xy();
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
this->cur_y = y;
|
||||
this->cur_x = 0;
|
||||
/* Sample at pixel centers. */
|
||||
this->cur_uv_row = this->start_uv + (y + 0.5f) * this->add_y + 0.5f * this->add_x;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
uchar4 mb = math::interpolate_bilinear_border_byte(
|
||||
this->mask->byte_data(), this->mask->x, this->mask->y, uv.x, uv.y);
|
||||
float3 m;
|
||||
rgb_uchar_to_float(m, mb);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->cur_x++;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
uchar4 m = math::interpolate_bilinear_border_byte(
|
||||
this->mask->byte_data(), this->mask->x, this->mask->y, uv.x, uv.y);
|
||||
float r = float(std::min({m.x, m.y, m.z})) * (1.0f / 255.0f);
|
||||
this->cur_x++;
|
||||
return r;
|
||||
}
|
||||
int64_t cur_x = 0, cur_y = 0;
|
||||
const ImBuf *mask;
|
||||
const float3x3 transform;
|
||||
float2 start_uv, add_x, add_y;
|
||||
float2 cur_uv_row;
|
||||
};
|
||||
|
||||
/* Given `T` that implements an `apply` function:
|
||||
*
|
||||
* template <typename ImageT, typename MaskSampler>
|
||||
* void apply(ImageT* image, MaskSampler &mask, int image_x, IndexRange y_range);
|
||||
*
|
||||
* this function calls the apply() function in parallel
|
||||
* chunks of the image to process, and with needed
|
||||
* uchar or float ImageT types, and with appropriate MaskSampler
|
||||
* instantiated, depending on whether the mask exists, data type
|
||||
* of the mask, and whether it needs a transformation or can be
|
||||
* sampled directly.
|
||||
*
|
||||
* Both input and mask images are expected to have
|
||||
* 4 (RGBA) color channels. Input is modified. */
|
||||
template<typename T>
|
||||
void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask, const float3x3 &mask_transform)
|
||||
{
|
||||
if (ibuf == nullptr) {
|
||||
return;
|
||||
}
|
||||
BLI_assert_msg(ibuf->channels == 0 || ibuf->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
BLI_assert_msg(mask == nullptr || mask->channels == 0 || mask->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
const bool direct_mask_sampling = mask == nullptr || (mask->x == ibuf->x && mask->y == ibuf->y &&
|
||||
math::is_identity(mask_transform));
|
||||
const int image_x = ibuf->x;
|
||||
uchar *image_byte = ibuf->byte_data_for_write();
|
||||
float *image_float = ibuf->float_data_for_write();
|
||||
threading::parallel_for(IndexRange(ibuf->y), 16, [&](IndexRange y_range) {
|
||||
const uchar *mask_byte = mask ? mask->byte_data() : nullptr;
|
||||
const float *mask_float = mask ? mask->float_data() : nullptr;
|
||||
|
||||
/* Instantiate the needed processing function based on image/mask
|
||||
* data types. */
|
||||
if (image_byte) {
|
||||
if (mask_byte) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectByte sampler(mask);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedByte sampler(mask, mask_transform);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else if (mask_float) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectFloat sampler(mask);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedFloat sampler(mask, mask_transform);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MaskSamplerNone sampler;
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else if (image_float) {
|
||||
if (mask_byte) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectByte sampler(mask);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedByte sampler(mask, mask_transform);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else if (mask_float) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectFloat sampler(mask);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedFloat sampler(mask, mask_transform);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MaskSamplerNone sampler;
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
54
blender-5.2.0/source/blender/sequencer/intern/multiview.cc
Normal file
54
blender-5.2.0/source/blender/sequencer/intern/multiview.cc
Normal file
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "multiview.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
void seq_anim_add_suffix(Scene *scene, MovieReader *anim, const int view_id)
|
||||
{
|
||||
const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, view_id);
|
||||
MOV_set_multiview_suffix(anim, suffix);
|
||||
}
|
||||
|
||||
int seq_num_files(Scene *scene, char views_format, const bool is_multiview)
|
||||
{
|
||||
if (!is_multiview) {
|
||||
return 1;
|
||||
}
|
||||
if (views_format == R_IMF_VIEWS_STEREO_3D) {
|
||||
return 1;
|
||||
}
|
||||
/* R_IMF_VIEWS_INDIVIDUAL */
|
||||
|
||||
return BKE_scene_multiview_num_views_get(&scene->r);
|
||||
}
|
||||
|
||||
void seq_multiview_name(Scene *scene,
|
||||
const int view_id,
|
||||
const char *prefix,
|
||||
const char *ext,
|
||||
char *r_path,
|
||||
size_t r_size)
|
||||
{
|
||||
const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, view_id);
|
||||
BLI_assert(ext != nullptr && suffix != nullptr && prefix != nullptr);
|
||||
BLI_snprintf(r_path, r_size, "%s%s%s", prefix, suffix, ext);
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
29
blender-5.2.0/source/blender/sequencer/intern/multiview.hh
Normal file
29
blender-5.2.0/source/blender/sequencer/intern/multiview.hh
Normal file
@@ -0,0 +1,29 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct MovieReader;
|
||||
struct Scene;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void seq_anim_add_suffix(Scene *scene, MovieReader *anim, int view_id);
|
||||
void seq_multiview_name(
|
||||
Scene *scene, int view_id, const char *prefix, const char *ext, char *r_path, size_t r_size);
|
||||
/**
|
||||
* The number of files will vary according to the stereo format.
|
||||
*/
|
||||
int seq_num_files(Scene *scene, char views_format, bool is_multiview);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
706
blender-5.2.0/source/blender/sequencer/intern/prefetch.cc
Normal file
706
blender-5.2.0/source/blender/sequencer/intern/prefetch.cc
Normal file
@@ -0,0 +1,706 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_screen_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BLI_threads.h"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_animsys.h"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
#include "DEG_depsgraph_debug.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "GPU_context.hh"
|
||||
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_prefetch.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
|
||||
#include "prefetch.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct RenderResult;
|
||||
struct Scene;
|
||||
struct ThreadSlot;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/* Prefetch several frames before the playhead, so that it is fast to move it a bit backwards. */
|
||||
static constexpr int before_playhead_frames = 5;
|
||||
|
||||
struct PrefetchJob {
|
||||
PrefetchJob *next = nullptr;
|
||||
PrefetchJob *prev = nullptr;
|
||||
|
||||
Main *bmain = nullptr;
|
||||
Main *bmain_eval = nullptr;
|
||||
Scene *scene = nullptr;
|
||||
Scene *scene_eval = nullptr;
|
||||
Depsgraph *depsgraph = nullptr;
|
||||
|
||||
ThreadMutex prefetch_suspend_mutex = {};
|
||||
ThreadCondition prefetch_suspend_cond = {};
|
||||
|
||||
ListBaseT<ThreadSlot> threads = {};
|
||||
|
||||
/* context */
|
||||
RenderData context = {};
|
||||
RenderData context_cpy = {};
|
||||
|
||||
/* prefetch area */
|
||||
int cfra = 0;
|
||||
int timeline_start = 0;
|
||||
int timeline_end = 0;
|
||||
int timeline_length = 0;
|
||||
int num_frames_prefetched = 0;
|
||||
int cache_flags = 0; /* Only used to detect cache flag changes. */
|
||||
|
||||
/* Control: */
|
||||
/* Set by prefetch. */
|
||||
bool running = false;
|
||||
bool waiting = false;
|
||||
bool stop = false;
|
||||
/* Set from outside. */
|
||||
bool is_scrubbing = false;
|
||||
|
||||
public:
|
||||
void init_depsgraph();
|
||||
void free_depsgraph();
|
||||
|
||||
void init_gpu();
|
||||
void free_gpu();
|
||||
};
|
||||
|
||||
static PrefetchJob *seq_prefetch_job_get(Scene *scene)
|
||||
{
|
||||
if (scene && scene->ed) {
|
||||
return scene->ed->runtime->prefetch_job;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool seq_prefetch_job_is_running(Scene *scene)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
|
||||
if (!pfjob) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pfjob->running;
|
||||
}
|
||||
|
||||
static void seq_prefetch_job_scrubbing_set(Scene *scene, bool is_scrubbing)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
|
||||
if (!pfjob) {
|
||||
return;
|
||||
}
|
||||
|
||||
pfjob->is_scrubbing = is_scrubbing;
|
||||
}
|
||||
|
||||
static bool seq_prefetch_job_is_waiting(Scene *scene)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
|
||||
if (!pfjob) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pfjob->waiting;
|
||||
}
|
||||
|
||||
static Strip *original_strip_get(const Strip *strip, ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
for (Strip &strip_orig : *seqbase) {
|
||||
if (STREQ(strip->name, strip_orig.name)) {
|
||||
return &strip_orig;
|
||||
}
|
||||
|
||||
if (strip_orig.type == STRIP_TYPE_META) {
|
||||
Strip *match = original_strip_get(strip, &strip_orig.seqbase);
|
||||
if (match != nullptr) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Strip *original_strip_get(const Strip *strip, Scene *scene)
|
||||
{
|
||||
Editing *ed = scene->ed;
|
||||
return original_strip_get(strip, &ed->seqbase);
|
||||
}
|
||||
|
||||
static RenderData *get_original_context(const RenderData *context)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(context->scene);
|
||||
return pfjob ? &pfjob->context : nullptr;
|
||||
}
|
||||
|
||||
Scene *prefetch_get_original_scene(const RenderData *context)
|
||||
{
|
||||
Scene *scene = context->scene;
|
||||
if (context->is_prefetch_render) {
|
||||
context = get_original_context(context);
|
||||
if (context != nullptr) {
|
||||
scene = context->scene;
|
||||
}
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
Scene *prefetch_get_original_scene_and_strip(const RenderData *context, const Strip *&strip)
|
||||
{
|
||||
Scene *scene = context->scene;
|
||||
if (context->is_prefetch_render) {
|
||||
context = get_original_context(context);
|
||||
if (context != nullptr) {
|
||||
scene = context->scene;
|
||||
strip = original_strip_get(strip, scene);
|
||||
}
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
static bool seq_prefetch_is_cache_full(Scene *scene)
|
||||
{
|
||||
return evict_caches_if_full(scene);
|
||||
}
|
||||
|
||||
static int seq_prefetch_cfra(PrefetchJob *pfjob)
|
||||
{
|
||||
int new_frame = pfjob->cfra + pfjob->num_frames_prefetched;
|
||||
const ScenePlaybackRange playback_range = BKE_scene_get_playback_range(pfjob->scene);
|
||||
if (new_frame >= playback_range.end_frame) {
|
||||
/* Wrap around to where we will jump when we reach the end frame. */
|
||||
new_frame = playback_range.start_frame + new_frame - playback_range.end_frame;
|
||||
}
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
static AnimationEvalContext seq_prefetch_anim_eval_context(PrefetchJob *pfjob)
|
||||
{
|
||||
return BKE_animsys_eval_context_construct(pfjob->depsgraph, seq_prefetch_cfra(pfjob));
|
||||
}
|
||||
|
||||
void seq_prefetch_get_time_range(Scene *scene, int *r_start, int *r_end)
|
||||
{
|
||||
/* When there is no prefetch job, return "impossible" negative values. */
|
||||
*r_start = std::numeric_limits<int>::min();
|
||||
*r_end = std::numeric_limits<int>::min();
|
||||
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
if (pfjob == nullptr) {
|
||||
return;
|
||||
}
|
||||
if ((scene->ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) == 0 || !pfjob->running) {
|
||||
return;
|
||||
}
|
||||
|
||||
*r_start = pfjob->cfra;
|
||||
*r_end = seq_prefetch_cfra(pfjob);
|
||||
}
|
||||
|
||||
void PrefetchJob::free_depsgraph()
|
||||
{
|
||||
if (this->depsgraph != nullptr) {
|
||||
DEG_graph_free(this->depsgraph);
|
||||
}
|
||||
this->depsgraph = nullptr;
|
||||
this->scene_eval = nullptr;
|
||||
}
|
||||
|
||||
static void seq_prefetch_update_depsgraph(PrefetchJob *pfjob)
|
||||
{
|
||||
DEG_evaluate_on_framechange(pfjob->depsgraph, seq_prefetch_cfra(pfjob));
|
||||
/* Prevent depsgraph from copying scene data to evaluated scene. It would reset updated frame. */
|
||||
DEG_ids_clear_recalc(pfjob->depsgraph, false);
|
||||
}
|
||||
|
||||
void PrefetchJob::init_depsgraph()
|
||||
{
|
||||
ViewLayer *view_layer = BKE_view_layer_default_render(this->scene);
|
||||
|
||||
this->depsgraph = DEG_graph_new(this->bmain_eval, this->scene, view_layer, DAG_EVAL_RENDER);
|
||||
DEG_debug_name_set(this->depsgraph, "SEQUENCER PREFETCH");
|
||||
|
||||
/* Make sure there is a correct evaluated scene pointer. */
|
||||
DEG_graph_build_for_render_pipeline(this->depsgraph);
|
||||
|
||||
/* Update immediately so we have proper evaluated scene. */
|
||||
seq_prefetch_update_depsgraph(this);
|
||||
|
||||
this->scene_eval = DEG_get_evaluated_scene(this->depsgraph);
|
||||
this->scene_eval->ed->cache_flag = SEQ_CACHE_NONE;
|
||||
}
|
||||
|
||||
void PrefetchJob::init_gpu()
|
||||
{
|
||||
this->context_cpy.gpu_context = gpu::GPU_create_secondary_context();
|
||||
}
|
||||
|
||||
void PrefetchJob::free_gpu()
|
||||
{
|
||||
if (this->context_cpy.gpu_context.ghost_context != nullptr) {
|
||||
gpu::GPU_destroy_secondary_context(this->context_cpy.gpu_context);
|
||||
this->context_cpy.gpu_context = {};
|
||||
}
|
||||
}
|
||||
|
||||
static void seq_prefetch_update_area(PrefetchJob *pfjob)
|
||||
{
|
||||
int cfra = math::max(pfjob->scene->r.cfra - before_playhead_frames, pfjob->timeline_start);
|
||||
|
||||
/* rebase */
|
||||
if (cfra > pfjob->cfra) {
|
||||
int delta = cfra - pfjob->cfra;
|
||||
pfjob->cfra = cfra;
|
||||
pfjob->num_frames_prefetched -= delta;
|
||||
|
||||
pfjob->num_frames_prefetched = std::max(pfjob->num_frames_prefetched, 0);
|
||||
}
|
||||
|
||||
/* reset */
|
||||
if (cfra < pfjob->cfra) {
|
||||
pfjob->cfra = cfra;
|
||||
pfjob->num_frames_prefetched = 0;
|
||||
}
|
||||
|
||||
/* timeline span changes */
|
||||
const ScenePlaybackRange playback_range = BKE_scene_get_playback_range(pfjob->scene);
|
||||
if (pfjob->timeline_start != playback_range.start_frame ||
|
||||
pfjob->timeline_end != playback_range.end_frame)
|
||||
{
|
||||
pfjob->timeline_start = playback_range.start_frame;
|
||||
pfjob->timeline_end = playback_range.end_frame;
|
||||
pfjob->timeline_length = playback_range.end_frame - playback_range.start_frame;
|
||||
/* Reset the number of prefetched frames as we need to re-evaluate which
|
||||
* frames to keep in the cache.
|
||||
*/
|
||||
pfjob->num_frames_prefetched = 0;
|
||||
}
|
||||
|
||||
/* cache flag changes */
|
||||
Scene *scene = pfjob->scene;
|
||||
if (pfjob->cache_flags != scene->ed->cache_flag) {
|
||||
pfjob->cache_flags = scene->ed->cache_flag;
|
||||
pfjob->num_frames_prefetched = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void prefetch_stop_all()
|
||||
{
|
||||
/* TODO(Richard): Use wm_jobs for prefetch, or pass main. */
|
||||
for (Scene *scene = static_cast<Scene *>(G.main->scenes.first); scene;
|
||||
scene = static_cast<Scene *>(scene->id.next))
|
||||
{
|
||||
prefetch_stop(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void prefetch_stop(Scene *scene)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
|
||||
if (!pfjob) {
|
||||
return;
|
||||
}
|
||||
|
||||
pfjob->stop = true;
|
||||
|
||||
while (pfjob->running) {
|
||||
BLI_condition_notify_one(&pfjob->prefetch_suspend_cond);
|
||||
}
|
||||
}
|
||||
|
||||
static void seq_prefetch_update_context(const RenderData *context)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(context->scene);
|
||||
|
||||
render_new_render_data(pfjob->bmain_eval,
|
||||
pfjob->depsgraph,
|
||||
pfjob->scene_eval,
|
||||
context->rectx,
|
||||
context->recty,
|
||||
context->preview_render_size,
|
||||
nullptr,
|
||||
&pfjob->context_cpy);
|
||||
pfjob->context_cpy.is_prefetch_render = true;
|
||||
|
||||
render_new_render_data(pfjob->bmain,
|
||||
pfjob->depsgraph,
|
||||
pfjob->scene,
|
||||
context->rectx,
|
||||
context->recty,
|
||||
context->preview_render_size,
|
||||
nullptr,
|
||||
&pfjob->context);
|
||||
pfjob->context.is_prefetch_render = false;
|
||||
}
|
||||
|
||||
static void seq_prefetch_update_scene(Scene *scene)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
|
||||
if (!pfjob) {
|
||||
return;
|
||||
}
|
||||
|
||||
pfjob->scene = scene;
|
||||
pfjob->free_depsgraph();
|
||||
pfjob->init_depsgraph();
|
||||
}
|
||||
|
||||
static void seq_prefetch_update_active_seqbase(PrefetchJob *pfjob)
|
||||
{
|
||||
MetaStack *ms_orig = meta_stack_active_get(editing_get(pfjob->scene));
|
||||
Editing *ed_eval = editing_get(pfjob->scene_eval);
|
||||
|
||||
if (ms_orig != nullptr) {
|
||||
Strip *meta_eval = original_strip_get(ms_orig->parent_strip, pfjob->scene_eval);
|
||||
ed_eval->current_meta_strip = meta_eval;
|
||||
}
|
||||
else {
|
||||
ed_eval->current_meta_strip = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void seq_prefetch_resume(Scene *scene)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
|
||||
if (pfjob && pfjob->waiting) {
|
||||
BLI_condition_notify_one(&pfjob->prefetch_suspend_cond);
|
||||
}
|
||||
}
|
||||
|
||||
void seq_prefetch_free(Scene *scene)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
|
||||
if (!pfjob) {
|
||||
return;
|
||||
}
|
||||
|
||||
prefetch_stop(scene);
|
||||
|
||||
BLI_threadpool_remove(&pfjob->threads, pfjob);
|
||||
BLI_threadpool_end(&pfjob->threads);
|
||||
BLI_mutex_end(&pfjob->prefetch_suspend_mutex);
|
||||
BLI_condition_end(&pfjob->prefetch_suspend_cond);
|
||||
pfjob->free_depsgraph();
|
||||
pfjob->free_gpu();
|
||||
BKE_main_free(pfjob->bmain_eval);
|
||||
scene->ed->runtime->prefetch_job = nullptr;
|
||||
MEM_delete(pfjob);
|
||||
}
|
||||
|
||||
static VectorSet<Strip *> query_scene_strips(Editing *ed)
|
||||
{
|
||||
Map<const Scene *, VectorSet<Strip *>> &strips_by_scene = lookup_strips_by_scene_map_get(ed);
|
||||
|
||||
VectorSet<Strip *> scene_strips;
|
||||
for (const VectorSet<Strip *> &strips : strips_by_scene.values()) {
|
||||
scene_strips.add_multiple(strips);
|
||||
}
|
||||
return scene_strips;
|
||||
}
|
||||
|
||||
/* Find whether any scene strips are indirectly rendered, e.g. as mask or effect inputs. */
|
||||
static bool seq_prefetch_scene_strip_is_rendered(const Scene *scene,
|
||||
ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
Span<Strip *> scene_strips,
|
||||
int timeline_frame,
|
||||
SeqRenderState state)
|
||||
{
|
||||
Vector<Strip *> rendered_strips = query_rendered_strips_sorted(
|
||||
scene, channels, seqbase, timeline_frame, 0);
|
||||
|
||||
/* Iterate over rendered strips. */
|
||||
for (Strip *strip : rendered_strips) {
|
||||
if (strip->type == STRIP_TYPE_META &&
|
||||
seq_prefetch_scene_strip_is_rendered(
|
||||
scene, &strip->channels, &strip->seqbase, scene_strips, timeline_frame, state))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Recursive "sequencer-type" scene strip detected, no point in attempting to render it. */
|
||||
if (state.strips_in_progress.contains(strip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strip->type == STRIP_TYPE_SCENE && (strip->flag & SEQ_SCENE_STRIPS) != 0 &&
|
||||
strip->scene != nullptr && editing_get(strip->scene))
|
||||
{
|
||||
state.strips_in_progress.add(strip);
|
||||
|
||||
const Scene *target_scene = strip->scene;
|
||||
Editing *target_ed = editing_get(target_scene);
|
||||
if (target_ed == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VectorSet<Strip *> target_scene_strips = query_scene_strips(target_ed);
|
||||
int target_timeline_frame = give_frame_index(scene, strip, timeline_frame) +
|
||||
target_scene->r.sfra;
|
||||
|
||||
if (seq_prefetch_scene_strip_is_rendered(target_scene,
|
||||
target_ed->current_channels(),
|
||||
target_ed->current_strips(),
|
||||
target_scene_strips,
|
||||
target_timeline_frame,
|
||||
state))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (Strip *strip_scene : scene_strips) {
|
||||
/* Check if the strip is an effect of the scene strip or uses it as modifier.
|
||||
* This also checks if `strip == strip_scene`. */
|
||||
if (relations_render_loop_check(strip, strip_scene)) {
|
||||
return true;
|
||||
}
|
||||
/* Adjustment strips with 'replace' blending can use scene strips in channels below it.
|
||||
* See #151629. */
|
||||
if (strip->type == STRIP_TYPE_ADJUSTMENT && strip->blend_mode == STRIP_BLEND_REPLACE &&
|
||||
strip_scene->intersects_frame(scene, timeline_frame) &&
|
||||
strip_scene->channel < strip->channel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Prefetch must avoid rendering scene strips, because rendering in background locks UI and can
|
||||
* make it unresponsive for long time periods. */
|
||||
static bool seq_prefetch_must_skip_frame(PrefetchJob *pfjob,
|
||||
ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
/* Pass in state to check for infinite recursion of "sequencer-type" scene strips. */
|
||||
SeqRenderState state = {};
|
||||
|
||||
VectorSet<Strip *> scene_strips = query_scene_strips(editing_get(pfjob->scene_eval));
|
||||
return seq_prefetch_scene_strip_is_rendered(
|
||||
pfjob->scene_eval, channels, seqbase, scene_strips, seq_prefetch_cfra(pfjob), state);
|
||||
}
|
||||
|
||||
static bool seq_prefetch_need_suspend(PrefetchJob *pfjob)
|
||||
{
|
||||
return seq_prefetch_is_cache_full(pfjob->scene) || pfjob->is_scrubbing ||
|
||||
(pfjob->num_frames_prefetched >= pfjob->timeline_length);
|
||||
}
|
||||
|
||||
static void seq_prefetch_do_suspend(PrefetchJob *pfjob)
|
||||
{
|
||||
BLI_mutex_lock(&pfjob->prefetch_suspend_mutex);
|
||||
while (seq_prefetch_need_suspend(pfjob) &&
|
||||
(pfjob->scene->ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) && !pfjob->stop)
|
||||
{
|
||||
pfjob->waiting = true;
|
||||
BLI_condition_wait(&pfjob->prefetch_suspend_cond, &pfjob->prefetch_suspend_mutex);
|
||||
seq_prefetch_update_area(pfjob);
|
||||
}
|
||||
pfjob->waiting = false;
|
||||
BLI_mutex_unlock(&pfjob->prefetch_suspend_mutex);
|
||||
}
|
||||
|
||||
static void *seq_prefetch_frames(void *job)
|
||||
{
|
||||
PrefetchJob *pfjob = static_cast<PrefetchJob *>(job);
|
||||
|
||||
while (true) {
|
||||
if (pfjob->cfra < pfjob->timeline_start || pfjob->cfra > pfjob->timeline_end) {
|
||||
/* Don't try to prefetch anything when we are outside of the timeline range. */
|
||||
break;
|
||||
}
|
||||
pfjob->scene_eval->ed->runtime->prefetch_job = nullptr;
|
||||
|
||||
seq_prefetch_update_depsgraph(pfjob);
|
||||
AnimData *adt = BKE_animdata_from_id(&pfjob->context_cpy.scene->id);
|
||||
AnimationEvalContext anim_eval_context = seq_prefetch_anim_eval_context(pfjob);
|
||||
BKE_animsys_evaluate_animdata(
|
||||
&pfjob->context_cpy.scene->id, adt, &anim_eval_context, ADT_RECALC_ALL, false);
|
||||
|
||||
/* This is quite hacky solution:
|
||||
* We need cross-reference original scene with copy for cache.
|
||||
* However depsgraph must not have this data, because it will try to kill this job.
|
||||
* Scene copy don't reference original scene. Perhaps, this could be done by depsgraph.
|
||||
* Set to nullptr before return!
|
||||
*/
|
||||
pfjob->scene_eval->ed->runtime->prefetch_job = pfjob;
|
||||
|
||||
ListBaseT<Strip> *seqbase = active_seqbase_get(editing_get(pfjob->scene_eval));
|
||||
ListBaseT<SeqTimelineChannel> *channels = channels_displayed_get(
|
||||
editing_get(pfjob->scene_eval));
|
||||
if (seq_prefetch_must_skip_frame(pfjob, channels, seqbase)) {
|
||||
pfjob->num_frames_prefetched++;
|
||||
/* Break instead of keep looping if the job should be terminated. */
|
||||
if (!(pfjob->scene->ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) ||
|
||||
!(pfjob->scene->ed->cache_flag & SEQ_CACHE_ALL_TYPES) || pfjob->stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ImBuf *ibuf = render_give_ibuf(&pfjob->context_cpy, seq_prefetch_cfra(pfjob), 0);
|
||||
pfjob->num_frames_prefetched++;
|
||||
IMB_freeImBuf(ibuf);
|
||||
|
||||
/* Suspend thread if there is nothing to be prefetched. */
|
||||
seq_prefetch_do_suspend(pfjob);
|
||||
|
||||
if (!(pfjob->scene->ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) ||
|
||||
!(pfjob->scene->ed->cache_flag & SEQ_CACHE_ALL_TYPES) || pfjob->stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
seq_prefetch_update_area(pfjob);
|
||||
}
|
||||
|
||||
pfjob->running = false;
|
||||
pfjob->scene_eval->ed->runtime->prefetch_job = nullptr;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static PrefetchJob *seq_prefetch_start_ex(const RenderData *context, float cfra)
|
||||
{
|
||||
PrefetchJob *pfjob = seq_prefetch_job_get(context->scene);
|
||||
|
||||
if (!pfjob) {
|
||||
if (!context->scene->ed) {
|
||||
return nullptr;
|
||||
}
|
||||
pfjob = MEM_new<PrefetchJob>("PrefetchJob");
|
||||
context->scene->ed->runtime->prefetch_job = pfjob;
|
||||
|
||||
BLI_threadpool_init(&pfjob->threads, seq_prefetch_frames, 1);
|
||||
BLI_mutex_init(&pfjob->prefetch_suspend_mutex);
|
||||
BLI_condition_init(&pfjob->prefetch_suspend_cond);
|
||||
|
||||
pfjob->bmain_eval = BKE_main_new();
|
||||
pfjob->scene = context->scene;
|
||||
pfjob->init_depsgraph();
|
||||
pfjob->init_gpu();
|
||||
}
|
||||
pfjob->bmain = context->bmain;
|
||||
|
||||
Scene *scene = pfjob->scene;
|
||||
const ScenePlaybackRange playback_range = BKE_scene_get_playback_range(pfjob->scene);
|
||||
pfjob->timeline_start = playback_range.start_frame;
|
||||
pfjob->timeline_end = playback_range.end_frame;
|
||||
pfjob->timeline_length = playback_range.end_frame - playback_range.start_frame;
|
||||
|
||||
pfjob->cfra = math::max(int(cfra - before_playhead_frames), pfjob->timeline_start);
|
||||
|
||||
pfjob->num_frames_prefetched = 0;
|
||||
pfjob->cache_flags = scene->ed->cache_flag;
|
||||
|
||||
pfjob->waiting = false;
|
||||
pfjob->stop = false;
|
||||
pfjob->running = true;
|
||||
|
||||
seq_prefetch_update_scene(context->scene);
|
||||
seq_prefetch_update_context(context);
|
||||
seq_prefetch_update_active_seqbase(pfjob);
|
||||
|
||||
BLI_threadpool_remove(&pfjob->threads, pfjob);
|
||||
BLI_threadpool_insert(&pfjob->threads, pfjob);
|
||||
|
||||
return pfjob;
|
||||
}
|
||||
|
||||
void seq_prefetch_start(const RenderData *context, float timeline_frame)
|
||||
{
|
||||
Scene *scene = context->scene;
|
||||
Editing *ed = scene->ed;
|
||||
bool has_strips = bool(ed->current_strips()->first);
|
||||
|
||||
if (!context->is_prefetch_render) {
|
||||
bool playing = context->is_playing;
|
||||
bool scrubbing = context->is_scrubbing;
|
||||
bool running = seq_prefetch_job_is_running(scene);
|
||||
seq_prefetch_job_scrubbing_set(scene, scrubbing);
|
||||
seq_prefetch_resume(scene);
|
||||
|
||||
/* conditions to start:
|
||||
* prefetch enabled, prefetch not running, not scrubbing, not playing,
|
||||
* cache storage enabled, has strips to render, not rendering, not doing modal transform -
|
||||
* important, see D7820. */
|
||||
if ((ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) && !running && !scrubbing && !playing &&
|
||||
(ed->cache_flag & SEQ_CACHE_ALL_TYPES) && has_strips && !G.is_rendering && !G.moving)
|
||||
{
|
||||
seq_prefetch_start_ex(context, timeline_frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool prefetch_need_redraw(const bContext *C, Scene *scene)
|
||||
{
|
||||
bScreen *screen = CTX_wm_screen(C);
|
||||
bool playing = screen->animtimer != nullptr;
|
||||
bool scrubbing = screen->scrubbing;
|
||||
bool running = seq_prefetch_job_is_running(scene);
|
||||
bool suspended = seq_prefetch_job_is_waiting(scene);
|
||||
|
||||
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
||||
bool showing_cache = sseq->cache_overlay.flag & SEQ_CACHE_SHOW;
|
||||
|
||||
/* force redraw, when prefetching and using cache view. */
|
||||
if (running && !playing && !suspended && showing_cache) {
|
||||
return true;
|
||||
}
|
||||
/* Sometimes scrubbing flag is set when not scrubbing. In that case I want to catch "event" of
|
||||
* stopping scrubbing */
|
||||
if (scrubbing) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
32
blender-5.2.0/source/blender/sequencer/intern/prefetch.hh
Normal file
32
blender-5.2.0/source/blender/sequencer/intern/prefetch.hh
Normal file
@@ -0,0 +1,32 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct RenderData;
|
||||
|
||||
/**
|
||||
* Start or resume prefetching.
|
||||
*/
|
||||
void seq_prefetch_start(const RenderData *context, float timeline_frame);
|
||||
void seq_prefetch_free(Scene *scene);
|
||||
bool seq_prefetch_job_is_running(Scene *scene);
|
||||
void seq_prefetch_get_time_range(Scene *scene, int *r_start, int *r_end);
|
||||
|
||||
Scene *prefetch_get_original_scene(const RenderData *context);
|
||||
Scene *prefetch_get_original_scene_and_strip(const RenderData *context, const Strip *&strip);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
719
blender-5.2.0/source/blender/sequencer/intern/proxy.cc
Normal file
719
blender-5.2.0/source/blender/sequencer/intern/proxy.cc
Normal file
@@ -0,0 +1,719 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2026 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# include "BLI_winstuff.h"
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "SEQ_proxy.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
|
||||
#include "cache/intra_frame_cache.hh"
|
||||
#include "multiview.hh"
|
||||
#include "proxy.hh"
|
||||
#include "render.hh"
|
||||
#include "sequencer.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct ProxyBuildContext {
|
||||
MovieProxyBuilder *movie_proxy_builder = nullptr;
|
||||
|
||||
int size_flags = 0;
|
||||
int quality = 0;
|
||||
bool overwrite = false;
|
||||
int view_id = 0;
|
||||
|
||||
Main *bmain = nullptr;
|
||||
Scene *scene = nullptr;
|
||||
Strip *strip = nullptr;
|
||||
};
|
||||
|
||||
IMB_Proxy_Size rendersize_to_proxysize(eSpaceSeq_Proxy_RenderSize render_size)
|
||||
{
|
||||
switch (render_size) {
|
||||
case SEQ_RENDER_SIZE_PROXY_25:
|
||||
return IMB_PROXY_25;
|
||||
case SEQ_RENDER_SIZE_PROXY_50:
|
||||
return IMB_PROXY_50;
|
||||
case SEQ_RENDER_SIZE_PROXY_75:
|
||||
return IMB_PROXY_75;
|
||||
case SEQ_RENDER_SIZE_PROXY_100:
|
||||
return IMB_PROXY_100;
|
||||
default:
|
||||
return IMB_PROXY_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
float rendersize_to_scale_factor(eSpaceSeq_Proxy_RenderSize render_size)
|
||||
{
|
||||
switch (render_size) {
|
||||
case SEQ_RENDER_SIZE_PROXY_25:
|
||||
return 0.25f;
|
||||
case SEQ_RENDER_SIZE_PROXY_50:
|
||||
return 0.5f;
|
||||
case SEQ_RENDER_SIZE_PROXY_75:
|
||||
return 0.75f;
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
bool seq_proxy_get_custom_file_filepath(const Strip *strip, char *filepath, const int view_id)
|
||||
{
|
||||
/* Ideally this would be #PROXY_MAXFILE however BLI_path_abs clamps to #FILE_MAX. */
|
||||
char filepath_temp[FILE_MAX];
|
||||
char suffix[24];
|
||||
StripProxy *proxy = strip->data->proxy;
|
||||
|
||||
if (proxy == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BLI_path_join(filepath_temp, sizeof(filepath_temp), proxy->dirpath, proxy->filename);
|
||||
BLI_path_abs(filepath_temp, BKE_main_blendfile_path_from_global());
|
||||
|
||||
if (view_id > 0) {
|
||||
SNPRINTF(suffix, "_%d", view_id);
|
||||
/* This will actually append suffix after extension
|
||||
* which is weird but how was originally coded in multi-view branch. */
|
||||
BLI_snprintf(filepath, PROXY_MAXFILE, "%s_%s", filepath_temp, suffix);
|
||||
}
|
||||
else {
|
||||
BLI_strncpy(filepath, filepath_temp, PROXY_MAXFILE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool seq_proxy_get_filepath_for_elem(const Scene *scene,
|
||||
const Strip &strip,
|
||||
const StripElem &strip_elem,
|
||||
eSpaceSeq_Proxy_RenderSize render_size,
|
||||
char *filepath,
|
||||
const int view_id)
|
||||
{
|
||||
char dirpath[PROXY_MAXFILE];
|
||||
char suffix[24] = {'\0'};
|
||||
Editing *ed = editing_get(scene);
|
||||
StripProxy *proxy = strip.data->proxy;
|
||||
|
||||
if (proxy == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Multi-view suffix. */
|
||||
if (view_id > 0) {
|
||||
SNPRINTF(suffix, "_%d", view_id);
|
||||
}
|
||||
|
||||
/* Per strip with Custom file situation is handled separately. */
|
||||
if (proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_FILE &&
|
||||
ed->proxy_storage != SEQ_EDIT_PROXY_DIR_STORAGE)
|
||||
{
|
||||
if (seq_proxy_get_custom_file_filepath(&strip, filepath, view_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ed->proxy_storage == SEQ_EDIT_PROXY_DIR_STORAGE) {
|
||||
/* Per project default. */
|
||||
if (ed->proxy_dir[0] == 0) {
|
||||
STRNCPY(dirpath, "//BL_proxy");
|
||||
}
|
||||
else { /* Per project with custom dirpath. */
|
||||
STRNCPY(dirpath, ed->proxy_dir);
|
||||
}
|
||||
BLI_path_abs(filepath, BKE_main_blendfile_path_from_global());
|
||||
}
|
||||
else {
|
||||
/* Pre strip with custom dir. */
|
||||
if (proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_DIR) {
|
||||
STRNCPY(dirpath, strip.data->proxy->dirpath);
|
||||
}
|
||||
else { /* Per strip default. */
|
||||
SNPRINTF(dirpath, "%s" SEP_STR "BL_proxy", strip.data->dirpath);
|
||||
}
|
||||
}
|
||||
|
||||
/* Proxy size number to be used in path. */
|
||||
int proxy_size_number = rendersize_to_scale_factor(render_size) * 100;
|
||||
|
||||
BLI_snprintf(filepath,
|
||||
PROXY_MAXFILE,
|
||||
"%s" SEP_STR "images" SEP_STR "%d" SEP_STR "%s_proxy%s.jpg",
|
||||
dirpath,
|
||||
proxy_size_number,
|
||||
strip_elem.filename,
|
||||
suffix);
|
||||
BLI_path_abs(filepath, BKE_main_blendfile_path_from_global());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool seq_proxy_get_filepath(Scene *scene,
|
||||
Strip *strip,
|
||||
int timeline_frame,
|
||||
eSpaceSeq_Proxy_RenderSize render_size,
|
||||
char *filepath,
|
||||
const int view_id)
|
||||
{
|
||||
if (strip->data->proxy == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return seq_proxy_get_filepath_for_elem(scene,
|
||||
*strip,
|
||||
*render_give_stripelem(scene, strip, timeline_frame),
|
||||
render_size,
|
||||
filepath,
|
||||
view_id);
|
||||
}
|
||||
|
||||
bool can_use_proxy(const RenderData *context, const Strip *strip, IMB_Proxy_Size psize)
|
||||
{
|
||||
if (strip->data->proxy == nullptr || !context->use_proxies) {
|
||||
return false;
|
||||
}
|
||||
|
||||
short size_flags = strip->data->proxy->build_size_flags;
|
||||
return (strip->flag & SEQ_USE_PROXY) != 0 && psize != IMB_PROXY_NONE &&
|
||||
(size_flags & psize) != 0;
|
||||
}
|
||||
|
||||
ImBuf *seq_proxy_fetch(const RenderData *context, Strip *strip, int timeline_frame)
|
||||
{
|
||||
char filepath[PROXY_MAXFILE];
|
||||
StripProxy *proxy = strip->data->proxy;
|
||||
const eSpaceSeq_Proxy_RenderSize psize = eSpaceSeq_Proxy_RenderSize(
|
||||
context->preview_render_size);
|
||||
|
||||
/* only use proxies, if they are enabled (even if present!) */
|
||||
if (!can_use_proxy(context, strip, rendersize_to_proxysize(psize))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_FILE) {
|
||||
int frameno = round_fl_to_int(give_frame_index(context->scene, strip, timeline_frame)) +
|
||||
strip->anim_startofs;
|
||||
if (proxy->anim == nullptr) {
|
||||
if (seq_proxy_get_filepath(
|
||||
context->scene, strip, timeline_frame, psize, filepath, context->view_id) == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
|
||||
* kept unchanged for the performance reasons. */
|
||||
proxy->anim = openanim(
|
||||
filepath, ImBufFlags::Zero, 0, true, strip->data->colorspace_settings.name);
|
||||
}
|
||||
if (proxy->anim == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
strip_open_anim_file(context->scene, strip, true);
|
||||
return MOV_decode_frame(proxy->anim, frameno, IMB_PROXY_NONE);
|
||||
}
|
||||
|
||||
if (seq_proxy_get_filepath(
|
||||
context->scene, strip, timeline_frame, psize, filepath, context->view_id) == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (BLI_exists(filepath)) {
|
||||
/* Proxies are already be in the sequencer colorspace for fast loading, don't perform
|
||||
* conversion of float to scene linear that would usually be done. */
|
||||
char colorspace[IMA_MAX_SPACE];
|
||||
STRNCPY(colorspace, context->scene->sequencer_colorspace_settings.name);
|
||||
return IMB_load_image_from_filepath(filepath,
|
||||
ImBufFlags::ByteData | ImBufFlags::Metadata |
|
||||
ImBufFlags::NoColorspaceConvert,
|
||||
colorspace);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the result of #BKE_scene_multiview_view_prefix_get.
|
||||
*/
|
||||
struct MultiViewPrefixVars {
|
||||
char prefix[FILE_MAX];
|
||||
char ext[FILE_MAXFILE];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the file this context would read from even exist,
|
||||
* if not, don't create the context.
|
||||
*
|
||||
* \param prefix_vars: Stores prefix variables for reuse,
|
||||
* these variables are for internal use, the caller must not depend on them.
|
||||
*
|
||||
* \note This function must first a `view_id` of zero, to initialize `prefix_vars`
|
||||
* for use with other views.
|
||||
*/
|
||||
static bool seq_proxy_multiview_context_invalid(Strip *strip,
|
||||
Scene *scene,
|
||||
const int view_id,
|
||||
MultiViewPrefixVars *prefix_vars)
|
||||
{
|
||||
if ((scene->r.scemode & R_MULTIVIEW) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((strip->type == STRIP_TYPE_IMAGE) && (strip->views_format == R_IMF_VIEWS_INDIVIDUAL)) {
|
||||
if (view_id == 0) {
|
||||
/* Clear on first use. */
|
||||
prefix_vars->prefix[0] = '\0';
|
||||
prefix_vars->ext[0] = '\0';
|
||||
|
||||
char filepath[FILE_MAX];
|
||||
BLI_path_join(
|
||||
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
||||
const char *ext_ptr = nullptr;
|
||||
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix_vars->prefix, &ext_ptr);
|
||||
if (ext_ptr != nullptr) {
|
||||
STRNCPY(prefix_vars->ext, ext_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (prefix_vars->prefix[0] == '\0') {
|
||||
return view_id != 0;
|
||||
}
|
||||
|
||||
char filepath[FILE_MAX];
|
||||
seq_multiview_name(scene, view_id, prefix_vars->prefix, prefix_vars->ext, filepath, FILE_MAX);
|
||||
if (BLI_access(filepath, R_OK) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return view_id != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the maximum possible number of required contexts
|
||||
*/
|
||||
static int seq_proxy_context_count(Strip *strip, Scene *scene)
|
||||
{
|
||||
int num_views = 1;
|
||||
|
||||
if ((scene->r.scemode & R_MULTIVIEW) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (strip->type) {
|
||||
case STRIP_TYPE_MOVIE: {
|
||||
num_views = int(strip->runtime->movie_readers.size());
|
||||
break;
|
||||
}
|
||||
case STRIP_TYPE_IMAGE: {
|
||||
switch (strip->views_format) {
|
||||
case R_IMF_VIEWS_INDIVIDUAL:
|
||||
num_views = BKE_scene_multiview_num_views_get(&scene->r);
|
||||
break;
|
||||
case R_IMF_VIEWS_STEREO_3D:
|
||||
num_views = 2;
|
||||
break;
|
||||
case R_IMF_VIEWS_MULTIVIEW:
|
||||
/* not supported at the moment */
|
||||
/* pass through */
|
||||
default:
|
||||
num_views = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return num_views;
|
||||
}
|
||||
|
||||
static bool seq_proxy_need_rebuild(Strip *strip, MovieReader *anim)
|
||||
{
|
||||
if ((strip->data->proxy->build_flags & SEQ_PROXY_SKIP_EXISTING) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
IMB_Proxy_Size required_proxies = IMB_Proxy_Size(strip->data->proxy->build_size_flags);
|
||||
int built_proxies = MOV_get_existing_proxies(anim);
|
||||
return (required_proxies & built_proxies) != required_proxies;
|
||||
}
|
||||
|
||||
bool proxy_build_start(Main *bmain,
|
||||
Scene *scene,
|
||||
Strip *strip,
|
||||
Set<std::string> *processed_paths,
|
||||
bool build_only_on_bad_performance,
|
||||
Vector<ProxyBuildContext *> &r_queue)
|
||||
{
|
||||
if (!strip->data || !strip->data->proxy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(strip->flag & SEQ_USE_PROXY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int num_files = seq_proxy_context_count(strip, scene);
|
||||
|
||||
MultiViewPrefixVars prefix_vars; /* Initialized by #seq_proxy_multiview_context_invalid. */
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
if (seq_proxy_multiview_context_invalid(strip, scene, i, &prefix_vars)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if proxies are already built here, because actually opening anims takes a lot of
|
||||
* time. */
|
||||
strip_open_anim_file(scene, strip, false);
|
||||
MovieReader *anim = strip->runtime->movie_reader_get(i);
|
||||
if (anim && !seq_proxy_need_rebuild(strip, anim)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
strip_free_movie_readers(strip);
|
||||
|
||||
ProxyBuildContext *context = MEM_new<ProxyBuildContext>("strip proxy rebuild context");
|
||||
|
||||
Strip *strip_new = strip_duplicate_recursive(
|
||||
bmain, scene, scene, nullptr, strip, StripDuplicate::Selected);
|
||||
|
||||
context->size_flags = strip_new->data->proxy->build_size_flags;
|
||||
context->quality = strip_new->data->proxy->quality;
|
||||
context->overwrite = (strip_new->data->proxy->build_flags & SEQ_PROXY_SKIP_EXISTING) == 0;
|
||||
|
||||
context->bmain = bmain;
|
||||
context->scene = scene;
|
||||
context->strip = strip_new;
|
||||
|
||||
context->view_id = i; /* only for images */
|
||||
|
||||
if (strip_new->type == STRIP_TYPE_MOVIE) {
|
||||
strip_open_anim_file(scene, strip_new, true);
|
||||
anim = strip_new->runtime->movie_reader_get(i);
|
||||
if (anim) {
|
||||
context->movie_proxy_builder = MOV_proxy_builder_start(anim,
|
||||
context->size_flags,
|
||||
context->quality,
|
||||
context->overwrite,
|
||||
processed_paths,
|
||||
build_only_on_bad_performance);
|
||||
}
|
||||
if (!context->movie_proxy_builder) {
|
||||
MEM_delete(context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
r_queue.append(context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void seq_proxy_build_frame(const Scene *scene,
|
||||
const int view_id,
|
||||
ImBuf *ibuf_full,
|
||||
const Strip &strip,
|
||||
const StripElem &strip_elem,
|
||||
int proxy_render_size,
|
||||
const bool overwrite)
|
||||
{
|
||||
char filepath[PROXY_MAXFILE];
|
||||
|
||||
if (!seq_proxy_get_filepath_for_elem(scene,
|
||||
strip,
|
||||
strip_elem,
|
||||
eSpaceSeq_Proxy_RenderSize(proxy_render_size),
|
||||
filepath,
|
||||
view_id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!overwrite && BLI_exists(filepath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int rectx = (proxy_render_size * ibuf_full->x) / 100;
|
||||
const int recty = (proxy_render_size * ibuf_full->y) / 100;
|
||||
|
||||
ImBuf *ibuf = ibuf_full;
|
||||
if (ibuf_full->x != rectx || ibuf_full->y != recty) {
|
||||
ibuf = IMB_scale_into_new(ibuf_full, rectx, recty, IMBScaleFilter::Nearest, true);
|
||||
}
|
||||
|
||||
const int quality = strip.data->proxy->quality;
|
||||
const bool save_float = ibuf->float_data() != nullptr;
|
||||
ibuf->foptions.quality = quality;
|
||||
if (save_float) {
|
||||
/* Float image: save as EXR with FP16 data and DWAA compression. */
|
||||
ibuf->ftype = IMB_FTYPE_OPENEXR;
|
||||
ibuf->foptions.flag = OPENEXR_HALF | R_IMF_EXR_CODEC_DWAA;
|
||||
}
|
||||
else {
|
||||
/* Byte image: save as JPG. */
|
||||
ibuf->ftype = IMB_FTYPE_JPG;
|
||||
if (ibuf->can_contain_alpha()) {
|
||||
ibuf->color_mode = ImColorMode::RGB; /* JPGs do not support alpha. */
|
||||
}
|
||||
}
|
||||
BLI_file_ensure_parent_dir_exists(filepath);
|
||||
|
||||
const bool ok = IMB_save_image(
|
||||
ibuf, filepath, save_float ? ImBufFlags::FloatData : ImBufFlags::ByteData);
|
||||
if (ok == false) {
|
||||
perror(filepath);
|
||||
}
|
||||
|
||||
if (ibuf != ibuf_full) {
|
||||
IMB_freeImBuf(ibuf);
|
||||
}
|
||||
}
|
||||
|
||||
static ImBuf *render_image_strip_frame(const ProxyBuildContext &context,
|
||||
const Strip &strip,
|
||||
char *filepath,
|
||||
char *prefix,
|
||||
const char *ext,
|
||||
int view_id)
|
||||
{
|
||||
ImBuf *ibuf = nullptr;
|
||||
|
||||
ImBufFlags flag = ImBufFlags::ByteData | ImBufFlags::Metadata | ImBufFlags::MultiLayer;
|
||||
if (strip.alpha_mode == SEQ_ALPHA_PREMUL) {
|
||||
flag |= ImBufFlags::AlphaPremul;
|
||||
}
|
||||
|
||||
if (prefix[0] == '\0') {
|
||||
ibuf = IMB_load_image_from_filepath(filepath, flag, strip.data->colorspace_settings.name);
|
||||
}
|
||||
else {
|
||||
char filepath_view[FILE_MAX];
|
||||
BKE_scene_multiview_view_prefix_get(context.scene, filepath, prefix, &ext);
|
||||
seq_multiview_name(context.scene, view_id, prefix, ext, filepath_view, FILE_MAX);
|
||||
ibuf = IMB_load_image_from_filepath(filepath_view, flag, strip.data->colorspace_settings.name);
|
||||
}
|
||||
if (ibuf == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
convert_multilayer_ibuf(ibuf);
|
||||
if (ibuf->float_data() != nullptr && ibuf->byte_data() != nullptr) {
|
||||
IMB_free_byte_pixels(ibuf); /* If both float & byte exist, free byte buffer. */
|
||||
}
|
||||
ensure_ibuf_is_sequencer_space(context.scene, ibuf, false);
|
||||
return ibuf;
|
||||
}
|
||||
|
||||
static void image_proxy_builder_process(ProxyBuildContext &context,
|
||||
const bool *job_stop,
|
||||
bool *job_update_ui,
|
||||
const FunctionRef<void(float progress)> set_progress_fn)
|
||||
{
|
||||
if (!context.strip || !context.strip->data) {
|
||||
return;
|
||||
}
|
||||
const Strip &strip = *context.strip;
|
||||
if (!(strip.flag & SEQ_USE_PROXY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If proxy is set to user-specified custom file, no need to rebuild anything. */
|
||||
/* that's why it is called custom... */
|
||||
if (strip.data->proxy && (strip.data->proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *base_path = ID_BLEND_PATH_FROM_GLOBAL(&context.scene->id);
|
||||
const int tot_views = BKE_scene_multiview_num_views_get(&context.scene->r);
|
||||
|
||||
for (int elem_index = 0; elem_index < strip.len; elem_index++) {
|
||||
const StripElem &s_elem = strip.data->stripdata[elem_index];
|
||||
|
||||
char filepath[FILE_MAX];
|
||||
const char *ext = nullptr;
|
||||
char prefix[FILE_MAX];
|
||||
|
||||
ImBuf *ibuf = nullptr;
|
||||
|
||||
BLI_path_join(filepath, sizeof(filepath), strip.data->dirpath, s_elem.filename);
|
||||
BLI_path_abs(filepath, base_path);
|
||||
|
||||
const int totfiles = seq_num_files(context.scene, strip.views_format, true);
|
||||
bool is_multiview_render = seq_image_strip_is_multiview_render(
|
||||
context.scene, &strip, totfiles, filepath, prefix, ext);
|
||||
|
||||
if (is_multiview_render) {
|
||||
Array<ImBuf *> ibufs_arr(tot_views, nullptr);
|
||||
|
||||
for (int view_id = 0; view_id < totfiles; view_id++) {
|
||||
ibufs_arr[view_id] = render_image_strip_frame(
|
||||
context, strip, filepath, prefix, ext, view_id);
|
||||
}
|
||||
|
||||
if (ibufs_arr[0] != nullptr) {
|
||||
if (strip.views_format == R_IMF_VIEWS_STEREO_3D) {
|
||||
IMB_ImBufFromStereo3d(strip.stereo3d_format, ibufs_arr[0], &ibufs_arr[0], &ibufs_arr[1]);
|
||||
}
|
||||
|
||||
/* Return the requested image; release the others. */
|
||||
ibuf = ibufs_arr[context.view_id];
|
||||
for (ImBuf *ib : ibufs_arr) {
|
||||
if (ib != ibuf) {
|
||||
IMB_freeImBuf(ib);
|
||||
}
|
||||
}
|
||||
if (ibuf) {
|
||||
seq_imbuf_assign_spaces(context.scene, ibuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ibuf = render_image_strip_frame(context, strip, filepath, prefix, ext, context.view_id);
|
||||
}
|
||||
|
||||
if (ibuf != nullptr) {
|
||||
if (context.size_flags & IMB_PROXY_25) {
|
||||
seq_proxy_build_frame(
|
||||
context.scene, context.view_id, ibuf, strip, s_elem, 25, context.overwrite);
|
||||
}
|
||||
if (context.size_flags & IMB_PROXY_50) {
|
||||
seq_proxy_build_frame(
|
||||
context.scene, context.view_id, ibuf, strip, s_elem, 50, context.overwrite);
|
||||
}
|
||||
if (context.size_flags & IMB_PROXY_75) {
|
||||
seq_proxy_build_frame(
|
||||
context.scene, context.view_id, ibuf, strip, s_elem, 75, context.overwrite);
|
||||
}
|
||||
if (context.size_flags & IMB_PROXY_100) {
|
||||
seq_proxy_build_frame(
|
||||
context.scene, context.view_id, ibuf, strip, s_elem, 100, context.overwrite);
|
||||
}
|
||||
|
||||
if (set_progress_fn) {
|
||||
float progress = float(elem_index) / float(strip.len);
|
||||
set_progress_fn(progress);
|
||||
}
|
||||
|
||||
*job_update_ui = true;
|
||||
|
||||
IMB_freeImBuf(ibuf);
|
||||
}
|
||||
|
||||
if (*job_stop || G.is_break) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void close_movie_proxy_builder(ProxyBuildContext *context, bool stop)
|
||||
{
|
||||
if (context->movie_proxy_builder == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (MovieReader *movie : context->strip->runtime->movie_readers) {
|
||||
MOV_close_proxies(movie);
|
||||
}
|
||||
MOV_proxy_builder_finish(context->movie_proxy_builder, stop);
|
||||
context->movie_proxy_builder = nullptr;
|
||||
}
|
||||
|
||||
void proxy_build_process(ProxyBuildContext *context,
|
||||
const bool *should_stop,
|
||||
bool *has_updated,
|
||||
const FunctionRef<void(float progress)> set_progress_fn)
|
||||
{
|
||||
if (context->strip->type == STRIP_TYPE_MOVIE) {
|
||||
if (context->movie_proxy_builder) {
|
||||
MOV_proxy_builder_process(
|
||||
context->movie_proxy_builder, should_stop, has_updated, set_progress_fn);
|
||||
close_movie_proxy_builder(context, *should_stop);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (context->strip->type == STRIP_TYPE_IMAGE) {
|
||||
image_proxy_builder_process(*context, should_stop, has_updated, set_progress_fn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void proxy_build_finish(ProxyBuildContext *context)
|
||||
{
|
||||
close_movie_proxy_builder(context, false);
|
||||
seq_free_strip_recurse(nullptr, context->strip, true);
|
||||
|
||||
MEM_delete(context);
|
||||
}
|
||||
|
||||
void proxy_set(Strip *strip, bool value)
|
||||
{
|
||||
if (value) {
|
||||
strip->flag |= SEQ_USE_PROXY;
|
||||
if (strip->data->proxy == nullptr) {
|
||||
strip->data->proxy = seq_strip_proxy_alloc();
|
||||
}
|
||||
}
|
||||
else {
|
||||
strip->flag &= ~SEQ_USE_PROXY;
|
||||
}
|
||||
}
|
||||
|
||||
void seq_proxy_index_dir_set(MovieReader *anim, const char *base_dir)
|
||||
{
|
||||
char dirname[FILE_MAX];
|
||||
char filename[FILE_MAXFILE];
|
||||
|
||||
MOV_get_filename(anim, filename, FILE_MAXFILE);
|
||||
BLI_path_join(dirname, sizeof(dirname), base_dir, filename);
|
||||
MOV_set_custom_proxy_dir(anim, dirname);
|
||||
}
|
||||
|
||||
void free_strip_proxy(Strip *strip)
|
||||
{
|
||||
if (strip->data && strip->data->proxy && strip->data->proxy->anim) {
|
||||
MOV_close(strip->data->proxy->anim);
|
||||
strip->data->proxy->anim = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
28
blender-5.2.0/source/blender/sequencer/intern/proxy.hh
Normal file
28
blender-5.2.0/source/blender/sequencer/intern/proxy.hh
Normal file
@@ -0,0 +1,28 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ImBuf;
|
||||
struct MovieReader;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct RenderData;
|
||||
|
||||
#define PROXY_MAXFILE (2 * FILE_MAXDIR + FILE_MAXFILE)
|
||||
ImBuf *seq_proxy_fetch(const RenderData *context, Strip *strip, int timeline_frame);
|
||||
bool seq_proxy_get_custom_file_filepath(const Strip *strip, char *filepath, int view_id);
|
||||
void free_strip_proxy(Strip *strip);
|
||||
void seq_proxy_index_dir_set(MovieReader *anim, const char *base_dir);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
98
blender-5.2.0/source/blender/sequencer/intern/proxy_job.cc
Normal file
98
blender-5.2.0/source/blender/sequencer/intern/proxy_job.cc
Normal file
@@ -0,0 +1,98 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
|
||||
#include "SEQ_proxy.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void proxy_freejob(void *pjv)
|
||||
{
|
||||
ProxyJob *pj = static_cast<ProxyJob *>(pjv);
|
||||
MEM_delete(pj);
|
||||
}
|
||||
|
||||
/* Only this runs inside thread. */
|
||||
static void proxy_startjob(void *pjv, wmJobWorkerStatus *worker_status)
|
||||
{
|
||||
ProxyJob *pj = static_cast<ProxyJob *>(pjv);
|
||||
for (const int i : pj->queue.index_range()) {
|
||||
ProxyBuildContext *context = pj->queue[i];
|
||||
proxy_build_process(
|
||||
context, &worker_status->stop, &worker_status->do_update, [&](const float new_progress) {
|
||||
/* Remap the progress of the current proxy to the total progress. */
|
||||
const float total_progress = (i + new_progress) / pj->queue.size();
|
||||
worker_status->progress = total_progress;
|
||||
});
|
||||
|
||||
if (worker_status->stop) {
|
||||
pj->stop = true;
|
||||
fprintf(stderr, "Canceling proxy rebuild on users request...\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void proxy_endjob(void *pjv)
|
||||
{
|
||||
ProxyJob *pj = static_cast<ProxyJob *>(pjv);
|
||||
Editing *ed = editing_get(pj->scene);
|
||||
|
||||
for (ProxyBuildContext *context : pj->queue) {
|
||||
proxy_build_finish(context);
|
||||
}
|
||||
pj->queue.clear();
|
||||
|
||||
relations_free_imbuf(pj->scene, &ed->seqbase, false);
|
||||
|
||||
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, pj->scene);
|
||||
}
|
||||
|
||||
ProxyJob *ED_seq_proxy_job_get(const bContext *C, wmJob *wm_job)
|
||||
{
|
||||
Scene *scene = CTX_data_sequencer_scene(C);
|
||||
ProxyJob *pj = static_cast<ProxyJob *>(WM_jobs_customdata_get(wm_job));
|
||||
if (!pj) {
|
||||
pj = MEM_new<ProxyJob>("proxy rebuild job");
|
||||
pj->scene = scene;
|
||||
pj->main = CTX_data_main(C);
|
||||
WM_jobs_customdata_set(wm_job, pj, proxy_freejob);
|
||||
WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_SEQUENCER, NC_SCENE | ND_SEQUENCER);
|
||||
WM_jobs_callbacks(wm_job, proxy_startjob, nullptr, nullptr, proxy_endjob);
|
||||
}
|
||||
return pj;
|
||||
}
|
||||
|
||||
wmJob *ED_seq_proxy_wm_job_get(const bContext *C)
|
||||
{
|
||||
Scene *scene = CTX_data_sequencer_scene(C);
|
||||
wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
|
||||
CTX_wm_window(C),
|
||||
scene,
|
||||
"Building proxies...",
|
||||
WM_JOB_PROGRESS,
|
||||
WM_JOB_TYPE_SEQ_BUILD_PROXY);
|
||||
return wm_job;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
2103
blender-5.2.0/source/blender/sequencer/intern/render.cc
Normal file
2103
blender-5.2.0/source/blender/sequencer/intern/render.cc
Normal file
File diff suppressed because it is too large
Load Diff
98
blender-5.2.0/source/blender/sequencer/intern/render.hh
Normal file
98
blender-5.2.0/source/blender/sequencer/intern/render.hh
Normal file
@@ -0,0 +1,98 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct ImBuf;
|
||||
struct Mask;
|
||||
struct RenderData;
|
||||
struct Scene;
|
||||
struct SeqTimelineChannel;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/* Recursion protection while rendering a single sequencer frame.
|
||||
* If the same scene or strip is seen, recursion stops. */
|
||||
struct SeqRenderState {
|
||||
Set<Scene *> scenes_in_progress;
|
||||
Set<Strip *> strips_in_progress;
|
||||
};
|
||||
|
||||
/* Strip corner coordinates in screen pixel space. Note that they might not be
|
||||
* axis aligned when rotation is present. */
|
||||
struct StripScreenQuad {
|
||||
float2 v0, v1, v2, v3;
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
return v0 == v1 && v2 == v3 && v0 == v2;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of rendering a strip: the produced image,
|
||||
* plus some auxiliary data.
|
||||
*/
|
||||
struct SeqResult {
|
||||
bool is_valid() const
|
||||
{
|
||||
return image != nullptr;
|
||||
}
|
||||
|
||||
ImBuf *image = nullptr;
|
||||
/* How much the resulting image should be translated, in pixels. */
|
||||
float2 translation = float2(0, 0);
|
||||
bool is_opaque_before_transform = false;
|
||||
};
|
||||
|
||||
SeqResult seq_render_give_ibuf_seqbase(const RenderData *context,
|
||||
SeqRenderState *state,
|
||||
float timeline_frame,
|
||||
int chan_shown,
|
||||
ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbasep);
|
||||
SeqResult seq_render_strip(const RenderData *context,
|
||||
SeqRenderState *state,
|
||||
Strip *strip,
|
||||
float timeline_frame);
|
||||
|
||||
/* Renders Mask into an image suitable for sequencer:
|
||||
* RGB channels contain mask intensity; alpha channel is opaque. */
|
||||
ImBuf *seq_render_mask(Depsgraph *depsgraph,
|
||||
int width,
|
||||
int height,
|
||||
const Mask *mask,
|
||||
float frame_index,
|
||||
bool make_float);
|
||||
|
||||
/* Converts image to sequencer color space, if needed. */
|
||||
void ensure_ibuf_is_sequencer_space(const Scene *scene, ImBuf *ibuf, bool make_float);
|
||||
|
||||
void seq_imbuf_assign_spaces(const Scene *scene, ImBuf *ibuf);
|
||||
|
||||
StripScreenQuad get_strip_screen_quad(const RenderData *context, const Strip *strip);
|
||||
|
||||
void convert_multilayer_ibuf(ImBuf *ibuf);
|
||||
bool seq_image_strip_is_multiview_render(const Scene *scene,
|
||||
const Strip *strip,
|
||||
int totfiles,
|
||||
const char *filepath,
|
||||
char *r_prefix,
|
||||
const char *r_ext);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
1354
blender-5.2.0/source/blender/sequencer/intern/sequencer.cc
Normal file
1354
blender-5.2.0/source/blender/sequencer/intern/sequencer.cc
Normal file
File diff suppressed because it is too large
Load Diff
40
blender-5.2.0/source/blender/sequencer/intern/sequencer.hh
Normal file
40
blender-5.2.0/source/blender/sequencer/intern/sequencer.hh
Normal file
@@ -0,0 +1,40 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_span.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Editing;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct StripProxy;
|
||||
|
||||
namespace seq {
|
||||
|
||||
/**
|
||||
* Cache must be freed before calling this function
|
||||
* since it leaves the #Editing::seqbase in an invalid state.
|
||||
*/
|
||||
void seq_free_strip_recurse(Scene *scene, Strip *strip, bool do_id_user);
|
||||
StripProxy *seq_strip_proxy_alloc();
|
||||
/**
|
||||
* Find effect strips, that use strip `strip` as one of inputs.
|
||||
* If lookup hash doesn't exist, it will be created. If hash is tagged as invalid, it will be
|
||||
* rebuilt.
|
||||
*
|
||||
* \param key: pointer to Strip inside of meta strip
|
||||
*
|
||||
* \return collection of effect strips
|
||||
*/
|
||||
Span<Strip *> SEQ_lookup_effects_by_strip(Editing *ed, const Strip *key);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
535
blender-5.2.0/source/blender/sequencer/intern/sound.cc
Normal file
535
blender-5.2.0/source/blender/sequencer/intern/sound.cc
Normal file
@@ -0,0 +1,535 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <xxhash.h>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_curve_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_sound_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_sound.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_sound.hh"
|
||||
|
||||
#include "strip_time.hh"
|
||||
|
||||
#ifdef WITH_AUDASPACE
|
||||
# include <fx/Echo.h>
|
||||
# ifdef WITH_CONVOLUTION
|
||||
# include <fx/Equalizer.h>
|
||||
# endif
|
||||
# include <fx/TimeStretchPitchScale.h>
|
||||
# include <util/Buffer.h>
|
||||
#endif
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* Unlike _update_sound_ functions,
|
||||
* these ones take info from audaspace to update sequence length! */
|
||||
const SoundModifierWorkerInfo workersSoundModifiers[] = {
|
||||
{eSeqModifierType_SoundEqualizer, sound_equalizermodifier_recreator},
|
||||
{eSeqModifierType_Pitch, pitchmodifier_recreator},
|
||||
{eSeqModifierType_Echo, echomodifier_recreator},
|
||||
{0, nullptr}};
|
||||
|
||||
#ifdef WITH_CONVOLUTION
|
||||
static bool sequencer_refresh_sound_length_recursive(Main *bmain,
|
||||
Scene *scene,
|
||||
ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
if (sequencer_refresh_sound_length_recursive(bmain, scene, &strip.seqbase)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if (strip.type == STRIP_TYPE_SOUND && strip.sound) {
|
||||
SoundInfo info;
|
||||
if (!BKE_sound_info_get(bmain, strip.sound, &info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int old = strip.len;
|
||||
float fac;
|
||||
|
||||
strip.len = std::max(
|
||||
1, int(round((info.length - strip.sound->offset_time) * scene->frames_per_second())));
|
||||
fac = float(strip.len) / float(old);
|
||||
old = strip.startofs;
|
||||
strip.startofs *= fac;
|
||||
strip.endofs *= fac;
|
||||
strip.start += (old -
|
||||
strip.startofs); /* So that visual/"real" start frame does not change! */
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
#endif
|
||||
|
||||
void sound_update_length(Main *bmain, Scene *scene)
|
||||
{
|
||||
#ifdef WITH_CONVOLUTION
|
||||
if (scene->ed) {
|
||||
sequencer_refresh_sound_length_recursive(bmain, scene, &scene->ed->seqbase);
|
||||
}
|
||||
#else
|
||||
UNUSED_VARS(bmain, scene);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sound_update_bounds_all(Scene *scene)
|
||||
{
|
||||
Editing *ed = scene->ed;
|
||||
|
||||
if (ed) {
|
||||
for (Strip &strip : ed->seqbase) {
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
strip_update_sound_bounds_recursive(scene, &strip);
|
||||
}
|
||||
else if (ELEM(strip.type, STRIP_TYPE_SOUND, STRIP_TYPE_SCENE)) {
|
||||
sound_update_bounds(scene, &strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sound_update_bounds(Scene *scene, Strip *strip)
|
||||
{
|
||||
if (strip->type == STRIP_TYPE_SCENE) {
|
||||
if (strip->scene && strip->runtime->scene_sound) {
|
||||
/* We have to take into account start frame of the sequence's scene! */
|
||||
int startofs = strip->startofs + strip->anim_startofs + strip->scene->r.sfra;
|
||||
|
||||
BKE_sound_move_scene_sound(scene,
|
||||
strip->runtime->scene_sound,
|
||||
strip->left_handle(),
|
||||
strip->right_handle(scene),
|
||||
startofs,
|
||||
0.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BKE_sound_move_scene_sound_defaults(scene, strip);
|
||||
}
|
||||
/* mute is set in strip_update_muting_recursive */
|
||||
}
|
||||
|
||||
static void strip_update_sound_recursive(Scene *scene, ListBaseT<Strip> *seqbasep, bSound *sound)
|
||||
{
|
||||
for (Strip &strip : *seqbasep) {
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
strip_update_sound_recursive(scene, &strip.seqbase, sound);
|
||||
}
|
||||
else if (strip.type == STRIP_TYPE_SOUND) {
|
||||
if (strip.runtime->scene_sound && sound == strip.sound) {
|
||||
BKE_sound_update_scene_sound(strip.runtime->scene_sound, sound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sound_update(Scene *scene, bSound *sound)
|
||||
{
|
||||
if (scene->ed) {
|
||||
strip_update_sound_recursive(scene, &scene->ed->seqbase, sound);
|
||||
}
|
||||
}
|
||||
|
||||
float sound_pitch_get(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
const Strip *meta_parent = lookup_meta_by_strip(scene->ed, strip);
|
||||
if (meta_parent != nullptr) {
|
||||
return strip->speed_factor * sound_pitch_get(scene, meta_parent);
|
||||
}
|
||||
return strip->speed_factor;
|
||||
}
|
||||
|
||||
EQCurveMappingData *sound_equalizer_add(SoundEqualizerModifierData *semd, float minX, float maxX)
|
||||
{
|
||||
EQCurveMappingData *eqcmd;
|
||||
|
||||
if (maxX < 0) {
|
||||
maxX = SOUND_EQUALIZER_DEFAULT_MAX_FREQ;
|
||||
}
|
||||
if (minX < 0) {
|
||||
minX = 0.0;
|
||||
}
|
||||
/* It's the same as #BKE_curvemapping_add, but changing the name. */
|
||||
eqcmd = MEM_new<EQCurveMappingData>("Equalizer");
|
||||
BKE_curvemapping_set_defaults(&eqcmd->curve_mapping,
|
||||
1, /* Total. */
|
||||
minX,
|
||||
-SOUND_EQUALIZER_DEFAULT_MAX_DB, /* Min x, y */
|
||||
maxX,
|
||||
SOUND_EQUALIZER_DEFAULT_MAX_DB, /* Max x, y */
|
||||
HD_AUTO_ANIM);
|
||||
|
||||
eqcmd->curve_mapping.preset = CURVE_PRESET_CONSTANT_MEDIAN;
|
||||
|
||||
rctf clipr;
|
||||
clipr.xmin = minX;
|
||||
clipr.xmax = maxX;
|
||||
clipr.ymin = 0.0;
|
||||
clipr.ymax = 0.0;
|
||||
|
||||
BKE_curvemap_reset(&eqcmd->curve_mapping.cm[0],
|
||||
&clipr,
|
||||
CURVE_PRESET_CONSTANT_MEDIAN,
|
||||
CurveMapSlopeType::Negative);
|
||||
|
||||
BLI_addtail(&semd->graphics, eqcmd);
|
||||
|
||||
return eqcmd;
|
||||
}
|
||||
|
||||
void sound_equalizermodifier_set_graphs(SoundEqualizerModifierData *semd, int number)
|
||||
{
|
||||
sound_equalizermodifier_free(reinterpret_cast<StripModifierData *>(semd));
|
||||
if (number == 1) {
|
||||
sound_equalizer_add(semd, SOUND_EQUALIZER_DEFAULT_MIN_FREQ, SOUND_EQUALIZER_DEFAULT_MAX_FREQ);
|
||||
}
|
||||
else if (number == 2) {
|
||||
sound_equalizer_add(semd, 30.0, 2000.0);
|
||||
sound_equalizer_add(semd, 2000.1, 20000.0);
|
||||
}
|
||||
else if (number == 3) {
|
||||
sound_equalizer_add(semd, 30.0, 1000.0);
|
||||
sound_equalizer_add(semd, 1000.1, 5000.0);
|
||||
sound_equalizer_add(semd, 5000.1, 20000.0);
|
||||
}
|
||||
}
|
||||
|
||||
EQCurveMappingData *sound_equalizermodifier_add_graph(SoundEqualizerModifierData *semd,
|
||||
float min_freq,
|
||||
float max_freq)
|
||||
{
|
||||
if (min_freq < 0.0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (max_freq < 0.0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (max_freq <= min_freq) {
|
||||
return nullptr;
|
||||
}
|
||||
return sound_equalizer_add(semd, min_freq, max_freq);
|
||||
}
|
||||
|
||||
void sound_equalizermodifier_remove_graph(SoundEqualizerModifierData *semd,
|
||||
EQCurveMappingData *eqcmd)
|
||||
{
|
||||
BLI_remlink_safe(&semd->graphics, eqcmd);
|
||||
MEM_delete(eqcmd);
|
||||
}
|
||||
|
||||
void sound_equalizermodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
SoundEqualizerModifierData *semd = reinterpret_cast<SoundEqualizerModifierData *>(smd);
|
||||
|
||||
sound_equalizer_add(semd, SOUND_EQUALIZER_DEFAULT_MIN_FREQ, SOUND_EQUALIZER_DEFAULT_MAX_FREQ);
|
||||
}
|
||||
|
||||
void sound_equalizermodifier_free(StripModifierData *smd)
|
||||
{
|
||||
SoundEqualizerModifierData *semd = reinterpret_cast<SoundEqualizerModifierData *>(smd);
|
||||
for (EQCurveMappingData &eqcmd : semd->graphics.items_mutable()) {
|
||||
BKE_curvemapping_free_data(&eqcmd.curve_mapping);
|
||||
MEM_delete(&eqcmd);
|
||||
}
|
||||
BLI_listbase_clear(&semd->graphics);
|
||||
}
|
||||
|
||||
void sound_equalizermodifier_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
SoundEqualizerModifierData *semd = reinterpret_cast<SoundEqualizerModifierData *>(smd);
|
||||
SoundEqualizerModifierData *semd_target = reinterpret_cast<SoundEqualizerModifierData *>(target);
|
||||
EQCurveMappingData *eqcmd_n;
|
||||
|
||||
BLI_listbase_clear(&semd_target->graphics);
|
||||
|
||||
for (EQCurveMappingData &eqcmd : semd->graphics) {
|
||||
eqcmd_n = MEM_dupalloc(&eqcmd);
|
||||
BKE_curvemapping_copy_data(&eqcmd_n->curve_mapping, &eqcmd.curve_mapping);
|
||||
|
||||
eqcmd_n->next = eqcmd_n->prev = nullptr;
|
||||
BLI_addtail(&semd_target->graphics, eqcmd_n);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_CONVOLUTION
|
||||
static uint64_t sound_equalizermodifier_get_params_hash(float *buf)
|
||||
{
|
||||
return XXH3_64bits(buf, sizeof(float) * SOUND_EQUALIZER_SIZE_DEFINITION);
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_Sound sound_equalizermodifier_recreator(Strip *strip,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound_in,
|
||||
bool &needs_update)
|
||||
{
|
||||
#ifdef WITH_CONVOLUTION
|
||||
UNUSED_VARS(strip);
|
||||
|
||||
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
|
||||
|
||||
/* No equalizer definition. */
|
||||
if (semd->graphics.is_empty()) {
|
||||
return sound_in;
|
||||
}
|
||||
|
||||
float *buf = MEM_new_array_zeroed<float>(SOUND_EQUALIZER_SIZE_DEFINITION, "eqrecreator");
|
||||
|
||||
CurveMapping *eq_mapping;
|
||||
CurveMap *cm;
|
||||
float minX;
|
||||
float maxX;
|
||||
float interval = SOUND_EQUALIZER_DEFAULT_MAX_FREQ / float(SOUND_EQUALIZER_SIZE_DEFINITION);
|
||||
|
||||
/* Visit all equalizer definitions. */
|
||||
for (EQCurveMappingData &mapping : semd->graphics) {
|
||||
eq_mapping = &mapping.curve_mapping;
|
||||
BKE_curvemapping_init(eq_mapping);
|
||||
cm = eq_mapping->cm;
|
||||
minX = eq_mapping->curr.xmin;
|
||||
maxX = eq_mapping->curr.xmax;
|
||||
int idx = int(ceil(minX / interval));
|
||||
int i = idx;
|
||||
for (; i * interval <= maxX && i < SOUND_EQUALIZER_SIZE_DEFINITION; i++) {
|
||||
float freq = i * interval;
|
||||
float val = BKE_curvemap_evaluateF(eq_mapping, cm, freq);
|
||||
if (fabs(val) > SOUND_EQUALIZER_DEFAULT_MAX_DB) {
|
||||
val = (val / fabs(val)) * SOUND_EQUALIZER_DEFAULT_MAX_DB;
|
||||
}
|
||||
buf[i] = val;
|
||||
/* To soften lower limit, but not the first position which is the constant value */
|
||||
if (i == idx && i > 2) {
|
||||
buf[i - 1] = 0.5 * (buf[i] + buf[i - 1]);
|
||||
}
|
||||
}
|
||||
/* To soften higher limit */
|
||||
if (i < SOUND_EQUALIZER_SIZE_DEFINITION) {
|
||||
buf[i] = 0.5 * (buf[i] + buf[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t curr_params_hash = sound_equalizermodifier_get_params_hash(buf);
|
||||
/* Only make new sound when necessary. It is faster and it prevents audio glitches. */
|
||||
if (!needs_update && smd->runtime->last_sound_in == sound_in &&
|
||||
curr_params_hash == smd->runtime->params_hash)
|
||||
{
|
||||
MEM_delete(buf);
|
||||
return smd->runtime->last_sound_out;
|
||||
}
|
||||
|
||||
std::shared_ptr<aud::Buffer> aud_buf = std::shared_ptr<aud::Buffer>(
|
||||
new aud::Buffer(sizeof(float) * SOUND_EQUALIZER_SIZE_DEFINITION));
|
||||
std::memcpy(aud_buf->getBuffer(), buf, sizeof(float) * SOUND_EQUALIZER_SIZE_DEFINITION);
|
||||
AUD_Sound sound_out = AUD_Sound(new aud::Equalizer(sound_in,
|
||||
aud_buf,
|
||||
SOUND_EQUALIZER_SIZE_DEFINITION,
|
||||
SOUND_EQUALIZER_DEFAULT_MAX_FREQ,
|
||||
SOUND_EQUALIZER_SIZE_CONVERSION));
|
||||
|
||||
needs_update = true;
|
||||
smd->runtime->last_sound_in = sound_in;
|
||||
smd->runtime->last_sound_out = sound_out;
|
||||
smd->runtime->params_hash = curr_params_hash;
|
||||
MEM_delete(buf);
|
||||
|
||||
return sound_out;
|
||||
#else
|
||||
UNUSED_VARS(strip, smd, sound_in, needs_update);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint64_t pitchmodifier_get_params_hash(PitchModifierData *pmd)
|
||||
{
|
||||
XXH3_state_t *state = XXH3_createState();
|
||||
XXH3_64bits_reset(state);
|
||||
|
||||
XXH3_64bits_update(state, &pmd->mode, sizeof(pmd->mode));
|
||||
XXH3_64bits_update(state, &pmd->quality, sizeof(pmd->quality));
|
||||
XXH3_64bits_update(state, &pmd->semitones, sizeof(pmd->semitones));
|
||||
XXH3_64bits_update(state, &pmd->cents, sizeof(pmd->cents));
|
||||
XXH3_64bits_update(state, &pmd->ratio, sizeof(pmd->ratio));
|
||||
XXH3_64bits_update(state, &pmd->preserve_formant, sizeof(pmd->preserve_formant));
|
||||
|
||||
uint64_t hash = XXH3_64bits_digest(state);
|
||||
XXH3_freeState(state);
|
||||
return hash;
|
||||
}
|
||||
|
||||
AUD_Sound pitchmodifier_recreator(Strip * /*strip*/,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound_in,
|
||||
bool &needs_update)
|
||||
{
|
||||
const uint64_t curr_params_hash = pitchmodifier_get_params_hash((PitchModifierData *)smd);
|
||||
if (!needs_update && smd->runtime->last_sound_in == sound_in &&
|
||||
curr_params_hash == smd->runtime->params_hash)
|
||||
{
|
||||
return smd->runtime->last_sound_out;
|
||||
}
|
||||
|
||||
#if defined(WITH_AUDASPACE) && defined(WITH_RUBBERBAND)
|
||||
PitchModifierData *pmd = (PitchModifierData *)smd;
|
||||
|
||||
aud::StretcherQuality quality;
|
||||
switch (pmd->quality) {
|
||||
case PITCH_QUALITY_HIGH:
|
||||
quality = aud::StretcherQuality::HIGH;
|
||||
break;
|
||||
case PITCH_QUALITY_FAST:
|
||||
quality = aud::StretcherQuality::FAST;
|
||||
break;
|
||||
case PITCH_QUALITY_CONSISTENT:
|
||||
quality = aud::StretcherQuality::CONSISTENT;
|
||||
break;
|
||||
default:
|
||||
quality = aud::StretcherQuality::HIGH;
|
||||
}
|
||||
|
||||
double pitch_scale = 0;
|
||||
int mode = pmd->mode;
|
||||
if (mode == PITCH_MODE_SEMITONES) {
|
||||
pitch_scale = pow(2.0, (pmd->semitones + (pmd->cents / 100.0)) / 12.0);
|
||||
}
|
||||
else if (mode == PITCH_MODE_RATIO) {
|
||||
pitch_scale = pmd->ratio;
|
||||
|
||||
if (pitch_scale <= 0.0) {
|
||||
pitch_scale = 1.0;
|
||||
pmd->ratio = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (pitch_scale == 0) {
|
||||
if (smd->runtime->last_sound_in == sound_in) {
|
||||
return smd->runtime->last_sound_out;
|
||||
}
|
||||
else {
|
||||
return sound_in;
|
||||
}
|
||||
}
|
||||
|
||||
AUD_Sound sound_out = AUD_Sound(
|
||||
new aud::TimeStretchPitchScale(sound_in, 1, pitch_scale, quality, pmd->preserve_formant));
|
||||
needs_update = true;
|
||||
smd->runtime->last_sound_in = sound_in;
|
||||
smd->runtime->last_sound_out = sound_out;
|
||||
smd->runtime->params_hash = curr_params_hash;
|
||||
|
||||
return sound_out;
|
||||
#else
|
||||
if (smd->runtime->last_sound_in == sound_in) {
|
||||
return smd->runtime->last_sound_out;
|
||||
}
|
||||
else {
|
||||
return sound_in;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDASPACE
|
||||
static uint64_t echomodifier_get_params_hash(EchoModifierData *emd)
|
||||
{
|
||||
XXH3_state_t *state = XXH3_createState();
|
||||
XXH3_64bits_reset(state);
|
||||
|
||||
XXH3_64bits_update(state, &emd->delay, sizeof(emd->delay));
|
||||
XXH3_64bits_update(state, &emd->feedback, sizeof(emd->feedback));
|
||||
XXH3_64bits_update(state, &emd->mix, sizeof(emd->mix));
|
||||
|
||||
uint64_t hash = XXH3_64bits_digest(state);
|
||||
XXH3_freeState(state);
|
||||
return hash;
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_Sound echomodifier_recreator(Strip * /*strip*/,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound_in,
|
||||
bool &needs_update)
|
||||
{
|
||||
#if defined(WITH_AUDASPACE)
|
||||
const uint64_t curr_params_hash = echomodifier_get_params_hash((EchoModifierData *)smd);
|
||||
if (!needs_update && smd->runtime->last_sound_in == sound_in &&
|
||||
curr_params_hash == smd->runtime->params_hash)
|
||||
{
|
||||
return smd->runtime->last_sound_out;
|
||||
}
|
||||
EchoModifierData *emd = (EchoModifierData *)smd;
|
||||
|
||||
AUD_Sound sound_out = AUD_Sound(
|
||||
new aud::Echo(sound_in, emd->delay, emd->feedback, emd->mix, true));
|
||||
needs_update = true;
|
||||
smd->runtime->last_sound_in = sound_in;
|
||||
smd->runtime->last_sound_out = sound_out;
|
||||
smd->runtime->params_hash = curr_params_hash;
|
||||
return sound_out;
|
||||
#else
|
||||
UNUSED_VARS(smd, sound_in, needs_update);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
const SoundModifierWorkerInfo *sound_modifier_worker_info_get(int type)
|
||||
{
|
||||
for (int i = 0; workersSoundModifiers[i].type > 0; i++) {
|
||||
if (workersSoundModifiers[i].type == type) {
|
||||
return &workersSoundModifiers[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AUD_Sound sound_modifier_recreator(Strip *strip,
|
||||
StripModifierData *smd,
|
||||
AUD_Sound sound,
|
||||
bool &needs_update)
|
||||
{
|
||||
|
||||
/* Check if the modifier mute flag has changed. */
|
||||
if ((smd->flag & STRIP_MODIFIER_FLAG_MUTE) != (smd->runtime->flag & STRIP_MODIFIER_FLAG_MUTE)) {
|
||||
eStripModifierFlag runtime_flag = smd->runtime->flag;
|
||||
/* Update the runtime mute flag and flag the sound handle for update. */
|
||||
runtime_flag &= ~(STRIP_MODIFIER_FLAG_MUTE); /* Clear the bit. */
|
||||
runtime_flag |= (smd->flag & STRIP_MODIFIER_FLAG_MUTE); /* Set the bit. */
|
||||
smd->runtime->flag = runtime_flag;
|
||||
needs_update = true;
|
||||
}
|
||||
|
||||
if (!(smd->flag & STRIP_MODIFIER_FLAG_MUTE)) {
|
||||
const SoundModifierWorkerInfo *smwi = sound_modifier_worker_info_get(smd->type);
|
||||
return smwi->recreator(strip, smd, sound, needs_update);
|
||||
}
|
||||
return sound;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
735
blender-5.2.0/source/blender/sequencer/intern/strip_add.cc
Normal file
735
blender-5.2.0/source/blender/sequencer/intern/strip_add.cc
Normal file
@@ -0,0 +1,735 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_mask_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_sound_types.h"
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_mask.hh"
|
||||
#include "BKE_movieclip.hh"
|
||||
#include "BKE_scene.hh"
|
||||
#include "BKE_sound.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "SEQ_add.hh"
|
||||
#include "SEQ_edit.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
#include "SEQ_transform.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "effects/effects.hh"
|
||||
#include "multiview.hh"
|
||||
#include "proxy.hh"
|
||||
#include "strip_time.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
void add_load_data_init(LoadData *load_data,
|
||||
const char *name,
|
||||
const char *path,
|
||||
const int start_frame,
|
||||
const int channel)
|
||||
{
|
||||
memset(load_data, 0, sizeof(LoadData));
|
||||
if (name != nullptr) {
|
||||
STRNCPY(load_data->name, name);
|
||||
}
|
||||
if (path != nullptr) {
|
||||
STRNCPY(load_data->path, path);
|
||||
}
|
||||
load_data->start_frame = start_frame;
|
||||
load_data->channel = channel;
|
||||
}
|
||||
|
||||
static void strip_add_generic_update(Scene *scene, Strip *strip)
|
||||
{
|
||||
strip_unique_name_set(scene, &scene->ed->seqbase, strip);
|
||||
/* Set effect time range values before cache invalidation. */
|
||||
strip_time_effect_range_set(scene, strip);
|
||||
relations_invalidate_cache(scene, strip);
|
||||
strip_lookup_invalidate(scene->ed);
|
||||
time_update_meta_strip_range(scene, lookup_meta_by_strip(scene->ed, strip));
|
||||
}
|
||||
|
||||
static void strip_add_set_name(Scene *scene, Strip *strip, LoadData *load_data)
|
||||
{
|
||||
if (load_data->name[0] != '\0') {
|
||||
edit_strip_name_set(scene, strip, load_data->name);
|
||||
}
|
||||
else {
|
||||
if (strip->type == STRIP_TYPE_SCENE) {
|
||||
edit_strip_name_set(scene, strip, load_data->scene->id.name + 2);
|
||||
}
|
||||
else if (strip->type == STRIP_TYPE_MOVIECLIP) {
|
||||
edit_strip_name_set(scene, strip, load_data->clip->id.name + 2);
|
||||
}
|
||||
else if (strip->type == STRIP_TYPE_MASK) {
|
||||
edit_strip_name_set(scene, strip, load_data->mask->id.name + 2);
|
||||
}
|
||||
else if (strip->is_effect()) {
|
||||
edit_strip_name_set(scene, strip, strip_give_name(strip));
|
||||
}
|
||||
else { /* Image, sound and movie. */
|
||||
edit_strip_name_set(scene, strip, load_data->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void strip_add_set_view_transform(Scene *scene, Strip *strip, LoadData *load_data)
|
||||
{
|
||||
const char *strip_colorspace = strip->data->colorspace_settings.name;
|
||||
|
||||
if (load_data->flags & SEQ_LOAD_SET_VIEW_TRANSFORM) {
|
||||
const char *role_colorspace_byte;
|
||||
role_colorspace_byte = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE);
|
||||
|
||||
if (STREQ(strip_colorspace, role_colorspace_byte)) {
|
||||
const ColorManagedDisplay *display = IMB_colormanagement_display_get_named(
|
||||
scene->display_settings.display_device);
|
||||
const char *default_view_transform =
|
||||
IMB_colormanagement_display_get_default_view_transform_name(display);
|
||||
STRNCPY_UTF8(scene->view_settings.view_transform, default_view_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Strip *add_scene_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
Strip *strip = strip_alloc(
|
||||
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_SCENE);
|
||||
strip->scene = load_data->scene;
|
||||
strip->scene_view_layer_name = BLI_strdup(BKE_view_layer_default_render(strip->scene)->name);
|
||||
strip->len = load_data->scene->r.efra - load_data->scene->r.sfra + 1;
|
||||
id_us_ensure_real(id_cast<ID *>(load_data->scene));
|
||||
strip_add_set_name(scene, strip, load_data);
|
||||
strip_add_generic_update(scene, strip);
|
||||
return strip;
|
||||
}
|
||||
|
||||
Strip *add_movieclip_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
Strip *strip = strip_alloc(
|
||||
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_MOVIECLIP);
|
||||
strip->clip = load_data->clip;
|
||||
strip->len = BKE_movieclip_get_duration(load_data->clip);
|
||||
id_us_ensure_real(id_cast<ID *>(load_data->clip));
|
||||
strip_add_set_name(scene, strip, load_data);
|
||||
strip_add_generic_update(scene, strip);
|
||||
return strip;
|
||||
}
|
||||
|
||||
Strip *add_mask_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
Strip *strip = strip_alloc(seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_MASK);
|
||||
strip->mask = load_data->mask;
|
||||
strip->len = BKE_mask_get_duration(load_data->mask);
|
||||
id_us_ensure_real(id_cast<ID *>(load_data->mask));
|
||||
strip_add_set_name(scene, strip, load_data);
|
||||
strip_add_generic_update(scene, strip);
|
||||
return strip;
|
||||
}
|
||||
|
||||
Strip *add_effect_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
Strip *strip = strip_alloc(
|
||||
seqbase, load_data->start_frame, load_data->channel, load_data->effect.type);
|
||||
|
||||
strip->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
|
||||
effect_ensure_initialized(strip);
|
||||
|
||||
const int min_inputs = effect_type_get_min_num_inputs(load_data->effect.type);
|
||||
if (min_inputs != 0 || load_data->effect.type == STRIP_TYPE_COMPOSITOR) {
|
||||
strip->input1 = load_data->effect.input1;
|
||||
strip->input2 = load_data->effect.input2;
|
||||
}
|
||||
|
||||
if (min_inputs == 1) {
|
||||
strip->blend_mode = strip->input1->blend_mode;
|
||||
strip->blend_opacity = strip->input1->blend_opacity;
|
||||
}
|
||||
|
||||
if (strip->input1 == nullptr) {
|
||||
strip->len = 1; /* Effect is generator, set non zero length. */
|
||||
strip->flag |= SEQ_SINGLE_FRAME_CONTENT;
|
||||
strip->right_handle_set(scene, load_data->start_frame + load_data->effect.length);
|
||||
}
|
||||
|
||||
strip_add_set_name(scene, strip, load_data);
|
||||
strip_add_generic_update(scene, strip);
|
||||
|
||||
return strip;
|
||||
}
|
||||
|
||||
void add_image_set_directory(Strip *strip, const char *dirpath)
|
||||
{
|
||||
STRNCPY(strip->data->dirpath, dirpath);
|
||||
}
|
||||
|
||||
void add_image_load_file(Scene *scene, Strip *strip, size_t strip_frame, const char *filename)
|
||||
{
|
||||
StripElem *se = render_give_stripelem(scene, strip, strip->content_start() + strip_frame);
|
||||
STRNCPY(se->filename, filename);
|
||||
}
|
||||
|
||||
void add_image_init_alpha_mode(Main *bmain, Scene *scene, Strip *strip)
|
||||
{
|
||||
if (strip->data && strip->data->stripdata) {
|
||||
char filepath[FILE_MAX];
|
||||
ImBuf *ibuf;
|
||||
|
||||
BLI_path_join(
|
||||
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH(bmain, &scene->id));
|
||||
|
||||
/* Initialize input color space. */
|
||||
if (strip->type == STRIP_TYPE_IMAGE) {
|
||||
ibuf = IMB_load_image_from_filepath(filepath,
|
||||
ImBufFlags::Test | ImBufFlags::MultiLayer |
|
||||
ImBufFlags::AlphaDetect,
|
||||
strip->data->colorspace_settings.name);
|
||||
|
||||
/* Byte images are default to straight alpha, however sequencer
|
||||
* works in pre-multiply space, so mark strip to be pre-multiplied first. */
|
||||
strip->alpha_mode = SEQ_ALPHA_STRAIGHT;
|
||||
if (ibuf) {
|
||||
if (flag_is_set(ibuf->flags, ImBufFlags::AlphaPremul)) {
|
||||
strip->alpha_mode = SEQ_ALPHA_PREMUL;
|
||||
}
|
||||
|
||||
IMB_freeImBuf(ibuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Strip *add_image_strip(Main *bmain, Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
Strip *strip = strip_alloc(
|
||||
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_IMAGE);
|
||||
strip->len = load_data->image.count;
|
||||
StripData *data = strip->data;
|
||||
data->stripdata = MEM_new_array<StripElem>(load_data->image.count, "stripelem");
|
||||
|
||||
if (strip->len == 1) {
|
||||
strip->flag |= SEQ_SINGLE_FRAME_CONTENT;
|
||||
}
|
||||
|
||||
/* Multiview settings. */
|
||||
if (load_data->use_multiview) {
|
||||
strip->flag |= SEQ_USE_VIEWS;
|
||||
strip->views_format = load_data->views_format;
|
||||
}
|
||||
if (load_data->stereo3d_format) {
|
||||
strip->stereo3d_format = MEM_new<Stereo3dFormat>("strip stereo3d format");
|
||||
*strip->stereo3d_format = *load_data->stereo3d_format;
|
||||
}
|
||||
|
||||
/* Set initial scale based on load_data->fit_method. */
|
||||
char file_path[FILE_MAX];
|
||||
STRNCPY(file_path, load_data->path);
|
||||
BLI_path_abs(file_path, ID_BLEND_PATH(bmain, &scene->id));
|
||||
|
||||
ImBuf *ibuf = IMB_load_image_from_filepath(file_path,
|
||||
ImBufFlags::ByteData | ImBufFlags::MultiLayer,
|
||||
strip->data->colorspace_settings.name);
|
||||
if (ibuf != nullptr) {
|
||||
/* Set image resolution. Assume that all images in sequence are same size. This fields are only
|
||||
* informative. */
|
||||
StripElem *strip_elem = data->stripdata;
|
||||
for (int i = 0; i < load_data->image.count; i++) {
|
||||
strip_elem->orig_width = ibuf->x;
|
||||
strip_elem->orig_height = ibuf->y;
|
||||
strip_elem++;
|
||||
}
|
||||
|
||||
set_scale_to_fit(strip, ibuf->x, ibuf->y, scene->r.xsch, scene->r.ysch, load_data->fit_method);
|
||||
IMB_freeImBuf(ibuf);
|
||||
}
|
||||
|
||||
/* Adjust starting length of strip from handle to handle.
|
||||
* Note that this differs from the content `strip->len`, which is always 1 for single images. */
|
||||
if (seq::transform_single_image_check(strip)) {
|
||||
strip->right_handle_set(scene, load_data->start_frame + load_data->image.length);
|
||||
}
|
||||
|
||||
strip_add_set_view_transform(scene, strip, load_data);
|
||||
strip_add_set_name(scene, strip, load_data);
|
||||
strip_add_generic_update(scene, strip);
|
||||
|
||||
return strip;
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDASPACE
|
||||
|
||||
Strip *add_sound_strip(Main *bmain, Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
/* Handles relative paths. */
|
||||
bSound *sound = BKE_sound_new_file_exists(bmain, load_data->path, load_data->stream_index);
|
||||
SoundInfo info;
|
||||
bool sound_loaded = BKE_sound_info_get(bmain, sound, &info);
|
||||
|
||||
if (!sound_loaded && !load_data->allow_invalid_file) {
|
||||
BKE_id_free_us(bmain, sound);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (info.specs.channels == SOUND_CHANNELS_INVALID && !load_data->allow_invalid_file) {
|
||||
BKE_id_free_us(bmain, sound);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Strip *strip = strip_alloc(
|
||||
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_SOUND);
|
||||
strip->sound = sound;
|
||||
strip->streamindex = load_data->stream_index;
|
||||
|
||||
/* We round the frame duration as the audio sample lengths usually does not
|
||||
* line up with the video frames. Therefore we round this number to the
|
||||
* nearest frame as the audio track usually overshoots or undershoots the
|
||||
* end frame of the video by a little bit.
|
||||
* See #47135 for under shoot example. */
|
||||
strip->len = std::max(
|
||||
1, int(round((info.length - sound->offset_time) * scene->frames_per_second())));
|
||||
|
||||
StripData *data = strip->data;
|
||||
/* We only need 1 element to store the filename. */
|
||||
StripElem *se = data->stripdata = MEM_new<StripElem>("stripelem");
|
||||
BLI_path_split_dir_file(
|
||||
load_data->path, data->dirpath, sizeof(data->dirpath), se->filename, sizeof(se->filename));
|
||||
|
||||
if (strip->sound != nullptr) {
|
||||
if (load_data->flags & SEQ_LOAD_SOUND_MONO) {
|
||||
strip->sound->flags |= SOUND_FLAGS_MONO;
|
||||
}
|
||||
|
||||
if (load_data->flags & SEQ_LOAD_SOUND_CACHE) {
|
||||
if (strip->sound) {
|
||||
strip->sound->flags |= SOUND_FLAGS_CACHING;
|
||||
}
|
||||
}
|
||||
|
||||
/* Turn on Display Waveform by default. */
|
||||
strip->flag |= SEQ_AUDIO_DRAW_WAVEFORM;
|
||||
|
||||
/* Turn on Preserve Pitch by default. */
|
||||
strip->flag |= SEQ_AUDIO_PITCH_CORRECTION;
|
||||
}
|
||||
|
||||
strip_add_set_name(scene, strip, load_data);
|
||||
strip_add_generic_update(scene, strip);
|
||||
|
||||
return strip;
|
||||
}
|
||||
|
||||
#else // WITH_AUDASPACE
|
||||
|
||||
Strip *add_sound_strip(Main * /*bmain*/,
|
||||
Scene * /*scene*/,
|
||||
ListBaseT<Strip> * /*seqbase*/,
|
||||
LoadData * /*load_data*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
#endif // WITH_AUDASPACE
|
||||
|
||||
Strip *add_meta_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
/* Allocate strip. */
|
||||
Strip *strip_meta = strip_alloc(
|
||||
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_META);
|
||||
|
||||
/* Set name. */
|
||||
strip_add_set_name(scene, strip_meta, load_data);
|
||||
|
||||
/* Set frames start and length. */
|
||||
strip_meta->start = load_data->start_frame;
|
||||
strip_meta->len = 1;
|
||||
|
||||
strip_add_generic_update(scene, strip_meta);
|
||||
|
||||
return strip_meta;
|
||||
}
|
||||
|
||||
Strip *add_movie_strip(Main *bmain, Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_data)
|
||||
{
|
||||
char filepath[sizeof(load_data->path)];
|
||||
STRNCPY(filepath, load_data->path);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH(bmain, &scene->id));
|
||||
|
||||
char colorspace[/*MAX_COLORSPACE_NAME*/ 64] = "\0";
|
||||
bool is_multiview_loaded = false;
|
||||
const int totfiles = seq_num_files(scene, load_data->views_format, load_data->use_multiview);
|
||||
Array<MovieReader *> anim_arr(totfiles, nullptr);
|
||||
|
||||
int orig_width = 0;
|
||||
int orig_height = 0;
|
||||
|
||||
if (load_data->use_multiview && (load_data->views_format == R_IMF_VIEWS_INDIVIDUAL)) {
|
||||
char prefix[FILE_MAX];
|
||||
const char *ext = nullptr;
|
||||
size_t j = 0;
|
||||
|
||||
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix, &ext);
|
||||
|
||||
if (prefix[0] != '\0') {
|
||||
for (int i = 0; i < totfiles; i++) {
|
||||
char filepath_view[FILE_MAX];
|
||||
|
||||
seq_multiview_name(scene, i, prefix, ext, filepath_view, sizeof(filepath_view));
|
||||
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
|
||||
* kept unchanged for the performance reasons. */
|
||||
anim_arr[j] = openanim(filepath_view, ImBufFlags::Zero, 0, true, colorspace);
|
||||
|
||||
if (anim_arr[j]) {
|
||||
seq_anim_add_suffix(scene, anim_arr[j], i);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
is_multiview_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_multiview_loaded == false) {
|
||||
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
|
||||
* kept unchanged for the performance reasons. */
|
||||
anim_arr[0] = openanim(filepath, ImBufFlags::Zero, load_data->stream_index, true, colorspace);
|
||||
}
|
||||
|
||||
if (anim_arr[0] == nullptr && !load_data->allow_invalid_file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
float video_fps = 0.0f;
|
||||
load_data->video_stream_start = 0.0;
|
||||
|
||||
if (anim_arr[0] != nullptr) {
|
||||
short fps_num;
|
||||
float fps_denom;
|
||||
bool have_fps = MOV_get_fps_num_denom(anim_arr[0], fps_num, fps_denom);
|
||||
if (have_fps) {
|
||||
video_fps = fps_num / fps_denom;
|
||||
}
|
||||
|
||||
/* Adjust scene's frame rate settings to match. */
|
||||
if (have_fps && (load_data->flags & SEQ_LOAD_MOVIE_SYNC_FPS)) {
|
||||
scene->r.frs_sec = fps_num;
|
||||
scene->r.frs_sec_base = fps_denom;
|
||||
DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_FPS | ID_RECALC_SEQUENCER_STRIPS);
|
||||
}
|
||||
|
||||
load_data->video_stream_start = MOV_get_start_offset_seconds(anim_arr[0]);
|
||||
}
|
||||
|
||||
Strip *strip = strip_alloc(
|
||||
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_MOVIE);
|
||||
strip->streamindex = load_data->stream_index;
|
||||
|
||||
/* Multiview settings. */
|
||||
if (load_data->use_multiview) {
|
||||
strip->flag |= SEQ_USE_VIEWS;
|
||||
strip->views_format = load_data->views_format;
|
||||
}
|
||||
if (load_data->stereo3d_format) {
|
||||
strip->stereo3d_format = MEM_new<Stereo3dFormat>("strip stereo3d format");
|
||||
*strip->stereo3d_format = *load_data->stereo3d_format;
|
||||
}
|
||||
|
||||
BLI_SCOPED_DEFER([&]() {
|
||||
for (MovieReader *mr : anim_arr) {
|
||||
if (!mr) {
|
||||
continue;
|
||||
}
|
||||
if (strip->intersects_frame(scene, scene->r.cfra)) {
|
||||
strip->runtime->movie_readers.append(mr);
|
||||
}
|
||||
else {
|
||||
MOV_close(mr);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (anim_arr[0] != nullptr) {
|
||||
strip->len = MOV_get_duration_frames(anim_arr[0]);
|
||||
|
||||
MOV_load_metadata(anim_arr[0]);
|
||||
|
||||
/* Set initial scale based on load_data->fit_method. */
|
||||
orig_width = MOV_get_image_width(anim_arr[0]);
|
||||
orig_height = MOV_get_image_height(anim_arr[0]);
|
||||
set_scale_to_fit(
|
||||
strip, orig_width, orig_height, scene->r.xsch, scene->r.ysch, load_data->fit_method);
|
||||
|
||||
float fps = MOV_get_fps(anim_arr[0]);
|
||||
if (fps > 0.0f) {
|
||||
strip->media_playback_rate = fps;
|
||||
}
|
||||
}
|
||||
|
||||
strip->len = std::max(1, strip->len);
|
||||
if (load_data->adjust_playback_rate) {
|
||||
strip->flag |= SEQ_AUTO_PLAYBACK_RATE;
|
||||
}
|
||||
|
||||
STRNCPY_UTF8(strip->data->colorspace_settings.name, colorspace);
|
||||
|
||||
StripData *data = strip->data;
|
||||
/* We only need 1 element for MOVIE strips. */
|
||||
StripElem *se;
|
||||
data->stripdata = se = MEM_new<StripElem>("stripelem");
|
||||
data->stripdata->orig_width = orig_width;
|
||||
data->stripdata->orig_height = orig_height;
|
||||
data->stripdata->orig_fps = video_fps;
|
||||
BLI_path_split_dir_file(
|
||||
load_data->path, data->dirpath, sizeof(data->dirpath), se->filename, sizeof(se->filename));
|
||||
|
||||
strip_add_set_view_transform(scene, strip, load_data);
|
||||
strip_add_set_name(scene, strip, load_data);
|
||||
strip_add_generic_update(scene, strip);
|
||||
|
||||
return strip;
|
||||
}
|
||||
|
||||
void add_reload_new_file(Main *bmain, Scene *scene, Strip *strip, const bool lock_range)
|
||||
{
|
||||
int prev_start_frame = 0, prev_end_frame = 0;
|
||||
/* NOTE: don't rename the strip, will break animation curves. */
|
||||
|
||||
if (ELEM(strip->type,
|
||||
STRIP_TYPE_MOVIE,
|
||||
STRIP_TYPE_IMAGE,
|
||||
STRIP_TYPE_SOUND,
|
||||
STRIP_TYPE_SCENE,
|
||||
STRIP_TYPE_META,
|
||||
STRIP_TYPE_MOVIECLIP,
|
||||
STRIP_TYPE_MASK) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (lock_range) {
|
||||
/* keep so we don't have to move the actual start and end points (only the data) */
|
||||
prev_start_frame = strip->left_handle();
|
||||
prev_end_frame = strip->right_handle(scene);
|
||||
}
|
||||
|
||||
switch (strip->type) {
|
||||
case STRIP_TYPE_IMAGE: {
|
||||
/* Hack? */
|
||||
size_t olen = MEM_allocN_len(strip->data->stripdata) / sizeof(StripElem);
|
||||
|
||||
strip->len = olen;
|
||||
strip->len -= strip->anim_startofs;
|
||||
strip->len -= strip->anim_endofs;
|
||||
strip->len = std::max(strip->len, 0);
|
||||
break;
|
||||
}
|
||||
case STRIP_TYPE_MOVIE: {
|
||||
char filepath[FILE_MAX];
|
||||
bool is_multiview_loaded = false;
|
||||
const bool is_multiview = (strip->flag & SEQ_USE_VIEWS) != 0 &&
|
||||
(scene->r.scemode & R_MULTIVIEW) != 0;
|
||||
|
||||
BLI_path_join(
|
||||
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH(bmain, &scene->id));
|
||||
|
||||
strip_free_movie_readers(strip);
|
||||
|
||||
if (is_multiview && (strip->views_format == R_IMF_VIEWS_INDIVIDUAL)) {
|
||||
char prefix[FILE_MAX];
|
||||
const char *ext = nullptr;
|
||||
const int totfiles = seq_num_files(scene, strip->views_format, true);
|
||||
int i = 0;
|
||||
|
||||
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix, &ext);
|
||||
|
||||
if (prefix[0] != '\0') {
|
||||
for (i = 0; i < totfiles; i++) {
|
||||
char filepath_view[FILE_MAX];
|
||||
|
||||
seq_multiview_name(scene, i, prefix, ext, filepath_view, sizeof(filepath_view));
|
||||
/* Sequencer takes care of colorspace conversion of the result. The input is the best
|
||||
* to be kept unchanged for the performance reasons. */
|
||||
MovieReader *anim = openanim(
|
||||
filepath_view,
|
||||
(strip->flag & SEQ_DEINTERLACE) ? ImBufFlags::Deinterlace : ImBufFlags::Zero,
|
||||
strip->streamindex,
|
||||
true,
|
||||
strip->data->colorspace_settings.name);
|
||||
|
||||
if (anim) {
|
||||
seq_anim_add_suffix(scene, anim, i);
|
||||
strip->runtime->movie_readers.append(anim);
|
||||
}
|
||||
}
|
||||
is_multiview_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_multiview_loaded == false) {
|
||||
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
|
||||
* kept unchanged for the performance reasons. */
|
||||
MovieReader *anim = openanim(filepath,
|
||||
(strip->flag & SEQ_DEINTERLACE) ? ImBufFlags::Deinterlace :
|
||||
ImBufFlags::Zero,
|
||||
strip->streamindex,
|
||||
true,
|
||||
strip->data->colorspace_settings.name);
|
||||
if (anim) {
|
||||
strip->runtime->movie_readers.append(anim);
|
||||
}
|
||||
}
|
||||
|
||||
/* use the first video as reference for everything */
|
||||
MovieReader *reader = strip->runtime->movie_reader_get();
|
||||
if (reader == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOV_load_metadata(reader);
|
||||
|
||||
strip->len = MOV_get_duration_frames(reader);
|
||||
|
||||
strip->len -= strip->anim_startofs;
|
||||
strip->len -= strip->anim_endofs;
|
||||
strip->len = std::max(strip->len, 0);
|
||||
break;
|
||||
}
|
||||
case STRIP_TYPE_MOVIECLIP:
|
||||
if (strip->clip == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
strip->len = BKE_movieclip_get_duration(strip->clip);
|
||||
|
||||
strip->len -= strip->anim_startofs;
|
||||
strip->len -= strip->anim_endofs;
|
||||
strip->len = std::max(strip->len, 0);
|
||||
break;
|
||||
case STRIP_TYPE_MASK:
|
||||
if (strip->mask == nullptr) {
|
||||
return;
|
||||
}
|
||||
strip->len = BKE_mask_get_duration(strip->mask);
|
||||
strip->len -= strip->anim_startofs;
|
||||
strip->len -= strip->anim_endofs;
|
||||
strip->len = std::max(strip->len, 0);
|
||||
break;
|
||||
case STRIP_TYPE_SOUND:
|
||||
#ifdef WITH_AUDASPACE
|
||||
if (!strip->sound) {
|
||||
return;
|
||||
}
|
||||
strip->len = ceil(double(BKE_sound_get_length(bmain, strip->sound)) *
|
||||
scene->frames_per_second());
|
||||
strip->len -= strip->anim_startofs;
|
||||
strip->len -= strip->anim_endofs;
|
||||
strip->len = std::max(strip->len, 0);
|
||||
#else
|
||||
UNUSED_VARS(bmain);
|
||||
return;
|
||||
#endif
|
||||
break;
|
||||
case STRIP_TYPE_SCENE: {
|
||||
strip->len = (strip->scene) ? strip->scene->r.efra - strip->scene->r.sfra + 1 : 0;
|
||||
strip->len -= strip->anim_startofs;
|
||||
strip->len -= strip->anim_endofs;
|
||||
strip->len = std::max(strip->len, 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
free_strip_proxy(strip);
|
||||
|
||||
if (lock_range) {
|
||||
strip->handles_set(scene, prev_start_frame, prev_end_frame);
|
||||
}
|
||||
|
||||
relations_invalidate_cache_raw(scene, strip);
|
||||
}
|
||||
|
||||
void add_movie_reload_if_needed(
|
||||
Main *bmain, Scene *scene, Strip *strip, bool *r_was_reloaded, bool *r_can_produce_frames)
|
||||
{
|
||||
BLI_assert_msg(strip->type == STRIP_TYPE_MOVIE,
|
||||
"This function is only implemented for movie strips.");
|
||||
|
||||
bool must_reload = false;
|
||||
if (strip->runtime->movie_readers.is_empty()) {
|
||||
/* No movie readers open: reload is necessary. */
|
||||
must_reload = true;
|
||||
}
|
||||
else {
|
||||
for (const MovieReader *reader : strip->runtime->movie_readers) {
|
||||
if (!MOV_is_initialized_and_valid(reader)) {
|
||||
/* A movie reader cannot produce frames, try reloading. */
|
||||
must_reload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!must_reload) {
|
||||
/* All good! */
|
||||
*r_was_reloaded = false;
|
||||
*r_can_produce_frames = true;
|
||||
return;
|
||||
}
|
||||
|
||||
add_reload_new_file(bmain, scene, strip, true);
|
||||
*r_was_reloaded = true;
|
||||
|
||||
if (strip->runtime->movie_readers.is_empty()) {
|
||||
/* No readers after reload -> can't produce frames. */
|
||||
*r_can_produce_frames = false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const MovieReader *reader : strip->runtime->movie_readers) {
|
||||
if (!MOV_is_initialized_and_valid(reader)) {
|
||||
/* There is still a movie that cannot produce frames. */
|
||||
*r_can_produce_frames = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* All good after a reload. */
|
||||
*r_can_produce_frames = true;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
157
blender-5.2.0/source/blender/sequencer/intern/strip_connect.cc
Normal file
157
blender-5.2.0/source/blender/sequencer/intern/strip_connect.cc
Normal file
@@ -0,0 +1,157 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_connect.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void strip_connections_free(Strip *strip)
|
||||
{
|
||||
if (strip == nullptr) {
|
||||
return;
|
||||
}
|
||||
ListBaseT<StripConnection> *connections = &strip->connections;
|
||||
for (StripConnection &con : connections->items_mutable()) {
|
||||
MEM_delete(&con);
|
||||
}
|
||||
connections->clear_no_delete();
|
||||
}
|
||||
|
||||
void connections_duplicate(ListBaseT<StripConnection> *connections_dst,
|
||||
ListBaseT<StripConnection> *connections_src)
|
||||
{
|
||||
for (StripConnection &con : *connections_src) {
|
||||
StripConnection *con_duplicate = MEM_new<StripConnection>(__func__, con);
|
||||
BLI_addtail(connections_dst, con_duplicate);
|
||||
}
|
||||
}
|
||||
|
||||
bool disconnect(Strip *strip)
|
||||
{
|
||||
if (strip == nullptr || strip->connections.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
/* Remove `StripConnections` from other strips' `connections` list that point to `strip`. */
|
||||
for (StripConnection &con_strip : strip->connections) {
|
||||
Strip *other = con_strip.strip_ref;
|
||||
for (StripConnection &con_other : other->connections.items_mutable()) {
|
||||
if (con_other.strip_ref == strip) {
|
||||
BLI_remlink(&other->connections, &con_other);
|
||||
MEM_delete(&con_other);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Now clear `connections` for `strip` itself. */
|
||||
strip_connections_free(strip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool disconnect(VectorSet<Strip *> &strip_list)
|
||||
{
|
||||
bool changed = false;
|
||||
for (Strip *strip : strip_list) {
|
||||
changed |= disconnect(strip);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void cut_one_way_connections(Strip *strip)
|
||||
{
|
||||
if (strip == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (StripConnection &con_strip : strip->connections.items_mutable()) {
|
||||
Strip *other = con_strip.strip_ref;
|
||||
bool is_one_way = true;
|
||||
for (StripConnection &con_other : other->connections) {
|
||||
if (con_other.strip_ref == strip) {
|
||||
/* The `other` sequence has a bidirectional connection with `strip`. */
|
||||
is_one_way = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_one_way) {
|
||||
BLI_remlink(&strip->connections, &con_strip);
|
||||
MEM_delete(&con_strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void connect(Strip *strip1, Strip *strip2)
|
||||
{
|
||||
if (strip1 == nullptr || strip2 == nullptr) {
|
||||
return;
|
||||
}
|
||||
VectorSet<Strip *> strip_list;
|
||||
strip_list.add(strip1);
|
||||
strip_list.add(strip2);
|
||||
|
||||
connect(strip_list);
|
||||
}
|
||||
|
||||
void connect(VectorSet<Strip *> &strip_list)
|
||||
{
|
||||
strip_list.remove_if([&](Strip *strip) { return strip == nullptr; });
|
||||
|
||||
for (Strip *strip1 : strip_list) {
|
||||
disconnect(strip1);
|
||||
for (Strip *strip2 : strip_list) {
|
||||
if (strip1 == strip2) {
|
||||
continue;
|
||||
}
|
||||
StripConnection *con = MEM_new<StripConnection>("stripconnection");
|
||||
con->strip_ref = strip2;
|
||||
BLI_addtail(&strip1->connections, con);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VectorSet<Strip *> connected_strips_get(const Strip *strip)
|
||||
{
|
||||
VectorSet<Strip *> connections;
|
||||
if (strip != nullptr) {
|
||||
for (StripConnection &con : strip->connections) {
|
||||
connections.add(con.strip_ref);
|
||||
}
|
||||
}
|
||||
return connections;
|
||||
}
|
||||
|
||||
bool is_strip_connected(const Strip *strip)
|
||||
{
|
||||
if (strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return !strip->connections.is_empty();
|
||||
}
|
||||
|
||||
bool are_strips_connected_together(VectorSet<Strip *> &strip_list)
|
||||
{
|
||||
const int expected_connection_num = strip_list.size() - 1;
|
||||
for (Strip *strip1 : strip_list) {
|
||||
VectorSet<Strip *> connections = connected_strips_get(strip1);
|
||||
int found_connection_num = connections.size();
|
||||
if (found_connection_num != expected_connection_num) {
|
||||
return false;
|
||||
}
|
||||
for (Strip *strip2 : connections) {
|
||||
if (!strip_list.contains(strip2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
528
blender-5.2.0/source/blender/sequencer/intern/strip_edit.cc
Normal file
528
blender-5.2.0/source/blender/sequencer/intern/strip_edit.cc
Normal file
@@ -0,0 +1,528 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "BKE_sound.hh"
|
||||
|
||||
#include "strip_time.hh"
|
||||
|
||||
#include "SEQ_add.hh"
|
||||
#include "SEQ_animation.hh"
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_connect.hh"
|
||||
|
||||
#include "SEQ_edit.hh"
|
||||
#include "SEQ_effects.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
#include "SEQ_transform.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
bool edit_strip_swap(Scene *scene, Strip *strip_a, Strip *strip_b, const char **r_error_str)
|
||||
{
|
||||
char name[sizeof(strip_a->name)];
|
||||
|
||||
if (strip_a->length(scene) != strip_b->length(scene)) {
|
||||
*r_error_str = N_("Strips must be the same length");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* type checking, could be more advanced but disallow sound vs non-sound copy */
|
||||
if (strip_a->type != strip_b->type) {
|
||||
if (strip_a->type == STRIP_TYPE_SOUND || strip_b->type == STRIP_TYPE_SOUND) {
|
||||
*r_error_str = N_("Strips were not compatible");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* disallow effects to swap with non-effects strips */
|
||||
if (strip_a->is_effect() != strip_b->is_effect()) {
|
||||
*r_error_str = N_("Strips were not compatible");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strip_a->is_effect() && strip_b->is_effect()) {
|
||||
if (strip_a->effect_num_inputs_get() != strip_b->effect_num_inputs_get()) {
|
||||
*r_error_str = N_("Strips must have the same number of inputs");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dna::shallow_swap(*strip_a, *strip_b);
|
||||
|
||||
/* swap back names so animation fcurves don't get swapped */
|
||||
STRNCPY(name, strip_a->name + 2);
|
||||
BLI_strncpy(strip_a->name + 2, strip_b->name + 2, sizeof(strip_b->name) - 2);
|
||||
BLI_strncpy(strip_b->name + 2, name, sizeof(strip_b->name) - 2);
|
||||
|
||||
/* swap back opacity, and overlay mode */
|
||||
std::swap(strip_a->blend_mode, strip_b->blend_mode);
|
||||
std::swap(strip_a->blend_opacity, strip_b->blend_opacity);
|
||||
|
||||
std::swap(strip_a->prev, strip_b->prev);
|
||||
std::swap(strip_a->next, strip_b->next);
|
||||
std::swap(strip_a->start, strip_b->start);
|
||||
std::swap(strip_a->startofs, strip_b->startofs);
|
||||
std::swap(strip_a->endofs, strip_b->endofs);
|
||||
std::swap(strip_a->channel, strip_b->channel);
|
||||
strip_time_effect_range_set(scene, strip_a);
|
||||
strip_time_effect_range_set(scene, strip_b);
|
||||
|
||||
strip_lookup_invalidate(editing_get(scene));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void strip_update_muting_recursive(ListBaseT<SeqTimelineChannel> *channels,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Strip *strip_meta,
|
||||
const bool mute)
|
||||
{
|
||||
/* For sound we go over full meta tree to update muted state,
|
||||
* since sound is played outside of evaluating the imbufs. */
|
||||
for (Strip &strip : *seqbasep) {
|
||||
bool strip_mute = (mute || render_is_muted(channels, &strip));
|
||||
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
/* if this is the current meta-strip, unmute because
|
||||
* all strips above this were set to mute */
|
||||
if (&strip == strip_meta) {
|
||||
strip_mute = false;
|
||||
}
|
||||
|
||||
strip_update_muting_recursive(&strip.channels, &strip.seqbase, strip_meta, strip_mute);
|
||||
}
|
||||
else if (ELEM(strip.type, STRIP_TYPE_SOUND, STRIP_TYPE_SCENE)) {
|
||||
if (strip.runtime->scene_sound) {
|
||||
BKE_sound_mute_scene_sound(strip.runtime->scene_sound, strip_mute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void edit_update_muting(Editing *ed)
|
||||
{
|
||||
if (ed) {
|
||||
/* mute all sounds up to current metastack list */
|
||||
MetaStack *ms = static_cast<MetaStack *>(ed->metastack.last);
|
||||
|
||||
if (ms) {
|
||||
strip_update_muting_recursive(&ed->channels, &ed->seqbase, ms->parent_strip, true);
|
||||
}
|
||||
else {
|
||||
strip_update_muting_recursive(&ed->channels, &ed->seqbase, nullptr, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sequencer_flag_users_for_removal(Scene *scene, ListBaseT<Strip> *seqbase, Strip *strip)
|
||||
{
|
||||
for (Strip &user_strip : *seqbase) {
|
||||
/* Look in meta-strips for usage of strip. */
|
||||
if (user_strip.type == STRIP_TYPE_META) {
|
||||
sequencer_flag_users_for_removal(scene, &user_strip.seqbase, strip);
|
||||
}
|
||||
|
||||
/* Clear strip from modifiers. */
|
||||
for (StripModifierData &smd : user_strip.modifiers) {
|
||||
if (smd.mask_strip == strip) {
|
||||
smd.mask_strip = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark effects for removal that use the strip. */
|
||||
if (relation_is_effect_of_strip(&user_strip, strip)) {
|
||||
user_strip.runtime->flag |= StripRuntimeFlag::MarkForDelete;
|
||||
/* Strips can be used as mask even if not in same seqbase. */
|
||||
sequencer_flag_users_for_removal(scene, &scene->ed->seqbase, &user_strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void edit_flag_for_removal(Scene *scene, ListBaseT<Strip> *seqbase, Strip *strip)
|
||||
{
|
||||
if (strip == nullptr || flag_is_set(strip->runtime->flag, StripRuntimeFlag::MarkForDelete)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Flag and remove meta children. */
|
||||
if (strip->type == STRIP_TYPE_META) {
|
||||
for (Strip &meta_child : strip->seqbase) {
|
||||
edit_flag_for_removal(scene, &strip->seqbase, &meta_child);
|
||||
}
|
||||
}
|
||||
|
||||
strip->runtime->flag |= StripRuntimeFlag::MarkForDelete;
|
||||
sequencer_flag_users_for_removal(scene, seqbase, strip);
|
||||
}
|
||||
|
||||
void edit_remove_flagged_strips(Scene *scene, ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
for (Strip &strip : seqbase->items_mutable()) {
|
||||
if (flag_is_set(strip.runtime->flag, StripRuntimeFlag::MarkForDelete)) {
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
edit_remove_flagged_strips(scene, &strip.seqbase);
|
||||
}
|
||||
free_animdata(scene, &strip);
|
||||
BLI_remlink(seqbase, &strip);
|
||||
strip_free(scene, &strip);
|
||||
strip_lookup_invalidate(scene->ed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool edit_move_strip_to_seqbase(Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
Strip *strip,
|
||||
ListBaseT<Strip> *dst_seqbase)
|
||||
{
|
||||
/* Move to meta. */
|
||||
BLI_remlink(seqbase, strip);
|
||||
BLI_addtail(dst_seqbase, strip);
|
||||
relations_invalidate_cache(scene, strip);
|
||||
|
||||
/* Update meta. */
|
||||
if (transform_test_overlap(scene, dst_seqbase, strip)) {
|
||||
transform_seqbase_shuffle(dst_seqbase, strip, scene);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool edit_move_strip_to_meta(Scene *scene,
|
||||
Strip *src_strip,
|
||||
Strip *dst_stripm,
|
||||
const char **r_error_str)
|
||||
{
|
||||
/* Find the appropriate seqbase */
|
||||
Editing *ed = editing_get(scene);
|
||||
ListBaseT<Strip> *seqbase = get_seqbase_by_strip(scene, src_strip);
|
||||
|
||||
if (dst_stripm->type != STRIP_TYPE_META) {
|
||||
*r_error_str = N_("Cannot move strip to non-meta strip");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src_strip == dst_stripm) {
|
||||
*r_error_str = N_("Strip cannot be moved into itself");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seqbase == &dst_stripm->seqbase) {
|
||||
*r_error_str = N_("Moved strip is already inside provided meta strip");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src_strip->type == STRIP_TYPE_META && exists_in_seqbase(dst_stripm, &src_strip->seqbase)) {
|
||||
*r_error_str = N_("Moved strip is parent of provided meta strip");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!exists_in_seqbase(dst_stripm, &ed->seqbase)) {
|
||||
*r_error_str = N_("Cannot move strip to different scene");
|
||||
return false;
|
||||
}
|
||||
|
||||
VectorSet<Strip *> strips;
|
||||
strips.add(src_strip);
|
||||
iterator_set_expand(seqbase, strips, query_strip_effect_chain);
|
||||
|
||||
for (Strip *strip : strips) {
|
||||
/* Move to meta. */
|
||||
edit_move_strip_to_seqbase(scene, seqbase, strip, &dst_stripm->seqbase);
|
||||
}
|
||||
|
||||
time_update_meta_strip_range(scene, dst_stripm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void seq_split_set_right_hold_offset(Main *bmain,
|
||||
Scene *scene,
|
||||
Strip *strip,
|
||||
int timeline_frame)
|
||||
{
|
||||
const float content_start = strip->content_start();
|
||||
const float content_end = strip->content_end(scene);
|
||||
|
||||
/* Adjust within range of extended still-frames before strip. */
|
||||
if (timeline_frame < content_start) {
|
||||
const float offset = content_start + 1 - timeline_frame;
|
||||
strip->start -= offset;
|
||||
strip->startofs += offset;
|
||||
}
|
||||
/* Adjust within range of strip contents. */
|
||||
else if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) {
|
||||
strip->endofs = 0;
|
||||
const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
|
||||
const float speed_factor = strip->media_playback_rate_factor(scene_fps);
|
||||
strip->anim_endofs += round_fl_to_int((content_end - timeline_frame) * speed_factor);
|
||||
}
|
||||
|
||||
/* Needed only to set `strip->len`. */
|
||||
add_reload_new_file(bmain, scene, strip, false);
|
||||
strip->right_handle_set(scene, timeline_frame);
|
||||
}
|
||||
|
||||
static void seq_split_set_left_hold_offset(Main *bmain,
|
||||
Scene *scene,
|
||||
Strip *strip,
|
||||
int timeline_frame)
|
||||
{
|
||||
const float content_start = strip->content_start();
|
||||
const float content_end = strip->content_end(scene);
|
||||
|
||||
/* Adjust within range of strip contents. */
|
||||
if ((timeline_frame >= content_start) && (timeline_frame <= content_end)) {
|
||||
const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
|
||||
const float speed_factor = strip->media_playback_rate_factor(scene_fps);
|
||||
strip->anim_startofs += round_fl_to_int((timeline_frame - content_start) * speed_factor);
|
||||
strip->start = timeline_frame;
|
||||
strip->startofs = 0;
|
||||
}
|
||||
/* Adjust within range of extended still-frames after strip. */
|
||||
else if (timeline_frame > content_end) {
|
||||
const float offset = timeline_frame - content_end + 1;
|
||||
strip->start += offset;
|
||||
strip->endofs += offset;
|
||||
}
|
||||
|
||||
/* Needed only to set `strip->len`. */
|
||||
add_reload_new_file(bmain, scene, strip, false);
|
||||
strip->left_handle_set(scene, timeline_frame);
|
||||
}
|
||||
|
||||
static bool seq_edit_split_intersect_check(const Scene *scene,
|
||||
const Strip *strip,
|
||||
const int timeline_frame)
|
||||
{
|
||||
return timeline_frame > strip->left_handle() && timeline_frame < strip->right_handle(scene);
|
||||
}
|
||||
|
||||
static void seq_edit_split_handle_strip_offsets(Main *bmain,
|
||||
Scene *scene,
|
||||
Strip *left_strip,
|
||||
Strip *right_strip,
|
||||
const int timeline_frame,
|
||||
const eSplitMethod method)
|
||||
{
|
||||
if (seq_edit_split_intersect_check(scene, right_strip, timeline_frame)) {
|
||||
switch (method) {
|
||||
case SPLIT_SOFT:
|
||||
right_strip->left_handle_set(scene, timeline_frame);
|
||||
break;
|
||||
case SPLIT_HARD:
|
||||
seq_split_set_left_hold_offset(bmain, scene, right_strip, timeline_frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (seq_edit_split_intersect_check(scene, left_strip, timeline_frame)) {
|
||||
switch (method) {
|
||||
case SPLIT_SOFT:
|
||||
left_strip->right_handle_set(scene, timeline_frame);
|
||||
break;
|
||||
case SPLIT_HARD:
|
||||
seq_split_set_right_hold_offset(bmain, scene, left_strip, timeline_frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool seq_edit_split_effect_inputs_intersect(const Scene *scene,
|
||||
const Strip *strip,
|
||||
const int timeline_frame)
|
||||
{
|
||||
bool input_does_intersect = false;
|
||||
if (strip->input1) {
|
||||
input_does_intersect |= seq_edit_split_intersect_check(scene, strip->input1, timeline_frame);
|
||||
if (strip->input1->is_effect()) {
|
||||
input_does_intersect |= seq_edit_split_effect_inputs_intersect(
|
||||
scene, strip->input1, timeline_frame);
|
||||
}
|
||||
}
|
||||
if (strip->input2) {
|
||||
input_does_intersect |= seq_edit_split_intersect_check(scene, strip->input2, timeline_frame);
|
||||
if (strip->input2->is_effect()) {
|
||||
input_does_intersect |= seq_edit_split_effect_inputs_intersect(
|
||||
scene, strip->input2, timeline_frame);
|
||||
}
|
||||
}
|
||||
return input_does_intersect;
|
||||
}
|
||||
|
||||
static bool seq_edit_split_operation_permitted_check(const Scene *scene,
|
||||
Span<Strip *> strips,
|
||||
const int timeline_frame,
|
||||
const char **r_error)
|
||||
{
|
||||
for (Strip *strip : strips) {
|
||||
const ListBaseT<SeqTimelineChannel> *channels = channels_displayed_get(editing_get(scene));
|
||||
if (transform_is_locked(channels, strip)) {
|
||||
*r_error = "Strip is locked.";
|
||||
return false;
|
||||
}
|
||||
if (!strip->is_effect()) {
|
||||
continue;
|
||||
}
|
||||
if (!seq_edit_split_intersect_check(scene, strip, timeline_frame)) {
|
||||
continue;
|
||||
}
|
||||
if (strip->effect_num_inputs_get() <= 1) {
|
||||
continue;
|
||||
}
|
||||
if (effect_is_transition(strip->type)) {
|
||||
*r_error = "Splitting transition effect is not permitted.";
|
||||
return false;
|
||||
}
|
||||
if (!seq_edit_split_effect_inputs_intersect(scene, strip, timeline_frame)) {
|
||||
*r_error = "Effect inputs don't overlap. Can not split such effect.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Strip *edit_strip_split(Main *bmain,
|
||||
Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
Strip *strip,
|
||||
const int timeline_frame,
|
||||
const eSplitMethod method,
|
||||
const bool ignore_connections,
|
||||
const char **r_error)
|
||||
{
|
||||
if (!seq_edit_split_intersect_check(scene, strip, timeline_frame)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Whole strip effect chain must be duplicated in order to preserve relationships. */
|
||||
VectorSet<Strip *> strips;
|
||||
strips.add(strip);
|
||||
iterator_set_expand(seqbase,
|
||||
strips,
|
||||
ignore_connections ? query_strip_effect_chain :
|
||||
query_strip_connected_and_effect_chain);
|
||||
|
||||
if (!seq_edit_split_operation_permitted_check(scene, strips, timeline_frame, r_error)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Store `F-Curves`, so original ones aren't renamed. */
|
||||
AnimationBackup animation_backup{};
|
||||
animation_backup_original(scene, &animation_backup);
|
||||
|
||||
ListBaseT<Strip> left_strips = {nullptr, nullptr};
|
||||
for (Strip *strip_iter : strips) {
|
||||
/* Move strips in collection from seqbase to new ListBase. */
|
||||
BLI_remlink(seqbase, strip_iter);
|
||||
BLI_addtail(&left_strips, strip_iter);
|
||||
|
||||
if (ignore_connections) {
|
||||
disconnect(strip_iter);
|
||||
}
|
||||
|
||||
/* Duplicate curves from backup, so they can be renamed along with split strips. */
|
||||
animation_duplicate_backup_to_scene(scene, strip_iter, &animation_backup);
|
||||
}
|
||||
|
||||
/* Duplicate ListBase. */
|
||||
ListBaseT<Strip> right_strips = {nullptr, nullptr};
|
||||
seqbase_duplicate_recursive(
|
||||
bmain, scene, scene, &right_strips, &left_strips, StripDuplicate::All, 0);
|
||||
|
||||
Strip *left_strip = static_cast<Strip *>(left_strips.first);
|
||||
Strip *right_strip = static_cast<Strip *>(right_strips.first);
|
||||
Strip *return_strip = nullptr;
|
||||
|
||||
/* Move strips from detached `ListBase`, otherwise they can't be flagged for removal. */
|
||||
BLI_movelisttolist(seqbase, &left_strips);
|
||||
BLI_movelisttolist(seqbase, &right_strips);
|
||||
|
||||
/* Rename duplicated strips. This has to be done immediately after adding
|
||||
* strips to seqbase, for lookup cache to work correctly. */
|
||||
Strip *strip_rename = right_strip;
|
||||
for (; strip_rename; strip_rename = strip_rename->next) {
|
||||
ensure_unique_name(strip_rename, scene);
|
||||
}
|
||||
|
||||
/* Split strips. */
|
||||
while (left_strip && right_strip) {
|
||||
if (left_strip->left_handle() >= timeline_frame) {
|
||||
edit_flag_for_removal(scene, seqbase, left_strip);
|
||||
}
|
||||
else if (right_strip->right_handle(scene) <= timeline_frame) {
|
||||
edit_flag_for_removal(scene, seqbase, right_strip);
|
||||
}
|
||||
else if (return_strip == nullptr) {
|
||||
/* Store return value - pointer to strip that will not be removed. */
|
||||
return_strip = right_strip;
|
||||
}
|
||||
|
||||
seq_edit_split_handle_strip_offsets(
|
||||
bmain, scene, left_strip, right_strip, timeline_frame, method);
|
||||
left_strip = left_strip->next;
|
||||
right_strip = right_strip->next;
|
||||
}
|
||||
|
||||
edit_remove_flagged_strips(scene, seqbase);
|
||||
animation_restore_original(scene, &animation_backup);
|
||||
|
||||
return return_strip;
|
||||
}
|
||||
|
||||
bool edit_remove_gaps(Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
const int initial_frame,
|
||||
const bool remove_all_gaps)
|
||||
{
|
||||
GapInfo gap_info = {0};
|
||||
seq_time_gap_info_get(scene, seqbase, initial_frame, &gap_info);
|
||||
|
||||
if (!gap_info.gap_exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (remove_all_gaps) {
|
||||
while (gap_info.gap_exists) {
|
||||
transform_offset_after_frame(scene, seqbase, -gap_info.gap_length, gap_info.gap_start_frame);
|
||||
seq_time_gap_info_get(scene, seqbase, initial_frame, &gap_info);
|
||||
}
|
||||
}
|
||||
else {
|
||||
transform_offset_after_frame(scene, seqbase, -gap_info.gap_length, gap_info.gap_start_frame);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void edit_strip_name_set(Scene *scene, Strip *strip, const char *new_name)
|
||||
{
|
||||
BLI_strncpy_utf8(strip->name + 2, new_name, MAX_NAME - 2);
|
||||
BLI_str_utf8_invalid_strip(strip->name + 2, strlen(strip->name + 2));
|
||||
strip_lookup_invalidate(scene->ed);
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
244
blender-5.2.0/source/blender/sequencer/intern/strip_lookup.cc
Normal file
244
blender-5.2.0/source/blender/sequencer/intern/strip_lookup.cc
Normal file
@@ -0,0 +1,244 @@
|
||||
/* SPDX-FileCopyrightText: 2021-2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "sequencer.hh"
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_mutex.hh"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static Mutex lookup_lock;
|
||||
|
||||
struct StripLookup {
|
||||
Map<std::string, Strip *> strip_by_name;
|
||||
Map<const Scene *, VectorSet<Strip *>> strips_by_scene;
|
||||
Map<const bNodeTree *, VectorSet<Strip *>> strips_by_compositor_node_group;
|
||||
Map<const Strip *, Strip *> meta_by_strip;
|
||||
Map<const Strip *, VectorSet<Strip *>> effects_by_strip;
|
||||
Map<const SeqTimelineChannel *, Strip *> owner_by_channel;
|
||||
bool is_valid = false;
|
||||
};
|
||||
|
||||
static void strip_lookup_append_effect(const Strip *input, Strip *effect, StripLookup *lookup)
|
||||
{
|
||||
if (input == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
VectorSet<Strip *> &effects = lookup->effects_by_strip.lookup_or_add_default(input);
|
||||
|
||||
effects.add(effect);
|
||||
}
|
||||
|
||||
static void strip_by_scene_lookup_build(Strip *strip, StripLookup *lookup)
|
||||
{
|
||||
if (strip->scene == nullptr) {
|
||||
return;
|
||||
}
|
||||
VectorSet<Strip *> &strips = lookup->strips_by_scene.lookup_or_add_default(strip->scene);
|
||||
strips.add(strip);
|
||||
}
|
||||
|
||||
static void strip_by_compositor_node_group_lookup_build(Strip *strip, StripLookup *lookup)
|
||||
{
|
||||
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
|
||||
const CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
|
||||
if (comp_data->node_group) {
|
||||
VectorSet<Strip *> &strips = lookup->strips_by_compositor_node_group.lookup_or_add_default(
|
||||
comp_data->node_group);
|
||||
strips.add(strip);
|
||||
}
|
||||
}
|
||||
|
||||
for (StripModifierData &modifier : strip->modifiers) {
|
||||
if (modifier.type != eSeqModifierType_Compositor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const SequencerCompositorModifierData *modifier_data =
|
||||
reinterpret_cast<SequencerCompositorModifierData *>(&modifier);
|
||||
if (!modifier_data->node_group) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VectorSet<Strip *> &strips = lookup->strips_by_compositor_node_group.lookup_or_add_default(
|
||||
modifier_data->node_group);
|
||||
strips.add(strip);
|
||||
}
|
||||
}
|
||||
|
||||
static void strip_lookup_build_effect(Strip *strip, StripLookup *lookup)
|
||||
{
|
||||
if (!strip->is_effect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
strip_lookup_append_effect(strip->input1, strip, lookup);
|
||||
strip_lookup_append_effect(strip->input2, strip, lookup);
|
||||
}
|
||||
|
||||
static void strip_lookup_build_from_seqbase(Strip *parent_meta,
|
||||
const ListBaseT<Strip> *seqbase,
|
||||
StripLookup *lookup)
|
||||
{
|
||||
if (parent_meta != nullptr) {
|
||||
for (SeqTimelineChannel &channel : parent_meta->channels) {
|
||||
lookup->owner_by_channel.add(&channel, parent_meta);
|
||||
}
|
||||
}
|
||||
|
||||
for (Strip &strip : *seqbase) {
|
||||
lookup->strip_by_name.add(strip.name + 2, &strip);
|
||||
lookup->meta_by_strip.add(&strip, parent_meta);
|
||||
strip_lookup_build_effect(&strip, lookup);
|
||||
strip_by_scene_lookup_build(&strip, lookup);
|
||||
strip_by_compositor_node_group_lookup_build(&strip, lookup);
|
||||
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
strip_lookup_build_from_seqbase(&strip, &strip.seqbase, lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void strip_lookup_build(const Editing *ed, StripLookup *lookup)
|
||||
{
|
||||
strip_lookup_build_from_seqbase(nullptr, &ed->seqbase, lookup);
|
||||
lookup->is_valid = true;
|
||||
}
|
||||
|
||||
static StripLookup *strip_lookup_new()
|
||||
{
|
||||
StripLookup *lookup = MEM_new<StripLookup>(__func__);
|
||||
return lookup;
|
||||
}
|
||||
|
||||
static void strip_lookup_free(StripLookup **lookup)
|
||||
{
|
||||
MEM_delete(*lookup);
|
||||
*lookup = nullptr;
|
||||
}
|
||||
|
||||
static void strip_lookup_rebuild(const Editing *ed, StripLookup **lookup)
|
||||
{
|
||||
strip_lookup_free(lookup);
|
||||
*lookup = strip_lookup_new();
|
||||
strip_lookup_build(ed, *lookup);
|
||||
}
|
||||
|
||||
static void strip_lookup_update_if_needed(const Editing *ed, StripLookup **lookup)
|
||||
{
|
||||
if (!ed) {
|
||||
return;
|
||||
}
|
||||
if (*lookup && (*lookup)->is_valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
strip_lookup_rebuild(ed, lookup);
|
||||
}
|
||||
|
||||
void strip_lookup_free(Editing *ed)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_free(&ed->runtime->strip_lookup);
|
||||
}
|
||||
|
||||
Strip *lookup_strip_by_name(Editing *ed, const char *key)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_update_if_needed(ed, &ed->runtime->strip_lookup);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
return lookup->strip_by_name.lookup_default(key, nullptr);
|
||||
}
|
||||
|
||||
Span<Strip *> lookup_strips_by_scene(Editing *ed, const Scene *key)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_update_if_needed(ed, &ed->runtime->strip_lookup);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
VectorSet<Strip *> &strips = lookup->strips_by_scene.lookup_or_add_default(key);
|
||||
return strips.as_span();
|
||||
}
|
||||
|
||||
Map<const Scene *, VectorSet<Strip *>> &lookup_strips_by_scene_map_get(Editing *ed)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_update_if_needed(ed, &ed->runtime->strip_lookup);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
return lookup->strips_by_scene;
|
||||
}
|
||||
|
||||
Span<Strip *> lookup_strips_by_compositor_node_group(Editing *ed, const bNodeTree *key)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
BLI_assert(key->type == NTREE_COMPOSIT);
|
||||
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_update_if_needed(ed, &ed->runtime->strip_lookup);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
VectorSet<Strip *> &strips = lookup->strips_by_compositor_node_group.lookup_or_add_default(key);
|
||||
return strips.as_span();
|
||||
}
|
||||
|
||||
Strip *lookup_meta_by_strip(Editing *ed, const Strip *key)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_update_if_needed(ed, &ed->runtime->strip_lookup);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
return lookup->meta_by_strip.lookup_default(key, nullptr);
|
||||
}
|
||||
|
||||
Span<Strip *> SEQ_lookup_effects_by_strip(Editing *ed, const Strip *key)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_update_if_needed(ed, &ed->runtime->strip_lookup);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
VectorSet<Strip *> &effects = lookup->effects_by_strip.lookup_or_add_default(key);
|
||||
return effects.as_span();
|
||||
}
|
||||
|
||||
Strip *lookup_strip_by_channel_owner(Editing *ed, const SeqTimelineChannel *channel)
|
||||
{
|
||||
BLI_assert(ed != nullptr);
|
||||
std::lock_guard lock(lookup_lock);
|
||||
strip_lookup_update_if_needed(ed, &ed->runtime->strip_lookup);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
return lookup->owner_by_channel.lookup_default(channel, nullptr);
|
||||
}
|
||||
|
||||
void strip_lookup_invalidate(const Editing *ed)
|
||||
{
|
||||
if (ed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(lookup_lock);
|
||||
StripLookup *lookup = ed->runtime->strip_lookup;
|
||||
if (lookup != nullptr) {
|
||||
lookup->is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
468
blender-5.2.0/source/blender/sequencer/intern/strip_relations.cc
Normal file
468
blender-5.2.0/source/blender/sequencer/intern/strip_relations.cc
Normal file
@@ -0,0 +1,468 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_session_uid.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_prefetch.hh"
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_thumbnail_cache.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "cache/final_image_cache.hh"
|
||||
#include "cache/intra_frame_cache.hh"
|
||||
#include "cache/source_image_cache.hh"
|
||||
#include "effects/effects.hh"
|
||||
#include "sequencer.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
bool relation_is_effect_of_strip(const Strip *effect, const Strip *input)
|
||||
{
|
||||
return ELEM(input, effect->input1, effect->input2);
|
||||
}
|
||||
|
||||
void cache_cleanup(Scene *scene, CacheCleanup mode)
|
||||
{
|
||||
if (flag_is_set(mode, CacheCleanup::Thumbnails)) {
|
||||
thumbnail_cache_clear(scene);
|
||||
}
|
||||
if (flag_is_set(mode, CacheCleanup::SourceImage)) {
|
||||
source_image_cache_clear(scene);
|
||||
}
|
||||
if (flag_is_set(mode, CacheCleanup::FinalImage)) {
|
||||
final_image_cache_clear(scene);
|
||||
}
|
||||
if (flag_is_set(mode, CacheCleanup::IntraFrame)) {
|
||||
intra_frame_cache_invalidate(scene);
|
||||
preview_cache_invalidate(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void cache_settings_changed(Scene *scene)
|
||||
{
|
||||
if (!(scene->ed->cache_flag & SEQ_CACHE_STORE_RAW)) {
|
||||
/* RAW caches has been disabled, clear them out. */
|
||||
source_image_cache_clear(scene);
|
||||
}
|
||||
if (!(scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT)) {
|
||||
/* Final caches has been disabled, clear them out. */
|
||||
final_image_cache_clear(scene);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_cache_full(const Scene *scene)
|
||||
{
|
||||
size_t cache_limit = size_t(U.memcachelimit) * 1024 * 1024;
|
||||
return source_image_cache_calc_memory_size(scene) + final_image_cache_calc_memory_size(scene) >
|
||||
cache_limit;
|
||||
}
|
||||
|
||||
bool evict_caches_if_full(Scene *scene)
|
||||
{
|
||||
if (!is_cache_full(scene)) {
|
||||
/* Cache is not full, we don't have to evict anything. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Cache is full, so we want to remove some images. We always try to remove one final image,
|
||||
* and some amount of source images for each final image, so that ratio of cached images
|
||||
* stays the same. Depending on the frame composition complexity, there can be lots of
|
||||
* source images cached for a single final frame; if we only removed one source image
|
||||
* we'd eventually have the cache still filled only with source images. */
|
||||
bool evicted_final = false;
|
||||
bool evicted_source = false;
|
||||
do {
|
||||
const size_t count_final = final_image_cache_get_image_count(scene);
|
||||
const size_t count_source = source_image_cache_get_image_count(scene);
|
||||
evicted_final = false;
|
||||
evicted_source = false;
|
||||
const bool final_active = scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT;
|
||||
/* Evict one final item, and as much from source as needed to maintain ratio. */
|
||||
if (count_final != 0) {
|
||||
evicted_final = final_image_cache_evict(scene);
|
||||
}
|
||||
/* Only remove source images if there's more of them than final ones. */
|
||||
if (count_source != 0 && (!final_active || count_source > count_final)) {
|
||||
evicted_source = source_image_cache_evict(scene);
|
||||
/* Only try to enforce the ratio when the final cache is active. */
|
||||
if (evicted_source && final_active) {
|
||||
const size_t items = divide_ceil_ul(count_source, std::max<size_t>(count_final, 1));
|
||||
/* Start at "1" to make sure we only try to evict more frames if the ratio is above 1:1. */
|
||||
for (size_t i = 1; i < items; i++) {
|
||||
if (!source_image_cache_evict(scene)) {
|
||||
/* Can't evict any more frames, stop. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} while (is_cache_full(scene) && (evicted_final || evicted_source));
|
||||
|
||||
/* Did we evict anything to free up the cache? */
|
||||
return !(evicted_final || evicted_source);
|
||||
}
|
||||
|
||||
static void update_range_with_effects(const Scene *scene, const Strip *strip, int2 &r_range)
|
||||
{
|
||||
r_range.x = std::min(r_range.x, strip->left_handle());
|
||||
r_range.y = std::max(r_range.y, strip->right_handle(scene) - 1);
|
||||
Span<Strip *> effects = SEQ_lookup_effects_by_strip(scene->ed, strip);
|
||||
for (Strip *effect : effects) {
|
||||
update_range_with_effects(scene, effect, r_range);
|
||||
}
|
||||
}
|
||||
|
||||
static void invalidate_final_cache_strip_range(Scene *scene, const Strip *strip)
|
||||
{
|
||||
int2 range{MAXFRAME, -MAXFRAME};
|
||||
update_range_with_effects(scene, strip, range);
|
||||
final_image_cache_invalidate_frame_range(scene, range.x, range.y);
|
||||
}
|
||||
|
||||
static void invalidate_raw_cache_of_parent_meta(Scene *scene, Strip *strip)
|
||||
{
|
||||
Strip *meta = lookup_meta_by_strip(editing_get(scene), strip);
|
||||
if (meta == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
relations_invalidate_cache_raw(scene, meta);
|
||||
}
|
||||
|
||||
void relations_invalidate_cache_raw(Scene *scene, Strip *strip)
|
||||
{
|
||||
source_image_cache_invalidate_strip(scene, strip);
|
||||
media_presence_invalidate_strip(scene, strip);
|
||||
relations_invalidate_cache(scene, strip);
|
||||
}
|
||||
|
||||
void relations_invalidate_cache(Scene *scene, Strip *strip)
|
||||
{
|
||||
if (strip->effectdata && strip->type == STRIP_TYPE_SPEED) {
|
||||
strip_effect_speed_rebuild_map(scene, strip);
|
||||
}
|
||||
|
||||
/* Zero-input compositor effect source caches also need to be invalidated. */
|
||||
if (strip->type == STRIP_TYPE_COMPOSITOR && !strip->is_effect_with_inputs()) {
|
||||
source_image_cache_invalidate_strip(scene, strip);
|
||||
}
|
||||
|
||||
invalidate_final_cache_strip_range(scene, strip);
|
||||
intra_frame_cache_invalidate(scene, strip);
|
||||
preview_cache_invalidate(scene);
|
||||
invalidate_raw_cache_of_parent_meta(scene, strip);
|
||||
|
||||
/* Needed to update VSE sound. */
|
||||
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
||||
prefetch_stop(scene);
|
||||
}
|
||||
|
||||
void relations_invalidate_scene_strips(const Main *bmain, const Scene *scene_target)
|
||||
{
|
||||
for (Scene &scene : bmain->scenes) {
|
||||
if (scene.ed != nullptr) {
|
||||
for (Strip *strip : lookup_strips_by_scene(editing_get(&scene), scene_target)) {
|
||||
relations_invalidate_cache_raw(&scene, strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void relations_update_view_layer_scene_strips(Main *bmain,
|
||||
Scene *scene,
|
||||
const char *old_name,
|
||||
const char *new_name)
|
||||
{
|
||||
for (Scene &scene_iter : bmain->scenes) {
|
||||
Editing *ed = seq::editing_get(&scene_iter);
|
||||
if (ed == nullptr) {
|
||||
continue;
|
||||
}
|
||||
for (Strip *strip : seq::lookup_strips_by_scene(ed, scene)) {
|
||||
BLI_assert(strip->scene_view_layer_name != nullptr);
|
||||
if (!STREQ(strip->scene_view_layer_name, old_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MEM_delete(strip->scene_view_layer_name);
|
||||
strip->scene_view_layer_name = new_name ?
|
||||
BLI_strdup(new_name) :
|
||||
BLI_strdup(
|
||||
BKE_view_layer_default_render(strip->scene)->name);
|
||||
if (new_name == nullptr) {
|
||||
/* View layer was deleted. */
|
||||
seq::relations_invalidate_cache_raw(&scene_iter, strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void relations_invalidate_compositor_users(const Main *bmain, const bNodeTree *node_tree)
|
||||
{
|
||||
for (Scene &scene : bmain->scenes) {
|
||||
if (scene.ed != nullptr) {
|
||||
for (Strip *strip : lookup_strips_by_compositor_node_group(editing_get(&scene), node_tree)) {
|
||||
relations_invalidate_cache(&scene, strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void invalidate_movieclip_strips(Scene *scene,
|
||||
MovieClip *clip_target,
|
||||
ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
for (Strip *strip = static_cast<Strip *>(seqbase->first); strip != nullptr; strip = strip->next)
|
||||
{
|
||||
if (strip->clip == clip_target) {
|
||||
relations_invalidate_cache_raw(scene, strip);
|
||||
}
|
||||
|
||||
if (strip->seqbase.first != nullptr) {
|
||||
invalidate_movieclip_strips(scene, clip_target, &strip->seqbase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void relations_invalidate_movieclip_strips(Main *bmain, MovieClip *clip_target)
|
||||
{
|
||||
for (Scene *scene = static_cast<Scene *>(bmain->scenes.first); scene != nullptr;
|
||||
scene = static_cast<Scene *>(scene->id.next))
|
||||
{
|
||||
if (scene->ed != nullptr) {
|
||||
invalidate_movieclip_strips(scene, clip_target, &scene->ed->seqbase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void relations_free_imbuf(Scene *scene, ListBaseT<Strip> *seqbase, bool for_render)
|
||||
{
|
||||
if (scene->ed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
prefetch_stop(scene);
|
||||
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (for_render && strip.intersects_frame(scene, scene->r.cfra)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strip.data) {
|
||||
if (strip.type == STRIP_TYPE_MOVIE) {
|
||||
strip_free_movie_readers(&strip);
|
||||
}
|
||||
if (strip.type == STRIP_TYPE_SPEED) {
|
||||
strip_effect_speed_rebuild_map(scene, &strip);
|
||||
}
|
||||
}
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
relations_free_imbuf(scene, &strip.seqbase, for_render);
|
||||
}
|
||||
if (strip.type == STRIP_TYPE_SCENE) {
|
||||
/* FIXME: recurse downwards,
|
||||
* but do recurse protection somehow! */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sequencer_all_free_anim_ibufs(const Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
int timeline_frame,
|
||||
const int frame_range[2])
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
for (Strip *strip = static_cast<Strip *>(seqbase->first); strip != nullptr; strip = strip->next)
|
||||
{
|
||||
if (!strip->intersects_frame(scene, timeline_frame) ||
|
||||
!((frame_range[0] <= timeline_frame) && (frame_range[1] > timeline_frame)))
|
||||
{
|
||||
strip_free_movie_readers(strip);
|
||||
}
|
||||
if (strip->type == STRIP_TYPE_META) {
|
||||
int meta_range[2];
|
||||
|
||||
MetaStack *ms = meta_stack_active_get(ed);
|
||||
if (ms != nullptr && ms->parent_strip == strip) {
|
||||
meta_range[0] = -MAXFRAME;
|
||||
meta_range[1] = MAXFRAME;
|
||||
}
|
||||
else {
|
||||
/* Limit frame range to meta strip. */
|
||||
meta_range[0] = max_ii(frame_range[0], strip->left_handle());
|
||||
meta_range[1] = min_ii(frame_range[1], strip->right_handle(scene));
|
||||
}
|
||||
|
||||
sequencer_all_free_anim_ibufs(scene, &strip->seqbase, timeline_frame, meta_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void relations_free_all_anim_ibufs(Scene *scene, int timeline_frame)
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
if (ed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int frame_range[2] = {-MAXFRAME, MAXFRAME};
|
||||
sequencer_all_free_anim_ibufs(scene, &ed->seqbase, timeline_frame, frame_range);
|
||||
}
|
||||
|
||||
static Strip *sequencer_check_scene_recursion(Scene *scene, ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (strip.type == STRIP_TYPE_SCENE && strip.scene == scene) {
|
||||
return &strip;
|
||||
}
|
||||
|
||||
if (strip.type == STRIP_TYPE_SCENE && (strip.flag & SEQ_SCENE_STRIPS)) {
|
||||
if (strip.scene && strip.scene->ed &&
|
||||
sequencer_check_scene_recursion(scene, &strip.scene->ed->seqbase))
|
||||
{
|
||||
return &strip;
|
||||
}
|
||||
}
|
||||
|
||||
if (strip.type == STRIP_TYPE_META && sequencer_check_scene_recursion(scene, &strip.seqbase)) {
|
||||
return &strip;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool relations_check_scene_recursion(Scene *scene, ReportList *reports)
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
if (ed == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Strip *recursive_seq = sequencer_check_scene_recursion(scene, &ed->seqbase);
|
||||
|
||||
if (recursive_seq != nullptr) {
|
||||
BKE_reportf(reports,
|
||||
RPT_WARNING,
|
||||
"Recursion detected in video sequencer. Strip %s at frame %d will not be rendered",
|
||||
recursive_seq->name + 2,
|
||||
recursive_seq->left_handle());
|
||||
|
||||
for (Strip &strip : ed->seqbase) {
|
||||
if (strip.type != STRIP_TYPE_SCENE && sequencer_strip_generates_image(&strip)) {
|
||||
/* There are other strips to render, so render them. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/* No other strips to render - cancel operator. */
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool relations_render_loop_check(Strip *strip_main, Strip *strip)
|
||||
{
|
||||
if (strip_main == nullptr || strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strip_main == strip) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((strip_main->input1 && relations_render_loop_check(strip_main->input1, strip)) ||
|
||||
(strip_main->input2 && relations_render_loop_check(strip_main->input2, strip)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (StripModifierData &smd : strip_main->modifiers) {
|
||||
if (smd.mask_strip && relations_render_loop_check(smd.mask_strip, strip)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void strip_free_movie_readers(Strip *strip)
|
||||
{
|
||||
for (MovieReader *anim : strip->runtime->movie_readers) {
|
||||
MOV_close(anim);
|
||||
}
|
||||
strip->runtime->movie_readers.clear();
|
||||
}
|
||||
|
||||
void relations_session_uid_generate(Strip *strip)
|
||||
{
|
||||
strip->runtime->session_uid = BLI_session_uid_generate();
|
||||
}
|
||||
|
||||
static bool get_uids_cb(Strip *strip, void *user_data)
|
||||
{
|
||||
Set<SessionUID> &used_uids = *static_cast<Set<SessionUID> *>(user_data);
|
||||
const SessionUID &session_uid = strip->runtime->session_uid;
|
||||
if (!BLI_session_uid_is_generated(&session_uid)) {
|
||||
printf("Sequence %s does not have UID generated.\n", strip->name);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (used_uids.contains(session_uid)) {
|
||||
printf("Sequence %s has duplicate UID generated.\n", strip->name);
|
||||
return true;
|
||||
}
|
||||
used_uids.add(session_uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
void relations_check_uids_unique_and_report(const Scene *scene)
|
||||
{
|
||||
if (scene->ed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<SessionUID> used_uids;
|
||||
foreach_strip(&scene->ed->seqbase, get_uids_cb, &used_uids);
|
||||
}
|
||||
|
||||
bool exists_in_seqbase(const Strip *strip, const ListBaseT<Strip> *seqbase)
|
||||
{
|
||||
for (Strip &strip_test : *seqbase) {
|
||||
if (strip_test.type == STRIP_TYPE_META && exists_in_seqbase(strip, &strip_test.seqbase)) {
|
||||
return true;
|
||||
}
|
||||
if (&strip_test == strip) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
1368
blender-5.2.0/source/blender/sequencer/intern/strip_retiming.cc
Normal file
1368
blender-5.2.0/source/blender/sequencer/intern/strip_retiming.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
Strip *select_active_get(const Scene *scene)
|
||||
{
|
||||
const Editing *ed = editing_get(scene);
|
||||
|
||||
if (ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ed->act_strip;
|
||||
}
|
||||
|
||||
void select_active_set(Scene *scene, Strip *strip)
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
|
||||
if (ed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ed->act_strip = strip;
|
||||
}
|
||||
|
||||
bool select_active_get_pair(Scene *scene, Strip **r_strip_act, Strip **r_strip_other)
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
|
||||
*r_strip_act = select_active_get(scene);
|
||||
|
||||
if (*r_strip_act == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*r_strip_other = nullptr;
|
||||
|
||||
for (Strip &strip : *ed->current_strips()) {
|
||||
if (strip.flag & SEQ_SELECT && (&strip != (*r_strip_act))) {
|
||||
if (*r_strip_other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*r_strip_other = &strip;
|
||||
}
|
||||
}
|
||||
|
||||
return (*r_strip_other != nullptr);
|
||||
}
|
||||
|
||||
bool select_has_any(const Scene *scene)
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
if (ed != nullptr) {
|
||||
for (Strip &strip : *ed->current_strips()) {
|
||||
if (strip.flag & SEQ_SELECT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
614
blender-5.2.0/source/blender/sequencer/intern/strip_time.cc
Normal file
614
blender-5.2.0/source/blender/sequencer/intern/strip_time.cc
Normal file
@@ -0,0 +1,614 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BKE_movieclip.hh"
|
||||
#include "BKE_sound.hh"
|
||||
|
||||
#include "DNA_sound_types.h"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "SEQ_animation.hh"
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_retiming.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
#include "SEQ_transform.hh"
|
||||
|
||||
#include "sequencer.hh"
|
||||
#include "strip_time.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
namespace blender {
|
||||
namespace seq {
|
||||
|
||||
float give_frame_index(const Scene *scene, const Strip *strip, float timeline_frame)
|
||||
{
|
||||
float frame_index;
|
||||
float sta = strip->content_start();
|
||||
float end = strip->is_effect() ? strip->right_handle(scene) : strip->content_end(scene) - 1;
|
||||
|
||||
if (end < sta) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strip->type == STRIP_TYPE_IMAGE && transform_single_image_check(strip)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strip->flag & SEQ_REVERSE_FRAMES) {
|
||||
frame_index = end - timeline_frame;
|
||||
}
|
||||
else {
|
||||
frame_index = timeline_frame - sta;
|
||||
}
|
||||
|
||||
frame_index = max_ff(frame_index, 0);
|
||||
|
||||
const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
|
||||
frame_index *= strip->media_playback_rate_factor(scene_fps);
|
||||
|
||||
if (retiming_has_keys(strip)) {
|
||||
const float retiming_factor = strip_retiming_evaluate(strip, frame_index);
|
||||
/* Retiming maps frame index from 0 up to `strip->len`, because key is positioned at the end of
|
||||
* last frame. Otherwise the last frame could not be retimed. */
|
||||
frame_index = retiming_factor * strip->len;
|
||||
}
|
||||
/* Clamp frame index to strip content frame range. */
|
||||
float frame_index_max = strip->is_effect() ? end - sta : strip->len - 1;
|
||||
frame_index = clamp_f(frame_index, 0, frame_index_max);
|
||||
|
||||
if (strip->strobe > 1.0f) {
|
||||
frame_index -= fmodf(double(frame_index), double(strip->strobe));
|
||||
}
|
||||
|
||||
return frame_index;
|
||||
}
|
||||
|
||||
static int metastrip_start_get(Strip *strip_meta)
|
||||
{
|
||||
return strip_meta->start + strip_meta->startofs;
|
||||
}
|
||||
|
||||
static int metastrip_end_get(Strip *strip_meta)
|
||||
{
|
||||
return strip_meta->start + strip_meta->len - strip_meta->endofs;
|
||||
}
|
||||
|
||||
static void strip_update_sound_bounds_recursive_impl(const Scene *scene,
|
||||
Strip *strip_meta,
|
||||
int start,
|
||||
int end)
|
||||
{
|
||||
/* For sound we go over full meta tree to update bounds of the sound strips,
|
||||
* since sound is played outside of evaluating the image-buffers (#ImBuf). */
|
||||
for (Strip &strip : strip_meta->seqbase) {
|
||||
if (strip.type == STRIP_TYPE_META) {
|
||||
strip_update_sound_bounds_recursive_impl(scene,
|
||||
&strip,
|
||||
max_ii(start, metastrip_start_get(&strip)),
|
||||
min_ii(end, metastrip_end_get(&strip)));
|
||||
}
|
||||
else if (ELEM(strip.type, STRIP_TYPE_SOUND, STRIP_TYPE_SCENE)) {
|
||||
if (strip.runtime->scene_sound) {
|
||||
int startofs = strip.startofs;
|
||||
int endofs = strip.endofs;
|
||||
if (strip.startofs + strip.start < start) {
|
||||
startofs = start - strip.start;
|
||||
}
|
||||
|
||||
if (strip.start + strip.len - strip.endofs > end) {
|
||||
endofs = strip.start + strip.len - end;
|
||||
}
|
||||
|
||||
double offset_time = 0.0f;
|
||||
if (strip.sound != nullptr) {
|
||||
offset_time = strip.sound->offset_time + strip.sound_offset;
|
||||
}
|
||||
|
||||
BKE_sound_move_scene_sound(scene,
|
||||
strip.runtime->scene_sound,
|
||||
strip.start + startofs,
|
||||
strip.start + strip.len - endofs,
|
||||
startofs + strip.anim_startofs,
|
||||
offset_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void strip_update_sound_bounds_recursive(const Scene *scene, Strip *strip_meta)
|
||||
{
|
||||
strip_update_sound_bounds_recursive_impl(
|
||||
scene, strip_meta, metastrip_start_get(strip_meta), metastrip_end_get(strip_meta));
|
||||
}
|
||||
|
||||
void time_update_meta_strip_range(const Scene *scene, Strip *strip_meta)
|
||||
{
|
||||
if (strip_meta == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strip_meta->seqbase.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int strip_start = strip_meta->left_handle();
|
||||
const int strip_end = strip_meta->right_handle(scene);
|
||||
|
||||
int min = MAXFRAME * 2;
|
||||
int max = -MAXFRAME * 2;
|
||||
for (Strip &strip : strip_meta->seqbase) {
|
||||
min = min_ii(strip.left_handle(), min);
|
||||
max = max_ii(strip.right_handle(scene), max);
|
||||
}
|
||||
|
||||
strip_meta->start = min + strip_meta->anim_startofs;
|
||||
strip_meta->len = max - strip_meta->anim_endofs - strip_meta->start;
|
||||
|
||||
/* Functions `SEQ_time_*_handle_frame_set()` can not be used here, because they are clamped, so
|
||||
* change must be done at once. */
|
||||
strip_meta->startofs = strip_start - strip_meta->start;
|
||||
strip_meta->startdisp = strip_start; /* Only to make files usable in older versions. */
|
||||
strip_meta->endofs = strip_meta->start + strip_meta->length(scene) - strip_end;
|
||||
strip_meta->enddisp = strip_end; /* Only to make files usable in older versions. */
|
||||
|
||||
strip_update_sound_bounds_recursive(scene, strip_meta);
|
||||
Span<Strip *> effects = SEQ_lookup_effects_by_strip(scene->ed, strip_meta);
|
||||
strip_time_update_effects_strip_range(scene, effects);
|
||||
time_update_meta_strip_range(scene, lookup_meta_by_strip(scene->ed, strip_meta));
|
||||
}
|
||||
|
||||
void strip_time_effect_range_set(const Scene *scene, Strip *strip)
|
||||
{
|
||||
if (strip->input1 == nullptr && strip->input2 == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strip->input1 && strip->input2) { /* 2 - input effect. */
|
||||
strip->startdisp = max_ii(strip->input1->left_handle(), strip->input2->left_handle());
|
||||
strip->enddisp = min_ii(strip->input1->right_handle(scene),
|
||||
strip->input2->right_handle(scene));
|
||||
}
|
||||
else if (strip->input1) { /* Single input effect. */
|
||||
strip->startdisp = strip->input1->right_handle(scene);
|
||||
strip->enddisp = strip->input1->left_handle();
|
||||
}
|
||||
else if (strip->input2) { /* Strip may be missing one of inputs. */
|
||||
strip->startdisp = strip->input2->right_handle(scene);
|
||||
strip->enddisp = strip->input2->left_handle();
|
||||
}
|
||||
|
||||
if (strip->startdisp > strip->enddisp) {
|
||||
std::swap(strip->startdisp, strip->enddisp);
|
||||
}
|
||||
|
||||
/* Values unusable for effects, these should be always 0. */
|
||||
strip->startofs = strip->endofs = strip->anim_startofs = strip->anim_endofs = 0;
|
||||
strip->start = strip->startdisp;
|
||||
strip->len = strip->enddisp - strip->startdisp;
|
||||
}
|
||||
|
||||
void strip_time_update_effects_strip_range(const Scene *scene, const Span<Strip *> effects)
|
||||
{
|
||||
/* First pass: Update length of immediate effects. */
|
||||
for (Strip *strip : effects) {
|
||||
strip_time_effect_range_set(scene, strip);
|
||||
}
|
||||
|
||||
/* Second pass: Recursive call to update effects in chain and in order, so they inherit length
|
||||
* correctly. */
|
||||
for (Strip *strip : effects) {
|
||||
Span<Strip *> effects_recurse = SEQ_lookup_effects_by_strip(scene->ed, strip);
|
||||
strip_time_update_effects_strip_range(scene, effects_recurse);
|
||||
}
|
||||
}
|
||||
|
||||
int time_find_next_prev_edit(Scene *scene,
|
||||
int timeline_frame,
|
||||
const short side,
|
||||
const bool do_skip_mute,
|
||||
const bool do_center,
|
||||
const bool do_unselected)
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
ListBaseT<SeqTimelineChannel> *channels = channels_displayed_get(ed);
|
||||
|
||||
int dist, best_dist, best_frame = timeline_frame;
|
||||
int strip_frames[2], strip_frames_tot;
|
||||
|
||||
/* In case where both is passed,
|
||||
* frame just finds the nearest end while frame_left the nearest start. */
|
||||
|
||||
best_dist = MAXFRAME * 2;
|
||||
|
||||
if (ed == nullptr) {
|
||||
return timeline_frame;
|
||||
}
|
||||
|
||||
for (Strip &strip : *ed->current_strips()) {
|
||||
int i;
|
||||
|
||||
if (do_skip_mute && render_is_muted(channels, &strip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (do_unselected && (strip.flag & SEQ_SELECT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (do_center) {
|
||||
strip_frames[0] = (strip.left_handle() + strip.right_handle(scene)) / 2;
|
||||
strip_frames_tot = 1;
|
||||
}
|
||||
else {
|
||||
strip_frames[0] = strip.left_handle();
|
||||
strip_frames[1] = strip.right_handle(scene);
|
||||
|
||||
strip_frames_tot = 2;
|
||||
}
|
||||
|
||||
for (i = 0; i < strip_frames_tot; i++) {
|
||||
const int strip_frame = strip_frames[i];
|
||||
|
||||
dist = MAXFRAME * 2;
|
||||
|
||||
switch (side) {
|
||||
case SIDE_LEFT:
|
||||
if (strip_frame < timeline_frame) {
|
||||
dist = timeline_frame - strip_frame;
|
||||
}
|
||||
break;
|
||||
case SIDE_RIGHT:
|
||||
if (strip_frame > timeline_frame) {
|
||||
dist = strip_frame - timeline_frame;
|
||||
}
|
||||
break;
|
||||
case SIDE_BOTH:
|
||||
dist = abs(strip_frame - timeline_frame);
|
||||
break;
|
||||
}
|
||||
|
||||
if (dist < best_dist) {
|
||||
best_frame = strip_frame;
|
||||
best_dist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best_frame;
|
||||
}
|
||||
|
||||
void timeline_init_boundbox(const Scene *scene, rctf *r_rect)
|
||||
{
|
||||
r_rect->xmin = scene->r.sfra;
|
||||
r_rect->xmax = scene->r.efra + 1;
|
||||
r_rect->ymin = 1.0f; /* The first strip is drawn at y == 1.0f */
|
||||
r_rect->ymax = 8.0f;
|
||||
}
|
||||
|
||||
void timeline_expand_boundbox(const Scene *scene, const ListBaseT<Strip> *seqbase, rctf *rect)
|
||||
{
|
||||
if (seqbase == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Strip &strip : *seqbase) {
|
||||
rect->xmin = std::min<float>(rect->xmin, strip.left_handle() - 1);
|
||||
rect->xmax = std::max<float>(rect->xmax, strip.right_handle(scene) + 1);
|
||||
/* We do +1 here to account for the channel thickness. Channel n has range of <n, n+1>. */
|
||||
rect->ymax = std::max(rect->ymax, strip.channel + 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void timeline_boundbox(const Scene *scene, const ListBaseT<Strip> *seqbase, rctf *r_rect)
|
||||
{
|
||||
timeline_init_boundbox(scene, r_rect);
|
||||
timeline_expand_boundbox(scene, seqbase, r_rect);
|
||||
}
|
||||
|
||||
static bool strip_exists_at_frame(const Scene *scene,
|
||||
Span<Strip *> strips,
|
||||
const int timeline_frame)
|
||||
{
|
||||
for (Strip *strip : strips) {
|
||||
if (strip->intersects_frame(scene, timeline_frame)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void seq_time_gap_info_get(const Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
const int initial_frame,
|
||||
GapInfo *r_gap_info)
|
||||
{
|
||||
rctf rectf;
|
||||
/* Get first and last frame. */
|
||||
timeline_boundbox(scene, seqbase, &rectf);
|
||||
const int sfra = int(rectf.xmin);
|
||||
const int efra = int(rectf.xmax);
|
||||
int timeline_frame = initial_frame;
|
||||
r_gap_info->gap_exists = false;
|
||||
|
||||
VectorSet strips = query_all_strips(seqbase);
|
||||
|
||||
if (!strip_exists_at_frame(scene, strips, initial_frame)) {
|
||||
/* Search backward for gap_start_frame. */
|
||||
for (; timeline_frame >= sfra; timeline_frame--) {
|
||||
if (strip_exists_at_frame(scene, strips, timeline_frame)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
r_gap_info->gap_start_frame = timeline_frame + 1;
|
||||
timeline_frame = initial_frame;
|
||||
}
|
||||
else {
|
||||
/* Search forward for gap_start_frame. */
|
||||
for (; timeline_frame <= efra; timeline_frame++) {
|
||||
if (!strip_exists_at_frame(scene, strips, timeline_frame)) {
|
||||
r_gap_info->gap_start_frame = timeline_frame;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Search forward for gap_end_frame. */
|
||||
for (; timeline_frame <= efra; timeline_frame++) {
|
||||
if (strip_exists_at_frame(scene, strips, timeline_frame)) {
|
||||
const int gap_end_frame = timeline_frame;
|
||||
r_gap_info->gap_length = gap_end_frame - r_gap_info->gap_start_frame;
|
||||
r_gap_info->gap_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void strip_time_slip_strip_ex(const Scene *scene,
|
||||
Strip *strip,
|
||||
int delta,
|
||||
float subframe_delta,
|
||||
bool slip_keyframes,
|
||||
bool recursed)
|
||||
{
|
||||
if (strip->type == STRIP_TYPE_SOUND && subframe_delta != 0.0f) {
|
||||
strip->sound_offset += subframe_delta / scene->frames_per_second();
|
||||
}
|
||||
|
||||
if (delta == 0 && (!slip_keyframes || subframe_delta == 0.0f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Skip effect strips where the length is dependent on another strip,
|
||||
* as they are calculated with #strip_time_update_effects_strip_range. */
|
||||
if (strip->input1 != nullptr || strip->input2 != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Effects only have a start frame and a length, so unless we're inside
|
||||
* a meta strip, there's no need to do anything. */
|
||||
if (!recursed && strip->is_effect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Move strips inside meta strip. */
|
||||
if (strip->type == STRIP_TYPE_META) {
|
||||
/* If the meta strip has no contents, don't do anything. */
|
||||
if (strip->seqbase.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Strip &strip_child : strip->seqbase) {
|
||||
/* The keyframes of strips inside meta strips should always be moved. */
|
||||
strip_time_slip_strip_ex(scene, &strip_child, delta, subframe_delta, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
strip->start = strip->start + delta;
|
||||
|
||||
if (slip_keyframes) {
|
||||
float anim_offset = delta;
|
||||
if (strip->type == STRIP_TYPE_SOUND) {
|
||||
anim_offset += subframe_delta;
|
||||
}
|
||||
offset_animdata(scene, strip, anim_offset);
|
||||
}
|
||||
|
||||
if (!recursed) {
|
||||
strip->startofs = strip->startofs - delta;
|
||||
strip->endofs = strip->endofs + delta;
|
||||
}
|
||||
|
||||
/* Only to make files usable in older versions. */
|
||||
strip->startdisp = strip->left_handle();
|
||||
strip->enddisp = strip->right_handle(scene);
|
||||
|
||||
Span<Strip *> effects = SEQ_lookup_effects_by_strip(scene->ed, strip);
|
||||
strip_time_update_effects_strip_range(scene, effects);
|
||||
}
|
||||
|
||||
void time_slip_strip(
|
||||
const Scene *scene, Strip *strip, int frame_delta, float subframe_delta, bool slip_keyframes)
|
||||
{
|
||||
strip_time_slip_strip_ex(scene, strip, frame_delta, subframe_delta, slip_keyframes, false);
|
||||
}
|
||||
|
||||
} // namespace seq
|
||||
|
||||
float Strip::media_playback_rate_factor(float scene_fps) const
|
||||
{
|
||||
if ((this->flag & SEQ_AUTO_PLAYBACK_RATE) == 0) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (this->media_playback_rate == 0.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
return this->media_playback_rate / scene_fps;
|
||||
}
|
||||
|
||||
float Strip::media_fps(Scene *scene)
|
||||
{
|
||||
switch (this->type) {
|
||||
case STRIP_TYPE_MOVIE: {
|
||||
seq::strip_open_anim_file(scene, this, true);
|
||||
const MovieReader *anim = this->runtime->movie_reader_get();
|
||||
if (anim == nullptr) {
|
||||
return 0.0f;
|
||||
}
|
||||
return MOV_get_fps(anim);
|
||||
}
|
||||
case STRIP_TYPE_MOVIECLIP:
|
||||
if (this->clip != nullptr) {
|
||||
return BKE_movieclip_get_fps(this->clip);
|
||||
}
|
||||
break;
|
||||
case STRIP_TYPE_SCENE:
|
||||
if (this->scene != nullptr) {
|
||||
return float(this->scene->r.frs_sec) / this->scene->r.frs_sec_base;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float Strip::content_start() const
|
||||
{
|
||||
return this->start;
|
||||
}
|
||||
|
||||
void Strip::content_start_set(const Scene *scene, int timeline_frame)
|
||||
{
|
||||
this->start = timeline_frame;
|
||||
Span<Strip *> effects = seq::SEQ_lookup_effects_by_strip(scene->ed, this);
|
||||
seq::strip_time_update_effects_strip_range(scene, effects);
|
||||
seq::time_update_meta_strip_range(scene, seq::lookup_meta_by_strip(scene->ed, this));
|
||||
}
|
||||
|
||||
float Strip::content_end(const Scene *scene) const
|
||||
{
|
||||
return this->content_start() + this->length(scene);
|
||||
}
|
||||
|
||||
int Strip::length(const Scene *scene) const
|
||||
{
|
||||
const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
|
||||
if (seq::retiming_has_keys(this)) {
|
||||
const int last_key_frame = seq::retiming_key_frame_get(
|
||||
scene, this, seq::retiming_last_key_get(this));
|
||||
/* Last key is mapped to last frame index. Numbering starts from 0. */
|
||||
const int sound_offset = this->rounded_sound_offset(scene_fps);
|
||||
return last_key_frame - this->content_start() - sound_offset;
|
||||
}
|
||||
|
||||
return this->len / this->media_playback_rate_factor(scene_fps);
|
||||
}
|
||||
|
||||
int Strip::rounded_sound_offset(float scene_fps) const
|
||||
{
|
||||
if (this->type == STRIP_TYPE_SOUND && this->sound != nullptr) {
|
||||
return round_fl_to_int((this->sound->offset_time + this->sound_offset) * scene_fps);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Strip::left_handle() const
|
||||
{
|
||||
if (this->input1 || this->input2) {
|
||||
return this->startdisp;
|
||||
}
|
||||
|
||||
return this->start + this->startofs;
|
||||
}
|
||||
|
||||
int Strip::right_handle(const Scene *scene) const
|
||||
{
|
||||
if (this->input1 || this->input2) {
|
||||
return this->enddisp;
|
||||
}
|
||||
|
||||
return this->content_end(scene) - this->endofs;
|
||||
}
|
||||
|
||||
void Strip::left_handle_set(const Scene *scene, int timeline_frame)
|
||||
{
|
||||
const float right_handle_orig_frame = this->right_handle(scene);
|
||||
|
||||
if (timeline_frame >= right_handle_orig_frame) {
|
||||
timeline_frame = right_handle_orig_frame - 1;
|
||||
}
|
||||
|
||||
float offset = timeline_frame - this->content_start();
|
||||
|
||||
if (seq::transform_single_image_check(this)) {
|
||||
/* This strip has only 1 frame of content that is always stretched to the whole strip length.
|
||||
* Move strip start left and adjust end offset to be negative (rightwards past the 1 frame). */
|
||||
this->content_start_set(scene, timeline_frame);
|
||||
this->endofs += offset;
|
||||
}
|
||||
else {
|
||||
this->startofs = offset;
|
||||
}
|
||||
|
||||
this->startdisp = timeline_frame; /* Only to make files usable in older versions. */
|
||||
|
||||
Span<Strip *> effects = seq::SEQ_lookup_effects_by_strip(scene->ed, this);
|
||||
seq::strip_time_update_effects_strip_range(scene, effects);
|
||||
seq::time_update_meta_strip_range(scene, seq::lookup_meta_by_strip(scene->ed, this));
|
||||
}
|
||||
|
||||
void Strip::right_handle_set(const Scene *scene, int timeline_frame)
|
||||
{
|
||||
const float left_handle_orig_frame = this->left_handle();
|
||||
|
||||
if (timeline_frame <= left_handle_orig_frame) {
|
||||
timeline_frame = left_handle_orig_frame + 1;
|
||||
}
|
||||
|
||||
this->endofs = this->content_end(scene) - timeline_frame;
|
||||
this->enddisp = timeline_frame; /* Only to make files usable in older versions. */
|
||||
|
||||
Span<Strip *> effects = seq::SEQ_lookup_effects_by_strip(scene->ed, this);
|
||||
seq::strip_time_update_effects_strip_range(scene, effects);
|
||||
seq::time_update_meta_strip_range(scene, seq::lookup_meta_by_strip(scene->ed, this));
|
||||
}
|
||||
|
||||
void Strip::handles_set(const Scene *scene, int left_frame, int right_frame)
|
||||
{
|
||||
BLI_assert(left_frame < right_frame);
|
||||
|
||||
if (left_frame >= this->right_handle(scene)) {
|
||||
/* Move right handle first to avoid clamping. */
|
||||
this->right_handle_set(scene, right_frame);
|
||||
this->left_handle_set(scene, left_frame);
|
||||
}
|
||||
else {
|
||||
this->left_handle_set(scene, left_frame);
|
||||
this->right_handle_set(scene, right_frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool Strip::intersects_frame(const Scene *scene, const int timeline_frame) const
|
||||
{
|
||||
return (this->left_handle() <= timeline_frame) && (this->right_handle(scene) > timeline_frame);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
51
blender-5.2.0/source/blender/sequencer/intern/strip_time.hh
Normal file
51
blender-5.2.0/source/blender/sequencer/intern/strip_time.hh
Normal file
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
#include "BLI_span.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void strip_update_sound_bounds_recursive(const Scene *scene, Strip *strip_meta);
|
||||
|
||||
/* Describes gap between strips in timeline. */
|
||||
struct GapInfo {
|
||||
int gap_start_frame; /* Start frame of the gap. */
|
||||
int gap_length; /* Length of the gap. */
|
||||
bool gap_exists; /* False if there are no gaps. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Find first gap between strips after initial_frame and describe it by filling data of r_gap_info
|
||||
*
|
||||
* \param scene: Scene in which strips are located.
|
||||
* \param seqbase: List in which strips are located.
|
||||
* \param initial_frame: frame on timeline from where gaps are searched for.
|
||||
* \param r_gap_info: data structure describing gap, that will be filled in by this function.
|
||||
*/
|
||||
void seq_time_gap_info_get(const Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
int initial_frame,
|
||||
GapInfo *r_gap_info);
|
||||
void strip_time_effect_range_set(const Scene *scene, Strip *strip);
|
||||
/**
|
||||
* Update strip `startdisp` and `enddisp` (n-input effects have no length to calculate these).
|
||||
*/
|
||||
void strip_time_update_effects_strip_range(const Scene *scene, Span<Strip *> effects);
|
||||
float strip_retiming_evaluate(const Strip *strip, const float frame_index);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
784
blender-5.2.0/source/blender/sequencer/intern/strip_transform.cc
Normal file
784
blender-5.2.0/source/blender/sequencer/intern/strip_transform.cc
Normal file
@@ -0,0 +1,784 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_movieclip_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#include "BLF_api.hh"
|
||||
|
||||
#include "SEQ_animation.hh"
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_edit.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
#include "SEQ_transform.hh"
|
||||
|
||||
#include "effects/effects.hh"
|
||||
#include "sequencer.hh"
|
||||
#include "strip_time.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Transform Utilities
|
||||
* \{ */
|
||||
|
||||
bool transform_single_image_check(const Strip *strip)
|
||||
{
|
||||
return (strip->flag & SEQ_SINGLE_FRAME_CONTENT) != 0;
|
||||
}
|
||||
|
||||
bool transform_is_locked(const ListBaseT<SeqTimelineChannel> *channels, const Strip *strip)
|
||||
{
|
||||
const SeqTimelineChannel *channel = channel_get_by_index(channels, strip->channel);
|
||||
return strip->flag & SEQ_LOCK ||
|
||||
(channel->is_locked() &&
|
||||
!flag_is_set(strip->runtime->flag, StripRuntimeFlag::IgnoreChannelLock));
|
||||
}
|
||||
|
||||
bool transform_strip_can_be_translated(const Strip *strip)
|
||||
{
|
||||
return !strip->is_effect_with_inputs();
|
||||
}
|
||||
|
||||
bool transform_test_overlap(const Scene *scene, Strip *strip1, Strip *strip2)
|
||||
{
|
||||
return (strip1 != strip2 && strip1->channel == strip2->channel &&
|
||||
((strip1->right_handle(scene) <= strip2->left_handle()) ||
|
||||
(strip1->left_handle() >= strip2->right_handle(scene))) == 0);
|
||||
}
|
||||
|
||||
bool transform_test_overlap(const Scene *scene, ListBaseT<Strip> *seqbasep, Strip *test)
|
||||
{
|
||||
Strip *strip;
|
||||
|
||||
strip = static_cast<Strip *>(seqbasep->first);
|
||||
while (strip) {
|
||||
if (transform_test_overlap(scene, test, strip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
strip = strip->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Timeline Strip Transform
|
||||
* \{ */
|
||||
|
||||
void transform_translate_strip(Scene *evil_scene, Strip *strip, int delta)
|
||||
{
|
||||
if (delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Meta strips requires their content is to be translated, and then frame range of the meta is
|
||||
* updated based on nested strips. This won't work for empty meta-strips,
|
||||
* so they can be treated as normal strip. */
|
||||
if (strip->type == STRIP_TYPE_META && !strip->seqbase.is_empty()) {
|
||||
for (Strip &strip_child : strip->seqbase) {
|
||||
transform_translate_strip(evil_scene, &strip_child, delta);
|
||||
}
|
||||
/* Move meta start/end points. */
|
||||
const int left_handle = strip->left_handle();
|
||||
const int right_handle = strip->right_handle(evil_scene);
|
||||
strip->handles_set(evil_scene, left_handle + delta, right_handle + delta);
|
||||
}
|
||||
else if (strip->input1 == nullptr && strip->input2 == nullptr) { /* All other strip types. */
|
||||
strip->start += delta;
|
||||
/* Only to make files usable in older versions. */
|
||||
strip->startdisp = strip->left_handle();
|
||||
strip->enddisp = strip->right_handle(evil_scene);
|
||||
}
|
||||
|
||||
offset_animdata(evil_scene, strip, delta);
|
||||
Span<Strip *> effects = SEQ_lookup_effects_by_strip(evil_scene->ed, strip);
|
||||
strip_time_update_effects_strip_range(evil_scene, effects);
|
||||
time_update_meta_strip_range(evil_scene, lookup_meta_by_strip(evil_scene->ed, strip));
|
||||
}
|
||||
|
||||
bool transform_seqbase_shuffle_ex(ListBaseT<Strip> *seqbasep,
|
||||
Strip *test,
|
||||
Scene *evil_scene,
|
||||
int channel_delta)
|
||||
{
|
||||
const int orig_channel = test->channel;
|
||||
BLI_assert(ELEM(channel_delta, -1, 1));
|
||||
|
||||
strip_channel_set(test, test->channel + channel_delta);
|
||||
|
||||
const ListBaseT<SeqTimelineChannel> *channels = channels_displayed_get(editing_get(evil_scene));
|
||||
SeqTimelineChannel *channel = channel_get_by_index(channels, test->channel);
|
||||
|
||||
bool use_fallback_translation = false;
|
||||
|
||||
while (transform_test_overlap(evil_scene, seqbasep, test) || channel->is_muted() ||
|
||||
channel->is_locked())
|
||||
{
|
||||
if ((channel_delta > 0) ? (test->channel + channel_delta >= MAX_CHANNELS) :
|
||||
(test->channel + channel_delta < 1))
|
||||
{
|
||||
use_fallback_translation = true;
|
||||
break;
|
||||
}
|
||||
|
||||
strip_channel_set(test, test->channel + channel_delta);
|
||||
channel = channel_get_by_index(channels, test->channel);
|
||||
}
|
||||
|
||||
/* Strip can not be moved to next free channel, translate it instead. */
|
||||
if (use_fallback_translation) {
|
||||
int new_frame = test->right_handle(evil_scene);
|
||||
|
||||
for (Strip &strip : *seqbasep) {
|
||||
if (strip.channel == orig_channel) {
|
||||
new_frame = max_ii(new_frame, strip.right_handle(evil_scene));
|
||||
}
|
||||
}
|
||||
|
||||
strip_channel_set(test, orig_channel);
|
||||
|
||||
new_frame = new_frame + (test->start - test->left_handle()); /* adjust by the startdisp */
|
||||
transform_translate_strip(evil_scene, test, new_frame - test->start);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool transform_seqbase_shuffle(ListBaseT<Strip> *seqbasep, Strip *test, Scene *evil_scene)
|
||||
{
|
||||
return transform_seqbase_shuffle_ex(seqbasep, test, evil_scene, 1);
|
||||
}
|
||||
|
||||
static bool shuffle_strip_test_overlap(const Scene *scene,
|
||||
const Strip *strip1,
|
||||
const Strip *strip2,
|
||||
const int offset)
|
||||
{
|
||||
BLI_assert(strip1 != strip2);
|
||||
return (strip1->channel == strip2->channel &&
|
||||
((strip1->right_handle(scene) + offset <= strip2->left_handle()) ||
|
||||
(strip1->left_handle() + offset >= strip2->right_handle(scene))) == 0);
|
||||
}
|
||||
|
||||
static int shuffle_strip_time_offset_get(const Scene *scene,
|
||||
Span<Strip *> strips_to_shuffle,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
char dir)
|
||||
{
|
||||
int offset = 0;
|
||||
bool all_conflicts_resolved = false;
|
||||
|
||||
while (!all_conflicts_resolved) {
|
||||
all_conflicts_resolved = true;
|
||||
for (Strip *strip : strips_to_shuffle) {
|
||||
for (Strip &strip_other : *seqbasep) {
|
||||
if (strips_to_shuffle.contains(&strip_other)) {
|
||||
continue;
|
||||
}
|
||||
if (relation_is_effect_of_strip(&strip_other, strip)) {
|
||||
continue;
|
||||
}
|
||||
if (!shuffle_strip_test_overlap(scene, strip, &strip_other, offset)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
all_conflicts_resolved = false;
|
||||
|
||||
if (dir == 'L') {
|
||||
offset = min_ii(offset, strip_other.left_handle() - strip->right_handle(scene));
|
||||
}
|
||||
else {
|
||||
offset = max_ii(offset, strip_other.right_handle(scene) - strip->left_handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool transform_seqbase_shuffle_time(Span<Strip *> strips_to_shuffle,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Scene *evil_scene,
|
||||
ListBaseT<TimeMarker> *markers,
|
||||
const bool use_sync_markers)
|
||||
{
|
||||
VectorSet<Strip *> empty_set;
|
||||
return transform_seqbase_shuffle_time(
|
||||
strips_to_shuffle, empty_set, seqbasep, evil_scene, markers, use_sync_markers);
|
||||
}
|
||||
|
||||
bool transform_seqbase_shuffle_time(Span<Strip *> strips_to_shuffle,
|
||||
Span<Strip *> time_dependent_strips,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Scene *evil_scene,
|
||||
ListBaseT<TimeMarker> *markers,
|
||||
const bool use_sync_markers)
|
||||
{
|
||||
int offset_l = shuffle_strip_time_offset_get(evil_scene, strips_to_shuffle, seqbasep, 'L');
|
||||
int offset_r = shuffle_strip_time_offset_get(evil_scene, strips_to_shuffle, seqbasep, 'R');
|
||||
int offset = (-offset_l < offset_r) ? offset_l : offset_r;
|
||||
|
||||
if (offset) {
|
||||
for (Strip *strip : strips_to_shuffle) {
|
||||
transform_translate_strip(evil_scene, strip, offset);
|
||||
strip->runtime->flag &= ~StripRuntimeFlag::Overlap;
|
||||
}
|
||||
|
||||
if (!time_dependent_strips.is_empty()) {
|
||||
for (Strip *strip : time_dependent_strips) {
|
||||
offset_animdata(evil_scene, strip, offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_sync_markers && !(evil_scene->toolsettings->lock_markers) && (markers != nullptr)) {
|
||||
/* affect selected markers - it's unlikely that we will want to affect all in this way? */
|
||||
for (TimeMarker &marker : *markers) {
|
||||
if (marker.flag & SELECT) {
|
||||
marker.frame += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return offset ? false : true;
|
||||
}
|
||||
|
||||
static VectorSet<Strip *> extract_standalone_strips(Span<Strip *> transformed_strips)
|
||||
{
|
||||
VectorSet<Strip *> standalone_strips;
|
||||
|
||||
for (Strip *strip : transformed_strips) {
|
||||
if (!strip->is_effect() || strip->input1 == nullptr) {
|
||||
standalone_strips.add(strip);
|
||||
}
|
||||
}
|
||||
return standalone_strips;
|
||||
}
|
||||
|
||||
/* Query strips positioned after left edge of transformed strips bound-box. */
|
||||
static VectorSet<Strip *> query_right_side_strips(ListBaseT<Strip> *seqbase,
|
||||
Span<Strip *> transformed_strips,
|
||||
Span<Strip *> time_dependent_strips)
|
||||
{
|
||||
int minframe = MAXFRAME;
|
||||
{
|
||||
for (Strip *strip : transformed_strips) {
|
||||
minframe = min_ii(minframe, strip->left_handle());
|
||||
}
|
||||
}
|
||||
|
||||
VectorSet<Strip *> right_side_strips;
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (!time_dependent_strips.is_empty() && time_dependent_strips.contains(&strip)) {
|
||||
continue;
|
||||
}
|
||||
if (transformed_strips.contains(&strip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((strip.flag & SEQ_SELECT) == 0 && strip.left_handle() >= minframe) {
|
||||
right_side_strips.add(&strip);
|
||||
}
|
||||
}
|
||||
return right_side_strips;
|
||||
}
|
||||
|
||||
/* Offset all strips positioned after left edge of transformed strips bound-box by amount equal
|
||||
* to overlap of transformed strips. */
|
||||
static void strip_transform_handle_expand_to_fit(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips,
|
||||
Span<Strip *> time_dependent_strips,
|
||||
bool use_sync_markers)
|
||||
{
|
||||
ListBaseT<TimeMarker> *markers = &scene->markers;
|
||||
|
||||
VectorSet right_side_strips = query_right_side_strips(
|
||||
seqbasep, transformed_strips, time_dependent_strips);
|
||||
|
||||
/* Temporarily move right side strips beyond timeline boundary. */
|
||||
for (Strip *strip : right_side_strips) {
|
||||
strip->channel += MAX_CHANNELS * 2;
|
||||
}
|
||||
|
||||
/* Shuffle transformed standalone strips. This is because transformed strips can overlap with
|
||||
* strips on left side. */
|
||||
VectorSet standalone_strips = extract_standalone_strips(transformed_strips);
|
||||
transform_seqbase_shuffle_time(
|
||||
standalone_strips, time_dependent_strips, seqbasep, scene, markers, use_sync_markers);
|
||||
|
||||
/* Move temporarily moved strips back to their original place and tag for shuffling. */
|
||||
for (Strip *strip : right_side_strips) {
|
||||
strip->channel -= MAX_CHANNELS * 2;
|
||||
}
|
||||
/* Shuffle again to displace strips on right side. Final effect shuffling is done in
|
||||
* SEQ_transform_handle_overlap. */
|
||||
transform_seqbase_shuffle_time(right_side_strips, seqbasep, scene, markers, use_sync_markers);
|
||||
}
|
||||
|
||||
static VectorSet<Strip *> query_overwrite_targets(const Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips)
|
||||
{
|
||||
VectorSet<Strip *> overwrite_targets = query_unselected_strips(seqbasep);
|
||||
|
||||
/* Effects of transformed strips can be unselected. These must not be included. */
|
||||
overwrite_targets.remove_if([&](Strip *strip) { return transformed_strips.contains(strip); });
|
||||
overwrite_targets.remove_if([&](Strip *strip) {
|
||||
bool does_overlap = false;
|
||||
for (Strip *strip_transformed : transformed_strips) {
|
||||
if (transform_test_overlap(scene, strip, strip_transformed)) {
|
||||
does_overlap = true;
|
||||
}
|
||||
}
|
||||
|
||||
return !does_overlap;
|
||||
});
|
||||
|
||||
return overwrite_targets;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static eOvelapDescrition overlap_description_get(const Scene *scene,
|
||||
const Strip *transformed,
|
||||
const Strip *target)
|
||||
{
|
||||
if (transformed->left_handle() <= target->left_handle() &&
|
||||
transformed->right_handle(scene) >= target->right_handle(scene))
|
||||
{
|
||||
return STRIP_OVERLAP_IS_FULL;
|
||||
}
|
||||
if (transformed->left_handle() > target->left_handle() &&
|
||||
transformed->right_handle(scene) < target->right_handle(scene))
|
||||
{
|
||||
return STRIP_OVERLAP_IS_INSIDE;
|
||||
}
|
||||
if (transformed->left_handle() <= target->left_handle() &&
|
||||
target->left_handle() <= transformed->right_handle(scene))
|
||||
{
|
||||
return STRIP_OVERLAP_LEFT_SIDE;
|
||||
}
|
||||
if (transformed->left_handle() <= target->right_handle(scene) &&
|
||||
target->right_handle(scene) <= transformed->right_handle(scene))
|
||||
{
|
||||
return STRIP_OVERLAP_RIGHT_SIDE;
|
||||
}
|
||||
return STRIP_OVERLAP_NONE;
|
||||
}
|
||||
|
||||
/* Split strip in 3 parts, remove middle part and fit transformed inside. */
|
||||
static void strip_transform_handle_overwrite_split(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
const Strip *transformed,
|
||||
Strip *target)
|
||||
{
|
||||
/* Because we are doing a soft split, bmain is not used in SEQ_edit_strip_split, so we can
|
||||
* pass nullptr here. */
|
||||
Main *bmain = nullptr;
|
||||
const char *error_msg = nullptr;
|
||||
Strip *split_strip = edit_strip_split(
|
||||
bmain, scene, seqbasep, target, transformed->left_handle(), SPLIT_SOFT, true, &error_msg);
|
||||
if (split_strip == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
error_msg = nullptr;
|
||||
if (edit_strip_split(bmain,
|
||||
scene,
|
||||
seqbasep,
|
||||
split_strip,
|
||||
transformed->right_handle(scene),
|
||||
SPLIT_SOFT,
|
||||
true,
|
||||
&error_msg) == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
edit_flag_for_removal(scene, seqbasep, split_strip);
|
||||
edit_remove_flagged_strips(scene, seqbasep);
|
||||
}
|
||||
|
||||
/* Trim strips by adjusting handle position.
|
||||
* This is bit more complicated in case overlap happens on effect. */
|
||||
static void strip_transform_handle_overwrite_trim(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
const Strip *transformed,
|
||||
Strip *target,
|
||||
const eOvelapDescrition overlap)
|
||||
{
|
||||
VectorSet targets = query_by_reference(target, seqbasep, query_strip_effect_chain);
|
||||
|
||||
/* Expand collection by adding all target's children, effects and their children. */
|
||||
if (target->is_effect()) {
|
||||
iterator_set_expand(seqbasep, targets, query_strip_effect_chain);
|
||||
}
|
||||
|
||||
/* Trim all non effects, that have influence on effect length which is overlapping. */
|
||||
for (Strip *strip : targets) {
|
||||
if (strip->is_effect_with_inputs()) {
|
||||
continue;
|
||||
}
|
||||
if (overlap == STRIP_OVERLAP_LEFT_SIDE) {
|
||||
strip->left_handle_set(scene, transformed->right_handle(scene));
|
||||
}
|
||||
else {
|
||||
BLI_assert(overlap == STRIP_OVERLAP_RIGHT_SIDE);
|
||||
strip->right_handle_set(scene, transformed->left_handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void strip_transform_handle_overwrite(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips)
|
||||
{
|
||||
VectorSet targets = query_overwrite_targets(scene, seqbasep, transformed_strips);
|
||||
VectorSet<Strip *> strips_to_delete;
|
||||
|
||||
const ListBaseT<SeqTimelineChannel> *channels = channels_displayed_get(editing_get(scene));
|
||||
for (Strip *target : targets) {
|
||||
for (Strip *transformed : transformed_strips) {
|
||||
if (transformed->channel != target->channel) {
|
||||
continue;
|
||||
}
|
||||
/* Do not allow overwriting/trimming/deleting locked strips. */
|
||||
if (transform_is_locked(channels, target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const eOvelapDescrition overlap = overlap_description_get(scene, transformed, target);
|
||||
|
||||
if (overlap == STRIP_OVERLAP_IS_FULL) {
|
||||
strips_to_delete.add(target);
|
||||
}
|
||||
else if (overlap == STRIP_OVERLAP_IS_INSIDE) {
|
||||
strip_transform_handle_overwrite_split(scene, seqbasep, transformed, target);
|
||||
}
|
||||
else if (ELEM(overlap, STRIP_OVERLAP_LEFT_SIDE, STRIP_OVERLAP_RIGHT_SIDE)) {
|
||||
strip_transform_handle_overwrite_trim(scene, seqbasep, transformed, target, overlap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove covered strips. This must be done in separate loop, because
|
||||
* `SEQ_edit_strip_split()` also uses `SEQ_edit_remove_flagged_sequences()`. See #91096. */
|
||||
if (!strips_to_delete.is_empty()) {
|
||||
for (Strip *strip : strips_to_delete) {
|
||||
edit_flag_for_removal(scene, seqbasep, strip);
|
||||
}
|
||||
edit_remove_flagged_strips(scene, seqbasep);
|
||||
}
|
||||
}
|
||||
|
||||
static void strip_transform_handle_overlap_shuffle(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips,
|
||||
Span<Strip *> time_dependent_strips,
|
||||
bool use_sync_markers)
|
||||
{
|
||||
ListBaseT<TimeMarker> *markers = &scene->markers;
|
||||
|
||||
/* Shuffle non strips with no effects attached. */
|
||||
VectorSet standalone_strips = extract_standalone_strips(transformed_strips);
|
||||
transform_seqbase_shuffle_time(
|
||||
standalone_strips, time_dependent_strips, seqbasep, scene, markers, use_sync_markers);
|
||||
}
|
||||
|
||||
void transform_handle_overlap(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips,
|
||||
bool use_sync_markers)
|
||||
{
|
||||
VectorSet<Strip *> empty_set;
|
||||
transform_handle_overlap(scene, seqbasep, transformed_strips, empty_set, use_sync_markers);
|
||||
}
|
||||
|
||||
void transform_handle_overlap(Scene *scene,
|
||||
ListBaseT<Strip> *seqbasep,
|
||||
Span<Strip *> transformed_strips,
|
||||
Span<Strip *> time_dependent_strips,
|
||||
bool use_sync_markers)
|
||||
{
|
||||
const eSeqOverlapMode overlap_mode = tool_settings_overlap_mode_get(scene);
|
||||
|
||||
switch (overlap_mode) {
|
||||
case SEQ_OVERLAP_EXPAND:
|
||||
strip_transform_handle_expand_to_fit(
|
||||
scene, seqbasep, transformed_strips, time_dependent_strips, use_sync_markers);
|
||||
break;
|
||||
case SEQ_OVERLAP_OVERWRITE:
|
||||
strip_transform_handle_overwrite(scene, seqbasep, transformed_strips);
|
||||
break;
|
||||
case SEQ_OVERLAP_SHUFFLE:
|
||||
strip_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 #90646. */
|
||||
for (Strip *strip : transformed_strips) {
|
||||
if (transform_test_overlap(scene, seqbasep, strip)) {
|
||||
transform_seqbase_shuffle(seqbasep, strip, scene);
|
||||
}
|
||||
strip->runtime->flag &= ~StripRuntimeFlag::Overlap;
|
||||
}
|
||||
}
|
||||
|
||||
void transform_offset_after_frame(Scene *scene,
|
||||
ListBaseT<Strip> *seqbase,
|
||||
const int delta,
|
||||
const int timeline_frame)
|
||||
{
|
||||
for (Strip &strip : *seqbase) {
|
||||
if (strip.left_handle() >= timeline_frame) {
|
||||
transform_translate_strip(scene, &strip, delta);
|
||||
relations_invalidate_cache(scene, &strip);
|
||||
}
|
||||
}
|
||||
|
||||
if (!scene->toolsettings->lock_markers) {
|
||||
for (TimeMarker &marker : scene->markers) {
|
||||
if (marker.frame >= timeline_frame) {
|
||||
marker.frame += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void strip_channel_set(Strip *strip, int channel)
|
||||
{
|
||||
strip->channel = math::clamp(channel, 1, MAX_CHANNELS);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Preview Image Transform
|
||||
* \{ */
|
||||
|
||||
float2 image_transform_mirror_factor_get(const Strip *strip)
|
||||
{
|
||||
float2 mirror(1.0f, 1.0f);
|
||||
|
||||
if ((strip->flag & SEQ_FLIPX) != 0) {
|
||||
mirror.x = -1.0f;
|
||||
}
|
||||
if ((strip->flag & SEQ_FLIPY) != 0) {
|
||||
mirror.y = -1.0f;
|
||||
}
|
||||
return mirror;
|
||||
}
|
||||
|
||||
float2 image_transform_raw_size_get(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
float2 scene_render_size(scene->r.xsch, scene->r.ysch);
|
||||
|
||||
if (ELEM(strip->type, STRIP_TYPE_MOVIE, STRIP_TYPE_IMAGE)) {
|
||||
const StripElem *selem = strip->data->stripdata;
|
||||
return {float(selem->orig_width), float(selem->orig_height)};
|
||||
}
|
||||
|
||||
if (strip->type == STRIP_TYPE_MOVIECLIP) {
|
||||
const MovieClip *clip = strip->clip;
|
||||
if (clip != nullptr && clip->lastsize[0] != 0 && clip->lastsize[1] != 0) {
|
||||
return {float(clip->lastsize[0]), float(clip->lastsize[1])};
|
||||
}
|
||||
}
|
||||
|
||||
if (strip->type == STRIP_TYPE_COLOR) {
|
||||
const SolidColorVars *data = static_cast<const SolidColorVars *>(strip->effectdata);
|
||||
return {float(data->width), float(data->height)};
|
||||
}
|
||||
|
||||
if (strip->type == STRIP_TYPE_TEXT) {
|
||||
TextVars *data = static_cast<TextVars *>(strip->effectdata);
|
||||
std::scoped_lock runtime_lock(text_runtime_mutex_get());
|
||||
text_effect_update_runtime(nullptr, *data, int2(scene_render_size));
|
||||
BLF_disable(data->runtime->font, BLF_BOLD | BLF_ITALIC);
|
||||
const float2 text_size(float(BLI_rcti_size_x(&data->runtime->text_boundbox)),
|
||||
float(BLI_rcti_size_y(&data->runtime->text_boundbox)));
|
||||
return text_size;
|
||||
}
|
||||
|
||||
return scene_render_size;
|
||||
}
|
||||
|
||||
/* Convert origin from a 0->1 range (where (0,0) is the bottom left of the image)
|
||||
* to the offset in view-space pixels from an image's center. */
|
||||
static float2 convert_origin_to_image_offset(const Scene *scene, const Strip *strip, float2 origin)
|
||||
{
|
||||
const float2 image_size = image_transform_raw_size_get(scene, strip);
|
||||
return image_size * origin - (image_size / 2.0f);
|
||||
}
|
||||
|
||||
float2 image_transform_origin_get(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
|
||||
const StripTransform *tr = strip->data->transform;
|
||||
if (strip->type != STRIP_TYPE_TEXT) {
|
||||
return tr->origin;
|
||||
}
|
||||
|
||||
/* Text strips 'fake' smaller bounds but their true image size is the size of the render. We must
|
||||
* convert from an origin relative to the text box -> an origin relative to the whole render. */
|
||||
const float2 text_size = image_transform_raw_size_get(scene, strip);
|
||||
const float2 render_size(scene->r.xsch, scene->r.ysch);
|
||||
|
||||
/* Before we scale the text origin down to produce the render origin, we must offset the origin
|
||||
* so that (0,0) corresponds to the center instead of (0.5, 0.5) for correct math. */
|
||||
const float2 offset_text_origin = float2(tr->origin) - float2(0.5f);
|
||||
const float2 render_origin = float2(0.5f) + offset_text_origin * (text_size / render_size);
|
||||
return render_origin;
|
||||
}
|
||||
|
||||
float2 image_transform_origin_preview_offset_get(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
const StripTransform *tr = strip->data->transform;
|
||||
const float2 origin_offset = convert_origin_to_image_offset(scene, strip, tr->origin);
|
||||
|
||||
const float2 viewport_pixel_aspect(scene->r.xasp / scene->r.yasp, 1.0f);
|
||||
const float2 mirror = image_transform_mirror_factor_get(strip);
|
||||
return (origin_offset + float2(tr->xofs, tr->yofs)) * mirror * viewport_pixel_aspect;
|
||||
}
|
||||
|
||||
float3x3 image_transform_matrix_get(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
const StripTransform *tr = strip->data->transform;
|
||||
const float3x3 matrix = math::from_loc_rot_scale<float3x3>(
|
||||
float2(tr->xofs, tr->yofs), tr->rotation, float2(tr->scale_x, tr->scale_y));
|
||||
|
||||
const float2 origin_offset = convert_origin_to_image_offset(scene, strip, tr->origin);
|
||||
return math::from_origin_transform(matrix, origin_offset);
|
||||
}
|
||||
|
||||
Array<float2> image_transform_quad_get(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
constexpr int num_corners = 4;
|
||||
const float2 image_size = image_transform_raw_size_get(scene, strip);
|
||||
|
||||
/* Raw quad before any rotation/scaling or text anchoring is applied.
|
||||
*
|
||||
* NOTE: For text strips, crops should only affect their visible result and not their bounding
|
||||
* box. Text effects can stray outside, so crop works on the full render buffer. */
|
||||
const StripCrop no_crop{};
|
||||
const StripCrop *crop = (strip->type == STRIP_TYPE_TEXT) ? &no_crop : strip->data->crop;
|
||||
float2 quad[num_corners]{
|
||||
{(image_size.x / 2) - crop->right, (image_size.y / 2) - crop->top}, /* Top right. */
|
||||
{(image_size.x / 2) - crop->right, (-image_size.y / 2) + crop->bottom}, /* Bottom right. */
|
||||
{(-image_size.x / 2) + crop->left, (-image_size.y / 2) + crop->bottom}, /* Bottom left. */
|
||||
{(-image_size.x / 2) + crop->left, (image_size.y / 2) - crop->top}, /* Top left. */
|
||||
};
|
||||
|
||||
if (strip->type == STRIP_TYPE_TEXT) {
|
||||
const TextVars *data = static_cast<TextVars *>(strip->effectdata);
|
||||
float2 offset(0, 0);
|
||||
|
||||
switch (data->anchor_x) {
|
||||
case SEQ_TEXT_ANCHOR_X_LEFT:
|
||||
offset.x += image_size.x / 2.0f;
|
||||
break;
|
||||
case SEQ_TEXT_ANCHOR_X_CENTER:
|
||||
break;
|
||||
case SEQ_TEXT_ANCHOR_X_RIGHT:
|
||||
offset.x += -image_size.x / 2.0f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (data->anchor_y) {
|
||||
case SEQ_TEXT_ANCHOR_Y_BOTTOM:
|
||||
offset.y += image_size.y / 2.0f;
|
||||
break;
|
||||
case SEQ_TEXT_ANCHOR_Y_CENTER:
|
||||
break;
|
||||
case SEQ_TEXT_ANCHOR_Y_TOP:
|
||||
offset.y += -image_size.y / 2.0f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (float2 &corner : quad) {
|
||||
corner += offset;
|
||||
}
|
||||
}
|
||||
|
||||
const float3x3 matrix = image_transform_matrix_get(scene, strip);
|
||||
const float2 viewport_pixel_aspect(scene->r.xasp / scene->r.yasp, 1.0f);
|
||||
const float2 mirror = image_transform_mirror_factor_get(strip);
|
||||
|
||||
Array<float2> quad_final(num_corners);
|
||||
for (const int i : IndexRange(num_corners)) {
|
||||
const float2 point = math::transform_point(matrix, quad[i]);
|
||||
quad_final[i] = point * mirror * viewport_pixel_aspect;
|
||||
}
|
||||
return quad_final;
|
||||
}
|
||||
|
||||
float2 image_preview_unit_to_px(const Scene *scene, const float2 co_src)
|
||||
{
|
||||
return {co_src.x * scene->r.xsch, co_src.y * scene->r.ysch};
|
||||
}
|
||||
|
||||
float2 image_preview_unit_from_px(const Scene *scene, const float2 co_src)
|
||||
{
|
||||
return {co_src.x / scene->r.xsch, co_src.y / scene->r.ysch};
|
||||
}
|
||||
|
||||
static Bounds<float2> negative_bounds()
|
||||
{
|
||||
return {float2(std::numeric_limits<float>::max()), float2(std::numeric_limits<float>::lowest())};
|
||||
}
|
||||
|
||||
Bounds<float2> image_transform_bounding_box_from_strips_get(Scene *scene, Span<Strip *> strips)
|
||||
{
|
||||
Bounds<float2> box = negative_bounds();
|
||||
|
||||
for (Strip *strip : strips) {
|
||||
const Array<float2> quad = image_transform_quad_get(scene, strip);
|
||||
const Bounds<float2> strip_box = *bounds::min_max(quad.as_span());
|
||||
box = bounds::merge(box, strip_box);
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::seq
|
||||
523
blender-5.2.0/source/blender/sequencer/intern/utils.cc
Normal file
523
blender-5.2.0/source/blender/sequencer/intern/utils.cc
Normal file
@@ -0,0 +1,523 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
|
||||
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "BKE_animsys.h"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_edit.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "multiview.hh"
|
||||
#include "proxy.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct StripUniqueInfo {
|
||||
Strip *strip;
|
||||
char name_src[STRIP_NAME_MAXSTR];
|
||||
char name_dest[STRIP_NAME_MAXSTR];
|
||||
int count;
|
||||
int match;
|
||||
};
|
||||
|
||||
static void seqbase_unique_name(ListBaseT<Strip> *seqbasep, StripUniqueInfo *sui)
|
||||
{
|
||||
for (Strip &strip : *seqbasep) {
|
||||
if ((sui->strip != &strip) && STREQ(sui->name_dest, strip.name + 2)) {
|
||||
/* STRIP_NAME_MAXSTR -4 for the number, -1 for \0, - 2 for r_prefix */
|
||||
SNPRINTF(
|
||||
sui->name_dest, "%.*s.%03d", STRIP_NAME_MAXSTR - 4 - 1 - 2, sui->name_src, sui->count++);
|
||||
sui->match = 1; /* be sure to re-scan */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool seqbase_unique_name_recursive_fn(Strip *strip, void *arg_pt)
|
||||
{
|
||||
if (strip->seqbase.first) {
|
||||
seqbase_unique_name(&strip->seqbase, static_cast<StripUniqueInfo *>(arg_pt));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void strip_unique_name_set(Scene *scene, ListBaseT<Strip> *seqbasep, Strip *strip)
|
||||
{
|
||||
StripUniqueInfo sui;
|
||||
char *dot;
|
||||
sui.strip = strip;
|
||||
STRNCPY(sui.name_src, strip->name + 2);
|
||||
STRNCPY(sui.name_dest, sui.name_src);
|
||||
sui.count = 1;
|
||||
sui.match = 1; /* assume the worst to start the loop */
|
||||
|
||||
/* Strip off the suffix only if it is purely numeric. */
|
||||
if ((dot = strrchr(sui.name_src, '.'))) {
|
||||
char *suffix = dot + 1;
|
||||
if (BLI_string_is_decimal(suffix)) {
|
||||
*dot = '\0';
|
||||
sui.count = atoi(suffix) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (sui.match) {
|
||||
sui.match = 0;
|
||||
seqbase_unique_name(seqbasep, &sui);
|
||||
foreach_strip(seqbasep, seqbase_unique_name_recursive_fn, &sui);
|
||||
}
|
||||
|
||||
edit_strip_name_set(scene, strip, sui.name_dest);
|
||||
}
|
||||
|
||||
const char *get_default_stripname_by_type(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case STRIP_TYPE_META:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Meta");
|
||||
case STRIP_TYPE_IMAGE:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Image");
|
||||
case STRIP_TYPE_SCENE:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Scene");
|
||||
case STRIP_TYPE_MOVIE:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Movie");
|
||||
case STRIP_TYPE_MOVIECLIP:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Clip");
|
||||
case STRIP_TYPE_MASK:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Mask");
|
||||
case STRIP_TYPE_SOUND:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Audio");
|
||||
case STRIP_TYPE_CROSS:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Crossfade");
|
||||
case STRIP_TYPE_GAMCROSS:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Gamma Crossfade");
|
||||
case STRIP_TYPE_COMPOSITOR:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Compositor");
|
||||
case STRIP_TYPE_ADD:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Add");
|
||||
case STRIP_TYPE_SUB:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Subtract");
|
||||
case STRIP_TYPE_MUL:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Multiply");
|
||||
case STRIP_TYPE_ALPHAOVER:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Alpha Over");
|
||||
case STRIP_TYPE_ALPHAUNDER:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Alpha Under");
|
||||
case STRIP_TYPE_COLORMIX:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Color Mix");
|
||||
case STRIP_TYPE_WIPE:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Wipe");
|
||||
case STRIP_TYPE_GLOW:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Glow");
|
||||
case STRIP_TYPE_COLOR:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Color");
|
||||
case STRIP_TYPE_MULTICAM:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Multicam");
|
||||
case STRIP_TYPE_ADJUSTMENT:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Adjustment");
|
||||
case STRIP_TYPE_SPEED:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Speed");
|
||||
case STRIP_TYPE_GAUSSIAN_BLUR:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Gaussian Blur");
|
||||
case STRIP_TYPE_TEXT:
|
||||
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Text");
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const char *strip_give_name(const Strip *strip)
|
||||
{
|
||||
const char *name = get_default_stripname_by_type(strip->type);
|
||||
|
||||
if (!name) {
|
||||
if (!strip->is_effect()) {
|
||||
return strip->data->dirpath;
|
||||
}
|
||||
|
||||
return DATA_("Effect");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
ListBaseT<Strip> *get_seqbase_from_strip(Strip *strip,
|
||||
ListBaseT<SeqTimelineChannel> **r_channels,
|
||||
int *r_offset)
|
||||
{
|
||||
ListBaseT<Strip> *seqbase = nullptr;
|
||||
|
||||
switch (strip->type) {
|
||||
case STRIP_TYPE_META: {
|
||||
seqbase = &strip->seqbase;
|
||||
*r_channels = &strip->channels;
|
||||
*r_offset = strip->content_start();
|
||||
break;
|
||||
}
|
||||
case STRIP_TYPE_SCENE: {
|
||||
if (strip->flag & SEQ_SCENE_STRIPS && strip->scene) {
|
||||
Editing *ed = editing_get(strip->scene);
|
||||
if (ed) {
|
||||
seqbase = &ed->seqbase;
|
||||
*r_channels = &ed->channels;
|
||||
*r_offset = strip->scene->r.sfra;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return seqbase;
|
||||
}
|
||||
|
||||
static MovieReader *open_anim_filepath(Strip *strip, const char *filepath, bool openfile)
|
||||
{
|
||||
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
|
||||
* kept unchanged for the performance reasons. */
|
||||
if (openfile) {
|
||||
return openanim(filepath,
|
||||
(strip->flag & SEQ_DEINTERLACE) ? ImBufFlags::Deinterlace : ImBufFlags::Zero,
|
||||
strip->streamindex,
|
||||
true,
|
||||
strip->data->colorspace_settings.name);
|
||||
}
|
||||
return openanim_noload(filepath,
|
||||
(strip->flag & SEQ_DEINTERLACE) ? ImBufFlags::Deinterlace :
|
||||
ImBufFlags::Zero,
|
||||
strip->streamindex,
|
||||
true,
|
||||
strip->data->colorspace_settings.name);
|
||||
}
|
||||
|
||||
static bool use_proxy(Editing *ed, Strip *strip)
|
||||
{
|
||||
StripProxy *proxy = strip->data->proxy;
|
||||
return proxy && ((proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_DIR) != 0 ||
|
||||
(ed->proxy_storage == SEQ_EDIT_PROXY_DIR_STORAGE));
|
||||
}
|
||||
|
||||
static void proxy_dir_get(Editing *ed, Strip *strip, char r_proxy_dirpath[FILE_MAX])
|
||||
{
|
||||
if (use_proxy(ed, strip)) {
|
||||
if (ed->proxy_storage == SEQ_EDIT_PROXY_DIR_STORAGE) {
|
||||
if (ed->proxy_dir[0] == 0) {
|
||||
BLI_strncpy(r_proxy_dirpath, "//BL_proxy", FILE_MAX);
|
||||
}
|
||||
else {
|
||||
BLI_strncpy(r_proxy_dirpath, ed->proxy_dir, FILE_MAX);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_strncpy(r_proxy_dirpath, strip->data->proxy->dirpath, FILE_MAX);
|
||||
}
|
||||
BLI_path_abs(r_proxy_dirpath, BKE_main_blendfile_path_from_global());
|
||||
}
|
||||
}
|
||||
|
||||
static void index_dir_set(Editing *ed, Strip *strip, MovieReader *reader)
|
||||
{
|
||||
if (reader == nullptr || !use_proxy(ed, strip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char proxy_dirpath[FILE_MAX];
|
||||
proxy_dir_get(ed, strip, proxy_dirpath);
|
||||
seq_proxy_index_dir_set(reader, proxy_dirpath);
|
||||
}
|
||||
|
||||
static bool open_anim_file_multiview(Scene *scene, Strip *strip, const char *filepath)
|
||||
{
|
||||
char prefix[FILE_MAX];
|
||||
const char *ext = nullptr;
|
||||
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix, &ext);
|
||||
|
||||
if (strip->views_format != R_IMF_VIEWS_INDIVIDUAL || prefix[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
Editing *ed = scene->ed;
|
||||
bool is_multiview_loaded = false;
|
||||
int totfiles = seq_num_files(scene, strip->views_format, true);
|
||||
|
||||
for (int i = 0; i < totfiles; i++) {
|
||||
const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, i);
|
||||
char filepath_view[FILE_MAX];
|
||||
SNPRINTF(filepath_view, "%s%s%s", prefix, suffix, ext);
|
||||
|
||||
/* Multiview files must be loaded, otherwise it is not possible to detect failure. */
|
||||
MovieReader *reader = open_anim_filepath(strip, filepath_view, true);
|
||||
|
||||
if (reader == nullptr) {
|
||||
strip_free_movie_readers(strip);
|
||||
return false; /* Multiview render failed. */
|
||||
}
|
||||
|
||||
index_dir_set(ed, strip, reader);
|
||||
strip->runtime->movie_readers.append(reader);
|
||||
MOV_set_multiview_suffix(reader, suffix);
|
||||
is_multiview_loaded = true;
|
||||
}
|
||||
|
||||
return is_multiview_loaded;
|
||||
}
|
||||
|
||||
void strip_open_anim_file(Scene *scene, Strip *strip, bool openfile)
|
||||
{
|
||||
if (!openfile && strip->runtime->movie_reader_get() != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset all the previously created anims. */
|
||||
strip_free_movie_readers(strip);
|
||||
|
||||
Editing *ed = scene->ed;
|
||||
char filepath[FILE_MAX];
|
||||
BLI_path_join(
|
||||
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
||||
|
||||
bool is_multiview = (strip->flag & SEQ_USE_VIEWS) != 0 && (scene->r.scemode & R_MULTIVIEW) != 0;
|
||||
bool multiview_is_loaded = false;
|
||||
|
||||
if (is_multiview) {
|
||||
multiview_is_loaded = open_anim_file_multiview(scene, strip, filepath);
|
||||
}
|
||||
|
||||
if (!is_multiview || !multiview_is_loaded) {
|
||||
MovieReader *reader = open_anim_filepath(strip, filepath, openfile);
|
||||
strip->runtime->movie_readers.append(reader);
|
||||
index_dir_set(ed, strip, reader);
|
||||
}
|
||||
}
|
||||
|
||||
const Strip *strip_topmost_get(const Scene *scene, int frame)
|
||||
{
|
||||
Editing *ed = scene->ed;
|
||||
|
||||
if (!ed) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ListBaseT<SeqTimelineChannel> *channels = channels_displayed_get(ed);
|
||||
const Strip *best_strip = nullptr;
|
||||
int best_channel = -1;
|
||||
|
||||
for (const Strip &strip : *ed->current_strips()) {
|
||||
if (render_is_muted(channels, &strip) || !strip.intersects_frame(scene, frame)) {
|
||||
continue;
|
||||
}
|
||||
/* Only use strips that generate an image, not ones that combine
|
||||
* other strips or apply some effect. */
|
||||
if (ELEM(strip.type,
|
||||
STRIP_TYPE_IMAGE,
|
||||
STRIP_TYPE_META,
|
||||
STRIP_TYPE_SCENE,
|
||||
STRIP_TYPE_MOVIE,
|
||||
STRIP_TYPE_COLOR,
|
||||
STRIP_TYPE_TEXT))
|
||||
{
|
||||
if (strip.channel > best_channel) {
|
||||
best_strip = &strip;
|
||||
best_channel = strip.channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best_strip;
|
||||
}
|
||||
|
||||
ListBaseT<Strip> *get_seqbase_by_strip(const Scene *scene, Strip *strip)
|
||||
{
|
||||
Editing *ed = editing_get(scene);
|
||||
ListBaseT<Strip> *main_seqbase = &ed->seqbase;
|
||||
Strip *strip_meta = lookup_meta_by_strip(ed, strip);
|
||||
|
||||
if (strip_meta != nullptr) {
|
||||
return &strip_meta->seqbase;
|
||||
}
|
||||
if (BLI_findindex(main_seqbase, strip) != -1) {
|
||||
return main_seqbase;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Strip *strip_from_strip_elem(ListBaseT<Strip> *seqbase, StripElem *se)
|
||||
{
|
||||
Strip *istrip;
|
||||
|
||||
for (istrip = static_cast<Strip *>(seqbase->first); istrip; istrip = istrip->next) {
|
||||
Strip *strip_found;
|
||||
if ((istrip->data && istrip->data->stripdata) &&
|
||||
ARRAY_HAS_ITEM(se, istrip->data->stripdata, istrip->len))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ((strip_found = strip_from_strip_elem(&istrip->seqbase, se))) {
|
||||
istrip = strip_found;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return istrip;
|
||||
}
|
||||
|
||||
Strip *get_strip_by_name(ListBaseT<Strip> *seqbase, const char *name, bool recursive)
|
||||
{
|
||||
for (Strip &istrip : *seqbase) {
|
||||
if (STREQ(name, istrip.name + 2)) {
|
||||
return &istrip;
|
||||
}
|
||||
if (recursive && !istrip.seqbase.is_empty()) {
|
||||
Strip *rseq = get_strip_by_name(&istrip.seqbase, name, true);
|
||||
if (rseq != nullptr) {
|
||||
return rseq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Mask *active_mask_get(Scene *scene)
|
||||
{
|
||||
Strip *strip_act = select_active_get(scene);
|
||||
|
||||
if (strip_act && strip_act->type == STRIP_TYPE_MASK) {
|
||||
return strip_act->mask;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void alpha_mode_from_file_extension(Strip *strip)
|
||||
{
|
||||
if (strip->data && strip->data->stripdata) {
|
||||
const char *filename = strip->data->stripdata->filename;
|
||||
strip->alpha_mode = eStripAlphaMode(BKE_image_alpha_mode_from_extension_ex(filename));
|
||||
}
|
||||
}
|
||||
|
||||
bool strip_has_valid_data(const Strip *strip)
|
||||
{
|
||||
switch (strip->type) {
|
||||
case STRIP_TYPE_MASK:
|
||||
return (strip->mask != nullptr);
|
||||
case STRIP_TYPE_MOVIECLIP:
|
||||
return (strip->clip != nullptr);
|
||||
case STRIP_TYPE_SCENE:
|
||||
return (strip->scene != nullptr);
|
||||
case STRIP_TYPE_SOUND:
|
||||
return (strip->sound != nullptr);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool sequencer_strip_generates_image(Strip *strip)
|
||||
{
|
||||
switch (strip->type) {
|
||||
case STRIP_TYPE_IMAGE:
|
||||
case STRIP_TYPE_SCENE:
|
||||
case STRIP_TYPE_MOVIE:
|
||||
case STRIP_TYPE_MOVIECLIP:
|
||||
case STRIP_TYPE_MASK:
|
||||
case STRIP_TYPE_COLOR:
|
||||
case STRIP_TYPE_TEXT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void set_scale_to_fit(const Strip *strip,
|
||||
const int image_width,
|
||||
const int image_height,
|
||||
const int preview_width,
|
||||
const int preview_height,
|
||||
const eSeqImageFitMethod fit_method)
|
||||
{
|
||||
StripTransform *transform = strip->data->transform;
|
||||
|
||||
switch (fit_method) {
|
||||
case SEQ_SCALE_TO_FIT:
|
||||
transform->scale_x = transform->scale_y = std::min(
|
||||
float(preview_width) / float(image_width), float(preview_height) / float(image_height));
|
||||
|
||||
break;
|
||||
case SEQ_SCALE_TO_FILL:
|
||||
|
||||
transform->scale_x = transform->scale_y = std::max(
|
||||
float(preview_width) / float(image_width), float(preview_height) / float(image_height));
|
||||
break;
|
||||
case SEQ_STRETCH_TO_FILL:
|
||||
transform->scale_x = float(preview_width) / float(image_width);
|
||||
transform->scale_y = float(preview_height) / float(image_height);
|
||||
break;
|
||||
case SEQ_USE_ORIGINAL_SIZE:
|
||||
transform->scale_x = 1.0f;
|
||||
transform->scale_y = 1.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_unique_name(Strip *strip, Scene *scene)
|
||||
{
|
||||
char name[STRIP_NAME_MAXSTR];
|
||||
|
||||
STRNCPY_UTF8(name, strip->name + 2);
|
||||
strip_unique_name_set(scene, &scene->ed->seqbase, strip);
|
||||
BKE_animdata_fix_paths_rename(&scene->id,
|
||||
scene->adt,
|
||||
nullptr,
|
||||
"sequence_editor.strips_all",
|
||||
name,
|
||||
strip->name + 2,
|
||||
0,
|
||||
0,
|
||||
/*verify_paths=*/false,
|
||||
/*infix_is_name=*/true);
|
||||
|
||||
if (strip->type == STRIP_TYPE_META) {
|
||||
for (Strip &strip_child : strip->seqbase) {
|
||||
ensure_unique_name(&strip_child, scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
22
blender-5.2.0/source/blender/sequencer/intern/utils.hh
Normal file
22
blender-5.2.0/source/blender/sequencer/intern/utils.hh
Normal file
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
|
||||
namespace seq {
|
||||
|
||||
bool sequencer_strip_generates_image(Strip *strip);
|
||||
void strip_open_anim_file(Scene *scene, Strip *strip, bool openfile);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
Reference in New Issue
Block a user