Blender V5.0
sequencer_thumbnails.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021-2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_context.hh"
10
11#include "BLI_array.hh"
12
13#include "IMB_imbuf.hh"
14
15#include "DNA_sequence_types.h"
16#include "DNA_space_types.h"
17#include "DNA_userdef_types.h"
18
19#include "GPU_batch.hh"
20#include "GPU_batch_presets.hh"
21#include "GPU_matrix.hh"
22#include "GPU_shader_shared.hh"
23#include "GPU_texture.hh"
24#include "GPU_uniform_buffer.hh"
25
27
28#include "SEQ_render.hh"
30
31#include "WM_api.hh"
32
33#include "sequencer_intern.hh"
35
36namespace blender::ed::vse {
37
38/* Information for one thumbnail picture in the timeline. Note that a single
39 * strip could have multiple thumbnails. */
42 /* Strip coordinates in timeline space (X: frames, Y: channels). */
44 /* Thumbnail coordinates in timeline space. */
45 float x1, x2, y1, y2;
46 /* Horizontal cropping of thumbnail image, in pixels. Often a thumbnail
47 * does not have to be cropped, in which case these are 0 and ibuf->x-1. */
50};
51
52static float thumb_calc_first_timeline_frame(const Strip *strip,
53 float left_handle,
54 float frame_step,
55 const rctf *view_area)
56{
57 int first_drawable_frame = max_iii(left_handle, strip->start, view_area->xmin);
58
59 /* First frame should correspond to handle position. */
60 if (first_drawable_frame == left_handle) {
61 return left_handle;
62 }
63
64 float aligned_frame_offset = int((first_drawable_frame - strip->start) / frame_step) *
65 frame_step;
66 return strip->start + aligned_frame_offset;
67}
68
69static float thumb_calc_next_timeline_frame(const Strip *strip,
70 float left_handle,
71 float last_frame,
72 float frame_step)
73{
74 float next_frame = last_frame + frame_step;
75
76 /* If handle position was displayed, align next frame with `strip->start`. */
77 if (last_frame == left_handle) {
78 next_frame = strip->start + (int((last_frame - strip->start) / frame_step) + 1) * frame_step;
79 }
80
81 return next_frame;
82}
83
84static void strip_get_thumb_image_dimensions(const Strip *strip,
85 float pixelx,
86 float pixely,
87 float *r_thumb_width,
88 float thumb_height,
89 float *r_image_width,
90 float *r_image_height)
91{
92 float image_width = strip->data->stripdata->orig_width;
93 float image_height = strip->data->stripdata->orig_height;
94
95 /* Fix the dimensions to be max SEQ_THUMB_SIZE for x or y. */
96 float aspect_ratio = image_width / image_height;
100 }
101 else {
103 image_width = round_fl_to_int(image_height * aspect_ratio);
104 }
105
106 /* Calculate thumb dimensions. */
107 aspect_ratio = image_width / image_height;
108 float thumb_h_px = thumb_height / pixely;
109 float thumb_width = aspect_ratio * thumb_h_px * pixelx;
110
111 *r_thumb_width = thumb_width;
112 *r_image_width = image_width;
113 *r_image_height = image_height;
114}
115
116static void get_seq_strip_thumbnails(const View2D *v2d,
117 const bContext *C,
118 Scene *scene,
119 const StripDrawContext &strip,
120 float pixelx,
121 float pixely,
122 bool is_muted,
123 Vector<SeqThumbInfo> &r_thumbs)
124{
125 if (!seq::strip_can_have_thumbnail(scene, strip.strip)) {
126 return;
127 }
128
129 /* No thumbnails if height of the strip is too small. */
130 const float thumb_height = strip.strip_content_top - strip.bottom;
131 if (thumb_height / pixely <= 20 * UI_SCALE_FAC) {
132 return;
133 }
134
135 float thumb_width, image_width, image_height;
137 strip.strip, pixelx, pixely, &thumb_width, thumb_height, &image_width, &image_height);
138
139 const float crop_x_multiplier = 1.0f / pixelx / (thumb_height / image_height / pixely);
140
141 float upper_thumb_bound = min_ff(strip.right_handle, strip.content_end);
142 if (strip.strip->type == STRIP_TYPE_IMAGE) {
143 upper_thumb_bound = strip.right_handle;
144 }
145
146 float timeline_frame = thumb_calc_first_timeline_frame(
147 strip.strip, strip.left_handle, thumb_width, &v2d->cur);
148
149 /* Start going over the strip length. */
150 while (timeline_frame < upper_thumb_bound) {
151 float thumb_x_end = timeline_frame + thumb_width;
152 bool clipped = false;
153
154 /* Reached end of view, no more thumbnails needed. */
155 if (timeline_frame > v2d->cur.xmax) {
156 break;
157 }
158
159 /* Set the clipping bound to show the left handle moving over thumbs and not shift thumbs. */
160 float cut_off = 0.0f;
161 if (strip.left_handle > timeline_frame && strip.left_handle < thumb_x_end) {
162 cut_off = strip.left_handle - timeline_frame;
163 clipped = true;
164 }
165
166 /* Clip if full thumbnail cannot be displayed. */
167 if (thumb_x_end > upper_thumb_bound) {
168 thumb_x_end = upper_thumb_bound;
169 clipped = true;
170 }
171
172 float cropx_min = cut_off * crop_x_multiplier;
173 float cropx_max = (thumb_x_end - timeline_frame) * crop_x_multiplier;
174 if (cropx_max < 1.0f) {
175 break;
176 }
177
178 /* Get the thumbnail image. */
179 ImBuf *ibuf = seq::thumbnail_cache_get(C, scene, strip.strip, timeline_frame);
180 if (ibuf == nullptr) {
181 break;
182 }
183
184 SeqThumbInfo thumb = {};
185 thumb.ibuf = ibuf;
186 thumb.cropx_min = 0;
187 thumb.cropx_max = ibuf->x - 1;
188 if (clipped) {
189 thumb.cropx_min = clamp_f(cropx_min, 0, ibuf->x - 1);
190 thumb.cropx_max = clamp_f(cropx_max - 1 * 0, 0, ibuf->x - 1);
191 }
192 thumb.left_handle = strip.left_handle;
193 thumb.right_handle = strip.right_handle;
194 thumb.is_muted = is_muted;
195 thumb.bottom = strip.bottom;
196 thumb.top = strip.top;
197 thumb.x1 = timeline_frame + cut_off;
198 thumb.x2 = thumb_x_end;
199 thumb.y1 = strip.bottom;
200 thumb.y2 = strip.strip_content_top;
201 r_thumbs.append(thumb);
202
203 timeline_frame = thumb_calc_next_timeline_frame(
204 strip.strip, strip.left_handle, timeline_frame, thumb_width);
205 }
206}
207
213 gpu::Batch *batch_ = nullptr;
219
232
239
241 const SeqThumbInfo &info, float width, const rcti &rect, int tex_width, int tex_height)
242 {
244 flush_batch();
245 }
246
247 SeqStripThumbData &res = thumbs_[thumbs_count_];
249
250 res.left = strips_batch_.pos_to_pixel_space_x(info.left_handle);
251 res.right = strips_batch_.pos_to_pixel_space_x(info.right_handle);
252 res.bottom = strips_batch_.pos_to_pixel_space_y(info.bottom);
253 res.top = strips_batch_.pos_to_pixel_space_y(info.top);
254 res.tint_color = float4(1.0f, 1.0f, 1.0f, info.is_muted ? 0.47f : 1.0f);
255 res.x1 = strips_batch_.pos_to_pixel_space_x(info.x1);
256 res.x2 = strips_batch_.pos_to_pixel_space_x(info.x2);
257 res.y1 = strips_batch_.pos_to_pixel_space_y(info.y1);
258 res.y2 = strips_batch_.pos_to_pixel_space_y(info.y2);
259 res.u1 = float(rect.xmin) / float(tex_width);
260 res.u2 = float(rect.xmin + width) / float(tex_width);
261 res.v1 = float(rect.ymin) / float(tex_height);
262 res.v2 = float(rect.ymax) / float(tex_height);
263 }
264
282};
283
285 StripsDrawBatch &strips_batch,
286 const Vector<StripDrawContext> &strips)
287{
288 /* Nothing to do if we're not showing thumbnails overall. */
289 if ((ctx->sseq->flag & SEQ_SHOW_OVERLAY) == 0 ||
291 {
292 return;
293 }
294
295 /* Gather information for all thumbnails. */
297 for (const StripDrawContext &strip : strips) {
299 ctx->v2d, ctx->C, ctx->scene, strip, ctx->pixelx, ctx->pixely, strip.is_muted, thumbs);
300 }
301 if (thumbs.is_empty()) {
302 return;
303 }
304
305 ColorManagedViewSettings *view_settings;
306 ColorManagedDisplaySettings *display_settings;
307 IMB_colormanagement_display_settings_from_ctx(ctx->C, &view_settings, &display_settings);
308
309 /* Arrange thumbnail images into a texture atlas, using a simple
310 * "add to current row until end, then start a new row". Thumbnail
311 * images are most often same height (but varying width due to horizontal
312 * cropping), so this simple algorithm works well enough. */
313 constexpr int ATLAS_WIDTH = 4096;
314 constexpr int ATLAS_MAX_HEIGHT = 4096;
315 int cur_row_x = 0;
316 int cur_row_y = 0;
317 int cur_row_height = 0;
318 Vector<rcti> rects;
319 rects.reserve(thumbs.size());
320 for (const SeqThumbInfo &info : thumbs) {
321 int cropx_min = int(info.cropx_min);
322 int cropx_max = int(math::ceil(info.cropx_max));
323 int width = cropx_max - cropx_min + 1;
324 int height = info.ibuf->y;
325 cur_row_height = math::max(cur_row_height, height);
326
327 /* If this thumb would not fit onto current row, start a new row. */
328 if (cur_row_x + width > ATLAS_WIDTH) {
329 cur_row_y += cur_row_height + 1; /* +1 empty pixel for bilinear filter. */
330 cur_row_height = height;
331 cur_row_x = 0;
332 if (cur_row_y > ATLAS_MAX_HEIGHT) {
333 break;
334 }
335 }
336
337 /* Record our rect. */
338 rcti rect{cur_row_x, cur_row_x + width, cur_row_y, cur_row_y + height};
339 rects.append(rect);
340
341 /* Advance to next item inside row. */
342 cur_row_x += width + 1; /* +1 empty pixel for bilinear filter. */
343 }
344
345 /* Create the atlas GPU texture. */
346 const int tex_width = ATLAS_WIDTH;
347 const int tex_height = cur_row_y + cur_row_height;
348 Array<uchar> tex_data(tex_width * tex_height * 4, 0);
349 for (int64_t i = 0; i < rects.size(); i++) {
350 /* Copy one thumbnail into atlas. */
351 const rcti &rect = rects[i];
352 SeqThumbInfo &info = thumbs[i];
353
354 void *cache_handle = nullptr;
355 uchar *display_buffer = IMB_display_buffer_acquire(
356 info.ibuf, view_settings, display_settings, &cache_handle);
357 if (display_buffer != nullptr && info.ibuf != nullptr) {
358 int cropx_min = int(info.cropx_min);
359 int cropx_max = int(math::ceil(info.cropx_max));
360 int width = cropx_max - cropx_min + 1;
361 int height = info.ibuf->y;
362 const uchar *src = display_buffer + cropx_min * 4;
363 uchar *dst = &tex_data[(rect.ymin * ATLAS_WIDTH + rect.xmin) * 4];
364 for (int y = 0; y < height; y++) {
365 memcpy(dst, src, width * 4);
366 src += info.ibuf->x * 4;
367 dst += ATLAS_WIDTH * 4;
368 }
369 }
370 IMB_display_buffer_release(cache_handle);
371
372 /* Release thumb image reference. */
373 IMB_freeImBuf(info.ibuf);
374 info.ibuf = nullptr;
375 }
376 blender::gpu::Texture *atlas = GPU_texture_create_2d("thumb_atlas",
377 tex_width,
378 tex_height,
379 1,
380 blender::gpu::TextureFormat::UNORM_8_8_8_8,
382 nullptr);
383 GPU_texture_update(atlas, GPU_DATA_UBYTE, tex_data.data());
384 GPU_texture_filter_mode(atlas, true);
386
387 /* Draw all thumbnails. */
390
391 ThumbsDrawBatch batch(strips_batch, atlas);
392 for (int64_t i = 0; i < rects.size(); i++) {
393 const rcti &rect = rects[i];
394 const SeqThumbInfo &info = thumbs[i];
395 batch.add_thumb(info, info.cropx_max - info.cropx_min + 1, rect, tex_width, tex_height);
396 }
397 batch.flush_batch();
398
400
401 GPU_texture_unbind(atlas);
402 GPU_texture_free(atlas);
403}
404
405} // namespace blender::ed::vse
MINLINE int round_fl_to_int(float a)
MINLINE float clamp_f(float value, float min, float max)
MINLINE float min_ff(float a, float b)
MINLINE int max_iii(int a, int b, int c)
static constexpr int image_width
static constexpr int image_height
unsigned char uchar
@ STRIP_TYPE_IMAGE
@ SEQ_TIMELINE_SHOW_THUMBNAILS
@ SEQ_SHOW_OVERLAY
#define UI_SCALE_FAC
void GPU_batch_draw_instance_range(blender::gpu::Batch *batch, int instance_first, int instance_count)
void GPU_batch_set_shader(blender::gpu::Batch *batch, blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
blender::gpu::Batch * GPU_batch_preset_quad()
void GPU_matrix_push_projection()
void GPU_matrix_pop_projection()
int GPU_shader_get_ubo_binding(blender::gpu::Shader *shader, const char *name)
int GPU_shader_get_sampler_binding(blender::gpu::Shader *shader, const char *name)
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
blender::gpu::Shader * GPU_shader_get_builtin_shader(GPUBuiltinShader shader)
@ GPU_SHADER_SEQUENCER_THUMBS
#define GPU_SEQ_STRIP_DRAW_DATA_LEN
void GPU_texture_unbind(blender::gpu::Texture *texture)
@ GPU_DATA_UBYTE
void GPU_texture_extend_mode(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER
blender::gpu::Texture * GPU_texture_create_2d(const char *name, int width, int height, int mip_len, blender::gpu::TextureFormat format, eGPUTextureUsage usage, const float *data)
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
void GPU_texture_bind(blender::gpu::Texture *texture, int unit)
void GPU_texture_free(blender::gpu::Texture *texture)
void GPU_texture_update(blender::gpu::Texture *texture, eGPUDataFormat data_format, const void *data)
void GPU_uniformbuf_free(blender::gpu::UniformBuf *ubo)
void GPU_uniformbuf_bind(blender::gpu::UniformBuf *ubo, int slot)
#define GPU_uniformbuf_create(size)
void GPU_uniformbuf_unbind(blender::gpu::UniformBuf *ubo)
void GPU_uniformbuf_update(blender::gpu::UniformBuf *ubo, const void *data)
unsigned char * IMB_display_buffer_acquire(ImBuf *ibuf, const ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings, void **cache_handle)
void IMB_display_buffer_release(void *cache_handle)
void IMB_colormanagement_display_settings_from_ctx(const bContext *C, ColorManagedViewSettings **r_view_settings, ColorManagedDisplaySettings **r_display_settings)
void IMB_freeImBuf(ImBuf *ibuf)
#define C
Definition RandGen.cpp:29
long long int int64_t
const T * data() const
Definition BLI_array.hh:312
int64_t size() const
void append(const T &value)
bool is_empty() const
void reserve(const int64_t min_capacity)
nullptr float
struct @021025263243242147216143265077100330027142264337::@225245033123204053237120173316075113304004012000 batch
static void get_seq_strip_thumbnails(const View2D *v2d, const bContext *C, Scene *scene, const StripDrawContext &strip, float pixelx, float pixely, bool is_muted, Vector< SeqThumbInfo > &r_thumbs)
void draw_strip_thumbnails(TimelineDrawContext *ctx, StripsDrawBatch &strips_batch, const blender::Vector< StripDrawContext > &strips)
static void strip_get_thumb_image_dimensions(const Strip *strip, float pixelx, float pixely, float *r_thumb_width, float thumb_height, float *r_image_width, float *r_image_height)
static float thumb_calc_next_timeline_frame(const Strip *strip, float left_handle, float last_frame, float frame_step)
static float thumb_calc_first_timeline_frame(const Strip *strip, float left_handle, float frame_step, const rctf *view_area)
T ceil(const T &a)
T max(const T &a, const T &b)
bool strip_can_have_thumbnail(const Scene *scene, const Strip *strip)
ImBuf * thumbnail_cache_get(const bContext *C, Scene *scene, const Strip *strip, float timeline_frame)
static constexpr int THUMB_SIZE
VecBase< float, 4 > float4
struct SequencerTimelineOverlay timeline_overlay
StripElem * stripdata
StripData * data
ThumbsDrawBatch(StripsDrawBatch &strips_batch, gpu::Texture *atlas)
void add_thumb(const SeqThumbInfo &info, float width, const rcti &rect, int tex_width, int tex_height)
float xmax
float xmin
int ymin
int ymax
int xmin
i
Definition text_draw.cc:230
void wmOrtho2_region_pixelspace(const ARegion *region)