UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
text.c File Reference
#include <wand/magick_wand.h>
#include "capture_filter.h"
#include "debug.h"
#include "lib_common.h"
#include "video.h"
#include "video_display.h"
#include "vo_postprocess.h"
#include "utils/color_out.h"
#include "utils/macros.h"
#include "utils/string.h"
#include "utils/text.h"
Include dependency graph for text.c:

Go to the source code of this file.

Classes

struct  state_text
 

Macros

#define MARGIN_X_DIV   60
 
#define MARGIN_Y_DIV   60
 
#define TEXT_H_DIV   30
 

Functions

static struct video_frame * cf_text_filter (void *state, struct video_frame *f)
 
static int cf_text_init (struct module *parent, const char *cfg, void **state)
 
static void init_text ()
 
 REGISTER_MODULE (text, &capture_filter_text_info, LIBRARY_CLASS_CAPTURE_FILTER, CAPTURE_FILTER_ABI_VERSION)
 
 REGISTER_MODULE (text, &vo_pp_text_info, LIBRARY_CLASS_VIDEO_POSTPROCESS, VO_PP_ABI_VERSION)
 
static void text_done (void *state)
 
static void text_get_out_desc (void *state, struct video_desc *out, int *in_display_mode, int *out_frames)
 
static bool text_get_property (void *state, int property, void *val, size_t *len)
 
static struct video_frame * text_getf (void *state)
 
static void * text_init (const char *config)
 
static bool text_postprocess (void *state, struct video_frame *in, struct video_frame *out, int req_pitch)
 
static bool text_postprocess_reconfigure (void *state, struct video_desc desc)
 

Variables

static const struct capture_filter_info capture_filter_text_info
 
static pthread_once_t vo_postprocess_text_initialized = PTHREAD_ONCE_INIT
 
static const struct vo_postprocess_info vo_pp_text_info
 

Detailed Description

Author
Martin Pulec pulec.nosp@m.@ces.nosp@m.net.c.nosp@m.z
Todo
Add more options - eg. text position and size. Add support for more pixel formats.

Definition in file text.c.

Macro Definition Documentation

◆ MARGIN_X_DIV

#define MARGIN_X_DIV   60

Definition at line 107 of file text.c.

Referenced by text_postprocess_reconfigure().

◆ MARGIN_Y_DIV

#define MARGIN_Y_DIV   60

Definition at line 108 of file text.c.

Referenced by text_postprocess_reconfigure().

◆ TEXT_H_DIV

#define TEXT_H_DIV   30

Definition at line 106 of file text.c.

Referenced by text_postprocess_reconfigure().

Function Documentation

◆ cf_text_filter()

static struct video_frame * cf_text_filter ( void * state,
struct video_frame * f )
static

Definition at line 399 of file text.c.

400{
401 struct state_text *s = (struct state_text *) state;
402
406 } else {
407 log_msg(LOG_LEVEL_WARNING, "[text] Cannot reinitialize!\n");
408 return NULL;
409 }
410 }
411
412 struct video_frame *out = vf_alloc_desc_data(s->saved_desc);
414 if (text_postprocess(state, f, out, vc_get_linesize(f->tiles[0].width, f->color_spec))) {
416 return out;
417 } else {
419 vf_free(out);
420 return NULL;
421 }
422}
void ** state
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_desc video_desc_from_frame(const struct video_frame *frame)
Returns struct video_desc from video frame.
int vc_get_linesize(unsigned int width, codec_t codec)
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
struct video_desc saved_desc
Definition text.c:88
unsigned int width
Definition types.h:280
void(* dispose)(struct video_frame *)
Definition types.h:245
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
struct video_frame_callbacks callbacks
Definition types.h:324
codec_t color_spec
Definition types.h:306
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.
#define VIDEO_FRAME_DISPOSE(frame)
Definition video_frame.h:76
static bool text_postprocess(void *state, struct video_frame *in, struct video_frame *out, int req_pitch)
Definition text.c:313
static bool text_postprocess_reconfigure(void *state, struct video_desc desc)
Definition text.c:191

References video_frame::callbacks, video_frame::color_spec, video_frame_callbacks::dispose, LOG_LEVEL_WARNING, log_msg(), state_text::saved_desc, state, text_postprocess(), text_postprocess_reconfigure(), video_frame::tiles, vc_get_linesize(), vf_alloc_desc_data(), vf_free(), video_desc_eq(), video_desc_from_frame(), VIDEO_FRAME_DISPOSE, and tile::width.

Here is the call graph for this function:

◆ cf_text_init()

static int cf_text_init ( struct module * parent,
const char * cfg,
void ** state )
static

Definition at line 178 of file text.c.

179{
180 (void) parent;
181 void *s = text_init(cfg);
182 if (!s) {
183 return 1;
184 } else {
185 *state = s;
186 return 0;
187 }
188}
const char * parent
Definition settings.cpp:198
static void * text_init(const char *config)
Definition text.c:118

References parent, state, and text_init().

Here is the call graph for this function:

◆ init_text()

static void init_text ( )
static

Definition at line 112 of file text.c.

112 {
113 MagickWandGenesis();
114 atexit(MagickWandTerminus);
115}

Referenced by text_init().

◆ REGISTER_MODULE() [1/2]

REGISTER_MODULE ( text ,
& capture_filter_text_info,
LIBRARY_CLASS_CAPTURE_FILTER ,
CAPTURE_FILTER_ABI_VERSION  )

◆ REGISTER_MODULE() [2/2]

REGISTER_MODULE ( text ,
& vo_pp_text_info,
LIBRARY_CLASS_VIDEO_POSTPROCESS ,
VO_PP_ABI_VERSION  )

◆ text_done()

static void text_done ( void * state)
static

Definition at line 424 of file text.c.

425{
426 struct state_text *s = (struct state_text *) state;
427
428 vf_free(s->in);
429
430 DestroyMagickWand(s->wand_bg);
431 DestroyMagickWand(s->wand_text);
432 DestroyDrawingWand(s->dw);
433
434 free(s->data);
435 free(s->text);
436 free(s);
437}
char * data
Definition text.c:74
struct video_frame * in
Definition text.c:73
char * text
Definition text.c:76
MagickWand * wand_bg
Definition text.c:91
MagickWand * wand_text
Definition text.c:92
DrawingWand * dw
Definition text.c:90

References state_text::data, state_text::dw, state_text::in, state, state_text::text, vf_free(), state_text::wand_bg, and state_text::wand_text.

Here is the call graph for this function:

◆ text_get_out_desc()

static void text_get_out_desc ( void * state,
struct video_desc * out,
int * in_display_mode,
int * out_frames )
static

Definition at line 439 of file text.c.

440{
441 struct state_text *s = (struct state_text *) state;
442
443 *out = video_desc_from_frame(s->in);
444
445 *in_display_mode = DISPLAY_PROPERTY_VIDEO_MERGED;
446 *out_frames = 1;
447}
@ DISPLAY_PROPERTY_VIDEO_MERGED
monolithic framebuffer

References DISPLAY_PROPERTY_VIDEO_MERGED, state_text::in, state, and video_desc_from_frame().

Here is the call graph for this function:

◆ text_get_property()

static bool text_get_property ( void * state,
int property,
void * val,
size_t * len )
static

Definition at line 95 of file text.c.

96{
99 UNUSED(val);
100 UNUSED(len);
101
102 return false;
103}
#define UNUSED(x)
int void * val
int void size_t * len
int property

References len, property, state, UNUSED, and val.

◆ text_getf()

static struct video_frame * text_getf ( void * state)
static

Definition at line 306 of file text.c.

307{
308 struct state_text *s = (struct state_text *) state;
309
310 return s->in;
311}

References state_text::in, and state.

◆ text_init()

static void * text_init ( const char * config)
static

Definition at line 118 of file text.c.

118 {
120
121 struct state_text *s;
122
123 if (strlen(config) == 0 || strcmp(config, "help") == 0) {
124 char desc[] = TBOLD("text") " video postprocess takes as a parameter text to be drawed. "
125 "Colons in text must be escaped with a backslash (see Examples). Spaces may be escaped or the whole argument should be enclosed by quotation marks.\n";
127 color_printf("\nUsage:\n");
128 color_printf("\t" TBOLD("-p text:<text>") "\n");
129 color_printf("\t" TBOLD("-p text:x=<x>:y=<y>:h=<text_height>:t=<text>") "\n");
130 color_printf("\nExamples:\n");
131 color_printf(TBOLD("\t-p text:stream1\n"
132 "\t-p text:x=100:y=100:h=20:t=text\n"
133 "\t-p \"text:Video stream from location XY\"\n"
134 "\t-p \"text:Text can also contains escaped colons - \\:\"\n")
135 "\n");
136 return NULL;
137 }
138
139 s = calloc(1, sizeof *s);
140 s->req_x = s->req_y = s->req_h = -1;
141
142 char *config_copy = strdup(config);
143 replace_all(config_copy, ESCAPED_COLON, DELDEL);
144 char *tmp = config_copy, *item, *save_ptr;
145 while ((item = strtok_r(tmp, ":", &save_ptr))) {
146 if (strstr(item, "x=") != NULL) {
147 s->req_x = atoi(item + 2);
148 } else if (strstr(item, "y=") != NULL) {
149 s->req_y = atoi(item + 2);
150 } else if (strstr(item, "h=") != NULL) {
151 s->req_h = atoi(item + 2);
152 } else if (strstr(item, "t=") != NULL) {
153 replace_all(item + 2, DELDEL, ":");
154 s->text = strdup(item + 2);
155 } else {
156 replace_all(item, DELDEL, ":");
157 s->text = strdup(item);
158 }
159 tmp = NULL;
160 }
161 free(config_copy);
162
163 if(!s->text || strlen(s->text) == 0){
164 log_msg(LOG_LEVEL_ERROR, "[text] Text can not be empty!\n");
165 free(s);
166 return NULL;
167 }
168
169 if (s->req_h < -1) {
170 log_msg(LOG_LEVEL_ERROR, "[text] Text height cannot be negative!\n");
171 free(s);
172 return NULL;
173 }
174
175 return s;
176}
struct video_desc desc
int color_printf(const char *format,...)
#define TBOLD(x)
Definition color_out.h:70
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
void replace_all(char *in, const char *from, const char *to)
Replaces all occurencies of 'from' to 'to' in string 'in'.
Definition string.c:71
#define DELDEL
Definition string.h:50
#define ESCAPED_COLON
Definition string.h:51
Definition hd-rum.c:27
int req_h
Definition text.c:77
int req_x
Definition text.c:77
int req_y
Definition text.c:77
char * wrap_paragraph(char *text)
Definition text.c:204
static pthread_once_t vo_postprocess_text_initialized
Definition text.c:110
static void init_text()
Definition text.c:112

References color_printf(), DELDEL, desc, ESCAPED_COLON, init_text(), LOG_LEVEL_ERROR, log_msg(), replace_all(), state_text::req_h, state_text::req_x, state_text::req_y, TBOLD, state_text::text, vo_postprocess_text_initialized, and wrap_paragraph().

Referenced by cf_text_init().

Here is the call graph for this function:

◆ text_postprocess()

static bool text_postprocess ( void * state,
struct video_frame * in,
struct video_frame * out,
int req_pitch )
static

Definition at line 313 of file text.c.

314{
315 MagickBooleanType status;
316 struct state_text *s = (struct state_text *) state;
317
318 int dst_linesize = vc_get_linesize(s->width, in->color_spec);
319 int src_linesize = vc_get_linesize(in->tiles[0].width, in->color_spec);
320
321 int im_height_px = in->tiles[0].height;
322
323 // copy over just the relevant "stripe"
324 // cannot copy just the rectangle because pixel formats
325 // are not necessarily byte aligned
326 char *stripe = malloc((size_t) s->text_height_px * dst_linesize);
327 assert(stripe != NULL && "Malloc failed");
328 for (ptrdiff_t y = 0; y < s->text_height_px; y++) {
329 // skip what is out-of-bounds
330 if (y + s->margin_y < 0) {
331 y += -s->margin_y - 1;
332 continue;
333 }
334 if (y + s->margin_y >= im_height_px)
335 break;
336
337 // copy what isn't
338 memcpy(stripe + (y) * dst_linesize,
339 in->tiles[0].data + (y + s->margin_y) * src_linesize,
340 dst_linesize);
341 }
342
343 // read image stripe into ImageMagick
344 MagickRemoveImage(s->wand_bg);
345
346 status = MagickSetSize(s->wand_bg, s->width, s->text_height_px);
347 assert(status == MagickTrue && "[text vo_pp.] MagickSetSize failed -- ");
348
349 status = MagickReadImageBlob(s->wand_bg, stripe, s->text_height_px * dst_linesize);
350 if (status != MagickTrue) {
351 log_msg(LOG_LEVEL_WARNING, "[text vo_pp.] MagickReadImageBlob failed!\n");
352 return false;
353 }
354
355 // compose it with text
356#ifdef WAND7
357 status = MagickCompositeImage(s->wand_bg, s->wand_text, OverCompositeOp, true, s->margin_x, 0);
358#else
359 status = MagickCompositeImage(s->wand_bg, s->wand_text, OverCompositeOp, s->margin_x, 0);
360#endif
361 if (status != MagickTrue) {
362 log_msg(LOG_LEVEL_WARNING, "[text vo_pp.] MagickCompositeImage failed!\n");
363 return false;
364 }
365
366 // extract the composed data
367 unsigned char *data;
368 size_t data_len;
369 data = MagickGetImageBlob(s->wand_bg, &data_len);
370 assert(data != NULL && "[text vo_pp.] MagickGetImageBlob failed!");
371
372 // send the input stream...
373 memcpy(out->tiles[0].data, in->tiles[0].data, in->tiles[0].data_len);
374
375 // ...and the text stripe on top of it
376 // if ((int) data_len == s->text_height_pt * dstlinesize) {
377 for (int y = 0; y < s->text_height_px; y++) {
378 // again, skip what is oob
379 if (y + s->margin_y < 0) {
380 y += - s->margin_y - 1; // 1 == don't y++ on continue
381 continue;
382 }
383 if (y + s->margin_y >= im_height_px) {
384 break;
385 }
386
387 memcpy(out->tiles[0].data + (y + s->margin_y) * req_pitch,
388 data + y * dst_linesize,
389 dst_linesize);
390 }
391 // }
392
393 free(data);
394
395 free(stripe);
396 return true;
397}
int margin_x
Definition text.c:80
int width
Definition text.c:78
int margin_y
Definition text.c:80
int text_height_px
Definition text.c:86
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int data_len
length of the data
Definition types.h:288
unsigned int height
Definition types.h:281

References video_frame::color_spec, state_text::data, tile::data, tile::data_len, tile::height, state_text::in, LOG_LEVEL_WARNING, log_msg(), state_text::margin_x, state_text::margin_y, state, state_text::text_height_px, video_frame::tiles, vc_get_linesize(), state_text::wand_bg, state_text::wand_text, state_text::width, and tile::width.

Referenced by cf_text_filter().

Here is the call graph for this function:

◆ text_postprocess_reconfigure()

static bool text_postprocess_reconfigure ( void * state,
struct video_desc desc )
static

Definition at line 191 of file text.c.

192{
193 struct state_text *s = (struct state_text *) state;
194
195 vf_free(s->in);
196 if (s->wand_text)
197 DestroyMagickWand(s->wand_text);
198 if (s->wand_bg)
199 DestroyMagickWand(s->wand_bg);
200 if (s->dw)
201 DestroyDrawingWand(s->dw);
202 s->in = 0;
203 s->wand_bg = 0;
204 s->wand_text = 0;
205 s->dw = 0;
206
208
209 s->margin_x = s->req_x == -1 ? (int) desc.width / MARGIN_X_DIV : s->req_x;
210 s->margin_y = s->req_y == -1 ? (int) desc.height / MARGIN_Y_DIV : s->req_y;
211 s->text_height_pt = s->req_h == -1 ? (int) desc.height / TEXT_H_DIV : s->req_h;
212 s->width = MIN(s->margin_x + strlen(s->text) * s->text_height_pt, desc.width);
213 s->text_base_y = MIN(s->margin_y + s->text_height_pt, (int) desc.height);
214
215
216 const char *color;
217 const char *color_outline;
218 const char *colorspace;
219 if (desc.color_spec == RGBA) {
220 color = "#333333FF";
221 color_outline = "#FFFFFFFF";
222 colorspace = "rgba";
223 } else if (desc.color_spec == RGB) {
224 color = "#333333FF";
225 color_outline = "#FFFFFFFF";
226 colorspace = "rgb";
227 } else if (desc.color_spec == UYVY) {
228 color = "#228080FF";
229 color_outline = "#EB8080FF";
230 colorspace = "UYVY";
231 } else {
232 bug_msg(LOG_LEVEL_ERROR, "[text vo_pp.] Codec not supported! ");
233 return false;
234 }
235
236 s->dw = NewDrawingWand();
237 DrawSetFontSize(s->dw, s->text_height_pt);
238 MagickBooleanType status = DrawSetFont(s->dw, "helvetica");
239 if(status != MagickTrue) {
240 log_msg(LOG_LEVEL_WARNING, "[text vo_pp.] DraweSetFont failed!\n");
241 return false;
242 }
243 {
244 PixelWand *pw = NewPixelWand();
245 PixelSetColor(pw, color);
246 DrawSetFillColor(s->dw, pw);
247 PixelSetColor(pw, color_outline);
248 DrawSetStrokeColor(s->dw, pw);
249 DestroyPixelWand(pw);
250 }
251
252 s->wand_bg = NewMagickWand();
253 status = MagickSetFormat(s->wand_bg, colorspace);
254 s->wand_text = NewMagickWand();
255 status &= MagickSetFormat(s->wand_text, colorspace);
256 if(status != MagickTrue) {
257 log_msg(LOG_LEVEL_WARNING, "[text vo_pp.] MagickSetFormat failed!\n");
258 return false;
259 }
260
261
262 status = MagickSetDepth(s->wand_bg, 8);
263 status &= MagickSetDepth(s->wand_text, 8);
264 assert(status == MagickTrue && "[text vo_pp.] MagickSetDepth failed");
265
266 PixelWand *transparent_bg = NewPixelWand();
267 // PixelSetColor(transparent_bg, "#cccccc80"); // for debugging
268 PixelSetColor(transparent_bg, "#00000000");
269
270 // still need a dummy canvas to query font metrics
271 MagickNewImage(s->wand_text, 1, 1, transparent_bg);
272
273 // glyph w, glyph h, ascender, descender, txt w, txt h
274 double *metrics = MagickQueryFontMetrics(s->wand_text, s->dw, s->text);
275 if(!metrics) {
276 log_msg(LOG_LEVEL_WARNING, "[text vo_pp.] MagickQueryFontMetrics failed!\n");
277 return false;
278 }
279 double descender = metrics[3]; // has negative value
280 s->text_width_px = metrics[4];
281 s->text_height_px = metrics[5]; // whole height, including the descender
282 free(metrics);
283
284 MagickRemoveImage(s->wand_text);
285 status = MagickNewImage(s->wand_text, s->text_width_px, s->text_height_px, transparent_bg);
286 if (!status) {
287 log_msg(LOG_LEVEL_WARNING, "[text vo_pp.] MagickNewImage failed!\n");
288 return false;
289 }
290
291 double x_off = 0;
292 double y_off_baseline = s->text_height_px - (-descender) + 1;
293 double rot = 0;
294 status = MagickAnnotateImage(s->wand_text, s->dw, x_off, y_off_baseline, rot, s->text);
295 if (!status) {
296 log_msg(LOG_LEVEL_WARNING, "[text vo_pp.] MagickAnnotateImage failed!\n\n"
297 "Perhaps the text contains invalid characters?\n");
298 return false;
299 }
300
301 DestroyPixelWand(transparent_bg);
302
303 return true;
304}
void bug_msg(int level, const char *format,...)
prints the error with the advice to report the bug
Definition debug.cpp:379
#define MIN(a, b)
Definition macros.h:63
int text_base_y
Definition text.c:79
int text_width_px
Definition text.c:85
int text_height_pt
Definition text.c:83
codec_t color_spec
Definition types.h:182
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
#define TEXT_H_DIV
Definition text.c:106
#define MARGIN_Y_DIV
Definition text.c:108
#define MARGIN_X_DIV
Definition text.c:107

References bug_msg(), video_desc::color_spec, desc, state_text::dw, video_desc::height, state_text::in, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, log_msg(), state_text::margin_x, MARGIN_X_DIV, state_text::margin_y, MARGIN_Y_DIV, MIN, state_text::req_h, state_text::req_x, state_text::req_y, RGB, RGBA, state, state_text::text, state_text::text_base_y, TEXT_H_DIV, state_text::text_height_pt, state_text::text_height_px, state_text::text_width_px, UYVY, vf_alloc_desc_data(), vf_free(), state_text::wand_bg, state_text::wand_text, state_text::width, and video_desc::width.

Referenced by cf_text_filter().

Here is the call graph for this function:

Variable Documentation

◆ capture_filter_text_info

const struct capture_filter_info capture_filter_text_info
static
Initial value:
= {
}
static void text_done(void *state)
Definition text.c:424
static int cf_text_init(struct module *parent, const char *cfg, void **state)
Definition text.c:178
static struct video_frame * cf_text_filter(void *state, struct video_frame *f)
Definition text.c:399

Definition at line 459 of file text.c.

459 {
461 text_done,
463};

◆ vo_postprocess_text_initialized

pthread_once_t vo_postprocess_text_initialized = PTHREAD_ONCE_INIT
static

Definition at line 110 of file text.c.

Referenced by text_init().

◆ vo_pp_text_info

const struct vo_postprocess_info vo_pp_text_info
static
Initial value:
= {
}
static void text_get_out_desc(void *state, struct video_desc *out, int *in_display_mode, int *out_frames)
Definition text.c:439
static bool text_get_property(void *state, int property, void *val, size_t *len)
Definition text.c:95
static struct video_frame * text_getf(void *state)
Definition text.c:306

Definition at line 449 of file text.c.