GCC Code Coverage Report
Directory: ../../../ffmpeg/ Exec Total Coverage
File: src/libavcodec/cdtoons.c Lines: 0 233 0.0 %
Date: 2020-04-04 00:26:16 Branches: 0 128 0.0 %

Line Branch Exec Source
1
/*
2
 * CDToons video decoder
3
 * Copyright (C) 2020 Alyssa Milburn <amilburn@zall.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
 * CDToons video decoder
25
 * @author Alyssa Milburn <amilburn@zall.org>
26
 */
27
28
#include <stdint.h>
29
30
#include "libavutil/attributes.h"
31
#include "libavutil/internal.h"
32
#include "avcodec.h"
33
#include "bytestream.h"
34
#include "internal.h"
35
36
#define CDTOONS_HEADER_SIZE   44
37
#define CDTOONS_MAX_SPRITES 1200
38
39
typedef struct CDToonsSprite {
40
    uint16_t flags;
41
    uint16_t owner_frame;
42
    uint16_t start_frame;
43
    uint16_t end_frame;
44
    unsigned int alloc_size;
45
    uint32_t size;
46
    uint8_t *data;
47
    int      active;
48
} CDToonsSprite;
49
50
typedef struct CDToonsContext {
51
    AVFrame *frame;
52
53
    uint16_t last_pal_id;   ///< The index of the active palette sprite.
54
    uint32_t pal[256];      ///< The currently-used palette data.
55
    CDToonsSprite sprites[CDTOONS_MAX_SPRITES];
56
} CDToonsContext;
57
58
static int cdtoons_render_sprite(AVCodecContext *avctx, const uint8_t *data,
59
                                 uint32_t data_size,
60
                                 int dst_x, int dst_y, int width, int height)
61
{
62
    CDToonsContext *c = avctx->priv_data;
63
    const uint8_t *next_line = data;
64
    const uint8_t *end = data + data_size;
65
    uint16_t line_size;
66
    uint8_t *dest;
67
    int skip = 0, to_skip, x;
68
69
    if (dst_x + width > avctx->width)
70
        width = avctx->width - dst_x;
71
    if (dst_y + height > avctx->height)
72
        height = avctx->height - dst_y;
73
74
    if (dst_x < 0) {
75
        /* we need to skip the start of the scanlines */
76
        skip = -dst_x;
77
        if (width <= skip)
78
            return 0;
79
        dst_x = 0;
80
    }
81
82
    for (int y = 0; y < height; y++) {
83
        /* one scanline at a time, size is provided */
84
        data      = next_line;
85
        if (end - data < 2)
86
            return 1;
87
        line_size = bytestream_get_be16(&data);
88
        if (end - data < line_size)
89
            return 1;
90
        next_line = data + line_size;
91
        if (dst_y + y < 0)
92
            continue;
93
94
        dest = c->frame->data[0] + (dst_y + y) * c->frame->linesize[0] + dst_x;
95
96
        to_skip = skip;
97
        x       = 0;
98
        while (x < width - skip) {
99
            int raw, size, step;
100
            uint8_t val;
101
102
            if (data >= end)
103
                return 1;
104
105
            val  = bytestream_get_byte(&data);
106
            raw  = !(val & 0x80);
107
            size = (int)(val & 0x7F) + 1;
108
109
            /* skip the start of a scanline if it is off-screen */
110
            if (to_skip >= size) {
111
                to_skip -= size;
112
                if (raw) {
113
                    step = size;
114
                } else {
115
                    step = 1;
116
                }
117
                if (next_line - data < step)
118
                    return 1;
119
                data += step;
120
                continue;
121
            } else if (to_skip) {
122
                size -= to_skip;
123
                if (raw) {
124
                    if (next_line - data < to_skip)
125
                        return 1;
126
                    data += to_skip;
127
                }
128
                to_skip = 0;
129
            }
130
131
            if (x + size >= width - skip)
132
                size = width - skip - x;
133
134
            /* either raw data, or a run of a single color */
135
            if (raw) {
136
                if (next_line - data < size)
137
                    return 1;
138
                memcpy(dest + x, data, size);
139
                data += size;
140
            } else {
141
                uint8_t color = bytestream_get_byte(&data);
142
                /* ignore transparent runs */
143
                if (color)
144
                    memset(dest + x, color, size);
145
            }
146
            x += size;
147
        }
148
    }
149
150
    return 0;
151
}
152
153
static int cdtoons_decode_frame(AVCodecContext *avctx, void *data,
154
                                int *got_frame, AVPacket *avpkt)
155
{
156
    CDToonsContext *c = avctx->priv_data;
157
    const uint8_t *buf = avpkt->data;
158
    const uint8_t *eod = avpkt->data + avpkt->size;
159
    const int buf_size = avpkt->size;
160
    uint16_t frame_id;
161
    uint8_t background_color;
162
    uint16_t sprite_count, sprite_offset;
163
    uint8_t referenced_count;
164
    uint16_t palette_id;
165
    uint8_t palette_set;
166
    int ret;
167
    int saw_embedded_sprites = 0;
168
169
    if (buf_size < CDTOONS_HEADER_SIZE)
170
        return AVERROR_INVALIDDATA;
171
172
    if ((ret = ff_reget_buffer(avctx, c->frame, 0)) < 0)
173
        return ret;
174
175
    /* a lot of the header is useless junk in the absence of
176
     * dirty rectangling etc */
177
    buf               += 2; /* version? (always 9?) */
178
    frame_id           = bytestream_get_be16(&buf);
179
    buf               += 2; /* blocks_valid_until */
180
    buf               += 1;
181
    background_color   = bytestream_get_byte(&buf);
182
    buf               += 16; /* clip rect, dirty rect */
183
    buf               += 4; /* flags */
184
    sprite_count       = bytestream_get_be16(&buf);
185
    sprite_offset      = bytestream_get_be16(&buf);
186
    buf               += 2; /* max block id? */
187
    referenced_count   = bytestream_get_byte(&buf);
188
    buf               += 1;
189
    palette_id         = bytestream_get_be16(&buf);
190
    palette_set        = bytestream_get_byte(&buf);
191
    buf               += 5;
192
193
    /* read new sprites introduced in this frame */
194
    buf = avpkt->data + sprite_offset;
195
    while (sprite_count--) {
196
        uint32_t size;
197
        uint16_t sprite_id;
198
199
        if (buf + 14 > eod)
200
            return AVERROR_INVALIDDATA;
201
202
        sprite_id = bytestream_get_be16(&buf);
203
        if (sprite_id >= CDTOONS_MAX_SPRITES) {
204
            av_log(avctx, AV_LOG_ERROR,
205
                   "Sprite ID %d is too high.\n", sprite_id);
206
            return AVERROR_INVALIDDATA;
207
        }
208
        if (c->sprites[sprite_id].active) {
209
            av_log(avctx, AV_LOG_ERROR,
210
                   "Sprite ID %d is a duplicate.\n", sprite_id);
211
            return AVERROR_INVALIDDATA;
212
        }
213
214
        c->sprites[sprite_id].flags = bytestream_get_be16(&buf);
215
        size                        = bytestream_get_be32(&buf);
216
        if (size < 14) {
217
            av_log(avctx, AV_LOG_ERROR,
218
                   "Sprite only has %d bytes of data.\n", size);
219
            return AVERROR_INVALIDDATA;
220
        }
221
        size -= 14;
222
        c->sprites[sprite_id].size        = size;
223
        c->sprites[sprite_id].owner_frame = frame_id;
224
        c->sprites[sprite_id].start_frame = bytestream_get_be16(&buf);
225
        c->sprites[sprite_id].end_frame   = bytestream_get_be16(&buf);
226
        buf += 2;
227
228
        if (size > buf_size || buf + size > eod)
229
            return AVERROR_INVALIDDATA;
230
231
        av_fast_padded_malloc(&c->sprites[sprite_id].data, &c->sprites[sprite_id].alloc_size, size);
232
        if (!c->sprites[sprite_id].data)
233
            return AVERROR(ENOMEM);
234
235
        c->sprites[sprite_id].active = 1;
236
237
        bytestream_get_buffer(&buf, c->sprites[sprite_id].data, size);
238
    }
239
240
    /* render any embedded sprites */
241
    while (buf < eod) {
242
        uint32_t tag, size;
243
        if (buf + 8 > eod) {
244
            av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for embedded sprites.\n");
245
            return AVERROR_INVALIDDATA;
246
        }
247
        tag  = bytestream_get_be32(&buf);
248
        size = bytestream_get_be32(&buf);
249
        if (tag == MKBETAG('D', 'i', 'f', 'f')) {
250
            uint16_t diff_count;
251
            if (buf + 10 > eod) {
252
                av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame.\n");
253
                return AVERROR_INVALIDDATA;
254
            }
255
            diff_count = bytestream_get_be16(&buf);
256
            buf       += 8; /* clip rect? */
257
            for (int i = 0; i < diff_count; i++) {
258
                int16_t top, left;
259
                uint16_t diff_size, width, height;
260
261
                if (buf + 16 > eod) {
262
                    av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame header.\n");
263
                    return AVERROR_INVALIDDATA;
264
                }
265
266
                top        = bytestream_get_be16(&buf);
267
                left       = bytestream_get_be16(&buf);
268
                buf       += 4; /* bottom, right */
269
                diff_size  = bytestream_get_be32(&buf);
270
                width      = bytestream_get_be16(&buf);
271
                height     = bytestream_get_be16(&buf);
272
                if (diff_size < 8 || diff_size - 4 > eod - buf) {
273
                    av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame data.\n");
274
                    return AVERROR_INVALIDDATA;
275
                }
276
                if (cdtoons_render_sprite(avctx, buf + 4, diff_size - 8,
277
                                      left, top, width, height)) {
278
                    av_log(avctx, AV_LOG_WARNING, "Ran beyond end of sprite while rendering.\n");
279
                }
280
                buf += diff_size - 4;
281
            }
282
            saw_embedded_sprites = 1;
283
        } else {
284
            /* we don't care about any other entries */
285
            if (size < 8 || size - 8 > eod - buf) {
286
                av_log(avctx, AV_LOG_WARNING, "Ran out of data for ignored entry (size %X, %d left).\n", size, (int)(eod - buf));
287
                return AVERROR_INVALIDDATA;
288
            }
289
            buf += (size - 8);
290
        }
291
    }
292
293
    /* was an intra frame? */
294
    if (saw_embedded_sprites)
295
        goto done;
296
297
    /* render any referenced sprites */
298
    buf = avpkt->data + CDTOONS_HEADER_SIZE;
299
    eod = avpkt->data + sprite_offset;
300
    for (int i = 0; i < referenced_count; i++) {
301
        const uint8_t *block_data;
302
        uint16_t sprite_id, width, height;
303
        int16_t top, left, right;
304
305
        if (buf + 10 > eod) {
306
            av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data when rendering.\n");
307
            return AVERROR_INVALIDDATA;
308
        }
309
310
        sprite_id = bytestream_get_be16(&buf);
311
        top       = bytestream_get_be16(&buf);
312
        left      = bytestream_get_be16(&buf);
313
        buf      += 2; /* bottom */
314
        right     = bytestream_get_be16(&buf);
315
316
        if ((i == 0) && (sprite_id == 0)) {
317
            /* clear background */
318
            memset(c->frame->data[0], background_color,
319
                   c->frame->linesize[0] * avctx->height);
320
        }
321
322
        if (!right)
323
            continue;
324
        if (sprite_id >= CDTOONS_MAX_SPRITES) {
325
            av_log(avctx, AV_LOG_ERROR,
326
                   "Sprite ID %d is too high.\n", sprite_id);
327
            return AVERROR_INVALIDDATA;
328
        }
329
330
        block_data = c->sprites[sprite_id].data;
331
        if (!c->sprites[sprite_id].active) {
332
            /* this can happen when seeking around */
333
            av_log(avctx, AV_LOG_WARNING, "Sprite %d is missing.\n", sprite_id);
334
            continue;
335
        }
336
        if (c->sprites[sprite_id].size < 14) {
337
            av_log(avctx, AV_LOG_ERROR, "Sprite %d is too small.\n", sprite_id);
338
            continue;
339
        }
340
341
        height      = bytestream_get_be16(&block_data);
342
        width       = bytestream_get_be16(&block_data);
343
        block_data += 10;
344
        if (cdtoons_render_sprite(avctx, block_data,
345
                              c->sprites[sprite_id].size - 14,
346
                              left, top, width, height)) {
347
            av_log(avctx, AV_LOG_WARNING, "Ran beyond end of sprite while rendering.\n");
348
        }
349
    }
350
351
    if (palette_id && (palette_id != c->last_pal_id)) {
352
        if (palette_id >= CDTOONS_MAX_SPRITES) {
353
            av_log(avctx, AV_LOG_ERROR,
354
                   "Palette ID %d is too high.\n", palette_id);
355
            return AVERROR_INVALIDDATA;
356
        }
357
        if (!c->sprites[palette_id].active) {
358
            /* this can happen when seeking around */
359
            av_log(avctx, AV_LOG_WARNING,
360
                   "Palette ID %d is missing.\n", palette_id);
361
            goto done;
362
        }
363
        if (c->sprites[palette_id].size != 256 * 2 * 3) {
364
            av_log(avctx, AV_LOG_ERROR,
365
                   "Palette ID %d is wrong size (%d).\n",
366
                   palette_id, c->sprites[palette_id].size);
367
            return AVERROR_INVALIDDATA;
368
        }
369
        c->last_pal_id = palette_id;
370
        if (!palette_set) {
371
            uint8_t *palette_data = c->sprites[palette_id].data;
372
            for (int i = 0; i < 256; i++) {
373
                /* QuickTime-ish palette: 16-bit RGB components */
374
                unsigned r, g, b;
375
                r             = *palette_data;
376
                g             = *(palette_data + 2);
377
                b             = *(palette_data + 4);
378
                c->pal[i]     = (0xFFU << 24) | (r << 16) | (g << 8) | b;
379
                palette_data += 6;
380
            }
381
            /* first palette entry indicates transparency */
382
            c->pal[0]                     = 0;
383
            c->frame->palette_has_changed = 1;
384
        }
385
    }
386
387
done:
388
    /* discard outdated blocks */
389
    for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) {
390
        if (c->sprites[i].end_frame > frame_id)
391
            continue;
392
        c->sprites[i].active = 0;
393
    }
394
395
    memcpy(c->frame->data[1], c->pal, AVPALETTE_SIZE);
396
397
    if ((ret = av_frame_ref(data, c->frame)) < 0)
398
        return ret;
399
400
    *got_frame = 1;
401
402
    /* always report that the buffer was completely consumed */
403
    return buf_size;
404
}
405
406
static av_cold int cdtoons_decode_init(AVCodecContext *avctx)
407
{
408
    CDToonsContext *c = avctx->priv_data;
409
410
    avctx->pix_fmt = AV_PIX_FMT_PAL8;
411
    c->last_pal_id = 0;
412
    c->frame       = av_frame_alloc();
413
    if (!c->frame)
414
        return AVERROR(ENOMEM);
415
416
    return 0;
417
}
418
419
static void cdtoons_flush(AVCodecContext *avctx)
420
{
421
    CDToonsContext *c = avctx->priv_data;
422
423
    c->last_pal_id = 0;
424
    for (int i = 0; i < CDTOONS_MAX_SPRITES; i++)
425
        c->sprites[i].active = 0;
426
}
427
428
static av_cold int cdtoons_decode_end(AVCodecContext *avctx)
429
{
430
    CDToonsContext *c = avctx->priv_data;
431
432
    for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) {
433
        av_freep(&c->sprites[i].data);
434
        c->sprites[i].active = 0;
435
    }
436
437
    av_frame_free(&c->frame);
438
439
    return 0;
440
}
441
442
AVCodec ff_cdtoons_decoder = {
443
    .name           = "cdtoons",
444
    .long_name      = NULL_IF_CONFIG_SMALL("CDToons video"),
445
    .type           = AVMEDIA_TYPE_VIDEO,
446
    .id             = AV_CODEC_ID_CDTOONS,
447
    .priv_data_size = sizeof(CDToonsContext),
448
    .init           = cdtoons_decode_init,
449
    .close          = cdtoons_decode_end,
450
    .decode         = cdtoons_decode_frame,
451
    .capabilities   = AV_CODEC_CAP_DR1,
452
    .flush          = cdtoons_flush,
453
};