You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-11-23 21:54:53 +02:00
lavfi: add drawvg video filter.
The drawvg filter can draw vector graphics on top of a video, using libcairo. It is enabled if FFmpeg is configured with `--enable-cairo`. The language for drawvg scripts is documented in `doc/drawvg-reference.texi`. There are two new tests: - `fate-filter-drawvg-interpreter` launch a script with most commands, and verify which libcairo functions are executed. - `fate-filter-drawvg-video` render a very simple image, just to verify that libcairo is working as expected. Signed-off-by: Ayose <ayosec@gmail.com>
This commit is contained in:
@@ -100,6 +100,7 @@ libavfilter/.*f_ebur128.* @haasn
|
||||
libavfilter/vf_blackdetect.* @haasn
|
||||
libavfilter/vf_colordetect.* @haasn
|
||||
libavfilter/vf_colorspace.* @rbultje
|
||||
libavfilter/.*drawvg.* @ayosec
|
||||
libavfilter/vf_icc.* @haasn
|
||||
libavfilter/vf_libplacebo.* @haasn
|
||||
libavfilter/vf_premultiply.* @haasn
|
||||
@@ -215,4 +216,5 @@ doc/.* @GyanD
|
||||
# tests
|
||||
# =====
|
||||
tests/checkasm/riscv/.* @Courmisch
|
||||
tests/ref/.*drawvg.* @ayosec
|
||||
tests/ref/fate/sub-mcc.* @programmerjake
|
||||
|
||||
@@ -4,6 +4,7 @@ releases are sorted from youngest to oldest.
|
||||
version <next>:
|
||||
- ffprobe -codec option
|
||||
- EXIF Metadata Parsing
|
||||
- drawvg filter (--enable-cairo)
|
||||
- gfxcapture: Windows.Graphics.Capture based window/monitor capture
|
||||
- hxvs demuxer for HXVS/HXVT IP camera format
|
||||
- MPEG-H 3D Audio decoding via mpeghdec
|
||||
|
||||
4
configure
vendored
4
configure
vendored
@@ -200,6 +200,7 @@ External library support:
|
||||
--disable-avfoundation disable Apple AVFoundation framework [autodetect]
|
||||
--enable-avisynth enable reading of AviSynth script files [no]
|
||||
--disable-bzlib disable bzlib [autodetect]
|
||||
--enable-cairo enable cairo [no]
|
||||
--disable-coreimage disable Apple CoreImage framework [autodetect]
|
||||
--enable-chromaprint enable audio fingerprinting with chromaprint [no]
|
||||
--enable-frei0r enable frei0r video filtering [no]
|
||||
@@ -1949,6 +1950,7 @@ EXTERNAL_LIBRARY_LIST="
|
||||
$EXTERNAL_LIBRARY_NONFREE_LIST
|
||||
$EXTERNAL_LIBRARY_VERSION3_LIST
|
||||
$EXTERNAL_LIBRARY_GPLV3_LIST
|
||||
cairo
|
||||
chromaprint
|
||||
gcrypt
|
||||
gnutls
|
||||
@@ -4003,6 +4005,7 @@ dnn_detect_filter_select="dnn"
|
||||
dnn_processing_filter_select="dnn"
|
||||
drawtext_filter_deps="libfreetype libharfbuzz"
|
||||
drawtext_filter_suggest="libfontconfig libfribidi"
|
||||
drawvg_filter_deps="cairo"
|
||||
elbg_filter_deps="avcodec"
|
||||
eq_filter_deps="gpl"
|
||||
erosion_opencl_filter_deps="opencl"
|
||||
@@ -7082,6 +7085,7 @@ done
|
||||
enabled avisynth && { require_headers "avisynth/avisynth_c.h avisynth/avs/version.h" &&
|
||||
{ test_cpp_condition avisynth/avs/version.h "AVS_MAJOR_VER >= 3 && AVS_MINOR_VER >= 7 && AVS_BUGFIX_VER >= 3 || AVS_MAJOR_VER >= 3 && AVS_MINOR_VER > 7 || AVS_MAJOR_VER > 3" ||
|
||||
die "ERROR: AviSynth+ header version must be >= 3.7.3"; } }
|
||||
enabled cairo && require_pkg_config cairo cairo "cairo.h" cairo_create
|
||||
enabled cuda_nvcc && { check_nvcc cuda_nvcc || die "ERROR: failed checking for nvcc."; }
|
||||
enabled chromaprint && { check_pkg_config chromaprint libchromaprint "chromaprint.h" chromaprint_get_version ||
|
||||
require chromaprint chromaprint.h chromaprint_get_version -lchromaprint; }
|
||||
|
||||
@@ -28,6 +28,7 @@ HTMLPAGES = $(AVPROGS-yes:%=doc/%.html) $(AVPROGS-yes:%=doc/%-all.html) $(COMP
|
||||
doc/mailing-list-faq.html \
|
||||
doc/nut.html \
|
||||
doc/platform.html \
|
||||
doc/drawvg-reference.html \
|
||||
$(SRC_PATH)/doc/bootstrap.min.css \
|
||||
$(SRC_PATH)/doc/style.min.css \
|
||||
$(SRC_PATH)/doc/default.css \
|
||||
|
||||
2772
doc/drawvg-reference.texi
Normal file
2772
doc/drawvg-reference.texi
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13049,6 +13049,78 @@ For more information about libfribidi, check:
|
||||
For more information about libharfbuzz, check:
|
||||
@url{https://github.com/harfbuzz/harfbuzz}.
|
||||
|
||||
@anchor{drawvg}
|
||||
@section drawvg
|
||||
|
||||
Draw vector graphics on top of video frames, by executing a script written in
|
||||
a custom language called VGS (@emph{Vector Graphics Script}).
|
||||
|
||||
The documentation for the language can be found in
|
||||
@ref{,,drawvg - Language Reference,drawvg-reference}.
|
||||
|
||||
Graphics are rendered using the @uref{https://cairographics.org/,cario 2D
|
||||
graphics library}.
|
||||
|
||||
To enable compilation of this filter, you need to configure FFmpeg with
|
||||
@code{--enable-cairo}.
|
||||
|
||||
@subsection Parameters
|
||||
|
||||
Either @code{script} or @code{file} must be set.
|
||||
|
||||
@table @option
|
||||
|
||||
@item s, script
|
||||
Script source to draw the graphics.
|
||||
|
||||
@item file
|
||||
Path of the file to load the script source.
|
||||
|
||||
@end table
|
||||
|
||||
@subsection Pixel Formats
|
||||
|
||||
Since Cairo only supports RGB images, if the input video is something else (like
|
||||
YUV 4:2:0), before executing the script the video is converted to a format
|
||||
compatible with Cairo. Then, you have to use use either the @ref{format} filter,
|
||||
or the @code{-pix_fmt} option, to convert it to the expected format in the
|
||||
output.
|
||||
|
||||
@subsection Examples
|
||||
|
||||
@itemize
|
||||
@item
|
||||
Draw the outline of an ellipse.
|
||||
|
||||
@example
|
||||
ffmpeg -i input.webm \
|
||||
-vf 'drawvg=ellipse (w/2) (h/2) (w/3) (h/3) stroke' \
|
||||
-pix_fmt yuv420p \
|
||||
output.webm
|
||||
@end example
|
||||
|
||||
@item
|
||||
|
||||
Draw a square rotating in the middle of the frame.
|
||||
|
||||
The script for drawvg is in a file @code{draw.vgs}:
|
||||
|
||||
@example
|
||||
translate (w/2) (h/2)
|
||||
rotate t
|
||||
rect -100 -100 200 200
|
||||
setcolor red@@0.5
|
||||
fill
|
||||
@end example
|
||||
|
||||
Then:
|
||||
|
||||
@example
|
||||
ffmpeg -i input.webm -vf 'drawvg=file=draw.vgs,format=yuv420p' output.webm
|
||||
@end example
|
||||
|
||||
@end itemize
|
||||
|
||||
@section edgedetect
|
||||
|
||||
Detect and draw edges. The filter uses the Canny Edge Detection algorithm.
|
||||
|
||||
@@ -298,6 +298,7 @@ OBJS-$(CONFIG_DRAWBOX_FILTER) += vf_drawbox.o
|
||||
OBJS-$(CONFIG_DRAWGRAPH_FILTER) += f_drawgraph.o
|
||||
OBJS-$(CONFIG_DRAWGRID_FILTER) += vf_drawbox.o
|
||||
OBJS-$(CONFIG_DRAWTEXT_FILTER) += vf_drawtext.o textutils.o
|
||||
OBJS-$(CONFIG_DRAWVG_FILTER) += vf_drawvg.o textutils.o
|
||||
OBJS-$(CONFIG_EDGEDETECT_FILTER) += vf_edgedetect.o edge_common.o
|
||||
OBJS-$(CONFIG_ELBG_FILTER) += vf_elbg.o
|
||||
OBJS-$(CONFIG_ENTROPY_FILTER) += vf_entropy.o
|
||||
@@ -681,6 +682,10 @@ SKIPHEADERS-$(CONFIG_VULKAN) += vulkan_filter.h
|
||||
TOOLS = graph2dot
|
||||
TESTPROGS = drawutils filtfmts formats integral
|
||||
|
||||
ifdef CONFIG_DRAWVG_FILTER
|
||||
TESTPROGS += drawvg
|
||||
endif
|
||||
|
||||
TOOLS-$(CONFIG_LIBZMQ) += zmqsend
|
||||
|
||||
clean::
|
||||
|
||||
@@ -272,6 +272,7 @@ extern const FFFilter ff_vf_drawbox;
|
||||
extern const FFFilter ff_vf_drawgraph;
|
||||
extern const FFFilter ff_vf_drawgrid;
|
||||
extern const FFFilter ff_vf_drawtext;
|
||||
extern const FFFilter ff_vf_drawvg;
|
||||
extern const FFFilter ff_vf_edgedetect;
|
||||
extern const FFFilter ff_vf_elbg;
|
||||
extern const FFFilter ff_vf_entropy;
|
||||
|
||||
1
libavfilter/tests/.gitignore
vendored
1
libavfilter/tests/.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
/dnn-layer-avgpool
|
||||
/dnn-layer-dense
|
||||
/drawutils
|
||||
/drawvg
|
||||
/filtfmts
|
||||
/formats
|
||||
/integral
|
||||
|
||||
350
libavfilter/tests/drawvg.c
Normal file
350
libavfilter/tests/drawvg.c
Normal file
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <cairo.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
|
||||
static void mock_av_log(void *ptr, int level, const char *fmt, va_list vl) {
|
||||
printf("av_log[%d]: ", level);
|
||||
vprintf(fmt, vl);
|
||||
}
|
||||
|
||||
#include "libavfilter/vf_drawvg.c"
|
||||
|
||||
// Mock for cairo functions.
|
||||
//
|
||||
// `MOCK_FN_n` macros define wrappers for functions that only receive `n`
|
||||
// arguments of type `double`.
|
||||
//
|
||||
// `MOCK_FN_I` macro wrap a function that receives a single integer value.
|
||||
|
||||
struct _cairo {
|
||||
double current_point_x;
|
||||
double current_point_y;
|
||||
};
|
||||
|
||||
static void update_current_point(cairo_t *cr, const char *func, double x, double y) {
|
||||
// Update current point only if the function name contains `_to`.
|
||||
if (strstr(func, "_to") == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strstr(func, "_rel_") == NULL) {
|
||||
cr->current_point_x = x;
|
||||
cr->current_point_y = y;
|
||||
} else {
|
||||
cr->current_point_x += x;
|
||||
cr->current_point_y += y;
|
||||
}
|
||||
}
|
||||
|
||||
#define MOCK_FN_0(func) \
|
||||
void func(cairo_t* cr) { \
|
||||
puts(#func); \
|
||||
}
|
||||
|
||||
#define MOCK_FN_1(func) \
|
||||
void func(cairo_t* cr, double a0) { \
|
||||
printf(#func " %.1f\n", a0); \
|
||||
}
|
||||
|
||||
#define MOCK_FN_2(func) \
|
||||
void func(cairo_t* cr, double a0, double a1) { \
|
||||
update_current_point(cr, #func, a0, a1); \
|
||||
printf(#func " %.1f %.1f\n", a0, a1); \
|
||||
}
|
||||
|
||||
#define MOCK_FN_4(func) \
|
||||
void func(cairo_t* cr, double a0, double a1, double a2, double a3) { \
|
||||
printf(#func " %.1f %.1f %.1f %.1f\n", a0, a1, a2, a3); \
|
||||
}
|
||||
|
||||
#define MOCK_FN_5(func) \
|
||||
void func(cairo_t* cr, double a0, double a1, double a2, double a3, double a4) { \
|
||||
printf(#func " %.1f %.1f %.1f %.1f %.1f\n", a0, a1, a2, a3, a4); \
|
||||
}
|
||||
|
||||
#define MOCK_FN_6(func) \
|
||||
void func(cairo_t* cr, double a0, double a1, double a2, double a3, double a4, double a5) { \
|
||||
update_current_point(cr, #func, a4, a5); \
|
||||
printf(#func " %.1f %.1f %.1f %.1f %.1f %.1f\n", a0, a1, a2, a3, a4, a5); \
|
||||
}
|
||||
|
||||
#define MOCK_FN_I(func, type) \
|
||||
void func(cairo_t* cr, type i) { \
|
||||
printf(#func " %d\n", (int)i); \
|
||||
}
|
||||
|
||||
MOCK_FN_5(cairo_arc);
|
||||
MOCK_FN_0(cairo_clip);
|
||||
MOCK_FN_0(cairo_clip_preserve);
|
||||
MOCK_FN_0(cairo_close_path);
|
||||
MOCK_FN_6(cairo_curve_to);
|
||||
MOCK_FN_0(cairo_fill);
|
||||
MOCK_FN_0(cairo_fill_preserve);
|
||||
MOCK_FN_0(cairo_identity_matrix);
|
||||
MOCK_FN_2(cairo_line_to);
|
||||
MOCK_FN_2(cairo_move_to);
|
||||
MOCK_FN_0(cairo_new_path);
|
||||
MOCK_FN_0(cairo_new_sub_path);
|
||||
MOCK_FN_4(cairo_rectangle);
|
||||
MOCK_FN_6(cairo_rel_curve_to);
|
||||
MOCK_FN_2(cairo_rel_line_to);
|
||||
MOCK_FN_2(cairo_rel_move_to);
|
||||
MOCK_FN_0(cairo_reset_clip);
|
||||
MOCK_FN_0(cairo_restore);
|
||||
MOCK_FN_1(cairo_rotate);
|
||||
MOCK_FN_0(cairo_save);
|
||||
MOCK_FN_2(cairo_scale);
|
||||
MOCK_FN_I(cairo_set_fill_rule, cairo_fill_rule_t);
|
||||
MOCK_FN_1(cairo_set_font_size);
|
||||
MOCK_FN_I(cairo_set_line_cap, cairo_line_cap_t);
|
||||
MOCK_FN_I(cairo_set_line_join, cairo_line_join_t);
|
||||
MOCK_FN_1(cairo_set_line_width);
|
||||
MOCK_FN_1(cairo_set_miter_limit);
|
||||
MOCK_FN_4(cairo_set_source_rgba);
|
||||
MOCK_FN_0(cairo_stroke);
|
||||
MOCK_FN_0(cairo_stroke_preserve);
|
||||
MOCK_FN_2(cairo_translate);
|
||||
|
||||
cairo_bool_t cairo_get_dash_count(cairo_t *cr) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
cairo_status_t cairo_status(cairo_t *cr) {
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void cairo_get_dash(cairo_t *cr, double *dashes, double *offset) {
|
||||
// Return a dummy value to verify that it is included in
|
||||
// the next call to `cairo_set_dash`.
|
||||
*dashes = -1;
|
||||
|
||||
if (offset)
|
||||
*offset = -2;
|
||||
}
|
||||
|
||||
void cairo_set_dash(cairo_t *cr, const double *dashes, int num_dashes, double offset) {
|
||||
printf("%s [", __func__);
|
||||
for (int i = 0; i < num_dashes; i++)
|
||||
printf(" %.1f", dashes[i]);
|
||||
printf(" ] %.1f\n", offset);
|
||||
}
|
||||
|
||||
cairo_bool_t cairo_has_current_point(cairo_t *cr) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cairo_get_current_point(cairo_t *cr, double *x, double *y) {
|
||||
*x = cr->current_point_x;
|
||||
*y = cr->current_point_y;
|
||||
}
|
||||
|
||||
void cairo_set_source(cairo_t *cr, cairo_pattern_t *source) {
|
||||
int count;
|
||||
double r, g, b, a;
|
||||
double x0, y0, x1, y1, r0, r1;
|
||||
|
||||
printf("%s", __func__);
|
||||
|
||||
#define PRINT_COLOR(prefix) \
|
||||
printf(prefix "#%02x%02x%02x%02x", (int)(r*255), (int)(g*255), (int)(b*255), (int)(a*255))
|
||||
|
||||
switch (cairo_pattern_get_type(source)) {
|
||||
case CAIRO_PATTERN_TYPE_SOLID:
|
||||
cairo_pattern_get_rgba(source, &r, &g, &b, &a);
|
||||
PRINT_COLOR(" ");
|
||||
break;
|
||||
|
||||
case CAIRO_PATTERN_TYPE_LINEAR:
|
||||
cairo_pattern_get_linear_points(source, &x0, &y0, &x1, &y1);
|
||||
printf(" lineargrad(%.1f %.1f %.1f %.1f)", x0, y0, x1, y1);
|
||||
break;
|
||||
|
||||
case CAIRO_PATTERN_TYPE_RADIAL:
|
||||
cairo_pattern_get_radial_circles(source, &x0, &y0, &r0, &x1, &y1, &r1);
|
||||
printf(" radialgrad(%.1f %.1f %.1f %.1f %.1f %.1f)", x0, y0, r0, x1, y1, r1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cairo_pattern_get_color_stop_count(source, &count) == CAIRO_STATUS_SUCCESS) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
cairo_pattern_get_color_stop_rgba(source, i, &x0, &r, &g, &b, &a);
|
||||
printf(" %.1f/", x0);
|
||||
PRINT_COLOR("");
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Verify that the `vgs_commands` array is sorted, so it can
|
||||
// be used with `bsearch(3)`.
|
||||
static void check_sorted_cmds_array(void) {
|
||||
int failures = 0;
|
||||
|
||||
for (int i = 0; i < FF_ARRAY_ELEMS(vgs_commands) - 1; i++) {
|
||||
if (vgs_comp_command_spec(&vgs_commands[i], &vgs_commands[i]) != 0) {
|
||||
printf("%s: comparator must return 0 for item %d\n", __func__, i);
|
||||
failures++;
|
||||
}
|
||||
|
||||
if (vgs_comp_command_spec(&vgs_commands[i], &vgs_commands[i + 1]) >= 0) {
|
||||
printf("%s: entry for '%s' must appear after '%s', at index %d\n",
|
||||
__func__, vgs_commands[i].name, vgs_commands[i + 1].name, i);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s: %d failures\n", __func__, failures);
|
||||
}
|
||||
|
||||
// Compile and run a script.
|
||||
static void check_script(int is_file, const char* source) {
|
||||
int ret;
|
||||
|
||||
AVDictionary *metadata = NULL;
|
||||
|
||||
struct VGSEvalState state;
|
||||
struct VGSParser parser;
|
||||
struct VGSProgram program;
|
||||
|
||||
struct _cairo cairo_ctx = { 0, 0 };
|
||||
|
||||
if (is_file) {
|
||||
uint8_t *s = NULL;
|
||||
|
||||
printf("\n--- %s: %s\n", __func__, av_basename(source));
|
||||
|
||||
ret = ff_load_textfile(NULL, source, &s, NULL);
|
||||
if (ret != 0) {
|
||||
printf("Failed to read %s: %d\n", source, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
source = s;
|
||||
} else {
|
||||
printf("\n--- %s: %s\n", __func__, source);
|
||||
}
|
||||
|
||||
ret = av_dict_parse_string(&metadata, "m.a=1:m.b=2", "=", ":", 0);
|
||||
av_assert0(ret == 0);
|
||||
|
||||
vgs_parser_init(&parser, source);
|
||||
|
||||
ret = vgs_parse(NULL, &parser, &program, 0);
|
||||
|
||||
int init_ret = vgs_eval_state_init(&state, &program, NULL, NULL);
|
||||
av_assert0(init_ret == 0);
|
||||
|
||||
for (int i = 0; i < VAR_COUNT; i++)
|
||||
state.vars[i] = 1 << i;
|
||||
|
||||
vgs_parser_free(&parser);
|
||||
|
||||
if (ret != 0) {
|
||||
printf("%s: vgs_parse = %d\n", __func__, ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
state.metadata = metadata;
|
||||
state.cairo_ctx = &cairo_ctx;
|
||||
|
||||
ret = vgs_eval(&state, &program);
|
||||
vgs_eval_state_free(&state);
|
||||
|
||||
if (ret != 0)
|
||||
printf("%s: vgs_eval = %d\n", __func__, ret);
|
||||
|
||||
exit:
|
||||
av_dict_free(&metadata);
|
||||
|
||||
if (is_file)
|
||||
av_free((void*)source);
|
||||
|
||||
vgs_free(&program);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
char buf[512];
|
||||
|
||||
av_log_set_callback(mock_av_log);
|
||||
|
||||
check_sorted_cmds_array();
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
check_script(1, argv[i]);
|
||||
|
||||
// Detect unclosed expressions.
|
||||
check_script(0, "M 0 (1*(t+1)");
|
||||
|
||||
// Invalid command.
|
||||
check_script(0, "save invalid 1 2");
|
||||
|
||||
// Invalid constant.
|
||||
check_script(0, "setlinecap unknown m 10 20");
|
||||
|
||||
// Missing arguments.
|
||||
check_script(0, "M 0 1 2");
|
||||
|
||||
// Invalid variable names.
|
||||
check_script(0, "setvar ba^d 0");
|
||||
|
||||
// Reserved names.
|
||||
check_script(0, "setvar cx 0");
|
||||
|
||||
// Max number of user variables.
|
||||
memset(buf, 0, sizeof(buf));
|
||||
for (int i = 0; i < USER_VAR_COUNT; i++) {
|
||||
av_strlcatf(buf, sizeof(buf), " setvar v%d %d", i, i);
|
||||
}
|
||||
av_strlcatf(buf, sizeof(buf), " M (v0) (v%d) 1 (unknown_var)", USER_VAR_COUNT - 1);
|
||||
check_script(0, buf);
|
||||
|
||||
// Too many variables.
|
||||
memset(buf, 0, sizeof(buf));
|
||||
for (int i = 0; i < USER_VAR_COUNT + 1; i++) {
|
||||
av_strlcatf(buf, sizeof(buf), " setvar v%d %d", i + 1, i);
|
||||
}
|
||||
check_script(0, buf);
|
||||
|
||||
// Invalid procedure names.
|
||||
check_script(0, "call a");
|
||||
check_script(0, "proc a { call b } call a");
|
||||
|
||||
// Invalid arguments list.
|
||||
check_script(0, "proc p0 a1 a2 a3 a4 a5 a6 a7 a8 { break }");
|
||||
check_script(0, "proc p0 a1 a2 { break } call p0 break");
|
||||
check_script(0, "proc p0 a1 a2 { break } call p0 1 2 3");
|
||||
|
||||
// Long expressions.
|
||||
memset(buf, 0, sizeof(buf));
|
||||
strncat(buf, "M 0 (1", sizeof(buf) - 1);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
strncat(buf, " + n", sizeof(buf) - 1);
|
||||
}
|
||||
strncat(buf, ")", sizeof(buf) - 1);
|
||||
check_script(0, buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
2708
libavfilter/vf_drawvg.c
Normal file
2708
libavfilter/vf_drawvg.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -452,6 +452,13 @@ fate-filter-fps-down-eof-pass: CMD = framecrc -lavfi testsrc2=r=7:d=3.5,fps=3:eo
|
||||
fate-filter-fps-start-drop: CMD = framecrc -lavfi testsrc2=r=7:d=3.5,fps=3:start_time=1.5
|
||||
fate-filter-fps-start-fill: CMD = framecrc -lavfi testsrc2=r=7:d=1.5,setpts=PTS+14,fps=3:start_time=1.5
|
||||
|
||||
DRAWVG_SCRIPT_ALL = $(SRC_PATH)/tests/ref/lavf/drawvg.all
|
||||
|
||||
FATE_FILTER-$(CONFIG_DRAWVG_FILTER) += fate-filter-drawvg-interpreter
|
||||
fate-filter-drawvg-interpreter: $(DRAWVG_SCRIPT_ALL)
|
||||
fate-filter-drawvg-interpreter: libavfilter/tests/drawvg$(EXESUF)
|
||||
fate-filter-drawvg-interpreter: CMD = run libavfilter/tests/drawvg$(EXESUF) $(DRAWVG_SCRIPT_ALL)
|
||||
|
||||
FATE_FILTER_SAMPLES-$(call FILTERDEMDEC, FPS SCALE, MOV, QTRLE) += fate-filter-fps-cfr fate-filter-fps
|
||||
fate-filter-fps-cfr: CMD = framecrc -auto_conversion_filters -i $(TARGET_SAMPLES)/qtrle/apple-animation-variable-fps-bug.mov -r 30 -fps_mode cfr -pix_fmt yuv420p
|
||||
fate-filter-fps: CMD = framecrc -auto_conversion_filters -i $(TARGET_SAMPLES)/qtrle/apple-animation-variable-fps-bug.mov -vf fps=30 -pix_fmt yuv420p
|
||||
@@ -602,6 +609,11 @@ fate-filter-tiltandshift-410: CMD = framecrc -c:v pgmyuv -i $(SRC) -flags +bitex
|
||||
fate-filter-tiltandshift-422: CMD = framecrc -c:v pgmyuv -i $(SRC) -flags +bitexact -vf scale=sws_flags=+accurate_rnd+bitexact,format=yuv422p,tiltandshift
|
||||
fate-filter-tiltandshift-444: CMD = framecrc -c:v pgmyuv -i $(SRC) -flags +bitexact -vf scale=sws_flags=+accurate_rnd+bitexact,format=yuv444p,tiltandshift
|
||||
|
||||
DRAWVG_SCRIPT_LINES = $(SRC_PATH)/tests/ref/lavf/drawvg.lines
|
||||
FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_DRAWVG_FILTER) += fate-filter-drawvg-video
|
||||
fate-filter-drawvg-video: $(DRAWVG_SCRIPT_LINES)
|
||||
fate-filter-drawvg-video: CMD = video_filter scale,format=bgr0,drawvg=file=$(DRAWVG_SCRIPT_LINES)
|
||||
|
||||
tests/pixfmts.mak: TAG = GEN
|
||||
tests/pixfmts.mak: ffmpeg$(PROGSSUF)$(EXESUF) | tests
|
||||
$(M)printf "PIXFMTS = " > $@
|
||||
|
||||
130
tests/ref/fate/filter-drawvg-interpreter
Normal file
130
tests/ref/fate/filter-drawvg-interpreter
Normal file
@@ -0,0 +1,130 @@
|
||||
check_sorted_cmds_array: 0 failures
|
||||
|
||||
--- check_script: drawvg.all
|
||||
cairo_set_line_join 0
|
||||
cairo_set_line_cap 1
|
||||
cairo_move_to 0.0 0.0
|
||||
cairo_rel_line_to 0.0 0.0
|
||||
cairo_rel_line_to 1.0 1.0
|
||||
cairo_rel_line_to 2.0 2.0
|
||||
cairo_line_to -1.0 -2.0
|
||||
av_log[32]: [29:7] 1 = 1.000000 | [29:9] foo = -1.000000 | [29:13] (bar - 2) = -4.000000
|
||||
cairo_set_source lineargrad(0.0 0.0 1.0 1.0) 0.0/#ff0000ff 1.0/#0000ffff
|
||||
cairo_save
|
||||
cairo_restore
|
||||
cairo_scale 1.0 1.0
|
||||
cairo_scale 2.0 3.0
|
||||
cairo_translate 4.0 5.0
|
||||
cairo_rotate 6.0
|
||||
cairo_identity_matrix
|
||||
cairo_rectangle 1.0 2.0 3.0 4.0
|
||||
cairo_save
|
||||
cairo_translate 1.0 2.0
|
||||
cairo_new_sub_path
|
||||
cairo_arc 0.0 0.0 3.0 0.0 6.3
|
||||
cairo_close_path
|
||||
cairo_new_sub_path
|
||||
cairo_restore
|
||||
cairo_new_sub_path
|
||||
cairo_arc 150.0 150.0 50.0 3.1 4.7
|
||||
cairo_arc 450.0 150.0 50.0 4.7 6.3
|
||||
cairo_arc 450.0 450.0 50.0 0.0 1.6
|
||||
cairo_arc 150.0 450.0 50.0 1.6 3.1
|
||||
cairo_close_path
|
||||
cairo_set_source #abcdefff
|
||||
cairo_stroke
|
||||
cairo_set_source lineargrad(0.0 1.0 2.0 3.0) 0.0/#ff0000ff 1.0/#0000ffff
|
||||
cairo_stroke
|
||||
cairo_set_source radialgrad(1.0 2.0 3.0 4.0 5.0 6.0) 0.0/#000000ff 0.1/#ffffffff 0.1/#000000ff 0.2/#ffffffff
|
||||
cairo_stroke
|
||||
cairo_move_to 10.0 50.0
|
||||
cairo_curve_to 20.0 33.3 30.0 33.3 40.0 50.0
|
||||
cairo_curve_to 50.0 66.7 60.0 66.7 70.0 50.0
|
||||
cairo_curve_to 80.0 33.3 90.0 33.3 100.0 50.0
|
||||
cairo_curve_to 120.0 100.0 140.0 0.0 200.0 50.0
|
||||
cairo_set_fill_rule 0
|
||||
cairo_fill_preserve
|
||||
cairo_set_fill_rule 1
|
||||
cairo_fill_preserve
|
||||
cairo_stroke_preserve
|
||||
cairo_set_fill_rule 0
|
||||
cairo_clip_preserve
|
||||
cairo_set_fill_rule 0
|
||||
cairo_fill
|
||||
cairo_set_fill_rule 1
|
||||
cairo_fill
|
||||
cairo_set_fill_rule 0
|
||||
cairo_clip
|
||||
cairo_set_fill_rule 1
|
||||
cairo_clip
|
||||
cairo_set_dash [ -1.0 1.0 ] -2.0
|
||||
cairo_set_dash [ -1.0 2.0 ] -2.0
|
||||
cairo_set_dash [ -1.0 3.0 ] -2.0
|
||||
cairo_set_dash [ -1.0 ] 4.0
|
||||
cairo_set_dash [ ] 0.0
|
||||
cairo_move_to 1.0 2.0
|
||||
cairo_rel_line_to -1.0 -2.0
|
||||
cairo_set_source #19334c66
|
||||
cairo_set_fill_rule 0
|
||||
cairo_fill
|
||||
cairo_set_source #475b3d66
|
||||
cairo_set_fill_rule 0
|
||||
cairo_fill
|
||||
cairo_set_source #7f99b2cc
|
||||
cairo_set_fill_rule 0
|
||||
cairo_fill
|
||||
cairo_set_source #a8d7efe5
|
||||
cairo_set_fill_rule 0
|
||||
cairo_fill
|
||||
cairo_rel_line_to 1.0 3.0
|
||||
cairo_rel_line_to nan 0.0
|
||||
|
||||
--- check_script: M 0 (1*(t+1)
|
||||
av_log[16]: Invalid token '(' at line 1, column 5: Unmatched parenthesis.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: save invalid 1 2
|
||||
av_log[16]: Invalid token 'invalid' at line 1, column 6: Expected command.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: setlinecap unknown m 10 20
|
||||
av_log[16]: Invalid token 'unknown' at line 1, column 12: Expected one of 'butt' 'round' 'square'.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: M 0 1 2
|
||||
av_log[16]: Invalid token '<EOF>' at line 1, column 8: Expected numeric argument.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: setvar ba^d 0
|
||||
av_log[16]: Invalid token 'ba^d' at line 1, column 8: Invalid variable name.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: setvar cx 0
|
||||
av_log[16]: Invalid token 'cx' at line 1, column 8: Reserved variable name.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: setvar v0 0 setvar v1 1 setvar v2 2 setvar v3 3 setvar v4 4 setvar v5 5 setvar v6 6 setvar v7 7 setvar v8 8 setvar v9 9 setvar v10 10 setvar v11 11 setvar v12 12 setvar v13 13 setvar v14 14 setvar v15 15 setvar v16 16 setvar v17 17 setvar v18 18 setvar v19 19 M (v0) (v19) 1 (unknown_var)
|
||||
av_log[16]: Undefined constant or missing '(' in 'unknown_var)'
|
||||
av_log[16]: Invalid token '(unknown_var)' at line 1, column 277: Invalid expression.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: setvar v1 0 setvar v2 1 setvar v3 2 setvar v4 3 setvar v5 4 setvar v6 5 setvar v7 6 setvar v8 7 setvar v9 8 setvar v10 9 setvar v11 10 setvar v12 11 setvar v13 12 setvar v14 13 setvar v15 14 setvar v16 15 setvar v17 16 setvar v18 17 setvar v19 18 setvar v20 19 setvar v21 20
|
||||
av_log[16]: Invalid token 'v21' at line 1, column 270: Too many user variables. Can define up to 20 variables.
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: call a
|
||||
av_log[16]: Missing body for procedure 'a'
|
||||
|
||||
--- check_script: proc a { call b } call a
|
||||
av_log[16]: Missing body for procedure 'b'
|
||||
|
||||
--- check_script: proc p0 a1 a2 a3 a4 a5 a6 a7 a8 { break }
|
||||
av_log[16]: Invalid token 'a7' at line 1, column 27: Too many parameters. Limit is 6
|
||||
check_script: vgs_parse = -22
|
||||
|
||||
--- check_script: proc p0 a1 a2 { break } call p0 break
|
||||
av_log[16]: Procedure expects 2 arguments, but received 0.
|
||||
--- check_script: proc p0 a1 a2 { break } call p0 1 2 3
|
||||
av_log[16]: Procedure expects 2 arguments, but received 3.
|
||||
--- check_script: M 0 (1 + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n + n)
|
||||
cairo_move_to 0.0 101.0
|
||||
1
tests/ref/fate/filter-drawvg-video
Normal file
1
tests/ref/fate/filter-drawvg-video
Normal file
@@ -0,0 +1 @@
|
||||
drawvg-video caa7642950ab2fb1367bd28c287f31bd
|
||||
100
tests/ref/lavf/drawvg.all
Normal file
100
tests/ref/lavf/drawvg.all
Normal file
@@ -0,0 +1,100 @@
|
||||
// Script to test how drawvg instructions are translated to cairo functions,
|
||||
// for `make fate-filter-drawvg-interpreter`.
|
||||
|
||||
// Comments.
|
||||
lineargrad 0 0 1 1
|
||||
colorstop 0 red // after a statement
|
||||
colorstop
|
||||
1 // in the middle of a statement
|
||||
blue
|
||||
|
||||
|
||||
// Constants.
|
||||
setlinejoin miter
|
||||
setlinecap round
|
||||
|
||||
// if/repeat
|
||||
M 0 0
|
||||
repeat 10 {
|
||||
if (eq(i,3)) { break }
|
||||
l (i) (i)
|
||||
}
|
||||
|
||||
// User variables.
|
||||
setvar foo -1
|
||||
setvar bar -2
|
||||
lineto foo (bar)
|
||||
|
||||
// Print
|
||||
print 1 foo (bar - 2)
|
||||
|
||||
// State
|
||||
save
|
||||
restore
|
||||
|
||||
// Transformation matrix.
|
||||
scale 1
|
||||
scalexy 2 3
|
||||
translate 4 5
|
||||
rotate 6
|
||||
resetmatrix
|
||||
|
||||
// Basic shapes
|
||||
rect 1 2 3 4
|
||||
circle 1 2 3
|
||||
roundedrect 100 100 400 400 50
|
||||
|
||||
// Sources
|
||||
setcolor #abcdef
|
||||
stroke
|
||||
|
||||
lineargrad 0 1 2 3
|
||||
colorstop 0 red 1 blue
|
||||
stroke
|
||||
|
||||
radialgrad 1 2 3 4 5 6
|
||||
repeat 2 { colorstop (i/10) black ((i+1)/10) white }
|
||||
stroke
|
||||
|
||||
// Curves. The next line should be compatible with SVG's <path>.
|
||||
M 10,50 Q 25,25 40,50 t 30,0 30,0 c 20 50 40 -50 100 0
|
||||
|
||||
// Preserve
|
||||
preserve fill
|
||||
preserve eofill
|
||||
preserve stroke
|
||||
preserve clip
|
||||
|
||||
// Fill/clip
|
||||
fill eofill
|
||||
clip eoclip
|
||||
|
||||
// Dashes
|
||||
setdash 1 2 3
|
||||
setdashoffset 4
|
||||
resetdash
|
||||
|
||||
// Procedures
|
||||
setvar a -1
|
||||
setvar b -2
|
||||
proc f2 a b { M a b break call invalid }
|
||||
proc f1 a { call f2 a 2 }
|
||||
proc f0 { call f1 1 }
|
||||
call f0
|
||||
l a b
|
||||
|
||||
// Colors
|
||||
setrgba 0.1 0.2 0.3 0.4 fill
|
||||
sethsla 100 0.2 0.3 0.4 fill
|
||||
|
||||
defrgba c0 0.5 0.6 0.7 0.8
|
||||
defhsla c1 200 0.7 0.8 0.9
|
||||
|
||||
setcolor c0 fill
|
||||
setcolor c1 fill
|
||||
|
||||
// Frame metadata
|
||||
getmetadata md0 m.a
|
||||
getmetadata md1 m.b
|
||||
getmetadata md2 m.c
|
||||
l md0 (md1 + 1) md2 0
|
||||
10
tests/ref/lavf/drawvg.lines
Normal file
10
tests/ref/lavf/drawvg.lines
Normal file
@@ -0,0 +1,10 @@
|
||||
// Render a square, for `make fate-filter-drawvg-video`.
|
||||
|
||||
M 10 10
|
||||
l 0 10
|
||||
h 10
|
||||
v -10
|
||||
h -10
|
||||
z
|
||||
setcolor blue
|
||||
stroke
|
||||
Reference in New Issue
Block a user