Skip to content
Snippets Groups Projects
webp.c 47.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Justin Ruggles's avatar
    Justin Ruggles committed
    /*
     * 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 Libav.
     *
     * Libav 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.
     *
     * Libav 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 Libav; 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
     */
    
    #include "libavutil/imgutils.h"
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    #include "avcodec.h"
    
    #include "bitstream.h"
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    #include "bytestream.h"
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    #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 */
    
        BitstreamContext bc;                /* bitstream reader for main image chunk */
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        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(BitstreamContext *bc, VLC_TYPE (*table)[2])
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    {
        int n, nb_bits;
        unsigned int index;
        int code;
    
    
        index = bitstream_peek(bc, 8);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        index = ff_reverse[index];
        code  = table[index][0];
        n     = table[index][1];
    
        if (n < 0) {
    
            bitstream_skip(bc, 8);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
            nb_bits = -n;
    
    
            index = bitstream_peek(bc, nb_bits);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
            index = (ff_reverse[index] >> (8 - nb_bits)) + code;
            code  = table[index][0];
            n     = table[index][1];
        }
    
        bitstream_skip(bc, n);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
        return code;
    }
    
    
    static int huff_reader_get_symbol(HuffReader *r, BitstreamContext *bc)
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    {
        if (r->simple) {
            if (r->nb_symbols == 1)
                return r->simple_symbols[0];
            else
    
                return r->simple_symbols[bitstream_read_bit(bc)];
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        } else
    
            return webp_get_vlc(bc, r->vlc.table);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    }
    
    static int huff_reader_build_canonical(HuffReader *r, int *code_lengths,
                                           int alphabet_size)
    {
    
        int len = 0, sym, code = 0, ret;
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        int max_code_length = 0;
        uint16_t *codes;
    
    
        /* special-case 1 symbol since the vlc reader cannot handle it */
        for (sym = 0; sym < alphabet_size; sym++) {
            if (code_lengths[sym] > 0) {
                len++;
                code = sym;
                if (len > 1)
                    break;
            }
        }
        if (len == 1) {
            r->nb_symbols = 1;
            r->simple_symbols[0] = code;
            r->simple = 1;
            return 0;
        }
    
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        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 = bitstream_read_bit(&s->bc) + 1;
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
    
        if (bitstream_read_bit(&s->bc))
            hc->simple_symbols[0] = bitstream_read(&s->bc, 8);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        else
    
            hc->simple_symbols[0] = bitstream_read_bit(&s->bc);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
        if (hc->nb_symbols == 2)
    
            hc->simple_symbols[1] = bitstream_read(&s->bc, 8);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
        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 + bitstream_read(&s->bc, 4);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
        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]] = bitstream_read(&s->bc, 3);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
        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 (bitstream_read_bit(&s->bc)) {
            int bits   = 2 + 2 * bitstream_read(&s->bc, 3);
            max_symbol = 2 + bitstream_read(&s->bc, bits);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
            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->bc);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
            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 + bitstream_read(&s->bc, 2);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                    length = prev_code_len;
                    break;
                case 17:
                    /* Code 17 emits a streak of zeros [3..10], i.e.,
                     * 3 + ReadBits(3) times. */
    
                    repeat = 3 + bitstream_read(&s->bc, 3);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                    break;
                case 18:
                    /* Code 18 emits a streak of zeros of length [11..138], i.e.,
                     * 11 + ReadBits(7) times. */
    
                    repeat = 11 + bitstream_read(&s->bc, 7);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                    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 = bitstream_read(&s->bc, 3) + 2;                                   \
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        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 p0 = GET_PIXEL_COMP(img->frame, x, y, 1);
                int p1 = GET_PIXEL_COMP(img->frame, x, y, 2);
                int p  = p0 << 8 | p1;
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                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 = bitstream_read(&s->bc, 8) + 1;
    
    Justin Ruggles's avatar
    Justin Ruggles committed
    
        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;
    
            int g0      = GET_PIXEL_COMP(gimg->frame, group_x, group_y, 1);
            int g1      = GET_PIXEL_COMP(gimg->frame, group_x, group_y, 2);
            group       = g0 << 8 | g1;
    
    Justin Ruggles's avatar
    Justin Ruggles committed
        }
    
        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 (bitstream_read_bit(&s->bc)) {
            img->color_cache_bits = bitstream_read(&s->bc, 4);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
            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 && bitstream_read_bit(&s->bc)) {
    
    Justin Ruggles's avatar
    Justin Ruggles committed
            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 (bitstream_read_bit(&s->bc)) {
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                    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->bc);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
            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->bc);
                p[3] = huff_reader_get_symbol(&hg[HUFF_IDX_BLUE],  &s->bc);
                p[0] = huff_reader_get_symbol(&hg[HUFF_IDX_ALPHA], &s->bc);
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                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 + bitstream_read(&s->bc, extra_bits) + 1;
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                }
    
                prefix_code = huff_reader_get_symbol(&hg[HUFF_IDX_DIST], &s->bc);
    
                if (prefix_code > 39) {
                    av_log(s->avctx, AV_LOG_ERROR,
                           "distance prefix code too large: %d\n", prefix_code);
                    return AVERROR_INVALIDDATA;
                }
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                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 + bitstream_read(&s->bc, extra_bits) + 1;
    
    Justin Ruggles's avatar
    Justin Ruggles committed
                }
    
                /* 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]);
            }