diff options
author | Michael Niedermayer <michaelni@gmx.at> | 2013-09-19 12:06:39 +0200 |
---|---|---|
committer | Michael Niedermayer <michaelni@gmx.at> | 2013-09-19 12:36:11 +0200 |
commit | b26ccf218d933660c551832b4b14976b2acb3f0c (patch) | |
tree | 149a7c27caf046cc105156d9e481cd7af651a90c | |
parent | ac2fc63ac6f938a4c995d87486013baffb5ca18a (diff) | |
parent | c4bfa098072ba338d83555d6e2199f7e1e64ffff (diff) | |
download | ffmpeg-b26ccf218d933660c551832b4b14976b2acb3f0c.tar.gz |
Merge commit 'c4bfa098072ba338d83555d6e2199f7e1e64ffff'
* commit 'c4bfa098072ba338d83555d6e2199f7e1e64ffff':
Add a WebP decoder
Conflicts:
Changelog
doc/general.texi
libavcodec/Makefile
libavcodec/avcodec.h
libavcodec/version.h
libavformat/img2.c
The previously existing webp decoder is disabled as the new
one supports lossless mode and alpha while the previous does not.
Merged-by: Michael Niedermayer <michaelni@gmx.at>
-rw-r--r-- | Changelog | 1 | ||||
-rw-r--r-- | doc/general.texi | 2 | ||||
-rw-r--r-- | libavcodec/Makefile | 1 | ||||
-rw-r--r-- | libavcodec/avcodec.h | 1 | ||||
-rw-r--r-- | libavcodec/codec_desc.c | 15 | ||||
-rw-r--r-- | libavcodec/utils.c | 1 | ||||
-rw-r--r-- | libavcodec/version.h | 4 | ||||
-rw-r--r-- | libavcodec/vp8.c | 48 | ||||
-rw-r--r-- | libavcodec/vp8.h | 7 | ||||
-rw-r--r-- | libavcodec/webp.c | 1455 |
10 files changed, 1502 insertions, 33 deletions
@@ -26,6 +26,7 @@ version <next> - adelay filter - pullup filter ported from libmpcodecs - ffprobe -read_intervals option +- Lossless and alpha support for WebP decoder version 2.0: diff --git a/doc/general.texi b/doc/general.texi index b45c18a38b..f4776c8cb7 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -488,6 +488,8 @@ following image formats are supported: @tab YUV, JPEG and some extension is not supported yet. @item Truevision Targa @tab X @tab X @tab Targa (.TGA) image format +@item WebP @tab @tab X + @tab WebP image format @item XBM @tab X @tab X @tab X BitMap image format @item XFace @tab X @tab X diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 0f53a7a805..6c084d55a3 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -476,6 +476,7 @@ OBJS-$(CONFIG_VQA_DECODER) += vqavideo.o OBJS-$(CONFIG_WAVPACK_DECODER) += wavpack.o OBJS-$(CONFIG_WAVPACK_ENCODER) += wavpackenc.o OBJS-$(CONFIG_WEBP_DECODER) += vp8.o vp8dsp.o vp56rac.o +OBJS-$(CONFIG_WEBP_DECODER) += webp.o OBJS-$(CONFIG_WEBVTT_DECODER) += webvttdec.o OBJS-$(CONFIG_WMALOSSLESS_DECODER) += wmalosslessdec.o wma_common.o OBJS-$(CONFIG_WMAPRO_DECODER) += wmaprodec.o wma.o wma_common.o diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 23fba4fb6d..997a00cbf6 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -275,6 +275,7 @@ enum AVCodecID { AV_CODEC_ID_AIC, AV_CODEC_ID_ESCAPE130_DEPRECATED, AV_CODEC_ID_G2M_DEPRECATED, + AV_CODEC_ID_WEBP_DEPRECATED, AV_CODEC_ID_BRENDER_PIX= MKBETAG('B','P','I','X'), AV_CODEC_ID_Y41P = MKBETAG('Y','4','1','P'), diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index 1e4cf2e23e..6ec9cefac7 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -1045,13 +1045,6 @@ static const AVCodecDescriptor codec_descriptors[] = { .props = AV_CODEC_PROP_LOSSY, }, { - .id = AV_CODEC_ID_WEBP, - .type = AVMEDIA_TYPE_VIDEO, - .name = "webp", - .long_name = NULL_IF_CONFIG_SMALL("WebP"), - .props = AV_CODEC_PROP_LOSSY, - }, - { .id = AV_CODEC_ID_PICTOR, .type = AVMEDIA_TYPE_VIDEO, .name = "pictor", @@ -1393,6 +1386,14 @@ static const AVCodecDescriptor codec_descriptors[] = { .long_name = NULL_IF_CONFIG_SMALL("Go2Meeting"), .props = AV_CODEC_PROP_LOSSY, }, + { + .id = AV_CODEC_ID_WEBP, + .type = AVMEDIA_TYPE_VIDEO, + .name = "webp", + .long_name = NULL_IF_CONFIG_SMALL("WebP"), + .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY | + AV_CODEC_PROP_LOSSLESS, + }, /* various PCM "codecs" */ { diff --git a/libavcodec/utils.c b/libavcodec/utils.c index 92b6443d32..7ca2044a6c 100644 --- a/libavcodec/utils.c +++ b/libavcodec/utils.c @@ -2487,6 +2487,7 @@ static enum AVCodecID remap_deprecated_codec_id(enum AVCodecID id) case AV_CODEC_ID_TAK_DEPRECATED : return AV_CODEC_ID_TAK; case AV_CODEC_ID_ESCAPE130_DEPRECATED : return AV_CODEC_ID_ESCAPE130; case AV_CODEC_ID_G2M_DEPRECATED : return AV_CODEC_ID_G2M; + case AV_CODEC_ID_WEBP_DEPRECATED: return AV_CODEC_ID_WEBP; default : return id; } } diff --git a/libavcodec/version.h b/libavcodec/version.h index f16db2aa5a..55065c3ca7 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,8 +29,8 @@ #include "libavutil/avutil.h" #define LIBAVCODEC_VERSION_MAJOR 55 -#define LIBAVCODEC_VERSION_MINOR 31 -#define LIBAVCODEC_VERSION_MICRO 101 +#define LIBAVCODEC_VERSION_MINOR 32 +#define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ LIBAVCODEC_VERSION_MINOR, \ diff --git a/libavcodec/vp8.c b/libavcodec/vp8.c index 9848802dbb..d7fac64411 100644 --- a/libavcodec/vp8.c +++ b/libavcodec/vp8.c @@ -1854,8 +1854,8 @@ static int vp8_decode_mb_row_sliced(AVCodecContext *avctx, void *tdata, return 0; } -static int vp8_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, - AVPacket *avpkt) +int ff_vp8_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, + AVPacket *avpkt) { VP8Context *s = avctx->priv_data; int ret, i, referenced, num_jobs; @@ -2009,7 +2009,7 @@ err: return ret; } -static av_cold int vp8_decode_free(AVCodecContext *avctx) +av_cold int ff_vp8_decode_free(AVCodecContext *avctx) { VP8Context *s = avctx->priv_data; int i; @@ -2032,7 +2032,7 @@ static av_cold int vp8_init_frames(VP8Context *s) return 0; } -static av_cold int vp8_decode_init(AVCodecContext *avctx) +av_cold int ff_vp8_decode_init(AVCodecContext *avctx) { VP8Context *s = avctx->priv_data; int ret; @@ -2046,7 +2046,7 @@ static av_cold int vp8_decode_init(AVCodecContext *avctx) ff_vp8dsp_init(&s->vp8dsp); if ((ret = vp8_init_frames(s)) < 0) { - vp8_decode_free(avctx); + ff_vp8_decode_free(avctx); return ret; } @@ -2061,7 +2061,7 @@ static av_cold int vp8_decode_init_thread_copy(AVCodecContext *avctx) s->avctx = avctx; if ((ret = vp8_init_frames(s)) < 0) { - vp8_decode_free(avctx); + ff_vp8_decode_free(avctx); return ret; } @@ -2147,7 +2147,7 @@ static int webp_decode_frame(AVCodecContext *avctx, void *data, int *data_size, pkt.data = buf; pkt.size = buf_size; - return vp8_decode_frame(avctx, data, data_size, &pkt); + return ff_vp8_decode_frame(avctx, data, data_size, &pkt); } AVCodec ff_vp8_decoder = { @@ -2155,9 +2155,9 @@ AVCodec ff_vp8_decoder = { .type = AVMEDIA_TYPE_VIDEO, .id = AV_CODEC_ID_VP8, .priv_data_size = sizeof(VP8Context), - .init = vp8_decode_init, - .close = vp8_decode_free, - .decode = vp8_decode_frame, + .init = ff_vp8_decode_init, + .close = ff_vp8_decode_free, + .decode = ff_vp8_decode_frame, .capabilities = CODEC_CAP_DR1 | CODEC_CAP_FRAME_THREADS | CODEC_CAP_SLICE_THREADS, .flush = vp8_decode_flush, .long_name = NULL_IF_CONFIG_SMALL("On2 VP8"), @@ -2165,17 +2165,17 @@ AVCodec ff_vp8_decoder = { .update_thread_context = ONLY_IF_THREADS_ENABLED(vp8_decode_update_thread_context), }; -AVCodec ff_webp_decoder = { - .name = "webp", - .type = AVMEDIA_TYPE_VIDEO, - .id = AV_CODEC_ID_WEBP, - .priv_data_size = sizeof(VP8Context), - .init = vp8_decode_init, - .close = vp8_decode_free, - .decode = webp_decode_frame, - .capabilities = CODEC_CAP_DR1 | CODEC_CAP_FRAME_THREADS | CODEC_CAP_SLICE_THREADS, - .flush = vp8_decode_flush, - .long_name = NULL_IF_CONFIG_SMALL("WebP"), - .init_thread_copy = ONLY_IF_THREADS_ENABLED(vp8_decode_init_thread_copy), - .update_thread_context = ONLY_IF_THREADS_ENABLED(vp8_decode_update_thread_context), -}; +// AVCodec ff_webp_decoder = { +// .name = "webp", +// .type = AVMEDIA_TYPE_VIDEO, +// .id = AV_CODEC_ID_WEBP, +// .priv_data_size = sizeof(VP8Context), +// .init = vp8_decode_init, +// .close = vp8_decode_free, +// .decode = webp_decode_frame, +// .capabilities = CODEC_CAP_DR1 | CODEC_CAP_FRAME_THREADS | CODEC_CAP_SLICE_THREADS, +// .flush = vp8_decode_flush, +// .long_name = NULL_IF_CONFIG_SMALL("WebP"), +// .init_thread_copy = ONLY_IF_THREADS_ENABLED(vp8_decode_init_thread_copy), +// .update_thread_context = ONLY_IF_THREADS_ENABLED(vp8_decode_update_thread_context), +// }; diff --git a/libavcodec/vp8.h b/libavcodec/vp8.h index f66954d35a..6b56e65168 100644 --- a/libavcodec/vp8.h +++ b/libavcodec/vp8.h @@ -271,4 +271,11 @@ typedef struct VP8Context { int mb_layout; } VP8Context; +int ff_vp8_decode_init(AVCodecContext *avctx); + +int ff_vp8_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, + AVPacket *avpkt); + +int ff_vp8_decode_free(AVCodecContext *avctx); + #endif /* AVCODEC_VP8_H */ diff --git a/libavcodec/webp.c b/libavcodec/webp.c new file mode 100644 index 0000000000..213e1cef16 --- /dev/null +++ b/libavcodec/webp.c @@ -0,0 +1,1455 @@ +/* + * WebP (.webp) image decoder + * Copyright (c) 2013 Aneesh Dogra <aneesh@sugarlabs.org> + * Copyright (c) 2013 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + * WebP image decoder + * + * @author Aneesh Dogra <aneesh@sugarlabs.org> + * Container and Lossy decoding + * + * @author Justin Ruggles <justin.ruggles@gmail.com> + * Lossless decoder + * Compressed alpha for lossy + * + * Unimplemented: + * - Animation + * - ICC profile + * - Exif and XMP metadata + */ + +#define BITSTREAM_READER_LE +#include "libavutil/imgutils.h" +#include "avcodec.h" +#include "bytestream.h" +#include "internal.h" +#include "get_bits.h" +#include "thread.h" +#include "vp8.h" + +#define VP8X_FLAG_ANIMATION 0x02 +#define VP8X_FLAG_XMP_METADATA 0x04 +#define VP8X_FLAG_EXIF_METADATA 0x08 +#define VP8X_FLAG_ALPHA 0x10 +#define VP8X_FLAG_ICC 0x20 + +#define MAX_PALETTE_SIZE 256 +#define MAX_CACHE_BITS 11 +#define NUM_CODE_LENGTH_CODES 19 +#define HUFFMAN_CODES_PER_META_CODE 5 +#define NUM_LITERAL_CODES 256 +#define NUM_LENGTH_CODES 24 +#define NUM_DISTANCE_CODES 40 +#define NUM_SHORT_DISTANCES 120 +#define MAX_HUFFMAN_CODE_LENGTH 15 + +static const uint16_t alphabet_sizes[HUFFMAN_CODES_PER_META_CODE] = { + NUM_LITERAL_CODES + NUM_LENGTH_CODES, + NUM_LITERAL_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES, + NUM_DISTANCE_CODES +}; + +static const uint8_t code_length_code_order[NUM_CODE_LENGTH_CODES] = { + 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 +}; + +static const int8_t lz77_distance_offsets[NUM_SHORT_DISTANCES][2] = { + { 0, 1 }, { 1, 0 }, { 1, 1 }, { -1, 1 }, { 0, 2 }, { 2, 0 }, { 1, 2 }, { -1, 2 }, + { 2, 1 }, { -2, 1 }, { 2, 2 }, { -2, 2 }, { 0, 3 }, { 3, 0 }, { 1, 3 }, { -1, 3 }, + { 3, 1 }, { -3, 1 }, { 2, 3 }, { -2, 3 }, { 3, 2 }, { -3, 2 }, { 0, 4 }, { 4, 0 }, + { 1, 4 }, { -1, 4 }, { 4, 1 }, { -4, 1 }, { 3, 3 }, { -3, 3 }, { 2, 4 }, { -2, 4 }, + { 4, 2 }, { -4, 2 }, { 0, 5 }, { 3, 4 }, { -3, 4 }, { 4, 3 }, { -4, 3 }, { 5, 0 }, + { 1, 5 }, { -1, 5 }, { 5, 1 }, { -5, 1 }, { 2, 5 }, { -2, 5 }, { 5, 2 }, { -5, 2 }, + { 4, 4 }, { -4, 4 }, { 3, 5 }, { -3, 5 }, { 5, 3 }, { -5, 3 }, { 0, 6 }, { 6, 0 }, + { 1, 6 }, { -1, 6 }, { 6, 1 }, { -6, 1 }, { 2, 6 }, { -2, 6 }, { 6, 2 }, { -6, 2 }, + { 4, 5 }, { -4, 5 }, { 5, 4 }, { -5, 4 }, { 3, 6 }, { -3, 6 }, { 6, 3 }, { -6, 3 }, + { 0, 7 }, { 7, 0 }, { 1, 7 }, { -1, 7 }, { 5, 5 }, { -5, 5 }, { 7, 1 }, { -7, 1 }, + { 4, 6 }, { -4, 6 }, { 6, 4 }, { -6, 4 }, { 2, 7 }, { -2, 7 }, { 7, 2 }, { -7, 2 }, + { 3, 7 }, { -3, 7 }, { 7, 3 }, { -7, 3 }, { 5, 6 }, { -5, 6 }, { 6, 5 }, { -6, 5 }, + { 8, 0 }, { 4, 7 }, { -4, 7 }, { 7, 4 }, { -7, 4 }, { 8, 1 }, { 8, 2 }, { 6, 6 }, + { -6, 6 }, { 8, 3 }, { 5, 7 }, { -5, 7 }, { 7, 5 }, { -7, 5 }, { 8, 4 }, { 6, 7 }, + { -6, 7 }, { 7, 6 }, { -7, 6 }, { 8, 5 }, { 7, 7 }, { -7, 7 }, { 8, 6 }, { 8, 7 } +}; + +enum AlphaCompression { + ALPHA_COMPRESSION_NONE, + ALPHA_COMPRESSION_VP8L, +}; + +enum AlphaFilter { + ALPHA_FILTER_NONE, + ALPHA_FILTER_HORIZONTAL, + ALPHA_FILTER_VERTICAL, + ALPHA_FILTER_GRADIENT, +}; + +enum TransformType { + PREDICTOR_TRANSFORM = 0, + COLOR_TRANSFORM = 1, + SUBTRACT_GREEN = 2, + COLOR_INDEXING_TRANSFORM = 3, +}; + +enum PredictionMode { + PRED_MODE_BLACK, + PRED_MODE_L, + PRED_MODE_T, + PRED_MODE_TR, + PRED_MODE_TL, + PRED_MODE_AVG_T_AVG_L_TR, + PRED_MODE_AVG_L_TL, + PRED_MODE_AVG_L_T, + PRED_MODE_AVG_TL_T, + PRED_MODE_AVG_T_TR, + PRED_MODE_AVG_AVG_L_TL_AVG_T_TR, + PRED_MODE_SELECT, + PRED_MODE_ADD_SUBTRACT_FULL, + PRED_MODE_ADD_SUBTRACT_HALF, +}; + +enum HuffmanIndex { + HUFF_IDX_GREEN = 0, + HUFF_IDX_RED = 1, + HUFF_IDX_BLUE = 2, + HUFF_IDX_ALPHA = 3, + HUFF_IDX_DIST = 4 +}; + +/* The structure of WebP lossless is an optional series of transformation data, + * followed by the primary image. The primary image also optionally contains + * an entropy group mapping if there are multiple entropy groups. There is a + * basic image type called an "entropy coded image" that is used for all of + * these. The type of each entropy coded image is referred to by the + * specification as its role. */ +enum ImageRole { + /* Primary Image: Stores the actual pixels of the image. */ + IMAGE_ROLE_ARGB, + + /* Entropy Image: Defines which Huffman group to use for different areas of + * the primary image. */ + IMAGE_ROLE_ENTROPY, + + /* Predictors: Defines which predictor type to use for different areas of + * the primary image. */ + IMAGE_ROLE_PREDICTOR, + + /* Color Transform Data: Defines the color transformation for different + * areas of the primary image. */ + IMAGE_ROLE_COLOR_TRANSFORM, + + /* Color Index: Stored as an image of height == 1. */ + IMAGE_ROLE_COLOR_INDEXING, + + IMAGE_ROLE_NB, +}; + +typedef struct HuffReader { + VLC vlc; /* Huffman decoder context */ + int simple; /* whether to use simple mode */ + int nb_symbols; /* number of coded symbols */ + uint16_t simple_symbols[2]; /* symbols for simple mode */ +} HuffReader; + +typedef struct ImageContext { + enum ImageRole role; /* role of this image */ + AVFrame *frame; /* AVFrame for data */ + int color_cache_bits; /* color cache size, log2 */ + uint32_t *color_cache; /* color cache data */ + int nb_huffman_groups; /* number of huffman groups */ + HuffReader *huffman_groups; /* reader for each huffman group */ + int size_reduction; /* relative size compared to primary image, log2 */ + int is_alpha_primary; +} ImageContext; + +typedef struct WebPContext { + VP8Context v; /* VP8 Context used for lossy decoding */ + GetBitContext gb; /* bitstream reader for main image chunk */ + AVFrame *alpha_frame; /* AVFrame for alpha data decompressed from VP8L */ + AVCodecContext *avctx; /* parent AVCodecContext */ + int initialized; /* set once the VP8 context is initialized */ + int has_alpha; /* has a separate alpha chunk */ + enum AlphaCompression alpha_compression; /* compression type for alpha chunk */ + enum AlphaFilter alpha_filter; /* filtering method for alpha chunk */ + uint8_t *alpha_data; /* alpha chunk data */ + int alpha_data_size; /* alpha chunk data size */ + int width; /* image width */ + int height; /* image height */ + int lossless; /* indicates lossless or lossy */ + + int nb_transforms; /* number of transforms */ + enum TransformType transforms[4]; /* transformations used in the image, in order */ + int reduced_width; /* reduced width for index image, if applicable */ + int nb_huffman_groups; /* number of huffman groups in the primary image */ + ImageContext image[IMAGE_ROLE_NB]; /* image context for each role */ +} WebPContext; + +#define GET_PIXEL(frame, x, y) \ + ((frame)->data[0] + (y) * frame->linesize[0] + 4 * (x)) + +#define GET_PIXEL_COMP(frame, x, y, c) \ + (*((frame)->data[0] + (y) * frame->linesize[0] + 4 * (x) + c)) + +static void image_ctx_free(ImageContext *img) +{ + int i, j; + + av_free(img->color_cache); + if (img->role != IMAGE_ROLE_ARGB && !img->is_alpha_primary) + av_frame_free(&img->frame); + if (img->huffman_groups) { + for (i = 0; i < img->nb_huffman_groups; i++) { + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; j++) + ff_free_vlc(&img->huffman_groups[i * HUFFMAN_CODES_PER_META_CODE + j].vlc); + } + av_free(img->huffman_groups); + } + memset(img, 0, sizeof(*img)); +} + + +/* Differs from get_vlc2() in the following ways: + * - codes are bit-reversed + * - assumes 8-bit table to make reversal simpler + * - assumes max depth of 2 since the max code length for WebP is 15 + */ +static av_always_inline int webp_get_vlc(GetBitContext *gb, VLC_TYPE (*table)[2]) +{ + int n, nb_bits; + unsigned int index; + int code; + + OPEN_READER(re, gb); + UPDATE_CACHE(re, gb); + + index = SHOW_UBITS(re, gb, 8); + index = ff_reverse[index]; + code = table[index][0]; + n = table[index][1]; + + if (n < 0) { + LAST_SKIP_BITS(re, gb, 8); + UPDATE_CACHE(re, gb); + + nb_bits = -n; + + index = SHOW_UBITS(re, gb, nb_bits); + index = (ff_reverse[index] >> (8 - nb_bits)) + code; + code = table[index][0]; + n = table[index][1]; + } + SKIP_BITS(re, gb, n); + + CLOSE_READER(re, gb); + + return code; +} + +static int huff_reader_get_symbol(HuffReader *r, GetBitContext *gb) +{ + if (r->simple) { + if (r->nb_symbols == 1) + return r->simple_symbols[0]; + else + return r->simple_symbols[get_bits1(gb)]; + } else + return webp_get_vlc(gb, r->vlc.table); +} + +static int huff_reader_build_canonical(HuffReader *r, int *code_lengths, + int alphabet_size) +{ + int len, sym, code, ret; + int max_code_length = 0; + uint16_t *codes; + + for (sym = 0; sym < alphabet_size; sym++) + max_code_length = FFMAX(max_code_length, code_lengths[sym]); + + if (max_code_length == 0 || max_code_length > MAX_HUFFMAN_CODE_LENGTH) + return AVERROR(EINVAL); + + codes = av_malloc(alphabet_size * sizeof(*codes)); + if (!codes) + return AVERROR(ENOMEM); + + code = 0; + r->nb_symbols = 0; + for (len = 1; len <= max_code_length; len++) { + for (sym = 0; sym < alphabet_size; sym++) { + if (code_lengths[sym] != len) + continue; + codes[sym] = code++; + r->nb_symbols++; + } + code <<= 1; + } + if (!r->nb_symbols) { + av_free(codes); + return AVERROR_INVALIDDATA; + } + + ret = init_vlc(&r->vlc, 8, alphabet_size, + code_lengths, sizeof(*code_lengths), sizeof(*code_lengths), + codes, sizeof(*codes), sizeof(*codes), 0); + if (ret < 0) { + av_free(codes); + return ret; + } + r->simple = 0; + + av_free(codes); + return 0; +} + +static void read_huffman_code_simple(WebPContext *s, HuffReader *hc) +{ + hc->nb_symbols = get_bits1(&s->gb) + 1; + + if (get_bits1(&s->gb)) + hc->simple_symbols[0] = get_bits(&s->gb, 8); + else + hc->simple_symbols[0] = get_bits1(&s->gb); + + if (hc->nb_symbols == 2) + hc->simple_symbols[1] = get_bits(&s->gb, 8); + + hc->simple = 1; +} + +static int read_huffman_code_normal(WebPContext *s, HuffReader *hc, + int alphabet_size) +{ + HuffReader code_len_hc = { { 0 }, 0, 0, { 0 } }; + int *code_lengths = NULL; + int code_length_code_lengths[NUM_CODE_LENGTH_CODES] = { 0 }; + int i, symbol, max_symbol, prev_code_len, ret; + int num_codes = 4 + get_bits(&s->gb, 4); + + if (num_codes > NUM_CODE_LENGTH_CODES) + return AVERROR_INVALIDDATA; + + for (i = 0; i < num_codes; i++) + code_length_code_lengths[code_length_code_order[i]] = get_bits(&s->gb, 3); + + ret = huff_reader_build_canonical(&code_len_hc, code_length_code_lengths, + NUM_CODE_LENGTH_CODES); + if (ret < 0) + goto finish; + + code_lengths = av_mallocz_array(alphabet_size, sizeof(*code_lengths)); + if (!code_lengths) { + ret = AVERROR(ENOMEM); + goto finish; + } + + if (get_bits1(&s->gb)) { + int bits = 2 + 2 * get_bits(&s->gb, 3); + max_symbol = 2 + get_bits(&s->gb, bits); + if (max_symbol > alphabet_size) { + av_log(s->avctx, AV_LOG_ERROR, "max symbol %d > alphabet size %d\n", + max_symbol, alphabet_size); + ret = AVERROR_INVALIDDATA; + goto finish; + } + } else { + max_symbol = alphabet_size; + } + + prev_code_len = 8; + symbol = 0; + while (symbol < alphabet_size) { + int code_len; + + if (!max_symbol--) + break; + code_len = huff_reader_get_symbol(&code_len_hc, &s->gb); + if (code_len < 16) { + /* Code length code [0..15] indicates literal code lengths. */ + code_lengths[symbol++] = code_len; + if (code_len) + prev_code_len = code_len; + } else { + int repeat = 0, length = 0; + switch (code_len) { + case 16: + /* Code 16 repeats the previous non-zero value [3..6] times, + * i.e., 3 + ReadBits(2) times. If code 16 is used before a + * non-zero value has been emitted, a value of 8 is repeated. */ + repeat = 3 + get_bits(&s->gb, 2); + length = prev_code_len; + break; + case 17: + /* Code 17 emits a streak of zeros [3..10], i.e., + * 3 + ReadBits(3) times. */ + repeat = 3 + get_bits(&s->gb, 3); + break; + case 18: + /* Code 18 emits a streak of zeros of length [11..138], i.e., + * 11 + ReadBits(7) times. */ + repeat = 11 + get_bits(&s->gb, 7); + break; + } + if (symbol + repeat > alphabet_size) { + av_log(s->avctx, AV_LOG_ERROR, + "invalid symbol %d + repeat %d > alphabet size %d\n", + symbol, repeat, alphabet_size); + ret = AVERROR_INVALIDDATA; + goto finish; + } + while (repeat-- > 0) + code_lengths[symbol++] = length; + } + } + + ret = huff_reader_build_canonical(hc, code_lengths, alphabet_size); + +finish: + ff_free_vlc(&code_len_hc.vlc); + av_free(code_lengths); + return ret; +} + +static int decode_entropy_coded_image(WebPContext *s, enum ImageRole role, + int w, int h); + +#define PARSE_BLOCK_SIZE(w, h) do { \ + block_bits = get_bits(&s->gb, 3) + 2; \ + blocks_w = FFALIGN((w), 1 << block_bits) >> block_bits; \ + blocks_h = FFALIGN((h), 1 << block_bits) >> block_bits; \ +} while (0) + +static int decode_entropy_image(WebPContext *s) +{ + ImageContext *img; + int ret, block_bits, width, blocks_w, blocks_h, x, y, max; + + width = s->width; + if (s->reduced_width > 0) + width = s->reduced_width; + + PARSE_BLOCK_SIZE(width, s->height); + + ret = decode_entropy_coded_image(s, IMAGE_ROLE_ENTROPY, blocks_w, blocks_h); + if (ret < 0) + return ret; + + img = &s->image[IMAGE_ROLE_ENTROPY]; + img->size_reduction = block_bits; + + /* the number of huffman groups is determined by the maximum group number + * coded in the entropy image */ + max = 0; + for (y = 0; y < img->frame->height; y++) { + for (x = 0; x < img->frame->width; x++) { + int p = GET_PIXEL_COMP(img->frame, x, y, 2); + max = FFMAX(max, p); + } + } + s->nb_huffman_groups = max + 1; + + return 0; +} + +static int parse_transform_predictor(WebPContext *s) +{ + int block_bits, blocks_w, blocks_h, ret; + + PARSE_BLOCK_SIZE(s->width, s->height); + + ret = decode_entropy_coded_image(s, IMAGE_ROLE_PREDICTOR, blocks_w, + blocks_h); + if (ret < 0) + return ret; + + s->image[IMAGE_ROLE_PREDICTOR].size_reduction = block_bits; + + return 0; +} + +static int parse_transform_color(WebPContext *s) +{ + int block_bits, blocks_w, blocks_h, ret; + + PARSE_BLOCK_SIZE(s->width, s->height); + + ret = decode_entropy_coded_image(s, IMAGE_ROLE_COLOR_TRANSFORM, blocks_w, + blocks_h); + if (ret < 0) + return ret; + + s->image[IMAGE_ROLE_COLOR_TRANSFORM].size_reduction = block_bits; + + return 0; +} + +static int parse_transform_color_indexing(WebPContext *s) +{ + ImageContext *img; + int width_bits, index_size, ret, x; + uint8_t *ct; + + index_size = get_bits(&s->gb, 8) + 1; + + if (index_size <= 2) + width_bits = 3; + else if (index_size <= 4) + width_bits = 2; + else if (index_size <= 16) + width_bits = 1; + else + width_bits = 0; + + ret = decode_entropy_coded_image(s, IMAGE_ROLE_COLOR_INDEXING, + index_size, 1); + if (ret < 0) + return ret; + + img = &s->image[IMAGE_ROLE_COLOR_INDEXING]; + img->size_reduction = width_bits; + if (width_bits > 0) + s->reduced_width = (s->width + ((1 << width_bits) - 1)) >> width_bits; + + /* color index values are delta-coded */ + ct = img->frame->data[0] + 4; + for (x = 4; x < img->frame->width * 4; x++, ct++) + ct[0] += ct[-4]; + + return 0; +} + +static HuffReader *get_huffman_group(WebPContext *s, ImageContext *img, + int x, int y) +{ + ImageContext *gimg = &s->image[IMAGE_ROLE_ENTROPY]; + int group = 0; + + if (gimg->size_reduction > 0) { + int group_x = x >> gimg->size_reduction; + int group_y = y >> gimg->size_reduction; + group = GET_PIXEL_COMP(gimg->frame, group_x, group_y, 2); + } + + return &img->huffman_groups[group * HUFFMAN_CODES_PER_META_CODE]; +} + +static av_always_inline void color_cache_put(ImageContext *img, uint32_t c) +{ + uint32_t cache_idx = (0x1E35A7BD * c) >> (32 - img->color_cache_bits); + img->color_cache[cache_idx] = c; +} + +static int decode_entropy_coded_image(WebPContext *s, enum ImageRole role, + int w, int h) +{ + ImageContext *img; + HuffReader *hg; + int i, j, ret, x, y, width; + + img = &s->image[role]; + img->role = role; + + if (!img->frame) { + img->frame = av_frame_alloc(); + if (!img->frame) + return AVERROR(ENOMEM); + } + + img->frame->format = AV_PIX_FMT_ARGB; + img->frame->width = w; + img->frame->height = h; + + if (role == IMAGE_ROLE_ARGB && !img->is_alpha_primary) { + ThreadFrame pt = { .f = img->frame }; + ret = ff_thread_get_buffer(s->avctx, &pt, 0); + } else + ret = av_frame_get_buffer(img->frame, 1); + if (ret < 0) + return ret; + + if (get_bits1(&s->gb)) { + img->color_cache_bits = get_bits(&s->gb, 4); + if (img->color_cache_bits < 1 || img->color_cache_bits > 11) { + av_log(s->avctx, AV_LOG_ERROR, "invalid color cache bits: %d\n", + img->color_cache_bits); + return AVERROR_INVALIDDATA; + } + img->color_cache = av_mallocz_array(1 << img->color_cache_bits, + sizeof(*img->color_cache)); + if (!img->color_cache) + return AVERROR(ENOMEM); + } else { + img->color_cache_bits = 0; + } + + img->nb_huffman_groups = 1; + if (role == IMAGE_ROLE_ARGB && get_bits1(&s->gb)) { + ret = decode_entropy_image(s); + if (ret < 0) + return ret; + img->nb_huffman_groups = s->nb_huffman_groups; + } + img->huffman_groups = av_mallocz_array(img->nb_huffman_groups * + HUFFMAN_CODES_PER_META_CODE, + sizeof(*img->huffman_groups)); + if (!img->huffman_groups) + return AVERROR(ENOMEM); + + for (i = 0; i < img->nb_huffman_groups; i++) { + hg = &img->huffman_groups[i * HUFFMAN_CODES_PER_META_CODE]; + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; j++) { + int alphabet_size = alphabet_sizes[j]; + if (!j && img->color_cache_bits > 0) + alphabet_size += 1 << img->color_cache_bits; + + if (get_bits1(&s->gb)) { + read_huffman_code_simple(s, &hg[j]); + } else { + ret = read_huffman_code_normal(s, &hg[j], alphabet_size); + if (ret < 0) + return ret; + } + } + } + + width = img->frame->width; + if (role == IMAGE_ROLE_ARGB && s->reduced_width > 0) + width = s->reduced_width; + + x = 0; y = 0; + while (y < img->frame->height) { + int v; + + hg = get_huffman_group(s, img, x, y); + v = huff_reader_get_symbol(&hg[HUFF_IDX_GREEN], &s->gb); + if (v < NUM_LITERAL_CODES) { + /* literal pixel values */ + uint8_t *p = GET_PIXEL(img->frame, x, y); + p[2] = v; + p[1] = huff_reader_get_symbol(&hg[HUFF_IDX_RED], &s->gb); + p[3] = huff_reader_get_symbol(&hg[HUFF_IDX_BLUE], &s->gb); + p[0] = huff_reader_get_symbol(&hg[HUFF_IDX_ALPHA], &s->gb); + if (img->color_cache_bits) + color_cache_put(img, AV_RB32(p)); + x++; + if (x == width) { + x = 0; + y++; + } + } else if (v < NUM_LITERAL_CODES + NUM_LENGTH_CODES) { + /* LZ77 backwards mapping */ + int prefix_code, length, distance, ref_x, ref_y; + + /* parse length and distance */ + prefix_code = v - NUM_LITERAL_CODES; + if (prefix_code < 4) { + length = prefix_code + 1; + } else { + int extra_bits = (prefix_code - 2) >> 1; + int offset = 2 + (prefix_code & 1) << extra_bits; + length = offset + get_bits(&s->gb, extra_bits) + 1; + } + prefix_code = huff_reader_get_symbol(&hg[HUFF_IDX_DIST], &s->gb); + if (prefix_code < 4) { + distance = prefix_code + 1; + } else { + int extra_bits = prefix_code - 2 >> 1; + int offset = 2 + (prefix_code & 1) << extra_bits; + distance = offset + get_bits(&s->gb, extra_bits) + 1; + } + + /* find reference location */ + if (distance <= NUM_SHORT_DISTANCES) { + int xi = lz77_distance_offsets[distance - 1][0]; + int yi = lz77_distance_offsets[distance - 1][1]; + distance = FFMAX(1, xi + yi * width); + } else { + distance -= NUM_SHORT_DISTANCES; + } + ref_x = x; + ref_y = y; + if (distance <= x) { + ref_x -= distance; + distance = 0; + } else { + ref_x = 0; + distance -= x; + } + while (distance >= width) { + ref_y--; + distance -= width; + } + if (distance > 0) { + ref_x = width - distance; + ref_y--; + } + ref_x = FFMAX(0, ref_x); + ref_y = FFMAX(0, ref_y); + + /* copy pixels + * source and dest regions can overlap and wrap lines, so just + * copy per-pixel */ + for (i = 0; i < length; i++) { + uint8_t *p_ref = GET_PIXEL(img->frame, ref_x, ref_y); + uint8_t *p = GET_PIXEL(img->frame, x, y); + + AV_COPY32(p, p_ref); + if (img->color_cache_bits) + color_cache_put(img, AV_RB32(p)); + x++; + ref_x++; + if (x == width) { + x = 0; + y++; + } + if (ref_x == width) { + ref_x = 0; + ref_y++; + } + if (y == img->frame->height || ref_y == img->frame->height) + break; + } + } else { + /* read from color cache */ + uint8_t *p = GET_PIXEL(img->frame, x, y); + int cache_idx = v - (NUM_LITERAL_CODES + NUM_LENGTH_CODES); + + if (!img->color_cache_bits) { + av_log(s->avctx, AV_LOG_ERROR, "color cache not found\n"); + return AVERROR_INVALIDDATA; + } + if (cache_idx >= 1 << img->color_cache_bits) { + av_log(s->avctx, AV_LOG_ERROR, + "color cache index out-of-bounds\n"); + return AVERROR_INVALIDDATA; + } + AV_WB32(p, img->color_cache[cache_idx]); + x++; + if (x == width) { + x = 0; + y++; + } + } + } + + return 0; +} + +/* PRED_MODE_BLACK */ +static void inv_predict_0(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + AV_WB32(p, 0xFF000000); +} + +/* PRED_MODE_L */ +static void inv_predict_1(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + AV_COPY32(p, p_l); +} + +/* PRED_MODE_T */ +static void inv_predict_2(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + AV_COPY32(p, p_t); +} + +/* PRED_MODE_TR */ +static void inv_predict_3(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + AV_COPY32(p, p_tr); +} + +/* PRED_MODE_TL */ +static void inv_predict_4(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + AV_COPY32(p, p_tl); +} + +/* PRED_MODE_AVG_T_AVG_L_TR */ +static void inv_predict_5(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = p_t[0] + (p_l[0] + p_tr[0] >> 1) >> 1; + p[1] = p_t[1] + (p_l[1] + p_tr[1] >> 1) >> 1; + p[2] = p_t[2] + (p_l[2] + p_tr[2] >> 1) >> 1; + p[3] = p_t[3] + (p_l[3] + p_tr[3] >> 1) >> 1; +} + +/* PRED_MODE_AVG_L_TL */ +static void inv_predict_6(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = p_l[0] + p_tl[0] >> 1; + p[1] = p_l[1] + p_tl[1] >> 1; + p[2] = p_l[2] + p_tl[2] >> 1; + p[3] = p_l[3] + p_tl[3] >> 1; +} + +/* PRED_MODE_AVG_L_T */ +static void inv_predict_7(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = p_l[0] + p_t[0] >> 1; + p[1] = p_l[1] + p_t[1] >> 1; + p[2] = p_l[2] + p_t[2] >> 1; + p[3] = p_l[3] + p_t[3] >> 1; +} + +/* PRED_MODE_AVG_TL_T */ +static void inv_predict_8(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = p_tl[0] + p_t[0] >> 1; + p[1] = p_tl[1] + p_t[1] >> 1; + p[2] = p_tl[2] + p_t[2] >> 1; + p[3] = p_tl[3] + p_t[3] >> 1; +} + +/* PRED_MODE_AVG_T_TR */ +static void inv_predict_9(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = p_t[0] + p_tr[0] >> 1; + p[1] = p_t[1] + p_tr[1] >> 1; + p[2] = p_t[2] + p_tr[2] >> 1; + p[3] = p_t[3] + p_tr[3] >> 1; +} + +/* PRED_MODE_AVG_AVG_L_TL_AVG_T_TR */ +static void inv_predict_10(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = (p_l[0] + p_tl[0] >> 1) + (p_t[0] + p_tr[0] >> 1) >> 1; + p[1] = (p_l[1] + p_tl[1] >> 1) + (p_t[1] + p_tr[1] >> 1) >> 1; + p[2] = (p_l[2] + p_tl[2] >> 1) + (p_t[2] + p_tr[2] >> 1) >> 1; + p[3] = (p_l[3] + p_tl[3] >> 1) + (p_t[3] + p_tr[3] >> 1) >> 1; +} + +/* PRED_MODE_SELECT */ +static void inv_predict_11(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + int diff = (FFABS(p_l[0] - p_tl[0]) - FFABS(p_t[0] - p_tl[0])) + + (FFABS(p_l[1] - p_tl[1]) - FFABS(p_t[1] - p_tl[1])) + + (FFABS(p_l[2] - p_tl[2]) - FFABS(p_t[2] - p_tl[2])) + + (FFABS(p_l[3] - p_tl[3]) - FFABS(p_t[3] - p_tl[3])); + if (diff <= 0) + AV_COPY32(p, p_t); + else + AV_COPY32(p, p_l); +} + +/* PRED_MODE_ADD_SUBTRACT_FULL */ +static void inv_predict_12(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = av_clip_uint8(p_l[0] + p_t[0] - p_tl[0]); + p[1] = av_clip_uint8(p_l[1] + p_t[1] - p_tl[1]); + p[2] = av_clip_uint8(p_l[2] + p_t[2] - p_tl[2]); + p[3] = av_clip_uint8(p_l[3] + p_t[3] - p_tl[3]); +} + +static av_always_inline uint8_t clamp_add_subtract_half(int a, int b, int c) +{ + int d = a + b >> 1; + return av_clip_uint8(d + (d - c) / 2); +} + +/* PRED_MODE_ADD_SUBTRACT_HALF */ +static void inv_predict_13(uint8_t *p, const uint8_t *p_l, const uint8_t *p_tl, + const uint8_t *p_t, const uint8_t *p_tr) +{ + p[0] = clamp_add_subtract_half(p_l[0], p_t[0], p_tl[0]); + p[1] = clamp_add_subtract_half(p_l[1], p_t[1], p_tl[1]); + p[2] = clamp_add_subtract_half(p_l[2], p_t[2], p_tl[2]); + p[3] = clamp_add_subtract_half(p_l[3], p_t[3], p_tl[3]); +} + +typedef void (*inv_predict_func)(uint8_t *p, const uint8_t *p_l, + const uint8_t *p_tl, const uint8_t *p_t, + const uint8_t *p_tr); + +static const inv_predict_func inverse_predict[14] = { + inv_predict_0, inv_predict_1, inv_predict_2, inv_predict_3, + inv_predict_4, inv_predict_5, inv_predict_6, inv_predict_7, + inv_predict_8, inv_predict_9, inv_predict_10, inv_predict_11, + inv_predict_12, inv_predict_13, +}; + +static void inverse_prediction(AVFrame *frame, enum PredictionMode m, int x, int y) +{ + uint8_t *dec, *p_l, *p_tl, *p_t, *p_tr; + uint8_t p[4]; + + dec = GET_PIXEL(frame, x, y); + p_l = GET_PIXEL(frame, x - 1, y); + p_tl = GET_PIXEL(frame, x - 1, y - 1); + p_t = GET_PIXEL(frame, x, y - 1); + if (x == frame->width - 1) + p_tr = GET_PIXEL(frame, 0, y); + else + p_tr = GET_PIXEL(frame, x + 1, y - 1); + + inverse_predict[m](p, p_l, p_tl, p_t, p_tr); + + dec[0] += p[0]; + dec[1] += p[1]; + dec[2] += p[2]; + dec[3] += p[3]; +} + +static int apply_predictor_transform(WebPContext *s) +{ + ImageContext *img = &s->image[IMAGE_ROLE_ARGB]; + ImageContext *pimg = &s->image[IMAGE_ROLE_PREDICTOR]; + int x, y; + + for (y = 0; y < img->frame->height; y++) { + for (x = 0; x < img->frame->width; x++) { + int tx = x >> pimg->size_reduction; + int ty = y >> pimg->size_reduction; + enum PredictionMode m = GET_PIXEL_COMP(pimg->frame, tx, ty, 2); + + if (x == 0) { + if (y == 0) + m = PRED_MODE_BLACK; + else + m = PRED_MODE_T; + } else if (y == 0) + m = PRED_MODE_L; + + if (m > 13) { + av_log(s->avctx, AV_LOG_ERROR, + "invalid predictor mode: %d\n", m); + return AVERROR_INVALIDDATA; + } + inverse_prediction(img->frame, m, x, y); + } + } + return 0; +} + +static av_always_inline uint8_t color_transform_delta(uint8_t color_pred, + uint8_t color) +{ + return (int)ff_u8_to_s8(color_pred) * ff_u8_to_s8(color) >> 5; +} + +static int apply_color_transform(WebPContext *s) +{ + ImageContext *img, *cimg; + int x, y, cx, cy; + uint8_t *p, *cp; + + img = &s->image[IMAGE_ROLE_ARGB]; + cimg = &s->image[IMAGE_ROLE_COLOR_TRANSFORM]; + + for (y = 0; y < img->frame->height; y++) { + for (x = 0; x < img->frame->width; x++) { + cx = x >> cimg->size_reduction; + cy = y >> cimg->size_reduction; + cp = GET_PIXEL(cimg->frame, cx, cy); + p = GET_PIXEL(img->frame, x, y); + + p[1] += color_transform_delta(cp[3], p[2]); + p[3] += color_transform_delta(cp[2], p[2]) + + color_transform_delta(cp[1], p[1]); + } + } + return 0; +} + +static int apply_subtract_green_transform(WebPContext *s) +{ + int x, y; + ImageContext *img = &s->image[IMAGE_ROLE_ARGB]; + + for (y = 0; y < img->frame->height; y++) { + for (x = 0; x < img->frame->width; x++) { + uint8_t *p = GET_PIXEL(img->frame, x, y); + p[1] += p[2]; + p[3] += p[2]; + } + } + return 0; +} + +static int apply_color_indexing_transform(WebPContext *s) +{ + ImageContext *img; + ImageContext *pal; + int i, x, y; + uint8_t *p, *pi; + + img = &s->image[IMAGE_ROLE_ARGB]; + pal = &s->image[IMAGE_ROLE_COLOR_INDEXING]; + + if (pal->size_reduction > 0) { + GetBitContext gb_g; + uint8_t *line; + int pixel_bits = 8 >> pal->size_reduction; + + line = av_malloc(img->frame->linesize[0]); + if (!line) + return AVERROR(ENOMEM); + + for (y = 0; y < img->frame->height; y++) { + p = GET_PIXEL(img->frame, 0, y); + memcpy(line, p, img->frame->linesize[0]); + init_get_bits(&gb_g, line, img->frame->linesize[0] * 8); + skip_bits(&gb_g, 16); + i = 0; + for (x = 0; x < img->frame->width; x++) { + p = GET_PIXEL(img->frame, x, y); + p[2] = get_bits(&gb_g, pixel_bits); + i++; + if (i == 1 << pal->size_reduction) { + skip_bits(&gb_g, 24); + i = 0; + } + } + } + av_free(line); + } + + for (y = 0; y < img->frame->height; y++) { + for (x = 0; x < img->frame->width; x++) { + p = GET_PIXEL(img->frame, x, y); + i = p[2]; + if (i >= pal->frame->width) { + av_log(s->avctx, AV_LOG_ERROR, "invalid palette index %d\n", i); + return AVERROR_INVALIDDATA; + } + pi = GET_PIXEL(pal->frame, i, 0); + AV_COPY32(p, pi); + } + } + + return 0; +} + +static int vp8_lossless_decode_frame(AVCodecContext *avctx, AVFrame *p, + int *got_frame, uint8_t *data_start, + unsigned int data_size, int is_alpha_chunk) +{ + WebPContext *s = avctx->priv_data; + int w, h, ret, i; + + if (!is_alpha_chunk) { + s->lossless = 1; + avctx->pix_fmt = AV_PIX_FMT_ARGB; + } + + ret = init_get_bits(&s->gb, data_start, data_size * 8); + if (ret < 0) + return ret; + + if (!is_alpha_chunk) { + if (get_bits(&s->gb, 8) != 0x2F) { + av_log(avctx, AV_LOG_ERROR, "Invalid WebP Lossless signature\n"); + return AVERROR_INVALIDDATA; + } + + w = get_bits(&s->gb, 14) + 1; + h = get_bits(&s->gb, 14) + 1; + if (s->width && s->width != w) { + av_log(avctx, AV_LOG_WARNING, "Width mismatch. %d != %d\n", + s->width, w); + } + s->width = w; + if (s->height && s->height != h) { + av_log(avctx, AV_LOG_WARNING, "Height mismatch. %d != %d\n", + s->width, w); + } + s->height = h; + ret = av_image_check_size(s->width, s->height, 0, avctx); + if (ret < 0) + return ret; + avcodec_set_dimensions(avctx, s->width, s->height); + + s->has_alpha = get_bits1(&s->gb); + + if (get_bits(&s->gb, 3) != 0x0) { + av_log(avctx, AV_LOG_ERROR, "Invalid WebP Lossless version\n"); + return AVERROR_INVALIDDATA; + } + } else { + if (!s->width || !s->height) + return AVERROR_BUG; + w = s->width; + h = s->height; + } + + /* parse transformations */ + s->nb_transforms = 0; + s->reduced_width = 0; + while (get_bits1(&s->gb)) { + enum TransformType transform = get_bits(&s->gb, 2); + s->transforms[s->nb_transforms++] = transform; + switch (transform) { + case PREDICTOR_TRANSFORM: + ret = parse_transform_predictor(s); + break; + case COLOR_TRANSFORM: + ret = parse_transform_color(s); + break; + case COLOR_INDEXING_TRANSFORM: + ret = parse_transform_color_indexing(s); + break; + } + if (ret < 0) + goto free_and_return; + } + + /* decode primary image */ + s->image[IMAGE_ROLE_ARGB].frame = p; + if (is_alpha_chunk) + s->image[IMAGE_ROLE_ARGB].is_alpha_primary = 1; + ret = decode_entropy_coded_image(s, IMAGE_ROLE_ARGB, w, h); + if (ret < 0) { + av_frame_free(&p); + goto free_and_return; + } + + /* apply transformations */ + for (i = s->nb_transforms - 1; i >= 0; i--) { + switch (s->transforms[i]) { + case PREDICTOR_TRANSFORM: + ret = apply_predictor_transform(s); + break; + case COLOR_TRANSFORM: + ret = apply_color_transform(s); + break; + case SUBTRACT_GREEN: + ret = apply_subtract_green_transform(s); + break; + case COLOR_INDEXING_TRANSFORM: + ret = apply_color_indexing_transform(s); + break; + } + if (ret < 0) { + av_frame_free(&p); + goto free_and_return; + } + } + + *got_frame = 1; + p->pict_type = AV_PICTURE_TYPE_I; + p->key_frame = 1; + ret = data_size; + +free_and_return: + for (i = 0; i < IMAGE_ROLE_NB; i++) + image_ctx_free(&s->image[i]); + + return ret; +} + +static void alpha_inverse_prediction(AVFrame *frame, enum AlphaFilter m) +{ + int x, y, ls; + uint8_t *dec; + + ls = frame->linesize[3]; + + /* filter first row using horizontal filter */ + dec = frame->data[3] + 1; + for (x = 1; x < frame->width; x++, dec++) + *dec += *(dec - 1); + + /* filter first column using vertical filter */ + dec = frame->data[3] + ls; + for (y = 1; y < frame->height; y++, dec += ls) + *dec += *(dec - ls); + + /* filter the rest using the specified filter */ + switch (m) { + case ALPHA_FILTER_HORIZONTAL: + for (y = 1; y < frame->height; y++) { + dec = frame->data[3] + y * ls + 1; + for (x = 1; x < frame->width; x++, dec++) + *dec += *(dec - 1); + } + break; + case ALPHA_FILTER_VERTICAL: + for (y = 1; y < frame->height; y++) { + dec = frame->data[3] + y * ls + 1; + for (x = 1; x < frame->width; x++, dec++) + *dec += *(dec - ls); + } + break; + case ALPHA_FILTER_GRADIENT: + for (y = 1; y < frame->height; y++) { + dec = frame->data[3] + y * ls + 1; + for (x = 1; x < frame->width; x++, dec++) + dec[0] += av_clip_uint8(*(dec - 1) + *(dec - ls) - *(dec - ls - 1)); + } + break; + } +} + +static int vp8_lossy_decode_alpha(AVCodecContext *avctx, AVFrame *p, + uint8_t *data_start, + unsigned int data_size) +{ + WebPContext *s = avctx->priv_data; + int x, y, ret; + + if (s->alpha_compression == ALPHA_COMPRESSION_NONE) { + GetByteContext gb; + + bytestream2_init(&gb, data_start, data_size); + for (y = 0; y < s->height; y++) + bytestream2_get_buffer(&gb, p->data[3] + p->linesize[3] * y, + s->width); + } else if (s->alpha_compression == ALPHA_COMPRESSION_VP8L) { + uint8_t *ap, *pp; + int alpha_got_frame = 0; + + s->alpha_frame = av_frame_alloc(); + if (!s->alpha_frame) + return AVERROR(ENOMEM); + + ret = vp8_lossless_decode_frame(avctx, s->alpha_frame, &alpha_got_frame, + data_start, data_size, 1); + if (ret < 0) { + av_frame_free(&s->alpha_frame); + return ret; + } + if (!alpha_got_frame) { + av_frame_free(&s->alpha_frame); + return AVERROR_INVALIDDATA; + } + + /* copy green component of alpha image to alpha plane of primary image */ + for (y = 0; y < s->height; y++) { + ap = GET_PIXEL(s->alpha_frame, 0, y) + 2; + pp = p->data[3] + p->linesize[3] * y; + for (x = 0; x < s->width; x++) { + *pp = *ap; + pp++; + ap += 4; + } + } + av_frame_free(&s->alpha_frame); + } + + /* apply alpha filtering */ + if (s->alpha_filter) + alpha_inverse_prediction(p, s->alpha_filter); + + return 0; +} + +static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, + int *got_frame, uint8_t *data_start, + unsigned int data_size) +{ + WebPContext *s = avctx->priv_data; + AVPacket pkt; + int ret; + + if (!s->initialized) { + ff_vp8_decode_init(avctx); + s->initialized = 1; + if (s->has_alpha) + avctx->pix_fmt = AV_PIX_FMT_YUVA420P; + } + s->lossless = 0; + + if (data_size > INT_MAX) { + av_log(avctx, AV_LOG_ERROR, "unsupported chunk size\n"); + return AVERROR_PATCHWELCOME; + } + + av_init_packet(&pkt); + pkt.data = data_start; + pkt.size = data_size; + + ret = ff_vp8_decode_frame(avctx, p, got_frame, &pkt); + if (s->has_alpha) { + ret = vp8_lossy_decode_alpha(avctx, p, s->alpha_data, + s->alpha_data_size); + if (ret < 0) + return ret; + } + return ret; +} + +static int webp_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, + AVPacket *avpkt) +{ + AVFrame * const p = data; + WebPContext *s = avctx->priv_data; + GetByteContext gb; + int ret; + uint32_t chunk_type, chunk_size; + int vp8x_flags = 0; + + s->avctx = avctx; + s->width = 0; + s->height = 0; + *got_frame = 0; + s->has_alpha = 0; + bytestream2_init(&gb, avpkt->data, avpkt->size); + + if (bytestream2_get_bytes_left(&gb) < 12) + return AVERROR_INVALIDDATA; + + if (bytestream2_get_le32(&gb) != MKTAG('R', 'I', 'F', 'F')) { + av_log(avctx, AV_LOG_ERROR, "missing RIFF tag\n"); + return AVERROR_INVALIDDATA; + } + + chunk_size = bytestream2_get_le32(&gb); + if (bytestream2_get_bytes_left(&gb) < chunk_size) + return AVERROR_INVALIDDATA; + + if (bytestream2_get_le32(&gb) != MKTAG('W', 'E', 'B', 'P')) { + av_log(avctx, AV_LOG_ERROR, "missing WEBP tag\n"); + return AVERROR_INVALIDDATA; + } + + while (bytestream2_get_bytes_left(&gb) > 0) { + char chunk_str[5] = { 0 }; + + chunk_type = bytestream2_get_le32(&gb); + chunk_size = bytestream2_get_le32(&gb); + if (chunk_size == UINT32_MAX) + return AVERROR_INVALIDDATA; + chunk_size += chunk_size & 1; + + if (bytestream2_get_bytes_left(&gb) < chunk_size) + return AVERROR_INVALIDDATA; + + switch (chunk_type) { + case MKTAG('V', 'P', '8', ' '): + if (!*got_frame) { + ret = vp8_lossy_decode_frame(avctx, p, got_frame, + avpkt->data + bytestream2_tell(&gb), + chunk_size); + if (ret < 0) + return ret; + } + bytestream2_skip(&gb, chunk_size); + break; + case MKTAG('V', 'P', '8', 'L'): + if (!*got_frame) { + ret = vp8_lossless_decode_frame(avctx, p, got_frame, + avpkt->data + bytestream2_tell(&gb), + chunk_size, 0); + if (ret < 0) + return ret; + } + bytestream2_skip(&gb, chunk_size); + break; + case MKTAG('V', 'P', '8', 'X'): + vp8x_flags = bytestream2_get_byte(&gb); + bytestream2_skip(&gb, 3); + s->width = bytestream2_get_le24(&gb) + 1; + s->height = bytestream2_get_le24(&gb) + 1; + ret = av_image_check_size(s->width, s->height, 0, avctx); + if (ret < 0) + return ret; + break; + case MKTAG('A', 'L', 'P', 'H'): { + int alpha_header, filter_m, compression; + + if (!(vp8x_flags & VP8X_FLAG_ALPHA)) { + av_log(avctx, AV_LOG_WARNING, + "ALPHA chunk present, but alpha bit not set in the " + "VP8X header\n"); + } + if (chunk_size == 0) { + av_log(avctx, AV_LOG_ERROR, "invalid ALPHA chunk size\n"); + return AVERROR_INVALIDDATA; + } + alpha_header = bytestream2_get_byte(&gb); + s->alpha_data = avpkt->data + bytestream2_tell(&gb); + s->alpha_data_size = chunk_size - 1; + bytestream2_skip(&gb, s->alpha_data_size); + + filter_m = (alpha_header >> 2) & 0x03; + compression = alpha_header & 0x03; + + if (compression > ALPHA_COMPRESSION_VP8L) { + av_log(avctx, AV_LOG_VERBOSE, + "skipping unsupported ALPHA chunk\n"); + } else { + s->has_alpha = 1; + s->alpha_compression = compression; + s->alpha_filter = filter_m; + } + + break; + } + case MKTAG('I', 'C', 'C', 'P'): + case MKTAG('A', 'N', 'I', 'M'): + case MKTAG('A', 'N', 'M', 'F'): + case MKTAG('E', 'X', 'I', 'F'): + case MKTAG('X', 'M', 'P', ' '): + AV_WL32(chunk_str, chunk_type); + av_log(avctx, AV_LOG_VERBOSE, "skipping unsupported chunk: %s\n", + chunk_str); + bytestream2_skip(&gb, chunk_size); + break; + default: + AV_WL32(chunk_str, chunk_type); + av_log(avctx, AV_LOG_VERBOSE, "skipping unknown chunk: %s\n", + chunk_str); + bytestream2_skip(&gb, chunk_size); + break; + } + } + + if (!*got_frame) { + av_log(avctx, AV_LOG_ERROR, "image data not found\n"); + return AVERROR_INVALIDDATA; + } + + return avpkt->size; +} + +static av_cold int webp_decode_close(AVCodecContext *avctx) +{ + WebPContext *s = avctx->priv_data; + + if (s->initialized) + return ff_vp8_decode_free(avctx); + + return 0; +} + +AVCodec ff_webp_decoder = { + .name = "webp", + .long_name = NULL_IF_CONFIG_SMALL("WebP image"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_WEBP, + .priv_data_size = sizeof(WebPContext), + .decode = webp_decode_frame, + .close = webp_decode_close, + .capabilities = CODEC_CAP_DR1 | CODEC_CAP_FRAME_THREADS, +}; |