VSE strip thumbnails

Draw thumbnails as strip overlay. This works for movie and image strips.
To draw thumbnails, this overlay has to be enabled and strips must be
tall enough.

The thumbnails are loaded from source file using separate thread and
stored in cache.

Drawing code uses only images stored in cache, and if any is missing,
background rendering job is started. If job can not render thumbnail,
to prevent endless loop of creating job for missing image it sets
`SEQ_FLAG_SKIP_THUMBNAILS` bit of `Sequence` flag.

To prevent visual glitches during timeline panning and zooming, `View2D`
flag `V2D_IS_NAVIGATING` is implemented. If bit is set, drawing code
will look for set of evenly distributed thumbnails that should be
guaranteed to exist and also set of previously displayed thumbnails.
Due to volatile nature of cache these thumbnails can be missing anyway,
in which case no new thumbnails will be drawn for particular strip.

Cache capacity is limited to 5000 thumbnails and performs cleanup of
non visible images when limit is reached.

ref T89143

Reviewed By: ISS

Differential Revision: https://developer.blender.org/D12266
This commit is contained in:
Aditya Y Jeppu 2021-09-21 10:38:15 +02:00 committed by Richard Antalik
parent fa2c1698b0
commit 997b5fe45d
Notes: blender-bot 2023-02-21 17:59:30 +01:00
Referenced by commit ba313f8a74, Fix crash duplicating sequencer area
Referenced by issue #89143, VSE Strip Thumbnails
23 changed files with 893 additions and 13 deletions

View File

@ -247,6 +247,7 @@ class SEQUENCER_PT_sequencer_overlay(Panel):
layout.prop(overlay_settings, "show_strip_offset", text="Offsets")
layout.prop(overlay_settings, "show_fcurves", text="F-Curves")
layout.prop(overlay_settings, "show_thumbnails", text="Thumbnails")
layout.prop(overlay_settings, "show_grid", text="Grid")
layout.separator()

View File

@ -1679,6 +1679,8 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
sseq->scopes.sep_waveform_ibuf = NULL;
sseq->scopes.vector_ibuf = NULL;
sseq->scopes.histogram_ibuf = NULL;
memset(&sseq->runtime, 0x0, sizeof(sseq->runtime));
}
else if (sl->spacetype == SPACE_PROPERTIES) {
SpaceProperties *sbuts = (SpaceProperties *)sl;

View File

@ -1774,7 +1774,7 @@ static void do_versions_seq_set_cache_defaults(Editing *ed)
static bool seq_update_flags_cb(Sequence *seq, void *UNUSED(user_data))
{
seq->flag &= ~(SEQ_FLAG_UNUSED_6 | SEQ_FLAG_UNUSED_18 | SEQ_FLAG_UNUSED_19 | SEQ_FLAG_UNUSED_21);
seq->flag &= ~((1 << 6) | (1 << 18) | (1 << 19) | (1 << 21));
if (seq->type == SEQ_TYPE_SPEED) {
SpeedControlVars *s = (SpeedControlVars *)seq->effectdata;
s->flags &= ~(SEQ_SPEED_UNUSED_1);

View File

@ -1318,6 +1318,22 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
}
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
if (sl->spacetype == SPACE_SEQ) {
ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase :
&sl->regionbase;
LISTBASE_FOREACH (ARegion *, region, regionbase) {
if (region->regiontype == RGN_TYPE_WINDOW) {
region->v2d.min[1] = 4.0f;
}
}
}
}
}
}
}
/**

View File

@ -5823,6 +5823,11 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C)
icon = ICON_SEQUENCE;
break;
}
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL)) {
handle_event = B_STOPSEQ;
icon = ICON_SEQUENCE;
break;
}
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_BUILD_PROXY)) {
handle_event = B_STOPCLIP;
icon = ICON_TRACKER;

View File

@ -147,6 +147,8 @@ static void view_pan_init(bContext *C, wmOperator *op)
const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1);
vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx;
vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy;
vpd->v2d->flag |= V2D_IS_NAVIGATING;
}
/* apply transform to view (i.e. adjust 'cur' rect) */
@ -190,6 +192,8 @@ static void view_pan_apply(bContext *C, wmOperator *op)
/* Cleanup temp custom-data. */
static void view_pan_exit(wmOperator *op)
{
v2dViewPanData *vpd = op->customdata;
vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@ -305,7 +309,7 @@ static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event)
return OPERATOR_RUNNING_MODAL;
}
static void view_pan_cancel(bContext *UNUSED(C), wmOperator *op)
static void view_pan_cancel(bContext *C, wmOperator *op)
{
view_pan_exit(op);
}
@ -358,6 +362,7 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event
View2DEdgePanData *vpd = op->customdata;
if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) {
vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
}
@ -371,6 +376,8 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event
static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op)
{
v2dViewPanData *vpd = op->customdata;
vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@ -680,6 +687,8 @@ static void view_zoomdrag_init(bContext *C, wmOperator *op)
vzd->v2d = &vzd->region->v2d;
/* False by default. Interactive callbacks (ie invoke()) can set it to true. */
vzd->zoom_to_mouse_pos = false;
vzd->v2d->flag |= V2D_IS_NAVIGATING;
}
/* apply transform to view (i.e. adjust 'cur' rect) */
@ -809,7 +818,8 @@ static void view_zoomstep_apply(bContext *C, wmOperator *op)
static void view_zoomstep_exit(wmOperator *op)
{
UI_view2d_zoom_cache_reset();
v2dViewZoomData *vzd = op->customdata;
vzd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@ -1041,6 +1051,7 @@ static void view_zoomdrag_exit(bContext *C, wmOperator *op)
if (op->customdata) {
v2dViewZoomData *vzd = op->customdata;
vzd->v2d->flag &= ~V2D_IS_NAVIGATING;
if (vzd->timer) {
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
@ -1911,6 +1922,8 @@ static void scroller_activate_init(bContext *C,
vsm->scrollbar_orig = ((scrollers.vert_max + scrollers.vert_min) / 2) + region->winrct.ymin;
}
vsm->v2d->flag |= V2D_IS_NAVIGATING;
ED_region_tag_redraw_no_rebuild(region);
}
@ -1921,6 +1934,7 @@ static void scroller_activate_exit(bContext *C, wmOperator *op)
v2dScrollerMove *vsm = op->customdata;
vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE);
vsm->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_freeN(op->customdata);
op->customdata = NULL;

View File

@ -25,6 +25,7 @@
#include <string.h>
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_math.h"
#include "BLI_string_utils.h"
#include "BLI_threads.h"
@ -44,6 +45,7 @@
#include "BKE_context.h"
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "BKE_sound.h"
@ -1283,6 +1285,520 @@ static void draw_seq_fcurve_overlay(
}
}
typedef struct ThumbnailDrawJob {
SeqRenderData context;
GHash *sequences_ghash;
Scene *scene;
rctf *view_area;
float pixelx;
float pixely;
} ThumbnailDrawJob;
typedef struct ThumbDataItem {
Sequence *seq_dupli;
Scene *scene;
} ThumbDataItem;
static void thumbnail_hash_data_free(void *val)
{
ThumbDataItem *item = val;
SEQ_sequence_free(item->scene, item->seq_dupli, 0);
MEM_freeN(val);
}
static void thumbnail_freejob(void *data)
{
ThumbnailDrawJob *tj = data;
BLI_ghash_free(tj->sequences_ghash, NULL, thumbnail_hash_data_free);
MEM_freeN(tj->view_area);
MEM_freeN(tj);
}
static void thumbnail_endjob(void *data)
{
ThumbnailDrawJob *tj = data;
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, tj->scene);
}
static bool check_seq_need_thumbnails(Sequence *seq, rctf *view_area)
{
if (seq->type != SEQ_TYPE_MOVIE && seq->type != SEQ_TYPE_IMAGE) {
return false;
}
if (min_ii(seq->startdisp, seq->start) > view_area->xmax) {
return false;
}
else if (max_ii(seq->enddisp, seq->start + seq->len) < view_area->xmin) {
return false;
}
else if (seq->machine + 1.0f < view_area->ymin) {
return false;
}
else if (seq->machine > view_area->ymax) {
return false;
}
return true;
}
static void seq_get_thumb_image_dimensions(Sequence *seq,
float pixelx,
float pixely,
float *r_thumb_width,
float *r_thumb_height,
float *r_image_width,
float *r_image_height)
{
float image_width = seq->strip->stripdata->orig_width;
float image_height = seq->strip->stripdata->orig_height;
/* Fix the dimensions to be max SEQ_RENDER_THUMB_SIZE (256) for x or y. */
float aspect_ratio = (float)image_width / image_height;
if (image_width > image_height) {
image_width = SEQ_RENDER_THUMB_SIZE;
image_height = round_fl_to_int(image_width / aspect_ratio);
}
else {
image_height = SEQ_RENDER_THUMB_SIZE;
image_width = round_fl_to_int(image_height * aspect_ratio);
}
/* Calculate thumb dimensions. */
float thumb_height = (SEQ_STRIP_OFSTOP - SEQ_STRIP_OFSBOTTOM) - (20 * U.dpi_fac * pixely);
aspect_ratio = ((float)image_width) / image_height;
float thumb_h_px = thumb_height / pixely;
float thumb_width = aspect_ratio * thumb_h_px * pixelx;
if (r_thumb_height == NULL) {
*r_thumb_width = thumb_width;
return;
}
*r_thumb_height = thumb_height;
*r_image_width = image_width;
*r_image_height = image_height;
*r_thumb_width = thumb_width;
}
static float seq_thumbnail_get_start_frame(Sequence *seq, float frame_step, rctf *view_area)
{
if (seq->start > view_area->xmin && seq->start < view_area->xmax) {
return seq->start;
}
/* Drawing and caching both check to see if strip is in view area or not before calling this
* function so assuming strip/part of strip in view. */
int no_invisible_thumbs = (view_area->xmin - seq->start) / frame_step;
return ((no_invisible_thumbs - 1) * frame_step) + seq->start;
}
static void thumbnail_start_job(void *data, short *stop, short *do_update, float *progress)
{
ThumbnailDrawJob *tj = data;
float start_frame, frame_step;
GHashIterator gh_iter;
BLI_ghashIterator_init(&gh_iter, tj->sequences_ghash);
while (!BLI_ghashIterator_done(&gh_iter) & !*stop) {
Sequence *seq_orig = BLI_ghashIterator_getKey(&gh_iter);
ThumbDataItem *val = BLI_ghash_lookup(tj->sequences_ghash, seq_orig);
if (check_seq_need_thumbnails(seq_orig, tj->view_area)) {
seq_get_thumb_image_dimensions(
val->seq_dupli, tj->pixelx, tj->pixely, &frame_step, NULL, NULL, NULL);
start_frame = seq_thumbnail_get_start_frame(seq_orig, frame_step, tj->view_area);
SEQ_render_thumbnails(
&tj->context, val->seq_dupli, seq_orig, start_frame, frame_step, tj->view_area, stop);
SEQ_render_thumbnails_base_set(&tj->context, val->seq_dupli, seq_orig, tj->view_area, stop);
}
BLI_ghashIterator_step(&gh_iter);
}
UNUSED_VARS(do_update, progress);
}
static SeqRenderData sequencer_thumbnail_context_init(const bContext *C)
{
struct Main *bmain = CTX_data_main(C);
struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Scene *scene = CTX_data_scene(C);
SpaceSeq *sseq = CTX_wm_space_seq(C);
SeqRenderData context = {0};
/* Taking rectx and recty as 0 as dimensions not known here, and context is used to calculate
* hash key but not necessary as other variables of SeqRenderData are unique enough. */
SEQ_render_new_render_data(bmain, depsgraph, scene, 0, 0, sseq->render_size, false, &context);
context.view_id = BKE_scene_multiview_view_id_get(&scene->r, STEREO_LEFT_NAME);
context.use_proxies = false;
return context;
}
static GHash *sequencer_thumbnail_ghash_init(const bContext *C, View2D *v2d, Editing *ed)
{
Scene *scene = CTX_data_scene(C);
/* Set the data for thumbnail caching job. */
GHash *thumb_data_hash = BLI_ghash_ptr_new("seq_duplicates_and_origs");
LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
ThumbDataItem *val_need_update = BLI_ghash_lookup(thumb_data_hash, seq);
if (val_need_update == NULL && check_seq_need_thumbnails(seq, &v2d->cur)) {
ThumbDataItem *val = MEM_callocN(sizeof(ThumbDataItem), "Thumbnail Hash Values");
val->seq_dupli = SEQ_sequence_dupli_recursive(scene, scene, NULL, seq, 0);
val->scene = scene;
BLI_ghash_insert(thumb_data_hash, seq, val);
}
else {
if (val_need_update != NULL) {
val_need_update->seq_dupli->start = seq->start;
val_need_update->seq_dupli->startdisp = seq->startdisp;
}
}
}
return thumb_data_hash;
}
static void sequencer_thumbnail_init_job(const bContext *C, View2D *v2d, Editing *ed)
{
wmJob *wm_job;
ThumbnailDrawJob *tj = NULL;
ScrArea *area = CTX_wm_area(C);
wm_job = WM_jobs_get(CTX_wm_manager(C),
CTX_wm_window(C),
CTX_data_scene(C),
"Draw Thumbnails",
0,
WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
/* Get the thumbnail job if it exists. */
tj = WM_jobs_customdata_get(wm_job);
if (!tj) {
tj = MEM_callocN(sizeof(ThumbnailDrawJob), "Thumbnail cache job");
/* Duplicate value of v2d->cur and v2d->tot to have module separation. */
rctf *view_area = MEM_callocN(sizeof(struct rctf), "viewport area");
view_area->xmax = v2d->cur.xmax;
view_area->xmin = v2d->cur.xmin;
view_area->ymax = v2d->cur.ymax;
view_area->ymin = v2d->cur.ymin;
tj->scene = CTX_data_scene(C);
tj->view_area = view_area;
tj->context = sequencer_thumbnail_context_init(C);
tj->sequences_ghash = sequencer_thumbnail_ghash_init(C, v2d, ed);
tj->pixelx = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask);
tj->pixely = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask);
WM_jobs_customdata_set(wm_job, tj, thumbnail_freejob);
WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_SEQUENCER, NC_SCENE | ND_SEQUENCER);
WM_jobs_callbacks(wm_job, thumbnail_start_job, NULL, NULL, thumbnail_endjob);
}
if (!WM_jobs_is_running(wm_job)) {
G.is_break = false;
WM_jobs_start(CTX_wm_manager(C), wm_job);
}
else {
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, NULL);
}
ED_area_tag_redraw(area);
}
static bool sequencer_thumbnail_v2d_is_navigating(const bContext *C)
{
ARegion *region = CTX_wm_region(C);
View2D *v2d = &region->v2d;
return (v2d->flag & V2D_IS_NAVIGATING) != 0;
}
static void sequencer_thumbnail_start_job_if_necessary(const bContext *C,
Editing *ed,
View2D *v2d,
bool thumbnail_is_missing)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if (sequencer_thumbnail_v2d_is_navigating(C)) {
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, NULL);
return;
}
/* `thumbnail_is_missing` should be set to true if missing image in strip. False when normal call
* to all strips done. */
if (v2d->cur.xmax != sseq->runtime.last_thumbnail_area.xmax ||
v2d->cur.ymax != sseq->runtime.last_thumbnail_area.ymax || thumbnail_is_missing) {
/* Stop the job first as view has changed. Pointless to continue old job. */
if (v2d->cur.xmax != sseq->runtime.last_thumbnail_area.xmax ||
v2d->cur.ymax != sseq->runtime.last_thumbnail_area.ymax) {
WM_jobs_stop(CTX_wm_manager(C), NULL, thumbnail_start_job);
}
sequencer_thumbnail_init_job(C, v2d, ed);
sseq->runtime.last_thumbnail_area = v2d->cur;
}
}
void last_displayed_thumbnails_list_free(void *val)
{
BLI_gset_free(val, NULL);
}
static GSet *last_displayed_thumbnails_list_ensure(const bContext *C, Sequence *seq)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if (sseq->runtime.last_displayed_thumbnails == NULL) {
sseq->runtime.last_displayed_thumbnails = BLI_ghash_ptr_new(__func__);
}
GSet *displayed_thumbnails = BLI_ghash_lookup(sseq->runtime.last_displayed_thumbnails, seq);
if (displayed_thumbnails == NULL) {
displayed_thumbnails = BLI_gset_int_new(__func__);
BLI_ghash_insert(sseq->runtime.last_displayed_thumbnails, seq, displayed_thumbnails);
}
return displayed_thumbnails;
}
static void last_displayed_thumbnails_list_cleanup(GSet *previously_displayed,
float range_start,
float range_end)
{
GSetIterator gset_iter;
BLI_gsetIterator_init(&gset_iter, previously_displayed);
while (!BLI_gsetIterator_done(&gset_iter)) {
int frame = (float)POINTER_AS_INT(BLI_gsetIterator_getKey(&gset_iter));
BLI_gsetIterator_step(&gset_iter);
if (frame > range_start && frame < range_end) {
BLI_gset_remove(previously_displayed, POINTER_FROM_INT(frame), NULL);
}
}
}
static int sequencer_thumbnail_closest_previous_frame_get(int timeline_frame,
GSet *previously_displayed)
{
int best_diff = INT_MAX;
int best_frame = timeline_frame;
/* Previously displayed thumbnails. */
GSetIterator gset_iter;
BLI_gsetIterator_init(&gset_iter, previously_displayed);
while (!BLI_gsetIterator_done(&gset_iter)) {
int frame = POINTER_AS_INT(BLI_gsetIterator_getKey(&gset_iter));
int diff = abs(frame - timeline_frame);
if (diff < best_diff) {
best_diff = diff;
best_frame = frame;
}
BLI_gsetIterator_step(&gset_iter);
}
return best_frame;
}
static int sequencer_thumbnail_closest_guaranteed_frame_get(Sequence *seq, int timeline_frame)
{
if (timeline_frame <= seq->startdisp) {
return seq->startdisp;
}
/* Set of "guaranteed" thumbnails. */
const int frame_index = timeline_frame - seq->startdisp;
const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(seq);
const int relative_base_frame = round_fl_to_int((frame_index / (float)frame_step)) * frame_step;
const int nearest_guaranted_absolute_frame = relative_base_frame + seq->startdisp;
return nearest_guaranted_absolute_frame;
}
static ImBuf *sequencer_thumbnail_closest_from_memory(const SeqRenderData *context,
Sequence *seq,
int timeline_frame,
GSet *previously_displayed,
rcti *crop,
bool clipped)
{
int frame_previous = sequencer_thumbnail_closest_previous_frame_get(timeline_frame,
previously_displayed);
ImBuf *ibuf_previous = SEQ_get_thumbnail(context, seq, frame_previous, crop, clipped);
int frame_guaranteed = sequencer_thumbnail_closest_guaranteed_frame_get(seq, timeline_frame);
ImBuf *ibuf_guaranteed = SEQ_get_thumbnail(context, seq, frame_guaranteed, crop, clipped);
if (ibuf_previous && ibuf_guaranteed &&
abs(frame_previous - timeline_frame) < abs(frame_guaranteed - timeline_frame)) {
IMB_freeImBuf(ibuf_guaranteed);
return ibuf_previous;
}
else {
IMB_freeImBuf(ibuf_previous);
return ibuf_guaranteed;
}
if (ibuf_previous == NULL) {
return ibuf_guaranteed;
}
if (ibuf_guaranteed == NULL) {
return ibuf_previous;
}
}
static void draw_seq_strip_thumbnail(View2D *v2d,
const bContext *C,
Scene *scene,
Sequence *seq,
float y1,
float y2,
float pixelx,
float pixely)
{
bool clipped = false;
float image_height, image_width, thumb_width, thumb_height;
rcti crop;
/* If width of the strip too small ignore drawing thumbnails. */
if ((y2 - y1) / pixely <= 40 * U.dpi_fac) {
return;
}
SeqRenderData context = sequencer_thumbnail_context_init(C);
if ((seq->flag & SEQ_FLAG_SKIP_THUMBNAILS) != 0) {
return;
}
seq_get_thumb_image_dimensions(
seq, pixelx, pixely, &thumb_width, &thumb_height, &image_width, &image_height);
float thumb_y_end = y1 + thumb_height - pixely;
float cut_off = 0;
float upper_thumb_bound = (seq->endstill) ? (seq->start + seq->len) : seq->enddisp;
if (seq->type == SEQ_TYPE_IMAGE) {
upper_thumb_bound = seq->enddisp;
}
float thumb_x_start = seq_thumbnail_get_start_frame(seq, thumb_width, &v2d->cur);
float thumb_x_end;
while (thumb_x_start + thumb_width < v2d->cur.xmin) {
thumb_x_start += thumb_width;
}
/* Ignore thumbs to the left of strip. */
while (thumb_x_start + thumb_width < seq->startdisp) {
thumb_x_start += thumb_width;
}
GSet *last_displayed_thumbnails = last_displayed_thumbnails_list_ensure(C, seq);
/* Cleanup thumbnail list outside of rendered range, which is cleaned up one by one to prevent
* flickering after zooming. */
if (!sequencer_thumbnail_v2d_is_navigating(C)) {
last_displayed_thumbnails_list_cleanup(last_displayed_thumbnails, -FLT_MAX, thumb_x_start);
}
/* Start drawing. */
while (thumb_x_start < upper_thumb_bound) {
thumb_x_end = thumb_x_start + thumb_width;
clipped = false;
/* Checks to make sure that thumbs are loaded only when in view and within the confines of the
* strip. Some may not be required but better to have conditions for safety as x1 here is
* point to start caching from and not drawing. */
if (thumb_x_start > v2d->cur.xmax) {
break;
}
/* Set the clipping bound to show the left handle moving over thumbs and not shift thumbs. */
if (IN_RANGE_INCL(seq->startdisp, thumb_x_start, thumb_x_end)) {
cut_off = seq->startdisp - thumb_x_start;
clipped = true;
}
/* Clip if full thumbnail cannot be displayed. */
if (thumb_x_end > (upper_thumb_bound)) {
thumb_x_end = upper_thumb_bound;
clipped = true;
if (thumb_x_end - thumb_x_start < 1) {
break;
}
}
float zoom_x = thumb_width / image_width;
float zoom_y = thumb_height / image_height;
float cropx_min = (cut_off / pixelx) / (zoom_y / pixely);
float cropx_max = ((thumb_x_end - thumb_x_start) / pixelx) / (zoom_y / pixely);
if (cropx_max == (thumb_x_end - thumb_x_start)) {
cropx_max = cropx_max + 1;
}
BLI_rcti_init(&crop, (int)(cropx_min), (int)cropx_max, 0, (int)(image_height)-1);
int timeline_frame = round_fl_to_int(thumb_x_start);
/* Get the image. */
ImBuf *ibuf = SEQ_get_thumbnail(&context, seq, timeline_frame, &crop, clipped);
if (!ibuf) {
sequencer_thumbnail_start_job_if_necessary(C, scene->ed, v2d, true);
ibuf = sequencer_thumbnail_closest_from_memory(
&context, seq, timeline_frame, last_displayed_thumbnails, &crop, clipped);
}
/* Store recently rendered frames, so they can be reused when zooming. */
else if (!sequencer_thumbnail_v2d_is_navigating(C)) {
/* Clear images in frame range occupied bynew thumbnail. */
last_displayed_thumbnails_list_cleanup(
last_displayed_thumbnails, thumb_x_start, thumb_x_end);
/* Insert new thumbnail frame to list. */
BLI_gset_add(last_displayed_thumbnails, POINTER_FROM_INT(timeline_frame));
}
/* If there is no image still, abort. */
if (!ibuf) {
break;
}
/* Transparency on overlap. */
if (seq->flag & SEQ_OVERLAP) {
GPU_blend(GPU_BLEND_ALPHA);
if (ibuf->rect) {
unsigned char *buf = (unsigned char *)ibuf->rect;
for (int pixel = ibuf->x * ibuf->y; pixel--; buf += 4) {
buf[3] = OVERLAP_ALPHA;
}
}
else if (ibuf->rect_float) {
float *buf = (float *)ibuf->rect_float;
for (int pixel = ibuf->x * ibuf->y; pixel--; buf += ibuf->channels) {
buf[3] = (OVERLAP_ALPHA / 255.0f);
}
}
}
ED_draw_imbuf_ctx_clipping(C,
ibuf,
thumb_x_start + cut_off,
y1,
true,
thumb_x_start + cut_off,
y1,
thumb_x_end,
thumb_y_end,
zoom_x,
zoom_y);
IMB_freeImBuf(ibuf);
GPU_blend(GPU_BLEND_NONE);
cut_off = 0;
thumb_x_start += thumb_width;
}
last_displayed_thumbnails_list_cleanup(last_displayed_thumbnails, thumb_x_start, FLT_MAX);
}
/* Draw visible strips. Bounds check are already made. */
static void draw_seq_strip(const bContext *C,
SpaceSeq *sseq,
@ -1356,6 +1872,12 @@ static void draw_seq_strip(const bContext *C,
drawmeta_contents(scene, seq, x1, y1, x2, y2);
}
if ((sseq->flag & SEQ_SHOW_OVERLAY) &&
(sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_THUMBNAILS) &&
(seq->type == SEQ_TYPE_MOVIE || seq->type == SEQ_TYPE_IMAGE)) {
draw_seq_strip_thumbnail(v2d, C, scene, seq, y1, y2, pixelx, pixely);
}
if ((sseq->flag & SEQ_SHOW_OVERLAY) &&
(sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_FCURVES)) {
draw_seq_fcurve_overlay(scene, v2d, seq, x1, y1, x2, y2, pixelx);

View File

@ -579,7 +579,6 @@ static int sequencer_slip_invoke(bContext *C, wmOperator *op, const wmEvent *eve
static bool sequencer_slip_recursively(Scene *scene, SlipData *data, int offset)
{
/* Only data types supported for now. */
Editing *ed = SEQ_editing_get(scene);
bool changed = false;
/* Iterate in reverse so meta-strips are iterated after their children. */
@ -633,7 +632,10 @@ static bool sequencer_slip_recursively(Scene *scene, SlipData *data, int offset)
}
}
if (changed) {
SEQ_relations_free_imbuf(scene, &ed->seqbase, false);
for (int i = data->num_seq - 1; i >= 0; i--) {
Sequence *seq = data->seq_array[i];
SEQ_relations_invalidate_cache_preprocessed(scene, seq);
}
}
return changed;
}

View File

@ -67,6 +67,7 @@ struct ImBuf *sequencer_ibuf_get(struct Main *bmain,
int timeline_frame,
int frame_ofs,
const char *viewname);
void last_displayed_thumbnails_list_free(void *val);
/* sequencer_edit.c */
struct View2D;

View File

@ -32,6 +32,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_utildefines.h"
#include "BKE_context.h"
@ -105,6 +106,9 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID |
SEQ_TIMELINE_SHOW_FCURVES;
BLI_rctf_init(&sseq->runtime.last_thumbnail_area, 0.0f, 0.0f, 0.0f, 0.0f);
sseq->runtime.last_displayed_thumbnails = NULL;
/* Tool header. */
region = MEM_callocN(sizeof(ARegion), "tool header for sequencer");
@ -174,7 +178,7 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
region->v2d.cur = region->v2d.tot;
region->v2d.min[0] = 10.0f;
region->v2d.min[1] = 0.5f;
region->v2d.min[1] = 4.0f;
region->v2d.max[0] = MAXFRAMEF;
region->v2d.max[1] = MAXSEQ;
@ -188,6 +192,8 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
region->v2d.keeptot = 0;
region->v2d.align = V2D_ALIGN_NO_NEG_Y;
sseq->runtime.last_displayed_thumbnails = NULL;
return (SpaceLink *)sseq;
}
@ -218,6 +224,12 @@ static void sequencer_free(SpaceLink *sl)
if (scopes->histogram_ibuf) {
IMB_freeImBuf(scopes->histogram_ibuf);
}
if (sseq->runtime.last_displayed_thumbnails) {
BLI_ghash_free(
sseq->runtime.last_displayed_thumbnails, NULL, last_displayed_thumbnails_list_free);
sseq->runtime.last_displayed_thumbnails = NULL;
}
}
/* Spacetype init callback. */

View File

@ -518,7 +518,7 @@ enum {
SEQ_OVERLAP = (1 << 3),
SEQ_FILTERY = (1 << 4),
SEQ_MUTE = (1 << 5),
SEQ_FLAG_UNUSED_6 = (1 << 6), /* cleared */
SEQ_FLAG_SKIP_THUMBNAILS = (1 << 6),
SEQ_REVERSE_FRAMES = (1 << 7),
SEQ_IPO_FRAME_LOCKED = (1 << 8),
SEQ_EFFECT_NOT_LOADED = (1 << 9),
@ -724,6 +724,7 @@ enum {
SEQ_CACHE_PREFETCH_ENABLE = (1 << 10),
SEQ_CACHE_DISK_CACHE_ENABLE = (1 << 11),
SEQ_CACHE_STORE_THUMBNAIL = (1 << 12),
};
#ifdef __cplusplus

View File

@ -598,6 +598,7 @@ typedef struct SequencerTimelineOverlay {
/* SequencerTimelineOverlay.flag */
typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag {
SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1),
SEQ_TIMELINE_SHOW_THUMBNAILS = (1 << 2),
SEQ_TIMELINE_SHOW_FCURVES = (1 << 5),
SEQ_TIMELINE_ALL_WAVEFORMS = (1 << 7), /* draw all waveforms */
SEQ_TIMELINE_NO_WAVEFORMS = (1 << 8), /* draw no waveforms */
@ -607,6 +608,13 @@ typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag {
SEQ_TIMELINE_SHOW_GRID = (1 << 18),
} eSpaceSeq_SequencerTimelineOverlay_Flag;
typedef struct SpaceSeqRuntime {
/** Required for Thumbnail job start condition. */
struct rctf last_thumbnail_area;
/** Stores lists of most recently displayed thumbnails. */
struct GHash *last_displayed_thumbnails;
} SpaceSeqRuntime;
/* Sequencer */
typedef struct SpaceSeq {
SpaceLink *next, *prev;
@ -650,6 +658,7 @@ typedef struct SpaceSeq {
char multiview_eye;
char _pad2[7];
SpaceSeqRuntime runtime;
} SpaceSeq;
/* SpaceSeq.mainb */

View File

@ -132,6 +132,8 @@ enum {
V2D_PIXELOFS_X = (1 << 2),
/* apply pixel offsets on y-axis when setting view matrices */
V2D_PIXELOFS_Y = (1 << 3),
/* zoom, pan or similar action is in progress */
V2D_IS_NAVIGATING = (1 << 9),
/* view settings need to be set still... */
V2D_IS_INIT = (1 << 10),
};

View File

@ -5444,6 +5444,11 @@ static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_OFFSETS);
RNA_def_property_ui_text(prop, "Show Offsets", "Display strip in/out offsets");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
prop = RNA_def_property(srna, "show_thumbnails", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_THUMBNAILS);
RNA_def_property_ui_text(prop, "Show Thumbnails", "Show strip thumbnails");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
}
static void rna_def_space_sequencer(BlenderRNA *brna)

View File

@ -27,6 +27,8 @@
extern "C" {
#endif
#define SEQ_RENDER_THUMB_SIZE 256
struct ListBase;
struct Main;
struct Scene;
@ -67,6 +69,25 @@ struct ImBuf *SEQ_render_give_ibuf(const SeqRenderData *context,
struct ImBuf *SEQ_render_give_ibuf_direct(const SeqRenderData *context,
float timeline_frame,
struct Sequence *seq);
void SEQ_render_thumbnails(const struct SeqRenderData *context,
struct Sequence *seq,
struct Sequence *seq_orig,
float start_frame,
float frame_step,
rctf *view_area,
short *stop);
struct ImBuf *SEQ_get_thumbnail(const struct SeqRenderData *context,
struct Sequence *seq,
float timeline_frame,
rcti *crop,
bool clipped);
int SEQ_render_thumbnails_guaranteed_set_frame_step_get(const struct Sequence *seq);
void SEQ_render_thumbnails_base_set(const struct SeqRenderData *context,
struct Sequence *seq,
struct Sequence *seq_orig,
rctf *view_area,
short *stop);
void SEQ_render_init_colorspace(struct Sequence *seq);
void SEQ_render_new_render_data(struct Main *bmain,
struct Depsgraph *depsgraph,

View File

@ -34,6 +34,7 @@ struct Mask;
struct Scene;
struct Sequence;
struct StripElem;
struct SeqRenderData;
void SEQ_sort(struct ListBase *seqbase);
void SEQ_sequence_base_unique_name_recursive(struct Scene *scene,
@ -57,7 +58,6 @@ void SEQ_set_scale_to_fit(const struct Sequence *seq,
const int preview_height,
const eSeqImageFitMethod fit_method);
void SEQ_ensure_unique_name(struct Sequence *seq, struct Scene *scene);
#ifdef __cplusplus
}
#endif

View File

@ -104,6 +104,7 @@
#define DCACHE_IMAGES_PER_FILE 100
#define DCACHE_CURRENT_VERSION 2
#define COLORSPACE_NAME_MAX 64 /* XXX: defined in imb intern */
#define THUMB_CACHE_LIMIT 5000
typedef struct DiskCacheHeaderEntry {
unsigned char encoding;
@ -148,6 +149,7 @@ typedef struct SeqCache {
struct BLI_mempool *items_pool;
struct SeqCacheKey *last_key;
SeqDiskCache *disk_cache;
int thumbnail_count;
} SeqCache;
typedef struct SeqCacheItem {
@ -776,7 +778,7 @@ static float seq_cache_timeline_frame_to_frame_index(Sequence *seq, float timeli
/* With raw images, map timeline_frame to strip input media frame range. This means that static
* images or extended frame range of movies will only generate one cache entry. No special
* treatment in converting frame index to timeline_frame is needed. */
if (type == SEQ_CACHE_STORE_RAW) {
if (type == SEQ_CACHE_STORE_RAW || type == SEQ_CACHE_STORE_THUMBNAIL) {
return seq_give_frame_index(seq, timeline_frame);
}
@ -875,7 +877,7 @@ static void seq_cache_put_ex(Scene *scene, SeqCacheKey *key, ImBuf *ibuf)
if (BLI_ghash_reinsert(cache->hash, key, item, seq_cache_keyfree, seq_cache_valfree)) {
IMB_refImBuf(ibuf);
if (!key->is_temp_cache) {
if (!key->is_temp_cache || key->type != SEQ_CACHE_STORE_THUMBNAIL) {
cache->last_key = key;
}
}
@ -1161,6 +1163,7 @@ static void seq_cache_create(Main *bmain, Scene *scene)
cache->hash = BLI_ghash_new(seq_cache_hashhash, seq_cache_hashcmp, "SeqCache hash");
cache->last_key = NULL;
cache->bmain = bmain;
cache->thumbnail_count = 0;
BLI_mutex_init(&cache->iterator_mutex);
scene->ed->cache = cache;
@ -1217,7 +1220,7 @@ void seq_cache_free_temp_cache(Scene *scene, short id, int timeline_frame)
SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter);
BLI_ghashIterator_step(&gh_iter);
if (key->is_temp_cache && key->task_id == id) {
if (key->is_temp_cache && key->task_id == id && key->type != SEQ_CACHE_STORE_THUMBNAIL) {
/* Use frame_index here to avoid freeing raw images if they are used for multiple frames. */
float frame_index = seq_cache_timeline_frame_to_frame_index(
key->seq, timeline_frame, key->type);
@ -1278,6 +1281,7 @@ void SEQ_cache_cleanup(Scene *scene)
BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree);
}
cache->last_key = NULL;
cache->thumbnail_count = 0;
seq_cache_unlock(scene);
}
@ -1345,6 +1349,46 @@ void seq_cache_cleanup_sequence(Scene *scene,
seq_cache_unlock(scene);
}
void seq_cache_thumbnail_cleanup(Scene *scene, rctf *view_area_safe)
{
/* Add offsets to the left and right end to keep some frames in cache. */
view_area_safe->xmax += 200;
view_area_safe->xmin -= 200;
view_area_safe->ymin -= 1;
view_area_safe->ymax += 1;
SeqCache *cache = seq_cache_get_from_scene(scene);
if (!cache) {
return;
}
GHashIterator gh_iter;
BLI_ghashIterator_init(&gh_iter, cache->hash);
while (!BLI_ghashIterator_done(&gh_iter)) {
SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter);
BLI_ghashIterator_step(&gh_iter);
const int frame_index = key->timeline_frame - key->seq->startdisp;
const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(key->seq);
const int relative_base_frame = round_fl_to_int((frame_index / (float)frame_step)) *
frame_step;
const int nearest_guaranted_absolute_frame = relative_base_frame + key->seq->startdisp;
if (nearest_guaranted_absolute_frame == key->timeline_frame) {
continue;
}
if ((key->type & SEQ_CACHE_STORE_THUMBNAIL) &&
(key->timeline_frame > view_area_safe->xmax ||
key->timeline_frame < view_area_safe->xmin || key->seq->machine > view_area_safe->ymax ||
key->seq->machine < view_area_safe->ymin)) {
BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree);
cache->thumbnail_count--;
}
}
cache->last_key = NULL;
}
struct ImBuf *seq_cache_get(const SeqRenderData *context,
Sequence *seq,
float timeline_frame,
@ -1436,6 +1480,37 @@ bool seq_cache_put_if_possible(
return false;
}
void seq_cache_thumbnail_put(
const SeqRenderData *context, Sequence *seq, float timeline_frame, ImBuf *i, rctf *view_area)
{
Scene *scene = context->scene;
if (!scene->ed->cache) {
seq_cache_create(context->bmain, scene);
}
seq_cache_lock(scene);
SeqCache *cache = seq_cache_get_from_scene(scene);
SeqCacheKey *key = seq_cache_allocate_key(
cache, context, seq, timeline_frame, SEQ_CACHE_STORE_THUMBNAIL);
/* Prevent reinserting, it breaks cache key linking. */
if (BLI_ghash_haskey(cache->hash, key)) {
seq_cache_unlock(scene);
return;
}
/* Limit cache to THUMB_CACHE_LIMIT (5000) images stored. */
if (cache->thumbnail_count >= THUMB_CACHE_LIMIT) {
rctf view_area_safe = *view_area;
seq_cache_thumbnail_cleanup(scene, &view_area_safe);
}
seq_cache_put_ex(scene, key, i);
cache->thumbnail_count++;
seq_cache_unlock(scene);
}
void seq_cache_put(
const SeqRenderData *context, Sequence *seq, float timeline_frame, int type, ImBuf *i)
{

View File

@ -46,6 +46,11 @@ void seq_cache_put(const struct SeqRenderData *context,
float timeline_frame,
int type,
struct ImBuf *i);
void seq_cache_thumbnail_put(const struct SeqRenderData *context,
struct Sequence *seq,
float timeline_frame,
struct ImBuf *i,
rctf *view_area);
bool seq_cache_put_if_possible(const struct SeqRenderData *context,
struct Sequence *seq,
float timeline_frame,
@ -60,6 +65,7 @@ void seq_cache_cleanup_sequence(struct Scene *scene,
struct Sequence *seq_changed,
int invalidate_types,
bool force_seq_changed_range);
void seq_cache_thumbnail_cleanup(Scene *scene, rctf *view_area);
bool seq_cache_is_full(void);
#ifdef __cplusplus

View File

@ -434,6 +434,31 @@ static void sequencer_image_crop_init(const Sequence *seq,
BLI_rctf_init(r_crop, left, in->x - right, bottom, in->y - top);
}
static void sequencer_thumbnail_transform(ImBuf *in, ImBuf *out)
{
float image_scale_factor = (float)out->x / in->x;
float transform_matrix[3][3];
/* Set to keep same loc,scale,rot but change scale to thumb size limit. */
const float scale_x = 1 * image_scale_factor;
const float scale_y = 1 * image_scale_factor;
const float image_center_offs_x = (out->x - in->x) / 2;
const float image_center_offs_y = (out->y - in->y) / 2;
const float pivot[2] = {in->x / 2, in->y / 2};
loc_rot_size_to_mat3(transform_matrix,
(const float[]){image_center_offs_x, image_center_offs_y},
0,
(const float[]){scale_x, scale_y});
transform_pivot_set_m3(transform_matrix, pivot);
invert_m3(transform_matrix);
/* No crop. */
rctf source_crop;
BLI_rctf_init(&source_crop, 0, in->x, 0, in->y);
IMB_transform(in, out, transform_matrix, &source_crop, IMB_FILTER_NEAREST);
}
static void sequencer_preprocess_transform_crop(
ImBuf *in, ImBuf *out, const SeqRenderData *context, Sequence *seq, const bool is_proxy_image)
{
@ -1896,7 +1921,164 @@ ImBuf *SEQ_render_give_ibuf_direct(const SeqRenderData *context,
seq_render_state_init(&state);
ImBuf *ibuf = seq_render_strip(context, &state, seq, timeline_frame);
return ibuf;
}
/* Gets the direct image from source and scales to thumbnail size. */
static ImBuf *seq_get_uncached_thumbnail(const SeqRenderData *context,
SeqRenderState *state,
Sequence *seq,
float timeline_frame)
{
bool is_proxy_image = false;
ImBuf *ibuf = do_render_strip_uncached(context, state, seq, timeline_frame, &is_proxy_image);
if (ibuf == NULL) {
return NULL;
}
float aspect_ratio = (float)ibuf->x / ibuf->y;
int rectx, recty;
/* Calculate new dimensions - THUMB_SIZE (256) for x or y. */
if (ibuf->x > ibuf->y) {
rectx = SEQ_RENDER_THUMB_SIZE;
recty = round_fl_to_int(rectx / aspect_ratio);
}
else {
recty = SEQ_RENDER_THUMB_SIZE;
rectx = round_fl_to_int(recty * aspect_ratio);
}
/* Scale ibuf to thumbnail size. */
ImBuf *scaled_ibuf = IMB_allocImBuf(rectx, recty, 32, ibuf->rect_float ? IB_rectfloat : IB_rect);
sequencer_thumbnail_transform(ibuf, scaled_ibuf);
seq_imbuf_assign_spaces(context->scene, scaled_ibuf);
IMB_freeImBuf(ibuf);
return scaled_ibuf;
}
/* Get cached thumbnails. */
ImBuf *SEQ_get_thumbnail(
const SeqRenderData *context, Sequence *seq, float timeline_frame, rcti *crop, bool clipped)
{
ImBuf *ibuf = seq_cache_get(context, seq, roundf(timeline_frame), SEQ_CACHE_STORE_THUMBNAIL);
if (!clipped || ibuf == NULL) {
return ibuf;
}
/* Do clipping. */
ImBuf *ibuf_cropped = IMB_dupImBuf(ibuf);
if (crop->xmin < 0 || crop->ymin < 0) {
crop->xmin = 0;
crop->ymin = 0;
}
if (crop->xmax >= ibuf->x || crop->ymax >= ibuf->y) {
crop->xmax = ibuf->x - 1;
crop->ymax = ibuf->y - 1;
}
IMB_rect_crop(ibuf_cropped, crop);
IMB_freeImBuf(ibuf);
return ibuf_cropped;
}
/* Render the series of thumbnails and store in cache. */
void SEQ_render_thumbnails(const SeqRenderData *context,
Sequence *seq,
Sequence *seq_orig,
float start_frame,
float frame_step,
rctf *view_area,
short *stop)
{
SeqRenderState state;
seq_render_state_init(&state);
/* Adding the hold offset value (seq->anim_startofs) to the start frame. Position of image not
* affected, but frame loaded affected. */
start_frame = start_frame - frame_step;
float upper_thumb_bound = (seq->endstill) ? (seq->start + seq->len) : seq->enddisp;
upper_thumb_bound = (upper_thumb_bound > view_area->xmax) ? view_area->xmax + frame_step :
upper_thumb_bound;
while ((start_frame < upper_thumb_bound) & !*stop) {
ImBuf *ibuf = seq_cache_get(
context, seq_orig, round_fl_to_int(start_frame), SEQ_CACHE_STORE_THUMBNAIL);
if (ibuf) {
IMB_freeImBuf(ibuf);
start_frame += frame_step;
continue;
}
ibuf = seq_get_uncached_thumbnail(context, &state, seq, round_fl_to_int(start_frame));
if (ibuf) {
seq_cache_thumbnail_put(context, seq_orig, round_fl_to_int(start_frame), ibuf, view_area);
IMB_freeImBuf(ibuf);
seq_orig->flag &= ~SEQ_FLAG_SKIP_THUMBNAILS;
}
else {
/* Can not open source file. */
seq_orig->flag |= SEQ_FLAG_SKIP_THUMBNAILS;
return;
}
start_frame += frame_step;
}
}
/* Get frame step for equally spaced thumbnails. These thumbnails should always be present in
* memory, so they can be used when zooming.*/
int SEQ_render_thumbnails_guaranteed_set_frame_step_get(const Sequence *seq)
{
const int content_len = (seq->enddisp - seq->startdisp - seq->startstill - seq->endstill);
/* Arbitrary, but due to performance reasons should be as low as possible. */
const int thumbnails_base_set_count = min_ii(content_len / 100, 30);
if (thumbnails_base_set_count <= 0) {
return 0;
}
return content_len / thumbnails_base_set_count;
}
/* Render set of evenly spaced thumbnails that are drawn when zooming. */
void SEQ_render_thumbnails_base_set(
const SeqRenderData *context, Sequence *seq, Sequence *seq_orig, rctf *view_area, short *stop)
{
SeqRenderState state;
seq_render_state_init(&state);
int timeline_frame = seq->startdisp;
const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(seq);
while (timeline_frame < seq->enddisp && !*stop) {
ImBuf *ibuf = seq_cache_get(
context, seq_orig, roundf(timeline_frame), SEQ_CACHE_STORE_THUMBNAIL);
if (ibuf) {
IMB_freeImBuf(ibuf);
if (frame_step == 0) {
return;
}
timeline_frame += frame_step;
continue;
}
ibuf = seq_get_uncached_thumbnail(context, &state, seq, timeline_frame);
if (ibuf) {
seq_cache_thumbnail_put(context, seq_orig, timeline_frame, ibuf, view_area);
IMB_freeImBuf(ibuf);
}
if (frame_step == 0) {
return;
}
timeline_frame += frame_step;
}
}
/** \} */

View File

@ -957,6 +957,8 @@ static bool seq_read_lib_cb(Sequence *seq, void *user_data)
BLI_listbase_clear(&seq->anims);
SEQ_modifier_blend_read_lib(reader, sce, &seq->modifiers);
seq->flag &= ~SEQ_FLAG_SKIP_THUMBNAILS;
return true;
}

View File

@ -42,6 +42,7 @@
#include "SEQ_edit.h"
#include "SEQ_iterator.h"
#include "SEQ_relations.h"
#include "SEQ_render.h"
#include "SEQ_select.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"

View File

@ -797,6 +797,7 @@ enum {
WM_JOB_TYPE_QUADRIFLOW_REMESH,
WM_JOB_TYPE_TRACE_IMAGE,
WM_JOB_TYPE_LINEART,
WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL,
/* add as needed, bake, seq proxy build
* if having hard coded values is a problem */
};

View File

@ -230,7 +230,7 @@ bool WM_jobs_test(const wmWindowManager *wm, const void *owner, int job_type)
LISTBASE_FOREACH (wmJob *, wm_job, &wm->jobs) {
if (wm_job->owner == owner) {
if (ELEM(job_type, WM_JOB_TYPE_ANY, wm_job->job_type)) {
if (wm_job->running || wm_job->suspended) {
if ((wm_job->flag & WM_JOB_PROGRESS) && (wm_job->running || wm_job->suspended)) {
return true;
}
}