GCC Code Coverage Report
Directory: ../../../ffmpeg/ Exec Total Coverage
File: src/libavcodec/ansi.c Lines: 168 311 54.0 %
Date: 2019-11-18 18:00:01 Branches: 104 202 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_UNDERLINE    0x08  /**< Underline (mode 4) */
38
#define ATTR_BLINK        0x10  /**< Blink/Bright-background (mode 5) */
39
#define ATTR_REVERSE      0x40  /**< Reverse (mode 7) */
40
#define ATTR_CONCEALED    0x80  /**< Concealed (mode 8) */
41
42
#define DEFAULT_FG_COLOR     7  /**< CGA color index */
43
#define DEFAULT_BG_COLOR     0
44
#define DEFAULT_SCREEN_MODE  3  /**< 80x25 */
45
46
#define FONT_WIDTH           8  /**< Font width */
47
48
/** map ansi color index to cga palette index */
49
static const uint8_t ansi_to_cga[16] = {
50
    0,  4,  2,  6,  1,  5,  3, 7, 8, 12, 10, 14,  9, 13, 11, 15
51
};
52
53
typedef struct AnsiContext {
54
    AVFrame *frame;
55
    int x;                /**< x cursor position (pixels) */
56
    int y;                /**< y cursor position (pixels) */
57
    int sx;               /**< saved x cursor position (pixels) */
58
    int sy;               /**< saved y cursor position (pixels) */
59
    const uint8_t* font;  /**< font */
60
    int font_height;      /**< font height */
61
    int attributes;       /**< attribute flags */
62
    int fg;               /**< foreground color */
63
    int bg;               /**< background color */
64
    int first_frame;
65
66
    /* ansi parser state machine */
67
    enum {
68
        STATE_NORMAL = 0,
69
        STATE_ESCAPE,
70
        STATE_CODE,
71
        STATE_MUSIC_PREAMBLE
72
    } state;
73
#define MAX_NB_ARGS 4
74
    int args[MAX_NB_ARGS];
75
    int nb_args;          /**< number of arguments (may exceed MAX_NB_ARGS) */
76
} AnsiContext;
77
78
4
static av_cold int decode_init(AVCodecContext *avctx)
79
{
80
4
    AnsiContext *s = avctx->priv_data;
81
4
    avctx->pix_fmt = AV_PIX_FMT_PAL8;
82
83
    /* defaults */
84
4
    s->font        = avpriv_vga16_font;
85
4
    s->font_height = 16;
86
4
    s->fg          = DEFAULT_FG_COLOR;
87
4
    s->bg          = DEFAULT_BG_COLOR;
88
89

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

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



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

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



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

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


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

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


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

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


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

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

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

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