| @@ -0,0 +1,820 @@ |
| | 1 | /* |
| | 2 | * This file is part of FFmpeg. |
| | 3 | * |
| | 4 | * FFmpeg is free software; you can redistribute it and/or |
| | 5 | * modify it under the terms of the GNU Lesser General Public |
| | 6 | * License as published by the Free Software Foundation; either |
| | 7 | * version 2.1 of the License, or (at your option) any later version. |
| | 8 | * |
| | 9 | * FFmpeg is distributed in the hope that it will be useful, |
| | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| | 12 | * Lesser General Public License for more details. |
| | 13 | * |
| | 14 | * You should have received a copy of the GNU Lesser General Public |
| | 15 | * License along with FFmpeg; if not, write to the Free Software |
| | 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| | 17 | */ |
| | 18 | |
| | 19 | /** |
| | 20 | * @file QR encoder source and filter. |
| | 21 | * |
| | 22 | * A QR code (quick-response code) is a type of two-dimensional matrix |
| | 23 | * barcode, invented in 1994, by Japanese company Denso Wave for |
| | 24 | * labelling automobile parts. |
| | 25 | * |
| | 26 | * This source uses the libqrencode library to generate QR code: |
| | 27 | * https://fukuchi.org/works/qrencode/ |
| | 28 | */ |
| | 29 | |
| | 30 | //#define DEBUG |
| | 31 | |
| | 32 | #include "config_components.h" |
| | 33 | |
| | 34 | #include "libavutil/internal.h" |
| | 35 | #include "libavutil/imgutils.h" |
| | 36 | #include "libavutil/opt.h" |
| | 37 | #include "libavutil/lfg.h" |
| | 38 | #include "libavutil/random_seed.h" |
| | 39 | |
| | 40 | #include "avfilter.h" |
| | 41 | #include "drawutils.h" |
| | 42 | #include "internal.h" |
| | 43 | #include "formats.h" |
| | 44 | #include "textutils.h" |
| | 45 | #include "video.h" |
| | 46 | #include "libswscale/swscale.h" |
| | 47 | |
| | 48 | #include <qrencode.h> |
| | 49 | |
| | 50 | enum var_name { |
| | 51 | VAR_dar, |
| | 52 | VAR_duration, |
| | 53 | VAR_hsub, VAR_vsub, |
| | 54 | VAR_main_h, VAR_H, |
| | 55 | VAR_main_w, VAR_W, |
| | 56 | VAR_n, |
| | 57 | VAR_pict_type, |
| | 58 | VAR_qr_w, VAR_w, |
| | 59 | VAR_rendered_padded_qr_w, VAR_Q, |
| | 60 | VAR_rendered_qr_w, VAR_q, |
| | 61 | VAR_sar, |
| | 62 | VAR_t, |
| | 63 | VAR_x, |
| | 64 | VAR_y, |
| | 65 | VAR_VARS_NB |
| | 66 | }; |
| | 67 | |
| | 68 | static const char *const var_names[] = { |
| | 69 | "dar", |
| | 70 | "duration", |
| | 71 | "hsub", "vsub", |
| | 72 | "main_h", "H", ///< height of the input video |
| | 73 | "main_w", "W", ///< width of the input video |
| | 74 | "n", ///< number of frame |
| | 75 | "pict_type", |
| | 76 | "qr_w", "w", ///< width of the QR code |
| | 77 | "rendered_padded_qr_w", "Q", ///< width of the rendered QR code |
| | 78 | "rendered_qr_w", "q", ///< width of the rendered QR code |
| | 79 | "sar", |
| | 80 | "t", ///< timestamp expressed in seconds |
| | 81 | "x", |
| | 82 | "y", |
| | 83 | NULL |
| | 84 | }; |
| | 85 | |
| | 86 | #define V(name_) qr->var_values[VAR_##name_] |
| | 87 | |
| | 88 | enum Expansion { |
| | 89 | EXPANSION_NONE, |
| | 90 | EXPANSION_NORMAL |
| | 91 | }; |
| | 92 | |
| | 93 | typedef struct QREncodeContext { |
| | 94 | const AVClass *class; |
| | 95 | |
| | 96 | char is_source; |
| | 97 | char *x_expr; |
| | 98 | char *y_expr; |
| | 99 | AVExpr *x_pexpr, *y_pexpr; |
| | 100 | |
| | 101 | char *rendered_qrcode_width_expr; |
| | 102 | char *rendered_padded_qrcode_width_expr; |
| | 103 | AVExpr *rendered_qrcode_width_pexpr, *rendered_padded_qrcode_width_pexpr; |
| | 104 | |
| | 105 | int rendered_qrcode_width; |
| | 106 | int rendered_padded_qrcode_width; |
| | 107 | |
| | 108 | unsigned char *text; |
| | 109 | char *textfile; |
| | 110 | uint64_t pts; |
| | 111 | |
| | 112 | int level; |
| | 113 | char case_sensitive; |
| | 114 | |
| | 115 | uint8_t foreground_color[4]; |
| | 116 | uint8_t background_color[4]; |
| | 117 | |
| | 118 | FFDrawContext draw; |
| | 119 | FFDrawColor draw_foreground_color; ///< foreground color |
| | 120 | FFDrawColor draw_background_color; ///< background color |
| | 121 | |
| | 122 | /* these are only used when nothing must be encoded */ |
| | 123 | FFDrawContext draw0; |
| | 124 | FFDrawColor draw0_background_color; ///< background color |
| | 125 | |
| | 126 | uint8_t *qrcode_data[4]; |
| | 127 | int qrcode_linesize[4]; |
| | 128 | uint8_t *qrcode_mask_data[4]; |
| | 129 | int qrcode_mask_linesize[4]; |
| | 130 | |
| | 131 | /* only used for filter to contain scaled image to blend on top of input */ |
| | 132 | uint8_t *rendered_qrcode_data[4]; |
| | 133 | int rendered_qrcode_linesize[4]; |
| | 134 | |
| | 135 | int qrcode_width; |
| | 136 | int padded_qrcode_width; |
| | 137 | |
| | 138 | AVRational frame_rate; |
| | 139 | |
| | 140 | int expansion; ///< expansion mode to use for the text |
| | 141 | FFExpandTextContext expand_text; ///< expand text in case expansion is enabled |
| | 142 | AVBPrint expanded_text; ///< used to contain the expanded text |
| | 143 | |
| | 144 | double var_values[VAR_VARS_NB]; |
| | 145 | AVLFG lfg; ///< random generator |
| | 146 | AVDictionary *metadata; |
| | 147 | } QREncodeContext; |
| | 148 | |
| | 149 | #define OFFSET(x) offsetof(QREncodeContext, x) |
| | 150 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
| | 151 | #define TFLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM |
| | 152 | |
| | 153 | #define COMMON_OPTIONS \ |
| | 154 | { "qrcode_width", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS }, \ |
| | 155 | { "q", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS }, \ |
| | 156 | { "padded_qrcode_width", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "q"}, 0, INT_MAX, FLAGS }, \ |
| | 157 | { "Q", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "q"}, 0, INT_MAX, FLAGS }, \ |
| | 158 | { "case_sensitive", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, \ |
| | 159 | { "cs", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, \ |
| | 160 | \ |
| | 161 | { "level", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, "level"}, \ |
| | 162 | { "l", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, "level"}, \ |
| | 163 | { "L", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_L }, 0, 0, FLAGS, "level" }, \ |
| | 164 | { "M", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_M }, 0, 0, FLAGS, "level" }, \ |
| | 165 | { "Q", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_Q }, 0, 0, FLAGS, "level" }, \ |
| | 166 | { "H", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_H }, 0, 0, FLAGS, "level" }, \ |
| | 167 | \ |
| | 168 | {"expansion", "set the expansion mode", OFFSET(expansion), AV_OPT_TYPE_INT, {.i64=EXPANSION_NORMAL}, 0, 2, FLAGS, "expansion"}, \ |
| | 169 | {"none", "set no expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NONE}, 0, 0, FLAGS, "expansion"}, \ |
| | 170 | {"normal", "set normal expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NORMAL}, 0, 0, FLAGS, "expansion"}, \ |
| | 171 | \ |
| | 172 | { "foreground_color", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS }, \ |
| | 173 | { "fc", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS }, \ |
| | 174 | { "background_color", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS }, \ |
| | 175 | { "bc", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS }, \ |
| | 176 | \ |
| | 177 | {"text", "set text to encode", OFFSET(text), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, \ |
| | 178 | {"textfile", "set text file to encode", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, \ |
| | 179 | |
| | 180 | static const char *const fun2_names[] = { |
| | 181 | "rand" |
| | 182 | }; |
| | 183 | |
| | 184 | static double drand(void *opaque, double min, double max) |
| | 185 | { |
| | 186 | return min + (max-min) / UINT_MAX * av_lfg_get(opaque); |
| | 187 | } |
| | 188 | |
| | 189 | static const ff_eval_func2 fun2[] = { |
| | 190 | drand, |
| | 191 | NULL |
| | 192 | }; |
| | 193 | |
| | 194 | static int func_pts(void *ctx, AVBPrint *bp, const char *function_name, |
| | 195 | unsigned argc, char **argv) |
| | 196 | { |
| | 197 | QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; |
| | 198 | const char *fmt; |
| | 199 | const char *strftime_fmt = NULL; |
| | 200 | const char *delta = NULL; |
| | 201 | double t = qr->var_values[VAR_t]; |
| | 202 | |
| | 203 | // argv: pts, FMT, [DELTA, strftime_fmt] |
| | 204 | |
| | 205 | fmt = argc >= 1 ? argv[0] : "flt"; |
| | 206 | if (argc >= 2) { |
| | 207 | delta = argv[1]; |
| | 208 | } |
| | 209 | if (argc >= 3) { |
| | 210 | strftime_fmt = argv[2]; |
| | 211 | } |
| | 212 | |
| | 213 | return ff_print_pts(ctx, bp, t, delta, fmt, strftime_fmt); |
| | 214 | } |
| | 215 | |
| | 216 | static int func_frame_num(void *ctx, AVBPrint *bp, const char *function_name, |
| | 217 | unsigned argc, char **argv) |
| | 218 | { |
| | 219 | QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; |
| | 220 | |
| | 221 | av_bprintf(bp, "%d", (int)V(n)); |
| | 222 | return 0; |
| | 223 | } |
| | 224 | |
| | 225 | static int func_strftime(void *ctx, AVBPrint *bp, const char *function_name, |
| | 226 | unsigned argc, char **argv) |
| | 227 | { |
| | 228 | const char *strftime_fmt = argc ? argv[0] : NULL; |
| | 229 | |
| | 230 | return ff_print_time(ctx, bp, strftime_fmt, !strcmp(function_name, "localtime")); |
| | 231 | } |
| | 232 | |
| | 233 | static int func_frame_metadata(void *ctx, AVBPrint *bp, const char *function_name, |
| | 234 | unsigned argc, char **argv) |
| | 235 | { |
| | 236 | QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; |
| | 237 | AVDictionaryEntry *e = av_dict_get(qr->metadata, argv[0], NULL, 0); |
| | 238 | |
| | 239 | if (e && e->value) |
| | 240 | av_bprintf(bp, "%s", e->value); |
| | 241 | else if (argc >= 2) |
| | 242 | av_bprintf(bp, "%s", argv[1]); |
| | 243 | |
| | 244 | return 0; |
| | 245 | } |
| | 246 | |
| | 247 | static int func_eval_expr(void *ctx, AVBPrint *bp, const char *function_name, |
| | 248 | unsigned argc, char **argv) |
| | 249 | { |
| | 250 | QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; |
| | 251 | |
| | 252 | return ff_print_eval_expr(ctx, bp, argv[0], |
| | 253 | fun2_names, fun2, |
| | 254 | var_names, qr->var_values, &qr->lfg); |
| | 255 | } |
| | 256 | |
| | 257 | static int func_eval_expr_formatted(void *ctx, AVBPrint *bp, const char *function_name, |
| | 258 | unsigned argc, char **argv) |
| | 259 | { |
| | 260 | QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; |
| | 261 | int ret; |
| | 262 | int positions = -1; |
| | 263 | |
| | 264 | /* |
| | 265 | * argv[0] expression to be converted to `int` |
| | 266 | * argv[1] format: 'x', 'X', 'd' or 'u' |
| | 267 | * argv[2] positions printed (optional) |
| | 268 | */ |
| | 269 | |
| | 270 | if (argc == 3) { |
| | 271 | ret = sscanf(argv[2], "%u", &positions); |
| | 272 | if (ret != 1) { |
| | 273 | av_log(ctx, AV_LOG_ERROR, "expr_int_format(): Invalid number of positions" |
| | 274 | " to print: '%s'\n", argv[2]); |
| | 275 | return AVERROR(EINVAL); |
| | 276 | } |
| | 277 | } |
| | 278 | |
| | 279 | return ff_print_formatted_eval_expr(ctx, bp, argv[0], |
| | 280 | fun2_names, fun2, |
| | 281 | var_names, qr->var_values, |
| | 282 | &qr->lfg, |
| | 283 | argv[1][0], positions); |
| | 284 | } |
| | 285 | |
| | 286 | static FFExpandTextFunction expand_text_functions[] = { |
| | 287 | { "expr", 1, 1, func_eval_expr }, |
| | 288 | { "e", 1, 1, func_eval_expr }, |
| | 289 | { "expr_formatted", 2, 3, func_eval_expr_formatted }, |
| | 290 | { "ef", 2, 3, func_eval_expr_formatted }, |
| | 291 | { "metadata", 1, 2, func_frame_metadata }, |
| | 292 | { "frame_num", 0, 0, func_frame_num }, |
| | 293 | { "n", 0, 0, func_frame_num }, |
| | 294 | { "gmtime", 0, 1, func_strftime }, |
| | 295 | { "localtime", 0, 1, func_strftime }, |
| | 296 | { "pts", 0, 3, func_pts } |
| | 297 | }; |
| | 298 | |
| | 299 | static av_cold int init(AVFilterContext *ctx) |
| | 300 | { |
| | 301 | QREncodeContext *qr = ctx->priv; |
| | 302 | int ret; |
| | 303 | |
| | 304 | av_lfg_init(&qr->lfg, av_get_random_seed()); |
| | 305 | |
| | 306 | qr->qrcode_width = -1; |
| | 307 | qr->rendered_padded_qrcode_width = -1; |
| | 308 | |
| | 309 | if (qr->textfile) { |
| | 310 | if (qr->text) { |
| | 311 | av_log(ctx, AV_LOG_ERROR, |
| | 312 | "Both text and text file provided. Please provide only one\n"); |
| | 313 | return AVERROR(EINVAL); |
| | 314 | } |
| | 315 | if ((ret = ff_load_textfile(ctx, (const char *)qr->textfile, &(qr->text), NULL)) < 0) |
| | 316 | return ret; |
| | 317 | } |
| | 318 | |
| | 319 | qr->expand_text = (FFExpandTextContext) { |
| | 320 | .log_ctx = ctx, |
| | 321 | .functions = expand_text_functions, |
| | 322 | .functions_nb = FF_ARRAY_ELEMS(expand_text_functions) |
| | 323 | }; |
| | 324 | |
| | 325 | av_bprint_init(&qr->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED); |
| | 326 | |
| | 327 | return 0; |
| | 328 | } |
| | 329 | |
| | 330 | static av_cold void uninit(AVFilterContext *ctx) |
| | 331 | { |
| | 332 | QREncodeContext *qr = ctx->priv; |
| | 333 | |
| | 334 | av_expr_free(qr->x_pexpr); |
| | 335 | av_expr_free(qr->y_pexpr); |
| | 336 | |
| | 337 | av_bprint_finalize(&qr->expanded_text, NULL); |
| | 338 | |
| | 339 | av_freep(&qr->qrcode_data[0]); |
| | 340 | av_freep(&qr->rendered_qrcode_data[0]); |
| | 341 | av_freep(&qr->qrcode_mask_data[0]); |
| | 342 | } |
| | 343 | |
| | 344 | #ifdef DEBUG |
| | 345 | static void show_qrcode(AVFilterContext *ctx, const QRcode *qrcode) |
| | 346 | { |
| | 347 | int i, j; |
| | 348 | char *line = av_malloc(qrcode->width + 1); |
| | 349 | const char *p = qrcode->data; |
| | 350 | |
| | 351 | if (!line) |
| | 352 | return; |
| | 353 | for (i = 0; i < qrcode->width; i++) { |
| | 354 | for (j = 0; j < qrcode->width; j++) |
| | 355 | line[j] = (*p++)&1 ? '@' : ' '; |
| | 356 | line[j] = 0; |
| | 357 | av_log(ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line); |
| | 358 | } |
| | 359 | av_free(line); |
| | 360 | } |
| | 361 | #endif |
| | 362 | |
| | 363 | static int draw_qrcode(AVFilterContext *ctx, AVFrame *frame) |
| | 364 | { |
| | 365 | QREncodeContext *qr = ctx->priv; |
| | 366 | struct SwsContext *sws = NULL; |
| | 367 | QRcode *qrcode = NULL; |
| | 368 | int i, j; |
| | 369 | char qrcode_width_changed; |
| | 370 | int ret; |
| | 371 | int offset; |
| | 372 | uint8_t *srcp; |
| | 373 | uint8_t *dstp0, *dstp; |
| | 374 | |
| | 375 | av_bprint_clear(&qr->expanded_text); |
| | 376 | |
| | 377 | switch (qr->expansion) { |
| | 378 | case EXPANSION_NONE: |
| | 379 | av_bprintf(&qr->expanded_text, "%s", qr->text); |
| | 380 | break; |
| | 381 | case EXPANSION_NORMAL: |
| | 382 | if ((ret = ff_expand_text(&qr->expand_text, qr->text, &qr->expanded_text)) < 0) |
| | 383 | return ret; |
| | 384 | break; |
| | 385 | } |
| | 386 | |
| | 387 | if (!qr->expanded_text.str || qr->expanded_text.str[0] == 0) { |
| | 388 | if (qr->is_source) { |
| | 389 | ff_fill_rectangle(&qr->draw0, &qr->draw0_background_color, |
| | 390 | frame->data, frame->linesize, |
| | 391 | 0, 0, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width); |
| | 392 | } |
| | 393 | |
| | 394 | return 0; |
| | 395 | } |
| | 396 | |
| | 397 | av_log(ctx, AV_LOG_DEBUG, "Encoding string '%s'\n", qr->expanded_text.str); |
| | 398 | qrcode = QRcode_encodeString(qr->expanded_text.str, 1, qr->level, QR_MODE_8, |
| | 399 | qr->case_sensitive); |
| | 400 | if (!qrcode) { |
| | 401 | ret = AVERROR(errno); |
| | 402 | av_log(ctx, AV_LOG_ERROR, |
| | 403 | "Failed to encode string with error \'%s\'\n", av_err2str(ret)); |
| | 404 | goto end; |
| | 405 | } |
| | 406 | |
| | 407 | av_log(ctx, AV_LOG_DEBUG, |
| | 408 | "Encoded QR with width:%d version:%d\n", qrcode->width, qrcode->version); |
| | 409 | #ifdef DEBUG |
| | 410 | show_qrcode(ctx, (const QRcode *)qrcode); |
| | 411 | #endif |
| | 412 | |
| | 413 | qrcode_width_changed = qr->qrcode_width != qrcode->width; |
| | 414 | qr->qrcode_width = qrcode->width; |
| | 415 | |
| | 416 | // realloc mask if needed |
| | 417 | if (qrcode_width_changed) { |
| | 418 | av_freep(&qr->qrcode_mask_data[0]); |
| | 419 | ret = av_image_alloc(qr->qrcode_mask_data, qr->qrcode_mask_linesize, |
| | 420 | qrcode->width, qrcode->width, |
| | 421 | AV_PIX_FMT_GRAY8, 16); |
| | 422 | if (ret < 0) { |
| | 423 | av_log(ctx, AV_LOG_ERROR, |
| | 424 | "Failed to allocate image for QR code with width %d\n", qrcode->width); |
| | 425 | goto end; |
| | 426 | } |
| | 427 | } |
| | 428 | |
| | 429 | /* fill mask */ |
| | 430 | dstp0 = qr->qrcode_mask_data[0]; |
| | 431 | srcp = qrcode->data; |
| | 432 | |
| | 433 | for (i = 0; i < qrcode->width; i++) { |
| | 434 | dstp = dstp0; |
| | 435 | for (j = 0; j < qrcode->width; j++) |
| | 436 | *dstp++ = (*srcp++ & 1) ? 255 : 0; |
| | 437 | dstp0 += qr->qrcode_mask_linesize[0]; |
| | 438 | } |
| | 439 | |
| | 440 | if (qr->is_source) { |
| | 441 | if (qrcode_width_changed) { |
| | 442 | /* realloc padded image */ |
| | 443 | |
| | 444 | // compute virtual non-rendered padded size |
| | 445 | // Q/q = W/w |
| | 446 | qr->padded_qrcode_width = |
| | 447 | ((double)qr->rendered_padded_qrcode_width / qr->rendered_qrcode_width) * qrcode->width; |
| | 448 | |
| | 449 | av_freep(&qr->qrcode_data[0]); |
| | 450 | ret = av_image_alloc(qr->qrcode_data, qr->qrcode_linesize, |
| | 451 | qr->padded_qrcode_width, qr->padded_qrcode_width, |
| | 452 | AV_PIX_FMT_ARGB, 16); |
| | 453 | if (ret < 0) { |
| | 454 | av_log(ctx, AV_LOG_ERROR, |
| | 455 | "Failed to allocate image for QR code with width %d\n", |
| | 456 | qr->padded_qrcode_width); |
| | 457 | goto end; |
| | 458 | } |
| | 459 | } |
| | 460 | |
| | 461 | /* fill padding */ |
| | 462 | ff_fill_rectangle(&qr->draw, &qr->draw_background_color, |
| | 463 | qr->qrcode_data, qr->qrcode_linesize, |
| | 464 | 0, 0, qr->padded_qrcode_width, qr->padded_qrcode_width); |
| | 465 | |
| | 466 | /* blend mask */ |
| | 467 | offset = (qr->padded_qrcode_width - qr->qrcode_width) / 2; |
| | 468 | ff_blend_mask(&qr->draw, &qr->draw_foreground_color, |
| | 469 | qr->qrcode_data, qr->qrcode_linesize, |
| | 470 | qr->padded_qrcode_width, qr->padded_qrcode_width, |
| | 471 | qr->qrcode_mask_data[0], qr->qrcode_mask_linesize[0], qrcode->width, qrcode->width, |
| | 472 | 3, 0, offset, offset); |
| | 473 | |
| | 474 | /* scale padded QR over the frame */ |
| | 475 | sws = sws_alloc_context(); |
| | 476 | if (!sws) { |
| | 477 | ret = AVERROR(ENOMEM); |
| | 478 | goto end; |
| | 479 | } |
| | 480 | |
| | 481 | av_opt_set_int(sws, "srcw", qr->padded_qrcode_width, 0); |
| | 482 | av_opt_set_int(sws, "srch", qr->padded_qrcode_width, 0); |
| | 483 | av_opt_set_int(sws, "src_format", AV_PIX_FMT_ARGB, 0); |
| | 484 | av_opt_set_int(sws, "dstw", qr->rendered_padded_qrcode_width, 0); |
| | 485 | av_opt_set_int(sws, "dsth", qr->rendered_padded_qrcode_width, 0); |
| | 486 | av_opt_set_int(sws, "dst_format", frame->format, 0); |
| | 487 | av_opt_set_int(sws, "sws_flags", SWS_POINT, 0); |
| | 488 | |
| | 489 | if ((ret = sws_init_context(sws, NULL, NULL)) < 0) |
| | 490 | goto end; |
| | 491 | |
| | 492 | sws_scale(sws, |
| | 493 | (const uint8_t *const *)&qr->qrcode_data, qr->qrcode_linesize, |
| | 494 | 0, qr->padded_qrcode_width, |
| | 495 | frame->data, frame->linesize); |
| | 496 | } else { |
| | 497 | #define EVAL_EXPR(name_) \ |
| | 498 | av_expr_eval(qr->name_##_pexpr, qr->var_values, &qr->lfg); |
| | 499 | |
| | 500 | V(qr_w) = V(w) = qrcode->width; |
| | 501 | |
| | 502 | V(rendered_qr_w) = V(q) = EVAL_EXPR(rendered_qrcode_width); |
| | 503 | V(rendered_padded_qr_w) = V(Q) = EVAL_EXPR(rendered_padded_qrcode_width); |
| | 504 | /* It is necessary if q is expressed from Q */ |
| | 505 | V(rendered_qr_w) = V(q) = EVAL_EXPR(rendered_qrcode_width); |
| | 506 | |
| | 507 | V(x) = EVAL_EXPR(x); |
| | 508 | V(y) = EVAL_EXPR(y); |
| | 509 | /* It is necessary if x is expressed from y */ |
| | 510 | V(x) = EVAL_EXPR(x); |
| | 511 | |
| | 512 | av_log(ctx, AV_LOG_DEBUG, |
| | 513 | "Rendering QR code with values n:%d w:%d q:%d Q:%d x:%d y:%d t:%f\n", |
| | 514 | (int)V(n), (int)V(w), (int)V(q), (int)V(Q), (int)V(x), (int)V(y), V(t)); |
| | 515 | |
| | 516 | /* blend rectangle over the target */ |
| | 517 | ff_blend_rectangle(&qr->draw, &qr->draw_background_color, |
| | 518 | frame->data, frame->linesize, frame->width, frame->height, |
| | 519 | V(x), V(y), V(Q), V(Q)); |
| | 520 | |
| | 521 | if (V(q) != qr->rendered_qrcode_width) { |
| | 522 | av_freep(&qr->rendered_qrcode_data[0]); |
| | 523 | qr->rendered_qrcode_width = V(q); |
| | 524 | |
| | 525 | ret = av_image_alloc(qr->rendered_qrcode_data, qr->rendered_qrcode_linesize, |
| | 526 | qr->rendered_qrcode_width, qr->rendered_qrcode_width, |
| | 527 | AV_PIX_FMT_GRAY8, 16); |
| | 528 | if (ret < 0) { |
| | 529 | av_log(ctx, AV_LOG_ERROR, |
| | 530 | "Failed to allocate image for rendered QR code with width %d\n", |
| | 531 | qr->rendered_qrcode_width); |
| | 532 | goto end; |
| | 533 | } |
| | 534 | } |
| | 535 | |
| | 536 | /* scale mask */ |
| | 537 | sws = sws_alloc_context(); |
| | 538 | if (!sws) { |
| | 539 | ret = AVERROR(ENOMEM); |
| | 540 | goto end; |
| | 541 | } |
| | 542 | |
| | 543 | av_opt_set_int(sws, "srcw", qr->qrcode_width, 0); |
| | 544 | av_opt_set_int(sws, "srch", qr->qrcode_width, 0); |
| | 545 | av_opt_set_int(sws, "src_format", AV_PIX_FMT_GRAY8, 0); |
| | 546 | av_opt_set_int(sws, "dstw", qr->rendered_qrcode_width, 0); |
| | 547 | av_opt_set_int(sws, "dsth", qr->rendered_qrcode_width, 0); |
| | 548 | av_opt_set_int(sws, "dst_format", AV_PIX_FMT_GRAY8, 0); |
| | 549 | av_opt_set_int(sws, "sws_flags", SWS_POINT, 0); |
| | 550 | |
| | 551 | if ((ret = sws_init_context(sws, NULL, NULL)) < 0) |
| | 552 | goto end; |
| | 553 | |
| | 554 | sws_scale(sws, |
| | 555 | (const uint8_t *const *)&qr->qrcode_mask_data, qr->qrcode_mask_linesize, |
| | 556 | 0, qr->qrcode_width, |
| | 557 | qr->rendered_qrcode_data, qr->rendered_qrcode_linesize); |
| | 558 | |
| | 559 | /* blend mask over the input frame */ |
| | 560 | offset = (V(Q) - V(q)) / 2; |
| | 561 | ff_blend_mask(&qr->draw, &qr->draw_foreground_color, |
| | 562 | frame->data, frame->linesize, frame->width, frame->height, |
| | 563 | qr->rendered_qrcode_data[0], qr->rendered_qrcode_linesize[0], |
| | 564 | qr->rendered_qrcode_width, qr->rendered_qrcode_width, |
| | 565 | 3, 0, V(x) + offset, V(y) + offset); |
| | 566 | } |
| | 567 | |
| | 568 | end: |
| | 569 | sws_freeContext(sws); |
| | 570 | QRcode_free(qrcode); |
| | 571 | |
| | 572 | return ret; |
| | 573 | } |
| | 574 | |
| | 575 | #if CONFIG_QRENCODESRC_FILTER |
| | 576 | |
| | 577 | static const AVOption qrencodesrc_options[] = { |
| | 578 | COMMON_OPTIONS |
| | 579 | { "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS }, |
| | 580 | { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS }, |
| | 581 | { NULL } |
| | 582 | }; |
| | 583 | |
| | 584 | AVFILTER_DEFINE_CLASS(qrencodesrc); |
| | 585 | |
| | 586 | static int qrencodesrc_config_props(AVFilterLink *outlink) |
| | 587 | { |
| | 588 | AVFilterContext *ctx = outlink->src; |
| | 589 | QREncodeContext *qr = ctx->priv; |
| | 590 | int ret; |
| | 591 | |
| | 592 | qr->is_source = 1; |
| | 593 | V(x) = V(y) = 0; |
| | 594 | |
| | 595 | #define PARSE_AND_EVAL_EXPR(var_name_, expr_name_) \ |
| | 596 | ret = av_expr_parse_and_eval(&qr->var_values[VAR_##var_name_], \ |
| | 597 | qr->expr_name_##_expr, \ |
| | 598 | var_names, qr->var_values, \ |
| | 599 | NULL, NULL, \ |
| | 600 | fun2_names, fun2, \ |
| | 601 | &qr->lfg, 0, ctx); \ |
| | 602 | if (ret < 0) { \ |
| | 603 | av_log(ctx, AV_LOG_ERROR, \ |
| | 604 | "Could not evaluate expression '%s'\n", \ |
| | 605 | qr->expr_name_##_expr); \ |
| | 606 | return ret; \ |
| | 607 | } |
| | 608 | |
| | 609 | /* undefined for the source */ |
| | 610 | V(main_w) = V(W) = NAN; |
| | 611 | V(main_h) = V(H) = NAN; |
| | 612 | V(x) = V(y) = V(t) = V(n) = NAN; |
| | 613 | V(dar) = V(sar) = 1.0; |
| | 614 | |
| | 615 | PARSE_AND_EVAL_EXPR(rendered_qr_w, rendered_qrcode_width); |
| | 616 | V(q) = V(rendered_qr_w); |
| | 617 | PARSE_AND_EVAL_EXPR(rendered_padded_qr_w, rendered_padded_qrcode_width); |
| | 618 | V(Q) = V(rendered_padded_qr_w); |
| | 619 | PARSE_AND_EVAL_EXPR(rendered_qr_w, rendered_qrcode_width); |
| | 620 | V(q) = V(rendered_qr_w); |
| | 621 | |
| | 622 | qr->rendered_qrcode_width = V(rendered_qr_w); |
| | 623 | qr->rendered_padded_qrcode_width = V(rendered_padded_qr_w); |
| | 624 | |
| | 625 | av_log(ctx, AV_LOG_VERBOSE, |
| | 626 | "q:%d Q:%d case_sensitive:%d level:%d\n", |
| | 627 | (int)qr->rendered_qrcode_width, (int)qr->rendered_padded_qrcode_width, |
| | 628 | qr->case_sensitive, qr->level); |
| | 629 | |
| | 630 | if (qr->rendered_padded_qrcode_width < qr->rendered_qrcode_width) { |
| | 631 | av_log(ctx, AV_LOG_ERROR, |
| | 632 | "Resulting padded QR code width (%d) is lesser than the QR code width (%d)\n", |
| | 633 | qr->rendered_padded_qrcode_width, qr->rendered_qrcode_width); |
| | 634 | return AVERROR(EINVAL); |
| | 635 | } |
| | 636 | |
| | 637 | ff_draw_init(&qr->draw, AV_PIX_FMT_ARGB, FF_DRAW_PROCESS_ALPHA); |
| | 638 | ff_draw_color(&qr->draw, &qr->draw_foreground_color, (const uint8_t *)&qr->foreground_color); |
| | 639 | ff_draw_color(&qr->draw, &qr->draw_background_color, (const uint8_t *)&qr->background_color); |
| | 640 | |
| | 641 | ff_draw_init(&qr->draw0, outlink->format, FF_DRAW_PROCESS_ALPHA); |
| | 642 | ff_draw_color(&qr->draw0, &qr->draw0_background_color, (const uint8_t *)&qr->background_color); |
| | 643 | |
| | 644 | outlink->w = qr->rendered_padded_qrcode_width; |
| | 645 | outlink->h = qr->rendered_padded_qrcode_width; |
| | 646 | outlink->time_base = av_inv_q(qr->frame_rate); |
| | 647 | outlink->frame_rate = qr->frame_rate; |
| | 648 | |
| | 649 | return 0; |
| | 650 | } |
| | 651 | |
| | 652 | static int request_frame(AVFilterLink *outlink) |
| | 653 | { |
| | 654 | AVFilterContext *ctx = (AVFilterContext *)outlink->src; |
| | 655 | QREncodeContext *qr = ctx->priv; |
| | 656 | AVFrame *frame = |
| | 657 | ff_get_video_buffer(outlink, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width); |
| | 658 | int ret; |
| | 659 | |
| | 660 | if (!frame) |
| | 661 | return AVERROR(ENOMEM); |
| | 662 | frame->sample_aspect_ratio = (AVRational) {1, 1}; |
| | 663 | V(n) = frame->pts = qr->pts++; |
| | 664 | V(t) = qr->pts * av_q2d(outlink->time_base); |
| | 665 | |
| | 666 | if ((ret = draw_qrcode(ctx, frame)) < 0) |
| | 667 | return ret; |
| | 668 | |
| | 669 | return ff_filter_frame(outlink, frame); |
| | 670 | } |
| | 671 | |
| | 672 | static int qrencodesrc_query_formats(AVFilterContext *ctx) |
| | 673 | { |
| | 674 | enum AVPixelFormat pix_fmt; |
| | 675 | FFDrawContext draw; |
| | 676 | AVFilterFormats *fmts = NULL; |
| | 677 | int ret; |
| | 678 | |
| | 679 | // this is needed to support both the no-draw and draw cases |
| | 680 | // for the no-draw case we use FFDrawContext to write on the input picture ref |
| | 681 | for (pix_fmt = 0; av_pix_fmt_desc_get(pix_fmt); pix_fmt++) |
| | 682 | if (ff_draw_init(&draw, pix_fmt, 0) >= 0 && |
| | 683 | sws_isSupportedOutput(pix_fmt) && |
| | 684 | (ret = ff_add_format(&fmts, pix_fmt)) < 0) |
| | 685 | return ret; |
| | 686 | |
| | 687 | return ff_set_common_formats(ctx, fmts); |
| | 688 | } |
| | 689 | |
| | 690 | static const AVFilterPad qrencodesrc_outputs[] = { |
| | 691 | { |
| | 692 | .name = "default", |
| | 693 | .type = AVMEDIA_TYPE_VIDEO, |
| | 694 | .request_frame = request_frame, |
| | 695 | .config_props = qrencodesrc_config_props, |
| | 696 | } |
| | 697 | }; |
| | 698 | |
| | 699 | const AVFilter ff_vsrc_qrencodesrc = { |
| | 700 | .name = "qrencodesrc", |
| | 701 | .description = NULL_IF_CONFIG_SMALL("Generate a QR code."), |
| | 702 | .priv_size = sizeof(QREncodeContext), |
| | 703 | .priv_class = &qrencodesrc_class, |
| | 704 | .init = init, |
| | 705 | .uninit = uninit, |
| | 706 | .inputs = NULL, |
| | 707 | FILTER_OUTPUTS(qrencodesrc_outputs), |
| | 708 | FILTER_QUERY_FUNC(qrencodesrc_query_formats), |
| | 709 | }; |
| | 710 | |
| | 711 | #endif // CONFIG_QRENCODESRC_FILTER |
| | 712 | |
| | 713 | #if CONFIG_QRENCODE_FILTER |
| | 714 | |
| | 715 | static const AVOption qrencode_options[] = { |
| | 716 | COMMON_OPTIONS |
| | 717 | {"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS}, |
| | 718 | {"y", "set y expression", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS}, |
| | 719 | { NULL } |
| | 720 | }; |
| | 721 | |
| | 722 | AVFILTER_DEFINE_CLASS(qrencode); |
| | 723 | |
| | 724 | static int qrencode_config_input(AVFilterLink *inlink) |
| | 725 | { |
| | 726 | AVFilterContext *ctx = inlink->dst; |
| | 727 | QREncodeContext *qr = ctx->priv; |
| | 728 | char *expr; |
| | 729 | int ret; |
| | 730 | |
| | 731 | qr->is_source = 0; |
| | 732 | |
| | 733 | ff_draw_init(&qr->draw, inlink->format, FF_DRAW_PROCESS_ALPHA); |
| | 734 | |
| | 735 | V(W) = V(main_w) = inlink->w; |
| | 736 | V(H) = V(main_h) = inlink->h; |
| | 737 | V(sar) = inlink->sample_aspect_ratio.num ? av_q2d(inlink->sample_aspect_ratio) : 1; |
| | 738 | V(dar) = (double)inlink->w / inlink->h * V(sar); |
| | 739 | V(hsub) = 1 << qr->draw.hsub_max; |
| | 740 | V(vsub) = 1 << qr->draw.vsub_max; |
| | 741 | V(t) = NAN; |
| | 742 | V(x) = V(y) = NAN; |
| | 743 | |
| | 744 | qr->x_pexpr = qr->y_pexpr = NULL; |
| | 745 | qr->x_pexpr = qr->y_pexpr = NULL; |
| | 746 | |
| | 747 | #define PARSE_EXPR(name_) \ |
| | 748 | ret = av_expr_parse(&qr->name_##_pexpr, expr = qr->name_##_expr, var_names, \ |
| | 749 | NULL, NULL, fun2_names, fun2, 0, ctx); \ |
| | 750 | if (ret < 0) { \ |
| | 751 | av_log(ctx, AV_LOG_ERROR, \ |
| | 752 | "Could not to parse expression '%s' for '%s'\n", \ |
| | 753 | expr, #name_); \ |
| | 754 | return AVERROR(EINVAL); \ |
| | 755 | } |
| | 756 | |
| | 757 | PARSE_EXPR(x); |
| | 758 | PARSE_EXPR(y); |
| | 759 | PARSE_EXPR(rendered_qrcode_width); |
| | 760 | PARSE_EXPR(rendered_padded_qrcode_width); |
| | 761 | |
| | 762 | ff_draw_init(&qr->draw, inlink->format, FF_DRAW_PROCESS_ALPHA); |
| | 763 | ff_draw_color(&qr->draw, &qr->draw_foreground_color, (const uint8_t *)&qr->foreground_color); |
| | 764 | ff_draw_color(&qr->draw, &qr->draw_background_color, (const uint8_t *)&qr->background_color); |
| | 765 | |
| | 766 | qr->rendered_qrcode_width = -1; |
| | 767 | |
| | 768 | return 0; |
| | 769 | } |
| | 770 | |
| | 771 | static int qrencode_query_formats(AVFilterContext *ctx) |
| | 772 | { |
| | 773 | return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0)); |
| | 774 | } |
| | 775 | |
| | 776 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) |
| | 777 | { |
| | 778 | AVFilterContext *ctx = inlink->dst; |
| | 779 | AVFilterLink *outlink = ctx->outputs[0]; |
| | 780 | QREncodeContext *qr = ctx->priv; |
| | 781 | int ret; |
| | 782 | |
| | 783 | V(n) = inlink->frame_count_out; |
| | 784 | V(t) = frame->pts == AV_NOPTS_VALUE ? |
| | 785 | NAN : frame->pts * av_q2d(inlink->time_base); |
| | 786 | V(pict_type) = frame->pict_type; |
| | 787 | V(duration) = frame->duration * av_q2d(inlink->time_base); |
| | 788 | |
| | 789 | qr->metadata = frame->metadata; |
| | 790 | |
| | 791 | if ((ret = draw_qrcode(ctx, frame)) < 0) |
| | 792 | return ret; |
| | 793 | |
| | 794 | return ff_filter_frame(outlink, frame); |
| | 795 | } |
| | 796 | |
| | 797 | static const AVFilterPad avfilter_vf_qrencode_inputs[] = { |
| | 798 | { |
| | 799 | .name = "default", |
| | 800 | .type = AVMEDIA_TYPE_VIDEO, |
| | 801 | .flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE, |
| | 802 | .filter_frame = filter_frame, |
| | 803 | .config_props = qrencode_config_input, |
| | 804 | }, |
| | 805 | }; |
| | 806 | |
| | 807 | const AVFilter ff_vf_qrencode = { |
| | 808 | .name = "qrencode", |
| | 809 | .description = NULL_IF_CONFIG_SMALL("Draw a QR code on top of video frames."), |
| | 810 | .priv_size = sizeof(QREncodeContext), |
| | 811 | .priv_class = &qrencode_class, |
| | 812 | .init = init, |
| | 813 | .uninit = uninit, |
| | 814 | FILTER_INPUTS(avfilter_vf_qrencode_inputs), |
| | 815 | FILTER_OUTPUTS(ff_video_default_filterpad), |
| | 816 | FILTER_QUERY_FUNC(qrencode_query_formats), |
| | 817 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
| | 818 | }; |
| | 819 | |
| | 820 | #endif // CONFIG_QRENCODE_FILTER |