VSE: Media transform redesign

This patch changes behavior of strip transform and crop feature.

Purpose of this change is to allow display arbitrary portion of input
image, simplify user interface and workflow.
Offset and Crop values in old files are converted in versioning.
Offset animation is also converted. Crop animation and animation of
crop or offset enable properties is not taken into account

Changes in behavior and interface:
- If image is added to timeline it is scaled to fit inside preview area
while maintaining aspect ratio. Image is centered. This is considered
as a baseline for further transformation.
- Scale and rotation was added, so it is possible to transform image at
it's original resolution.
- Crop will not affect image transformation (does not move image).
- Values of Crop and Transform Position are in pixels, these values are
corrected if preview is fraction of project resolution.
- Transform and Mirror panel has been removed and new Transform panel
and Crop panel is moved to Adjust panel. Mirror is now part of new
Transform panel.

Technical changes:
- Preprocessing stage must work on duplicated image, because original is
cached. Previously Crop and Offset could run at once and required only
one duplication of image. This is not the case with new algorithms, so
duplication on demand is implemented. Transformation can read original
image and will output new image that is safe to modify. It should be
possible to add crop step to transform algorithm, so that Crop won't
require previous duplication though.
- Use Crop and Use Translation checkboxes were removed. Individual
values are compared to default values to check if image needs to be
processed. In case of transform this will be done also if resolution of
source.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D8393
This commit is contained in:
Richard Antalik 2020-11-02 19:53:33 +01:00
parent 5ed4e1e23a
commit 0277579b28
Notes: blender-bot 2023-02-13 20:43:03 +01:00
Referenced by commit 6b3eca661d, Revert "VSE: Media transform redesign"
Referenced by issue #82165, Video Editing: Crop is Stretch & Offset
Referenced by issue #82165, Video Editing: Crop is Stretch & Offset
11 changed files with 569 additions and 210 deletions

View File

@ -949,54 +949,28 @@ class SEQUENCER_PT_strip(SequencerButtonsPanel, Panel):
row.prop(strip, "mute", toggle=True, icon_only=True, emboss=False)
class SEQUENCER_PT_adjust_transform_offset(SequencerButtonsPanel, Panel):
bl_label = "Offset"
bl_parent_id = "SEQUENCER_PT_adjust_transform"
bl_options = {'DEFAULT_CLOSED'}
bl_category = "Strip"
@classmethod
def poll(cls, context):
strip = act_strip(context)
return strip.type != 'SOUND'
def draw_header(self, context):
strip = act_strip(context)
self.layout.prop(strip, "use_translation", text="")
def draw(self, context):
strip = act_strip(context)
layout = self.layout
layout.use_property_split = True
layout.active = strip.use_translation and (not strip.mute)
col = layout.column(align=True)
col.prop(strip.transform, "offset_x", text="Position X")
col.prop(strip.transform, "offset_y", text="Y")
class SEQUENCER_PT_adjust_transform_crop(SequencerButtonsPanel, Panel):
class SEQUENCER_PT_adjust_crop(SequencerButtonsPanel, Panel):
bl_label = "Crop"
bl_parent_id = "SEQUENCER_PT_adjust_transform"
bl_options = {'DEFAULT_CLOSED'}
bl_category = "Strip"
@classmethod
def poll(cls, context):
if not cls.has_sequencer(context):
return False
strip = act_strip(context)
if not strip:
return False
strip = act_strip(context)
return strip.type != 'SOUND'
def draw_header(self, context):
strip = act_strip(context)
self.layout.prop(strip, "use_crop", text="")
def draw(self, context):
strip = act_strip(context)
layout = self.layout
layout.use_property_split = True
layout.active = strip.use_crop and (not strip.mute)
layout.active = not strip.mute
col = layout.column(align=True)
col.prop(strip.crop, "min_x")
@ -1590,21 +1564,19 @@ class SEQUENCER_PT_time(SequencerButtonsPanel, Panel):
split.label(text="%d-%d (%d)" % (sta, end, end - sta + 1), translate=False)
class SEQUENCER_PT_adjust(SequencerButtonsPanel, Panel):
bl_label = "Adjust"
bl_category = "Strip"
def draw(self, context):
pass
class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
bl_label = "Sound"
bl_parent_id = "SEQUENCER_PT_adjust"
bl_category = "Strip"
@classmethod
def poll(cls, context):
if not cls.has_sequencer(context):
return False
strip = act_strip(context)
if not strip:
return False
strip = act_strip(context)
return strip.type == 'SOUND'
@ -1636,11 +1608,17 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel):
bl_label = "Compositing"
bl_parent_id = "SEQUENCER_PT_adjust"
bl_category = "Strip"
@classmethod
def poll(cls, context):
if not cls.has_sequencer(context):
return False
strip = act_strip(context)
if not strip:
return False
strip = act_strip(context)
return strip.type != 'SOUND'
@ -1659,8 +1637,8 @@ class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel):
class SEQUENCER_PT_adjust_transform(SequencerButtonsPanel, Panel):
bl_label = "Transform"
bl_parent_id = "SEQUENCER_PT_adjust"
bl_category = "Strip"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
@ -1671,23 +1649,26 @@ class SEQUENCER_PT_adjust_transform(SequencerButtonsPanel, Panel):
if not strip:
return False
return strip.type in {
'MOVIE', 'IMAGE', 'SCENE', 'MOVIECLIP', 'MASK',
'META', 'ADD', 'SUBTRACT', 'ALPHA_OVER', 'TEXT',
'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY',
'OVER_DROP', 'WIPE', 'GLOW', 'TRANSFORM', 'COLOR',
'MULTICAM', 'SPEED', 'ADJUSTMENT', 'COLORMIX'
}
strip = act_strip(context)
return strip.type != 'SOUND'
def draw(self, context):
layout = self.layout
strip = act_strip(context)
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.active = not strip.mute
col = layout.column(align=True)
col.prop(strip.transform, "offset_x", text="Position X")
col.prop(strip.transform, "offset_y", text="Y")
col = layout.column(align=True)
col.prop(strip.transform, "scale_x", text="Scale X")
col.prop(strip.transform, "scale_y", text="Y")
col = layout.column(align=True)
col.prop(strip.transform, "rotation", text="Rotation")
row = layout.row(heading="Mirror")
sub = row.row(align=True)
sub.prop(strip, "use_flip_x", text="X", toggle=True)
@ -1696,7 +1677,6 @@ class SEQUENCER_PT_adjust_transform(SequencerButtonsPanel, Panel):
class SEQUENCER_PT_adjust_video(SequencerButtonsPanel, Panel):
bl_label = "Video"
bl_parent_id = "SEQUENCER_PT_adjust"
bl_options = {'DEFAULT_CLOSED'}
bl_category = "Strip"
@ -1745,7 +1725,6 @@ class SEQUENCER_PT_adjust_video(SequencerButtonsPanel, Panel):
class SEQUENCER_PT_adjust_color(SequencerButtonsPanel, Panel):
bl_label = "Color"
bl_parent_id = "SEQUENCER_PT_adjust"
bl_options = {'DEFAULT_CLOSED'}
bl_category = "Strip"
@ -2234,11 +2213,9 @@ classes = (
SEQUENCER_PT_effect_text_style,
SEQUENCER_PT_effect_text_layout,
SEQUENCER_PT_adjust,
SEQUENCER_PT_adjust_comp,
SEQUENCER_PT_adjust_transform,
SEQUENCER_PT_adjust_transform_offset,
SEQUENCER_PT_adjust_transform_crop,
SEQUENCER_PT_adjust_crop,
SEQUENCER_PT_adjust_video,
SEQUENCER_PT_adjust_color,
SEQUENCER_PT_adjust_sound,

View File

@ -39,7 +39,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 1
#define BLENDER_FILE_SUBVERSION 2
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@ -322,9 +322,13 @@ void mat4_to_size(float size[3], const float M[4][4]);
void mat4_to_size_fix_shear(float size[3], const float M[4][4]);
void translate_m3(float mat[3][3], float tx, float ty);
void translate_m4(float mat[4][4], float tx, float ty, float tz);
void rotate_m3(float mat[3][3], const float angle);
void rotate_m4(float mat[4][4], const char axis, const float angle);
void rescale_m3(float mat[3][3], const float scale[2]);
void rescale_m4(float mat[4][4], const float scale[3]);
void transform_pivot_set_m3(float mat[3][3], const float pivot[2]);
void transform_pivot_set_m4(float mat[4][4], const float pivot[3]);
void mat3_to_rot_size(float rot[3][3], float size[3], const float mat3[3][3]);
@ -334,6 +338,10 @@ void mat4_decompose(float loc[3], float quat[4], float size[3], const float wmat
void mat3_polar_decompose(const float mat3[3][3], float r_U[3][3], float r_P[3][3]);
void loc_rot_size_to_mat3(float R[3][3],
const float loc[2],
const float angle,
const float size[2]);
void loc_rot_size_to_mat4(float R[4][4],
const float loc[3],
const float rot[3][3],

View File

@ -2223,6 +2223,12 @@ void scale_m4_fl(float R[4][4], float scale)
R[3][0] = R[3][1] = R[3][2] = 0.0;
}
void translate_m3(float mat[3][3], float tx, float ty)
{
mat[2][0] += (tx * mat[0][0] + ty * mat[1][0]);
mat[2][1] += (tx * mat[0][1] + ty * mat[1][1]);
}
void translate_m4(float mat[4][4], float Tx, float Ty, float Tz)
{
mat[3][0] += (Tx * mat[0][0] + Ty * mat[1][0] + Tz * mat[2][0]);
@ -2230,6 +2236,18 @@ void translate_m4(float mat[4][4], float Tx, float Ty, float Tz)
mat[3][2] += (Tx * mat[0][2] + Ty * mat[1][2] + Tz * mat[2][2]);
}
void rotate_m3(float mat[3][3], const float angle)
{
const float angle_cos = cosf(angle);
const float angle_sin = sinf(angle);
for (int col = 0; col < 3; col++) {
float temp = angle_cos * mat[0][col] + angle_sin * mat[1][col];
mat[1][col] = -angle_sin * mat[0][col] + angle_cos * mat[1][col];
mat[0][col] = temp;
}
}
/* TODO: enum for axis? */
/**
* Rotate a matrix in-place.
@ -2275,6 +2293,12 @@ void rotate_m4(float mat[4][4], const char axis, const float angle)
}
}
void rescale_m3(float mat[3][3], const float scale[2])
{
mul_v3_fl(mat[0], scale[0]);
mul_v3_fl(mat[1], scale[1]);
}
/** Scale a matrix in-place. */
void rescale_m4(float mat[4][4], const float scale[3])
{
@ -2305,6 +2329,20 @@ void transform_pivot_set_m4(float mat[4][4], const float pivot[3])
mul_m4_m4m4(mat, mat, tmat);
}
void transform_pivot_set_m3(float mat[3][3], const float pivot[2])
{
float tmat[3][3];
unit_m3(tmat);
copy_v2_v2(tmat[2], pivot);
mul_m3_m3m3(mat, tmat, mat);
/* invert the matrix */
negate_v2(tmat[2]);
mul_m3_m3m3(mat, mat, tmat);
}
void blend_m3_m3m3(float out[3][3],
const float dst[3][3],
const float src[3][3],
@ -2484,6 +2522,21 @@ bool equals_m4m4(const float mat1[4][4], const float mat2[4][4])
equals_v4v4(mat1[2], mat2[2]) && equals_v4v4(mat1[3], mat2[3]));
}
/**
* Make a 3x3 matrix out of 3 transform components.
* Matrices are made in the order: `loc * rot * scale`
*/
void loc_rot_size_to_mat3(float R[3][3],
const float loc[2],
const float angle,
const float size[2])
{
unit_m3(R);
translate_m3(R, loc[0], loc[1]);
rotate_m3(R, angle);
rescale_m3(R, size);
}
/**
* Make a 4x4 matrix out of 3 transform components.
* Matrices are made in the order: `scale * rot * loc`

View File

@ -44,12 +44,14 @@
#include "DNA_rigidbody_types.h"
#include "DNA_screen_types.h"
#include "DNA_shader_fx_types.h"
#include "DNA_space_types.h"
#include "DNA_tracking_types.h"
#include "DNA_workspace_types.h"
#include "BKE_animsys.h"
#include "BKE_collection.h"
#include "BKE_colortools.h"
#include "BKE_fcurve.h"
#include "BKE_gpencil.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
@ -59,12 +61,154 @@
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "SEQ_sequencer.h"
#include "BLO_readfile.h"
#include "readfile.h"
/* Make preferences read-only, use versioning_userdef.c. */
#define U (*((const UserDef *)&U))
/* image_size is width or height depending what RNA property is converted - X or Y. */
static void seq_convert_transform_animation(const Scene *scene,
const char *path,
const int image_size)
{
if (scene->adt == NULL || scene->adt->action == NULL) {
return;
}
FCurve *fcu = BKE_fcurve_find(&scene->adt->action->curves, path, 0);
if (fcu != NULL && !BKE_fcurve_is_empty(fcu)) {
BezTriple *bezt = fcu->bezt;
for (int i = 0; i < fcu->totvert; i++, bezt++) {
/* Same math as with old_image_center_*, but simplified. */
bezt->vec[1][1] = image_size / 2 + bezt->vec[1][1] - scene->r.xsch / 2;
}
}
}
static void seq_convert_transform_crop(const Scene *scene,
Sequence *seq,
const eSpaceSeq_Proxy_RenderSize render_size)
{
StripCrop *c = seq->strip->crop;
StripTransform *t = seq->strip->transform;
int old_image_center_x = scene->r.xsch / 2;
int old_image_center_y = scene->r.ysch / 2;
int image_size_x = scene->r.xsch;
int image_size_y = scene->r.ysch;
/* Hardcoded legacy bit-flags which has been removed. */
const uint32_t use_transform_flag = (1 << 16);
const uint32_t use_crop_flag = (1 << 17);
const StripElem *s_elem = BKE_sequencer_give_stripelem(seq, seq->start);
if (s_elem != NULL) {
image_size_x = s_elem->orig_width;
image_size_y = s_elem->orig_height;
if (SEQ_can_use_proxy(seq, SEQ_rendersize_to_proxysize(render_size))) {
image_size_x /= BKE_sequencer_rendersize_to_scale_factor(render_size);
image_size_y /= BKE_sequencer_rendersize_to_scale_factor(render_size);
}
}
/* Default scale. */
if (t->scale_x == 0.0f && t->scale_y == 0.0f) {
t->scale_x = 1.0f;
t->scale_y = 1.0f;
}
/* Clear crop if it was unused. This must happen before converting values. */
if ((seq->flag & use_crop_flag) == 0) {
c->bottom = c->top = c->left = c->right = 0;
}
if ((seq->flag & use_transform_flag) == 0) {
t->xofs = t->yofs = 0;
/* Reverse scale to fit for strips not using offset. */
float project_aspect = (float)scene->r.xsch / (float)scene->r.ysch;
float image_aspect = (float)image_size_x / (float)image_size_y;
if (project_aspect > image_aspect) {
t->scale_x = project_aspect / image_aspect;
}
else {
t->scale_y = image_aspect / project_aspect;
}
}
if ((seq->flag & use_crop_flag) != 0 && (seq->flag & use_transform_flag) == 0) {
/* Calculate image offset. */
float s_x = scene->r.xsch / image_size_x;
float s_y = scene->r.ysch / image_size_y;
old_image_center_x += c->right * s_x - c->left * s_x;
old_image_center_y += c->top * s_y - c->bottom * s_y;
/* Convert crop to scale. */
int cropped_image_size_x = image_size_x - c->right - c->left;
int cropped_image_size_y = image_size_y - c->top - c->bottom;
c->bottom = c->top = c->left = c->right = 0;
t->scale_x *= (float)image_size_x / (float)cropped_image_size_x;
t->scale_y *= (float)image_size_y / (float)cropped_image_size_y;
}
if ((seq->flag & use_transform_flag) != 0) {
/* Convert image offset. */
old_image_center_x = image_size_x / 2 - c->left + t->xofs;
old_image_center_y = image_size_y / 2 - c->bottom + t->yofs;
/* Preserve original image size. */
t->scale_x = t->scale_y = MAX2((float)image_size_x / (float)scene->r.xsch,
(float)image_size_y / (float)scene->r.ysch);
/* Convert crop. */
if ((seq->flag & use_crop_flag) != 0) {
c->top /= t->scale_x;
c->bottom /= t->scale_x;
c->left /= t->scale_x;
c->right /= t->scale_x;
}
}
t->xofs = old_image_center_x - scene->r.xsch / 2;
t->yofs = old_image_center_y - scene->r.ysch / 2;
/* Convert offset animation, but only if crop is not used. */
if ((seq->flag & use_transform_flag) != 0 && (seq->flag & use_crop_flag) == 0) {
char name_esc[(sizeof(seq->name) - 2) * 2], *path;
BLI_strescape(name_esc, seq->name + 2, sizeof(name_esc));
path = BLI_sprintfN("sequence_editor.sequences_all[\"%s\"].transform.offset_x", name_esc);
seq_convert_transform_animation(scene, path, image_size_x);
MEM_freeN(path);
path = BLI_sprintfN("sequence_editor.sequences_all[\"%s\"].transform.offset_y", name_esc);
seq_convert_transform_animation(scene, path, image_size_y);
MEM_freeN(path);
}
seq->flag &= ~use_transform_flag;
seq->flag &= ~use_crop_flag;
}
static void seq_convert_transform_crop_lb(const Scene *scene,
const ListBase *lb,
const eSpaceSeq_Proxy_RenderSize render_size)
{
LISTBASE_FOREACH (Sequence *, seq, lb) {
if (seq->type != SEQ_TYPE_SOUND_RAM) {
seq_convert_transform_crop(scene, seq, render_size);
}
if (seq->type == SEQ_TYPE_META) {
seq_convert_transform_crop_lb(scene, &seq->seqbase, render_size);
}
}
}
void do_versions_after_linking_290(Main *bmain, ReportList *UNUSED(reports))
{
if (!MAIN_VERSION_ATLEAST(bmain, 290, 1)) {
@ -292,6 +436,31 @@ void do_versions_after_linking_290(Main *bmain, ReportList *UNUSED(reports))
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 292, 2)) {
eSpaceSeq_Proxy_RenderSize render_size = 100;
for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
switch (sl->spacetype) {
case SPACE_SEQ: {
SpaceSeq *sseq = (SpaceSeq *)sl;
render_size = sseq->render_size;
break;
}
}
}
}
}
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->ed != NULL) {
seq_convert_transform_crop_lb(scene, &scene->ed->seqbase, render_size);
}
}
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -684,6 +684,8 @@ void IMB_rectfill_area(struct ImBuf *ibuf,
int x2,
int y2,
struct ColorManagedDisplay *display);
void IMB_rectfill_area_replace(
const struct ImBuf *ibuf, const float col[4], int x1, int y1, int x2, int y2);
void IMB_rectfill_alpha(struct ImBuf *ibuf, const float value);
/* This should not be here, really,

View File

@ -1069,8 +1069,11 @@ void IMB_rectblend_threaded(ImBuf *dbuf,
}
}
/* fill */
/**
* Replace pixels of entire image with solid color.
* \param ibuf an image to be filled with color. It must be 4 channel image.
* \param col RGBA color, which is assigned directly to both byte (via scaling) and float buffers.
*/
void IMB_rectfill(ImBuf *drect, const float col[4])
{
int num;
@ -1103,6 +1106,61 @@ void IMB_rectfill(ImBuf *drect, const float col[4])
}
}
/**
* Replace pixels of image area with solid color.
* \param ibuf an image to be filled with color. It must be 4 channel image.
* \param col RGBA color, which is assigned directly to both byte (via scaling) and float buffers.
* \param x1, y1, x2, y2 (x1, y1) defines starting point of the rectangular area to be filled,
* (x2, y2) is the end point. Note that values are allowed to be loosely ordered, which means that
* x2 is allowed to be lower than x1, as well as y2 is allowed to be lower than y1. No matter the
* order the area between x1 and x2, and y1 and y2 is filled.
*/
void IMB_rectfill_area_replace(
const ImBuf *ibuf, const float col[4], int x1, int y1, int x2, int y2)
{
/* Sanity checks. */
BLI_assert(ibuf->channels == 4);
if (ibuf->channels != 4) {
return;
}
int width = ibuf->x;
int height = ibuf->y;
CLAMP(x1, 0, width);
CLAMP(x2, 0, width);
CLAMP(y1, 0, height);
CLAMP(y2, 0, height);
if (x1 > x2) {
SWAP(int, x1, x2);
}
if (y1 > y2) {
SWAP(int, y1, y2);
}
if (x1 == x2 || y1 == y2) {
return;
}
unsigned char col_char[4] = {col[0] * 255, col[1] * 255, col[2] * 255, col[3] * 255};
for (int y = y1; y < y2; y++) {
for (int x = x1; x < x2; x++) {
size_t offset = ((size_t)ibuf->x) * y * 4 + 4 * x;
if (ibuf->rect) {
unsigned char *rrect = (unsigned char *)ibuf->rect + offset;
memcpy(rrect, &col_char, sizeof(unsigned char) * 4);
}
if (ibuf->rect_float) {
float *rrectf = ibuf->rect_float + offset;
memcpy(rrectf, &col, sizeof(float) * 4);
}
}
}
}
void buf_rectfill_area(unsigned char *rect,
float *rectf,
int width,
@ -1214,6 +1272,21 @@ void buf_rectfill_area(unsigned char *rect,
}
}
/**
* Blend pixels of image area with solid color.
*
* For images with uchar buffer use color matching image colorspace.
* For images with float buffer use color display colorspace.
* If display colorspace can not be referenced, use color in SRGB colorspace.
*
* \param ibuf an image to be filled with color. It must be 4 channel image.
* \param col RGBA color.
* \param x1, y1, x2, y2 (x1, y1) defines starting point of the rectangular area to be filled,
* (x2, y2) is the end point. Note that values are allowed to be loosely ordered, which means that
* x2 is allowed to be lower than x1, as well as y2 is allowed to be lower than y1. No matter the
* order the area between x1 and x2, and y1 and y2 is filled.
* \param display colorspace reference for display space.
*/
void IMB_rectfill_area(ImBuf *ibuf,
const float col[4],
int x1,

View File

@ -68,6 +68,9 @@ typedef struct StripCrop {
typedef struct StripTransform {
int xofs;
int yofs;
float scale_x;
float scale_y;
float rotation;
} StripTransform;
typedef struct StripColorBalance {
@ -494,8 +497,8 @@ enum {
SEQ_MAKE_FLOAT = (1 << 13),
SEQ_LOCK = (1 << 14),
SEQ_USE_PROXY = (1 << 15),
SEQ_USE_TRANSFORM = (1 << 16),
SEQ_USE_CROP = (1 << 17),
SEQ_FLAG_UNUSED_23 = (1 << 16), /* cleared */
SEQ_FLAG_UNUSED_22 = (1 << 17), /* cleared */
SEQ_FLAG_UNUSED_18 = (1 << 18), /* cleared */
SEQ_FLAG_UNUSED_19 = (1 << 19), /* cleared */
SEQ_FLAG_UNUSED_21 = (1 << 21), /* cleared */

View File

@ -473,34 +473,6 @@ static void rna_Sequence_use_proxy_set(PointerRNA *ptr, bool value)
BKE_sequencer_proxy_set(seq, value != 0);
}
static void rna_Sequence_use_translation_set(PointerRNA *ptr, bool value)
{
Sequence *seq = (Sequence *)ptr->data;
if (value) {
seq->flag |= SEQ_USE_TRANSFORM;
if (seq->strip->transform == NULL) {
seq->strip->transform = MEM_callocN(sizeof(struct StripTransform), "StripTransform");
}
}
else {
seq->flag &= ~SEQ_USE_TRANSFORM;
}
}
static void rna_Sequence_use_crop_set(PointerRNA *ptr, bool value)
{
Sequence *seq = (Sequence *)ptr->data;
if (value) {
seq->flag |= SEQ_USE_CROP;
if (seq->strip->crop == NULL) {
seq->strip->crop = MEM_callocN(sizeof(struct StripCrop), "StripCrop");
}
}
else {
seq->flag &= ~SEQ_USE_CROP;
}
}
static int transform_seq_cmp_fn(Sequence *seq, void *arg_pt)
{
SequenceSearchData *data = arg_pt;
@ -1409,18 +1381,35 @@ static void rna_def_strip_transform(BlenderRNA *brna)
RNA_def_struct_ui_text(srna, "Sequence Transform", "Transform parameters for a sequence strip");
RNA_def_struct_sdna(srna, "StripTransform");
prop = RNA_def_property(srna, "scale_x", PROP_FLOAT, PROP_UNSIGNED);
RNA_def_property_float_sdna(prop, NULL, "scale_x");
RNA_def_property_ui_text(prop, "Scale X", "Scale along X axis");
RNA_def_property_ui_range(prop, 0, FLT_MAX, 3, 3);
RNA_def_property_float_default(prop, 1.0f);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update");
prop = RNA_def_property(srna, "scale_y", PROP_FLOAT, PROP_UNSIGNED);
RNA_def_property_float_sdna(prop, NULL, "scale_y");
RNA_def_property_ui_text(prop, "Scale Y", "Scale along Y axis");
RNA_def_property_ui_range(prop, 0, FLT_MAX, 3, 3);
RNA_def_property_float_default(prop, 1.0f);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update");
prop = RNA_def_property(srna, "offset_x", PROP_INT, PROP_PIXEL);
RNA_def_property_int_sdna(prop, NULL, "xofs");
RNA_def_property_ui_text(
prop, "Offset X", "Amount to move the input on the X axis within its boundaries");
RNA_def_property_ui_range(prop, -4096, 4096, 1, -1);
RNA_def_property_ui_text(prop, "Translate X", "Move along X axis");
RNA_def_property_ui_range(prop, INT_MIN, INT_MAX, 1, 6);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update");
prop = RNA_def_property(srna, "offset_y", PROP_INT, PROP_PIXEL);
RNA_def_property_int_sdna(prop, NULL, "yofs");
RNA_def_property_ui_text(
prop, "Offset Y", "Amount to move the input on the Y axis within its boundaries");
RNA_def_property_ui_range(prop, -4096, 4096, 1, -1);
RNA_def_property_ui_text(prop, "Translate Y", "Move along Y axis");
RNA_def_property_ui_range(prop, INT_MIN, INT_MAX, 1, 6);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update");
prop = RNA_def_property(srna, "rotation", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_float_sdna(prop, NULL, "rotation");
RNA_def_property_ui_text(prop, "Rotation", "Rotate around image centr");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update");
RNA_def_struct_path_func(srna, "rna_SequenceTransform_path");
@ -2175,22 +2164,10 @@ static void rna_def_filter_video(StructRNA *srna)
RNA_def_property_ui_text(prop, "Strobe", "Only display every nth frame");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_raw_update");
prop = RNA_def_property(srna, "use_translation", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_USE_TRANSFORM);
RNA_def_property_ui_text(prop, "Use Translation", "Translate image before processing");
RNA_def_property_boolean_funcs(prop, NULL, "rna_Sequence_use_translation_set");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_raw_update");
prop = RNA_def_property(srna, "transform", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "strip->transform");
RNA_def_property_ui_text(prop, "Transform", "");
prop = RNA_def_property(srna, "use_crop", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_USE_CROP);
RNA_def_property_ui_text(prop, "Use Crop", "Crop image before processing");
RNA_def_property_boolean_funcs(prop, NULL, "rna_Sequence_use_crop_set");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_raw_update");
prop = RNA_def_property(srna, "crop", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "strip->crop");
RNA_def_property_ui_text(prop, "Crop", "");

View File

@ -44,6 +44,7 @@ struct TextVars;
struct bContext;
struct bSound;
struct SeqIndexBuildContext;
enum IMB_Proxy_Size;
/* Wipe effect */
enum {
@ -156,6 +157,8 @@ void BKE_sequencer_new_render_data(struct Main *bmain,
int preview_render_size,
int for_render,
SeqRenderData *r_context);
bool SEQ_can_use_proxy(struct Sequence *seq, enum IMB_Proxy_Size psize);
enum IMB_Proxy_Size SEQ_rendersize_to_proxysize(int render_size);
/* **********************************************************************
* sequencer.c

View File

@ -1631,7 +1631,7 @@ typedef struct SeqIndexBuildContext {
#define PROXY_MAXFILE (2 * FILE_MAXDIR + FILE_MAXFILE)
static IMB_Proxy_Size seq_rendersize_to_proxysize(int render_size)
IMB_Proxy_Size SEQ_rendersize_to_proxysize(int render_size)
{
switch (render_size) {
case SEQ_RENDER_SIZE_PROXY_25:
@ -1904,7 +1904,7 @@ static bool seq_proxy_get_fname(Editing *ed,
return true;
}
static bool seq_can_use_proxy(Sequence *seq, IMB_Proxy_Size psize)
bool SEQ_can_use_proxy(Sequence *seq, IMB_Proxy_Size psize)
{
if (seq->strip->proxy == NULL) {
return false;
@ -1922,7 +1922,7 @@ static ImBuf *seq_proxy_fetch(const SeqRenderData *context, Sequence *seq, int c
StripAnim *sanim;
/* only use proxies, if they are enabled (even if present!) */
if (!seq_can_use_proxy(seq, seq_rendersize_to_proxysize(psize))) {
if (!SEQ_can_use_proxy(seq, SEQ_rendersize_to_proxysize(psize))) {
return NULL;
}
@ -2653,6 +2653,28 @@ void BKE_sequencer_color_balance_apply(
* - Premultiply
*/
static bool sequencer_use_transform(const Sequence *seq)
{
const StripTransform *transform = seq->strip->transform;
if (transform->xofs != 0 || transform->yofs != 0 || transform->scale_x != 1 ||
transform->scale_y != 1 || transform->rotation != 0) {
return true;
}
return false;
}
static bool sequencer_use_crop(const Sequence *seq)
{
const StripCrop *crop = seq->strip->crop;
if (crop->left > 0 || crop->right > 0 || crop->top > 0 || crop->bottom > 0) {
return true;
}
return false;
}
bool BKE_sequencer_input_have_to_preprocess(const SeqRenderData *context,
Sequence *seq,
float UNUSED(cfra))
@ -2663,8 +2685,8 @@ bool BKE_sequencer_input_have_to_preprocess(const SeqRenderData *context,
return false;
}
if (seq->flag &
(SEQ_FILTERY | SEQ_USE_CROP | SEQ_USE_TRANSFORM | SEQ_FLIPX | SEQ_FLIPY | SEQ_MAKE_FLOAT)) {
if ((seq->flag & (SEQ_FILTERY | SEQ_FLIPX | SEQ_FLIPY | SEQ_MAKE_FLOAT)) ||
sequencer_use_crop(seq) || sequencer_use_transform(seq)) {
return true;
}
@ -2689,6 +2711,83 @@ bool BKE_sequencer_input_have_to_preprocess(const SeqRenderData *context,
return false;
}
typedef struct ImageTransformThreadInitData {
ImBuf *ibuf_source;
ImBuf *ibuf_out;
StripTransform *transform;
float scale_to_fit;
float image_scale_factor;
bool for_render;
} ImageTransformThreadInitData;
typedef struct ImageTransformThreadData {
ImBuf *ibuf_source;
ImBuf *ibuf_out;
StripTransform *transform;
float scale_to_fit;
float image_scale_factor;
bool for_render;
int start_line;
int tot_line;
} ImageTransformThreadData;
static void sequencer_image_transform_init(void *handle_v,
int start_line,
int tot_line,
void *init_data_v)
{
ImageTransformThreadData *handle = (ImageTransformThreadData *)handle_v;
const ImageTransformThreadInitData *init_data = (ImageTransformThreadInitData *)init_data_v;
handle->ibuf_source = init_data->ibuf_source;
handle->ibuf_out = init_data->ibuf_out;
handle->transform = init_data->transform;
handle->scale_to_fit = init_data->scale_to_fit;
handle->image_scale_factor = init_data->image_scale_factor;
handle->for_render = init_data->for_render;
handle->start_line = start_line;
handle->tot_line = tot_line;
}
static void *sequencer_image_transform_do_thread(void *data_v)
{
const ImageTransformThreadData *data = (ImageTransformThreadData *)data_v;
const StripTransform *transform = data->transform;
const float scale_x = transform->scale_x * data->scale_to_fit;
const float scale_y = transform->scale_y * data->scale_to_fit;
const float scale_to_fit_offs_x = (data->ibuf_out->x - data->ibuf_source->x) / 2;
const float scale_to_fit_offs_y = (data->ibuf_out->y - data->ibuf_source->y) / 2;
const float translate_x = transform->xofs * data->image_scale_factor + scale_to_fit_offs_x;
const float translate_y = transform->yofs * data->image_scale_factor + scale_to_fit_offs_y;
const int width = data->ibuf_out->x;
const int height = data->ibuf_out->y;
const float pivot[2] = {width / 2 - scale_to_fit_offs_x, height / 2 - scale_to_fit_offs_y};
float transform_matrix[3][3];
loc_rot_size_to_mat3(transform_matrix,
(const float[]){translate_x, translate_y},
transform->rotation,
(const float[]){scale_x, scale_y});
invert_m3(transform_matrix);
transform_pivot_set_m3(transform_matrix, pivot);
for (int yi = data->start_line; yi < data->start_line + data->tot_line; yi++) {
for (int xi = 0; xi < width; xi++) {
float uv[2] = {xi, yi};
mul_v2_m3v2(uv, transform_matrix, uv);
if (data->for_render) {
bilinear_interpolation(data->ibuf_source, data->ibuf_out, uv[0], uv[1], xi, yi);
}
else {
nearest_interpolation(data->ibuf_source, data->ibuf_out, uv[0], uv[1], xi, yi);
}
}
}
return NULL;
}
static ImBuf *input_preprocess(const SeqRenderData *context,
Sequence *seq,
float cfra,
@ -2696,131 +2795,124 @@ static ImBuf *input_preprocess(const SeqRenderData *context,
const bool is_proxy_image)
{
Scene *scene = context->scene;
float mul;
ibuf = IMB_makeSingleUser(ibuf);
ImBuf *preprocessed_ibuf = NULL;
/* Deinterlace. */
if ((seq->flag & SEQ_FILTERY) && !ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_MOVIECLIP)) {
IMB_filtery(ibuf);
/* Change original image pointer to avoid another duplication in SEQ_USE_TRANSFORM. */
preprocessed_ibuf = IMB_makeSingleUser(ibuf);
ibuf = preprocessed_ibuf;
IMB_filtery(preprocessed_ibuf);
}
if (seq->flag & (SEQ_USE_CROP | SEQ_USE_TRANSFORM)) {
StripCrop c = {0};
StripTransform t = {0};
/* Calculate scale factor, so image fits in preview area with original aspect ratio. */
const float scale_to_fit_factor = MIN2((float)context->rectx / (float)ibuf->x,
(float)context->recty / (float)ibuf->y);
if (seq->flag & SEQ_USE_CROP && seq->strip->crop) {
c = *seq->strip->crop;
}
if (seq->flag & SEQ_USE_TRANSFORM && seq->strip->transform) {
t = *seq->strip->transform;
}
/* Get scale factor if preview resolution doesn't match project resolution. */
float preview_scale_factor;
if (context->preview_render_size == SEQ_RENDER_SIZE_SCENE) {
preview_scale_factor = (float)scene->r.size / 100;
}
else {
preview_scale_factor = BKE_sequencer_rendersize_to_scale_factor(context->preview_render_size);
}
/* Calculate scale factor for current image if needed. */
double scale_factor, image_scale_factor = 1.0;
if (context->preview_render_size == SEQ_RENDER_SIZE_SCENE) {
scale_factor = image_scale_factor = (double)scene->r.size / 100;
}
else {
scale_factor = BKE_sequencer_rendersize_to_scale_factor(context->preview_render_size);
if (!is_proxy_image) {
image_scale_factor = scale_factor;
}
}
if (sequencer_use_crop(seq)) {
/* Change original image pointer to avoid another duplication in SEQ_USE_TRANSFORM. */
preprocessed_ibuf = IMB_makeSingleUser(ibuf);
ibuf = preprocessed_ibuf;
if (image_scale_factor != 1.0) {
if (context->for_render) {
IMB_scaleImBuf(ibuf, ibuf->x * image_scale_factor, ibuf->y * image_scale_factor);
}
else {
IMB_scalefastImBuf(ibuf, ibuf->x * image_scale_factor, ibuf->y * image_scale_factor);
}
}
const int width = ibuf->x;
const int height = ibuf->y;
const StripCrop *c = seq->strip->crop;
t.xofs *= scale_factor;
t.yofs *= scale_factor;
c.left *= scale_factor;
c.right *= scale_factor;
c.top *= scale_factor;
c.bottom *= scale_factor;
const int left = c->left / scale_to_fit_factor * preview_scale_factor;
const int right = c->right / scale_to_fit_factor * preview_scale_factor;
const int top = c->top / scale_to_fit_factor * preview_scale_factor;
const int bottom = c->bottom / scale_to_fit_factor * preview_scale_factor;
const float col[4] = {0.0f, 0.0f, 0.0f, 0.0f};
int sx, sy, dx, dy;
sx = ibuf->x - c.left - c.right;
sy = ibuf->y - c.top - c.bottom;
/* Left. */
IMB_rectfill_area_replace(preprocessed_ibuf, col, 0, 0, left, height);
/* Bottom. */
IMB_rectfill_area_replace(preprocessed_ibuf, col, left, 0, width, bottom);
/* Right. */
IMB_rectfill_area_replace(preprocessed_ibuf, col, width - right, bottom, width, height);
/* Top. */
IMB_rectfill_area_replace(preprocessed_ibuf, col, left, height - top, width - right, height);
}
if (seq->flag & SEQ_USE_TRANSFORM) {
dx = context->rectx;
dy = context->recty;
}
else {
dx = sx;
dy = sy;
}
if (sequencer_use_transform(seq) || context->rectx != ibuf->x || context->recty != ibuf->y) {
const int x = context->rectx;
const int y = context->recty;
preprocessed_ibuf = IMB_allocImBuf(x, y, 32, ibuf->rect_float ? IB_rectfloat : IB_rect);
if (c.top + c.bottom >= ibuf->y || c.left + c.right >= ibuf->x || t.xofs >= dx ||
t.yofs >= dy) {
return NULL;
}
ImBuf *i = IMB_allocImBuf(dx, dy, 32, ibuf->rect_float ? IB_rectfloat : IB_rect);
IMB_rectcpy(i, ibuf, t.xofs, t.yofs, c.left, c.bottom, sx, sy);
sequencer_imbuf_assign_spaces(scene, i);
IMB_metadata_copy(i, ibuf);
ImageTransformThreadInitData init_data = {NULL};
init_data.ibuf_source = ibuf;
init_data.ibuf_out = preprocessed_ibuf;
init_data.transform = seq->strip->transform;
init_data.scale_to_fit = scale_to_fit_factor;
init_data.image_scale_factor = preview_scale_factor;
init_data.for_render = context->for_render;
IMB_processor_apply_threaded(context->recty,
sizeof(ImageTransformThreadData),
&init_data,
sequencer_image_transform_init,
sequencer_image_transform_do_thread);
sequencer_imbuf_assign_spaces(scene, preprocessed_ibuf);
IMB_metadata_copy(preprocessed_ibuf, ibuf);
IMB_freeImBuf(ibuf);
ibuf = i;
}
/* Duplicate ibuf if we still have original. */
if (preprocessed_ibuf == NULL) {
preprocessed_ibuf = IMB_makeSingleUser(ibuf);
}
if (seq->flag & SEQ_FLIPX) {
IMB_flipx(ibuf);
IMB_flipx(preprocessed_ibuf);
}
if (seq->flag & SEQ_FLIPY) {
IMB_flipy(ibuf);
IMB_flipy(preprocessed_ibuf);
}
if (seq->sat != 1.0f) {
IMB_saturation(ibuf, seq->sat);
IMB_saturation(preprocessed_ibuf, seq->sat);
}
mul = seq->mul;
if (seq->flag & SEQ_MAKE_FLOAT) {
if (!preprocessed_ibuf->rect_float) {
BKE_sequencer_imbuf_to_sequencer_space(scene, preprocessed_ibuf, true);
}
if (preprocessed_ibuf->rect) {
imb_freerectImBuf(preprocessed_ibuf);
}
}
float mul = seq->mul;
if (seq->blend_mode == SEQ_BLEND_REPLACE) {
mul *= seq->blend_opacity / 100.0f;
}
if (seq->flag & SEQ_MAKE_FLOAT) {
if (!ibuf->rect_float) {
BKE_sequencer_imbuf_to_sequencer_space(scene, ibuf, true);
}
if (ibuf->rect) {
imb_freerectImBuf(ibuf);
}
}
if (mul != 1.0f) {
multibuf(ibuf, mul);
}
if (ibuf->x != context->rectx || ibuf->y != context->recty) {
if (context->for_render) {
IMB_scaleImBuf(ibuf, (short)context->rectx, (short)context->recty);
}
else {
IMB_scalefastImBuf(ibuf, (short)context->rectx, (short)context->recty);
}
multibuf(preprocessed_ibuf, mul);
}
if (seq->modifiers.first) {
ImBuf *ibuf_new = BKE_sequence_modifier_apply_stack(context, seq, ibuf, cfra);
ImBuf *ibuf_new = BKE_sequence_modifier_apply_stack(context, seq, preprocessed_ibuf, cfra);
if (ibuf_new != ibuf) {
IMB_metadata_copy(ibuf_new, ibuf);
IMB_freeImBuf(ibuf);
ibuf = ibuf_new;
if (ibuf_new != preprocessed_ibuf) {
IMB_metadata_copy(ibuf_new, preprocessed_ibuf);
IMB_freeImBuf(preprocessed_ibuf);
preprocessed_ibuf = ibuf_new;
}
}
return ibuf;
return preprocessed_ibuf;
}
/*********************** strip rendering functions *************************/
@ -3190,11 +3282,11 @@ static ImBuf *seq_render_movie_strip_view(const SeqRenderData *context,
bool *r_is_proxy_image)
{
ImBuf *ibuf = NULL;
IMB_Proxy_Size psize = seq_rendersize_to_proxysize(context->preview_render_size);
IMB_Proxy_Size psize = SEQ_rendersize_to_proxysize(context->preview_render_size);
IMB_anim_set_preseek(sanim->anim, seq->anim_preseek);
if (seq_can_use_proxy(seq, psize)) {
if (SEQ_can_use_proxy(seq, psize)) {
/* Try to get a proxy image.
* Movie proxies are handled by ImBuf module with exception of `custom file` setting. */
if (context->scene->ed->proxy_storage != SEQ_EDIT_PROXY_DIR_STORAGE &&
@ -3326,7 +3418,7 @@ static ImBuf *seq_render_movieclip_strip(const SeqRenderData *context,
{
ImBuf *ibuf = NULL;
MovieClipUser user;
IMB_Proxy_Size psize = seq_rendersize_to_proxysize(context->preview_render_size);
IMB_Proxy_Size psize = SEQ_rendersize_to_proxysize(context->preview_render_size);
if (!seq->clip) {
return NULL;
@ -5347,6 +5439,8 @@ static Strip *seq_strip_alloc(int type)
if (ELEM(type, SEQ_TYPE_SOUND_RAM, SEQ_TYPE_SOUND_HD) == 0) {
strip->transform = MEM_callocN(sizeof(struct StripTransform), "StripTransform");
strip->transform->scale_x = 1;
strip->transform->scale_y = 1;
strip->crop = MEM_callocN(sizeof(struct StripCrop), "StripCrop");
}