From cd559bb49a3417ab1ca75df3de0029300e5095df Mon Sep 17 00:00:00 2001 From: Stefano Sabatini Date: Wed, 25 Jul 2012 12:23:31 +0200 Subject: [PATCH] lavc: add xface image decoder and encoder Based on libcompface code by James Ashton , and relicensed to LGPL with the author's consent. --- Changelog | 1 + doc/general.texi | 2 + libavcodec/Makefile | 2 + libavcodec/allcodecs.c | 1 + libavcodec/avcodec.h | 1 + libavcodec/codec_desc.c | 6 + libavcodec/version.h | 2 +- libavcodec/xface.c | 381 ++++++++++++++++++++++++++++++++++++++++ libavcodec/xface.h | 95 ++++++++++ libavcodec/xfacedec.c | 207 ++++++++++++++++++++++ libavcodec/xfaceenc.c | 237 +++++++++++++++++++++++++ libavformat/img2.c | 1 + libavformat/img2enc.c | 2 +- libavformat/version.h | 2 +- 14 files changed, 937 insertions(+), 3 deletions(-) create mode 100644 libavcodec/xface.c create mode 100644 libavcodec/xface.h create mode 100644 libavcodec/xfacedec.c create mode 100644 libavcodec/xfaceenc.c diff --git a/Changelog b/Changelog index 2fd22393a3..0864c3e335 100644 --- a/Changelog +++ b/Changelog @@ -11,6 +11,7 @@ version next: - TAK demuxer, decoder and parser - DTS-HD demuxer - remove -same_quant, it hasn't worked for years +- X-Face image encoder and decoder version 1.0: diff --git a/doc/general.texi b/doc/general.texi index 7f50c11cc5..b4176d5daa 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -441,6 +441,8 @@ following image formats are supported: @tab Targa (.TGA) image format @item XBM @tab X @tab X @tab X BitMap image format +@item XFace @tab X @tab X + @tab X-Face image format @item XWD @tab X @tab X @tab X Window Dump image format @end multitable diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 47a01db55a..e865e9be9f 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -482,6 +482,8 @@ OBJS-$(CONFIG_XAN_WC4_DECODER) += xxan.o OBJS-$(CONFIG_XBIN_DECODER) += bintext.o cga_data.o OBJS-$(CONFIG_XBM_DECODER) += xbmdec.o OBJS-$(CONFIG_XBM_ENCODER) += xbmenc.o +OBJS-$(CONFIG_XFACE_DECODER) += xfacedec.o xface.o +OBJS-$(CONFIG_XFACE_ENCODER) += xfaceenc.o xface.o OBJS-$(CONFIG_XL_DECODER) += xl.o OBJS-$(CONFIG_XSUB_DECODER) += xsubdec.o OBJS-$(CONFIG_XSUB_ENCODER) += xsubenc.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index ae88fabc0c..d7fb1d105b 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -265,6 +265,7 @@ void avcodec_register_all(void) REGISTER_DECODER (XAN_WC3, xan_wc3); REGISTER_DECODER (XAN_WC4, xan_wc4); REGISTER_ENCDEC (XBM, xbm); + REGISTER_ENCDEC (XFACE, xface); REGISTER_DECODER (XL, xl); REGISTER_ENCDEC (XWD, xwd); REGISTER_ENCDEC (Y41P, y41p); diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 27e249f5b9..84a1d0ba67 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -283,6 +283,7 @@ enum AVCodecID { AV_CODEC_ID_PAF_VIDEO = MKBETAG('P','A','F','V'), AV_CODEC_ID_AVRN = MKBETAG('A','V','R','n'), AV_CODEC_ID_CPIA = MKBETAG('C','P','I','A'), + AV_CODEC_ID_XFACE = MKBETAG('X','F','A','C'), /* various PCM "codecs" */ AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index d411981606..fcefea4ae7 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -1315,6 +1315,12 @@ static const AVCodecDescriptor codec_descriptors[] = { .name = "cpia", .long_name = NULL_IF_CONFIG_SMALL("CPiA video format"), }, + { + .id = AV_CODEC_ID_XFACE, + .type = AVMEDIA_TYPE_VIDEO, + .name = "xface", + .long_name = NULL_IF_CONFIG_SMALL("X-face image"), + }, /* various PCM "codecs" */ { diff --git a/libavcodec/version.h b/libavcodec/version.h index 2e407ce585..17e7468239 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,7 +29,7 @@ #include "libavutil/avutil.h" #define LIBAVCODEC_VERSION_MAJOR 54 -#define LIBAVCODEC_VERSION_MINOR 65 +#define LIBAVCODEC_VERSION_MINOR 66 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ diff --git a/libavcodec/xface.c b/libavcodec/xface.c new file mode 100644 index 0000000000..0ebf2f2ec0 --- /dev/null +++ b/libavcodec/xface.c @@ -0,0 +1,381 @@ +/* + * Copyright (c) 1990 James Ashton - Sydney University + * Copyright (c) 2012 Stefano Sabatini + * + * 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 + * X-Face common data and utilities definition. + */ + +#include "xface.h" + +void ff_big_add(BigInt *b, uint8_t a) +{ + int i; + uint8_t *w; + uint16_t c; + + a &= XFACE_WORDMASK; + if (a == 0) + return; + w = b->words; + c = a; + for (i = 0; i < b->nb_words && c; i++) { + c += *w; + *w++ = c & XFACE_WORDMASK; + c >>= XFACE_BITSPERWORD; + } + if (i == b->nb_words && c) { + b->nb_words++; + *w = c & XFACE_WORDMASK; + } +} + +void ff_big_div(BigInt *b, uint8_t a, uint8_t *r) +{ + int i; + uint8_t *w; + uint16_t c, d; + + a &= XFACE_WORDMASK; + if (a == 1 || b->nb_words == 0) { + *r = 0; + return; + } + + /* treat this as a == WORDCARRY and just shift everything right a WORD */ + if (a == 0) { + i = --b->nb_words; + w = b->words; + *r = *w; + while (i--) { + *w = *(w + 1); + w++; + } + *w = 0; + return; + } + i = b->nb_words; + w = b->words + i; + c = 0; + while (i--) { + c <<= XFACE_BITSPERWORD; + c += *--w; + d = c / (uint16_t)a; + c = c % (uint16_t)a; + *w = d & XFACE_WORDMASK; + } + *r = c; + if (b->words[b->nb_words - 1] == 0) + b->nb_words--; +} + +void ff_big_mul(BigInt *b, uint8_t a) +{ + int i; + uint8_t *w; + uint16_t c; + + a &= XFACE_WORDMASK; + if (a == 1 || b->nb_words == 0) + return; + if (a == 0) { + /* treat this as a == WORDCARRY and just shift everything left a WORD */ + i = b->nb_words++; + w = b->words + i; + while (i--) { + *w = *(w - 1); + w--; + } + *w = 0; + return; + } + i = b->nb_words; + w = b->words; + c = 0; + while (i--) { + c += (uint16_t)*w * (uint16_t)a; + *(w++) = c & XFACE_WORDMASK; + c >>= XFACE_BITSPERWORD; + } + if (c) { + b->nb_words++; + *w = c & XFACE_WORDMASK; + } +} + +const ProbRange ff_xface_probranges_per_level[4][3] = { + // black grey white + { { 1, 255}, {251, 0}, { 4, 251} }, /* Top of tree almost always grey */ + { { 1, 255}, {200, 0}, { 55, 200} }, + { { 33, 223}, {159, 0}, { 64, 159} }, + { {131, 0}, { 0, 0}, {125, 131} }, /* Grey disallowed at bottom */ +}; + +const ProbRange ff_xface_probranges_2x2[16] = { + { 0, 0}, {38, 0}, {38, 38}, {13, 152}, + {38, 76}, {13, 165}, {13, 178}, { 6, 230}, + {38, 114}, {13, 191}, {13, 204}, { 6, 236}, + {13, 217}, { 6, 242}, { 5, 248}, { 3, 253}, +}; + +/* + * The "guess the next pixel" tables follow. Normally there are 12 + * neighbour pixels used to give 1<<12 cases as we get closer to the + * upper left corner lesser numbers of neighbours are available. + * + * Each byte in the tables represents 8 boolean values starting from + * the most significant bit. + */ + +static const uint8_t g_00[] = { + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xe3, 0xdf, 0x05, 0x17, + 0x05, 0x0f, 0x00, 0x1b, 0x0f, 0xdf, 0x00, 0x04, 0x00, 0x00, + 0x0d, 0x0f, 0x03, 0x7f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1d, + 0x45, 0x2f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x0a, 0xff, 0xff, + 0x00, 0x04, 0x00, 0x05, 0x01, 0x3f, 0xcf, 0xff, 0x10, 0x01, + 0x80, 0xc9, 0x0f, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x1b, 0x1f, 0xff, 0xff, 0x4f, 0x54, 0x07, 0x1f, 0x57, 0x47, + 0xd7, 0x3d, 0xff, 0xff, 0x5f, 0x1f, 0x7f, 0xff, 0x7f, 0x7f, + 0x05, 0x0f, 0x01, 0x0f, 0x0f, 0x5f, 0x9b, 0xdf, 0x7f, 0xff, + 0x5f, 0x1d, 0x5f, 0xff, 0x0f, 0x1f, 0x0f, 0x5f, 0x03, 0x1f, + 0x4f, 0x5f, 0xf7, 0x7f, 0x7f, 0xff, 0x0d, 0x0f, 0xfb, 0xff, + 0xf7, 0xbf, 0x0f, 0x4f, 0xd7, 0x3f, 0x4f, 0x7f, 0xff, 0xff, + 0x67, 0xbf, 0x56, 0x25, 0x1f, 0x7f, 0x9f, 0xff, 0x00, 0x00, + 0x00, 0x05, 0x5f, 0x7f, 0x01, 0xdf, 0x14, 0x00, 0x05, 0x0f, + 0x07, 0xa2, 0x09, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x5f, + 0x18, 0xd7, 0x94, 0x71, 0x00, 0x05, 0x1f, 0xb7, 0x0c, 0x07, + 0x0f, 0x0f, 0x00, 0x0f, 0x0f, 0x1f, 0x84, 0x8f, 0x05, 0x15, + 0x05, 0x0f, 0x4f, 0xff, 0x87, 0xdf, 0x05, 0x01, 0x10, 0x00, + 0x0f, 0x0f, 0x00, 0x08, 0x05, 0x04, 0x04, 0x01, 0x4f, 0xff, + 0x9f, 0x8f, 0x4a, 0x40, 0x5f, 0x5f, 0xff, 0xfe, 0xdf, 0xff, + 0x7f, 0xf7, 0xff, 0x7f, 0xff, 0xff, 0x7b, 0xff, 0x0f, 0xfd, + 0xd7, 0x5f, 0x4f, 0x7f, 0x7f, 0xdf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x77, 0xdf, 0x7f, 0x4f, 0xef, 0xff, 0xff, 0x77, 0xff, + 0xff, 0xff, 0x6f, 0xff, 0x0f, 0x4f, 0xff, 0xff, 0x9d, 0xff, + 0x0f, 0xef, 0xff, 0xdf, 0x6f, 0xff, 0xff, 0xff, 0x4f, 0xff, + 0xcd, 0x0f, 0x4f, 0xff, 0xff, 0xdf, 0x00, 0x00, 0x00, 0x0b, + 0x05, 0x02, 0x02, 0x0f, 0x04, 0x00, 0x00, 0x0c, 0x01, 0x06, + 0x00, 0x0f, 0x20, 0x03, 0x00, 0x00, 0x05, 0x0f, 0x40, 0x08, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x0c, 0x0f, 0x01, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x14, 0x01, 0x05, + 0x01, 0x15, 0xaf, 0x0f, 0x00, 0x01, 0x10, 0x00, 0x08, 0x00, + 0x46, 0x0c, 0x20, 0x00, 0x88, 0x00, 0x0f, 0x15, 0xff, 0xdf, + 0x02, 0x00, 0x00, 0x0f, 0x7f, 0x5f, 0xdb, 0xff, 0x4f, 0x3e, + 0x05, 0x0f, 0x7f, 0xf7, 0x95, 0x4f, 0x0d, 0x0f, 0x01, 0x0f, + 0x4f, 0x5f, 0x9f, 0xdf, 0x25, 0x0e, 0x0d, 0x0d, 0x4f, 0x7f, + 0x8f, 0x0f, 0x0f, 0xfa, 0x04, 0x4f, 0x4f, 0xff, 0xf7, 0x77, + 0x47, 0xed, 0x05, 0x0f, 0xff, 0xff, 0xdf, 0xff, 0x4f, 0x6f, + 0xd8, 0x5f, 0x0f, 0x7f, 0xdf, 0x5f, 0x07, 0x0f, 0x94, 0x0d, + 0x1f, 0xff, 0xff, 0xff, 0x00, 0x02, 0x00, 0x03, 0x46, 0x57, + 0x01, 0x0d, 0x01, 0x08, 0x01, 0x0f, 0x47, 0x6c, 0x0d, 0x0f, + 0x02, 0x00, 0x00, 0x00, 0x0b, 0x4f, 0x00, 0x08, 0x05, 0x00, + 0x95, 0x01, 0x0f, 0x7f, 0x0c, 0x0f, 0x01, 0x0e, 0x00, 0x00, + 0x0f, 0x41, 0x00, 0x00, 0x04, 0x24, 0x0d, 0x0f, 0x0f, 0x7f, + 0xcf, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x00, 0x00, + 0x06, 0x26, 0xcf, 0x05, 0xcf, 0x7f, 0xdf, 0xdf, 0x00, 0x00, + 0x17, 0x5f, 0xff, 0xfd, 0xff, 0xff, 0x46, 0x09, 0x4f, 0x5f, + 0x7f, 0xfd, 0xdf, 0xff, 0x0a, 0x88, 0xa7, 0x7f, 0x7f, 0xff, + 0xff, 0xff, 0x0f, 0x04, 0xdf, 0x7f, 0x4f, 0xff, 0x9f, 0xff, + 0x0e, 0xe6, 0xdf, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x0f, 0xec, + 0x8f, 0x4f, 0x7f, 0xff, 0xdf, 0xff, 0x0f, 0xcf, 0xdf, 0xff, + 0x6f, 0x7f, 0xff, 0xff, 0x03, 0x0c, 0x9d, 0x0f, 0x7f, 0xff, + 0xff, 0xff, +}; + +static const uint8_t g_01[] = { + 0x37, 0x73, 0x00, 0x19, 0x57, 0x7f, 0xf5, 0xfb, 0x70, 0x33, + 0xf0, 0xf9, 0x7f, 0xff, 0xff, 0xff, +}; + +static const uint8_t g_02[] = { + 0x50, +}; + +static const uint8_t g_10[] = { + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0xf3, 0x5f, 0x84, 0x04, + 0x17, 0x9f, 0x04, 0x23, 0x05, 0xff, 0x00, 0x00, 0x00, 0x02, + 0x03, 0x03, 0x33, 0xd7, 0x05, 0x03, 0x5f, 0x3f, 0x17, 0x33, + 0xff, 0xff, 0x00, 0x80, 0x02, 0x04, 0x12, 0x00, 0x11, 0x57, + 0x05, 0x25, 0x05, 0x03, 0x35, 0xbf, 0x9f, 0xff, 0x07, 0x6f, + 0x20, 0x40, 0x17, 0x06, 0xfa, 0xe8, 0x01, 0x07, 0x1f, 0x9f, + 0x1f, 0xff, 0xff, 0xff, +}; + +static const uint8_t g_20[] = { + 0x04, 0x00, 0x01, 0x01, 0x43, 0x2e, 0xff, 0x3f, +}; + +static const uint8_t g_30[] = { + 0x11, 0x11, 0x11, 0x11, 0x51, 0x11, 0x13, 0x11, 0x11, 0x11, + 0x13, 0x11, 0x11, 0x11, 0x33, 0x11, 0x13, 0x11, 0x13, 0x13, + 0x13, 0x13, 0x31, 0x31, 0x11, 0x01, 0x11, 0x11, 0x71, 0x11, + 0x11, 0x75, +}; + +static const uint8_t g_40[] = { + 0x00, 0x0f, 0x00, 0x09, 0x00, 0x0d, 0x00, 0x0d, 0x00, 0x0f, + 0x00, 0x4e, 0xe4, 0x0d, 0x10, 0x0f, 0x00, 0x0f, 0x44, 0x4f, + 0x00, 0x1e, 0x0f, 0x0f, 0xae, 0xaf, 0x45, 0x7f, 0xef, 0xff, + 0x0f, 0xff, 0x00, 0x09, 0x01, 0x11, 0x00, 0x01, 0x1c, 0xdd, + 0x00, 0x15, 0x00, 0xff, 0x00, 0x10, 0x00, 0xfd, 0x00, 0x0f, + 0x4f, 0x5f, 0x3d, 0xff, 0xff, 0xff, 0x4f, 0xff, 0x1c, 0xff, + 0xdf, 0xff, 0x8f, 0xff, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x15, + 0x01, 0x07, 0x00, 0x01, 0x02, 0x1f, 0x01, 0x11, 0x05, 0x7f, + 0x00, 0x1f, 0x41, 0x57, 0x1f, 0xff, 0x05, 0x77, 0x0d, 0x5f, + 0x4d, 0xff, 0x4f, 0xff, 0x0f, 0xff, 0x00, 0x00, 0x02, 0x05, + 0x00, 0x11, 0x05, 0x7d, 0x10, 0x15, 0x2f, 0xff, 0x40, 0x50, + 0x0d, 0xfd, 0x04, 0x0f, 0x07, 0x1f, 0x07, 0x7f, 0x0f, 0xbf, + 0x0d, 0x7f, 0x0f, 0xff, 0x4d, 0x7d, 0x0f, 0xff, +}; + +static const uint8_t g_11[] = { + 0x01, 0x13, 0x03, 0x7f, +}; + +static const uint8_t g_21[] = { + 0x17, +}; + +static const uint8_t g_31[] = { + 0x55, 0x57, 0x57, 0x7f, +}; + +static const uint8_t g_41[] = { + 0x01, 0x01, 0x01, 0x1f, 0x03, 0x1f, 0x3f, 0xff, +}; + +static const uint8_t g_12[] = { + 0x40, +}; + +static const uint8_t g_22[] = { + 0x00, +}; + +static const uint8_t g_32[] = { + 0x10, +}; + +static const uint8_t g_42[] = { + 0x10, +}; + +void ff_xface_generate_face(uint8_t *dst, uint8_t * const src) +{ + int h, i, j, k, l, m; + + for (j = 0; j < XFACE_HEIGHT; j++) { + for (i = 0; i < XFACE_WIDTH; i++) { + h = i + j * XFACE_WIDTH; + k = 0; + + /* + Compute k, encoding the bits *before* the current one, contained in the + image buffer. That is, given the grid: + + l i + | | + v v + +--+--+--+--+--+ + m -> | 1| 2| 3| 4| 5| + +--+--+--+--+--+ + | 6| 7| 8| 9|10| + +--+--+--+--+--+ + j -> |11|12| *| | | + +--+--+--+--+--+ + + the value k for the pixel marked as "*" will contain the bit encoding of + the values in the matrix marked from "1" to "12". In case the pixel is + near the border of the grid, the number of values contained within the + grid will be lesser than 12. + */ + + for (l = i - 2; l <= i + 2; l++) { + for (m = j - 2; m <= j; m++) { + if (l >= i && m == j) + continue; + if (l > 0 && l <= XFACE_WIDTH && m > 0) + k = 2*k + src[l + m * XFACE_WIDTH]; + } + } + + /* + Use the guess for the given position and the computed value of k. + + The following table shows the number of digits in k, depending on + the position of the pixel, and shows the corresponding guess table + to use: + + i=1 i=2 i=3 i=w-1 i=w + +----+----+----+ ... +----+----+ + j=1 | 0 | 1 | 2 | | 2 | 2 | + |g22 |g12 |g02 | |g42 |g32 | + +----+----+----+ ... +----+----+ + j=2 | 3 | 5 | 7 | | 6 | 5 | + |g21 |g11 |g01 | |g41 |g31 | + +----+----+----+ ... +----+----+ + j=3 | 5 | 9 | 12 | | 10 | 8 | + |g20 |g10 |g00 | |g40 |g30 | + +----+----+----+ ... +----+----+ + */ + +#define GEN(table) dst[h] ^= (table[k>>3]>>(7-(k&7)))&1 + + switch (i) { + case 1: + switch (j) { + case 1: GEN(g_22); break; + case 2: GEN(g_21); break; + default: GEN(g_20); break; + } + break; + case 2: + switch (j) { + case 1: GEN(g_12); break; + case 2: GEN(g_11); break; + default: GEN(g_10); break; + } + break; + case XFACE_WIDTH - 1: + switch (j) { + case 1: GEN(g_42); break; + case 2: GEN(g_41); break; + default: GEN(g_40); break; + } + break; + case XFACE_WIDTH: + switch (j) { + case 1: GEN(g_32); break; + case 2: GEN(g_31); break; + default: GEN(g_30); break; + } + break; + default: + switch (j) { + case 1: GEN(g_02); break; + case 2: GEN(g_01); break; + default: GEN(g_00); break; + } + break; + } + } + } +} diff --git a/libavcodec/xface.h b/libavcodec/xface.h new file mode 100644 index 0000000000..cd59ba084a --- /dev/null +++ b/libavcodec/xface.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 1990 James Ashton - Sydney University + * Copyright (c) 2012 Stefano Sabatini + * + * 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 + * X-Face common definitions. + */ + +#include + +/* define the face size - 48x48x1 */ +#define XFACE_WIDTH 48 +#define XFACE_HEIGHT 48 +#define XFACE_PIXELS (XFACE_WIDTH * XFACE_HEIGHT) + +/* compressed output uses the full range of printable characters. + * In ASCII these are in a contiguous block so we just need to know + * the first and last. The total number of printables is needed too. */ +#define XFACE_FIRST_PRINT '!' +#define XFACE_LAST_PRINT '~' +#define XFACE_PRINTS (XFACE_LAST_PRINT - XFACE_FIRST_PRINT + 1) + +/* + * Image is encoded as a big integer, using characters from '~' to + * '!', for a total of 92 symbols. In order to express 48x48=2304 + * bits, we need a total of 354 digits, as given by: + * ceil(lg_92(2^2304)) = 354 + */ +#define XFACE_MAX_DIGITS 354 + +#define XFACE_BITSPERWORD 8 +#define XFACE_WORDCARRY (1 << XFACE_BITSPERWORD) +#define XFACE_WORDMASK (XFACE_WORDCARRY - 1) + +#define XFACE_MAX_WORDS ((XFACE_PIXELS * 2 + XFACE_BITSPERWORD - 1) / XFACE_BITSPERWORD) + +/* Portable, very large unsigned integer arithmetic is needed. + * Implementation uses arrays of WORDs. */ +typedef struct { + int nb_words; + uint8_t words[XFACE_MAX_WORDS]; +} BigInt; + +/** + * Add a to b storing the result in b. + */ +void ff_big_add(BigInt *b, uint8_t a); + +/** + * Divide b by a storing the result in b and the remainder in the word + * pointed to by r. + */ +void ff_big_div(BigInt *b, uint8_t a, uint8_t *r); + +/** + * Multiply a by b storing the result in b. + */ +void ff_big_mul(BigInt *b, uint8_t a); + +/* Each face is encoded using 9 octrees of 16x16 each. Each level of the + * trees has varying probabilities of being white, grey or black. + * The table below is based on sampling many faces */ +enum XFaceColor { XFACE_COLOR_BLACK = 0, XFACE_COLOR_GREY, XFACE_COLOR_WHITE }; + +/* Data of varying probabilities are encoded by a value in the range 0 - 255. + * The probability of the data determines the range of possible encodings. + * Offset gives the first possible encoding of the range. */ +typedef struct { + int range; + int offset; +} ProbRange; + +extern const ProbRange ff_xface_probranges_per_level[4][3]; + +extern const ProbRange ff_xface_probranges_2x2[16]; + +void ff_xface_generate_face(uint8_t *dst, uint8_t * const src); diff --git a/libavcodec/xfacedec.c b/libavcodec/xfacedec.c new file mode 100644 index 0000000000..7cccfa5c94 --- /dev/null +++ b/libavcodec/xfacedec.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 1990 James Ashton - Sydney University + * Copyright (c) 2012 Stefano Sabatini + * + * 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 + * X-Face decoder, based on libcompface, by James Ashton. + */ + +#include "libavutil/pixdesc.h" +#include "avcodec.h" +#include "bytestream.h" +#include "xface.h" + +static int pop_integer(BigInt *b, const ProbRange *pranges) +{ + uint8_t r; + int i; + + /* extract the last byte into r, and shift right b by 8 bits */ + ff_big_div(b, 0, &r); + + i = 0; + while (r < pranges->offset || r >= pranges->range + pranges->offset) { + pranges++; + i++; + } + ff_big_mul(b, pranges->range); + ff_big_add(b, r - pranges->offset); + return i; +} + +static void pop_greys(BigInt *b, char *bitmap, int w, int h) +{ + if (w > 3) { + w /= 2; + h /= 2; + pop_greys(b, bitmap, w, h); + pop_greys(b, bitmap + w, w, h); + pop_greys(b, bitmap + XFACE_WIDTH * h, w, h); + pop_greys(b, bitmap + XFACE_WIDTH * h + w, w, h); + } else { + w = pop_integer(b, ff_xface_probranges_2x2); + if (w & 1) bitmap[0] = 1; + if (w & 2) bitmap[1] = 1; + if (w & 4) bitmap[XFACE_WIDTH] = 1; + if (w & 8) bitmap[XFACE_WIDTH + 1] = 1; + } +} + +static void decode_block(BigInt *b, char *bitmap, int w, int h, int level) +{ + switch (pop_integer(b, &ff_xface_probranges_per_level[level][0])) { + case XFACE_COLOR_WHITE: + return; + case XFACE_COLOR_BLACK: + pop_greys(b, bitmap, w, h); + return; + default: + w /= 2; + h /= 2; + level++; + decode_block(b, bitmap, w, h, level); + decode_block(b, bitmap + w, w, h, level); + decode_block(b, bitmap + h * XFACE_WIDTH, w, h, level); + decode_block(b, bitmap + w + h * XFACE_WIDTH, w, h, level); + return; + } +} + +typedef struct XFaceContext { + AVFrame frame; + uint8_t bitmap[XFACE_PIXELS]; ///< image used internally for decoding +} XFaceContext; + +static av_cold int xface_decode_init(AVCodecContext *avctx) +{ + XFaceContext *xface = avctx->priv_data; + + avcodec_get_frame_defaults(&xface->frame); + + if (avctx->width || avctx->height) { + if (avctx->width != XFACE_WIDTH || avctx->height != XFACE_HEIGHT) { + av_log(avctx, AV_LOG_ERROR, + "Size value %dx%d not supported, only accepts a size of %dx%d\n", + avctx->width, avctx->height, XFACE_WIDTH, XFACE_HEIGHT); + return AVERROR(EINVAL); + } + } + + avctx->width = XFACE_WIDTH; + avctx->height = XFACE_HEIGHT; + avctx->pix_fmt = AV_PIX_FMT_MONOWHITE; + + return 0; +} + +static av_cold int xface_decode_close(AVCodecContext *avctx) +{ + XFaceContext *xface = avctx->priv_data; + + if (xface->frame.data[0]) + avctx->release_buffer(avctx, &xface->frame); + + return 0; +} + +static int xface_decode_frame(AVCodecContext *avctx, + void *data, int *data_size, + AVPacket *avpkt) +{ + XFaceContext *xface = avctx->priv_data; + int ret, i, j, k; + uint8_t byte; + BigInt b = {0}; + char *buf; + int64_t c; + + if (xface->frame.data[0]) + avctx->release_buffer(avctx, &xface->frame); + xface->frame.data[0] = NULL; + if ((ret = avctx->get_buffer(avctx, &xface->frame)) < 0) + return ret; + xface->frame.reference = 0; + + for (i = 0, k = 0; avpkt->data[i] && i < avpkt->size; i++) { + c = avpkt->data[i]; + + /* ignore invalid digits */ + if (c < XFACE_FIRST_PRINT || c > XFACE_LAST_PRINT) + continue; + + if (++k > XFACE_MAX_DIGITS) { + av_log(avctx, AV_LOG_WARNING, + "Buffer is longer than expected, truncating at byte %d\n", i); + break; + } + ff_big_mul(&b, XFACE_PRINTS); + ff_big_add(&b, c - XFACE_FIRST_PRINT); + } + + /* decode image and put it in bitmap */ + memset(xface->bitmap, 0, XFACE_PIXELS); + buf = xface->bitmap; + decode_block(&b, buf, 16, 16, 0); + decode_block(&b, buf + 16, 16, 16, 0); + decode_block(&b, buf + 32, 16, 16, 0); + decode_block(&b, buf + XFACE_WIDTH * 16, 16, 16, 0); + decode_block(&b, buf + XFACE_WIDTH * 16 + 16, 16, 16, 0); + decode_block(&b, buf + XFACE_WIDTH * 16 + 32, 16, 16, 0); + decode_block(&b, buf + XFACE_WIDTH * 32 , 16, 16, 0); + decode_block(&b, buf + XFACE_WIDTH * 32 + 16, 16, 16, 0); + decode_block(&b, buf + XFACE_WIDTH * 32 + 32, 16, 16, 0); + + ff_xface_generate_face(xface->bitmap, xface->bitmap); + + /* convert image from 1=black 0=white bitmap to MONOWHITE */ + buf = xface->frame.data[0]; + for (i = 0, j = 0, k = 0, byte = 0; i < XFACE_PIXELS; i++) { + byte += xface->bitmap[i]; + if (k == 7) { + buf[j++] = byte; + byte = k = 0; + } else { + k++; + byte <<= 1; + } + if (j == XFACE_WIDTH/8) { + j = 0; + buf += xface->frame.linesize[0]; + } + } + + *data_size = sizeof(AVFrame); + *(AVFrame*)data = xface->frame; + + return avpkt->size; +} + +AVCodec ff_xface_decoder = { + .name = "xface", + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_XFACE, + .priv_data_size = sizeof(XFaceContext), + .init = xface_decode_init, + .close = xface_decode_close, + .decode = xface_decode_frame, + .pix_fmts = (const enum AVPixelFormat[]) { AV_PIX_FMT_MONOWHITE, AV_PIX_FMT_NONE }, + .long_name = NULL_IF_CONFIG_SMALL("X-face image"), +}; diff --git a/libavcodec/xfaceenc.c b/libavcodec/xfaceenc.c new file mode 100644 index 0000000000..db63f59cfb --- /dev/null +++ b/libavcodec/xfaceenc.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 1990 James Ashton - Sydney University + * Copyright (c) 2012 Stefano Sabatini + * + * 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 + * X-Face encoder, based on libcompface, by James Ashton. + */ + +#include "xface.h" +#include "avcodec.h" +#include "internal.h" + +typedef struct XFaceContext { + AVClass *class; + uint8_t bitmap[XFACE_PIXELS]; ///< image used internally for decoding + int max_line_len; ///< max line length for compressed data + int set_header; ///< set X-Face header in the output +} XFaceContext; + +static int all_same(char *bitmap, int w, int h) +{ + char val, *row; + int x; + + val = *bitmap; + while (h--) { + row = bitmap; + x = w; + while (x--) + if (*(row++) != val) + return 0; + bitmap += XFACE_WIDTH; + } + return 1; +} + +static int all_black(char *bitmap, int w, int h) +{ + if (w > 3) { + w /= 2; + h /= 2; + return (all_black(bitmap, w, h) && all_black(bitmap + w, w, h) && + all_black(bitmap + XFACE_WIDTH * h, w, h) && + all_black(bitmap + XFACE_WIDTH * h + w, w, h)); + } else { + /* at least one pixel in the 2x2 grid is non-zero */ + return *bitmap || *(bitmap + 1) || + *(bitmap + XFACE_WIDTH) || *(bitmap + XFACE_WIDTH + 1); + } +} + +static int all_white(char *bitmap, int w, int h) +{ + return *bitmap == 0 && all_same(bitmap, w, h); +} + +typedef struct { + const ProbRange *prob_ranges[XFACE_PIXELS*2]; + int prob_ranges_idx; +} ProbRangesQueue; + +static inline int pq_push(ProbRangesQueue *pq, const ProbRange *p) +{ + if (pq->prob_ranges_idx >= XFACE_PIXELS * 2 - 1) + return -1; + pq->prob_ranges[pq->prob_ranges_idx++] = p; + return 0; +} + +static void push_greys(ProbRangesQueue *pq, char *bitmap, int w, int h) +{ + if (w > 3) { + w /= 2; + h /= 2; + push_greys(pq, bitmap, w, h); + push_greys(pq, bitmap + w, w, h); + push_greys(pq, bitmap + XFACE_WIDTH * h, w, h); + push_greys(pq, bitmap + XFACE_WIDTH * h + w, w, h); + } else { + const ProbRange *p = ff_xface_probranges_2x2 + + *bitmap + + 2 * *(bitmap + 1) + + 4 * *(bitmap + XFACE_WIDTH) + + 8 * *(bitmap + XFACE_WIDTH + 1); + pq_push(pq, p); + } +} + +static void encode_block(char *bitmap, int w, int h, int level, ProbRangesQueue *pq) +{ + if (all_white(bitmap, w, h)) { + pq_push(pq, &ff_xface_probranges_per_level[level][XFACE_COLOR_WHITE]); + } else if (all_black(bitmap, w, h)) { + pq_push(pq, &ff_xface_probranges_per_level[level][XFACE_COLOR_BLACK]); + push_greys(pq, bitmap, w, h); + } else { + pq_push(pq, &ff_xface_probranges_per_level[level][XFACE_COLOR_GREY]); + w /= 2; + h /= 2; + level++; + encode_block(bitmap, w, h, level, pq); + encode_block(bitmap + w, w, h, level, pq); + encode_block(bitmap + h * XFACE_WIDTH, w, h, level, pq); + encode_block(bitmap + w + h * XFACE_WIDTH, w, h, level, pq); + } +} + +static av_cold int xface_encode_init(AVCodecContext *avctx) +{ + avctx->coded_frame = avcodec_alloc_frame(); + if (!avctx->coded_frame) + return AVERROR(ENOMEM); + avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I; + + return 0; +} + +static void push_integer(BigInt *b, const ProbRange *prange) +{ + uint8_t r; + + ff_big_div(b, prange->range, &r); + ff_big_mul(b, 0); + ff_big_add(b, r + prange->offset); +} + +static int xface_encode_frame(AVCodecContext *avctx, AVPacket *pkt, + const AVFrame *frame, int *got_packet) +{ + XFaceContext *xface = avctx->priv_data; + ProbRangesQueue pq = {{ 0 }, 0}; + uint8_t bitmap_copy[XFACE_PIXELS]; + BigInt b = {0}; + int i, j, k, ret = 0; + const uint8_t *buf; + uint8_t *p; + char intbuf[XFACE_MAX_DIGITS]; + + if (avctx->width || avctx->height) { + if (avctx->width != XFACE_WIDTH || avctx->height != XFACE_HEIGHT) { + av_log(avctx, AV_LOG_ERROR, + "Size value %dx%d not supported, only accepts a size of %dx%d\n", + avctx->width, avctx->height, XFACE_WIDTH, XFACE_HEIGHT); + return AVERROR(EINVAL); + } + } + avctx->width = XFACE_WIDTH; + avctx->height = XFACE_HEIGHT; + + /* convert image from MONOWHITE to 1=black 0=white bitmap */ + buf = frame->data[0]; + for (i = 0, j = 0; i < XFACE_PIXELS; ) { + for (k = 0; k < 8; k++) + xface->bitmap[i++] = (buf[j]>>(7-k))&1; + if (++j == XFACE_WIDTH/8) { + buf += frame->linesize[0]; + j = 0; + } + } + + /* create a copy of bitmap */ + memcpy(bitmap_copy, xface->bitmap, XFACE_PIXELS); + ff_xface_generate_face(xface->bitmap, bitmap_copy); + + encode_block(xface->bitmap, 16, 16, 0, &pq); + encode_block(xface->bitmap + 16, 16, 16, 0, &pq); + encode_block(xface->bitmap + 32, 16, 16, 0, &pq); + encode_block(xface->bitmap + XFACE_WIDTH * 16, 16, 16, 0, &pq); + encode_block(xface->bitmap + XFACE_WIDTH * 16 + 16, 16, 16, 0, &pq); + encode_block(xface->bitmap + XFACE_WIDTH * 16 + 32, 16, 16, 0, &pq); + encode_block(xface->bitmap + XFACE_WIDTH * 32, 16, 16, 0, &pq); + encode_block(xface->bitmap + XFACE_WIDTH * 32 + 16, 16, 16, 0, &pq); + encode_block(xface->bitmap + XFACE_WIDTH * 32 + 32, 16, 16, 0, &pq); + + while (pq.prob_ranges_idx > 0) + push_integer(&b, pq.prob_ranges[--pq.prob_ranges_idx]); + + /* write the inverted big integer in b to intbuf */ + i = 0; + while (b.nb_words) { + uint8_t r; + ff_big_div(&b, XFACE_PRINTS, &r); + intbuf[i++] = r + XFACE_FIRST_PRINT; + } + + if ((ret = ff_alloc_packet2(avctx, pkt, i+2)) < 0) + return ret; + + /* revert the number, and close the buffer */ + p = pkt->data; + while (--i >= 0) + *(p++) = intbuf[i]; + *(p++) = '\n'; + *(p++) = 0; + + pkt->flags |= AV_PKT_FLAG_KEY; + *got_packet = 1; + + return 0; +} + +static av_cold int xface_encode_close(AVCodecContext *avctx) +{ + av_freep(&avctx->coded_frame); + + return 0; +} + +AVCodec ff_xface_encoder = { + .name = "xface", + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_XFACE, + .priv_data_size = sizeof(XFaceContext), + .init = xface_encode_init, + .close = xface_encode_close, + .encode2 = xface_encode_frame, + .pix_fmts = (const enum PixelFormat[]) { AV_PIX_FMT_MONOWHITE, AV_PIX_FMT_NONE }, + .long_name = NULL_IF_CONFIG_SMALL("X-face image"), +}; diff --git a/libavformat/img2.c b/libavformat/img2.c index ed4b39afe0..3867477dea 100644 --- a/libavformat/img2.c +++ b/libavformat/img2.c @@ -74,6 +74,7 @@ static const IdStrMap img_tags[] = { { AV_CODEC_ID_PICTOR , "pic"}, { AV_CODEC_ID_V210X , "yuv10"}, { AV_CODEC_ID_XBM , "xbm"}, + { AV_CODEC_ID_XFACE , "xface"}, { AV_CODEC_ID_XWD , "xwd"}, { AV_CODEC_ID_NONE , NULL} }; diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c index 2956ca0ef3..dea4572209 100644 --- a/libavformat/img2enc.c +++ b/libavformat/img2enc.c @@ -152,7 +152,7 @@ AVOutputFormat ff_image2_muxer = { .long_name = NULL_IF_CONFIG_SMALL("image2 sequence"), .extensions = "bmp,dpx,jls,jpeg,jpg,ljpg,pam,pbm,pcx,pgm,pgmyuv,png," "ppm,sgi,tga,tif,tiff,jp2,j2c,xwd,sun,ras,rs,im1,im8,im24," - "sunras,xbm", + "sunras,xbm,xface", .priv_data_size = sizeof(VideoMuxData), .video_codec = AV_CODEC_ID_MJPEG, .write_header = write_header, diff --git a/libavformat/version.h b/libavformat/version.h index 1bac92ba3e..c757fdd07b 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -31,7 +31,7 @@ #define LIBAVFORMAT_VERSION_MAJOR 54 #define LIBAVFORMAT_VERSION_MINOR 32 -#define LIBAVFORMAT_VERSION_MICRO 100 +#define LIBAVFORMAT_VERSION_MICRO 101 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \