Switch to QImage scaling when downscaling as QPainter is awful#1214
Switch to QImage scaling when downscaling as QPainter is awful#1214ddennedy merged 11 commits intomltframework:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adjusts Qt blending downscaling to avoid poor-quality QPainter scaling (Qt6 regression), by pre-scaling via QImage when reducing image size.
Changes:
- Detects rescale quality preference via
consumer.rescaleand toggles HQ painting accordingly. - Pre-scales the source
QImagewhen downscaling (instead of applying a QPainter scale transform). - Makes QPainter render hints conditional on the chosen interpolation mode and draws either the scaled or original image.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
bmatherly
left a comment
There was a problem hiding this comment.
The artifacts kind of look like the operation is not working on pixels that have some transparency (around the border of the logos). I did not recreate the problem myself. Does it only happen on images with transparency? Or does it still occur if the image background is fully opaque? I'm just wondering if there is some parameter we need to pass to the painter to make sure it handles transparency (alpha) correctly.
|
|
||
| char *interps = mlt_properties_get(frame_properties, "consumer.rescale"); | ||
| if (interps) { | ||
| interps = strdup(interps); |
There was a problem hiding this comment.
Can you explain why you copy the string? I think you can use it directly.
There was a problem hiding this comment.
Using strdup on this string property is a common thing in our source code that dates way back. The property getter returns a pointer, and after the call the string it points to can be changed including NULLed, leading to comparison over-reads or crashes. I think this is more of an issue for a string property on a service getting changed by an application, but these are checking a frame property set by mlt_consumer that is not going to be mutated by another thread.
This technique goes back to a bug fix in 9a19d7b. The bug report is not available, and there are other fixes mixed in with that commit. So, hard to say what was the actual fix. Maybe this was the ultimate fix, and something later actually did.
There was a problem hiding this comment.
This technique goes back to a bug fix in 9a19d7b
Thanks for the reference. I suppose the author (or the AI they used) just followed other examples for interp.
The property getter returns a pointer, and after the call the string it points to can be changed including NULLed, leading to comparison over-reads or crashes.
While that is a true statement, this strdup() trick doesn't fix that because the pointer could still be changed after the get() call and before the strdup call. So this trick only reduces the probability. If data being changed is a concern, then the service should be locked.
I would prefer that we not propagate this unnecessary strdup and start setting a good example for future authors. But I can appreciate that it is following an established convention.
There was a problem hiding this comment.
Yes, I just copied code used in the qtblend transition that was initially based on the affine transition where this is still used. But sure, I will clean that up
There was a problem hiding this comment.
But you removed protection for the string making it worse. If you are going to remove strdup() you need to add mlt_service_lock() before getting it and mlt_service_unlock() after you are done with using it. This must be fixesd.
Looking at the images attached here, even the lines inside the opaque areas have very bad aliasing suggesting no interpolation. |
|
JB, are the images you provided scaled-up for emphasis? In the docs, both QPainter and QImage say the hints provided both do bilinear interpolation: However, Claude suggested this difference: The Qt docs description is misleading/simplified. Despite the "bilinear filtering" wording, Qt's internal implementation of The meaningful difference is:
For a 4× downscale, bilinear reads 4 out of ~16 contributing source pixels; the area-average reads all ~16. The Qt docs use "bilinear" as a loose synonym for "smooth/filtered" vs "nearest-neighbor" — not as a precise description of the algorithm. So the fix is correct: pre-scaling via |
Oh yeah. I see what you mean. |
|
FYI I plan to make a release by April 24 if you want to get this in. |
|
You need to address the comments including the ones from Copilot and provide an overall status (possibly by requesting me to review it). "address" means to give a brief reply and resolve it if resolved. If I see some resolved ones but others unresolved that means not everything is resolved, and this should not be merged. |
|
Yes, sorry I was hoping to give it a review just after pushing my changes, but had to deal with other stuff, should have marked it as draft. Should be ok now, sorry. |
Co-authored-by: Dan Dennedy <dan@dennedy.org>
Co-authored-by: Dan Dennedy <dan@dennedy.org>
|
Thanks for noticing the interpolation error! |

Seems like a Qt6 regression, but QPainter scaling gives terrible results when downscaling. So in such cases, scale using QImage before painting. Kdenlive issue 2123
Comparison of results (updated QImage scaling on top)
