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