Skip to content

Commit

Permalink
Fix various aspect ratio issues with qtblend filter/transition (#1064)
Browse files Browse the repository at this point in the history
Fix various aspect ratio issues with qtblend filter/transition
  • Loading branch information
j-b-m authored Feb 14, 2025
1 parent 95b60a4 commit de332f8
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 72 deletions.
1 change: 1 addition & 0 deletions src/modules/qt/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <framework/mlt.h>


class QImage;

bool createQApplicationIfNeeded(mlt_service service);
Expand Down
116 changes: 86 additions & 30 deletions src/modules/qt/filter_qtblend.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* filter_lightshow.cpp -- animate color to the audio
* Copyright (C) 2015 Meltytech, LLC
* filter_qtblend.cpp -- Qt composite filter
* Copyright (C) 2015-2025 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand All @@ -26,6 +26,9 @@
#include <QPainter>
#include <QTransform>

#define MLT_QTBLEND_MAX_DIMENSION (16000)


/** Get the image.
*/
static int filter_get_image(mlt_frame frame,
Expand Down Expand Up @@ -66,19 +69,22 @@ static int filter_get_image(mlt_frame frame,
1.0};
int b_width = mlt_properties_get_int(frame_properties, "meta.media.width");
int b_height = mlt_properties_get_int(frame_properties, "meta.media.height");
bool distort = mlt_properties_get_int(properties, "distort");

if (b_height == 0) {
b_width = normalized_width;
b_height = normalized_height;
}
// Special case - aspect_ratio = 0
if (mlt_frame_get_aspect_ratio(frame) == 0) {
double output_ar = mlt_profile_sar(profile);
mlt_frame_set_aspect_ratio(frame, output_ar);
mlt_frame_set_aspect_ratio(frame, consumer_ar);
}
double b_ar = mlt_frame_get_aspect_ratio(frame);
double b_dar = b_ar * b_width / b_height;
double opacity = 1.0;

// If the _qtblend_scaled property is defined, a qtblend filter was already applied
int qtblendRescaled = mlt_properties_get_int(frame_properties, "qtblend_scaled");
if (mlt_properties_get(properties, "rect")) {
rect = mlt_properties_anim_get_rect(properties, "rect", position, length);
if (::strchr(mlt_properties_get(properties, "rect"), '%')) {
Expand All @@ -87,30 +93,82 @@ static int filter_get_image(mlt_frame frame,
rect.w *= normalized_width;
rect.h *= normalized_height;
}
double scale = mlt_profile_scale_width(profile, *width);
if (scale != 1.0) {
rect.x *= scale;
rect.w *= scale;
}
scale = mlt_profile_scale_height(profile, *height);
if (scale != 1.0) {
rect.y *= scale;
rect.h *= scale;
}
transform.translate(rect.x, rect.y);
opacity = rect.o;
hasAlpha = rect.o < 1 || rect.x != 0 || rect.y != 0 || rect.w != *width
|| rect.h != *height;
if (qtblendRescaled) {
// Another qtblend filter was already applied
// In this case, the *width and *height are set to the source resolution to ensure we don't lose too much details on multiple scaling operations
// We requested a image with full media resolution, adjust rect to profile
// Check if we have consumer scaling enabled since we cannot use *width and *height
double consumerScale = mlt_properties_get_double(frame_properties,
"qtblend_preview_scaling");
if (consumerScale > 0.) {
b_width *= consumerScale;
b_height *= consumerScale;
}

if (mlt_properties_get_int(properties, "distort") == 0) {
b_height = qMax(1, qMin(qRound(rect.h), b_height));
b_width = qMax(1, qRound(b_height * b_dar / b_ar / consumer_ar));
// Always request an image that follows the consumer aspect ratio
double consumer_dar = normalized_width * consumer_ar / normalized_height;
int tmpWidth = b_width;
int tmpHeight = b_height;
double scaleFactor = qMax(*width / rect.w, *height / rect.h);
if (scaleFactor > 1.) {
// Use the highest necessary resolution image
tmpWidth *= scaleFactor;
tmpHeight *= scaleFactor;
}
if (consumer_dar > b_dar) {
*width = qBound(qRound(normalized_width * consumerScale),
tmpWidth,
MLT_QTBLEND_MAX_DIMENSION);
*height = qRound(*width * consumer_ar * normalized_height / normalized_width);
} else {
*height = qBound(qRound(normalized_height * consumerScale),
tmpHeight,
MLT_QTBLEND_MAX_DIMENSION);
*width = qRound(*height * normalized_width / normalized_height / consumer_ar);
}
// Adjust rect to new scaling
double scale = (double) *width / normalized_width;
if (scale != 1.0) {
rect.x *= scale;
rect.w *= scale;
}
scale = (double) *height / normalized_height;
if (scale != 1.0) {
rect.y *= scale;
rect.h *= scale;
}
} else {
b_width = qMax(1, qRound(b_width * b_ar / consumer_ar));
}
if (!hasAlpha && (b_width < *width || b_height < *height)) {
hasAlpha = true;
// First instance of a qtblend filter
double scale = mlt_profile_scale_width(profile, *width);
// Store consumer scaling for further uses
mlt_properties_set_int(frame_properties, "qtblend_scaled", 1);
mlt_properties_set_double(frame_properties, "qtblend_preview_scaling", scale);
// Apply scaling
if (scale != 1.0) {
rect.x *= scale;
rect.w *= scale;
if (distort) {
b_width *= scale;
} else {
// Apply consumer scaling to the source image request
b_width *= scale;
b_height *= scale;
}
}
scale = mlt_profile_scale_height(profile, *height);
if (scale != 1.0) {
rect.y *= scale;
rect.h *= scale;
if (distort) {
b_height *= scale;
}
}
}
transform.translate(rect.x, rect.y);
opacity = rect.o;
hasAlpha = rect.o < 1 || rect.x != 0 || rect.y != 0 || rect.w != *width || rect.h != *height
|| rect.w / b_dar < *height || rect.h * b_dar < *width || b_width < *width
|| b_height < *height;
} else {
b_width = *width;
b_height = *height;
Expand Down Expand Up @@ -154,7 +212,6 @@ static int filter_get_image(mlt_frame frame,
// fetch image
*format = mlt_image_rgba;
uint8_t *src_image = NULL;

error = mlt_frame_get_image(frame, &src_image, format, &b_width, &b_height, 0);

// Put source buffer into QImage
Expand All @@ -164,13 +221,12 @@ static int filter_get_image(mlt_frame frame,
int image_size = mlt_image_format_size(*format, *width, *height, NULL);

// resize to rect
if (mlt_properties_get_int(properties, "distort")) {
if (distort) {
transform.scale(rect.w / b_width, rect.h / b_height);
} else {
// Determine scale with respect to aspect ratio.
double geometry_dar = rect.w * consumer_ar / rect.h;
double scale;
if (b_dar > geometry_dar) {
double resize_dar = rect.w * consumer_ar / rect.h;
if (b_dar >= resize_dar) {
scale = rect.w / b_width;
} else {
scale = rect.h / b_height * b_ar;
Expand Down
104 changes: 62 additions & 42 deletions src/modules/qt/transition_qtblend.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* transition_qtblend.cpp -- Qt composite transition
* Copyright (c) 2016 Jean-Baptiste Mardelle <[email protected]>
* Copyright (c) 2016-2025 Jean-Baptiste Mardelle <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -42,8 +42,6 @@ static int get_image(mlt_frame a_frame,
uint8_t *b_image = NULL;
// hasAlpha indicates whether the source material has an alpha channel
bool hasAlpha = *format == mlt_image_rgba;
// forceAlpha is true if some operation makes it mandatory to perform the alpha compositing, like padding or scaling
bool forceAlpha = false;
double opacity = 1.0;
QTransform transform;
// reference rect
Expand All @@ -58,7 +56,9 @@ static int get_image(mlt_frame a_frame,
mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(transition));
int b_width = mlt_properties_get_int(b_properties, "meta.media.width");
int b_height = mlt_properties_get_int(b_properties, "meta.media.height");

bool distort = mlt_properties_get_int(transition_properties, "distort");
double consumer_ar = mlt_profile_sar(profile);

// Check the producer's native format before fetching image
int sourceFormat = mlt_properties_get_int(b_properties, "format");
Expand All @@ -73,13 +73,35 @@ static int get_image(mlt_frame a_frame,
}
double b_ar = mlt_frame_get_aspect_ratio(b_frame);
double b_dar = b_ar * b_width / b_height;
double geometry_dar = consumer_ar * *width / *height;
rect.w = -1;
rect.h = -1;
double transformScale = 1.;
// forceAlpha is true if some operation makes it mandatory to perform the alpha compositing, like padding or scaling
bool forceAlpha = b_dar != geometry_dar;

if (!distort && (b_height < *height || b_width < *width)) {
// Source image is smaller than profile, request full frame
if (b_dar > geometry_dar) {
transformScale = b_dar / geometry_dar;
} else {
transformScale = geometry_dar / b_dar;
}
b_width = *width;
b_height = *height;
}

double scalex = mlt_profile_scale_width(profile, *width);
double scaley = mlt_profile_scale_height(profile, *height);

if (scalex != 1.) {
// We are using consumer scaling, fetch a lower resolution image too
b_height *= scalex;
b_width *= scaley;
}
int request_width = *width;
int request_height = *height;

// Check transform
if (mlt_properties_get(transition_properties, "rect")) {
rect = mlt_properties_anim_get_rect(transition_properties, "rect", position, length);
Expand All @@ -91,33 +113,27 @@ static int get_image(mlt_frame a_frame,
rect.h *= *height;
} else {
// Adjust to preview scaling
double scale = mlt_profile_scale_width(profile, *width);
if (scale != 1.0) {
rect.x *= scale;
rect.w *= scale;
if (scalex != 1.0) {
rect.x *= scalex;
rect.w *= scalex;
if (distort) {
b_width *= scale;
b_width *= scalex;
}
}
scale = mlt_profile_scale_height(profile, *height);
if (scale != 1.0) {
rect.y *= scale;
rect.h *= scale;
if (scaley != 1.0) {
rect.y *= scaley;
rect.h *= scaley;
if (distort) {
b_height *= scale;
b_height *= scaley;
}
}
}

transform.translate(rect.x, rect.y);
opacity = rect.o;
if (!distort) {
b_width = qMin(qRound(rect.w), b_width);
b_height = qMin(qRound(rect.h), b_height);
transform.translate((rect.w - b_width) / 2.0, (rect.h - b_height) / 2.0);
}
if (opacity < 1 || rect.x != 0 || rect.y != 0 || (rect.x + rect.w != *width)
|| (rect.y + rect.h != *height)) {
if (!forceAlpha
&& (opacity < 1 || rect.x != 0 || rect.y != 0 || (rect.x + rect.w != *width)
|| (rect.y + rect.h != *height))) {
// we will process operations on top frame, so also process b_frame
forceAlpha = true;
}
Expand All @@ -129,9 +145,8 @@ static int get_image(mlt_frame a_frame,
b_width = *width;
}

double output_ar = mlt_profile_sar(profile);
if (mlt_frame_get_aspect_ratio(b_frame) == 0) {
mlt_frame_set_aspect_ratio(b_frame, output_ar);
mlt_frame_set_aspect_ratio(b_frame, consumer_ar);
}

if (mlt_properties_get(transition_properties, "rotation")) {
Expand All @@ -157,12 +172,6 @@ static int get_image(mlt_frame a_frame,
if (interps)
interps = strdup(interps);

if (error) {
return error;
}
if (distort && b_width != 0 && b_height != 0) {
transform.scale(rect.w / b_width, rect.h / b_height);
}
// Check profile dar vs image dar image
if (!forceAlpha && rect.w == -1 && b_dar != mlt_profile_dar(profile)) {
// Activate transparency if the clips don't have the same aspect ratio
Expand All @@ -175,8 +184,6 @@ static int get_image(mlt_frame a_frame,
}

// Check if we have transparency
int request_width = b_width;
int request_height = b_height;
bool imageFetched = false;
if (!forceAlpha) {
if (!hasAlpha || *format == mlt_image_rgba) {
Expand Down Expand Up @@ -212,20 +219,36 @@ static int get_image(mlt_frame a_frame,
return 0;
}
}

if (!imageFetched) {
*format = mlt_image_rgba;
error = mlt_frame_get_image(b_frame, &b_image, format, &b_width, &b_height, 0);
}
if (b_frame->convert_image
&& (*format != mlt_image_rgba || b_width != request_width || b_height != request_height)) {
mlt_properties_set_int(b_properties, "convert_image_width", request_width);
mlt_properties_set_int(b_properties, "convert_image_height", request_height);
&& (*format != mlt_image_rgba)) {
b_frame->convert_image(b_frame, &b_image, format, mlt_image_rgba);
b_width = request_width;
b_height = request_height;
}
*format = mlt_image_rgba;
if (distort) {
if (b_width != 0 && b_height != 0) {
transform.scale(rect.w / b_width, rect.h / b_height);
}
} else if (rect.w > 0 && rect.h > 0) {
double resize_dar = rect.w * consumer_ar / rect.h;
double scale;
if (b_dar > resize_dar) {
scale = rect.w / b_width;
if (b_dar < geometry_dar) {
scale *= transformScale;
}
} else {
scale = rect.h / b_height;
if (b_dar > geometry_dar) {
scale *= transformScale;
}
}
transform.translate((rect.w - (b_width * scale)) / 2.0, (rect.h - (b_height * scale)) / 2.0);
transform.scale(scale, scale);
}

// Get bottom frame
uint8_t *a_image = NULL;
Expand All @@ -241,12 +264,9 @@ static int get_image(mlt_frame a_frame,
// Copy bottom frame in output
memcpy(*image, a_image, image_size);

bool hqPainting = false;
if (interps) {
if (strcmp(interps, "bilinear") == 0 || strcmp(interps, "bicubic") == 0) {
hqPainting = true;
}
}
// We don't do subpixel smoothing for nearest neighbour interpolation
// so people can use that to upscale pixel art and keep the hard edges.
bool hqPainting = interps && strcmp(interps, "nearest") != 0;

// convert bottom mlt image to qimage
QImage bottomImg;
Expand Down

0 comments on commit de332f8

Please sign in to comment.