diff options
23 files changed, 893 insertions, 13 deletions
diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 806b6100cc8..543164f25fc 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -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() 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 = ®ion->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; } } |