Skip to content

Commit 863e0c5

Browse files
committed
Fix Brightness on the track below a clip with transparency
As reported here: https://forum.shotcut.org/t/brightness-on-the-track-below-a-clip-with-transparency/50806 The problem is that when a level is applied to YUV, it results in a different brightness than when that same level is applied to RGB This change supports a fix for that in a few ways: * Make sure all alpha processing uses the same math regardless of format * Make sure all the RGB processing uses the same math * Add a new "rgb_only" parameter that allows the application to specify that brightness processing must always be done in RGB space
1 parent 7d7b199 commit 863e0c5

File tree

2 files changed

+35
-15
lines changed

2 files changed

+35
-15
lines changed

src/modules/core/filter_brightness.c

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* filter_brightness.c -- brightness, fade, and opacity filter
3-
* Copyright (C) 2003-2025 Meltytech, LLC
3+
* Copyright (C) 2003-2026 Meltytech, LLC
44
*
55
* This library is free software; you can redistribute it and/or
66
* modify it under the terms of the GNU Lesser General Public
@@ -43,8 +43,8 @@ static int sliced_proc(int id, int index, int jobs, void *cookie)
4343

4444
// Only process if level is something other than 1
4545
if (ctx->level != 1.0) {
46-
int32_t m = ctx->level * (1 << 16);
4746
if (ctx->image->format == mlt_image_yuv422) {
47+
int32_t m = ctx->level * (1 << 16);
4848
int32_t n = 128 * ((1 << 16) - m);
4949
int min = ctx->full_range ? 0 : 16;
5050
int max_luma = ctx->full_range ? 255 : 235;
@@ -63,9 +63,9 @@ static int sliced_proc(int id, int index, int jobs, void *cookie)
6363
uint8_t *p = ctx->image->planes[0]
6464
+ ((slice_line_start + line) * ctx->image->strides[0]);
6565
for (int pixel = 0; pixel < ctx->image->width; pixel++) {
66-
p[0] = CLAMP((p[0] * m) >> 16, 0, 255);
67-
p[1] = CLAMP((p[1] * m) >> 16, 0, 255);
68-
p[2] = CLAMP((p[2] * m) >> 16, 0, 255);
66+
p[0] = CLAMP(round((double) p[0] * ctx->level), 0, 255);
67+
p[1] = CLAMP(round((double) p[1] * ctx->level), 0, 255);
68+
p[2] = CLAMP(round((double) p[2] * ctx->level), 0, 255);
6969
p += 4;
7070
}
7171
}
@@ -74,9 +74,9 @@ static int sliced_proc(int id, int index, int jobs, void *cookie)
7474
uint8_t *p = ctx->image->planes[0]
7575
+ ((slice_line_start + line) * ctx->image->strides[0]);
7676
for (int pixel = 0; pixel < ctx->image->width; pixel++) {
77-
p[0] = CLAMP((p[0] * m) >> 16, 0, 255);
78-
p[1] = CLAMP((p[1] * m) >> 16, 0, 255);
79-
p[2] = CLAMP((p[2] * m) >> 16, 0, 255);
77+
p[0] = CLAMP(round((double) p[0] * ctx->level), 0, 255);
78+
p[1] = CLAMP(round((double) p[1] * ctx->level), 0, 255);
79+
p[2] = CLAMP(round((double) p[2] * ctx->level), 0, 255);
8080
p += 3;
8181
}
8282
}
@@ -96,14 +96,13 @@ static int sliced_proc(int id, int index, int jobs, void *cookie)
9696

9797
// Process the alpha channel if requested.
9898
if (ctx->alpha_level != 1.0) {
99-
int32_t m = ctx->alpha_level * (1 << 16);
10099
if (ctx->image->format == mlt_image_rgba) {
101100
for (int line = 0; line < slice_height; line++) {
102101
uint8_t *p = ctx->image->planes[0]
103102
+ ((slice_line_start + line) * ctx->image->strides[0]) + 3;
104-
for (int pixel = 0; pixel < ctx->image->width; pixel++) {
105-
*p = CLAMP((*p * m) >> 16, 0, 255);
106-
p += 4;
103+
int components_in_row = ctx->image->width * 4;
104+
for (int col = 0; col < components_in_row; col += 4) {
105+
p[col] = CLAMP(round((double) p[col] * ctx->alpha_level), 0, 255);
107106
}
108107
}
109108
} else if (ctx->image->format == mlt_image_rgba64) {
@@ -120,8 +119,7 @@ static int sliced_proc(int id, int index, int jobs, void *cookie)
120119
uint8_t *p = ctx->image->planes[3]
121120
+ ((slice_line_start + line) * ctx->image->strides[3]);
122121
for (int pixel = 0; pixel < ctx->image->width; pixel++) {
123-
*p = CLAMP((*p * m) >> 16, 0, 255);
124-
p++;
122+
p[pixel] = CLAMP(round((double) p[pixel] * ctx->alpha_level), 0, 255);
125123
}
126124
}
127125
}
@@ -169,19 +167,31 @@ static int filter_get_image(mlt_frame frame,
169167
case mlt_image_rgb:
170168
case mlt_image_rgba:
171169
case mlt_image_rgba64:
170+
break;
172171
case mlt_image_yuv422:
172+
if (mlt_properties_get_int(properties, "rgb_only")) {
173+
*format = mlt_image_rgba;
174+
}
173175
break;
174176
case mlt_image_movit:
175177
case mlt_image_opengl_texture:
176178
*format = mlt_image_rgba;
177179
break;
178180
case mlt_image_none:
179181
case mlt_image_invalid:
182+
*format = mlt_image_rgba;
183+
break;
180184
case mlt_image_yuv420p:
185+
if (mlt_properties_get_int(properties, "rgb_only")) {
186+
*format = mlt_image_rgba;
187+
} else {
188+
*format = mlt_image_yuv422;
189+
}
190+
break;
181191
case mlt_image_yuv422p16:
182192
case mlt_image_yuv420p10:
183193
case mlt_image_yuv444p10:
184-
*format = mlt_image_yuv422;
194+
*format = mlt_image_rgba64;
185195
break;
186196
}
187197
}

src/modules/core/filter_brightness.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,13 @@ parameters:
5353
CPUs. Otherwise, set the number of threads to use up to the slice count.
5454
minimum: 0
5555
default: 0
56+
57+
- identifier: rgb_only
58+
title: RGB Only
59+
description: >
60+
When enabled, the filter will only operate on RGB even if YUV is requested.
61+
Applying level changes to YUV does not result in the same brightness
62+
as applying the same level to RGB. This option ensures consistent brightness
63+
in case the image format changes.
64+
type: boolean
65+
default: 0

0 commit comments

Comments
 (0)