Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenkernel/intern/screen.c2
-rw-r--r--source/blender/blenloader/intern/versioning_280.c2
-rw-r--r--source/blender/blenloader/intern/versioning_300.c16
-rw-r--r--source/blender/editors/interface/interface_templates.c5
-rw-r--r--source/blender/editors/interface/view2d_ops.c18
-rw-r--r--source/blender/editors/space_sequencer/sequencer_draw.c522
-rw-r--r--source/blender/editors/space_sequencer/sequencer_edit.c6
-rw-r--r--source/blender/editors/space_sequencer/sequencer_intern.h1
-rw-r--r--source/blender/editors/space_sequencer/space_sequencer.c14
-rw-r--r--source/blender/makesdna/DNA_sequence_types.h3
-rw-r--r--source/blender/makesdna/DNA_space_types.h9
-rw-r--r--source/blender/makesdna/DNA_view2d_types.h2
-rw-r--r--source/blender/makesrna/intern/rna_space.c5
-rw-r--r--source/blender/sequencer/SEQ_render.h21
-rw-r--r--source/blender/sequencer/SEQ_utils.h2
-rw-r--r--source/blender/sequencer/intern/image_cache.c81
-rw-r--r--source/blender/sequencer/intern/image_cache.h6
-rw-r--r--source/blender/sequencer/intern/render.c184
-rw-r--r--source/blender/sequencer/intern/sequencer.c2
-rw-r--r--source/blender/sequencer/intern/utils.c1
-rw-r--r--source/blender/windowmanager/WM_api.h1
-rw-r--r--source/blender/windowmanager/intern/wm_jobs.c2
22 files changed, 892 insertions, 13 deletions
diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c
index 73e25a22225..4c38536b662 100644
--- a/source/blender/blenkernel/intern/screen.c
+++ b/source/blender/blenkernel/intern/screen.c
@@ -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;
diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c
index f667361d166..9f2c090c242 100644
--- a/source/blender/blenloader/intern/versioning_280.c
+++ b/source/blender/blenloader/intern/versioning_280.c
@@ -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);
diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c
index d0fa6c282f4..1a19bbbee5c 100644
--- a/source/blender/blenloader/intern/versioning_300.c
+++ b/source/blender/blenloader/intern/versioning_300.c
@@ -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;
+ }
+ }
+ }
+ }
+ }
+ }
}
/**
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 0c9eb20af19..320371ad9ea 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -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;
diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c
index 1fd1b6c984d..8fab34d486c 100644
--- a/source/blender/editors/interface/view2d_ops.c
+++ b/source/blender/editors/interface/view2d_ops.c
@@ -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;
diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c
index e063e12a9a8..e9e29ae3677 100644
--- a/source/blender/editors/space_sequencer/sequencer_draw.c
+++ b/source/blender/editors/space_sequencer/sequencer_draw.c
@@ -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,
@@ -1357,6 +1873,12 @@ static void draw_seq_strip(const bContext *C,
}
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);
}
diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c
index b95b7fa0620..9f21fc0676c 100644
--- a/source/blender/editors/space_sequencer/sequencer_edit.c
+++ b/source/blender/editors/space_sequencer/sequencer_edit.c
@@ -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;
}
diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h
index 767ac76efe6..5b5c381509f 100644
--- a/source/blender/editors/space_sequencer/sequencer_intern.h
+++ b/source/blender/editors/space_sequencer/sequencer_intern.h
@@ -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;
diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c
index cb5fe6f0ef3..0f23c61a2ca 100644
--- a/source/blender/editors/space_sequencer/space_sequencer.c
+++ b/source/blender/editors/space_sequencer/space_sequencer.c
@@ -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. */
diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h
index 782b4ac34aa..25330acd486 100644
--- a/source/blender/makesdna/DNA_sequence_types.h
+++ b/source/blender/makesdna/DNA_sequence_types.h
@@ -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
diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h
index 84dc58345e3..e849039fa93 100644
--- a/source/blender/makesdna/DNA_space_types.h
+++ b/source/blender/makesdna/DNA_space_types.h
@@ -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 */
diff --git a/source/blender/makesdna/DNA_view2d_types.h b/source/blender/makesdna/DNA_view2d_types.h
index c385ac04bd3..f8166305fd9 100644
--- a/source/blender/makesdna/DNA_view2d_types.h
+++ b/source/blender/makesdna/DNA_view2d_types.h
@@ -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),
};
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 70fe2dbc75a..a05cef7a1cd 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -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)
diff --git a/source/blender/sequencer/SEQ_render.h b/source/blender/sequencer/SEQ_render.h
index c138daf1318..50280db55ff 100644
--- a/source/blender/sequencer/SEQ_render.h
+++ b/source/blender/sequencer/SEQ_render.h
@@ -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,
diff --git a/source/blender/sequencer/SEQ_utils.h b/source/blender/sequencer/SEQ_utils.h
index 09de7bc67e9..d30a1b2d7ae 100644
--- a/source/blender/sequencer/SEQ_utils.h
+++ b/source/blender/sequencer/SEQ_utils.h
@@ -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
diff --git a/source/blender/sequencer/intern/image_cache.c b/source/blender/sequencer/intern/image_cache.c
index 86bd840ce31..86c198075e9 100644
--- a/source/blender/sequencer/intern/image_cache.c
+++ b/source/blender/sequencer/intern/image_cache.c
@@ -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)
{
diff --git a/source/blender/sequencer/intern/image_cache.h b/source/blender/sequencer/intern/image_cache.h
index 63c559caee9..60031311985 100644
--- a/source/blender/sequencer/intern/image_cache.h
+++ b/source/blender/sequencer/intern/image_cache.h
@@ -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
diff --git a/source/blender/sequencer/intern/render.c b/source/blender/sequencer/intern/render.c
index e42a3f4d6e9..b2642228c91 100644
--- a/source/blender/sequencer/intern/render.c
+++ b/source/blender/sequencer/intern/render.c
@@ -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;
+ }
+}
+
/** \} */
diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c
index d3f8411cf0a..382bd51aae1 100644
--- a/source/blender/sequencer/intern/sequencer.c
+++ b/source/blender/sequencer/intern/sequencer.c
@@ -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;
}
diff --git a/source/blender/sequencer/intern/utils.c b/source/blender/sequencer/intern/utils.c
index 1d3e7e4a223..8421aab5217 100644
--- a/source/blender/sequencer/intern/utils.c
+++ b/source/blender/sequencer/intern/utils.c
@@ -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"
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 4fe1530628b..6794b1f4091 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -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 */
};
diff --git a/source/blender/windowmanager/intern/wm_jobs.c b/source/blender/windowmanager/intern/wm_jobs.c
index 6494c337c10..2604105896d 100644
--- a/source/blender/windowmanager/intern/wm_jobs.c
+++ b/source/blender/windowmanager/intern/wm_jobs.c
@@ -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;
}
}