GCC Code Coverage Report
Directory: ../../../ffmpeg/ Exec Total Coverage
File: src/libavcodec/ansi.c Lines: 168 311 54.0 %
Date: 2020-07-11 02:49:52 Branches: 105 204 51.5 %

Line Branch Exec Source
1
/*
2
 * ASCII/ANSI art decoder
3
 * Copyright (c) 2010 Peter Ross <pross@xvid.org>
4
 *
5
 * This file is part of FFmpeg.
6
 *
7
 * FFmpeg is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with FFmpeg; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
 */
21
22
/**
23
 * @file
24
 * ASCII/ANSI art decoder
25
 */
26
27
#include "libavutil/common.h"
28
#include "libavutil/frame.h"
29
#include "libavutil/lfg.h"
30
#include "libavutil/xga_font_data.h"
31
#include "avcodec.h"
32
#include "cga_data.h"
33
#include "internal.h"
34
35
#define ATTR_BOLD         0x01  /**< Bold/Bright-foreground (mode 1) */
36
#define ATTR_FAINT        0x02  /**< Faint (mode 2) */
37
#define ATTR_ITALICS      0x04  /**< Italics (mode 3) */
38
#define ATTR_UNDERLINE    0x08  /**< Underline (mode 4) */
39
#define ATTR_BLINK        0x10  /**< Blink/Bright-background (mode 5) */
40
#define ATTR_REVERSE      0x40  /**< Reverse (mode 7) */
41
#define ATTR_CONCEALED    0x80  /**< Concealed (mode 8) */
42
43
#define DEFAULT_FG_COLOR     7  /**< CGA color index */
44
#define DEFAULT_BG_COLOR     0
45
#define DEFAULT_SCREEN_MODE  3  /**< 80x25 */
46
47
#define FONT_WIDTH           8  /**< Font width */
48
49
/** map ansi color index to cga palette index */
50
static const uint8_t ansi_to_cga[16] = {
51
    0,  4,  2,  6,  1,  5,  3, 7, 8, 12, 10, 14,  9, 13, 11, 15
52
};
53
54
typedef struct AnsiContext {
55
    AVFrame *frame;
56
    int x;                /**< x cursor position (pixels) */
57
    int y;                /**< y cursor position (pixels) */
58
    int sx;               /**< saved x cursor position (pixels) */
59
    int sy;               /**< saved y cursor position (pixels) */
60
    const uint8_t* font;  /**< font */
61
    int font_height;      /**< font height */
62
    int attributes;       /**< attribute flags */
63
    int fg;               /**< foreground color */
64
    int bg;               /**< background color */
65
    int first_frame;
66
67
    /* ansi parser state machine */
68
    enum {
69
        STATE_NORMAL = 0,
70
        STATE_ESCAPE,
71
        STATE_CODE,
72
        STATE_MUSIC_PREAMBLE
73
    } state;
74
#define MAX_NB_ARGS 4
75
    int args[MAX_NB_ARGS];
76
    int nb_args;          /**< number of arguments (may exceed MAX_NB_ARGS) */
77
} AnsiContext;
78
79
4
static av_cold int decode_init(AVCodecContext *avctx)
80
{
81
4
    AnsiContext *s = avctx->priv_data;
82
4
    avctx->pix_fmt = AV_PIX_FMT_PAL8;
83
84
    /* defaults */
85
4
    s->font        = avpriv_vga16_font;
86
4
    s->font_height = 16;
87
4
    s->fg          = DEFAULT_FG_COLOR;
88
4
    s->bg          = DEFAULT_BG_COLOR;
89
90

6
    if (!avctx->width || !avctx->height) {
91
2
        int ret = ff_set_dimensions(avctx, 80 << 3, 25 << 4);
92
2
        if (ret < 0)
93
            return ret;
94

2
    } else if (avctx->width % FONT_WIDTH || avctx->height % s->font_height) {
95
        av_log(avctx, AV_LOG_ERROR, "Invalid dimensions %d %d\n", avctx->width, avctx->height);
96
        return AVERROR(EINVAL);
97
    }
98
99
4
    s->frame = av_frame_alloc();
100
4
    if (!s->frame)
101
        return AVERROR(ENOMEM);
102
103
4
    return 0;
104
}
105
106
25
static void set_palette(uint32_t *pal)
107
{
108
    int r, g, b;
109
25
    memcpy(pal, ff_cga_palette, 16 * 4);
110
25
    pal += 16;
111
#define COLOR(x) ((x) * 40 + 55)
112
175
    for (r = 0; r < 6; r++)
113
1050
        for (g = 0; g < 6; g++)
114
6300
            for (b = 0; b < 6; b++)
115
5400
                *pal++ = 0xFF000000 | (COLOR(r) << 16) | (COLOR(g) << 8) | COLOR(b);
116
#define GRAY(x) ((x) * 10 + 8)
117
625
    for (g = 0; g < 24; g++)
118
600
        *pal++ = 0xFF000000 | (GRAY(g) << 16) | (GRAY(g) << 8) | GRAY(g);
119
25
}
120
121
630
static void hscroll(AVCodecContext *avctx)
122
{
123
630
    AnsiContext *s = avctx->priv_data;
124
    int i;
125
126
630
    if (s->y <= avctx->height - 2*s->font_height) {
127
483
        s->y += s->font_height;
128
483
        return;
129
    }
130
131
147
    i = 0;
132
56595
    for (; i < avctx->height - s->font_height; i++)
133
56448
        memcpy(s->frame->data[0] + i * s->frame->linesize[0],
134
56448
               s->frame->data[0] + (i + s->font_height) * s->frame->linesize[0],
135
56448
               avctx->width);
136
2499
    for (; i < avctx->height; i++)
137
2352
        memset(s->frame->data[0] + i * s->frame->linesize[0],
138
2352
            DEFAULT_BG_COLOR, avctx->width);
139
}
140
141
static void erase_line(AVCodecContext * avctx, int xoffset, int xlength)
142
{
143
    AnsiContext *s = avctx->priv_data;
144
    int i;
145
    for (i = 0; i < s->font_height; i++)
146
        memset(s->frame->data[0] + (s->y + i)*s->frame->linesize[0] + xoffset,
147
            DEFAULT_BG_COLOR, xlength);
148
}
149
150
2
static void erase_screen(AVCodecContext *avctx)
151
{
152
2
    AnsiContext *s = avctx->priv_data;
153
    int i;
154
802
    for (i = 0; i < avctx->height; i++)
155
800
        memset(s->frame->data[0] + i * s->frame->linesize[0], DEFAULT_BG_COLOR, avctx->width);
156
2
    s->x = s->y = 0;
157
2
}
158
159
/**
160
 * Draw character to screen
161
 */
162
12126
static void draw_char(AVCodecContext *avctx, int c)
163
{
164
12126
    AnsiContext *s = avctx->priv_data;
165
12126
    int fg = s->fg;
166
12126
    int bg = s->bg;
167
168
12126
    if ((s->attributes & ATTR_BOLD))
169
3026
        fg += 8;
170
12126
    if ((s->attributes & ATTR_BLINK))
171
        bg += 8;
172
12126
    if ((s->attributes & ATTR_REVERSE))
173
        FFSWAP(int, fg, bg);
174
12126
    if ((s->attributes & ATTR_CONCEALED))
175
        fg = bg;
176
12126
    ff_draw_pc_font(s->frame->data[0] + s->y * s->frame->linesize[0] + s->x,
177
12126
                    s->frame->linesize[0], s->font, s->font_height, c, fg, bg);
178
12126
    s->x += FONT_WIDTH;
179
12126
    if (s->x > avctx->width - FONT_WIDTH) {
180
145
        s->x = 0;
181
145
        hscroll(avctx);
182
    }
183
12126
}
184
185
/**
186
 * Execute ANSI escape code
187
 * @return 0 on success, negative on error
188
 */
189
4624
static int execute_code(AVCodecContext * avctx, int c)
190
{
191
4624
    AnsiContext *s = avctx->priv_data;
192
    int ret, i;
193
4624
    int width  = avctx->width;
194
4624
    int height = avctx->height;
195
196



4624
    switch(c) {
197
435
    case 'A': //Cursor Up
198
435
        s->y = FFMAX(s->y - (s->nb_args > 0 ? s->args[0]*s->font_height : s->font_height), 0);
199
435
        break;
200
    case 'B': //Cursor Down
201
        s->y = FFMIN(s->y + (s->nb_args > 0 ? s->args[0]*s->font_height : s->font_height), avctx->height - s->font_height);
202
        break;
203
473
    case 'C': //Cursor Right
204

473
        s->x = FFMIN(s->x + (s->nb_args > 0 ? s->args[0]*FONT_WIDTH : FONT_WIDTH), avctx->width  - FONT_WIDTH);
205
473
        break;
206
    case 'D': //Cursor Left
207
        s->x = FFMAX(s->x - (s->nb_args > 0 ? s->args[0]*FONT_WIDTH : FONT_WIDTH), 0);
208
        break;
209
    case 'H': //Cursor Position
210
    case 'f': //Horizontal and Vertical Position
211
        s->y = s->nb_args > 0 ? av_clip((s->args[0] - 1)*s->font_height, 0, avctx->height - s->font_height) : 0;
212
        s->x = s->nb_args > 1 ? av_clip((s->args[1] - 1)*FONT_WIDTH,     0, avctx->width  - FONT_WIDTH) : 0;
213
        break;
214
    case 'h': //set screen mode
215
    case 'l': //reset screen mode
216
        if (s->nb_args < 2)
217
            s->args[0] = DEFAULT_SCREEN_MODE;
218
        switch(s->args[0]) {
219
        case 0: case 1: case 4: case 5: case 13: case 19: //320x200 (25 rows)
220
            s->font = avpriv_cga_font;
221
            s->font_height = 8;
222
            width  = 40<<3;
223
            height = 25<<3;
224
            break;
225
        case 2: case 3: //640x400 (25 rows)
226
            s->font = avpriv_vga16_font;
227
            s->font_height = 16;
228
            width  = 80<<3;
229
            height = 25<<4;
230
            break;
231
        case 6: case 14: //640x200 (25 rows)
232
            s->font = avpriv_cga_font;
233
            s->font_height = 8;
234
            width  = 80<<3;
235
            height = 25<<3;
236
            break;
237
        case 7: //set line wrapping
238
            break;
239
        case 15: case 16: //640x350 (43 rows)
240
            s->font = avpriv_cga_font;
241
            s->font_height = 8;
242
            width  = 80<<3;
243
            height = 43<<3;
244
            break;
245
        case 17: case 18: //640x480 (60 rows)
246
            s->font = avpriv_cga_font;
247
            s->font_height = 8;
248
            width  = 80<<3;
249
            height = 60<<4;
250
            break;
251
        default:
252
            avpriv_request_sample(avctx, "Unsupported screen mode");
253
        }
254
        s->x = av_clip(s->x, 0, width  - FONT_WIDTH);
255
        s->y = av_clip(s->y, 0, height - s->font_height);
256
        if (width != avctx->width || height != avctx->height) {
257
            av_frame_unref(s->frame);
258
            ret = ff_set_dimensions(avctx, width, height);
259
            if (ret < 0)
260
                return ret;
261
            if ((ret = ff_get_buffer(avctx, s->frame,
262
                                     AV_GET_BUFFER_FLAG_REF)) < 0)
263
                return ret;
264
            s->frame->pict_type           = AV_PICTURE_TYPE_I;
265
            s->frame->palette_has_changed = 1;
266
            set_palette((uint32_t *)s->frame->data[1]);
267
            erase_screen(avctx);
268
        } else if (c == 'l') {
269
            erase_screen(avctx);
270
        }
271
        break;
272
    case 'J': //Erase in Page
273
        switch (s->args[0]) {
274
        case 0:
275
            erase_line(avctx, s->x, avctx->width - s->x);
276
            if (s->y < avctx->height - s->font_height)
277
                memset(s->frame->data[0] + (s->y + s->font_height)*s->frame->linesize[0],
278
                    DEFAULT_BG_COLOR, (avctx->height - s->y - s->font_height)*s->frame->linesize[0]);
279
            break;
280
        case 1:
281
            erase_line(avctx, 0, s->x);
282
            if (s->y > 0)
283
                memset(s->frame->data[0], DEFAULT_BG_COLOR, s->y * s->frame->linesize[0]);
284
            break;
285
        case 2:
286
            erase_screen(avctx);
287
        }
288
        break;
289
    case 'K': //Erase in Line
290
        switch(s->args[0]) {
291
        case 0:
292
            erase_line(avctx, s->x, avctx->width - s->x);
293
            break;
294
        case 1:
295
            erase_line(avctx, 0, s->x);
296
            break;
297
        case 2:
298
            erase_line(avctx, 0, avctx->width);
299
        }
300
        break;
301
3716
    case 'm': //Select Graphics Rendition
302
3716
        if (s->nb_args == 0) {
303
            s->nb_args = 1;
304
            s->args[0] = 0;
305
        }
306
8955
        for (i = 0; i < FFMIN(s->nb_args, MAX_NB_ARGS); i++) {
307
5239
            int m = s->args[i];
308
5239
            if (m == 0) {
309
479
                s->attributes = 0;
310
479
                s->fg = DEFAULT_FG_COLOR;
311
479
                s->bg = DEFAULT_BG_COLOR;
312



4760
            } else if (m == 1 || m == 2 || m == 3 || m == 4 || m == 5 || m == 7 || m == 8) {
313
477
                s->attributes |= 1 << (m - 1);
314

4283
            } else if (m >= 30 && m <= 37) {
315
1651
                s->fg = ansi_to_cga[m - 30];
316


2632
            } else if (m == 38 && i + 2 < FFMIN(s->nb_args, MAX_NB_ARGS) && s->args[i + 1] == 5 && s->args[i + 2] < 256) {
317
512
                int index = s->args[i + 2];
318
512
                s->fg = index < 16 ? ansi_to_cga[index] : index;
319
512
                i += 2;
320
2120
            } else if (m == 39) {
321
                s->fg = ansi_to_cga[DEFAULT_FG_COLOR];
322

2120
            } else if (m >= 40 && m <= 47) {
323
1608
                s->bg = ansi_to_cga[m - 40];
324


512
            } else if (m == 48 && i + 2 < FFMIN(s->nb_args, MAX_NB_ARGS) && s->args[i + 1] == 5 && s->args[i + 2] < 256) {
325
512
                int index = s->args[i + 2];
326
512
                s->bg = index < 16 ? ansi_to_cga[index] : index;
327
512
                i += 2;
328
            } else if (m == 49) {
329
                s->fg = ansi_to_cga[DEFAULT_BG_COLOR];
330
            } else {
331
                avpriv_request_sample(avctx, "Unsupported rendition parameter");
332
            }
333
        }
334
3716
        break;
335
    case 'n': //Device Status Report
336
    case 'R': //report current line and column
337
        /* ignore */
338
        break;
339
    case 's': //Save Cursor Position
340
        s->sx = s->x;
341
        s->sy = s->y;
342
        break;
343
    case 'u': //Restore Cursor Position
344
        s->x = av_clip(s->sx, 0, avctx->width  - FONT_WIDTH);
345
        s->y = av_clip(s->sy, 0, avctx->height - s->font_height);
346
        break;
347
    default:
348
        avpriv_request_sample(avctx, "Unknown escape code");
349
        break;
350
    }
351
4624
    s->x = av_clip(s->x, 0, avctx->width  - FONT_WIDTH);
352
4624
    s->y = av_clip(s->y, 0, avctx->height - s->font_height);
353
4624
    return 0;
354
}
355
356
25
static int decode_frame(AVCodecContext *avctx,
357
                            void *data, int *got_frame,
358
                            AVPacket *avpkt)
359
{
360
25
    AnsiContext *s = avctx->priv_data;
361
25
    uint8_t *buf = avpkt->data;
362
25
    int buf_size = avpkt->size;
363
25
    const uint8_t *buf_end   = buf+buf_size;
364
    int ret, i, count;
365
366
25
    if ((ret = ff_reget_buffer(avctx, s->frame, 0)) < 0)
367
        return ret;
368
25
    if (!avctx->frame_number) {
369
802
        for (i=0; i<avctx->height; i++)
370
800
            memset(s->frame->data[0]+ i*s->frame->linesize[0], 0, avctx->width);
371
2
        memset(s->frame->data[1], 0, AVPALETTE_SIZE);
372
    }
373
374
25
    s->frame->pict_type           = AV_PICTURE_TYPE_I;
375
25
    s->frame->palette_has_changed = 1;
376
25
    set_palette((uint32_t *)s->frame->data[1]);
377
25
    if (!s->first_frame) {
378
2
        erase_screen(avctx);
379
2
        s->first_frame = 1;
380
    }
381
382
42502
    while(buf < buf_end) {
383

42477
        switch(s->state) {
384
16136
        case STATE_NORMAL:
385


16136
            switch (buf[0]) {
386
1
            case 0x00: //NUL
387
            case 0x07: //BEL
388
            case 0x1A: //SUB
389
                /* ignore */
390
1
                break;
391
            case 0x08: //BS
392
                s->x = FFMAX(s->x - 1, 0);
393
                break;
394
256
            case 0x09: //HT
395
256
                i = s->x / FONT_WIDTH;
396
256
                count = ((i + 8) & ~7) - i;
397
2048
                for (i = 0; i < count; i++)
398
1792
                    draw_char(avctx, ' ');
399
256
                break;
400
485
            case 0x0A: //LF
401
485
                hscroll(avctx);
402
921
            case 0x0D: //CR
403
921
                s->x = 0;
404
921
                break;
405
            case 0x0C: //FF
406
                erase_screen(avctx);
407
                break;
408
4624
            case 0x1B: //ESC
409
4624
                s->state = STATE_ESCAPE;
410
4624
                break;
411
10334
            default:
412
10334
                draw_char(avctx, buf[0]);
413
            }
414
16136
            break;
415
4624
        case STATE_ESCAPE:
416
4624
            if (buf[0] == '[') {
417
4624
                s->state   = STATE_CODE;
418
4624
                s->nb_args = 0;
419
4624
                s->args[0] = -1;
420
            } else {
421
                s->state = STATE_NORMAL;
422
                draw_char(avctx, 0x1B);
423
                continue;
424
            }
425
4624
            break;
426
21717
        case STATE_CODE:
427

21717
            switch(buf[0]) {
428
13522
            case '0': case '1': case '2': case '3': case '4':
429
            case '5': case '6': case '7': case '8': case '9':
430

13522
                if (s->nb_args < MAX_NB_ARGS && s->args[s->nb_args] < 6553)
431
13522
                    s->args[s->nb_args] = FFMAX(s->args[s->nb_args], 0) * 10 + buf[0] - '0';
432
13522
                break;
433
3571
            case ';':
434
3571
                s->nb_args++;
435
3571
                if (s->nb_args < MAX_NB_ARGS)
436
3571
                    s->args[s->nb_args] = 0;
437
3571
                break;
438
            case 'M':
439
                s->state = STATE_MUSIC_PREAMBLE;
440
                break;
441
            case '=': case '?':
442
                /* ignore */
443
                break;
444
4624
            default:
445
4624
                if (s->nb_args > MAX_NB_ARGS)
446
                    av_log(avctx, AV_LOG_WARNING, "args overflow (%i)\n", s->nb_args);
447

4624
                if (s->nb_args < MAX_NB_ARGS && s->args[s->nb_args] >= 0)
448
4188
                    s->nb_args++;
449
4624
                if ((ret = execute_code(avctx, buf[0])) < 0)
450
                    return ret;
451
4624
                s->state = STATE_NORMAL;
452
            }
453
21717
            break;
454
        case STATE_MUSIC_PREAMBLE:
455
            if (buf[0] == 0x0E || buf[0] == 0x1B)
456
                s->state = STATE_NORMAL;
457
            /* ignore music data */
458
            break;
459
        }
460
42477
        buf++;
461
    }
462
463
25
    *got_frame = 1;
464
25
    if ((ret = av_frame_ref(data, s->frame)) < 0)
465
        return ret;
466
25
    return buf_size;
467
}
468
469
4
static av_cold int decode_close(AVCodecContext *avctx)
470
{
471
4
    AnsiContext *s = avctx->priv_data;
472
473
4
    av_frame_free(&s->frame);
474
4
    return 0;
475
}
476
477
AVCodec ff_ansi_decoder = {
478
    .name           = "ansi",
479
    .long_name      = NULL_IF_CONFIG_SMALL("ASCII/ANSI art"),
480
    .type           = AVMEDIA_TYPE_VIDEO,
481
    .id             = AV_CODEC_ID_ANSI,
482
    .priv_data_size = sizeof(AnsiContext),
483
    .init           = decode_init,
484
    .close          = decode_close,
485
    .decode         = decode_frame,
486
    .capabilities   = AV_CODEC_CAP_DR1,
487
    .caps_internal  = FF_CODEC_CAP_INIT_THREADSAFE,
488
};