663 lines
23 KiB
C++
663 lines
23 KiB
C++
/* SPDX-FileCopyrightText: 2021 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup spseq
|
|
*/
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_math_vector.hh"
|
|
#include "BLI_math_vector_types.hh"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_scene.hh"
|
|
|
|
#include "IMB_imbuf.hh"
|
|
#include "IMB_imbuf_types.hh"
|
|
|
|
#include "ED_screen.hh"
|
|
|
|
#include "BIF_glutil.hh"
|
|
|
|
#include "SEQ_channels.hh"
|
|
#include "SEQ_relations.hh"
|
|
#include "SEQ_render.hh"
|
|
#include "SEQ_sequencer.hh"
|
|
#include "SEQ_time.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
/* Own include. */
|
|
#include "sequencer_intern.hh"
|
|
|
|
using namespace blender;
|
|
|
|
struct ThumbnailDrawJob {
|
|
SeqRenderData context;
|
|
GHash *sequences_ghash;
|
|
Scene *scene;
|
|
const rctf *view_area;
|
|
float pixelx;
|
|
float pixely;
|
|
float thumb_height;
|
|
};
|
|
|
|
struct ThumbDataItem {
|
|
Sequence *seq_dupli;
|
|
Scene *scene;
|
|
};
|
|
|
|
static void thumbnail_hash_data_free(void *val)
|
|
{
|
|
ThumbDataItem *item = static_cast<ThumbDataItem *>(val);
|
|
SEQ_sequence_free(item->scene, item->seq_dupli);
|
|
MEM_freeN(val);
|
|
}
|
|
|
|
static void thumbnail_freejob(void *data)
|
|
{
|
|
ThumbnailDrawJob *tj = static_cast<ThumbnailDrawJob *>(data);
|
|
BLI_ghash_free(tj->sequences_ghash, nullptr, thumbnail_hash_data_free);
|
|
MEM_freeN((void *)tj->view_area);
|
|
MEM_freeN(tj);
|
|
}
|
|
|
|
static void thumbnail_endjob(void *data)
|
|
{
|
|
ThumbnailDrawJob *tj = static_cast<ThumbnailDrawJob *>(data);
|
|
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, tj->scene);
|
|
}
|
|
|
|
static bool check_seq_need_thumbnails(const Scene *scene, Sequence *seq, const rctf *view_area)
|
|
{
|
|
if (!ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) {
|
|
return false;
|
|
}
|
|
if (min_ii(SEQ_time_left_handle_frame_get(scene, seq), SEQ_time_start_frame_get(seq)) >
|
|
view_area->xmax)
|
|
{
|
|
return false;
|
|
}
|
|
if (max_ii(SEQ_time_right_handle_frame_get(scene, seq),
|
|
SEQ_time_content_end_frame_get(scene, seq)) < view_area->xmin)
|
|
{
|
|
return false;
|
|
}
|
|
if (seq->machine + 1.0f < view_area->ymin) {
|
|
return false;
|
|
}
|
|
if (seq->machine > view_area->ymax) {
|
|
return false;
|
|
}
|
|
|
|
/* Handle is moved, but not for this strip. */
|
|
if ((G.moving & G_TRANSFORM_SEQ) != 0 && (seq->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) == 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void seq_get_thumb_image_dimensions(Sequence *seq,
|
|
float pixelx,
|
|
float pixely,
|
|
float *r_thumb_width,
|
|
float 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 = 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. */
|
|
aspect_ratio = image_width / image_height;
|
|
float thumb_h_px = thumb_height / pixely;
|
|
float thumb_width = aspect_ratio * thumb_h_px * pixelx;
|
|
|
|
*r_thumb_width = thumb_width;
|
|
if (r_image_width && r_image_height) {
|
|
*r_image_width = image_width;
|
|
*r_image_height = image_height;
|
|
}
|
|
}
|
|
|
|
static void thumbnail_start_job(void *data, wmJobWorkerStatus *worker_status)
|
|
{
|
|
ThumbnailDrawJob *tj = static_cast<ThumbnailDrawJob *>(data);
|
|
const Scene *scene = tj->scene;
|
|
float frame_step;
|
|
|
|
GHashIterator gh_iter;
|
|
|
|
/* First pass: render visible images. */
|
|
BLI_ghashIterator_init(&gh_iter, tj->sequences_ghash);
|
|
while (!BLI_ghashIterator_done(&gh_iter) && !worker_status->stop) {
|
|
Sequence *seq_orig = static_cast<Sequence *>(BLI_ghashIterator_getKey(&gh_iter));
|
|
ThumbDataItem *val = static_cast<ThumbDataItem *>(
|
|
BLI_ghash_lookup(tj->sequences_ghash, seq_orig));
|
|
|
|
if (check_seq_need_thumbnails(scene, seq_orig, tj->view_area)) {
|
|
seq_get_thumb_image_dimensions(
|
|
val->seq_dupli, tj->pixelx, tj->pixely, &frame_step, tj->thumb_height, nullptr, nullptr);
|
|
SEQ_render_thumbnails(
|
|
&tj->context, val->seq_dupli, seq_orig, frame_step, tj->view_area, &worker_status->stop);
|
|
SEQ_relations_sequence_free_anim(val->seq_dupli);
|
|
}
|
|
BLI_ghashIterator_step(&gh_iter);
|
|
}
|
|
|
|
/* Second pass: render "guaranteed" set of images. */
|
|
BLI_ghashIterator_init(&gh_iter, tj->sequences_ghash);
|
|
while (!BLI_ghashIterator_done(&gh_iter) && !worker_status->stop) {
|
|
Sequence *seq_orig = static_cast<Sequence *>(BLI_ghashIterator_getKey(&gh_iter));
|
|
ThumbDataItem *val = static_cast<ThumbDataItem *>(
|
|
BLI_ghash_lookup(tj->sequences_ghash, seq_orig));
|
|
|
|
if (check_seq_need_thumbnails(scene, seq_orig, tj->view_area)) {
|
|
seq_get_thumb_image_dimensions(
|
|
val->seq_dupli, tj->pixelx, tj->pixely, &frame_step, tj->thumb_height, nullptr, nullptr);
|
|
SEQ_render_thumbnails_base_set(
|
|
&tj->context, val->seq_dupli, seq_orig, tj->view_area, &worker_status->stop);
|
|
SEQ_relations_sequence_free_anim(val->seq_dupli);
|
|
}
|
|
BLI_ghashIterator_step(&gh_iter);
|
|
}
|
|
}
|
|
|
|
static SeqRenderData sequencer_thumbnail_context_init(const bContext *C)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
SeqRenderData context = {nullptr};
|
|
|
|
/* 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;
|
|
context.ignore_missing_media = true;
|
|
context.scene = scene;
|
|
|
|
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 = static_cast<ThumbDataItem *>(
|
|
BLI_ghash_lookup(thumb_data_hash, seq));
|
|
if (val_need_update == nullptr && check_seq_need_thumbnails(scene, seq, &v2d->cur)) {
|
|
ThumbDataItem *val = MEM_cnew<ThumbDataItem>("Thumbnail Hash Values");
|
|
val->seq_dupli = SEQ_sequence_dupli_recursive(scene, scene, nullptr, seq, 0);
|
|
val->scene = scene;
|
|
BLI_ghash_insert(thumb_data_hash, seq, val);
|
|
}
|
|
else {
|
|
if (val_need_update != nullptr) {
|
|
val_need_update->seq_dupli->start = seq->start;
|
|
val_need_update->seq_dupli->startdisp = SEQ_time_left_handle_frame_get(scene, seq);
|
|
}
|
|
}
|
|
}
|
|
|
|
return thumb_data_hash;
|
|
}
|
|
|
|
static void sequencer_thumbnail_init_job(const bContext *C,
|
|
View2D *v2d,
|
|
Editing *ed,
|
|
float thumb_height)
|
|
{
|
|
wmJob *wm_job;
|
|
ThumbnailDrawJob *tj = nullptr;
|
|
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",
|
|
eWM_JobFlag(0),
|
|
WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
|
|
|
|
/* Get the thumbnail job if it exists. */
|
|
tj = static_cast<ThumbnailDrawJob *>(WM_jobs_customdata_get(wm_job));
|
|
if (!tj) {
|
|
tj = MEM_cnew<ThumbnailDrawJob>("Thumbnail cache job");
|
|
|
|
/* Duplicate value of v2d->cur and v2d->tot to have module separation. */
|
|
rctf *view_area = MEM_cnew<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);
|
|
tj->thumb_height = thumb_height;
|
|
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, nullptr, nullptr, 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, nullptr);
|
|
}
|
|
|
|
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, float thumb_height)
|
|
{
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
|
|
if (sequencer_thumbnail_v2d_is_navigating(C)) {
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, nullptr);
|
|
return;
|
|
}
|
|
|
|
/* During rendering, cache is wiped, it doesn't make sense to render thumbnails. */
|
|
if (G.is_rendering) {
|
|
return;
|
|
}
|
|
|
|
/* Job start requested, but over area which has been processed. Unless `thumbnail_is_missing` is
|
|
* true, ignore this request as all images are in view. */
|
|
if (v2d->cur.xmax == sseq->runtime->last_thumbnail_area.xmax &&
|
|
v2d->cur.ymax == sseq->runtime->last_thumbnail_area.ymax && !thumbnail_is_missing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* 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_type(CTX_wm_manager(C), nullptr, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
|
|
}
|
|
|
|
sequencer_thumbnail_init_job(C, v2d, ed, thumb_height);
|
|
sseq->runtime->last_thumbnail_area = v2d->cur;
|
|
}
|
|
|
|
void last_displayed_thumbnails_list_free(void *val)
|
|
{
|
|
BLI_gset_free(static_cast<GSet *>(val), nullptr);
|
|
}
|
|
|
|
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 == nullptr) {
|
|
sseq->runtime->last_displayed_thumbnails = BLI_ghash_ptr_new(__func__);
|
|
}
|
|
|
|
GSet *displayed_thumbnails = static_cast<GSet *>(
|
|
BLI_ghash_lookup(sseq->runtime->last_displayed_thumbnails, seq));
|
|
if (displayed_thumbnails == nullptr) {
|
|
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), nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(Scene *scene,
|
|
Sequence *seq,
|
|
int timeline_frame)
|
|
{
|
|
if (timeline_frame <= SEQ_time_left_handle_frame_get(scene, seq)) {
|
|
return SEQ_time_left_handle_frame_get(scene, seq);
|
|
}
|
|
|
|
/* Set of "guaranteed" thumbnails. */
|
|
const int frame_index = timeline_frame - SEQ_time_left_handle_frame_get(scene, seq);
|
|
const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(scene, 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_time_left_handle_frame_get(scene, seq);
|
|
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(
|
|
context->scene, seq, timeline_frame);
|
|
ImBuf *ibuf_guaranteed = SEQ_get_thumbnail(context, seq, frame_guaranteed, crop, clipped);
|
|
|
|
ImBuf *closest_in_memory = nullptr;
|
|
|
|
if (ibuf_previous && ibuf_guaranteed) {
|
|
if (abs(frame_previous - timeline_frame) < abs(frame_guaranteed - timeline_frame)) {
|
|
IMB_freeImBuf(ibuf_guaranteed);
|
|
closest_in_memory = ibuf_previous;
|
|
}
|
|
else {
|
|
IMB_freeImBuf(ibuf_previous);
|
|
closest_in_memory = ibuf_guaranteed;
|
|
}
|
|
}
|
|
|
|
if (ibuf_previous == nullptr) {
|
|
closest_in_memory = ibuf_guaranteed;
|
|
}
|
|
|
|
if (ibuf_guaranteed == nullptr) {
|
|
closest_in_memory = ibuf_previous;
|
|
}
|
|
|
|
return closest_in_memory;
|
|
}
|
|
|
|
static void make_ibuf_semitransparent(ImBuf *ibuf)
|
|
{
|
|
const uchar alpha = 120;
|
|
if (ibuf->byte_buffer.data) {
|
|
uchar *buf = ibuf->byte_buffer.data;
|
|
for (int pixel = ibuf->x * ibuf->y; pixel--; buf += 4) {
|
|
buf[3] = alpha;
|
|
}
|
|
}
|
|
if (ibuf->float_buffer.data) {
|
|
float *buf = ibuf->float_buffer.data;
|
|
for (int pixel = ibuf->x * ibuf->y; pixel--; buf += ibuf->channels) {
|
|
buf[3] = (alpha / 255.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Signed distance to rounded box, centered at origin.
|
|
* Reference: https://iquilezles.org/articles/distfunctions2d/ */
|
|
static float sdf_rounded_box(float2 pos, float2 size, float radius)
|
|
{
|
|
float2 q = math::abs(pos) - size + radius;
|
|
return math::min(math::max(q.x, q.y), 0.0f) + math::length(math::max(q, float2(0.0f))) - radius;
|
|
}
|
|
|
|
static void eval_round_corners_pixel(
|
|
ImBuf *ibuf, float radius, float2 bmin, float2 bmax, float2 pos)
|
|
{
|
|
int ix = int(pos.x);
|
|
int iy = int(pos.y);
|
|
if (ix < 0 || ix >= ibuf->x || iy < 0 || iy >= ibuf->y) {
|
|
return;
|
|
}
|
|
float2 center = (bmin + bmax) * 0.5f;
|
|
float2 size = (bmax - bmin) * 0.5f;
|
|
float d = sdf_rounded_box(pos - center, size, radius);
|
|
if (d <= 0.0f) {
|
|
return;
|
|
}
|
|
/* Outside of rounded rectangle, set pixel alpha to zero. */
|
|
if (ibuf->byte_buffer.data != nullptr) {
|
|
int64_t ofs = (int64_t(iy) * ibuf->x + ix) * 4;
|
|
ibuf->byte_buffer.data[ofs + 3] = 0;
|
|
}
|
|
if (ibuf->float_buffer.data != nullptr) {
|
|
int64_t ofs = (int64_t(iy) * ibuf->x + ix) * ibuf->channels;
|
|
ibuf->float_buffer.data[ofs + 3] = 0.0f;
|
|
}
|
|
}
|
|
|
|
static void make_ibuf_round_corners(ImBuf *ibuf, float radius, float2 bmin, float2 bmax)
|
|
{
|
|
/* Evaluate radius*radius squares at corners. */
|
|
for (int by = 0; by < radius; by++) {
|
|
for (int bx = 0; bx < radius; bx++) {
|
|
eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmin.x + bx, bmin.y + by));
|
|
eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmax.x - bx, bmin.y + by));
|
|
eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmin.x + bx, bmax.y - by));
|
|
eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmax.x - bx, bmax.y - by));
|
|
}
|
|
}
|
|
}
|
|
|
|
void draw_seq_strip_thumbnail(View2D *v2d,
|
|
const bContext *C,
|
|
Scene *scene,
|
|
Sequence *seq,
|
|
float y1,
|
|
float y2,
|
|
float y_top,
|
|
float pixelx,
|
|
float pixely,
|
|
float round_radius)
|
|
{
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
if ((sseq->flag & SEQ_SHOW_OVERLAY) == 0 ||
|
|
(sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_THUMBNAILS) == 0 ||
|
|
!ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
StripElem *se = seq->strip->stripdata;
|
|
if (se->orig_height == 0 || se->orig_width == 0) {
|
|
return;
|
|
}
|
|
|
|
/* If width of the strip too small ignore drawing thumbnails. */
|
|
if ((y2 - y1) / pixely <= 20 * UI_SCALE_FAC) {
|
|
return;
|
|
}
|
|
|
|
SeqRenderData context = sequencer_thumbnail_context_init(C);
|
|
|
|
if ((seq->flag & SEQ_FLAG_SKIP_THUMBNAILS) != 0) {
|
|
return;
|
|
}
|
|
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
ListBase *channels = ed ? SEQ_channels_displayed_get(ed) : nullptr;
|
|
|
|
float thumb_width, image_width, image_height;
|
|
const float thumb_height = y2 - y1;
|
|
seq_get_thumb_image_dimensions(
|
|
seq, pixelx, pixely, &thumb_width, thumb_height, &image_width, &image_height);
|
|
|
|
const float zoom_y = thumb_height / image_height;
|
|
const float crop_x_multiplier = 1.0f / pixelx / (zoom_y / pixely);
|
|
|
|
float thumb_y_end = y1 + thumb_height;
|
|
|
|
const float seq_left_handle = SEQ_time_left_handle_frame_get(scene, seq);
|
|
const float seq_right_handle = SEQ_time_right_handle_frame_get(scene, seq);
|
|
|
|
float upper_thumb_bound = SEQ_time_has_right_still_frames(scene, seq) ?
|
|
SEQ_time_content_end_frame_get(scene, seq) :
|
|
seq_right_handle;
|
|
if (seq->type == SEQ_TYPE_IMAGE) {
|
|
upper_thumb_bound = seq_right_handle;
|
|
}
|
|
|
|
float timeline_frame = SEQ_render_thumbnail_first_frame_get(scene, seq, thumb_width, &v2d->cur);
|
|
|
|
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, timeline_frame);
|
|
}
|
|
|
|
/* Start drawing. */
|
|
while (timeline_frame < upper_thumb_bound) {
|
|
float thumb_x_end = timeline_frame + thumb_width;
|
|
bool 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 (timeline_frame > v2d->cur.xmax) {
|
|
break;
|
|
}
|
|
|
|
/* Set the clipping bound to show the left handle moving over thumbs and not shift thumbs. */
|
|
float cut_off = 0.0f;
|
|
if (seq_left_handle > timeline_frame && seq_left_handle < thumb_x_end) {
|
|
cut_off = seq_left_handle - timeline_frame;
|
|
clipped = true;
|
|
}
|
|
|
|
/* Clip if full thumbnail cannot be displayed. */
|
|
if (thumb_x_end > upper_thumb_bound) {
|
|
thumb_x_end = upper_thumb_bound;
|
|
clipped = true;
|
|
}
|
|
|
|
int cropx_min = int(cut_off * crop_x_multiplier);
|
|
int cropx_max = int((thumb_x_end - timeline_frame) * crop_x_multiplier);
|
|
if (cropx_max < 1) {
|
|
break;
|
|
}
|
|
rcti crop;
|
|
BLI_rcti_init(&crop, cropx_min, cropx_max - 1, 0, int(image_height) - 1);
|
|
|
|
/* 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, thumb_height);
|
|
|
|
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 by new thumbnail. */
|
|
last_displayed_thumbnails_list_cleanup(
|
|
last_displayed_thumbnails, timeline_frame, 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 mute. */
|
|
bool muted = channels ? SEQ_render_is_muted(channels, seq) : false;
|
|
if (muted) {
|
|
make_ibuf_semitransparent(ibuf);
|
|
}
|
|
|
|
/* If thumbnail start or end falls within strip corner rounding area,
|
|
* we need to manually set thumbnail pixels that are outside of rounded
|
|
* rectangle to be transparent. Ideally this would be done on the GPU
|
|
* while drawing, but since rendering is done through OCIO shaders that
|
|
* is hard to do. */
|
|
const float xpos = timeline_frame + cut_off;
|
|
|
|
const float zoom_x = (thumb_x_end - xpos) / ibuf->x;
|
|
|
|
const float radius = ibuf->y * round_radius * pixely / (y2 - y1);
|
|
if (radius > 0.9f) {
|
|
if (xpos < seq_left_handle + round_radius * pixelx ||
|
|
thumb_x_end > seq_right_handle - round_radius * pixelx)
|
|
{
|
|
/* Work on a copy of the thumbnail image, so that corner rounding
|
|
* is not stored into thumbnail cache. */
|
|
ImBuf *copy = IMB_dupImBuf(ibuf);
|
|
IMB_freeImBuf(ibuf);
|
|
ibuf = copy;
|
|
|
|
float round_y_top = ibuf->y * (y_top - y1) / (y2 - y1);
|
|
make_ibuf_round_corners(ibuf,
|
|
radius,
|
|
float2((seq_left_handle - xpos) / zoom_x, 0),
|
|
float2((seq_right_handle - xpos) / zoom_x, round_y_top));
|
|
}
|
|
}
|
|
|
|
ED_draw_imbuf_ctx_clipping(
|
|
C, ibuf, xpos, y1, true, xpos, y1, thumb_x_end, thumb_y_end, zoom_x, zoom_y);
|
|
IMB_freeImBuf(ibuf);
|
|
timeline_frame = SEQ_render_thumbnail_next_frame_get(scene, seq, timeline_frame, thumb_width);
|
|
}
|
|
last_displayed_thumbnails_list_cleanup(last_displayed_thumbnails, timeline_frame, FLT_MAX);
|
|
}
|