There is actually a need for the origin and end point not to be defined.
We can not automatically insert them with the y value of the first and
last point as it will influence the curves in a wrong way.
Fixes #5397
... | ... |
@@ -5710,10 +5710,6 @@ strictly increasing over the x-axis, and their @var{x} and @var{y} values must |
5710 | 5710 |
be in the @var{[0;1]} interval. If the computed curves happened to go outside |
5711 | 5711 |
the vector spaces, the values will be clipped accordingly. |
5712 | 5712 |
|
5713 |
-If there is no key point defined in @code{x=0}, the filter will automatically |
|
5714 |
-insert a @var{(0;0)} point. In the same way, if there is no key point defined |
|
5715 |
-in @code{x=1}, the filter will automatically insert a @var{(1;1)} point. |
|
5716 |
- |
|
5717 | 5713 |
The filter accepts the following options: |
5718 | 5714 |
|
5719 | 5715 |
@table @option |
... | ... |
@@ -5765,13 +5761,13 @@ defined using the following syntax: @code{x0/y0 x1/y1 x2/y2 ...}. |
5765 | 5765 |
@item |
5766 | 5766 |
Increase slightly the middle level of blue: |
5767 | 5767 |
@example |
5768 |
-curves=blue='0.5/0.58' |
|
5768 |
+curves=blue='0/0 0.5/0.58 1/1' |
|
5769 | 5769 |
@end example |
5770 | 5770 |
|
5771 | 5771 |
@item |
5772 | 5772 |
Vintage effect: |
5773 | 5773 |
@example |
5774 |
-curves=r='0/0.11 .42/.51 1/0.95':g='0.50/0.48':b='0/0.22 .49/.44 1/0.8' |
|
5774 |
+curves=r='0/0.11 .42/.51 1/0.95':g='0/0 0.50/0.48 1/1':b='0/0.22 .49/.44 1/0.8' |
|
5775 | 5775 |
@end example |
5776 | 5776 |
Here we obtain the following coordinates for each components: |
5777 | 5777 |
@table @var |
... | ... |
@@ -5798,7 +5794,7 @@ curves=vintage |
5798 | 5798 |
@item |
5799 | 5799 |
Use a Photoshop preset and redefine the points of the green component: |
5800 | 5800 |
@example |
5801 |
-curves=psfile='MyCurvesPresets/purple.acv':green='0.45/0.53' |
|
5801 |
+curves=psfile='MyCurvesPresets/purple.acv':green='0/0 0.45/0.53 1/1' |
|
5802 | 5802 |
@end example |
5803 | 5803 |
@end itemize |
5804 | 5804 |
|
... | ... |
@@ -110,25 +110,25 @@ static const struct { |
110 | 110 |
const char *master; |
111 | 111 |
} curves_presets[] = { |
112 | 112 |
[PRESET_COLOR_NEGATIVE] = { |
113 |
- "0/1 0.129/1 0.466/0.498 0.725/0 1/0", |
|
114 |
- "0/1 0.109/1 0.301/0.498 0.517/0 1/0", |
|
115 |
- "0/1 0.098/1 0.235/0.498 0.423/0 1/0", |
|
113 |
+ "0.129/1 0.466/0.498 0.725/0", |
|
114 |
+ "0.109/1 0.301/0.498 0.517/0", |
|
115 |
+ "0.098/1 0.235/0.498 0.423/0", |
|
116 | 116 |
}, |
117 | 117 |
[PRESET_CROSS_PROCESS] = { |
118 |
- "0.25/0.156 0.501/0.501 0.686/0.745", |
|
119 |
- "0.25/0.188 0.38/0.501 0.745/0.815 1/0.815", |
|
120 |
- "0.231/0.094 0.709/0.874", |
|
118 |
+ "0/0 0.25/0.156 0.501/0.501 0.686/0.745 1/1", |
|
119 |
+ "0/0 0.25/0.188 0.38/0.501 0.745/0.815 1/0.815", |
|
120 |
+ "0/0 0.231/0.094 0.709/0.874 1/1", |
|
121 | 121 |
}, |
122 |
- [PRESET_DARKER] = { .master = "0.5/0.4" }, |
|
123 |
- [PRESET_INCREASE_CONTRAST] = { .master = "0.149/0.066 0.831/0.905 0.905/0.98" }, |
|
124 |
- [PRESET_LIGHTER] = { .master = "0.4/0.5" }, |
|
125 |
- [PRESET_LINEAR_CONTRAST] = { .master = "0.305/0.286 0.694/0.713" }, |
|
126 |
- [PRESET_MEDIUM_CONTRAST] = { .master = "0.286/0.219 0.639/0.643" }, |
|
122 |
+ [PRESET_DARKER] = { .master = "0/0 0.5/0.4 1/1" }, |
|
123 |
+ [PRESET_INCREASE_CONTRAST] = { .master = "0/0 0.149/0.066 0.831/0.905 0.905/0.98 1/1" }, |
|
124 |
+ [PRESET_LIGHTER] = { .master = "0/0 0.4/0.5 1/1" }, |
|
125 |
+ [PRESET_LINEAR_CONTRAST] = { .master = "0/0 0.305/0.286 0.694/0.713 1/1" }, |
|
126 |
+ [PRESET_MEDIUM_CONTRAST] = { .master = "0/0 0.286/0.219 0.639/0.643 1/1" }, |
|
127 | 127 |
[PRESET_NEGATIVE] = { .master = "0/1 1/0" }, |
128 |
- [PRESET_STRONG_CONTRAST] = { .master = "0.301/0.196 0.592/0.6 0.686/0.737" }, |
|
128 |
+ [PRESET_STRONG_CONTRAST] = { .master = "0/0 0.301/0.196 0.592/0.6 0.686/0.737 1/1" }, |
|
129 | 129 |
[PRESET_VINTAGE] = { |
130 | 130 |
"0/0.11 0.42/0.51 1/0.95", |
131 |
- "0.50/0.48", |
|
131 |
+ "0/0 0.50/0.48 1/1", |
|
132 | 132 |
"0/0.22 0.49/0.44 1/0.8", |
133 | 133 |
} |
134 | 134 |
}; |
... | ... |
@@ -177,28 +177,11 @@ static int parse_points_str(AVFilterContext *ctx, struct keypoint **points, cons |
177 | 177 |
last = point; |
178 | 178 |
} |
179 | 179 |
|
180 |
- /* auto insert first key point if missing at x=0 */ |
|
181 |
- if (!*points) { |
|
182 |
- last = make_point(0, 0, NULL); |
|
183 |
- if (!last) |
|
184 |
- return AVERROR(ENOMEM); |
|
185 |
- last->x = last->y = 0; |
|
186 |
- *points = last; |
|
187 |
- } else if ((*points)->x != 0.) { |
|
188 |
- struct keypoint *newfirst = make_point(0, 0, *points); |
|
189 |
- if (!newfirst) |
|
190 |
- return AVERROR(ENOMEM); |
|
191 |
- *points = newfirst; |
|
192 |
- } |
|
193 |
- |
|
194 |
- av_assert0(last); |
|
195 |
- |
|
196 |
- /* auto insert last key point if missing at x=1 */ |
|
197 |
- if (last->x != 1.) { |
|
198 |
- struct keypoint *point = make_point(1, 1, NULL); |
|
199 |
- if (!point) |
|
200 |
- return AVERROR(ENOMEM); |
|
201 |
- last->next = point; |
|
180 |
+ if (*points && !(*points)->next) { |
|
181 |
+ av_log(ctx, AV_LOG_WARNING, "Only one point (at (%f;%f)) is defined, " |
|
182 |
+ "this is unlikely to behave as you expect. You probably want" |
|
183 |
+ "at least 2 points.", |
|
184 |
+ (*points)->x, (*points)->y); |
|
202 | 185 |
} |
203 | 186 |
|
204 | 187 |
return 0; |
... | ... |
@@ -222,14 +205,28 @@ static int get_nb_points(const struct keypoint *d) |
222 | 222 |
static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint *points) |
223 | 223 |
{ |
224 | 224 |
int i, ret = 0; |
225 |
- const struct keypoint *point; |
|
225 |
+ const struct keypoint *point = points; |
|
226 | 226 |
double xprev = 0; |
227 | 227 |
|
228 |
+ double (*matrix)[3]; |
|
229 |
+ double *h, *r; |
|
228 | 230 |
int n = get_nb_points(points); // number of splines |
229 | 231 |
|
230 |
- double (*matrix)[3] = av_calloc(n, sizeof(*matrix)); |
|
231 |
- double *h = av_malloc((n - 1) * sizeof(*h)); |
|
232 |
- double *r = av_calloc(n, sizeof(*r)); |
|
232 |
+ if (n == 0) { |
|
233 |
+ for (i = 0; i < 256; i++) |
|
234 |
+ y[i] = i; |
|
235 |
+ return 0; |
|
236 |
+ } |
|
237 |
+ |
|
238 |
+ if (n == 1) { |
|
239 |
+ for (i = 0; i < 256; i++) |
|
240 |
+ y[i] = av_clip_uint8(point->y * 255); |
|
241 |
+ return 0; |
|
242 |
+ } |
|
243 |
+ |
|
244 |
+ matrix = av_calloc(n, sizeof(*matrix)); |
|
245 |
+ h = av_malloc((n - 1) * sizeof(*h)); |
|
246 |
+ r = av_calloc(n, sizeof(*r)); |
|
233 | 247 |
|
234 | 248 |
if (!matrix || !h || !r) { |
235 | 249 |
ret = AVERROR(ENOMEM); |
... | ... |
@@ -277,9 +274,14 @@ static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint * |
277 | 277 |
for (i = n - 2; i >= 0; i--) |
278 | 278 |
r[i] = r[i] - matrix[i][AD] * r[i + 1]; |
279 | 279 |
|
280 |
- /* compute the graph with x=[0..255] */ |
|
281 |
- i = 0; |
|
282 | 280 |
point = points; |
281 |
+ |
|
282 |
+ /* left padding */ |
|
283 |
+ for (i = 0; i < (int)(point->x * 255); i++) |
|
284 |
+ y[i] = av_clip_uint8(point->y * 255); |
|
285 |
+ |
|
286 |
+ /* compute the graph with x=[x0..xN] */ |
|
287 |
+ i = 0; |
|
283 | 288 |
av_assert0(point->next); // always at least 2 key points |
284 | 289 |
while (point->next) { |
285 | 290 |
double yc = point->y; |
... | ... |
@@ -300,7 +302,7 @@ static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint * |
300 | 300 |
for (x = x_start; x <= x_end; x++) { |
301 | 301 |
double xx = (x - x_start) * 1/255.; |
302 | 302 |
double yy = a + b*xx + c*xx*xx + d*xx*xx*xx; |
303 |
- y[x] = av_clipf(yy, 0, 1) * 255; |
|
303 |
+ y[x] = av_clip_uint8(yy * 255); |
|
304 | 304 |
av_log(ctx, AV_LOG_DEBUG, "f(%f)=%f -> y[%d]=%d\n", xx, yy, x, y[x]); |
305 | 305 |
} |
306 | 306 |
|
... | ... |
@@ -308,6 +310,10 @@ static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint * |
308 | 308 |
i++; |
309 | 309 |
} |
310 | 310 |
|
311 |
+ /* right padding */ |
|
312 |
+ for (i = (int)(point->x * 255); i <= 255; i++) |
|
313 |
+ y[i] = av_clip_uint8(point->y * 255); |
|
314 |
+ |
|
311 | 315 |
end: |
312 | 316 |
av_free(matrix); |
313 | 317 |
av_free(h); |