From: Stefano Sabatini <stefasab@gmail.com>
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Subject: Re: [FFmpeg-devel] [PATCH 2/2] lavfi: add qrencodesrc source
Date: Sat, 9 Dec 2023 19:13:46 +0100
Message-ID: <ZXSuWs3bmpEQuigR@mariano> (raw)
In-Reply-To: <20231130004914.329717-3-stefasab@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 381 bytes --]
On date Thursday 2023-11-30 01:49:14 +0100, Stefano Sabatini wrote:
> ---
> configure | 4 +
> libavfilter/Makefile | 1 +
> libavfilter/allfilters.c | 1 +
> libavfilter/vsrc_qrencode.c | 435 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 441 insertions(+)
> create mode 100644 libavfilter/vsrc_qrencode.c
Rev2 with padding and doc.
[-- Attachment #2: 0002-lavfi-add-qrencodesrc-source.patch --]
[-- Type: text/x-diff, Size: 31359 bytes --]
From b16201db38d3e492635a9d202b0e654811a52ec5 Mon Sep 17 00:00:00 2001
From: Stefano Sabatini <stefasab@gmail.com>
Date: Tue, 28 Nov 2023 23:58:15 +0100
Subject: [PATCH 2/2] lavfi: add qrencodesrc source
Introduce qrencodesrc source.
---
configure | 4 +
doc/filters.texi | 219 +++++++++++++++
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vsrc_qrencode.c | 547 ++++++++++++++++++++++++++++++++++++
5 files changed, 772 insertions(+)
create mode 100644 libavfilter/vsrc_qrencode.c
diff --git a/configure b/configure
index 838e627084..a2e9026c49 100755
--- a/configure
+++ b/configure
@@ -256,6 +256,7 @@ External library support:
--enable-libopus enable Opus de/encoding via libopus [no]
--enable-libplacebo enable libplacebo library [no]
--enable-libpulse enable Pulseaudio input via libpulse [no]
+ --enable-libqrencode enable QR encode generation via libqrencode [no]
--enable-librabbitmq enable RabbitMQ library [no]
--enable-librav1e enable AV1 encoding via rav1e [no]
--enable-librist enable RIST via librist [no]
@@ -1879,6 +1880,7 @@ EXTERNAL_LIBRARY_LIST="
libopus
libplacebo
libpulse
+ libqrencode
librabbitmq
librav1e
librist
@@ -3769,6 +3771,7 @@ nnedi_filter_deps="gpl"
ocr_filter_deps="libtesseract"
ocv_filter_deps="libopencv"
openclsrc_filter_deps="opencl"
+qrencodesrc_filter_deps="libqrencode"
overlay_opencl_filter_deps="opencl"
overlay_qsv_filter_deps="libmfx"
overlay_qsv_filter_select="qsvvpp"
@@ -6812,6 +6815,7 @@ enabled libopus && {
}
enabled libplacebo && require_pkg_config libplacebo "libplacebo >= 4.192.0" libplacebo/vulkan.h pl_vulkan_create
enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new
+enabled libqrencode && require_pkg_config libqrencode libqrencode qrencode.h QRcode_encodeString
enabled librabbitmq && require_pkg_config librabbitmq "librabbitmq >= 0.7.1" amqp.h amqp_new_connection
enabled librav1e && require_pkg_config librav1e "rav1e >= 0.5.0" rav1e.h rav1e_context_new
enabled librist && require_pkg_config librist "librist >= 0.2.7" librist/librist.h rist_receiver_create
diff --git a/doc/filters.texi b/doc/filters.texi
index de19d130cc..09aee86a46 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -28660,6 +28660,225 @@ ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c
@end example
@end itemize
+@section qrencodesrc
+
+Generate a QR code using the libqrencode library (see
+@url{https://fukuchi.org/works/qrencode/}).
+
+To enable the compilation of this source, you need to configure FFmpeg with with
+@code{--enable-libqrencode}.
+
+The QR code is generated from the provided text or text pattern. The
+corresponding QR code is scaled and put in the video output according to the
+specified output size options.
+
+In case no text is specified, the QR code is not generated, but an empty colored
+output is returned instead.
+
+This source accepts the following options:
+
+@table @option
+
+@item qrcode_width, w
+@item padded_qrcode_width, W
+Specify an expression for the width of the rendered QR code, with and without
+padding. The @var{qrcode_width} expression can reference the value set by the
+@var{padded_qrcode_width} expression, and vice versa.
+By default @var{padded_qrcode_width} is set to @var{qrcode_width}, meaning that
+there is no padding.
+
+@item rate, r
+Specify the frame rate of the sourced video, as the number of frames
+generated per second. It has to be a string in the format
+@var{frame_rate_num}/@var{frame_rate_den}, an integer number, a floating point
+number or a valid video frame rate abbreviation. The default value is
+"25".
+
+@item case_sensitive, cs
+Instruct libqrencode to use case sensitive encoding. This is enabled by
+default. This can be disabled to reduce the QR encoding size.
+
+@item level, l
+Specify the QR encoding error correction level. With an higher correction level,
+the encoding size will increase but the code will be more robust to corruption.
+Lower level is @var{L}.
+
+It accepts the following values:
+@table @samp
+@item L
+@item M
+@item Q
+@item H
+@end table
+
+@item expansion
+Select how the input text is expanded. Can be either @code{none}, or
+@code{normal} (default). See the @ref{qrencodesrc_expansion, Text expansion}
+section below for details.
+
+@item text
+@item textfile
+Define the text to be rendered. In case neither is specified, no QR is encoded
+(just an empty colored frame).
+
+In case expansion is enabled, the text is treated as a text template, using the
+drawtext expansion mechanism. See section below.
+
+@item background_color, bc
+@item foreground_color, fc
+Set the QR code and background color. The default value of
+@var{foreground_color} is "black", the default value of @var{background_color}
+is "white".
+
+For the syntax of the color options, check the @ref{color syntax,,"Color"
+section in the ffmpeg-utils manual,ffmpeg-utils}.
+@end table
+
+@anchor{qrencodesrc_expansion}
+@subsection Text expansion
+
+If @option{expansion} is set to @code{none}, the text is printed verbatim.
+
+If @option{expansion} is set to @code{normal} (which is the default),
+the following expansion mechanism is used.
+
+The backslash character @samp{\}, followed by any character, always expands to
+the second character.
+
+Sequences of the form @code{%@{...@}} are expanded. The text between the
+braces is a function name, possibly followed by arguments separated by ':'.
+If the arguments contain special characters or delimiters (':' or '@}'),
+they should be escaped.
+
+Note that they probably must also be escaped as the value for the @option{text}
+option in the filter argument string and as the filter argument in the
+filtergraph description, and possibly also for the shell, that makes up to four
+levels of escaping; using a text file with the @option{textfile} option avoids
+these problems.
+
+The following functions are available:
+
+@table @command
+@item n, frame_num
+return the frame number
+
+@item pts
+Return the timestamp of the current frame.
+
+It can take up to two arguments.
+
+The first argument is the format of the timestamp; it defaults to @code{flt} for
+seconds as a decimal number with microsecond accuracy; @code{hms} stands for a
+formatted @var{[-]HH:MM:SS.mmm} timestamp with millisecond accuracy.
+@code{gmtime} stands for the timestamp of the frame formatted as UTC time;
+@code{localtime} stands for the timestamp of the frame formatted as local time
+zone time. If the format is set to @code{hms24hh}, the time is formatted in 24h
+format (00-23).
+
+The second argument is an offset added to the timestamp.
+
+If the format is set to @code{localtime} or @code{gmtime}, a third argument may
+be supplied: a @code{strftime} C function format string. By default,
+@var{YYYY-MM-DD HH:MM:SS} format will be used.
+
+@item expr, e
+Evaluate the expression's value and output as a double.
+
+It must take one argument specifying the expression to be evaluated, which
+accepts the following constants:
+
+@table @option
+@item n
+the frame number
+
+@item q
+the QR code width
+
+@item t
+the time of the frame
+
+@item w
+return the rendered QR code width
+
+@item W
+return the rendered padded QR code width
+@end table
+
+@item expr_formatted, ef
+Evaluate the expression's value and output as a formatted string.
+
+The first argument is the expression to be evaluated, just as for the @var{expr} function.
+The second argument specifies the output format. Allowed values are @samp{x},
+@samp{X}, @samp{d} and @samp{u}. They are treated exactly as in the
+@code{printf} function.
+The third parameter is optional and sets the number of positions taken by the output.
+It can be used to add padding with zeros from the left.
+
+@item gmtime
+The time at which the filter is running, expressed in UTC.
+It can accept an argument: a @code{strftime} C function format string.
+The format string is extended to support the variable @var{%[1-6]N}
+which prints fractions of the second with optionally specified number of digits.
+
+@item localtime
+The time at which the filter is running, expressed in the local time zone.
+It can accept an argument: a @code{strftime} C function format string.
+The format string is extended to support the variable @var{%[1-6]N}
+which prints fractions of the second with optionally specified number of digits.
+
+@item rand(min, max)
+return a random number included between @var{min} and @var{max}
+@end table
+
+@subsection Examples
+
+@itemize
+@item
+Generate a QR code encoding the specified text with the default size:
+@example
+qrencodesrc=text=www.ffmpeg.org
+@end example
+
+@item
+Same as below, but select blue on pink colors:
+@example
+qrencodesrc=text=www.ffmpeg.org:bc=pink:fc=blue
+@end example
+
+@item
+Generate a QR code with width of 200 pixels and padding, making the padded width
+4/3 of the QR code width:
+@example
+qrencodesrc=text=www.ffmpeg.org:w=200:W=4/3*w
+@end example
+
+@item
+Generate a QR code with padded width of 200 pixels and padding, making the QR
+code width 3/4 of the padded width:
+@example
+qrencodesrc=text=www.ffmpeg.org:W=200:w=3/4*W
+@end example
+
+@item
+Generate a QR code encoding the frame number:
+@example
+qrencodesrc=text=%@{n@}
+@end example
+
+@item
+Generate a QR code encoding the GMT timestamp:
+@example
+qrencodesrc=text=%@{gmtime@}
+@end example
+
+@item
+Generate a QR code encoding the timestamp expressed as a float:
+@example
+qrencodesrc=text=%@{pts@}
+@end example
+
+@end itemize
+
@anchor{allrgb}
@anchor{allyuv}
@anchor{color}
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 1f9bbcc1af..b30e436fb3 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -598,6 +598,7 @@ OBJS-$(CONFIG_NULLSRC_FILTER) += vsrc_testsrc.o
OBJS-$(CONFIG_OPENCLSRC_FILTER) += vf_program_opencl.o opencl.o
OBJS-$(CONFIG_PAL75BARS_FILTER) += vsrc_testsrc.o
OBJS-$(CONFIG_PAL100BARS_FILTER) += vsrc_testsrc.o
+OBJS-$(CONFIG_QRENCODESRC_FILTER) += vsrc_qrencode.o textutils.o
OBJS-$(CONFIG_RGBTESTSRC_FILTER) += vsrc_testsrc.o
OBJS-$(CONFIG_SIERPINSKI_FILTER) += vsrc_sierpinski.o
OBJS-$(CONFIG_SMPTEBARS_FILTER) += vsrc_testsrc.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index ed7c32be94..c5bf55ed53 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -560,6 +560,7 @@ extern const AVFilter ff_vsrc_mandelbrot;
extern const AVFilter ff_vsrc_mptestsrc;
extern const AVFilter ff_vsrc_nullsrc;
extern const AVFilter ff_vsrc_openclsrc;
+extern const AVFilter ff_vsrc_qrencodesrc;
extern const AVFilter ff_vsrc_pal75bars;
extern const AVFilter ff_vsrc_pal100bars;
extern const AVFilter ff_vsrc_rgbtestsrc;
diff --git a/libavfilter/vsrc_qrencode.c b/libavfilter/vsrc_qrencode.c
new file mode 100644
index 0000000000..71e282eaa5
--- /dev/null
+++ b/libavfilter/vsrc_qrencode.c
@@ -0,0 +1,547 @@
+/*
+ * 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
+ */
+
+/**
+ * @file QR encoder source.
+ *
+ * A QR code (quick-response code) is a type of two-dimensional matrix
+ * barcode, invented in 1994, by Japanese company Denso Wave for
+ * labelling automobile parts.
+ *
+ * This source uses the libqrencode library to generate QR code:
+ * https://fukuchi.org/works/qrencode/
+ */
+
+//#define DEBUG
+
+#include "libavutil/internal.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/opt.h"
+#include "libavutil/lfg.h"
+#include "libavutil/random_seed.h"
+
+#include "avfilter.h"
+#include "drawutils.h"
+#include "internal.h"
+#include "formats.h"
+#include "textutils.h"
+#include "video.h"
+#include "libswscale/swscale.h"
+
+#include <qrencode.h>
+
+enum var_name {
+ VAR_n,
+ VAR_q,
+ VAR_t,
+ VAR_w,
+ VAR_W,
+ VAR_VARS_NB
+};
+
+static const char *const var_names[] = {
+ "n", ///< number of frame
+ "q", ///< width of the QR code
+ "t", ///< timestamp expressed in seconds
+ "w", ///< rendered width of the QR code
+ "W", ///< rendered width of the padded QR code
+ NULL
+};
+
+enum Expansion {
+ EXPANSION_NONE,
+ EXPANSION_NORMAL
+};
+
+typedef struct QREncodeContext {
+ const AVClass *class;
+
+ char *rendered_qrcode_width_expr;
+ char *rendered_padded_qrcode_width_expr;
+
+ int rendered_qrcode_width;
+ int rendered_padded_qrcode_width;
+
+ unsigned char *text;
+ char *textfile;
+ uint64_t pts;
+
+ int level;
+ char case_sensitive;
+
+ uint8_t foreground_color[4];
+ uint8_t background_color[4];
+
+ FFDrawContext draw;
+ FFDrawColor draw_foreground_color; ///< foreground color
+ FFDrawColor draw_background_color; ///< background color
+
+ /* these are only used when nothing must be encoded */
+ FFDrawContext draw0;
+ FFDrawColor draw0_background_color; ///< background color
+
+ uint8_t *qrcode_data[4];
+ int qrcode_linesize[4];
+ uint8_t *qrcode_mask_data[4];
+ int qrcode_mask_linesize[4];
+ int qrcode_width;
+ int padded_qrcode_width;
+
+ AVRational frame_rate;
+
+ int expansion; ///< expansion mode to use for the text
+ FFExpandTextContext expand_text; ///< expand text in case expansion is enabled
+ AVBPrint expanded_text; ///< used to contain the expanded text
+
+ double var_values[VAR_VARS_NB];
+ AVLFG lfg; ///< random generator
+} QREncodeContext;
+
+#define OFFSET(x) offsetof(QREncodeContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+static const AVOption qrencodesrc_options[] = {
+ { "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
+ { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS },
+ { "qrcode_width", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS },
+ { "w", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS },
+ { "padded_qrcode_width", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "w"}, 0, INT_MAX, FLAGS },
+ { "W", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "w"}, 0, INT_MAX, FLAGS },
+ { "case_sensitive", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS },
+ { "cs", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS },
+
+ { "level", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, "level"},
+ { "l", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, "level"},
+ { "L", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_L }, 0, 0, FLAGS, "level" },
+ { "M", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_M }, 0, 0, FLAGS, "level" },
+ { "Q", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_Q }, 0, 0, FLAGS, "level" },
+ { "H", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_H }, 0, 0, FLAGS, "level" },
+
+ {"expansion", "set the expansion mode", OFFSET(expansion), AV_OPT_TYPE_INT, {.i64=EXPANSION_NORMAL}, 0, 2, FLAGS, "expansion"},
+ {"none", "set no expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NONE}, 0, 0, FLAGS, "expansion"},
+ {"normal", "set normal expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NORMAL}, 0, 0, FLAGS, "expansion"},
+
+ { "foreground_color", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS },
+ { "fc", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS },
+ { "background_color", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS },
+ { "bc", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS },
+
+ {"text", "set text to encode", OFFSET(text), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS},
+ {"textfile", "set text file to encode", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS},
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(qrencodesrc);
+
+static const char *const fun2_names[] = {
+ "rand"
+};
+
+static double drand(void *opaque, double min, double max)
+{
+ return min + (max-min) / UINT_MAX * av_lfg_get(opaque);
+}
+
+static const ff_eval_func2 fun2[] = {
+ drand,
+ NULL
+};
+
+static int func_pts(void *ctx, AVBPrint *bp, const char *function_name,
+ unsigned argc, char **argv)
+{
+ QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
+ const char *fmt;
+ const char *strftime_fmt = NULL;
+ const char *delta = NULL;
+ double t = qr->var_values[VAR_t];
+
+ // argv: pts, FMT, [DELTA, strftime_fmt]
+
+ fmt = argc >= 1 ? argv[0] : "flt";
+ if (argc >= 2) {
+ delta = argv[1];
+ }
+ if (argc >= 3) {
+ strftime_fmt = argv[2];
+ }
+
+ return ff_print_pts(ctx, bp, t, delta, fmt, strftime_fmt);
+}
+
+static int func_frame_num(void *ctx, AVBPrint *bp, const char *function_name,
+ unsigned argc, char **argv)
+{
+ QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
+
+ av_bprintf(bp, "%d", (int)qr->var_values[VAR_n]);
+ return 0;
+}
+
+static int func_strftime(void *ctx, AVBPrint *bp, const char *function_name,
+ unsigned argc, char **argv)
+{
+ const char *strftime_fmt = argc ? argv[0] : NULL;
+
+ return ff_print_time(ctx, bp, strftime_fmt, !strcmp(function_name, "localtime"));
+}
+
+static int func_eval_expr(void *ctx, AVBPrint *bp, const char *function_name,
+ unsigned argc, char **argv)
+{
+ QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
+
+ return ff_print_eval_expr(ctx, bp, argv[0],
+ fun2_names, fun2,
+ var_names, qr->var_values, &qr->lfg);
+}
+
+static int func_eval_expr_formatted(void *ctx, AVBPrint *bp, const char *function_name,
+ unsigned argc, char **argv)
+{
+ QREncodeContext *qr = ((AVFilterContext *)ctx)->priv;
+ int ret;
+ int positions = -1;
+
+ /*
+ * argv[0] expression to be converted to `int`
+ * argv[1] format: 'x', 'X', 'd' or 'u'
+ * argv[2] positions printed (optional)
+ */
+
+ if (argc == 3) {
+ ret = sscanf(argv[2], "%u", &positions);
+ if (ret != 1) {
+ av_log(ctx, AV_LOG_ERROR, "expr_int_format(): Invalid number of positions"
+ " to print: '%s'\n", argv[2]);
+ return AVERROR(EINVAL);
+ }
+ }
+
+ return ff_print_formatted_eval_expr(ctx, bp, argv[0],
+ fun2_names, fun2,
+ var_names, qr->var_values,
+ &qr->lfg,
+ argv[1][0], positions);
+}
+
+static FFExpandTextFunction expand_text_functions[] = {
+ { "expr", 1, 1, func_eval_expr },
+ { "e", 1, 1, func_eval_expr },
+ { "expr_formatted", 2, 3, func_eval_expr_formatted },
+ { "ef", 2, 3, func_eval_expr_formatted },
+ { "frame_num", 0, 0, func_frame_num },
+ { "n", 0, 0, func_frame_num },
+ { "gmtime", 0, 1, func_strftime },
+ { "localtime", 0, 1, func_strftime },
+ { "pts", 0, 3, func_pts }
+};
+
+static av_cold int init(AVFilterContext *ctx)
+{
+ QREncodeContext *qr = ctx->priv;
+ int ret;
+
+ ff_draw_init(&qr->draw, AV_PIX_FMT_ARGB, FF_DRAW_PROCESS_ALPHA);
+ ff_draw_color(&qr->draw, &qr->draw_foreground_color, (const uint8_t *)&qr->foreground_color);
+ ff_draw_color(&qr->draw, &qr->draw_background_color, (const uint8_t *)&qr->background_color);
+
+ av_lfg_init(&qr->lfg, av_get_random_seed());
+
+#define RENDER_EXPR(var_name_, expr_name_) \
+ ret = av_expr_parse_and_eval(&qr->var_values[VAR_##var_name_], \
+ qr->expr_name_##_expr, \
+ var_names, qr->var_values, \
+ NULL, NULL, \
+ fun2_names, fun2, \
+ &qr->lfg, 0, ctx); \
+ if (ret < 0) { \
+ av_log(ctx, AV_LOG_ERROR, \
+ "Could not evaluate expression '%s'\n", \
+ qr->expr_name_##_expr); \
+ return ret; \
+ }
+
+ RENDER_EXPR(w, rendered_qrcode_width);
+ RENDER_EXPR(W, rendered_padded_qrcode_width);
+ RENDER_EXPR(w, rendered_qrcode_width);
+
+ qr->rendered_qrcode_width = qr->var_values[VAR_w];
+ qr->rendered_padded_qrcode_width = qr->var_values[VAR_W];
+
+ if (qr->rendered_padded_qrcode_width < qr->rendered_qrcode_width) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Resulting padded QR code width (%d) is lesser than the QR code width (%d)\n",
+ qr->rendered_padded_qrcode_width, qr->rendered_qrcode_width);
+ return AVERROR(EINVAL);
+ }
+
+ qr->qrcode_width = -1;
+
+ if (qr->textfile) {
+ if (qr->text) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Both text and text file provided. Please provide only one\n");
+ return AVERROR(EINVAL);
+ }
+ if ((ret = ff_load_textfile(ctx, (const char *)qr->textfile, &(qr->text), NULL)) < 0)
+ return ret;
+ }
+
+ av_log(ctx, AV_LOG_VERBOSE,
+ "w:%d W:%d case_sensitive:%d level:%d\n",
+ (int)qr->rendered_qrcode_width, (int)qr->rendered_padded_qrcode_width,
+ qr->case_sensitive, qr->level);
+
+ qr->expand_text = (FFExpandTextContext) {
+ .log_ctx = ctx,
+ .functions = expand_text_functions,
+ .functions_nb = FF_ARRAY_ELEMS(expand_text_functions)
+ };
+
+ av_bprint_init(&qr->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+ QREncodeContext *qr = ctx->priv;
+
+ av_bprint_finalize(&qr->expanded_text, NULL);
+ av_freep(&qr->qrcode_data[0]);
+}
+
+static int config_props(AVFilterLink *outlink)
+{
+ QREncodeContext *qr = outlink->src->priv;
+
+ ff_draw_init(&qr->draw0, outlink->format, FF_DRAW_PROCESS_ALPHA);
+ ff_draw_color(&qr->draw0, &qr->draw0_background_color, (const uint8_t *)&qr->background_color);
+
+ outlink->w = qr->rendered_padded_qrcode_width;
+ outlink->h = qr->rendered_padded_qrcode_width;
+ outlink->time_base = av_inv_q(qr->frame_rate);
+ outlink->frame_rate = qr->frame_rate;
+
+ return 0;
+}
+
+#ifdef DEBUG
+static void show_qrcode(AVFilterContext *ctx, const QRcode *qrcode)
+{
+ int i, j;
+ char *line = av_malloc(qrcode->width + 1);
+ const char *p = qrcode->data;
+
+ if (!line)
+ return;
+ for (i = 0; i < qrcode->width; i++) {
+ for (j = 0; j < qrcode->width; j++)
+ line[j] = (*p++)&1 ? '@' : ' ';
+ line[j] = 0;
+ av_log(ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line);
+ }
+ av_free(line);
+}
+#endif
+
+static int draw_qrcode(AVFilterContext *ctx, AVFrame *picref)
+{
+ QREncodeContext *qr = ctx->priv;
+ struct SwsContext *sws = NULL;
+ QRcode *qrcode = NULL;
+ int i, j;
+ int ret;
+ int offset;
+ uint8_t *srcp;
+ uint8_t *dstp0, *dstp;
+
+ if (!qr->expanded_text.str || qr->expanded_text.str[0] == 0) {
+ ff_fill_rectangle(&qr->draw0, &qr->draw0_background_color,
+ picref->data, picref->linesize,
+ 0, 0, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width);
+ return 0;
+ }
+
+ av_log(ctx, AV_LOG_DEBUG, "Encoding string '%s'\n", qr->expanded_text.str);
+ qrcode = QRcode_encodeString(qr->expanded_text.str, 1, qr->level, QR_MODE_8,
+ qr->case_sensitive);
+ if (!qrcode) {
+ ret = AVERROR(errno);
+ av_log(ctx, AV_LOG_ERROR,
+ "Failed to encode string with error \'%s\'\n", av_err2str(ret));
+ goto error;
+ }
+
+ av_log(ctx, AV_LOG_DEBUG,
+ "Encoded QR with width:%d version:%d\n", qrcode->width, qrcode->version);
+#ifdef DEBUG
+ show_qrcode(ctx, (const QRcode *)qrcode);
+#endif
+
+ qr->var_values[VAR_q] = qrcode->width;
+
+ if (qrcode->width != qr->qrcode_width) {
+ qr->qrcode_width = qrcode->width;
+
+ // compute virtual non-rendered padded size
+ // Q/q = W/w
+ qr->padded_qrcode_width =
+ ((double)qr->rendered_padded_qrcode_width / qr->rendered_qrcode_width) * qrcode->width;
+
+ ret = av_image_alloc(qr->qrcode_data, qr->qrcode_linesize,
+ qr->padded_qrcode_width, qr->padded_qrcode_width,
+ AV_PIX_FMT_ARGB, 16);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Failed to allocate image for QR code with width %d\n", qr->padded_qrcode_width);
+ goto error;
+ }
+
+ ret = av_image_alloc(qr->qrcode_mask_data, qr->qrcode_mask_linesize,
+ qrcode->width, qrcode->width,
+ AV_PIX_FMT_GRAY8, 16);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to allocate image for QR code with width %d\n", qrcode->width);
+ goto error;
+ }
+
+ }
+
+ ff_fill_rectangle(&qr->draw, &qr->draw_background_color,
+ qr->qrcode_data, qr->qrcode_linesize,
+ 0, 0, qr->padded_qrcode_width, qr->padded_qrcode_width);
+
+ /* fill mask */
+ dstp0 = qr->qrcode_mask_data[0];
+ srcp = qrcode->data;
+
+ for (i = 0; i < qrcode->width; i++) {
+ dstp = dstp0;
+ for (j = 0; j < qrcode->width; j++)
+ *dstp++ = (*srcp++ & 1) ? 255 : 0;
+ dstp0 += qr->qrcode_mask_linesize[0];
+ }
+
+ offset = (qr->padded_qrcode_width - qr->qrcode_width) / 2;
+ ff_blend_mask(&qr->draw, &qr->draw_foreground_color,
+ qr->qrcode_data, qr->qrcode_linesize,
+ qr->padded_qrcode_width, qr->padded_qrcode_width,
+ qr->qrcode_mask_data[0], qr->qrcode_mask_linesize[0], qrcode->width, qrcode->width,
+ 3, 0, offset, offset);
+
+ sws = sws_alloc_context();
+ if (!sws) {
+ ret = AVERROR(ENOMEM);
+ goto error;
+ }
+
+ av_opt_set_int(sws, "srcw", qr->padded_qrcode_width, 0);
+ av_opt_set_int(sws, "srch", qr->padded_qrcode_width, 0);
+ av_opt_set_int(sws, "src_format", AV_PIX_FMT_ARGB, 0);
+ av_opt_set_int(sws, "dstw", qr->rendered_padded_qrcode_width, 0);
+ av_opt_set_int(sws, "dsth", qr->rendered_padded_qrcode_width, 0);
+ av_opt_set_int(sws, "dst_format", picref->format, 0);
+ av_opt_set_int(sws, "sws_flags", SWS_POINT, 0);
+
+ if ((ret = sws_init_context(sws, NULL, NULL)) < 0)
+ goto error;
+
+ sws_scale(sws,
+ (const uint8_t *const *)&qr->qrcode_data, qr->qrcode_linesize,
+ 0, qr->padded_qrcode_width,
+ picref->data, picref->linesize);
+
+error:
+ sws_freeContext(sws);
+ QRcode_free(qrcode);
+
+ return ret;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+ AVFilterContext *ctx = (AVFilterContext *)outlink->src;
+ QREncodeContext *qr = ctx->priv;
+ AVFrame *picref =
+ ff_get_video_buffer(outlink, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width);
+ int ret;
+
+ if (!picref)
+ return AVERROR(ENOMEM);
+ picref->sample_aspect_ratio = (AVRational) {1, 1};
+ qr->var_values[VAR_n] = picref->pts = qr->pts++;
+ qr->var_values[VAR_t] = qr->pts * av_q2d(outlink->time_base);
+
+ av_bprint_clear(&qr->expanded_text);
+
+ switch (qr->expansion) {
+ case EXPANSION_NONE:
+ av_bprintf(&qr->expanded_text, "%s", qr->text);
+ break;
+ case EXPANSION_NORMAL:
+ if ((ret = ff_expand_text(&qr->expand_text, qr->text, &qr->expanded_text)) < 0)
+ return ret;
+ break;
+ }
+
+ if ((ret = draw_qrcode(ctx, picref)) < 0)
+ return ret;
+
+ return ff_filter_frame(outlink, picref);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+ enum AVPixelFormat pix_fmt;
+ FFDrawContext draw;
+ AVFilterFormats *fmts = NULL;
+ int ret;
+
+ // this is needed to support both the no-draw and draw cases
+ // for the no-draw case we use FFDrawContext to write on the input picture ref
+ for (pix_fmt = 0; av_pix_fmt_desc_get(pix_fmt); pix_fmt++)
+ if (ff_draw_init(&draw, pix_fmt, 0) >= 0 &&
+ sws_isSupportedOutput(pix_fmt) &&
+ (ret = ff_add_format(&fmts, pix_fmt)) < 0)
+ return ret;
+
+ return ff_set_common_formats(ctx, fmts);
+}
+
+static const AVFilterPad qrencode_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .request_frame = request_frame,
+ .config_props = config_props,
+ }
+};
+
+const AVFilter ff_vsrc_qrencodesrc = {
+ .name = "qrencodesrc",
+ .description = NULL_IF_CONFIG_SMALL("Generate a QR code."),
+ .priv_size = sizeof(QREncodeContext),
+ .priv_class = &qrencodesrc_class,
+ .init = init,
+ .uninit = uninit,
+ .inputs = NULL,
+ FILTER_OUTPUTS(qrencode_outputs),
+ FILTER_QUERY_FUNC(query_formats),
+};
--
2.34.1
[-- Attachment #3: Type: text/plain, Size: 251 bytes --]
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
next prev parent reply other threads:[~2023-12-09 18:13 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-11-30 0:49 [FFmpeg-devel] [POC][PATCHSET] Add " Stefano Sabatini
2023-11-30 0:49 ` [FFmpeg-devel] [PATCH 1/2] lavfi: introduce textutils Stefano Sabatini
2023-12-03 15:05 ` Stefano Sabatini
2023-12-09 18:12 ` Stefano Sabatini
2023-12-16 15:35 ` Stefano Sabatini
2024-01-03 12:58 ` Timo Rothenpieler
2024-01-03 15:50 ` Stefano Sabatini
2023-12-06 20:20 ` Nicolas George
2023-11-30 0:49 ` [FFmpeg-devel] [PATCH 2/2] lavfi: add qrencodesrc source Stefano Sabatini
2023-12-09 18:13 ` Stefano Sabatini [this message]
2023-12-11 20:59 ` Kyle Swanson
2023-12-11 23:34 ` Stefano Sabatini
2023-12-17 18:00 ` Stefano Sabatini
2023-12-24 17:43 ` Stefano Sabatini
2024-01-02 21:09 ` Stefano Sabatini
2023-11-30 11:07 ` [FFmpeg-devel] [POC][PATCHSET] Add " Tomas Härdin
2023-11-30 11:21 ` Paul B Mahol
2023-12-02 19:19 ` Stefano Sabatini
2023-11-30 11:54 ` Nicolas George
[not found] ` <D4F91B2F-6380-48AA-8883-315FA76CA828@cosmin.at>
2023-11-30 15:39 ` Cosmin Stejerean via ffmpeg-devel
2023-12-01 15:08 ` Tomas Härdin
2023-12-02 19:27 ` Stefano Sabatini
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ZXSuWs3bmpEQuigR@mariano \
--to=stefasab@gmail.com \
--cc=ffmpeg-devel@ffmpeg.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git