Commit bb2cb0a6 authored by Vittorio Giovara's avatar Vittorio Giovara

quickdraw: Switch to greedy parsing

Quickdraw packs data as a series of codes that the application is supposed
to handle, but it does not define any order in which they might appear.
Since it's unfeasible to support *all* opcodes defined by the spec,
only handle well-known blocks containing video data and ignore any unknown
or unsupported ones.

Move palette loading and rle decoding to separate functions to resue them
in other blocks and drop format initialization in init since it can
support more formats than pal8.

Validate width and height.
parent d00f1e0f
/* /*
* QuickDraw (qdrw) codec * QuickDraw (qdrw) codec
* Copyright (c) 2004 Konstantin Shishkov * Copyright (c) 2004 Konstantin Shishkov
* Copyright (c) 2015 Vittorio Giovara
* *
* This file is part of Libav. * This file is part of Libav.
* *
...@@ -22,6 +23,7 @@ ...@@ -22,6 +23,7 @@
/** /**
* @file * @file
* Apple QuickDraw codec. * Apple QuickDraw codec.
* https://developer.apple.com/legacy/library/documentation/mac/QuickDraw/QuickDraw-461.html
*/ */
#include "libavutil/common.h" #include "libavutil/common.h"
...@@ -30,40 +32,136 @@ ...@@ -30,40 +32,136 @@
#include "bytestream.h" #include "bytestream.h"
#include "internal.h" #include "internal.h"
enum QuickdrawOpcodes {
PACKBITSRECT = 0x0098,
PACKBITSRGN,
EOP = 0x00FF,
};
static int parse_palette(AVCodecContext *avctx, GetByteContext *gbc,
uint32_t *pal, int colors)
{
int i;
for (i = 0; i <= colors; i++) {
uint8_t r, g, b;
unsigned int idx = bytestream2_get_be16(gbc); /* color index */
if (idx > 255) {
av_log(avctx, AV_LOG_WARNING,
"Palette index out of range: %u\n", idx);
bytestream2_skip(gbc, 6);
continue;
}
r = bytestream2_get_byte(gbc);
bytestream2_skip(gbc, 1);
g = bytestream2_get_byte(gbc);
bytestream2_skip(gbc, 1);
b = bytestream2_get_byte(gbc);
bytestream2_skip(gbc, 1);
pal[idx] = (r << 16) | (g << 8) | b;
}
return 0;
}
static int decode_rle(AVCodecContext *avctx, AVFrame *p, GetByteContext *gbc)
{
int i;
uint8_t *outdata = p->data[0];
for (i = 0; i < avctx->height; i++) {
int size, left, code, pix;
uint8_t *out = outdata;
/* size of packed line */
size = left = bytestream2_get_be16(gbc);
if (bytestream2_get_bytes_left(gbc) < size)
return AVERROR_INVALIDDATA;
/* decode line */
while (left > 0) {
code = bytestream2_get_byte(gbc);
if (code & 0x80 ) { /* run */
pix = bytestream2_get_byte(gbc);
memset(out, pix, 257 - code);
out += 257 - code;
left -= 2;
} else { /* copy */
bytestream2_get_buffer(gbc, out, code + 1);
out += code + 1;
left -= 2 + code;
}
}
outdata += p->linesize[0];
}
return 0;
}
static int decode_frame(AVCodecContext *avctx, static int decode_frame(AVCodecContext *avctx,
void *data, int *got_frame, void *data, int *got_frame,
AVPacket *avpkt) AVPacket *avpkt)
{ {
AVFrame * const p = data; AVFrame * const p = data;
GetByteContext gbc; GetByteContext gbc;
uint8_t* outdata;
int colors; int colors;
int i, ret; int w, h, ret;
uint32_t *pal;
if ((ret = ff_get_buffer(avctx, p, 0)) < 0) { bytestream2_init(&gbc, avpkt->data, avpkt->size);
av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
/* smallest PICT header */
if (bytestream2_get_bytes_left(&gbc) < 40) {
av_log(avctx, AV_LOG_ERROR, "Frame is too small %d\n",
bytestream2_get_bytes_left(&gbc));
return AVERROR_INVALIDDATA;
}
bytestream2_skip(&gbc, 6);
h = bytestream2_get_be16(&gbc);
w = bytestream2_get_be16(&gbc);
ret = ff_set_dimensions(avctx, w, h);
if (ret < 0)
return ret; return ret;
/* version 1 is identified by 0x1101
* it uses byte-aligned opcodes rather than word-aligned */
if (bytestream2_get_be32(&gbc) != 0x001102FF) {
avpriv_request_sample(avctx, "QuickDraw version 1");
return AVERROR_PATCHWELCOME;
} }
p->pict_type = AV_PICTURE_TYPE_I;
p->key_frame = 1;
outdata = p->data[0]; bytestream2_skip(&gbc, 26);
bytestream2_init(&gbc, avpkt->data, avpkt->size); while (bytestream2_get_bytes_left(&gbc) >= 4) {
int bppcnt, bpp;
int opcode = bytestream2_get_be16(&gbc);
if (bytestream2_get_bytes_left(&gbc) < 0x68 + 4) { switch(opcode) {
av_log(avctx, AV_LOG_ERROR, "Frame is too small %d\n", case PACKBITSRECT:
bytestream2_get_bytes_left(&gbc)); case PACKBITSRGN:
av_log(avctx, AV_LOG_DEBUG, "Parsing Packbit opcode\n");
bytestream2_skip(&gbc, 30);
bppcnt = bytestream2_get_be16(&gbc); /* cmpCount */
bpp = bytestream2_get_be16(&gbc); /* cmpSize */
av_log(avctx, AV_LOG_DEBUG, "bppcount %d bpp %d\n", bppcnt, bpp);
if (bppcnt == 1 && bpp == 8) {
avctx->pix_fmt = AV_PIX_FMT_PAL8;
} else {
av_log(avctx, AV_LOG_ERROR,
"Invalid pixel format (bppcnt %d bpp %d) in Packbit\n",
bppcnt, bpp);
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
} }
/* jump to palette */ /* jump to palette */
bytestream2_skip(&gbc, 0x68); bytestream2_skip(&gbc, 18);
colors = bytestream2_get_be32(&gbc); colors = bytestream2_get_be16(&gbc);
if (colors < 0 || colors > 256) { if (colors < 0 || colors > 256) {
av_log(avctx, AV_LOG_ERROR, "Error color count - %i(0x%X)\n", colors, colors); av_log(avctx, AV_LOG_ERROR,
"Error color count - %i(0x%X)\n", colors, colors);
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
} }
if (bytestream2_get_bytes_left(&gbc) < (colors + 1) * 8) { if (bytestream2_get_bytes_left(&gbc) < (colors + 1) * 8) {
...@@ -71,65 +169,59 @@ static int decode_frame(AVCodecContext *avctx, ...@@ -71,65 +169,59 @@ static int decode_frame(AVCodecContext *avctx,
bytestream2_get_bytes_left(&gbc)); bytestream2_get_bytes_left(&gbc));
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
} }
if ((ret = ff_get_buffer(avctx, p, 0)) < 0) {
pal = (uint32_t*)p->data[1]; av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
for (i = 0; i <= colors; i++) { return ret;
uint8_t r, g, b;
unsigned int idx = bytestream2_get_be16(&gbc); /* color index */
if (idx > 255) {
av_log(avctx, AV_LOG_ERROR, "Palette index out of range: %u\n", idx);
bytestream2_skip(&gbc, 6);
continue;
}
r = bytestream2_get_byte(&gbc);
bytestream2_skip(&gbc, 1);
g = bytestream2_get_byte(&gbc);
bytestream2_skip(&gbc, 1);
b = bytestream2_get_byte(&gbc);
bytestream2_skip(&gbc, 1);
pal[idx] = (r << 16) | (g << 8) | b;
} }
parse_palette(avctx, &gbc, (uint32_t *)p->data[1], colors);
p->palette_has_changed = 1; p->palette_has_changed = 1;
/* skip unneeded data */ /* jump to image data */
bytestream2_skip(&gbc, 18); bytestream2_skip(&gbc, 18);
for (i = 0; i < avctx->height; i++) { if (opcode == PACKBITSRGN) {
int size, left, code, pix; bytestream2_skip(&gbc, 2 + 8); /* size + rect */
uint8_t *out = outdata; avpriv_report_missing_feature(avctx, "Packbit mask region");
}
/* size of packed line */
size = left = bytestream2_get_be16(&gbc);
if (bytestream2_get_bytes_left(&gbc) < size)
return AVERROR_INVALIDDATA;
/* decode line */ ret = decode_rle(avctx, p, &gbc);
while (left > 0) { if (ret < 0)
code = bytestream2_get_byte(&gbc); return ret;
if (code & 0x80 ) { /* run */ *got_frame = 1;
pix = bytestream2_get_byte(&gbc); break;
memset(out, pix, 257 - code); default:
out += 257 - code; av_log(avctx, AV_LOG_TRACE, "Unknown 0x%04X opcode\n", opcode);
left -= 2; break;
} else { /* copy */
bytestream2_get_buffer(&gbc, out, code + 1);
out += code + 1;
left -= 2 + code;
} }
/* exit the loop when a known pixel block has been found */
if (*got_frame) {
int eop, trail;
/* re-align to a word */
bytestream2_skip(&gbc, bytestream2_get_bytes_left(&gbc) % 2);
eop = bytestream2_get_be16(&gbc);
trail = bytestream2_get_bytes_left(&gbc);
if (eop != EOP)
av_log(avctx, AV_LOG_WARNING,
"Missing end of picture opcode (found 0x%04X)\n", eop);
if (trail)
av_log(avctx, AV_LOG_WARNING, "Got %d trailing bytes\n", trail);
break;
} }
outdata += p->linesize[0];
} }
*got_frame = 1; if (*got_frame) {
p->pict_type = AV_PICTURE_TYPE_I;
p->key_frame = 1;
return avpkt->size; return avpkt->size;
} } else {
av_log(avctx, AV_LOG_ERROR, "Frame contained no usable data\n");
static av_cold int decode_init(AVCodecContext *avctx)
{
avctx->pix_fmt= AV_PIX_FMT_PAL8;
return 0; return AVERROR_INVALIDDATA;
}
} }
AVCodec ff_qdraw_decoder = { AVCodec ff_qdraw_decoder = {
...@@ -137,7 +229,6 @@ AVCodec ff_qdraw_decoder = { ...@@ -137,7 +229,6 @@ AVCodec ff_qdraw_decoder = {
.long_name = NULL_IF_CONFIG_SMALL("Apple QuickDraw"), .long_name = NULL_IF_CONFIG_SMALL("Apple QuickDraw"),
.type = AVMEDIA_TYPE_VIDEO, .type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_QDRAW, .id = AV_CODEC_ID_QDRAW,
.init = decode_init,
.decode = decode_frame, .decode = decode_frame,
.capabilities = CODEC_CAP_DR1, .capabilities = CODEC_CAP_DR1,
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment