mirror of https://github.com/FFmpeg/FFmpeg.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
16 KiB
516 lines
16 KiB
/* |
|
* QuickDraw (qdrw) codec |
|
* Copyright (c) 2004 Konstantin Shishkov |
|
* Copyright (c) 2015 Vittorio Giovara |
|
* |
|
* This file is part of FFmpeg. |
|
* |
|
* FFmpeg is free software; you can redistribute it and/or |
|
* modify it under the terms of the GNU Lesser General Public |
|
* License as published by the Free Software Foundation; either |
|
* version 2.1 of the License, or (at your option) any later version. |
|
* |
|
* FFmpeg is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* Lesser General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Lesser General Public |
|
* License along with FFmpeg; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
*/ |
|
|
|
/** |
|
* @file |
|
* Apple QuickDraw codec. |
|
* https://developer.apple.com/legacy/library/documentation/mac/QuickDraw/QuickDraw-461.html |
|
*/ |
|
|
|
#include "libavutil/common.h" |
|
#include "libavutil/intreadwrite.h" |
|
#include "avcodec.h" |
|
#include "bytestream.h" |
|
#include "internal.h" |
|
|
|
enum QuickdrawOpcodes { |
|
CLIP = 0x0001, |
|
PACKBITSRECT = 0x0098, |
|
PACKBITSRGN, |
|
DIRECTBITSRECT, |
|
DIRECTBITSRGN, |
|
SHORTCOMMENT = 0x00A0, |
|
LONGCOMMENT, |
|
|
|
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] = (0xFFU << 24) | (r << 16) | (g << 8) | b; |
|
} |
|
return 0; |
|
} |
|
|
|
static int decode_rle_bpp2(AVCodecContext *avctx, AVFrame *p, GetByteContext *gbc) |
|
{ |
|
int offset = avctx->width; |
|
uint8_t *outdata = p->data[0]; |
|
int i, j; |
|
|
|
for (i = 0; i < avctx->height; i++) { |
|
int size, left, code, pix; |
|
uint8_t *out = outdata; |
|
int pos = 0; |
|
|
|
/* size of packed line */ |
|
if (offset / 4 > 200) |
|
size = left = bytestream2_get_be16(gbc); |
|
else |
|
size = left = bytestream2_get_byte(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); |
|
for (j = 0; j < 257 - code; j++) { |
|
if (pos < offset) |
|
out[pos++] = (pix & 0xC0) >> 6; |
|
if (pos < offset) |
|
out[pos++] = (pix & 0x30) >> 4; |
|
if (pos < offset) |
|
out[pos++] = (pix & 0x0C) >> 2; |
|
if (pos < offset) |
|
out[pos++] = (pix & 0x03); |
|
} |
|
left -= 2; |
|
} else { /* copy */ |
|
for (j = 0; j < code + 1; j++) { |
|
pix = bytestream2_get_byte(gbc); |
|
if (pos < offset) |
|
out[pos++] = (pix & 0xC0) >> 6; |
|
if (pos < offset) |
|
out[pos++] = (pix & 0x30) >> 4; |
|
if (pos < offset) |
|
out[pos++] = (pix & 0x0C) >> 2; |
|
if (pos < offset) |
|
out[pos++] = (pix & 0x03); |
|
} |
|
left -= 1 + (code + 1); |
|
} |
|
} |
|
outdata += p->linesize[0]; |
|
} |
|
return 0; |
|
} |
|
|
|
static int decode_rle_bpp4(AVCodecContext *avctx, AVFrame *p, GetByteContext *gbc) |
|
{ |
|
int offset = avctx->width; |
|
uint8_t *outdata = p->data[0]; |
|
int i, j; |
|
|
|
for (i = 0; i < avctx->height; i++) { |
|
int size, left, code, pix; |
|
uint8_t *out = outdata; |
|
int pos = 0; |
|
|
|
/* 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); |
|
for (j = 0; j < 257 - code; j++) { |
|
if (pos < offset) |
|
out[pos++] = (pix & 0xF0) >> 4; |
|
if (pos < offset) |
|
out[pos++] = pix & 0xF; |
|
} |
|
left -= 2; |
|
} else { /* copy */ |
|
for (j = 0; j < code + 1; j++) { |
|
pix = bytestream2_get_byte(gbc); |
|
if (pos < offset) |
|
out[pos++] = (pix & 0xF0) >> 4; |
|
if (pos < offset) |
|
out[pos++] = pix & 0xF; |
|
} |
|
left -= 1 + (code + 1); |
|
} |
|
} |
|
outdata += p->linesize[0]; |
|
} |
|
return 0; |
|
} |
|
|
|
static int decode_rle16(AVCodecContext *avctx, AVFrame *p, GetByteContext *gbc) |
|
{ |
|
int offset = avctx->width; |
|
uint8_t *outdata = p->data[0]; |
|
int i, j; |
|
|
|
for (i = 0; i < avctx->height; i++) { |
|
int size, left, code, pix; |
|
uint16_t *out = (uint16_t *)outdata; |
|
int pos = 0; |
|
|
|
/* 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_be16(gbc); |
|
for (j = 0; j < 257 - code; j++) { |
|
if (pos < offset) { |
|
out[pos++] = pix; |
|
} |
|
} |
|
left -= 3; |
|
} else { /* copy */ |
|
for (j = 0; j < code + 1; j++) { |
|
if (pos < offset) { |
|
out[pos++] = bytestream2_get_be16(gbc); |
|
} else { |
|
bytestream2_skip(gbc, 2); |
|
} |
|
} |
|
left -= 1 + (code + 1) * 2; |
|
} |
|
} |
|
outdata += p->linesize[0]; |
|
} |
|
return 0; |
|
} |
|
|
|
static int decode_rle(AVCodecContext *avctx, AVFrame *p, GetByteContext *gbc, |
|
int step) |
|
{ |
|
int i, j; |
|
int offset = avctx->width * step; |
|
uint8_t *outdata = p->data[0]; |
|
|
|
for (i = 0; i < avctx->height; i++) { |
|
int size, left, code, pix; |
|
uint8_t *out = outdata; |
|
int pos = 0; |
|
|
|
/* 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); |
|
for (j = 0; j < 257 - code; j++) { |
|
if (pos < offset) |
|
out[pos] = pix; |
|
pos += step; |
|
if (pos >= offset && step > 1) { |
|
pos -= offset; |
|
pos++; |
|
} |
|
} |
|
left -= 2; |
|
} else { /* copy */ |
|
for (j = 0; j < code + 1; j++) { |
|
pix = bytestream2_get_byte(gbc); |
|
if (pos < offset) |
|
out[pos] = pix; |
|
pos += step; |
|
if (pos >= offset && step > 1) { |
|
pos -= offset; |
|
pos++; |
|
} |
|
} |
|
left -= 2 + code; |
|
} |
|
} |
|
outdata += p->linesize[0]; |
|
} |
|
return 0; |
|
} |
|
|
|
static int check_header(const char *buf, int buf_size) |
|
{ |
|
unsigned w, h, v0, v1; |
|
|
|
if (buf_size < 40) |
|
return 0; |
|
|
|
w = AV_RB16(buf+6); |
|
h = AV_RB16(buf+8); |
|
v0 = AV_RB16(buf+10); |
|
v1 = AV_RB16(buf+12); |
|
|
|
if (!w || !h) |
|
return 0; |
|
|
|
if (v0 == 0x1101) |
|
return 1; |
|
if (v0 == 0x0011 && v1 == 0x02FF) |
|
return 2; |
|
return 0; |
|
} |
|
|
|
|
|
static int decode_frame(AVCodecContext *avctx, |
|
void *data, int *got_frame, |
|
AVPacket *avpkt) |
|
{ |
|
AVFrame * const p = data; |
|
GetByteContext gbc; |
|
int colors; |
|
int w, h, ret; |
|
int ver; |
|
|
|
bytestream2_init(&gbc, avpkt->data, avpkt->size); |
|
if ( bytestream2_get_bytes_left(&gbc) >= 552 |
|
&& check_header(gbc.buffer + 512, bytestream2_get_bytes_left(&gbc) - 512) |
|
) |
|
bytestream2_skip(&gbc, 512); |
|
|
|
ver = check_header(gbc.buffer, bytestream2_get_bytes_left(&gbc)); |
|
|
|
/* 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; |
|
|
|
/* version 1 is identified by 0x1101 |
|
* it uses byte-aligned opcodes rather than word-aligned */ |
|
if (ver == 1) { |
|
avpriv_request_sample(avctx, "QuickDraw version 1"); |
|
return AVERROR_PATCHWELCOME; |
|
} else if (ver != 2) { |
|
avpriv_request_sample(avctx, "QuickDraw version unknown (%X)", bytestream2_get_be32(&gbc)); |
|
return AVERROR_PATCHWELCOME; |
|
} |
|
|
|
bytestream2_skip(&gbc, 4+26); |
|
|
|
while (bytestream2_get_bytes_left(&gbc) >= 4) { |
|
int bppcnt, bpp; |
|
int rowbytes, pack_type; |
|
int opcode = bytestream2_get_be16(&gbc); |
|
|
|
switch(opcode) { |
|
case CLIP: |
|
bytestream2_skip(&gbc, 10); |
|
break; |
|
case PACKBITSRECT: |
|
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 if (bppcnt == 1 && (bpp == 4 || bpp == 2)) { |
|
avctx->pix_fmt = AV_PIX_FMT_PAL8; |
|
} else if (bppcnt == 3 && bpp == 5) { |
|
avctx->pix_fmt = AV_PIX_FMT_RGB555; |
|
} else { |
|
av_log(avctx, AV_LOG_ERROR, |
|
"Invalid pixel format (bppcnt %d bpp %d) in Packbit\n", |
|
bppcnt, bpp); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
/* jump to palette */ |
|
bytestream2_skip(&gbc, 18); |
|
colors = bytestream2_get_be16(&gbc); |
|
|
|
if (colors < 0 || colors > 256) { |
|
av_log(avctx, AV_LOG_ERROR, |
|
"Error color count - %i(0x%X)\n", colors, colors); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if (bytestream2_get_bytes_left(&gbc) < (colors + 1) * 8) { |
|
av_log(avctx, AV_LOG_ERROR, "Palette is too small %d\n", |
|
bytestream2_get_bytes_left(&gbc)); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if ((ret = ff_get_buffer(avctx, p, 0)) < 0) |
|
return ret; |
|
|
|
parse_palette(avctx, &gbc, (uint32_t *)p->data[1], colors); |
|
p->palette_has_changed = 1; |
|
|
|
/* jump to image data */ |
|
bytestream2_skip(&gbc, 18); |
|
|
|
if (opcode == PACKBITSRGN) { |
|
bytestream2_skip(&gbc, 2 + 8); /* size + rect */ |
|
avpriv_report_missing_feature(avctx, "Packbit mask region"); |
|
} |
|
|
|
if (avctx->pix_fmt == AV_PIX_FMT_RGB555) |
|
ret = decode_rle16(avctx, p, &gbc); |
|
else if (bpp == 2) |
|
ret = decode_rle_bpp2(avctx, p, &gbc); |
|
else if (bpp == 4) |
|
ret = decode_rle_bpp4(avctx, p, &gbc); |
|
else |
|
ret = decode_rle(avctx, p, &gbc, bppcnt); |
|
if (ret < 0) |
|
return ret; |
|
*got_frame = 1; |
|
break; |
|
case DIRECTBITSRECT: |
|
case DIRECTBITSRGN: |
|
av_log(avctx, AV_LOG_DEBUG, "Parsing Directbit opcode\n"); |
|
|
|
bytestream2_skip(&gbc, 4); |
|
rowbytes = bytestream2_get_be16(&gbc) & 0x3FFF; |
|
if (rowbytes <= 250) { |
|
avpriv_report_missing_feature(avctx, "Short rowbytes"); |
|
return AVERROR_PATCHWELCOME; |
|
} |
|
|
|
bytestream2_skip(&gbc, 4); |
|
h = bytestream2_get_be16(&gbc); |
|
w = bytestream2_get_be16(&gbc); |
|
bytestream2_skip(&gbc, 2); |
|
|
|
ret = ff_set_dimensions(avctx, w, h); |
|
if (ret < 0) |
|
return ret; |
|
|
|
pack_type = bytestream2_get_be16(&gbc); |
|
|
|
bytestream2_skip(&gbc, 16); |
|
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 == 3 && bpp == 8) { |
|
avctx->pix_fmt = AV_PIX_FMT_RGB24; |
|
} else if (bppcnt == 3 && bpp == 5) { |
|
avctx->pix_fmt = AV_PIX_FMT_RGB555; |
|
} else if (bppcnt == 4 && bpp == 8) { |
|
avctx->pix_fmt = AV_PIX_FMT_ARGB; |
|
} else { |
|
av_log(avctx, AV_LOG_ERROR, |
|
"Invalid pixel format (bppcnt %d bpp %d) in Directbit\n", |
|
bppcnt, bpp); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
/* set packing when default is selected */ |
|
if (pack_type == 0) |
|
pack_type = bppcnt; |
|
|
|
if (pack_type != 3 && pack_type != 4) { |
|
avpriv_request_sample(avctx, "Pack type %d", pack_type); |
|
return AVERROR_PATCHWELCOME; |
|
} |
|
if ((ret = ff_get_buffer(avctx, p, 0)) < 0) |
|
return ret; |
|
|
|
/* jump to data */ |
|
bytestream2_skip(&gbc, 30); |
|
|
|
if (opcode == DIRECTBITSRGN) { |
|
bytestream2_skip(&gbc, 2 + 8); /* size + rect */ |
|
avpriv_report_missing_feature(avctx, "DirectBit mask region"); |
|
} |
|
|
|
if (avctx->pix_fmt == AV_PIX_FMT_RGB555) |
|
ret = decode_rle16(avctx, p, &gbc); |
|
else |
|
ret = decode_rle(avctx, p, &gbc, bppcnt); |
|
if (ret < 0) |
|
return ret; |
|
*got_frame = 1; |
|
break; |
|
case LONGCOMMENT: |
|
bytestream2_get_be16(&gbc); |
|
bytestream2_skip(&gbc, bytestream2_get_be16(&gbc)); |
|
break; |
|
default: |
|
av_log(avctx, AV_LOG_TRACE, "Unknown 0x%04X opcode\n", opcode); |
|
break; |
|
} |
|
/* 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; |
|
} |
|
} |
|
|
|
if (*got_frame) { |
|
p->pict_type = AV_PICTURE_TYPE_I; |
|
p->key_frame = 1; |
|
|
|
return avpkt->size; |
|
} else { |
|
av_log(avctx, AV_LOG_ERROR, "Frame contained no usable data\n"); |
|
|
|
return AVERROR_INVALIDDATA; |
|
} |
|
} |
|
|
|
AVCodec ff_qdraw_decoder = { |
|
.name = "qdraw", |
|
.long_name = NULL_IF_CONFIG_SMALL("Apple QuickDraw"), |
|
.type = AVMEDIA_TYPE_VIDEO, |
|
.id = AV_CODEC_ID_QDRAW, |
|
.decode = decode_frame, |
|
.capabilities = AV_CODEC_CAP_DR1, |
|
};
|
|
|