525 lines
17 KiB
C
Executable File
525 lines
17 KiB
C
Executable File
/*
|
|
* 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 pixmap)
|
|
{
|
|
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 && !pixmap) {
|
|
av_log(avctx, AV_LOG_WARNING,
|
|
"Palette index out of range: %u\n", idx);
|
|
bytestream2_skip(gbc, 6);
|
|
continue;
|
|
}
|
|
if (avctx->pix_fmt != AV_PIX_FMT_PAL8)
|
|
return AVERROR_INVALIDDATA;
|
|
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[pixmap ? i : 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 flags;
|
|
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");
|
|
|
|
flags = bytestream2_get_be16(&gbc) & 0xC000;
|
|
bytestream2_skip(&gbc, 28);
|
|
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;
|
|
|
|
ret = parse_palette(avctx, &gbc, (uint32_t *)p->data[1], colors, flags & 0x8000);
|
|
if (ret < 0)
|
|
return ret;
|
|
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 || bppcnt == 2 && bpp == 8) {
|
|
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 (bytestream2_get_bytes_left(&gbc) < 30)
|
|
return AVERROR_INVALIDDATA;
|
|
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,
|
|
};
|