4da9a48d7c
libwebp 1.6.0's decode + demux path (lossy VP8, lossless VP8L, VP8X container, ALPH alpha, and the WebPDemux/WebPAnimDecoder animation API) amalgamated into one stb-style header, built as portable C99 with all SIMD, threads and host file I/O disabled. amalgamate.py regenerates webpdec.h from a pinned, auto-cloned libwebp checkout; tests/run.sh checks decode output bit-exactly against reference libwebp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
21488 lines
794 KiB
C++
21488 lines
794 KiB
C++
/*
|
|
* webpdec.h -- single-file WebP decoder (stb-style).
|
|
*
|
|
* libwebp's decode + demux path -- lossy VP8, lossless VP8L, the VP8X extended
|
|
* container, the ALPH alpha chunk, plus the demux/animation API (WebPDemux,
|
|
* WebPAnimDecoder) -- amalgamated into one file and built as portable C99 with
|
|
* all SIMD, threads and host file I/O disabled. The public API is the standard
|
|
* libwebp one (WebPDecodeRGBA, WebPDemux*, WebPAnimDecoder*).
|
|
*
|
|
* Usage: in exactly ONE translation unit,
|
|
* #define WEBPDEC_IMPLEMENTATION
|
|
* #include "webpdec.h"
|
|
* Everywhere else, just #include "webpdec.h". Decode-only (no encoder/muxer).
|
|
* Link with -lm.
|
|
*
|
|
* Generated by amalgamate.py from libwebp 1.6.0 (see UPSTREAM_COMMIT).
|
|
* BSD-style license (see LICENSE) + PATENTS grant. Copyright Google LLC.
|
|
* DO NOT EDIT BY HAND -- rerun `python3 amalgamate.py`.
|
|
*/
|
|
#ifndef WEBPDEC_H
|
|
#define WEBPDEC_H
|
|
/* >>> src/webp/types.h */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Common types + memory wrappers
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_WEBP_TYPES_H_
|
|
#define WEBP_WEBP_TYPES_H_
|
|
|
|
#include <stddef.h> // IWYU pragma: export for size_t
|
|
|
|
#ifndef _MSC_VER
|
|
#include <inttypes.h> // IWYU pragma: export
|
|
#if defined(__cplusplus) || !defined(__STRICT_ANSI__) || \
|
|
(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
|
|
#define WEBP_INLINE inline
|
|
#else
|
|
#define WEBP_INLINE
|
|
#endif
|
|
#else
|
|
typedef signed char int8_t;
|
|
typedef unsigned char uint8_t;
|
|
typedef signed short int16_t;
|
|
typedef unsigned short uint16_t;
|
|
typedef signed int int32_t;
|
|
typedef unsigned int uint32_t;
|
|
typedef unsigned long long int uint64_t;
|
|
typedef long long int int64_t;
|
|
#define WEBP_INLINE __forceinline
|
|
#endif /* _MSC_VER */
|
|
|
|
#ifndef WEBP_NODISCARD
|
|
#if defined(WEBP_ENABLE_NODISCARD) && WEBP_ENABLE_NODISCARD
|
|
#if (defined(__cplusplus) && __cplusplus >= 201703L) || \
|
|
(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L)
|
|
#define WEBP_NODISCARD [[nodiscard]]
|
|
#else
|
|
// gcc's __attribute__((warn_unused_result)) does not work for enums.
|
|
#if defined(__clang__) && defined(__has_attribute)
|
|
#if __has_attribute(warn_unused_result)
|
|
#define WEBP_NODISCARD __attribute__((warn_unused_result))
|
|
#else
|
|
#define WEBP_NODISCARD
|
|
#endif /* __has_attribute(warn_unused_result) */
|
|
#else
|
|
#define WEBP_NODISCARD
|
|
#endif /* defined(__clang__) && defined(__has_attribute) */
|
|
#endif /* (defined(__cplusplus) && __cplusplus >= 201700L) || \
|
|
(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) */
|
|
#else
|
|
#define WEBP_NODISCARD
|
|
#endif /* defined(WEBP_ENABLE_NODISCARD) && WEBP_ENABLE_NODISCARD */
|
|
#endif /* WEBP_NODISCARD */
|
|
|
|
#ifndef WEBP_EXTERN
|
|
// This explicitly marks library functions and allows for changing the
|
|
// signature for e.g., Windows DLL builds.
|
|
#if defined(_WIN32) && defined(WEBP_DLL)
|
|
#define WEBP_EXTERN __declspec(dllexport)
|
|
#elif defined(__GNUC__) && __GNUC__ >= 4
|
|
#define WEBP_EXTERN extern __attribute__((visibility("default")))
|
|
#else
|
|
#define WEBP_EXTERN extern
|
|
#endif /* defined(_WIN32) && defined(WEBP_DLL) */
|
|
#endif /* WEBP_EXTERN */
|
|
|
|
#ifndef WEBP_FALLTHROUGH
|
|
#if (defined(__cplusplus) && __cplusplus >= 201703L) || \
|
|
(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L)
|
|
#define WEBP_FALLTHROUGH [[fallthrough]]
|
|
#else
|
|
#define WEBP_FALLTHROUGH
|
|
#endif
|
|
#endif /* WEBP_FALLTHROUGH */
|
|
|
|
// Macro to check ABI compatibility (same major revision number)
|
|
#define WEBP_ABI_IS_INCOMPATIBLE(a, b) (((a) >> 8) != ((b) >> 8))
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// Allocates 'size' bytes of memory. Returns NULL upon error. Memory
|
|
// must be deallocated by calling WebPFree(). This function is made available
|
|
// by the core 'libwebp' library.
|
|
WEBP_NODISCARD WEBP_EXTERN void* WebPMalloc(size_t size);
|
|
|
|
// Releases memory returned by the WebPDecode*() functions (from decode.h).
|
|
WEBP_EXTERN void WebPFree(void* ptr);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#include <string.h> // For memcpy and friends
|
|
|
|
#ifdef WEBP_SUPPORT_FBOUNDS_SAFETY
|
|
|
|
// As explained in src/utils/bounds_safety.h, the below macros are defined
|
|
// somewhat delicately to handle a three-state setup:
|
|
//
|
|
// State 1: No -fbounds-safety enabled anywhere, all macros below should act
|
|
// as-if -fbounds-safety doesn't exist.
|
|
// State 2: A file with -fbounds-safety enabled calling into files with or
|
|
// without -fbounds-safety.
|
|
// State 3: A file without -fbounds-safety enabled calling into files with
|
|
// -fbounds-safety. ABI breaking annotations must stay to force a
|
|
// build failure and force us to use non-ABI breaking annotations.
|
|
//
|
|
// Currently, we only allow non-ABI changing annotations in this file to ensure
|
|
// we don't accidentally change the ABI for public functions.
|
|
|
|
#include <ptrcheck.h>
|
|
|
|
#define WEBP_ASSUME_UNSAFE_INDEXABLE_ABI \
|
|
__ptrcheck_abi_assume_unsafe_indexable()
|
|
|
|
#define WEBP_COUNTED_BY(x) __counted_by(x)
|
|
#define WEBP_COUNTED_BY_OR_NULL(x) __counted_by_or_null(x)
|
|
#define WEBP_SIZED_BY(x) __sized_by(x)
|
|
#define WEBP_SIZED_BY_OR_NULL(x) __sized_by_or_null(x)
|
|
#define WEBP_ENDED_BY(x) __ended_by(x)
|
|
|
|
#define WEBP_UNSAFE_INDEXABLE __unsafe_indexable
|
|
#define WEBP_SINGLE __single
|
|
|
|
#define WEBP_UNSAFE_FORGE_SINGLE(typ, ptr) __unsafe_forge_single(typ, ptr)
|
|
|
|
#define WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(typ, ptr, size) \
|
|
__unsafe_forge_bidi_indexable(typ, ptr, size)
|
|
|
|
// Provide memcpy/memset/memmove wrappers to make migration easier.
|
|
#define WEBP_UNSAFE_MEMCPY(dst, src, size) \
|
|
do { \
|
|
memcpy(WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(uint8_t*, dst, size), \
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(uint8_t*, src, size), size); \
|
|
} while (0)
|
|
|
|
#define WEBP_UNSAFE_MEMSET(dst, c, size) \
|
|
do { \
|
|
memset(WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(uint8_t*, dst, size), c, size); \
|
|
} while (0)
|
|
|
|
#define WEBP_UNSAFE_MEMMOVE(dst, src, size) \
|
|
do { \
|
|
memmove(WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(uint8_t*, dst, size), \
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(uint8_t*, src, size), size); \
|
|
} while (0)
|
|
|
|
#define WEBP_UNSAFE_MEMCMP(s1, s2, size) \
|
|
memcmp(WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(uint8_t*, s1, size), \
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(uint8_t*, s2, size), size)
|
|
|
|
#else // WEBP_SUPPORT_FBOUNDS_SAFETY
|
|
|
|
#define WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#define WEBP_COUNTED_BY(x)
|
|
#define WEBP_COUNTED_BY_OR_NULL(x)
|
|
#define WEBP_SIZED_BY(x)
|
|
#define WEBP_SIZED_BY_OR_NULL(x)
|
|
#define WEBP_ENDED_BY(x)
|
|
|
|
#define WEBP_UNSAFE_INDEXABLE
|
|
#define WEBP_SINGLE
|
|
|
|
#define WEBP_UNSAFE_MEMCPY(dst, src, size) memcpy(dst, src, size)
|
|
#define WEBP_UNSAFE_MEMSET(dst, c, size) memset(dst, c, size)
|
|
#define WEBP_UNSAFE_MEMMOVE(dst, src, size) memmove(dst, src, size)
|
|
#define WEBP_UNSAFE_MEMCMP(s1, s2, size) memcmp(s1, s2, size)
|
|
|
|
#define WEBP_UNSAFE_FORGE_SINGLE(typ, ptr) ((typ)(ptr))
|
|
#define WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(typ, ptr, size) ((typ)(ptr))
|
|
|
|
#endif // WEBP_SUPPORT_FBOUNDS_SAFETY
|
|
|
|
// This macro exists to indicate intentionality with self-assignments and
|
|
// silence -Wself-assign compiler warnings.
|
|
#define WEBP_SELF_ASSIGN(x) x = x
|
|
|
|
#endif // WEBP_WEBP_TYPES_H_
|
|
/* >>> src/webp/decode.h */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Main decoding functions for WebP images.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_WEBP_DECODE_H_
|
|
#define WEBP_WEBP_DECODE_H_
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define WEBP_DECODER_ABI_VERSION 0x0210 // MAJOR(8b) + MINOR(8b)
|
|
|
|
// Note: forward declaring enumerations is not allowed in (strict) C and C++,
|
|
// the types are left here for reference.
|
|
// typedef enum VP8StatusCode VP8StatusCode;
|
|
// typedef enum WEBP_CSP_MODE WEBP_CSP_MODE;
|
|
typedef struct WebPRGBABuffer WebPRGBABuffer;
|
|
typedef struct WebPYUVABuffer WebPYUVABuffer;
|
|
typedef struct WebPDecBuffer WebPDecBuffer;
|
|
typedef struct WebPIDecoder WebPIDecoder;
|
|
typedef struct WebPBitstreamFeatures WebPBitstreamFeatures;
|
|
typedef struct WebPDecoderOptions WebPDecoderOptions;
|
|
typedef struct WebPDecoderConfig WebPDecoderConfig;
|
|
|
|
// Return the decoder's version number, packed in hexadecimal using 8bits for
|
|
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
|
WEBP_EXTERN int WebPGetDecoderVersion(void);
|
|
|
|
// Retrieve basic header information: width, height.
|
|
// This function will also validate the header, returning true on success,
|
|
// false otherwise. '*width' and '*height' are only valid on successful return.
|
|
// Pointers 'width' and 'height' can be passed NULL if deemed irrelevant.
|
|
// Note: The following chunk sequences (before the raw VP8/VP8L data) are
|
|
// considered valid by this function:
|
|
// RIFF + VP8(L)
|
|
// RIFF + VP8X + (optional chunks) + VP8(L)
|
|
// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose.
|
|
// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPGetInfo(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
int* width, int* height);
|
|
|
|
// Decodes WebP images pointed to by 'data' and returns RGBA samples, along
|
|
// with the dimensions in *width and *height. The ordering of samples in
|
|
// memory is R, G, B, A, R, G, B, A... in scan order (endian-independent).
|
|
// The returned pointer should be deleted calling WebPFree().
|
|
// Returns NULL in case of error.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBA(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
int* width, int* height);
|
|
|
|
// Same as WebPDecodeRGBA, but returning A, R, G, B, A, R, G, B... ordered data.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeARGB(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
int* width, int* height);
|
|
|
|
// Same as WebPDecodeRGBA, but returning B, G, R, A, B, G, R, A... ordered data.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRA(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
int* width, int* height);
|
|
|
|
// Same as WebPDecodeRGBA, but returning R, G, B, R, G, B... ordered data.
|
|
// If the bitstream contains transparency, it is ignored.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGB(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
int* width, int* height);
|
|
|
|
// Same as WebPDecodeRGB, but returning B, G, R, B, G, R... ordered data.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGR(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
int* width, int* height);
|
|
|
|
// Decode WebP images pointed to by 'data' to Y'UV format(*). The pointer
|
|
// returned is the Y samples buffer. Upon return, *u and *v will point to
|
|
// the U and V chroma data. These U and V buffers need NOT be passed to
|
|
// WebPFree(), unlike the returned Y luma one. The dimension of the U and V
|
|
// planes are both (*width + 1) / 2 and (*height + 1) / 2.
|
|
// Upon return, the Y buffer has a stride returned as '*stride', while U and V
|
|
// have a common stride returned as '*uv_stride'.
|
|
// 'width' and 'height' may be NULL, the other pointers must not be.
|
|
// Returns NULL in case of error.
|
|
// (*) Also named Y'CbCr. See: https://en.wikipedia.org/wiki/YCbCr
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeYUV(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
int* width, int* height, uint8_t** u, uint8_t** v, int* stride,
|
|
int* uv_stride);
|
|
|
|
// These five functions are variants of the above ones, that decode the image
|
|
// directly into a pre-allocated buffer 'output_buffer'. The maximum storage
|
|
// available in this buffer is indicated by 'output_buffer_size'. If this
|
|
// storage is not sufficient (or an error occurred), NULL is returned.
|
|
// Otherwise, output_buffer is returned, for convenience.
|
|
// The parameter 'output_stride' specifies the distance (in bytes)
|
|
// between scanlines. Hence, output_buffer_size is expected to be at least
|
|
// output_stride x picture-height. A negative stride can be used to flip
|
|
// the image vertically. In this case, the 'output_buffer' should point to
|
|
// the start of the last row of the allocated buffer, and 'output_buffer_size'
|
|
// should be at least abs(output_stride) x picture-height.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBAInto(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(output_buffer_size) output_buffer,
|
|
size_t output_buffer_size, int output_stride);
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeARGBInto(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(output_buffer_size) output_buffer,
|
|
size_t output_buffer_size, int output_stride);
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRAInto(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(output_buffer_size) output_buffer,
|
|
size_t output_buffer_size, int output_stride);
|
|
|
|
// RGB and BGR variants. Here too the transparency information, if present,
|
|
// will be dropped and ignored.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBInto(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(output_buffer_size) output_buffer,
|
|
size_t output_buffer_size, int output_stride);
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRInto(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(output_buffer_size) output_buffer,
|
|
size_t output_buffer_size, int output_stride);
|
|
|
|
// WebPDecodeYUVInto() is a variant of WebPDecodeYUV() that operates directly
|
|
// into pre-allocated luma/chroma plane buffers. This function requires the
|
|
// strides to be passed: one for the luma plane and one for each of the
|
|
// chroma ones. The size of each plane buffer is passed as 'luma_size',
|
|
// 'u_size' and 'v_size' respectively.
|
|
// Pointer to the luma plane ('*luma') is returned or NULL if an error occurred
|
|
// during decoding (or because some buffers were found to be too small).
|
|
// Strides can be negative to flip the planes vertically. In this case, the
|
|
// pointers ('luma', 'u', 'v') should point to the start of the last row of
|
|
// the respective buffers. The sizes should be at least abs(luma_stride) x
|
|
// height for the luma plane, and abs(u_stride) x ((height + 1) / 2) and
|
|
// abs(v_stride) x ((height + 1) / 2) for the chroma planes.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeYUVInto(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(luma_size) luma, size_t luma_size, int luma_stride,
|
|
uint8_t* WEBP_COUNTED_BY(u_size) u, size_t u_size, int u_stride,
|
|
uint8_t* WEBP_COUNTED_BY(v_size) v, size_t v_size, int v_stride);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Output colorspaces and buffer
|
|
|
|
// Colorspaces
|
|
// Note: the naming describes the byte-ordering of packed samples in memory.
|
|
// For instance, MODE_BGRA relates to samples ordered as B,G,R,A,B,G,R,A,...
|
|
// Non-capital names (e.g.:MODE_Argb) relates to pre-multiplied RGB channels.
|
|
// RGBA-4444 and RGB-565 colorspaces are represented by following byte-order:
|
|
// RGBA-4444: [r3 r2 r1 r0 g3 g2 g1 g0], [b3 b2 b1 b0 a3 a2 a1 a0], ...
|
|
// RGB-565: [r4 r3 r2 r1 r0 g5 g4 g3], [g2 g1 g0 b4 b3 b2 b1 b0], ...
|
|
// In the case WEBP_SWAP_16BITS_CSP is defined, the bytes are swapped for
|
|
// these two modes:
|
|
// RGBA-4444: [b3 b2 b1 b0 a3 a2 a1 a0], [r3 r2 r1 r0 g3 g2 g1 g0], ...
|
|
// RGB-565: [g2 g1 g0 b4 b3 b2 b1 b0], [r4 r3 r2 r1 r0 g5 g4 g3], ...
|
|
|
|
typedef enum WEBP_CSP_MODE {
|
|
MODE_RGB = 0,
|
|
MODE_RGBA = 1,
|
|
MODE_BGR = 2,
|
|
MODE_BGRA = 3,
|
|
MODE_ARGB = 4,
|
|
MODE_RGBA_4444 = 5,
|
|
MODE_RGB_565 = 6,
|
|
// RGB-premultiplied transparent modes (alpha value is preserved)
|
|
MODE_rgbA = 7,
|
|
MODE_bgrA = 8,
|
|
MODE_Argb = 9,
|
|
MODE_rgbA_4444 = 10,
|
|
// YUV modes must come after RGB ones.
|
|
MODE_YUV = 11,
|
|
MODE_YUVA = 12, // yuv 4:2:0
|
|
MODE_LAST = 13
|
|
} WEBP_CSP_MODE;
|
|
|
|
// Some useful macros:
|
|
static WEBP_INLINE int WebPIsPremultipliedMode(WEBP_CSP_MODE mode) {
|
|
return (mode == MODE_rgbA || mode == MODE_bgrA || mode == MODE_Argb ||
|
|
mode == MODE_rgbA_4444);
|
|
}
|
|
|
|
static WEBP_INLINE int WebPIsAlphaMode(WEBP_CSP_MODE mode) {
|
|
return (mode == MODE_RGBA || mode == MODE_BGRA || mode == MODE_ARGB ||
|
|
mode == MODE_RGBA_4444 || mode == MODE_YUVA ||
|
|
WebPIsPremultipliedMode(mode));
|
|
}
|
|
|
|
static WEBP_INLINE int WebPIsRGBMode(WEBP_CSP_MODE mode) {
|
|
return (mode < MODE_YUV);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPDecBuffer: Generic structure for describing the output sample buffer.
|
|
|
|
struct WebPRGBABuffer { // view as RGBA
|
|
uint8_t* rgba; // pointer to RGBA samples
|
|
int stride; // stride in bytes from one scanline to the next.
|
|
size_t size; // total size of the *rgba buffer.
|
|
};
|
|
|
|
struct WebPYUVABuffer { // view as YUVA
|
|
uint8_t *y, *u, *v, *a; // pointer to luma, chroma U/V, alpha samples
|
|
int y_stride; // luma stride
|
|
int u_stride, v_stride; // chroma strides
|
|
int a_stride; // alpha stride
|
|
size_t y_size; // luma plane size
|
|
size_t u_size, v_size; // chroma planes size
|
|
size_t a_size; // alpha-plane size
|
|
};
|
|
|
|
// Output buffer
|
|
struct WebPDecBuffer {
|
|
WEBP_CSP_MODE colorspace; // Colorspace.
|
|
int width, height; // Dimensions.
|
|
int is_external_memory; // If non-zero, 'private_memory' pointer is not
|
|
// used. If value is '2' or more, the external
|
|
// memory is considered 'slow' and multiple
|
|
// read/write will be avoided.
|
|
union {
|
|
WebPRGBABuffer RGBA;
|
|
WebPYUVABuffer YUVA;
|
|
} u; // Nameless union of buffer parameters.
|
|
uint32_t pad[4]; // padding for later use
|
|
|
|
uint8_t* private_memory; // Internally allocated memory (only when
|
|
// is_external_memory is 0). Should not be used
|
|
// externally, but accessed via the buffer union.
|
|
};
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPInitDecBufferInternal(WebPDecBuffer*, int);
|
|
|
|
// Initialize the structure as empty. Must be called before any other use.
|
|
// Returns false in case of version mismatch
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPInitDecBuffer(WebPDecBuffer* buffer) {
|
|
return WebPInitDecBufferInternal(buffer, WEBP_DECODER_ABI_VERSION);
|
|
}
|
|
|
|
// Free any memory associated with the buffer. Must always be called last.
|
|
// Note: doesn't free the 'buffer' structure itself.
|
|
WEBP_EXTERN void WebPFreeDecBuffer(WebPDecBuffer* buffer);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Enumeration of the status codes
|
|
|
|
typedef enum WEBP_NODISCARD VP8StatusCode {
|
|
VP8_STATUS_OK = 0,
|
|
VP8_STATUS_OUT_OF_MEMORY,
|
|
VP8_STATUS_INVALID_PARAM,
|
|
VP8_STATUS_BITSTREAM_ERROR,
|
|
VP8_STATUS_UNSUPPORTED_FEATURE,
|
|
VP8_STATUS_SUSPENDED,
|
|
VP8_STATUS_USER_ABORT,
|
|
VP8_STATUS_NOT_ENOUGH_DATA
|
|
} VP8StatusCode;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Incremental decoding
|
|
//
|
|
// This API allows streamlined decoding of partial data.
|
|
// Picture can be incrementally decoded as data become available thanks to the
|
|
// WebPIDecoder object. This object can be left in a SUSPENDED state if the
|
|
// picture is only partially decoded, pending additional input.
|
|
// Code example:
|
|
/*
|
|
WebPInitDecBuffer(&output_buffer);
|
|
output_buffer.colorspace = mode;
|
|
...
|
|
WebPIDecoder* idec = WebPINewDecoder(&output_buffer);
|
|
while (additional_data_is_available) {
|
|
// ... (get additional data in some new_data[] buffer)
|
|
status = WebPIAppend(idec, new_data, new_data_size);
|
|
if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) {
|
|
break; // an error occurred.
|
|
}
|
|
|
|
// The above call decodes the current available buffer.
|
|
// Part of the image can now be refreshed by calling
|
|
// WebPIDecGetRGB()/WebPIDecGetYUVA() etc.
|
|
}
|
|
WebPIDelete(idec);
|
|
*/
|
|
|
|
// Creates a new incremental decoder with the supplied buffer parameter.
|
|
// This output_buffer can be passed NULL, in which case a default output buffer
|
|
// is used (with MODE_RGB). Otherwise, an internal reference to 'output_buffer'
|
|
// is kept, which means that the lifespan of 'output_buffer' must be larger than
|
|
// that of the returned WebPIDecoder object.
|
|
// The supplied 'output_buffer' content MUST NOT be changed between calls to
|
|
// WebPIAppend() or WebPIUpdate() unless 'output_buffer.is_external_memory' is
|
|
// not set to 0. In such a case, it is allowed to modify the pointers, size and
|
|
// stride of output_buffer.u.RGBA or output_buffer.u.YUVA, provided they remain
|
|
// within valid bounds.
|
|
// All other fields of WebPDecBuffer MUST remain constant between calls.
|
|
// Returns NULL if the allocation failed.
|
|
WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewDecoder(
|
|
WebPDecBuffer* output_buffer);
|
|
|
|
// This function allocates and initializes an incremental-decoder object, which
|
|
// will output the RGB/A samples specified by 'csp' into a preallocated
|
|
// buffer 'output_buffer'. The size of this buffer is at least
|
|
// 'output_buffer_size' and the stride (distance in bytes between two scanlines)
|
|
// is specified by 'output_stride'.
|
|
// Additionally, output_buffer can be passed NULL in which case the output
|
|
// buffer will be allocated automatically when the decoding starts. The
|
|
// colorspace 'csp' is taken into account for allocating this buffer. All other
|
|
// parameters are ignored.
|
|
// Returns NULL if the allocation failed, or if some parameters are invalid.
|
|
WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewRGB(
|
|
WEBP_CSP_MODE csp,
|
|
uint8_t* WEBP_COUNTED_BY(output_buffer_size) output_buffer,
|
|
size_t output_buffer_size, int output_stride);
|
|
|
|
// This function allocates and initializes an incremental-decoder object, which
|
|
// will output the raw luma/chroma samples into a preallocated planes if
|
|
// supplied. The luma plane is specified by its pointer 'luma', its size
|
|
// 'luma_size' and its stride 'luma_stride'. Similarly, the chroma-u plane
|
|
// is specified by the 'u', 'u_size' and 'u_stride' parameters, and the chroma-v
|
|
// plane by 'v' and 'v_size'. And same for the alpha-plane. The 'a' pointer
|
|
// can be pass NULL in case one is not interested in the transparency plane.
|
|
// Conversely, 'luma' can be passed NULL if no preallocated planes are supplied.
|
|
// In this case, the output buffer will be automatically allocated (using
|
|
// MODE_YUVA) when decoding starts. All parameters are then ignored.
|
|
// Returns NULL if the allocation failed or if a parameter is invalid.
|
|
WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewYUVA(
|
|
uint8_t* WEBP_COUNTED_BY(luma_size) luma, size_t luma_size, int luma_stride,
|
|
uint8_t* WEBP_COUNTED_BY(u_size) u, size_t u_size, int u_stride,
|
|
uint8_t* WEBP_COUNTED_BY(v_size) v, size_t v_size, int v_stride,
|
|
uint8_t* WEBP_COUNTED_BY(a_size) a, size_t a_size, int a_stride);
|
|
|
|
// Deprecated version of the above, without the alpha plane.
|
|
// Kept for backward compatibility.
|
|
WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewYUV(
|
|
uint8_t* WEBP_COUNTED_BY(luma_size) luma, size_t luma_size, int luma_stride,
|
|
uint8_t* WEBP_COUNTED_BY(u_size) u, size_t u_size, int u_stride,
|
|
uint8_t* WEBP_COUNTED_BY(v_size) v, size_t v_size, int v_stride);
|
|
|
|
// Deletes the WebPIDecoder object and associated memory. Must always be called
|
|
// if WebPINewDecoder, WebPINewRGB or WebPINewYUV succeeded.
|
|
WEBP_EXTERN void WebPIDelete(WebPIDecoder* idec);
|
|
|
|
// Copies and decodes the next available data. Returns VP8_STATUS_OK when
|
|
// the image is successfully decoded. Returns VP8_STATUS_SUSPENDED when more
|
|
// data is expected. Returns error in other cases.
|
|
WEBP_EXTERN VP8StatusCode WebPIAppend(WebPIDecoder* idec,
|
|
const uint8_t* WEBP_COUNTED_BY(data_size)
|
|
data,
|
|
size_t data_size);
|
|
|
|
// A variant of the above function to be used when data buffer contains
|
|
// partial data from the beginning. In this case data buffer is not copied
|
|
// to the internal memory.
|
|
// Note that the value of the 'data' pointer can change between calls to
|
|
// WebPIUpdate, for instance when the data buffer is resized to fit larger data.
|
|
WEBP_EXTERN VP8StatusCode WebPIUpdate(WebPIDecoder* idec,
|
|
const uint8_t* WEBP_COUNTED_BY(data_size)
|
|
data,
|
|
size_t data_size);
|
|
|
|
// Returns the RGB/A image decoded so far. Returns NULL if output params
|
|
// are not initialized yet. The RGB/A output type corresponds to the colorspace
|
|
// specified during call to WebPINewDecoder() or WebPINewRGB().
|
|
// *last_y is the index of last decoded row in raster scan order. Some pointers
|
|
// (*last_y, *width etc.) can be NULL if corresponding information is not
|
|
// needed. The values in these pointers are only valid on successful (non-NULL)
|
|
// return.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPIDecGetRGB(const WebPIDecoder* idec,
|
|
int* last_y, int* width,
|
|
int* height, int* stride);
|
|
|
|
// Same as above function to get a YUVA image. Returns pointer to the luma
|
|
// plane or NULL in case of error. If there is no alpha information
|
|
// the alpha pointer '*a' will be returned NULL.
|
|
WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPIDecGetYUVA(const WebPIDecoder* idec,
|
|
int* last_y, uint8_t** u,
|
|
uint8_t** v, uint8_t** a,
|
|
int* width, int* height,
|
|
int* stride, int* uv_stride,
|
|
int* a_stride);
|
|
|
|
// Deprecated alpha-less version of WebPIDecGetYUVA(): it will ignore the
|
|
// alpha information (if present). Kept for backward compatibility.
|
|
WEBP_NODISCARD static WEBP_INLINE uint8_t* WebPIDecGetYUV(
|
|
const WebPIDecoder* idec, int* last_y, uint8_t** u, uint8_t** v, int* width,
|
|
int* height, int* stride, int* uv_stride) {
|
|
return WebPIDecGetYUVA(idec, last_y, u, v, NULL, width, height, stride,
|
|
uv_stride, NULL);
|
|
}
|
|
|
|
// Generic call to retrieve information about the displayable area.
|
|
// If non NULL, the left/right/width/height pointers are filled with the visible
|
|
// rectangular area so far.
|
|
// Returns NULL in case the incremental decoder object is in an invalid state.
|
|
// Otherwise returns the pointer to the internal representation. This structure
|
|
// is read-only, tied to WebPIDecoder's lifespan and should not be modified.
|
|
WEBP_NODISCARD WEBP_EXTERN const WebPDecBuffer* WebPIDecodedArea(
|
|
const WebPIDecoder* idec, int* left, int* top, int* width, int* height);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Advanced decoding parametrization
|
|
//
|
|
// Code sample for using the advanced decoding API
|
|
/*
|
|
// A) Init a configuration object
|
|
WebPDecoderConfig config;
|
|
CHECK(WebPInitDecoderConfig(&config));
|
|
|
|
// B) optional: retrieve the bitstream's features.
|
|
CHECK(WebPGetFeatures(data, data_size, &config.input) == VP8_STATUS_OK);
|
|
|
|
// C) Adjust 'config', if needed
|
|
config.options.no_fancy_upsampling = 1;
|
|
config.output.colorspace = MODE_BGRA;
|
|
// etc.
|
|
|
|
// Note that you can also make config.output point to an externally
|
|
// supplied memory buffer, provided it's big enough to store the decoded
|
|
// picture. Otherwise, config.output will just be used to allocate memory
|
|
// and store the decoded picture.
|
|
|
|
// D) Decode!
|
|
CHECK(WebPDecode(data, data_size, &config) == VP8_STATUS_OK);
|
|
|
|
// E) Decoded image is now in config.output (and config.output.u.RGBA)
|
|
|
|
// F) Reclaim memory allocated in config's object. It's safe to call
|
|
// this function even if the memory is external and wasn't allocated
|
|
// by WebPDecode().
|
|
WebPFreeDecBuffer(&config.output);
|
|
*/
|
|
|
|
// Features gathered from the bitstream
|
|
struct WebPBitstreamFeatures {
|
|
int width; // Width in pixels, as read from the bitstream.
|
|
int height; // Height in pixels, as read from the bitstream.
|
|
int has_alpha; // True if the bitstream contains an alpha channel.
|
|
int has_animation; // True if the bitstream is an animation.
|
|
int format; // 0 = undefined (/mixed), 1 = lossy, 2 = lossless
|
|
|
|
uint32_t pad[5]; // padding for later use
|
|
};
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_EXTERN VP8StatusCode
|
|
WebPGetFeaturesInternal(const uint8_t* WEBP_COUNTED_BY(data_size),
|
|
size_t data_size, WebPBitstreamFeatures*, int);
|
|
|
|
// Retrieve features from the bitstream. The *features structure is filled
|
|
// with information gathered from the bitstream.
|
|
// Returns VP8_STATUS_OK when the features are successfully retrieved. Returns
|
|
// VP8_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the
|
|
// features from headers. Returns error in other cases.
|
|
// Note: The following chunk sequences (before the raw VP8/VP8L data) are
|
|
// considered valid by this function:
|
|
// RIFF + VP8(L)
|
|
// RIFF + VP8X + (optional chunks) + VP8(L)
|
|
// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose.
|
|
// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose.
|
|
static WEBP_INLINE VP8StatusCode
|
|
WebPGetFeatures(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, WebPBitstreamFeatures* features) {
|
|
return WebPGetFeaturesInternal(data, data_size, features,
|
|
WEBP_DECODER_ABI_VERSION);
|
|
}
|
|
|
|
// Decoding options
|
|
struct WebPDecoderOptions {
|
|
int bypass_filtering; // if true, skip the in-loop filtering
|
|
int no_fancy_upsampling; // if true, use faster pointwise upsampler
|
|
int use_cropping; // if true, cropping is applied _first_
|
|
int crop_left, crop_top; // top-left position for cropping.
|
|
// Will be snapped to even values.
|
|
int crop_width, crop_height; // dimension of the cropping area
|
|
int use_scaling; // if true, scaling is applied _afterward_
|
|
int scaled_width, scaled_height; // final resolution. if one is 0, it is
|
|
// guessed from the other one to keep the
|
|
// original ratio.
|
|
int use_threads; // if true, use multi-threaded decoding
|
|
int dithering_strength; // dithering strength (0=Off, 100=full)
|
|
int flip; // if true, flip output vertically
|
|
int alpha_dithering_strength; // alpha dithering strength in [0..100]
|
|
|
|
uint32_t pad[5]; // padding for later use
|
|
};
|
|
|
|
// Main object storing the configuration for advanced decoding.
|
|
struct WebPDecoderConfig {
|
|
WebPBitstreamFeatures input; // Immutable bitstream features (optional)
|
|
WebPDecBuffer output; // Output buffer (can point to external mem)
|
|
WebPDecoderOptions options; // Decoding options
|
|
};
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPInitDecoderConfigInternal(WebPDecoderConfig*,
|
|
int);
|
|
|
|
// Initialize the configuration as empty. This function must always be
|
|
// called first, unless WebPGetFeatures() is to be called.
|
|
// Returns false in case of mismatched version.
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPInitDecoderConfig(
|
|
WebPDecoderConfig* config) {
|
|
return WebPInitDecoderConfigInternal(config, WEBP_DECODER_ABI_VERSION);
|
|
}
|
|
|
|
// Returns true if 'config' is non-NULL and all configuration parameters are
|
|
// within their valid ranges.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPValidateDecoderConfig(
|
|
const WebPDecoderConfig* config);
|
|
|
|
// Instantiate a new incremental decoder object with the requested
|
|
// configuration. The bitstream can be passed using 'data' and 'data_size'
|
|
// parameter, in which case the features will be parsed and stored into
|
|
// config->input. Otherwise, 'data' can be NULL and no parsing will occur.
|
|
// Note that 'config' can be NULL too, in which case a default configuration
|
|
// is used. If 'config' is not NULL, it must outlive the WebPIDecoder object
|
|
// as some references to its fields will be used. No internal copy of 'config'
|
|
// is made.
|
|
// The return WebPIDecoder object must always be deleted calling WebPIDelete().
|
|
// Returns NULL in case of error (and config->status will then reflect
|
|
// the error condition, if available).
|
|
WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPIDecode(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
WebPDecoderConfig* config);
|
|
|
|
// Non-incremental version. This version decodes the full data at once, taking
|
|
// 'config' into account. Returns decoding status (which should be VP8_STATUS_OK
|
|
// if the decoding was successful). Note that 'config' cannot be NULL.
|
|
WEBP_EXTERN VP8StatusCode WebPDecode(const uint8_t* WEBP_COUNTED_BY(data_size)
|
|
data,
|
|
size_t data_size,
|
|
WebPDecoderConfig* config);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_WEBP_DECODE_H_
|
|
/* >>> src/webp/demux.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Demux API.
|
|
// Enables extraction of image and extended format data from WebP files.
|
|
|
|
// Code Example: Demuxing WebP data to extract all the frames, ICC profile
|
|
// and EXIF/XMP metadata.
|
|
/*
|
|
WebPDemuxer* demux = WebPDemux(&webp_data);
|
|
|
|
uint32_t width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
|
|
uint32_t height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
|
|
// ... (Get information about the features present in the WebP file).
|
|
uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);
|
|
|
|
// ... (Iterate over all frames).
|
|
WebPIterator iter;
|
|
if (WebPDemuxGetFrame(demux, 1, &iter)) {
|
|
do {
|
|
// ... (Consume 'iter'; e.g. Decode 'iter.fragment' with WebPDecode(),
|
|
// ... and get other frame properties like width, height, offsets etc.
|
|
// ... see 'struct WebPIterator' below for more info).
|
|
} while (WebPDemuxNextFrame(&iter));
|
|
WebPDemuxReleaseIterator(&iter);
|
|
}
|
|
|
|
// ... (Extract metadata).
|
|
WebPChunkIterator chunk_iter;
|
|
if (flags & ICCP_FLAG) WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter);
|
|
// ... (Consume the ICC profile in 'chunk_iter.chunk').
|
|
WebPDemuxReleaseChunkIterator(&chunk_iter);
|
|
if (flags & EXIF_FLAG) WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter);
|
|
// ... (Consume the EXIF metadata in 'chunk_iter.chunk').
|
|
WebPDemuxReleaseChunkIterator(&chunk_iter);
|
|
if (flags & XMP_FLAG) WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter);
|
|
// ... (Consume the XMP metadata in 'chunk_iter.chunk').
|
|
WebPDemuxReleaseChunkIterator(&chunk_iter);
|
|
WebPDemuxDelete(demux);
|
|
*/
|
|
|
|
#ifndef WEBP_WEBP_DEMUX_H_
|
|
#define WEBP_WEBP_DEMUX_H_
|
|
|
|
#include <stddef.h>
|
|
|
|
/* >>> src/webp/mux_types.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Data-types common to the mux and demux libraries.
|
|
//
|
|
// Author: Urvang (urvang@google.com)
|
|
|
|
#ifndef WEBP_WEBP_MUX_TYPES_H_
|
|
#define WEBP_WEBP_MUX_TYPES_H_
|
|
|
|
#include <string.h> // memset()
|
|
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// Note: forward declaring enumerations is not allowed in (strict) C and C++,
|
|
// the types are left here for reference.
|
|
// typedef enum WebPFeatureFlags WebPFeatureFlags;
|
|
// typedef enum WebPMuxAnimDispose WebPMuxAnimDispose;
|
|
// typedef enum WebPMuxAnimBlend WebPMuxAnimBlend;
|
|
typedef struct WebPData WebPData;
|
|
|
|
// VP8X Feature Flags.
|
|
typedef enum WebPFeatureFlags {
|
|
ANIMATION_FLAG = 0x00000002,
|
|
XMP_FLAG = 0x00000004,
|
|
EXIF_FLAG = 0x00000008,
|
|
ALPHA_FLAG = 0x00000010,
|
|
ICCP_FLAG = 0x00000020,
|
|
|
|
ALL_VALID_FLAGS = 0x0000003e
|
|
} WebPFeatureFlags;
|
|
|
|
// Dispose method (animation only). Indicates how the area used by the current
|
|
// frame is to be treated before rendering the next frame on the canvas.
|
|
typedef enum WebPMuxAnimDispose {
|
|
WEBP_MUX_DISPOSE_NONE, // Do not dispose.
|
|
WEBP_MUX_DISPOSE_BACKGROUND // Dispose to background color.
|
|
} WebPMuxAnimDispose;
|
|
|
|
// Blend operation (animation only). Indicates how transparent pixels of the
|
|
// current frame are blended with those of the previous canvas.
|
|
typedef enum WebPMuxAnimBlend {
|
|
WEBP_MUX_BLEND, // Blend.
|
|
WEBP_MUX_NO_BLEND // Do not blend.
|
|
} WebPMuxAnimBlend;
|
|
|
|
// Data type used to describe 'raw' data, e.g., chunk data
|
|
// (ICC profile, metadata) and WebP compressed image data.
|
|
// 'bytes' memory must be allocated using WebPMalloc() and such.
|
|
struct WebPData {
|
|
const uint8_t* bytes;
|
|
size_t size;
|
|
};
|
|
|
|
// Initializes the contents of the 'webp_data' object with default values.
|
|
static WEBP_INLINE void WebPDataInit(WebPData* webp_data) {
|
|
if (webp_data != NULL) {
|
|
WEBP_UNSAFE_MEMSET(webp_data, 0, sizeof(*webp_data));
|
|
}
|
|
}
|
|
|
|
// Clears the contents of the 'webp_data' object by calling WebPFree().
|
|
// Does not deallocate the object itself.
|
|
static WEBP_INLINE void WebPDataClear(WebPData* webp_data) {
|
|
if (webp_data != NULL) {
|
|
WebPFree((void*)webp_data->bytes);
|
|
WebPDataInit(webp_data);
|
|
}
|
|
}
|
|
|
|
// Allocates necessary storage for 'dst' and copies the contents of 'src'.
|
|
// Returns true on success.
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPDataCopy(const WebPData* src,
|
|
WebPData* dst) {
|
|
if (src == NULL || dst == NULL) return 0;
|
|
WebPDataInit(dst);
|
|
if (src->bytes != NULL && src->size != 0) {
|
|
dst->bytes = (uint8_t*)WebPMalloc(src->size);
|
|
if (dst->bytes == NULL) return 0;
|
|
WEBP_UNSAFE_MEMCPY((void*)dst->bytes, src->bytes, src->size);
|
|
dst->size = src->size;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_WEBP_MUX_TYPES_H_
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define WEBP_DEMUX_ABI_VERSION 0x0107 // MAJOR(8b) + MINOR(8b)
|
|
|
|
// Note: forward declaring enumerations is not allowed in (strict) C and C++,
|
|
// the types are left here for reference.
|
|
// typedef enum WebPDemuxState WebPDemuxState;
|
|
// typedef enum WebPFormatFeature WebPFormatFeature;
|
|
typedef struct WebPDemuxer WebPDemuxer;
|
|
typedef struct WebPIterator WebPIterator;
|
|
typedef struct WebPChunkIterator WebPChunkIterator;
|
|
typedef struct WebPAnimInfo WebPAnimInfo;
|
|
typedef struct WebPAnimDecoderOptions WebPAnimDecoderOptions;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Returns the version number of the demux library, packed in hexadecimal using
|
|
// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
|
WEBP_EXTERN int WebPGetDemuxVersion(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Life of a Demux object
|
|
|
|
typedef enum WebPDemuxState {
|
|
WEBP_DEMUX_PARSE_ERROR = -1, // An error occurred while parsing.
|
|
WEBP_DEMUX_PARSING_HEADER = 0, // Not enough data to parse full header.
|
|
WEBP_DEMUX_PARSED_HEADER = 1, // Header parsing complete,
|
|
// data may be available.
|
|
WEBP_DEMUX_DONE = 2 // Entire file has been parsed.
|
|
} WebPDemuxState;
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD WEBP_EXTERN WebPDemuxer* WebPDemuxInternal(const WebPData*, int,
|
|
WebPDemuxState*, int);
|
|
|
|
// Parses the full WebP file given by 'data'. For single images the WebP file
|
|
// header alone or the file header and the chunk header may be absent.
|
|
// Returns a WebPDemuxer object on successful parse, NULL otherwise.
|
|
WEBP_NODISCARD static WEBP_INLINE WebPDemuxer* WebPDemux(const WebPData* data) {
|
|
return WebPDemuxInternal(data, 0, NULL, WEBP_DEMUX_ABI_VERSION);
|
|
}
|
|
|
|
// Parses the possibly incomplete WebP file given by 'data'.
|
|
// If 'state' is non-NULL it will be set to indicate the status of the demuxer.
|
|
// Returns NULL in case of error or if there isn't enough data to start parsing;
|
|
// and a WebPDemuxer object on successful parse.
|
|
// Note that WebPDemuxer keeps internal pointers to 'data' memory segment.
|
|
// If this data is volatile, the demuxer object should be deleted (by calling
|
|
// WebPDemuxDelete()) and WebPDemuxPartial() called again on the new data.
|
|
// This is usually an inexpensive operation.
|
|
WEBP_NODISCARD static WEBP_INLINE WebPDemuxer* WebPDemuxPartial(
|
|
const WebPData* data, WebPDemuxState* state) {
|
|
return WebPDemuxInternal(data, 1, state, WEBP_DEMUX_ABI_VERSION);
|
|
}
|
|
|
|
// Frees memory associated with 'dmux'.
|
|
WEBP_EXTERN void WebPDemuxDelete(WebPDemuxer* dmux);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Data/information extraction.
|
|
|
|
typedef enum WebPFormatFeature {
|
|
WEBP_FF_FORMAT_FLAGS, // bit-wise combination of WebPFeatureFlags
|
|
// corresponding to the 'VP8X' chunk (if present).
|
|
WEBP_FF_CANVAS_WIDTH,
|
|
WEBP_FF_CANVAS_HEIGHT,
|
|
WEBP_FF_LOOP_COUNT, // only relevant for animated file
|
|
WEBP_FF_BACKGROUND_COLOR, // idem.
|
|
WEBP_FF_FRAME_COUNT // Number of frames present in the demux object.
|
|
// In case of a partial demux, this is the number
|
|
// of frames seen so far, with the last frame
|
|
// possibly being partial.
|
|
} WebPFormatFeature;
|
|
|
|
// Get the 'feature' value from the 'dmux'.
|
|
// NOTE: values are only valid if WebPDemux() was used or WebPDemuxPartial()
|
|
// returned a state > WEBP_DEMUX_PARSING_HEADER.
|
|
// If 'feature' is WEBP_FF_FORMAT_FLAGS, the returned value is a bit-wise
|
|
// combination of WebPFeatureFlags values.
|
|
// If 'feature' is WEBP_FF_LOOP_COUNT, WEBP_FF_BACKGROUND_COLOR, the returned
|
|
// value is only meaningful if the bitstream is animated.
|
|
WEBP_EXTERN uint32_t WebPDemuxGetI(const WebPDemuxer* dmux,
|
|
WebPFormatFeature feature);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Frame iteration.
|
|
|
|
struct WebPIterator {
|
|
int frame_num;
|
|
int num_frames; // equivalent to WEBP_FF_FRAME_COUNT.
|
|
int x_offset, y_offset; // offset relative to the canvas.
|
|
int width, height; // dimensions of this frame.
|
|
int duration; // display duration in milliseconds.
|
|
WebPMuxAnimDispose dispose_method; // dispose method for the frame.
|
|
int complete; // true if 'fragment' contains a full frame. partial images
|
|
// may still be decoded with the WebP incremental decoder.
|
|
WebPData fragment; // The frame given by 'frame_num'. Note for historical
|
|
// reasons this is called a fragment.
|
|
int has_alpha; // True if the frame contains transparency.
|
|
WebPMuxAnimBlend blend_method; // Blend operation for the frame.
|
|
|
|
uint32_t pad[2]; // padding for later use.
|
|
void* private_; // for internal use only.
|
|
};
|
|
|
|
// Retrieves frame 'frame_number' from 'dmux'.
|
|
// 'iter->fragment' points to the frame on return from this function.
|
|
// Setting 'frame_number' equal to 0 will return the last frame of the image.
|
|
// Returns false if 'dmux' is NULL or frame 'frame_number' is not present.
|
|
// Call WebPDemuxReleaseIterator() when use of the iterator is complete.
|
|
// NOTE: 'dmux' must persist for the lifetime of 'iter'.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPDemuxGetFrame(const WebPDemuxer* dmux,
|
|
int frame_number,
|
|
WebPIterator* iter);
|
|
|
|
// Sets 'iter->fragment' to point to the next ('iter->frame_num' + 1) or
|
|
// previous ('iter->frame_num' - 1) frame. These functions do not loop.
|
|
// Returns true on success, false otherwise.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPDemuxNextFrame(WebPIterator* iter);
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPDemuxPrevFrame(WebPIterator* iter);
|
|
|
|
// Releases any memory associated with 'iter'.
|
|
// Must be called before any subsequent calls to WebPDemuxGetChunk() on the same
|
|
// iter. Also, must be called before destroying the associated WebPDemuxer with
|
|
// WebPDemuxDelete().
|
|
WEBP_EXTERN void WebPDemuxReleaseIterator(WebPIterator* iter);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Chunk iteration.
|
|
|
|
struct WebPChunkIterator {
|
|
// The current and total number of chunks with the fourcc given to
|
|
// WebPDemuxGetChunk().
|
|
int chunk_num;
|
|
int num_chunks;
|
|
WebPData chunk; // The payload of the chunk.
|
|
|
|
uint32_t pad[6]; // padding for later use
|
|
void* private_;
|
|
};
|
|
|
|
// Retrieves the 'chunk_number' instance of the chunk with id 'fourcc' from
|
|
// 'dmux'.
|
|
// 'fourcc' is a character array containing the fourcc of the chunk to return,
|
|
// e.g., "ICCP", "XMP ", "EXIF", etc.
|
|
// Setting 'chunk_number' equal to 0 will return the last chunk in a set.
|
|
// Returns true if the chunk is found, false otherwise. Image related chunk
|
|
// payloads are accessed through WebPDemuxGetFrame() and related functions.
|
|
// Call WebPDemuxReleaseChunkIterator() when use of the iterator is complete.
|
|
// NOTE: 'dmux' must persist for the lifetime of the iterator.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPDemuxGetChunk(const WebPDemuxer* dmux,
|
|
const char fourcc[4],
|
|
int chunk_number,
|
|
WebPChunkIterator* iter);
|
|
|
|
// Sets 'iter->chunk' to point to the next ('iter->chunk_num' + 1) or previous
|
|
// ('iter->chunk_num' - 1) chunk. These functions do not loop.
|
|
// Returns true on success, false otherwise.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPDemuxNextChunk(WebPChunkIterator* iter);
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPDemuxPrevChunk(WebPChunkIterator* iter);
|
|
|
|
// Releases any memory associated with 'iter'.
|
|
// Must be called before destroying the associated WebPDemuxer with
|
|
// WebPDemuxDelete().
|
|
WEBP_EXTERN void WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPAnimDecoder API
|
|
//
|
|
// This API allows decoding (possibly) animated WebP images.
|
|
//
|
|
// Code Example:
|
|
/*
|
|
WebPAnimDecoderOptions dec_options;
|
|
WebPAnimDecoderOptionsInit(&dec_options);
|
|
// Tune 'dec_options' as needed.
|
|
WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options);
|
|
WebPAnimInfo anim_info;
|
|
WebPAnimDecoderGetInfo(dec, &anim_info);
|
|
for (uint32_t i = 0; i < anim_info.loop_count; ++i) {
|
|
while (WebPAnimDecoderHasMoreFrames(dec)) {
|
|
uint8_t* buf;
|
|
int timestamp;
|
|
WebPAnimDecoderGetNext(dec, &buf, ×tamp);
|
|
// ... (Render 'buf' based on 'timestamp').
|
|
// ... (Do NOT free 'buf', as it is owned by 'dec').
|
|
}
|
|
WebPAnimDecoderReset(dec);
|
|
}
|
|
const WebPDemuxer* demuxer = WebPAnimDecoderGetDemuxer(dec);
|
|
// ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data).
|
|
WebPAnimDecoderDelete(dec);
|
|
*/
|
|
|
|
typedef struct WebPAnimDecoder WebPAnimDecoder; // Main opaque object.
|
|
|
|
// Global options.
|
|
struct WebPAnimDecoderOptions {
|
|
// Output colorspace. Only the following modes are supported:
|
|
// MODE_RGBA, MODE_BGRA, MODE_rgbA and MODE_bgrA.
|
|
WEBP_CSP_MODE color_mode;
|
|
int use_threads; // If true, use multi-threaded decoding.
|
|
uint32_t padding[7]; // Padding for later use.
|
|
};
|
|
|
|
// Internal, version-checked, entry point.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderOptionsInitInternal(
|
|
WebPAnimDecoderOptions*, int);
|
|
|
|
// Should always be called, to initialize a fresh WebPAnimDecoderOptions
|
|
// structure before modification. Returns false in case of version mismatch.
|
|
// WebPAnimDecoderOptionsInit() must have succeeded before using the
|
|
// 'dec_options' object.
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPAnimDecoderOptionsInit(
|
|
WebPAnimDecoderOptions* dec_options) {
|
|
return WebPAnimDecoderOptionsInitInternal(dec_options,
|
|
WEBP_DEMUX_ABI_VERSION);
|
|
}
|
|
|
|
// Internal, version-checked, entry point.
|
|
WEBP_NODISCARD WEBP_EXTERN WebPAnimDecoder* WebPAnimDecoderNewInternal(
|
|
const WebPData*, const WebPAnimDecoderOptions*, int);
|
|
|
|
// Creates and initializes a WebPAnimDecoder object.
|
|
// Parameters:
|
|
// webp_data - (in) WebP bitstream. This should remain unchanged during the
|
|
// lifetime of the output WebPAnimDecoder object.
|
|
// dec_options - (in) decoding options. Can be passed NULL to choose
|
|
// reasonable defaults (in particular, color mode MODE_RGBA
|
|
// will be picked).
|
|
// Returns:
|
|
// A pointer to the newly created WebPAnimDecoder object, or NULL in case of
|
|
// parsing error, invalid option or memory error.
|
|
WEBP_NODISCARD static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew(
|
|
const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options) {
|
|
return WebPAnimDecoderNewInternal(webp_data, dec_options,
|
|
WEBP_DEMUX_ABI_VERSION);
|
|
}
|
|
|
|
// Global information about the animation..
|
|
struct WebPAnimInfo {
|
|
uint32_t canvas_width;
|
|
uint32_t canvas_height;
|
|
uint32_t loop_count;
|
|
uint32_t bgcolor;
|
|
uint32_t frame_count;
|
|
uint32_t pad[4]; // padding for later use
|
|
};
|
|
|
|
// Get global information about the animation.
|
|
// Parameters:
|
|
// dec - (in) decoder instance to get information from.
|
|
// info - (out) global information fetched from the animation.
|
|
// Returns:
|
|
// True on success.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderGetInfo(
|
|
const WebPAnimDecoder* dec, WebPAnimInfo* info);
|
|
|
|
// Fetch the next frame from 'dec' based on options supplied to
|
|
// WebPAnimDecoderNew(). This will be a fully reconstructed canvas of size
|
|
// 'canvas_width * 4 * canvas_height', and not just the frame sub-rectangle. The
|
|
// returned buffer 'buf' is valid only until the next call to
|
|
// WebPAnimDecoderGetNext(), WebPAnimDecoderReset() or WebPAnimDecoderDelete().
|
|
// Parameters:
|
|
// dec - (in/out) decoder instance from which the next frame is to be fetched.
|
|
// buf - (out) decoded frame.
|
|
// timestamp - (out) timestamp of the frame in milliseconds.
|
|
// Returns:
|
|
// False if any of the arguments are NULL, or if there is a parsing or
|
|
// decoding error, or if there are no more frames. Otherwise, returns true.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderGetNext(WebPAnimDecoder* dec,
|
|
uint8_t** buf,
|
|
int* timestamp);
|
|
|
|
// Check if there are more frames left to decode.
|
|
// Parameters:
|
|
// dec - (in) decoder instance to be checked.
|
|
// Returns:
|
|
// True if 'dec' is not NULL and some frames are yet to be decoded.
|
|
// Otherwise, returns false.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderHasMoreFrames(
|
|
const WebPAnimDecoder* dec);
|
|
|
|
// Resets the WebPAnimDecoder object, so that next call to
|
|
// WebPAnimDecoderGetNext() will restart decoding from 1st frame. This would be
|
|
// helpful when all frames need to be decoded multiple times (e.g.
|
|
// info.loop_count times) without destroying and recreating the 'dec' object.
|
|
// Parameters:
|
|
// dec - (in/out) decoder instance to be reset
|
|
WEBP_EXTERN void WebPAnimDecoderReset(WebPAnimDecoder* dec);
|
|
|
|
// Grab the internal demuxer object.
|
|
// Getting the demuxer object can be useful if one wants to use operations only
|
|
// available through demuxer; e.g. to get XMP/EXIF/ICC metadata. The returned
|
|
// demuxer object is owned by 'dec' and is valid only until the next call to
|
|
// WebPAnimDecoderDelete().
|
|
//
|
|
// Parameters:
|
|
// dec - (in) decoder instance from which the demuxer object is to be fetched.
|
|
WEBP_NODISCARD WEBP_EXTERN const WebPDemuxer* WebPAnimDecoderGetDemuxer(
|
|
const WebPAnimDecoder* dec);
|
|
|
|
// Deletes the WebPAnimDecoder object.
|
|
// Parameters:
|
|
// dec - (in/out) decoder instance to be deleted
|
|
WEBP_EXTERN void WebPAnimDecoderDelete(WebPAnimDecoder* dec);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_WEBP_DEMUX_H_
|
|
#endif /* WEBPDEC_H */
|
|
|
|
#ifdef WEBPDEC_IMPLEMENTATION
|
|
#define HAVE_CONFIG_H
|
|
/* >>> src/dec/alpha_dec.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Alpha-plane decompression.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
|
|
/* >>> src/dec/alphai_dec.h */
|
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Alpha decoder: internal header.
|
|
//
|
|
// Author: Urvang (urvang@google.com)
|
|
|
|
#ifndef WEBP_DEC_ALPHAI_DEC_H_
|
|
#define WEBP_DEC_ALPHAI_DEC_H_
|
|
|
|
/* >>> src/dec/vp8_dec.h */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Low-level API for VP8 decoder
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_DEC_VP8_DEC_H_
|
|
#define WEBP_DEC_VP8_DEC_H_
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Lower-level API
|
|
//
|
|
// These functions provide fine-grained control of the decoding process.
|
|
// The call flow should resemble:
|
|
//
|
|
// VP8Io io;
|
|
// VP8InitIo(&io);
|
|
// io.data = data;
|
|
// io.data_size = size;
|
|
// /* customize io's functions (setup()/put()/teardown()) if needed. */
|
|
//
|
|
// VP8Decoder* dec = VP8New();
|
|
// int ok = VP8Decode(dec, &io);
|
|
// if (!ok) printf("Error: %s\n", VP8StatusMessage(dec));
|
|
// VP8Delete(dec);
|
|
// return ok;
|
|
|
|
// Input / Output
|
|
typedef struct VP8Io VP8Io;
|
|
typedef int (*VP8IoPutHook)(const VP8Io* io);
|
|
typedef int (*VP8IoSetupHook)(VP8Io* io);
|
|
typedef void (*VP8IoTeardownHook)(const VP8Io* io);
|
|
|
|
struct VP8Io {
|
|
// set by VP8GetHeaders()
|
|
int width, height; // picture dimensions, in pixels (invariable).
|
|
// These are the original, uncropped dimensions.
|
|
// The actual area passed to put() is stored
|
|
// in mb_w / mb_h fields.
|
|
|
|
// set before calling put()
|
|
int mb_y; // position of the current rows (in pixels)
|
|
int mb_w; // number of columns in the sample
|
|
int mb_h; // number of rows in the sample
|
|
const uint8_t *y, *u, *v; // rows to copy (in yuv420 format)
|
|
int y_stride; // row stride for luma
|
|
int uv_stride; // row stride for chroma
|
|
|
|
void* opaque; // user data
|
|
|
|
// called when fresh samples are available. Currently, samples are in
|
|
// YUV420 format, and can be up to width x 24 in size (depending on the
|
|
// in-loop filtering level, e.g.). Should return false in case of error
|
|
// or abort request. The actual size of the area to update is mb_w x mb_h
|
|
// in size, taking cropping into account.
|
|
VP8IoPutHook put;
|
|
|
|
// called just before starting to decode the blocks.
|
|
// Must return false in case of setup error, true otherwise. If false is
|
|
// returned, teardown() will NOT be called. But if the setup succeeded
|
|
// and true is returned, then teardown() will always be called afterward.
|
|
VP8IoSetupHook setup;
|
|
|
|
// Called just after block decoding is finished (or when an error occurred
|
|
// during put()). Is NOT called if setup() failed.
|
|
VP8IoTeardownHook teardown;
|
|
|
|
// this is a recommendation for the user-side yuv->rgb converter. This flag
|
|
// is set when calling setup() hook and can be overwritten by it. It then
|
|
// can be taken into consideration during the put() method.
|
|
int fancy_upsampling;
|
|
|
|
// Input buffer.
|
|
size_t data_size;
|
|
const uint8_t* data;
|
|
|
|
// If true, in-loop filtering will not be performed even if present in the
|
|
// bitstream. Switching off filtering may speed up decoding at the expense
|
|
// of more visible blocking. Note that output will also be non-compliant
|
|
// with the VP8 specifications.
|
|
int bypass_filtering;
|
|
|
|
// Cropping parameters.
|
|
int use_cropping;
|
|
int crop_left, crop_right, crop_top, crop_bottom;
|
|
|
|
// Scaling parameters.
|
|
int use_scaling;
|
|
int scaled_width, scaled_height;
|
|
|
|
// If non NULL, pointer to the alpha data (if present) corresponding to the
|
|
// start of the current row (That is: it is pre-offset by mb_y and takes
|
|
// cropping into account).
|
|
const uint8_t* a;
|
|
};
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD int VP8InitIoInternal(VP8Io* const, int);
|
|
|
|
// Set the custom IO function pointers and user-data. The setter for IO hooks
|
|
// should be called before initiating incremental decoding. Returns true if
|
|
// WebPIDecoder object is successfully modified, false otherwise.
|
|
WEBP_NODISCARD int WebPISetIOHooks(WebPIDecoder* const idec, VP8IoPutHook put,
|
|
VP8IoSetupHook setup,
|
|
VP8IoTeardownHook teardown, void* user_data);
|
|
|
|
// Main decoding object. This is an opaque structure.
|
|
typedef struct VP8Decoder VP8Decoder;
|
|
|
|
// Create a new decoder object.
|
|
VP8Decoder* VP8New(void);
|
|
|
|
// Must be called to make sure 'io' is initialized properly.
|
|
// Returns false in case of version mismatch. Upon such failure, no other
|
|
// decoding function should be called (VP8Decode, VP8GetHeaders, ...)
|
|
WEBP_NODISCARD static WEBP_INLINE int VP8InitIo(VP8Io* const io) {
|
|
return VP8InitIoInternal(io, WEBP_DECODER_ABI_VERSION);
|
|
}
|
|
|
|
// Decode the VP8 frame header. Returns true if ok.
|
|
// Note: 'io->data' must be pointing to the start of the VP8 frame header.
|
|
WEBP_NODISCARD int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io);
|
|
|
|
// Decode a picture. Will call VP8GetHeaders() if it wasn't done already.
|
|
// Returns false in case of error.
|
|
WEBP_NODISCARD int VP8Decode(VP8Decoder* const dec, VP8Io* const io);
|
|
|
|
// Return current status of the decoder:
|
|
VP8StatusCode VP8Status(VP8Decoder* const dec);
|
|
|
|
// return readable string corresponding to the last status.
|
|
const char* VP8StatusMessage(VP8Decoder* const dec);
|
|
|
|
// Resets the decoder in its initial state, reclaiming memory.
|
|
// Not a mandatory call between calls to VP8Decode().
|
|
void VP8Clear(VP8Decoder* const dec);
|
|
|
|
// Destroy the decoder object.
|
|
void VP8Delete(VP8Decoder* const dec);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Miscellaneous VP8/VP8L bitstream probing functions.
|
|
|
|
// Returns true if the next 3 bytes in data contain the VP8 signature.
|
|
WEBP_EXTERN int VP8CheckSignature(
|
|
const uint8_t* const WEBP_COUNTED_BY(data_size) data, size_t data_size);
|
|
|
|
// Validates the VP8 data-header and retrieves basic header information viz
|
|
// width and height. Returns 0 in case of formatting error. *width/*height
|
|
// can be passed NULL.
|
|
WEBP_EXTERN int VP8GetInfo(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, // data available so far
|
|
size_t chunk_size, // total data size expected in the chunk
|
|
int* const width, int* const height);
|
|
|
|
// Returns true if the next byte(s) in data is a VP8L signature.
|
|
WEBP_EXTERN int VP8LCheckSignature(const uint8_t* const WEBP_COUNTED_BY(size)
|
|
data,
|
|
size_t size);
|
|
|
|
// Validates the VP8L data-header and retrieves basic header information viz
|
|
// width, height and alpha. Returns 0 in case of formatting error.
|
|
// width/height/has_alpha can be passed NULL.
|
|
WEBP_EXTERN int VP8LGetInfo(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, // data available so far
|
|
int* const width, int* const height,
|
|
int* const has_alpha);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DEC_VP8_DEC_H_
|
|
/* >>> src/dec/webpi_dec.h */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Internal header: WebP decoding parameters and custom IO on buffer
|
|
//
|
|
// Author: somnath@google.com (Somnath Banerjee)
|
|
|
|
#ifndef WEBP_DEC_WEBPI_DEC_H_
|
|
#define WEBP_DEC_WEBPI_DEC_H_
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#include <stddef.h>
|
|
|
|
/* >>> src/utils/rescaler_utils.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Rescaling functions
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_UTILS_RESCALER_UTILS_H_
|
|
#define WEBP_UTILS_RESCALER_UTILS_H_
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* >>> src/utils/bounds_safety.h */
|
|
// Copyright 2025 Google LLC
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Adds compatibility / portability macros to support usage of -fbounds-safety
|
|
|
|
#ifndef WEBP_UTILS_BOUNDS_SAFETY_H_
|
|
#define WEBP_UTILS_BOUNDS_SAFETY_H_
|
|
|
|
#ifdef WEBP_SUPPORT_FBOUNDS_SAFETY
|
|
|
|
#include <ptrcheck.h>
|
|
//
|
|
// There's some inherent complexity here due to the way -fbounds-safety works.
|
|
// Some annotations (notably __indexable and __bidi_indexable) change the ABI
|
|
// of the function or struct, so we don't want those annotations to silently
|
|
// disappear if they're expected.
|
|
//
|
|
// In ptrcheck.h provided by the compiler, ABI changing annotations do not
|
|
// "vanish" under any build configuration. This is intentional. Consider the
|
|
// following example:
|
|
//
|
|
// == Safe.h, where Safe.c is always compiled with -fbounds-safety ==
|
|
// Forward declare my_function, implemented in Safe.c
|
|
// void my_function(char *__bidi_indexable ptr);
|
|
//
|
|
// If we have a project that does not use -fbounds-safety, and we want to call
|
|
// my_function that was pre-built with -fbounds-safety, this annotation cannot
|
|
// vanish or there'll be an ABI mismatch, which may fail to compile or have
|
|
// worse behaviors at runtime.
|
|
//
|
|
// TODO: https://issues.webmproject.org/432511225 - In the future, we should
|
|
// have CMake append to a header file (like this one) that libwebp was built
|
|
// with -fbounds-safety, so that we know to never make annotations vanish.
|
|
|
|
// The annotations below are ABI breaking as they turn normal pointers into
|
|
// "wide" pointers. Breaking them down:
|
|
// * __indexable is akin to { ptr_curr, ptr_end }, and can only be
|
|
// forward-indexed.
|
|
// * __bidi_indexable (bidirectional indexable) is
|
|
// { ptr_begin, ptr_curr, ptr_end }
|
|
// and can be both forward and backward indexed.
|
|
// See https://clang.llvm.org/docs/BoundsSafety.html for more comprehensive
|
|
// documentation
|
|
#define WEBP_INDEXABLE __indexable
|
|
#define WEBP_BIDI_INDEXABLE __bidi_indexable
|
|
|
|
#else // WEBP_SUPPORT_FBOUNDS_SAFETY
|
|
|
|
#define WEBP_INDEXABLE
|
|
#define WEBP_BIDI_INDEXABLE
|
|
|
|
#endif // WEBP_SUPPORT_FBOUNDS_SAFETY
|
|
|
|
#endif // WEBP_UTILS_BOUNDS_SAFETY_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#define WEBP_RESCALER_RFIX 32 // fixed-point precision for multiplies
|
|
#define WEBP_RESCALER_ONE (1ull << WEBP_RESCALER_RFIX)
|
|
#define WEBP_RESCALER_FRAC(x, y) \
|
|
((uint32_t)(((uint64_t)(x) << WEBP_RESCALER_RFIX) / (y)))
|
|
|
|
// Structure used for on-the-fly rescaling
|
|
typedef uint32_t rescaler_t; // type for side-buffer
|
|
typedef struct WebPRescaler WebPRescaler;
|
|
struct WebPRescaler {
|
|
int x_expand; // true if we're expanding in the x direction
|
|
int y_expand; // true if we're expanding in the y direction
|
|
int num_channels; // bytes to jump between pixels
|
|
uint32_t fx_scale; // fixed-point scaling factors
|
|
uint32_t fy_scale; // ''
|
|
uint32_t fxy_scale; // ''
|
|
int y_accum; // vertical accumulator
|
|
int y_add, y_sub; // vertical increments
|
|
int x_add, x_sub; // horizontal increments
|
|
int src_width, src_height; // source dimensions
|
|
int dst_width, dst_height; // destination dimensions
|
|
int src_y, dst_y; // row counters for input and output
|
|
uint8_t* dst;
|
|
int dst_stride;
|
|
// work buffer
|
|
rescaler_t* WEBP_COUNTED_BY(dst_width* num_channels) irow;
|
|
rescaler_t* WEBP_COUNTED_BY(dst_width* num_channels) frow;
|
|
};
|
|
|
|
// Initialize a rescaler given scratch area 'work' and dimensions of src & dst.
|
|
// Returns false in case of error.
|
|
int WebPRescalerInit(WebPRescaler* const rescaler, int src_width,
|
|
int src_height, uint8_t* const dst, int dst_width,
|
|
int dst_height, int dst_stride, int num_channels,
|
|
rescaler_t* const WEBP_COUNTED_BY(2ULL * dst_width *
|
|
num_channels) work);
|
|
|
|
// If either 'scaled_width' or 'scaled_height' (but not both) is 0 the value
|
|
// will be calculated preserving the aspect ratio, otherwise the values are
|
|
// left unmodified. Returns true on success, false if either value is 0 after
|
|
// performing the scaling calculation.
|
|
int WebPRescalerGetScaledDimensions(int src_width, int src_height,
|
|
int* const scaled_width,
|
|
int* const scaled_height);
|
|
|
|
// Returns the number of input lines needed next to produce one output line,
|
|
// considering that the maximum available input lines are 'max_num_lines'.
|
|
int WebPRescaleNeededLines(const WebPRescaler* const rescaler,
|
|
int max_num_lines);
|
|
|
|
// Import multiple rows over all channels, until at least one row is ready to
|
|
// be exported. Returns the actual number of lines that were imported.
|
|
int WebPRescalerImport(WebPRescaler* const rescaler, int num_rows,
|
|
const uint8_t* src, int src_stride);
|
|
|
|
// Export as many rows as possible. Return the numbers of rows written.
|
|
int WebPRescalerExport(WebPRescaler* const rescaler);
|
|
|
|
// Return true if input is finished
|
|
static WEBP_INLINE int WebPRescalerInputDone(
|
|
const WebPRescaler* const rescaler) {
|
|
return (rescaler->src_y >= rescaler->src_height);
|
|
}
|
|
// Return true if output is finished
|
|
static WEBP_INLINE int WebPRescalerOutputDone(
|
|
const WebPRescaler* const rescaler) {
|
|
return (rescaler->dst_y >= rescaler->dst_height);
|
|
}
|
|
|
|
// Return true if there are pending output rows ready.
|
|
static WEBP_INLINE int WebPRescalerHasPendingOutput(
|
|
const WebPRescaler* const rescaler) {
|
|
return !WebPRescalerOutputDone(rescaler) && (rescaler->y_accum <= 0);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_RESCALER_UTILS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPDecParams: Decoding output parameters. Transient internal object.
|
|
|
|
typedef struct WebPDecParams WebPDecParams;
|
|
typedef int (*OutputFunc)(const VP8Io* const io, WebPDecParams* const p);
|
|
typedef int (*OutputAlphaFunc)(const VP8Io* const io, WebPDecParams* const p,
|
|
int expected_num_out_lines);
|
|
typedef int (*OutputRowFunc)(WebPDecParams* const p, int y_pos,
|
|
int max_out_lines);
|
|
|
|
struct WebPDecParams {
|
|
WebPDecBuffer* output; // output buffer.
|
|
uint8_t *tmp_y, *tmp_u, *tmp_v; // cache for the fancy upsampler
|
|
// or used for tmp rescaling
|
|
|
|
int last_y; // coordinate of the line that was last output
|
|
const WebPDecoderOptions* options; // if not NULL, use alt decoding features
|
|
|
|
WebPRescaler *scaler_y, *scaler_u, *scaler_v, *scaler_a; // rescalers
|
|
void* memory; // overall scratch memory for the output work.
|
|
|
|
OutputFunc emit; // output RGB or YUV samples
|
|
OutputAlphaFunc emit_alpha; // output alpha channel
|
|
OutputRowFunc emit_alpha_row; // output one line of rescaled alpha values
|
|
};
|
|
|
|
// Should be called first, before any use of the WebPDecParams object.
|
|
void WebPResetDecParams(WebPDecParams* const params);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Header parsing helpers
|
|
|
|
// Structure storing a description of the RIFF headers.
|
|
typedef struct {
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data; // input buffer
|
|
size_t data_size; // input buffer size
|
|
int have_all_data; // true if all data is known to be available
|
|
size_t offset; // offset to main data chunk (VP8 or VP8L)
|
|
const uint8_t* WEBP_COUNTED_BY(alpha_data_size)
|
|
alpha_data; // points to alpha chunk (if present)
|
|
size_t alpha_data_size; // alpha chunk size
|
|
size_t compressed_size; // VP8/VP8L compressed data size
|
|
size_t riff_size; // size of the riff payload (or 0 if absent)
|
|
int is_lossless; // true if a VP8L chunk is present
|
|
} WebPHeaderStructure;
|
|
|
|
// Skips over all valid chunks prior to the first VP8/VP8L frame header.
|
|
// Returns: VP8_STATUS_OK, VP8_STATUS_BITSTREAM_ERROR (invalid header/chunk),
|
|
// VP8_STATUS_NOT_ENOUGH_DATA (partial input) or VP8_STATUS_UNSUPPORTED_FEATURE
|
|
// in the case of non-decodable features (animation for instance).
|
|
// In 'headers', compressed_size, offset, alpha_data, alpha_size, and lossless
|
|
// fields are updated appropriately upon success.
|
|
VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Misc utils
|
|
|
|
// Returns true if crop dimensions are within image bounds.
|
|
int WebPCheckCropDimensions(int image_width, int image_height, int x, int y,
|
|
int w, int h);
|
|
|
|
// Initializes VP8Io with custom setup, io and teardown functions. The default
|
|
// hooks will use the supplied 'params' as io->opaque handle.
|
|
void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io);
|
|
|
|
// Setup crop_xxx fields, mb_w and mb_h in io. 'src_colorspace' refers
|
|
// to the *compressed* format, not the output one.
|
|
WEBP_NODISCARD int WebPIoInitFromOptions(
|
|
const WebPDecoderOptions* const options, VP8Io* const io,
|
|
WEBP_CSP_MODE src_colorspace);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Internal functions regarding WebPDecBuffer memory (in buffer.c).
|
|
// Don't really need to be externally visible for now.
|
|
|
|
// Prepare 'buffer' with the requested initial dimensions width/height.
|
|
// If no external storage is supplied, initializes buffer by allocating output
|
|
// memory and setting up the stride information. Validate the parameters. Return
|
|
// an error code in case of problem (no memory, or invalid stride / size /
|
|
// dimension / etc.). If *options is not NULL, also verify that the options'
|
|
// parameters are valid and apply them to the width/height dimensions of the
|
|
// output buffer. This takes cropping / scaling / rotation into account.
|
|
// Also incorporates the options->flip flag to flip the buffer parameters if
|
|
// needed.
|
|
VP8StatusCode WebPAllocateDecBuffer(int width, int height,
|
|
const WebPDecoderOptions* const options,
|
|
WebPDecBuffer* const buffer);
|
|
|
|
// Flip buffer vertically by negating the various strides.
|
|
VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer);
|
|
|
|
// Copy 'src' into 'dst' buffer, making sure 'dst' is not marked as owner of the
|
|
// memory (still held by 'src'). No pixels are copied.
|
|
void WebPCopyDecBuffer(const WebPDecBuffer* const src,
|
|
WebPDecBuffer* const dst);
|
|
|
|
// Copy and transfer ownership from src to dst (beware of parameter order!)
|
|
void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst);
|
|
|
|
// Copy pixels from 'src' into a *preallocated* 'dst' buffer. Returns
|
|
// VP8_STATUS_INVALID_PARAM if the 'dst' is not set up correctly for the copy.
|
|
VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src,
|
|
WebPDecBuffer* const dst);
|
|
|
|
// Returns true if decoding will be slow with the current configuration
|
|
// and bitstream features.
|
|
int WebPAvoidSlowMemory(const WebPDecBuffer* const output,
|
|
const WebPBitstreamFeatures* const features);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DEC_WEBPI_DEC_H_
|
|
/* >>> src/dsp/dsp.h */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Speed-critical functions.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_DSP_DSP_H_
|
|
#define WEBP_DSP_DSP_H_
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
/* >>> src/webp/config.h */
|
|
/* intentionally empty: forces libwebp's generic-C path */
|
|
#endif
|
|
|
|
/* >>> src/dec/common_dec.h */
|
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Definitions and macros common to encoding and decoding
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_DEC_COMMON_DEC_H_
|
|
#define WEBP_DEC_COMMON_DEC_H_
|
|
|
|
// intra prediction modes
|
|
enum {
|
|
B_DC_PRED = 0, // 4x4 modes
|
|
B_TM_PRED = 1,
|
|
B_VE_PRED = 2,
|
|
B_HE_PRED = 3,
|
|
B_RD_PRED = 4,
|
|
B_VR_PRED = 5,
|
|
B_LD_PRED = 6,
|
|
B_VL_PRED = 7,
|
|
B_HD_PRED = 8,
|
|
B_HU_PRED = 9,
|
|
NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED, // = 10
|
|
|
|
// Luma16 or UV modes
|
|
DC_PRED = B_DC_PRED,
|
|
V_PRED = B_VE_PRED,
|
|
H_PRED = B_HE_PRED,
|
|
TM_PRED = B_TM_PRED,
|
|
B_PRED = NUM_BMODES, // refined I4x4 mode
|
|
NUM_PRED_MODES = 4,
|
|
|
|
// special modes
|
|
B_DC_PRED_NOTOP = 4,
|
|
B_DC_PRED_NOLEFT = 5,
|
|
B_DC_PRED_NOTOPLEFT = 6,
|
|
NUM_B_DC_MODES = 7
|
|
};
|
|
|
|
enum {
|
|
MB_FEATURE_TREE_PROBS = 3,
|
|
NUM_MB_SEGMENTS = 4,
|
|
NUM_REF_LF_DELTAS = 4,
|
|
NUM_MODE_LF_DELTAS = 4, // I4x4, ZERO, *, SPLIT
|
|
MAX_NUM_PARTITIONS = 8,
|
|
// Probabilities
|
|
NUM_TYPES = 4, // 0: i16-AC, 1: i16-DC, 2:chroma-AC, 3:i4-AC
|
|
NUM_BANDS = 8,
|
|
NUM_CTX = 3,
|
|
NUM_PROBAS = 11
|
|
};
|
|
|
|
// Check that webp_csp_mode is within the bounds of WEBP_CSP_MODE.
|
|
int IsValidColorspace(int webp_csp_mode);
|
|
|
|
// Lossless: maximum number of histogram images (sub-blocks). This is defined
|
|
// for encoding efficiency, the standard allows for more.
|
|
#define MAX_HUFF_IMAGE_SIZE 2600
|
|
|
|
#endif // WEBP_DEC_COMMON_DEC_H_
|
|
/* >>> src/dsp/cpu.h */
|
|
// Copyright 2022 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// CPU detection functions and macros.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_DSP_CPU_H_
|
|
#define WEBP_DSP_CPU_H_
|
|
|
|
#include <stddef.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
|
|
#if defined(__GNUC__)
|
|
#define LOCAL_GCC_VERSION ((__GNUC__ << 8) | __GNUC_MINOR__)
|
|
#define LOCAL_GCC_PREREQ(maj, min) (LOCAL_GCC_VERSION >= (((maj) << 8) | (min)))
|
|
#else
|
|
#define LOCAL_GCC_VERSION 0
|
|
#define LOCAL_GCC_PREREQ(maj, min) 0
|
|
#endif
|
|
|
|
#if defined(__clang__)
|
|
#define LOCAL_CLANG_VERSION ((__clang_major__ << 8) | __clang_minor__)
|
|
#define LOCAL_CLANG_PREREQ(maj, min) \
|
|
(LOCAL_CLANG_VERSION >= (((maj) << 8) | (min)))
|
|
#else
|
|
#define LOCAL_CLANG_VERSION 0
|
|
#define LOCAL_CLANG_PREREQ(maj, min) 0
|
|
#endif
|
|
|
|
#ifndef __has_builtin
|
|
#define __has_builtin(x) 0
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// x86 defines.
|
|
|
|
#if !defined(HAVE_CONFIG_H)
|
|
#if defined(_MSC_VER) && _MSC_VER > 1310 && \
|
|
(defined(_M_X64) || defined(_M_IX86))
|
|
#define WEBP_MSC_SSE2 // Visual C++ SSE2 targets
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1500 && \
|
|
(defined(_M_X64) || defined(_M_IX86))
|
|
#define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1700 && \
|
|
(defined(_M_X64) || defined(_M_IX86))
|
|
#define WEBP_MSC_AVX2 // Visual C++ AVX2 targets
|
|
#endif
|
|
#endif
|
|
|
|
// WEBP_HAVE_* are used to indicate the presence of the instruction set in dsp
|
|
// files without intrinsics, allowing the corresponding Init() to be called.
|
|
// Files containing intrinsics will need to be built targeting the instruction
|
|
// set so should succeed on one of the earlier tests.
|
|
#if (defined(__SSE2__) || defined(WEBP_MSC_SSE2)) && \
|
|
(!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE2))
|
|
#define WEBP_USE_SSE2
|
|
#endif
|
|
|
|
#if defined(WEBP_USE_SSE2) && !defined(WEBP_HAVE_SSE2)
|
|
#define WEBP_HAVE_SSE2
|
|
#endif
|
|
|
|
#if (defined(__SSE4_1__) || defined(WEBP_MSC_SSE41)) && \
|
|
(!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE41))
|
|
#define WEBP_USE_SSE41
|
|
#endif
|
|
|
|
#if defined(WEBP_USE_SSE41) && !defined(WEBP_HAVE_SSE41)
|
|
#define WEBP_HAVE_SSE41
|
|
#endif
|
|
|
|
#if (defined(__AVX2__) || defined(WEBP_MSC_AVX2)) && \
|
|
(!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_AVX2))
|
|
#define WEBP_USE_AVX2
|
|
#endif
|
|
|
|
#if defined(WEBP_USE_AVX2) && !defined(WEBP_HAVE_AVX2)
|
|
#define WEBP_HAVE_AVX2
|
|
#endif
|
|
|
|
#if defined(WEBP_MSC_AVX2) && _MSC_VER <= 1900
|
|
#include <immintrin.h>
|
|
|
|
static WEBP_INLINE int _mm256_extract_epi32(__m256i a, const int i) {
|
|
return a.m256i_i32[i & 7];
|
|
}
|
|
static WEBP_INLINE int _mm256_cvtsi256_si32(__m256i a) {
|
|
return _mm256_extract_epi32(a, 0);
|
|
}
|
|
#endif
|
|
|
|
#undef WEBP_MSC_AVX2
|
|
#undef WEBP_MSC_SSE41
|
|
#undef WEBP_MSC_SSE2
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Arm defines.
|
|
|
|
// The intrinsics currently cause compiler errors with arm-nacl-gcc and the
|
|
// inline assembly would need to be modified for use with Native Client.
|
|
#if ((defined(__ARM_NEON__) || defined(__aarch64__)) && \
|
|
(!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_NEON))) && \
|
|
!defined(__native_client__)
|
|
#define WEBP_USE_NEON
|
|
#endif
|
|
|
|
#if !defined(WEBP_USE_NEON) && defined(__ANDROID__) && \
|
|
defined(__ARM_ARCH_7A__) && defined(HAVE_CPU_FEATURES_H)
|
|
#define WEBP_ANDROID_NEON // Android targets that may have NEON
|
|
#define WEBP_USE_NEON
|
|
#endif
|
|
|
|
// Note: ARM64 is supported in Visual Studio 2017, but requires the direct
|
|
// inclusion of arm64_neon.h; Visual Studio 2019 includes this file in
|
|
// arm_neon.h. Compile errors were seen with Visual Studio 2019 16.4 with
|
|
// vtbl4_u8(); a fix was made in 16.6.
|
|
#if defined(_MSC_VER) && \
|
|
((_MSC_VER >= 1700 && defined(_M_ARM)) || \
|
|
(_MSC_VER >= 1926 && (defined(_M_ARM64) || defined(_M_ARM64EC))))
|
|
#define WEBP_USE_NEON
|
|
#define WEBP_USE_INTRINSICS
|
|
#endif
|
|
|
|
#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)
|
|
#define WEBP_AARCH64 1
|
|
#else
|
|
#define WEBP_AARCH64 0
|
|
#endif
|
|
|
|
#if defined(WEBP_USE_NEON) && !defined(WEBP_HAVE_NEON)
|
|
#define WEBP_HAVE_NEON
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// MIPS defines.
|
|
|
|
#if defined(__mips__) && !defined(__mips64) && defined(__mips_isa_rev) && \
|
|
(__mips_isa_rev >= 1) && (__mips_isa_rev < 6)
|
|
#define WEBP_USE_MIPS32
|
|
#if (__mips_isa_rev >= 2)
|
|
#define WEBP_USE_MIPS32_R2
|
|
#if defined(__mips_dspr2) || (defined(__mips_dsp_rev) && __mips_dsp_rev >= 2)
|
|
#define WEBP_USE_MIPS_DSP_R2
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(__mips_msa) && defined(__mips_isa_rev) && (__mips_isa_rev >= 5)
|
|
#define WEBP_USE_MSA
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifndef WEBP_DSP_OMIT_C_CODE
|
|
#define WEBP_DSP_OMIT_C_CODE 1
|
|
#endif
|
|
|
|
#if defined(WEBP_USE_NEON) && WEBP_DSP_OMIT_C_CODE
|
|
#define WEBP_NEON_OMIT_C_CODE 1
|
|
#else
|
|
#define WEBP_NEON_OMIT_C_CODE 0
|
|
#endif
|
|
|
|
#if !(LOCAL_CLANG_PREREQ(3, 8) || LOCAL_GCC_PREREQ(4, 8) || WEBP_AARCH64)
|
|
#define WEBP_NEON_WORK_AROUND_GCC 1
|
|
#else
|
|
#define WEBP_NEON_WORK_AROUND_GCC 0
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// This macro prevents thread_sanitizer from reporting known concurrent writes.
|
|
#define WEBP_TSAN_IGNORE_FUNCTION
|
|
#if defined(__has_feature)
|
|
#if __has_feature(thread_sanitizer)
|
|
#undef WEBP_TSAN_IGNORE_FUNCTION
|
|
#define WEBP_TSAN_IGNORE_FUNCTION __attribute__((no_sanitize_thread))
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(__has_feature)
|
|
// Clang 21 should have all the MSAN fixes needed for WebP.
|
|
#if __has_feature(memory_sanitizer) && !LOCAL_CLANG_PREREQ(21, 0)
|
|
#define WEBP_MSAN
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(WEBP_USE_THREAD)
|
|
#if defined(_WIN32)
|
|
#include <windows.h>
|
|
|
|
#if _WIN32_WINNT < 0x0600
|
|
#error _WIN32_WINNT must target Windows Vista / Server 2008 or newer.
|
|
#endif
|
|
// clang-format off
|
|
#define WEBP_DSP_INIT_VARS(func) \
|
|
static VP8CPUInfo func##_last_cpuinfo_used = \
|
|
(VP8CPUInfo)&func##_last_cpuinfo_used; \
|
|
static SRWLOCK func##_lock = SRWLOCK_INIT
|
|
#define WEBP_DSP_INIT(func) \
|
|
do { \
|
|
AcquireSRWLockExclusive(&func##_lock); \
|
|
if (func##_last_cpuinfo_used != VP8GetCPUInfo) func(); \
|
|
func##_last_cpuinfo_used = VP8GetCPUInfo; \
|
|
ReleaseSRWLockExclusive(&func##_lock); \
|
|
} while (0)
|
|
// clang-format on
|
|
#else // !defined(_WIN32)
|
|
// NOLINTNEXTLINE
|
|
#include <pthread.h>
|
|
|
|
// clang-format off
|
|
#define WEBP_DSP_INIT_VARS(func) \
|
|
static VP8CPUInfo func##_last_cpuinfo_used = \
|
|
(VP8CPUInfo)&func##_last_cpuinfo_used; \
|
|
static pthread_mutex_t func##_lock = PTHREAD_MUTEX_INITIALIZER
|
|
#define WEBP_DSP_INIT(func) \
|
|
do { \
|
|
if (pthread_mutex_lock(&func##_lock)) break; \
|
|
if (func##_last_cpuinfo_used != VP8GetCPUInfo) func(); \
|
|
func##_last_cpuinfo_used = VP8GetCPUInfo; \
|
|
(void)pthread_mutex_unlock(&func##_lock); \
|
|
} while (0)
|
|
// clang-format on
|
|
#endif // defined(_WIN32)
|
|
#else // !defined(WEBP_USE_THREAD)
|
|
// clang-format off
|
|
#define WEBP_DSP_INIT_VARS(func) \
|
|
static volatile VP8CPUInfo func##_last_cpuinfo_used = \
|
|
(VP8CPUInfo)&func##_last_cpuinfo_used
|
|
#define WEBP_DSP_INIT(func) \
|
|
do { \
|
|
if (func##_last_cpuinfo_used == VP8GetCPUInfo) break; \
|
|
func(); \
|
|
func##_last_cpuinfo_used = VP8GetCPUInfo; \
|
|
} while (0)
|
|
// clang-format on
|
|
#endif // defined(WEBP_USE_THREAD)
|
|
|
|
// Defines an Init + helper function that control multiple initialization of
|
|
// function pointers / tables.
|
|
/* Usage:
|
|
WEBP_DSP_INIT_FUNC(InitFunc) {
|
|
...function body
|
|
}
|
|
*/
|
|
#define WEBP_DSP_INIT_FUNC(name) \
|
|
WEBP_DSP_INIT_VARS(name##_body); \
|
|
static WEBP_TSAN_IGNORE_FUNCTION void name##_body(void); \
|
|
WEBP_TSAN_IGNORE_FUNCTION void name(void) { WEBP_DSP_INIT(name##_body); } \
|
|
static WEBP_TSAN_IGNORE_FUNCTION void name##_body(void)
|
|
|
|
#define WEBP_UBSAN_IGNORE_UNDEF
|
|
#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW
|
|
#if defined(__clang__) && defined(__has_attribute)
|
|
#if __has_attribute(no_sanitize)
|
|
// This macro prevents the undefined behavior sanitizer from reporting
|
|
// failures. This is only meant to silence unaligned loads on platforms that
|
|
// are known to support them.
|
|
#undef WEBP_UBSAN_IGNORE_UNDEF
|
|
#define WEBP_UBSAN_IGNORE_UNDEF __attribute__((no_sanitize("undefined")))
|
|
|
|
// This macro prevents the undefined behavior sanitizer from reporting
|
|
// failures related to unsigned integer overflows. This is only meant to
|
|
// silence cases where this well defined behavior is expected.
|
|
#undef WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW
|
|
#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW \
|
|
__attribute__((no_sanitize("unsigned-integer-overflow")))
|
|
#endif
|
|
#endif
|
|
|
|
// If 'ptr' is NULL, returns NULL. Otherwise returns 'ptr + off'.
|
|
// Prevents undefined behavior sanitizer nullptr-with-nonzero-offset warning.
|
|
#if !defined(WEBP_OFFSET_PTR)
|
|
#define WEBP_OFFSET_PTR(ptr, off) (((ptr) == NULL) ? NULL : ((ptr) + (off)))
|
|
#endif
|
|
|
|
// Regularize the definition of WEBP_SWAP_16BIT_CSP (backward compatibility)
|
|
#if !defined(WEBP_SWAP_16BIT_CSP)
|
|
#define WEBP_SWAP_16BIT_CSP 0
|
|
#endif
|
|
|
|
// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__)
|
|
#if !defined(WORDS_BIGENDIAN) && \
|
|
(defined(__BIG_ENDIAN__) || defined(_M_PPC) || \
|
|
(defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)))
|
|
#define WORDS_BIGENDIAN
|
|
#endif
|
|
|
|
typedef enum {
|
|
kSSE2,
|
|
kSSE3,
|
|
kSlowSSSE3, // special feature for slow SSSE3 architectures
|
|
kSSE4_1,
|
|
kAVX,
|
|
kAVX2,
|
|
kNEON,
|
|
kMIPS32,
|
|
kMIPSdspR2,
|
|
kMSA
|
|
} CPUFeature;
|
|
|
|
// returns true if the CPU supports the feature.
|
|
typedef int (*VP8CPUInfo)(CPUFeature feature);
|
|
|
|
#endif // WEBP_DSP_CPU_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define BPS 32 // this is the common stride for enc/dec
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WEBP_RESTRICT
|
|
|
|
// Declares a pointer with the restrict type qualifier if available.
|
|
// This allows code to hint to the compiler that only this pointer references a
|
|
// particular object or memory region within the scope of the block in which it
|
|
// is declared. This may allow for improved optimizations due to the lack of
|
|
// pointer aliasing. See also:
|
|
// https://en.cppreference.com/w/c/language/restrict
|
|
#if defined(__GNUC__)
|
|
#define WEBP_RESTRICT __restrict__
|
|
#elif defined(_MSC_VER)
|
|
#define WEBP_RESTRICT __restrict
|
|
#else
|
|
#define WEBP_RESTRICT
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Init stub generator
|
|
|
|
// Defines an init function stub to ensure each module exposes a symbol,
|
|
// avoiding a compiler warning.
|
|
#define WEBP_DSP_INIT_STUB(func) \
|
|
extern void func(void); \
|
|
void func(void) {}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Encoding
|
|
|
|
// Transforms
|
|
// VP8Idct: Does one of two inverse transforms. If do_two is set, the transforms
|
|
// will be done for (ref, in, dst) and (ref + 4, in + 16, dst + 4).
|
|
typedef void (*VP8Idct)(const uint8_t* WEBP_RESTRICT ref,
|
|
const int16_t* WEBP_RESTRICT in,
|
|
uint8_t* WEBP_RESTRICT dst, int do_two);
|
|
typedef void (*VP8Fdct)(const uint8_t* WEBP_RESTRICT src,
|
|
const uint8_t* WEBP_RESTRICT ref,
|
|
int16_t* WEBP_RESTRICT out);
|
|
typedef void (*VP8WHT)(const int16_t* WEBP_RESTRICT in,
|
|
int16_t* WEBP_RESTRICT out);
|
|
extern VP8Idct VP8ITransform;
|
|
extern VP8Fdct VP8FTransform;
|
|
extern VP8Fdct VP8FTransform2; // performs two transforms at a time
|
|
extern VP8WHT VP8FTransformWHT;
|
|
// Predictions
|
|
// *dst is the destination block. *top and *left can be NULL.
|
|
typedef void (*VP8IntraPreds)(uint8_t* WEBP_RESTRICT dst,
|
|
const uint8_t* WEBP_RESTRICT left,
|
|
const uint8_t* WEBP_RESTRICT top);
|
|
typedef void (*VP8Intra4Preds)(uint8_t* WEBP_RESTRICT dst,
|
|
const uint8_t* WEBP_RESTRICT top);
|
|
extern VP8Intra4Preds VP8EncPredLuma4;
|
|
extern VP8IntraPreds VP8EncPredLuma16;
|
|
extern VP8IntraPreds VP8EncPredChroma8;
|
|
|
|
typedef int (*VP8Metric)(const uint8_t* WEBP_RESTRICT pix,
|
|
const uint8_t* WEBP_RESTRICT ref);
|
|
extern VP8Metric VP8SSE16x16, VP8SSE16x8, VP8SSE8x8, VP8SSE4x4;
|
|
typedef int (*VP8WMetric)(const uint8_t* WEBP_RESTRICT pix,
|
|
const uint8_t* WEBP_RESTRICT ref,
|
|
const uint16_t* WEBP_RESTRICT const weights);
|
|
// The weights for VP8TDisto4x4 and VP8TDisto16x16 contain a row-major
|
|
// 4 by 4 symmetric matrix.
|
|
extern VP8WMetric VP8TDisto4x4, VP8TDisto16x16;
|
|
|
|
// Compute the average (DC) of four 4x4 blocks.
|
|
// Each sub-4x4 block #i sum is stored in dc[i].
|
|
typedef void (*VP8MeanMetric)(const uint8_t* WEBP_RESTRICT ref, uint32_t dc[4]);
|
|
extern VP8MeanMetric VP8Mean16x4;
|
|
|
|
typedef void (*VP8BlockCopy)(const uint8_t* WEBP_RESTRICT src,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
extern VP8BlockCopy VP8Copy4x4;
|
|
extern VP8BlockCopy VP8Copy16x8;
|
|
// Quantization
|
|
struct VP8Matrix; // forward declaration
|
|
typedef int (*VP8QuantizeBlock)(
|
|
int16_t in[16], int16_t out[16],
|
|
const struct VP8Matrix* WEBP_RESTRICT const mtx);
|
|
// Same as VP8QuantizeBlock, but quantizes two consecutive blocks.
|
|
typedef int (*VP8Quantize2Blocks)(
|
|
int16_t in[32], int16_t out[32],
|
|
const struct VP8Matrix* WEBP_RESTRICT const mtx);
|
|
|
|
extern VP8QuantizeBlock VP8EncQuantizeBlock;
|
|
extern VP8Quantize2Blocks VP8EncQuantize2Blocks;
|
|
|
|
// specific to 2nd transform:
|
|
typedef int (*VP8QuantizeBlockWHT)(
|
|
int16_t in[16], int16_t out[16],
|
|
const struct VP8Matrix* WEBP_RESTRICT const mtx);
|
|
extern VP8QuantizeBlockWHT VP8EncQuantizeBlockWHT;
|
|
|
|
extern const int VP8DspScan[16 + 4 + 4];
|
|
|
|
// Collect histogram for susceptibility calculation.
|
|
#define MAX_COEFF_THRESH 31 // size of histogram used by CollectHistogram.
|
|
typedef struct {
|
|
// We only need to store max_value and last_non_zero, not the distribution.
|
|
int max_value;
|
|
int last_non_zero;
|
|
} VP8Histogram;
|
|
typedef void (*VP8CHisto)(const uint8_t* WEBP_RESTRICT ref,
|
|
const uint8_t* WEBP_RESTRICT pred, int start_block,
|
|
int end_block,
|
|
VP8Histogram* WEBP_RESTRICT const histo);
|
|
extern VP8CHisto VP8CollectHistogram;
|
|
// General-purpose util function to help VP8CollectHistogram().
|
|
void VP8SetHistogramData(const int distribution[MAX_COEFF_THRESH + 1],
|
|
VP8Histogram* const histo);
|
|
|
|
// must be called before using any of the above
|
|
void VP8EncDspInit(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// cost functions (encoding)
|
|
|
|
extern const uint16_t VP8EntropyCost[256]; // 8bit fixed-point log(p)
|
|
// approximate cost per level:
|
|
extern const uint16_t VP8LevelFixedCosts[2047 /*MAX_LEVEL*/ + 1];
|
|
extern const uint8_t VP8EncBands[16 + 1];
|
|
|
|
struct VP8Residual;
|
|
typedef void (*VP8SetResidualCoeffsFunc)(
|
|
const int16_t* WEBP_RESTRICT const coeffs,
|
|
struct VP8Residual* WEBP_RESTRICT const res);
|
|
extern VP8SetResidualCoeffsFunc VP8SetResidualCoeffs;
|
|
|
|
// Cost calculation function.
|
|
typedef int (*VP8GetResidualCostFunc)(int ctx0,
|
|
const struct VP8Residual* const res);
|
|
extern VP8GetResidualCostFunc VP8GetResidualCost;
|
|
|
|
// must be called before anything using the above
|
|
void VP8EncDspCostInit(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// SSIM / PSNR utils
|
|
|
|
// struct for accumulating statistical moments
|
|
typedef struct {
|
|
uint32_t w; // sum(w_i) : sum of weights
|
|
uint32_t xm, ym; // sum(w_i * x_i), sum(w_i * y_i)
|
|
uint32_t xxm, xym, yym; // sum(w_i * x_i * x_i), etc.
|
|
} VP8DistoStats;
|
|
|
|
// Compute the final SSIM value
|
|
// The non-clipped version assumes stats->w = (2 * VP8_SSIM_KERNEL + 1)^2.
|
|
double VP8SSIMFromStats(const VP8DistoStats* const stats);
|
|
double VP8SSIMFromStatsClipped(const VP8DistoStats* const stats);
|
|
|
|
#define VP8_SSIM_KERNEL 3 // total size of the kernel: 2 * VP8_SSIM_KERNEL + 1
|
|
typedef double (*VP8SSIMGetClippedFunc)(const uint8_t* src1, int stride1,
|
|
const uint8_t* src2, int stride2,
|
|
int xo, int yo, // center position
|
|
int W, int H); // plane dimension
|
|
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
// This version is called with the guarantee that you can load 8 bytes and
|
|
// 8 rows at offset src1 and src2
|
|
typedef double (*VP8SSIMGetFunc)(const uint8_t* src1, int stride1,
|
|
const uint8_t* src2, int stride2);
|
|
|
|
extern VP8SSIMGetFunc VP8SSIMGet; // unclipped / unchecked
|
|
extern VP8SSIMGetClippedFunc VP8SSIMGetClipped; // with clipping
|
|
#endif
|
|
|
|
#if !defined(WEBP_DISABLE_STATS)
|
|
typedef uint32_t (*VP8AccumulateSSEFunc)(const uint8_t* src1,
|
|
const uint8_t* src2, int len);
|
|
extern VP8AccumulateSSEFunc VP8AccumulateSSE;
|
|
#endif
|
|
|
|
// must be called before using any of the above directly
|
|
void VP8SSIMDspInit(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Decoding
|
|
|
|
typedef void (*VP8DecIdct)(const int16_t* WEBP_RESTRICT coeffs,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
// when doing two transforms, coeffs is actually int16_t[2][16].
|
|
typedef void (*VP8DecIdct2)(const int16_t* WEBP_RESTRICT coeffs,
|
|
uint8_t* WEBP_RESTRICT dst, int do_two);
|
|
extern VP8DecIdct2 VP8Transform;
|
|
extern VP8DecIdct VP8TransformAC3;
|
|
extern VP8DecIdct VP8TransformUV;
|
|
extern VP8DecIdct VP8TransformDC;
|
|
extern VP8DecIdct VP8TransformDCUV;
|
|
extern VP8WHT VP8TransformWHT;
|
|
|
|
#define WEBP_TRANSFORM_AC3_C1 20091
|
|
#define WEBP_TRANSFORM_AC3_C2 35468
|
|
#define WEBP_TRANSFORM_AC3_MUL1(a) ((((a) * WEBP_TRANSFORM_AC3_C1) >> 16) + (a))
|
|
#define WEBP_TRANSFORM_AC3_MUL2(a) (((a) * WEBP_TRANSFORM_AC3_C2) >> 16)
|
|
|
|
// *dst is the destination block, with stride BPS. Boundary samples are
|
|
// assumed accessible when needed.
|
|
typedef void (*VP8PredFunc)(uint8_t* dst);
|
|
extern VP8PredFunc VP8PredLuma16[NUM_B_DC_MODES];
|
|
extern VP8PredFunc VP8PredChroma8[NUM_B_DC_MODES];
|
|
extern VP8PredFunc VP8PredLuma4[NUM_BMODES];
|
|
|
|
// clipping tables (for filtering)
|
|
extern const int8_t* const VP8ksclip1; // clips [-1020, 1020] to [-128, 127]
|
|
extern const int8_t* const VP8ksclip2; // clips [-112, 112] to [-16, 15]
|
|
extern const uint8_t* const VP8kclip1; // clips [-255,511] to [0,255]
|
|
extern const uint8_t* const VP8kabs0; // abs(x) for x in [-255,255]
|
|
// must be called first
|
|
void VP8InitClipTables(void);
|
|
|
|
// simple filter (only for luma)
|
|
typedef void (*VP8SimpleFilterFunc)(uint8_t* p, int stride, int thresh);
|
|
extern VP8SimpleFilterFunc VP8SimpleVFilter16;
|
|
extern VP8SimpleFilterFunc VP8SimpleHFilter16;
|
|
extern VP8SimpleFilterFunc VP8SimpleVFilter16i; // filter 3 inner edges
|
|
extern VP8SimpleFilterFunc VP8SimpleHFilter16i;
|
|
|
|
// regular filter (on both macroblock edges and inner edges)
|
|
typedef void (*VP8LumaFilterFunc)(uint8_t* luma, int stride, int thresh,
|
|
int ithresh, int hev_t);
|
|
typedef void (*VP8ChromaFilterFunc)(uint8_t* WEBP_RESTRICT u,
|
|
uint8_t* WEBP_RESTRICT v, int stride,
|
|
int thresh, int ithresh, int hev_t);
|
|
// on outer edge
|
|
extern VP8LumaFilterFunc VP8VFilter16;
|
|
extern VP8LumaFilterFunc VP8HFilter16;
|
|
extern VP8ChromaFilterFunc VP8VFilter8;
|
|
extern VP8ChromaFilterFunc VP8HFilter8;
|
|
|
|
// on inner edge
|
|
extern VP8LumaFilterFunc VP8VFilter16i; // filtering 3 inner edges altogether
|
|
extern VP8LumaFilterFunc VP8HFilter16i;
|
|
extern VP8ChromaFilterFunc VP8VFilter8i; // filtering u and v altogether
|
|
extern VP8ChromaFilterFunc VP8HFilter8i;
|
|
|
|
// Dithering. Combines dithering values (centered around 128) with dst[],
|
|
// according to: dst[] = clip(dst[] + (((dither[]-128) + 8) >> 4)
|
|
#define VP8_DITHER_DESCALE 4
|
|
#define VP8_DITHER_DESCALE_ROUNDER (1 << (VP8_DITHER_DESCALE - 1))
|
|
#define VP8_DITHER_AMP_BITS 7
|
|
#define VP8_DITHER_AMP_CENTER (1 << VP8_DITHER_AMP_BITS)
|
|
extern void (*VP8DitherCombine8x8)(const uint8_t* WEBP_RESTRICT dither,
|
|
uint8_t* WEBP_RESTRICT dst, int dst_stride);
|
|
|
|
// must be called before anything using the above
|
|
void VP8DspInit(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebP I/O
|
|
|
|
#define FANCY_UPSAMPLING // undefined to remove fancy upsampling support
|
|
|
|
// Convert a pair of y/u/v lines together to the output rgb/a colorspace.
|
|
// bottom_y can be NULL if only one line of output is needed (at top/bottom).
|
|
typedef void (*WebPUpsampleLinePairFunc)(
|
|
const uint8_t* WEBP_RESTRICT top_y, const uint8_t* WEBP_RESTRICT bottom_y,
|
|
const uint8_t* WEBP_RESTRICT top_u, const uint8_t* WEBP_RESTRICT top_v,
|
|
const uint8_t* WEBP_RESTRICT cur_u, const uint8_t* WEBP_RESTRICT cur_v,
|
|
uint8_t* WEBP_RESTRICT top_dst, uint8_t* WEBP_RESTRICT bottom_dst, int len);
|
|
|
|
#ifdef FANCY_UPSAMPLING
|
|
|
|
// Fancy upsampling functions to convert YUV to RGB(A) modes
|
|
extern WebPUpsampleLinePairFunc WebPUpsamplers[MODE_LAST];
|
|
|
|
#endif // FANCY_UPSAMPLING
|
|
|
|
// Per-row point-sampling methods.
|
|
typedef void (*WebPSamplerRowFunc)(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst, int len);
|
|
// Generic function to apply 'WebPSamplerRowFunc' to the whole plane:
|
|
void WebPSamplerProcessPlane(const uint8_t* WEBP_RESTRICT y, int y_stride,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v, int uv_stride,
|
|
uint8_t* WEBP_RESTRICT dst, int dst_stride,
|
|
int width, int height, WebPSamplerRowFunc func);
|
|
|
|
// Sampling functions to convert rows of YUV to RGB(A)
|
|
extern WebPSamplerRowFunc WebPSamplers[MODE_LAST];
|
|
|
|
// General function for converting two lines of ARGB or RGBA.
|
|
// 'alpha_is_last' should be true if 0xff000000 is stored in memory as
|
|
// as 0x00, 0x00, 0x00, 0xff (little endian).
|
|
WebPUpsampleLinePairFunc WebPGetLinePairConverter(int alpha_is_last);
|
|
|
|
// YUV444->RGB converters
|
|
typedef void (*WebPYUV444Converter)(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst, int len);
|
|
|
|
extern WebPYUV444Converter WebPYUV444Converters[MODE_LAST];
|
|
|
|
// Must be called before using the WebPUpsamplers[] (and for premultiplied
|
|
// colorspaces like rgbA, rgbA4444, etc)
|
|
void WebPInitUpsamplers(void);
|
|
// Must be called before using WebPSamplers[]
|
|
void WebPInitSamplers(void);
|
|
// Must be called before using WebPYUV444Converters[]
|
|
void WebPInitYUV444Converters(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// ARGB -> YUV converters
|
|
|
|
// Convert ARGB samples to luma Y.
|
|
extern void (*WebPConvertARGBToY)(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT y, int width);
|
|
// Convert ARGB samples to U/V with downsampling. do_store should be '1' for
|
|
// even lines and '0' for odd ones. 'src_width' is the original width, not
|
|
// the U/V one.
|
|
extern void (*WebPConvertARGBToUV)(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT u,
|
|
uint8_t* WEBP_RESTRICT v, int src_width,
|
|
int do_store);
|
|
|
|
// Convert a row of accumulated (four-values) of rgba32 toward U/V
|
|
extern void (*WebPConvertRGBA32ToUV)(const uint16_t* WEBP_RESTRICT rgb,
|
|
uint8_t* WEBP_RESTRICT u,
|
|
uint8_t* WEBP_RESTRICT v, int width);
|
|
|
|
// Convert RGB or BGR to Y. Step is 3 or 4. If step is 4, data is RGBA or BGRA.
|
|
extern void (*WebPConvertRGBToY)(const uint8_t* WEBP_RESTRICT rgb,
|
|
uint8_t* WEBP_RESTRICT y, int width, int step);
|
|
extern void (*WebPConvertBGRToY)(const uint8_t* WEBP_RESTRICT bgr,
|
|
uint8_t* WEBP_RESTRICT y, int width, int step);
|
|
|
|
// used for plain-C fallback.
|
|
extern void WebPConvertARGBToUV_C(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT u,
|
|
uint8_t* WEBP_RESTRICT v, int src_width,
|
|
int do_store);
|
|
extern void WebPConvertRGBA32ToUV_C(const uint16_t* WEBP_RESTRICT rgb,
|
|
uint8_t* WEBP_RESTRICT u,
|
|
uint8_t* WEBP_RESTRICT v, int width);
|
|
|
|
// Must be called before using the above.
|
|
void WebPInitConvertARGBToYUV(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rescaler
|
|
|
|
struct WebPRescaler;
|
|
|
|
// Import a row of data and save its contribution in the rescaler.
|
|
// 'channel' denotes the channel number to be imported. 'Expand' corresponds to
|
|
// the wrk->x_expand case. Otherwise, 'Shrink' is to be used.
|
|
typedef void (*WebPRescalerImportRowFunc)(
|
|
struct WebPRescaler* WEBP_RESTRICT const wrk,
|
|
const uint8_t* WEBP_RESTRICT src);
|
|
|
|
extern WebPRescalerImportRowFunc WebPRescalerImportRowExpand;
|
|
extern WebPRescalerImportRowFunc WebPRescalerImportRowShrink;
|
|
|
|
// Export one row (starting at x_out position) from rescaler.
|
|
// 'Expand' corresponds to the wrk->y_expand case.
|
|
// Otherwise 'Shrink' is to be used
|
|
typedef void (*WebPRescalerExportRowFunc)(struct WebPRescaler* const wrk);
|
|
extern WebPRescalerExportRowFunc WebPRescalerExportRowExpand;
|
|
extern WebPRescalerExportRowFunc WebPRescalerExportRowShrink;
|
|
|
|
// Plain-C implementation, as fall-back.
|
|
extern void WebPRescalerImportRowExpand_C(
|
|
struct WebPRescaler* WEBP_RESTRICT const wrk,
|
|
const uint8_t* WEBP_RESTRICT src);
|
|
extern void WebPRescalerImportRowShrink_C(
|
|
struct WebPRescaler* WEBP_RESTRICT const wrk,
|
|
const uint8_t* WEBP_RESTRICT src);
|
|
extern void WebPRescalerExportRowExpand_C(struct WebPRescaler* const wrk);
|
|
extern void WebPRescalerExportRowShrink_C(struct WebPRescaler* const wrk);
|
|
|
|
// Main entry calls:
|
|
extern void WebPRescalerImportRow(struct WebPRescaler* WEBP_RESTRICT const wrk,
|
|
const uint8_t* WEBP_RESTRICT src);
|
|
// Export one row (starting at x_out position) from rescaler.
|
|
extern void WebPRescalerExportRow(struct WebPRescaler* const wrk);
|
|
|
|
// Must be called first before using the above.
|
|
void WebPRescalerDspInit(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Utilities for processing transparent channel.
|
|
|
|
// Apply alpha pre-multiply on an rgba, bgra or argb plane of size w * h.
|
|
// alpha_first should be 0 for argb, 1 for rgba or bgra (where alpha is last).
|
|
extern void (*WebPApplyAlphaMultiply)(uint8_t* rgba, int alpha_first, int w,
|
|
int h, int stride);
|
|
|
|
// Same, buf specifically for RGBA4444 format
|
|
extern void (*WebPApplyAlphaMultiply4444)(uint8_t* rgba4444, int w, int h,
|
|
int stride);
|
|
|
|
// Dispatch the values from alpha[] plane to the ARGB destination 'dst'.
|
|
// Returns true if alpha[] plane has non-trivial values different from 0xff.
|
|
extern int (*WebPDispatchAlpha)(const uint8_t* WEBP_RESTRICT alpha,
|
|
int alpha_stride, int width, int height,
|
|
uint8_t* WEBP_RESTRICT dst, int dst_stride);
|
|
|
|
// Transfer packed 8b alpha[] values to green channel in dst[], zero'ing the
|
|
// A/R/B values. 'dst_stride' is the stride for dst[] in uint32_t units.
|
|
extern void (*WebPDispatchAlphaToGreen)(const uint8_t* WEBP_RESTRICT alpha,
|
|
int alpha_stride, int width, int height,
|
|
uint32_t* WEBP_RESTRICT dst,
|
|
int dst_stride);
|
|
|
|
// Extract the alpha values from 32b values in argb[] and pack them into alpha[]
|
|
// (this is the opposite of WebPDispatchAlpha).
|
|
// Returns true if there's only trivial 0xff alpha values.
|
|
extern int (*WebPExtractAlpha)(const uint8_t* WEBP_RESTRICT argb,
|
|
int argb_stride, int width, int height,
|
|
uint8_t* WEBP_RESTRICT alpha, int alpha_stride);
|
|
|
|
// Extract the green values from 32b values in argb[] and pack them into alpha[]
|
|
// (this is the opposite of WebPDispatchAlphaToGreen).
|
|
extern void (*WebPExtractGreen)(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT alpha, int size);
|
|
|
|
// Pre-Multiply operation transforms x into x * A / 255 (where x=Y,R,G or B).
|
|
// Un-Multiply operation transforms x into x * 255 / A.
|
|
|
|
// Pre-Multiply or Un-Multiply (if 'inverse' is true) argb values in a row.
|
|
extern void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse);
|
|
|
|
// Same a WebPMultARGBRow(), but for several rows.
|
|
void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows,
|
|
int inverse);
|
|
|
|
// Same for a row of single values, with side alpha values.
|
|
extern void (*WebPMultRow)(uint8_t* WEBP_RESTRICT const ptr,
|
|
const uint8_t* WEBP_RESTRICT const alpha, int width,
|
|
int inverse);
|
|
|
|
// Same a WebPMultRow(), but for several 'num_rows' rows.
|
|
void WebPMultRows(uint8_t* WEBP_RESTRICT ptr, int stride,
|
|
const uint8_t* WEBP_RESTRICT alpha, int alpha_stride,
|
|
int width, int num_rows, int inverse);
|
|
|
|
// Plain-C versions, used as fallback by some implementations.
|
|
void WebPMultRow_C(uint8_t* WEBP_RESTRICT const ptr,
|
|
const uint8_t* WEBP_RESTRICT const alpha, int width,
|
|
int inverse);
|
|
void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse);
|
|
|
|
#ifdef WORDS_BIGENDIAN
|
|
// ARGB packing function: a/r/g/b input is rgba or bgra order.
|
|
extern void (*WebPPackARGB)(const uint8_t* WEBP_RESTRICT a,
|
|
const uint8_t* WEBP_RESTRICT r,
|
|
const uint8_t* WEBP_RESTRICT g,
|
|
const uint8_t* WEBP_RESTRICT b, int len,
|
|
uint32_t* WEBP_RESTRICT out);
|
|
#endif
|
|
|
|
// RGB packing function. 'step' can be 3 or 4. r/g/b input is rgb or bgr order.
|
|
extern void (*WebPPackRGB)(const uint8_t* WEBP_RESTRICT r,
|
|
const uint8_t* WEBP_RESTRICT g,
|
|
const uint8_t* WEBP_RESTRICT b, int len, int step,
|
|
uint32_t* WEBP_RESTRICT out);
|
|
|
|
// This function returns true if src[i] contains a value different from 0xff.
|
|
extern int (*WebPHasAlpha8b)(const uint8_t* src, int length);
|
|
// This function returns true if src[4*i] contains a value different from 0xff.
|
|
extern int (*WebPHasAlpha32b)(const uint8_t* src, int length);
|
|
// replaces transparent values in src[] by 'color'.
|
|
extern void (*WebPAlphaReplace)(uint32_t* src, int length, uint32_t color);
|
|
|
|
// To be called first before using the above.
|
|
void WebPInitAlphaProcessing(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Filter functions
|
|
|
|
typedef enum { // Filter types.
|
|
WEBP_FILTER_NONE = 0,
|
|
WEBP_FILTER_HORIZONTAL,
|
|
WEBP_FILTER_VERTICAL,
|
|
WEBP_FILTER_GRADIENT,
|
|
WEBP_FILTER_LAST = WEBP_FILTER_GRADIENT + 1, // end marker
|
|
WEBP_FILTER_BEST, // meta-types
|
|
WEBP_FILTER_FAST
|
|
} WEBP_FILTER_TYPE;
|
|
|
|
typedef void (*WebPFilterFunc)(const uint8_t* WEBP_RESTRICT in, int width,
|
|
int height, int stride,
|
|
uint8_t* WEBP_RESTRICT out);
|
|
// In-place un-filtering.
|
|
// Warning! 'prev_line' pointer can be equal to 'cur_line' or 'preds'.
|
|
typedef void (*WebPUnfilterFunc)(const uint8_t* prev_line, const uint8_t* preds,
|
|
uint8_t* cur_line, int width);
|
|
|
|
// Filter the given data using the given predictor.
|
|
// 'in' corresponds to a 2-dimensional pixel array of size (stride * height)
|
|
// in raster order.
|
|
// 'stride' is number of bytes per scan line (with possible padding).
|
|
// 'out' should be pre-allocated.
|
|
extern WebPFilterFunc WebPFilters[WEBP_FILTER_LAST];
|
|
|
|
// In-place reconstruct the original data from the given filtered data.
|
|
// The reconstruction will be done for 'num_rows' rows starting from 'row'
|
|
// (assuming rows upto 'row - 1' are already reconstructed).
|
|
extern WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST];
|
|
|
|
// To be called first before using the above.
|
|
void VP8FiltersInit(void);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DSP_DSP_H_
|
|
/* >>> src/utils/filters_utils.h */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Spatial prediction using various filters
|
|
//
|
|
// Author: Urvang (urvang@google.com)
|
|
|
|
#ifndef WEBP_UTILS_FILTERS_UTILS_H_
|
|
#define WEBP_UTILS_FILTERS_UTILS_H_
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// Fast estimate of a potentially good filter.
|
|
WEBP_FILTER_TYPE WebPEstimateBestFilter(
|
|
const uint8_t* WEBP_COUNTED_BY((size_t)width* height) data, int width,
|
|
int height);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_FILTERS_UTILS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
struct VP8LDecoder; // Defined in dec/vp8li.h.
|
|
|
|
typedef struct ALPHDecoder ALPHDecoder;
|
|
struct ALPHDecoder {
|
|
int width;
|
|
int height;
|
|
int method;
|
|
WEBP_FILTER_TYPE filter;
|
|
int pre_processing;
|
|
struct VP8LDecoder* vp8l_dec;
|
|
VP8Io io;
|
|
int use_8b_decode; // Although alpha channel requires only 1 byte per
|
|
// pixel, sometimes VP8LDecoder may need to allocate
|
|
// 4 bytes per pixel internally during decode.
|
|
uint8_t* output;
|
|
const uint8_t* prev_line; // last output row (or NULL)
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// internal functions. Not public.
|
|
|
|
// Deallocate memory associated to dec->alpha_plane decoding
|
|
void WebPDeallocateAlphaMemory(VP8Decoder* const dec);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DEC_ALPHAI_DEC_H_
|
|
/* >>> src/dec/vp8i_dec.h */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// VP8 decoder: internal header.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_DEC_VP8I_DEC_H_
|
|
#define WEBP_DEC_VP8I_DEC_H_
|
|
|
|
#include <string.h> // for memcpy()
|
|
|
|
/* >>> src/dec/vp8li_dec.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Lossless decoder: internal header.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
// Vikas Arora(vikaas.arora@gmail.com)
|
|
|
|
#ifndef WEBP_DEC_VP8LI_DEC_H_
|
|
#define WEBP_DEC_VP8LI_DEC_H_
|
|
|
|
#include <string.h> // for memcpy()
|
|
|
|
/* >>> src/utils/bit_reader_utils.h */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Boolean decoder
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
// Vikas Arora (vikaas.arora@gmail.com)
|
|
|
|
#ifndef WEBP_UTILS_BIT_READER_UTILS_H_
|
|
#define WEBP_UTILS_BIT_READER_UTILS_H_
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
#ifdef _MSC_VER
|
|
#include <stdlib.h> // _byteswap_ulong
|
|
#endif
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// Warning! This macro triggers quite some MACRO wizardry around func signature!
|
|
#if !defined(BITTRACE)
|
|
#define BITTRACE 0 // 0 = off, 1 = print bits, 2 = print bytes
|
|
#endif
|
|
|
|
#if (BITTRACE > 0)
|
|
struct VP8BitReader;
|
|
extern void BitTrace(const struct VP8BitReader* const br, const char label[]);
|
|
#define BT_TRACK(br) BitTrace(br, label)
|
|
#define VP8Get(BR, L) VP8GetValue(BR, 1, L)
|
|
#else
|
|
#define BT_TRACK(br)
|
|
// We'll REMOVE the 'const char label[]' from all signatures and calls (!!):
|
|
#define VP8GetValue(BR, N, L) VP8GetValue(BR, N)
|
|
#define VP8Get(BR, L) VP8GetValue(BR, 1, L)
|
|
#define VP8GetSignedValue(BR, N, L) VP8GetSignedValue(BR, N)
|
|
#define VP8GetBit(BR, P, L) VP8GetBit(BR, P)
|
|
#define VP8GetBitAlt(BR, P, L) VP8GetBitAlt(BR, P)
|
|
#define VP8GetSigned(BR, V, L) VP8GetSigned(BR, V)
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// The Boolean decoder needs to maintain infinite precision on the 'value'
|
|
// field. However, since 'range' is only 8bit, we only need an active window of
|
|
// 8 bits for 'value". Left bits (MSB) gets zeroed and shifted away when
|
|
// 'value' falls below 128, 'range' is updated, and fresh bits read from the
|
|
// bitstream are brought in as LSB. To avoid reading the fresh bits one by one
|
|
// (slow), we cache BITS of them ahead. The total of (BITS + 8) bits must fit
|
|
// into a natural register (with type bit_t). To fetch BITS bits from bitstream
|
|
// we use a type lbit_t.
|
|
//
|
|
// BITS can be any multiple of 8 from 8 to 56 (inclusive).
|
|
// Pick values that fit natural register size.
|
|
|
|
#if defined(__i386__) || defined(_M_IX86) // x86 32bit
|
|
#define BITS 24
|
|
#elif defined(__x86_64__) || defined(_M_X64) // x86 64bit
|
|
#define BITS 56
|
|
#elif defined(__arm__) || defined(_M_ARM) // ARM
|
|
#define BITS 24
|
|
#elif WEBP_AARCH64 // ARM 64bit
|
|
#define BITS 56
|
|
#elif defined(__mips__) // MIPS
|
|
#define BITS 24
|
|
#elif defined(__wasm__) // WASM
|
|
#define BITS 56
|
|
#else // reasonable default
|
|
#define BITS 24
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Derived types and constants:
|
|
// bit_t = natural register type for storing 'value' (which is BITS+8 bits)
|
|
// range_t = register for 'range' (which is 8bits only)
|
|
|
|
#if (BITS > 24)
|
|
typedef uint64_t bit_t;
|
|
#else
|
|
typedef uint32_t bit_t;
|
|
#endif
|
|
|
|
typedef uint32_t range_t;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Bitreader
|
|
|
|
typedef struct VP8BitReader VP8BitReader;
|
|
struct VP8BitReader {
|
|
// boolean decoder (keep the field ordering as is!)
|
|
bit_t value; // current value
|
|
range_t range; // current range minus 1. In [127, 254] interval.
|
|
int bits; // number of valid bits left
|
|
// read buffer
|
|
const uint8_t* WEBP_ENDED_BY(buf_end) buf; // next byte to be read
|
|
const uint8_t* buf_end; // end of read buffer
|
|
// max packed-read position on buffer
|
|
const uint8_t* WEBP_UNSAFE_INDEXABLE buf_max;
|
|
int eof; // true if input is exhausted
|
|
};
|
|
|
|
// Initialize the bit reader and the boolean decoder.
|
|
void VP8InitBitReader(VP8BitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(size) start,
|
|
size_t size);
|
|
// Sets the working read buffer.
|
|
void VP8BitReaderSetBuffer(VP8BitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(size) start,
|
|
size_t size);
|
|
|
|
// Update internal pointers to displace the byte buffer by the
|
|
// relative offset 'offset'.
|
|
void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset);
|
|
|
|
// return the next value made of 'num_bits' bits
|
|
uint32_t VP8GetValue(VP8BitReader* const br, int num_bits, const char label[]);
|
|
|
|
// return the next value with sign-extension.
|
|
int32_t VP8GetSignedValue(VP8BitReader* const br, int num_bits,
|
|
const char label[]);
|
|
|
|
// bit_reader_inl.h will implement the following methods:
|
|
// static WEBP_INLINE int VP8GetBit(VP8BitReader* const br, int prob, ...)
|
|
// static WEBP_INLINE int VP8GetSigned(VP8BitReader* const br, int v, ...)
|
|
// and should be included by the .c files that actually need them.
|
|
// This is to avoid recompiling the whole library whenever this file is touched,
|
|
// and also allowing platform-specific ad-hoc hacks.
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Bitreader for lossless format
|
|
|
|
// maximum number of bits (inclusive) the bit-reader can handle:
|
|
#define VP8L_MAX_NUM_BIT_READ 24
|
|
|
|
#define VP8L_LBITS 64 // Number of bits prefetched (= bit-size of vp8l_val_t).
|
|
#define VP8L_WBITS 32 // Minimum number of bytes ready after VP8LFillBitWindow.
|
|
|
|
typedef uint64_t vp8l_val_t; // right now, this bit-reader can only use 64bit.
|
|
|
|
typedef struct {
|
|
vp8l_val_t val; // pre-fetched bits
|
|
const uint8_t* WEBP_COUNTED_BY(len) buf; // input byte buffer
|
|
size_t len; // buffer length
|
|
size_t pos; // byte position in buf
|
|
int bit_pos; // current bit-reading position in val
|
|
int eos; // true if a bit was read past the end of buffer
|
|
} VP8LBitReader;
|
|
|
|
void VP8LInitBitReader(VP8LBitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(length) start,
|
|
size_t length);
|
|
|
|
// Sets a new data buffer.
|
|
void VP8LBitReaderSetBuffer(VP8LBitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(length) buffer,
|
|
size_t length);
|
|
|
|
// Reads the specified number of bits from read buffer.
|
|
// Flags an error in case end_of_stream or n_bits is more than the allowed limit
|
|
// of VP8L_MAX_NUM_BIT_READ (inclusive).
|
|
// Flags 'eos' if this read attempt is going to cross the read buffer.
|
|
uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits);
|
|
|
|
// Return the prefetched bits, so they can be looked up.
|
|
static WEBP_INLINE uint32_t VP8LPrefetchBits(VP8LBitReader* const br) {
|
|
return (uint32_t)(br->val >> (br->bit_pos & (VP8L_LBITS - 1)));
|
|
}
|
|
|
|
// Returns true if there was an attempt at reading bit past the end of
|
|
// the buffer. Doesn't set br->eos flag.
|
|
static WEBP_INLINE int VP8LIsEndOfStream(const VP8LBitReader* const br) {
|
|
assert(br->pos <= br->len);
|
|
return br->eos || ((br->pos == br->len) && (br->bit_pos > VP8L_LBITS));
|
|
}
|
|
|
|
// For jumping over a number of bits in the bit stream when accessed with
|
|
// VP8LPrefetchBits and VP8LFillBitWindow.
|
|
// This function does *not* set br->eos, since it's speed-critical.
|
|
// Use with extreme care!
|
|
static WEBP_INLINE void VP8LSetBitPos(VP8LBitReader* const br, int val) {
|
|
br->bit_pos = val;
|
|
}
|
|
|
|
// Advances the read buffer by 4 bytes to make room for reading next 32 bits.
|
|
// Speed critical, but infrequent part of the code can be non-inlined.
|
|
extern void VP8LDoFillBitWindow(VP8LBitReader* const br);
|
|
static WEBP_INLINE void VP8LFillBitWindow(VP8LBitReader* const br) {
|
|
if (br->bit_pos >= VP8L_WBITS) VP8LDoFillBitWindow(br);
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_BIT_READER_UTILS_H_
|
|
/* >>> src/utils/color_cache_utils.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Color Cache for WebP Lossless
|
|
//
|
|
// Authors: Jyrki Alakuijala (jyrki@google.com)
|
|
// Urvang Joshi (urvang@google.com)
|
|
|
|
#ifndef WEBP_UTILS_COLOR_CACHE_UTILS_H_
|
|
#define WEBP_UTILS_COLOR_CACHE_UTILS_H_
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// Main color cache struct.
|
|
typedef struct {
|
|
uint32_t* WEBP_COUNTED_BY_OR_NULL(1u << hash_bits) colors; // color entries
|
|
int hash_shift; // Hash shift: 32 - 'hash_bits'.
|
|
int hash_bits;
|
|
} VP8LColorCache;
|
|
|
|
static const uint32_t kHashMul = 0x1e35a7bdu;
|
|
|
|
static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE int VP8LHashPix(
|
|
uint32_t argb, int shift) {
|
|
return (int)((argb * kHashMul) >> shift);
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t VP8LColorCacheLookup(const VP8LColorCache* const cc,
|
|
uint32_t key) {
|
|
assert((key >> cc->hash_bits) == 0u);
|
|
return cc->colors[key];
|
|
}
|
|
|
|
static WEBP_INLINE void VP8LColorCacheSet(const VP8LColorCache* const cc,
|
|
uint32_t key, uint32_t argb) {
|
|
assert((key >> cc->hash_bits) == 0u);
|
|
cc->colors[key] = argb;
|
|
}
|
|
|
|
static WEBP_INLINE void VP8LColorCacheInsert(const VP8LColorCache* const cc,
|
|
uint32_t argb) {
|
|
const int key = VP8LHashPix(argb, cc->hash_shift);
|
|
cc->colors[key] = argb;
|
|
}
|
|
|
|
static WEBP_INLINE int VP8LColorCacheGetIndex(const VP8LColorCache* const cc,
|
|
uint32_t argb) {
|
|
return VP8LHashPix(argb, cc->hash_shift);
|
|
}
|
|
|
|
// Return the key if cc contains argb, and -1 otherwise.
|
|
static WEBP_INLINE int VP8LColorCacheContains(const VP8LColorCache* const cc,
|
|
uint32_t argb) {
|
|
const int key = VP8LHashPix(argb, cc->hash_shift);
|
|
return (cc->colors[key] == argb) ? key : -1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Initializes the color cache with 'hash_bits' bits for the keys.
|
|
// Returns false in case of memory error.
|
|
int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits);
|
|
|
|
void VP8LColorCacheCopy(const VP8LColorCache* const src,
|
|
VP8LColorCache* const dst);
|
|
|
|
// Delete the memory associated to color cache.
|
|
void VP8LColorCacheClear(VP8LColorCache* const color_cache);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_COLOR_CACHE_UTILS_H_
|
|
/* >>> src/utils/huffman_utils.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Utilities for building and looking up Huffman trees.
|
|
//
|
|
// Author: Urvang Joshi (urvang@google.com)
|
|
|
|
#ifndef WEBP_UTILS_HUFFMAN_UTILS_H_
|
|
#define WEBP_UTILS_HUFFMAN_UTILS_H_
|
|
|
|
#include <assert.h>
|
|
|
|
/* >>> src/webp/format_constants.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Internal header for constants related to WebP file format.
|
|
//
|
|
// Author: Urvang (urvang@google.com)
|
|
|
|
#ifndef WEBP_WEBP_FORMAT_CONSTANTS_H_
|
|
#define WEBP_WEBP_FORMAT_CONSTANTS_H_
|
|
|
|
// Create fourcc of the chunk from the chunk tag characters.
|
|
#define MKFOURCC(a, b, c, d) ((a) | (b) << 8 | (c) << 16 | (uint32_t)(d) << 24)
|
|
|
|
// VP8 related constants.
|
|
#define VP8_SIGNATURE 0x9d012a // Signature in VP8 data.
|
|
#define VP8_MAX_PARTITION0_SIZE (1 << 19) // max size of mode partition
|
|
#define VP8_MAX_PARTITION_SIZE (1 << 24) // max size for token partition
|
|
#define VP8_FRAME_HEADER_SIZE 10 // Size of the frame header within VP8 data.
|
|
|
|
// VP8L related constants.
|
|
#define VP8L_SIGNATURE_SIZE 1 // VP8L signature size.
|
|
#define VP8L_MAGIC_BYTE 0x2f // VP8L signature byte.
|
|
#define VP8L_IMAGE_SIZE_BITS \
|
|
14 // Number of bits used to store width and height.
|
|
#define VP8L_VERSION_BITS 3 // 3 bits reserved for version.
|
|
#define VP8L_VERSION 0 // version 0
|
|
#define VP8L_FRAME_HEADER_SIZE 5 // Size of the VP8L frame header.
|
|
|
|
#define MAX_PALETTE_SIZE 256
|
|
#define MAX_CACHE_BITS 11
|
|
#define HUFFMAN_CODES_PER_META_CODE 5
|
|
#define ARGB_BLACK 0xff000000
|
|
|
|
#define DEFAULT_CODE_LENGTH 8
|
|
#define MAX_ALLOWED_CODE_LENGTH 15
|
|
|
|
#define NUM_LITERAL_CODES 256
|
|
#define NUM_LENGTH_CODES 24
|
|
#define NUM_DISTANCE_CODES 40
|
|
#define CODE_LENGTH_CODES 19
|
|
|
|
#define MIN_HUFFMAN_BITS 2 // min number of Huffman bits
|
|
#define NUM_HUFFMAN_BITS 3
|
|
|
|
// the maximum number of bits defining a transform is
|
|
// MIN_TRANSFORM_BITS + (1 << NUM_TRANSFORM_BITS) - 1
|
|
#define MIN_TRANSFORM_BITS 2
|
|
#define NUM_TRANSFORM_BITS 3
|
|
|
|
#define TRANSFORM_PRESENT \
|
|
1 // The bit to be written when next data to be read is a transform.
|
|
#define NUM_TRANSFORMS 4 // Maximum number of allowed transform in a bitstream.
|
|
typedef enum {
|
|
PREDICTOR_TRANSFORM = 0,
|
|
CROSS_COLOR_TRANSFORM = 1,
|
|
SUBTRACT_GREEN_TRANSFORM = 2,
|
|
COLOR_INDEXING_TRANSFORM = 3
|
|
} VP8LImageTransformType;
|
|
|
|
// Alpha related constants.
|
|
#define ALPHA_HEADER_LEN 1
|
|
#define ALPHA_NO_COMPRESSION 0
|
|
#define ALPHA_LOSSLESS_COMPRESSION 1
|
|
#define ALPHA_PREPROCESSED_LEVELS 1
|
|
|
|
// Mux related constants.
|
|
#define TAG_SIZE 4 // Size of a chunk tag (e.g. "VP8L").
|
|
#define CHUNK_SIZE_BYTES 4 // Size needed to store chunk's size.
|
|
#define CHUNK_HEADER_SIZE 8 // Size of a chunk header.
|
|
#define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP").
|
|
#define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk.
|
|
#define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk.
|
|
#define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk.
|
|
|
|
#define MAX_CANVAS_SIZE (1 << 24) // 24-bit max for VP8X width/height.
|
|
#define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height.
|
|
#define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count
|
|
#define MAX_DURATION (1 << 24) // maximum duration
|
|
#define MAX_POSITION_OFFSET (1 << 24) // maximum frame x/y offset
|
|
|
|
// Maximum chunk payload is such that adding the header and padding won't
|
|
// overflow a uint32_t.
|
|
#define MAX_CHUNK_PAYLOAD (~0U - CHUNK_HEADER_SIZE - 1)
|
|
|
|
#endif // WEBP_WEBP_FORMAT_CONSTANTS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define HUFFMAN_TABLE_BITS 8
|
|
#define HUFFMAN_TABLE_MASK ((1 << HUFFMAN_TABLE_BITS) - 1)
|
|
|
|
#define LENGTHS_TABLE_BITS 7
|
|
#define LENGTHS_TABLE_MASK ((1 << LENGTHS_TABLE_BITS) - 1)
|
|
|
|
// Huffman lookup table entry
|
|
typedef struct {
|
|
uint8_t bits; // number of bits used for this symbol
|
|
uint16_t value; // symbol value or table offset
|
|
} HuffmanCode;
|
|
|
|
// long version for holding 32b values
|
|
typedef struct {
|
|
int bits; // number of bits used for this symbol,
|
|
// or an impossible value if not a literal code.
|
|
uint32_t value; // 32b packed ARGB value if literal,
|
|
// or non-literal symbol otherwise
|
|
} HuffmanCode32;
|
|
|
|
// Contiguous memory segment of HuffmanCodes.
|
|
typedef struct HuffmanTablesSegment {
|
|
HuffmanCode* WEBP_COUNTED_BY_OR_NULL(size) start;
|
|
// Pointer to where we are writing into the segment. Starts at 'start' and
|
|
// cannot go beyond 'start' + 'size'.
|
|
HuffmanCode* WEBP_UNSAFE_INDEXABLE curr_table;
|
|
// Pointer to the next segment in the chain.
|
|
struct HuffmanTablesSegment* next;
|
|
int size;
|
|
} HuffmanTablesSegment;
|
|
|
|
// Chained memory segments of HuffmanCodes.
|
|
typedef struct HuffmanTables {
|
|
HuffmanTablesSegment root;
|
|
// Currently processed segment. At first, this is 'root'.
|
|
HuffmanTablesSegment* curr_segment;
|
|
} HuffmanTables;
|
|
|
|
// Allocates a HuffmanTables with 'size' contiguous HuffmanCodes. Returns 0 on
|
|
// memory allocation error, 1 otherwise.
|
|
WEBP_NODISCARD int VP8LHuffmanTablesAllocate(int size,
|
|
HuffmanTables* huffman_tables);
|
|
void VP8LHuffmanTablesDeallocate(HuffmanTables* const huffman_tables);
|
|
|
|
#define HUFFMAN_PACKED_BITS 6
|
|
#define HUFFMAN_PACKED_TABLE_SIZE (1u << HUFFMAN_PACKED_BITS)
|
|
|
|
// Huffman table group.
|
|
// Includes special handling for the following cases:
|
|
// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN)
|
|
// - is_trivial_code: only 1 code (no bit is read from bitstream)
|
|
// - use_packed_table: few enough literal symbols, so all the bit codes
|
|
// can fit into a small look-up table packed_table[]
|
|
// The common literal base, if applicable, is stored in 'literal_arb'.
|
|
typedef struct HTreeGroup HTreeGroup;
|
|
struct HTreeGroup {
|
|
HuffmanCode* htrees[HUFFMAN_CODES_PER_META_CODE];
|
|
int is_trivial_literal; // True, if huffman trees for Red, Blue & Alpha
|
|
// Symbols are trivial (have a single code).
|
|
uint32_t literal_arb; // If is_trivial_literal is true, this is the
|
|
// ARGB value of the pixel, with Green channel
|
|
// being set to zero.
|
|
int is_trivial_code; // true if is_trivial_literal with only one code
|
|
int use_packed_table; // use packed table below for short literal code
|
|
// table mapping input bits to a packed values, or escape case to literal code
|
|
HuffmanCode32 packed_table[HUFFMAN_PACKED_TABLE_SIZE];
|
|
};
|
|
|
|
// Creates the instance of HTreeGroup with specified number of tree-groups.
|
|
WEBP_NODISCARD HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups);
|
|
|
|
// Releases the memory allocated for HTreeGroup.
|
|
void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups);
|
|
|
|
// Builds Huffman lookup table assuming code lengths are in symbol order.
|
|
// The 'code_lengths' is pre-allocated temporary memory buffer used for creating
|
|
// the huffman table.
|
|
// Returns built table size or 0 in case of error (invalid tree or
|
|
// memory error).
|
|
WEBP_NODISCARD int VP8LBuildHuffmanTable(
|
|
HuffmanTables* const root_table, int root_bits,
|
|
const int WEBP_COUNTED_BY(code_lengths_size) code_lengths[],
|
|
int code_lengths_size);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_HUFFMAN_UTILS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef enum { READ_DATA = 0, READ_HDR = 1, READ_DIM = 2 } VP8LDecodeState;
|
|
|
|
typedef struct VP8LTransform VP8LTransform;
|
|
struct VP8LTransform {
|
|
VP8LImageTransformType type; // transform type.
|
|
int bits; // subsampling bits defining transform window.
|
|
int xsize; // transform window X index.
|
|
int ysize; // transform window Y index.
|
|
uint32_t* data; // transform data.
|
|
};
|
|
|
|
typedef struct {
|
|
int color_cache_size;
|
|
VP8LColorCache color_cache;
|
|
VP8LColorCache saved_color_cache; // for incremental
|
|
|
|
int huffman_mask;
|
|
int huffman_subsample_bits;
|
|
int huffman_xsize;
|
|
uint32_t* huffman_image;
|
|
int num_htree_groups;
|
|
HTreeGroup* htree_groups;
|
|
HuffmanTables huffman_tables;
|
|
} VP8LMetadata;
|
|
|
|
typedef struct VP8LDecoder VP8LDecoder;
|
|
struct VP8LDecoder {
|
|
VP8StatusCode status;
|
|
VP8LDecodeState state;
|
|
VP8Io* io;
|
|
|
|
const WebPDecBuffer* output; // shortcut to io->opaque->output
|
|
|
|
uint32_t* pixels; // Internal data: either uint8_t* for alpha
|
|
// or uint32_t* for BGRA.
|
|
uint32_t* argb_cache; // Scratch buffer for temporary BGRA storage.
|
|
uint16_t* accumulated_rgb_pixels; // Scratch buffer for accumulated RGB for
|
|
// YUV conversion.
|
|
|
|
VP8LBitReader br;
|
|
int incremental; // if true, incremental decoding is expected
|
|
VP8LBitReader saved_br; // note: could be local variables too
|
|
int saved_last_pixel;
|
|
|
|
int width;
|
|
int height;
|
|
int last_row; // last input row decoded so far.
|
|
int last_pixel; // last pixel decoded so far. However, it may
|
|
// not be transformed, scaled and
|
|
// color-converted yet.
|
|
int last_out_row; // last row output so far.
|
|
|
|
VP8LMetadata hdr;
|
|
|
|
int next_transform;
|
|
VP8LTransform transforms[NUM_TRANSFORMS];
|
|
// or'd bitset storing the transforms types.
|
|
uint32_t transforms_seen;
|
|
|
|
uint8_t* rescaler_memory; // Working memory for rescaling work.
|
|
WebPRescaler* rescaler; // Common rescaler for all channels.
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// internal functions. Not public.
|
|
|
|
struct ALPHDecoder; // Defined in dec/alphai.h.
|
|
|
|
// in vp8l.c
|
|
|
|
// Decodes image header for alpha data stored using lossless compression.
|
|
// Returns false in case of error.
|
|
WEBP_NODISCARD int VP8LDecodeAlphaHeader(
|
|
struct ALPHDecoder* const alph_dec,
|
|
const uint8_t* const WEBP_COUNTED_BY(data_size) data, size_t data_size);
|
|
|
|
// Decodes *at least* 'last_row' rows of alpha. If some of the initial rows are
|
|
// already decoded in previous call(s), it will resume decoding from where it
|
|
// was paused.
|
|
// Returns false in case of bitstream error.
|
|
WEBP_NODISCARD int VP8LDecodeAlphaImageStream(
|
|
struct ALPHDecoder* const alph_dec, int last_row);
|
|
|
|
// Allocates and initialize a new lossless decoder instance.
|
|
WEBP_NODISCARD VP8LDecoder* VP8LNew(void);
|
|
|
|
// Decodes the image header. Returns false in case of error.
|
|
WEBP_NODISCARD int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io);
|
|
|
|
// Decodes an image. It's required to decode the lossless header before calling
|
|
// this function. Returns false in case of error, with updated dec->status.
|
|
WEBP_NODISCARD int VP8LDecodeImage(VP8LDecoder* const dec);
|
|
|
|
// Clears and deallocate a lossless decoder instance.
|
|
void VP8LDelete(VP8LDecoder* const dec);
|
|
|
|
// Helper function for reading the different Huffman codes and storing them in
|
|
// 'huffman_tables' and 'htree_groups'.
|
|
// If mapping is NULL 'num_htree_groups_max' must equal 'num_htree_groups'.
|
|
// If it is not NULL, it maps 'num_htree_groups_max' indices to the
|
|
// 'num_htree_groups' groups. If 'num_htree_groups_max' > 'num_htree_groups',
|
|
// some of those indices map to -1. This is used for non-balanced codes to
|
|
// limit memory usage.
|
|
WEBP_NODISCARD int ReadHuffmanCodesHelper(
|
|
int color_cache_bits, int num_htree_groups, int num_htree_groups_max,
|
|
const int* const mapping, VP8LDecoder* const dec,
|
|
HuffmanTables* const huffman_tables, HTreeGroup** const htree_groups);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DEC_VP8LI_DEC_H_
|
|
/* >>> src/utils/random_utils.h */
|
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Pseudo-random utilities
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_UTILS_RANDOM_UTILS_H_
|
|
#define WEBP_UTILS_RANDOM_UTILS_H_
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define VP8_RANDOM_DITHER_FIX 8 // fixed-point precision for dithering
|
|
#define VP8_RANDOM_TABLE_SIZE 55
|
|
|
|
typedef struct {
|
|
int index1, index2;
|
|
uint32_t tab[VP8_RANDOM_TABLE_SIZE];
|
|
int amp;
|
|
} VP8Random;
|
|
|
|
// Initializes random generator with an amplitude 'dithering' in range [0..1].
|
|
void VP8InitRandom(VP8Random* const rg, float dithering);
|
|
|
|
// Returns a centered pseudo-random number with 'num_bits' amplitude.
|
|
// (uses D.Knuth's Difference-based random generator).
|
|
// 'amp' is in VP8_RANDOM_DITHER_FIX fixed-point precision.
|
|
static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE int VP8RandomBits2(
|
|
VP8Random* const rg, int num_bits, int amp) {
|
|
int diff;
|
|
assert(num_bits + VP8_RANDOM_DITHER_FIX <= 31);
|
|
diff = rg->tab[rg->index1] - rg->tab[rg->index2];
|
|
if (diff < 0) diff += (1u << 31);
|
|
rg->tab[rg->index1] = diff;
|
|
if (++rg->index1 == VP8_RANDOM_TABLE_SIZE) rg->index1 = 0;
|
|
if (++rg->index2 == VP8_RANDOM_TABLE_SIZE) rg->index2 = 0;
|
|
// sign-extend, 0-center
|
|
diff = (int)((uint32_t)diff << 1) >> (32 - num_bits);
|
|
diff = (diff * amp) >> VP8_RANDOM_DITHER_FIX; // restrict range
|
|
diff += 1 << (num_bits - 1); // shift back to 0.5-center
|
|
return diff;
|
|
}
|
|
|
|
static WEBP_INLINE int VP8RandomBits(VP8Random* const rg, int num_bits) {
|
|
return VP8RandomBits2(rg, num_bits, rg->amp);
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_RANDOM_UTILS_H_
|
|
/* >>> src/utils/thread_utils.h */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Multi-threaded worker
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_UTILS_THREAD_UTILS_H_
|
|
#define WEBP_UTILS_THREAD_UTILS_H_
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// State of the worker thread object
|
|
typedef enum {
|
|
NOT_OK = 0, // object is unusable
|
|
OK, // ready to work
|
|
WORK // busy finishing the current task
|
|
} WebPWorkerStatus;
|
|
|
|
// Function to be called by the worker thread. Takes two opaque pointers as
|
|
// arguments (data1 and data2), and should return false in case of error.
|
|
typedef int (*WebPWorkerHook)(void*, void*);
|
|
|
|
// Synchronization object used to launch job in the worker thread
|
|
typedef struct {
|
|
void* impl; // platform-dependent implementation worker details
|
|
WebPWorkerStatus status;
|
|
WebPWorkerHook hook; // hook to call
|
|
void* data1; // first argument passed to 'hook'
|
|
void* data2; // second argument passed to 'hook'
|
|
int had_error; // return value of the last call to 'hook'
|
|
} WebPWorker;
|
|
|
|
// The interface for all thread-worker related functions. All these functions
|
|
// must be implemented.
|
|
typedef struct {
|
|
// Must be called first, before any other method.
|
|
void (*Init)(WebPWorker* const worker);
|
|
// Must be called to initialize the object and spawn the thread. Re-entrant.
|
|
// Will potentially launch the thread. Returns false in case of error.
|
|
int (*Reset)(WebPWorker* const worker);
|
|
// Makes sure the previous work is finished. Returns true if worker->had_error
|
|
// was not set and no error condition was triggered by the working thread.
|
|
int (*Sync)(WebPWorker* const worker);
|
|
// Triggers the thread to call hook() with data1 and data2 arguments. These
|
|
// hook/data1/data2 values can be changed at any time before calling this
|
|
// function, but not be changed afterward until the next call to Sync().
|
|
void (*Launch)(WebPWorker* const worker);
|
|
// This function is similar to Launch() except that it calls the
|
|
// hook directly instead of using a thread. Convenient to bypass the thread
|
|
// mechanism while still using the WebPWorker structs. Sync() must
|
|
// still be called afterward (for error reporting).
|
|
void (*Execute)(WebPWorker* const worker);
|
|
// Kill the thread and terminate the object. To use the object again, one
|
|
// must call Reset() again.
|
|
void (*End)(WebPWorker* const worker);
|
|
} WebPWorkerInterface;
|
|
|
|
// Install a new set of threading functions, overriding the defaults. This
|
|
// should be done before any workers are started, i.e., before any encoding or
|
|
// decoding takes place. The contents of the interface struct are copied, it
|
|
// is safe to free the corresponding memory after this call. This function is
|
|
// not thread-safe. Return false in case of invalid pointer or methods.
|
|
WEBP_EXTERN int WebPSetWorkerInterface(
|
|
const WebPWorkerInterface* const winterface);
|
|
|
|
// Retrieve the currently set thread worker interface.
|
|
WEBP_EXTERN const WebPWorkerInterface* WebPGetWorkerInterface(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_THREAD_UTILS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Various defines and enums
|
|
|
|
// version numbers
|
|
#define DEC_MAJ_VERSION 1
|
|
#define DEC_MIN_VERSION 6
|
|
#define DEC_REV_VERSION 0
|
|
|
|
// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline).
|
|
// Constraints are: We need to store one 16x16 block of luma samples (y),
|
|
// and two 8x8 chroma blocks (u/v). These are better be 16-bytes aligned,
|
|
// in order to be SIMD-friendly. We also need to store the top, left and
|
|
// top-left samples (from previously decoded blocks), along with four
|
|
// extra top-right samples for luma (intra4x4 prediction only).
|
|
// One possible layout is, using 32 * (17 + 9) bytes:
|
|
//
|
|
// .+------ <- only 1 pixel high
|
|
// .|yyyyt.
|
|
// .|yyyyt.
|
|
// .|yyyyt.
|
|
// .|yyyy..
|
|
// .+--.+-- <- only 1 pixel high
|
|
// .|uu.|vv
|
|
// .|uu.|vv
|
|
//
|
|
// Every character is a 4x4 block, with legend:
|
|
// '.' = unused
|
|
// 'y' = y-samples 'u' = u-samples 'v' = u-samples
|
|
// '|' = left sample, '-' = top sample, '+' = top-left sample
|
|
// 't' = extra top-right sample for 4x4 modes
|
|
#define YUV_SIZE (BPS * 17 + BPS * 9)
|
|
#define Y_OFF (BPS * 1 + 8)
|
|
#define U_OFF (Y_OFF + BPS * 16 + BPS)
|
|
#define V_OFF (U_OFF + 16)
|
|
|
|
// minimal width under which lossy multi-threading is always disabled
|
|
#define MIN_WIDTH_FOR_THREADS 512
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Headers
|
|
|
|
typedef struct {
|
|
uint8_t key_frame;
|
|
uint8_t profile;
|
|
uint8_t show;
|
|
uint32_t partition_length;
|
|
} VP8FrameHeader;
|
|
|
|
typedef struct {
|
|
uint16_t width;
|
|
uint16_t height;
|
|
uint8_t xscale;
|
|
uint8_t yscale;
|
|
uint8_t colorspace; // 0 = YCbCr
|
|
uint8_t clamp_type;
|
|
} VP8PictureHeader;
|
|
|
|
// segment features
|
|
typedef struct {
|
|
int use_segment;
|
|
int update_map; // whether to update the segment map or not
|
|
int absolute_delta; // absolute or delta values for quantizer and filter
|
|
int8_t quantizer[NUM_MB_SEGMENTS]; // quantization changes
|
|
int8_t filter_strength[NUM_MB_SEGMENTS]; // filter strength for segments
|
|
} VP8SegmentHeader;
|
|
|
|
// probas associated to one of the contexts
|
|
typedef uint8_t VP8ProbaArray[NUM_PROBAS];
|
|
|
|
typedef struct { // all the probas associated to one band
|
|
VP8ProbaArray probas[NUM_CTX];
|
|
} VP8BandProbas;
|
|
|
|
// Struct collecting all frame-persistent probabilities.
|
|
typedef struct {
|
|
uint8_t segments[MB_FEATURE_TREE_PROBS];
|
|
// Type: 0:Intra16-AC 1:Intra16-DC 2:Chroma 3:Intra4
|
|
VP8BandProbas bands[NUM_TYPES][NUM_BANDS];
|
|
const VP8BandProbas* bands_ptr[NUM_TYPES][16 + 1];
|
|
} VP8Proba;
|
|
|
|
// Filter parameters
|
|
typedef struct {
|
|
int simple; // 0=complex, 1=simple
|
|
int level; // [0..63]
|
|
int sharpness; // [0..7]
|
|
int use_lf_delta;
|
|
int ref_lf_delta[NUM_REF_LF_DELTAS];
|
|
int mode_lf_delta[NUM_MODE_LF_DELTAS];
|
|
} VP8FilterHeader;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Informations about the macroblocks.
|
|
|
|
typedef struct { // filter specs
|
|
uint8_t f_limit; // filter limit in [3..189], or 0 if no filtering
|
|
uint8_t f_ilevel; // inner limit in [1..63]
|
|
uint8_t f_inner; // do inner filtering?
|
|
uint8_t hev_thresh; // high edge variance threshold in [0..2]
|
|
} VP8FInfo;
|
|
|
|
typedef struct { // Top/Left Contexts used for syntax-parsing
|
|
uint8_t nz; // non-zero AC/DC coeffs (4bit for luma + 4bit for chroma)
|
|
uint8_t nz_dc; // non-zero DC coeff (1bit)
|
|
} VP8MB;
|
|
|
|
// Dequantization matrices
|
|
typedef int quant_t[2]; // [DC / AC]. Can be 'uint16_t[2]' too (~slower).
|
|
typedef struct {
|
|
quant_t y1_mat, y2_mat, uv_mat;
|
|
|
|
int uv_quant; // U/V quantizer value
|
|
int dither; // dithering amplitude (0 = off, max=255)
|
|
} VP8QuantMatrix;
|
|
|
|
// Data needed to reconstruct a macroblock
|
|
typedef struct {
|
|
int16_t coeffs[384]; // 384 coeffs = (16+4+4) * 4*4
|
|
uint8_t is_i4x4; // true if intra4x4
|
|
uint8_t imodes[16]; // one 16x16 mode (#0) or sixteen 4x4 modes
|
|
uint8_t uvmode; // chroma prediction mode
|
|
// bit-wise info about the content of each sub-4x4 blocks (in decoding order).
|
|
// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to:
|
|
// code=0 -> no coefficient
|
|
// code=1 -> only DC
|
|
// code=2 -> first three coefficients are non-zero
|
|
// code=3 -> more than three coefficients are non-zero
|
|
// This allows to call specialized transform functions.
|
|
uint32_t non_zero_y;
|
|
uint32_t non_zero_uv;
|
|
uint8_t dither; // local dithering strength (deduced from non_zero*)
|
|
uint8_t skip;
|
|
uint8_t segment;
|
|
} VP8MBData;
|
|
|
|
// Persistent information needed by the parallel processing
|
|
typedef struct {
|
|
int id; // cache row to process (in [0..2])
|
|
int mb_y; // macroblock position of the row
|
|
int filter_row; // true if row-filtering is needed
|
|
VP8FInfo* f_info; // filter strengths (swapped with dec->f_info)
|
|
VP8MBData* mb_data; // reconstruction data (swapped with dec->mb_data)
|
|
VP8Io io; // copy of the VP8Io to pass to put()
|
|
} VP8ThreadContext;
|
|
|
|
// Saved top samples, per macroblock. Fits into a cache-line.
|
|
typedef struct {
|
|
uint8_t y[16], u[8], v[8];
|
|
} VP8TopSamples;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// VP8Decoder: the main opaque structure handed over to user
|
|
|
|
struct VP8Decoder {
|
|
VP8StatusCode status;
|
|
int ready; // true if ready to decode a picture with VP8Decode()
|
|
const char* error_msg; // set when status is not OK.
|
|
|
|
// Main data source
|
|
VP8BitReader br;
|
|
int incremental; // if true, incremental decoding is expected
|
|
|
|
// headers
|
|
VP8FrameHeader frm_hdr;
|
|
VP8PictureHeader pic_hdr;
|
|
VP8FilterHeader filter_hdr;
|
|
VP8SegmentHeader segment_hdr;
|
|
|
|
// Worker
|
|
WebPWorker worker;
|
|
int mt_method; // multi-thread method: 0=off, 1=[parse+recon][filter]
|
|
// 2=[parse][recon+filter]
|
|
int cache_id; // current cache row
|
|
int num_caches; // number of cached rows of 16 pixels (1, 2 or 3)
|
|
VP8ThreadContext thread_ctx; // Thread context
|
|
|
|
// dimension, in macroblock units.
|
|
int mb_w, mb_h;
|
|
|
|
// Macroblock to process/filter, depending on cropping and filter_type.
|
|
int tl_mb_x, tl_mb_y; // top-left MB that must be in-loop filtered
|
|
int br_mb_x, br_mb_y; // last bottom-right MB that must be decoded
|
|
|
|
// number of partitions minus one.
|
|
uint32_t num_parts_minus_one;
|
|
// per-partition boolean decoders.
|
|
VP8BitReader parts[MAX_NUM_PARTITIONS];
|
|
|
|
// Dithering strength, deduced from decoding options
|
|
int dither; // whether to use dithering or not
|
|
VP8Random dithering_rg; // random generator for dithering
|
|
|
|
// dequantization (one set of DC/AC dequant factor per segment)
|
|
VP8QuantMatrix dqm[NUM_MB_SEGMENTS];
|
|
|
|
// probabilities
|
|
VP8Proba proba;
|
|
int use_skip_proba;
|
|
uint8_t skip_p;
|
|
|
|
// Boundary data cache and persistent buffers.
|
|
uint8_t* intra_t; // top intra modes values: 4 * mb_w
|
|
uint8_t intra_l[4]; // left intra modes values
|
|
|
|
VP8TopSamples* yuv_t; // top y/u/v samples
|
|
|
|
VP8MB* mb_info; // contextual macroblock info (mb_w + 1)
|
|
VP8FInfo* f_info; // filter strength info
|
|
uint8_t* yuv_b; // main block for Y/U/V (size = YUV_SIZE)
|
|
|
|
uint8_t* cache_y; // macroblock row for storing unfiltered samples
|
|
uint8_t* cache_u;
|
|
uint8_t* cache_v;
|
|
int cache_y_stride;
|
|
int cache_uv_stride;
|
|
|
|
// main memory chunk for the above data. Persistent.
|
|
void* mem;
|
|
size_t mem_size;
|
|
|
|
// Per macroblock non-persistent infos.
|
|
int mb_x, mb_y; // current position, in macroblock units
|
|
VP8MBData* mb_data; // parsed reconstruction data
|
|
|
|
// Filtering side-info
|
|
int filter_type; // 0=off, 1=simple, 2=complex
|
|
VP8FInfo fstrengths[NUM_MB_SEGMENTS][2]; // precalculated per-segment/type
|
|
|
|
// Alpha
|
|
struct ALPHDecoder* alph_dec; // alpha-plane decoder object
|
|
const uint8_t* WEBP_COUNTED_BY(alpha_data_size)
|
|
alpha_data; // compressed alpha data (if present)
|
|
size_t alpha_data_size;
|
|
int is_alpha_decoded; // true if alpha_data is decoded in alpha_plane
|
|
uint8_t* alpha_plane_mem; // memory allocated for alpha_plane
|
|
uint8_t* alpha_plane; // output. Persistent, contains the whole data.
|
|
const uint8_t* alpha_prev_line; // last decoded alpha row (or NULL)
|
|
int alpha_dithering; // derived from decoding options (0=off, 100=full)
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// internal functions. Not public.
|
|
|
|
// in vp8.c
|
|
int VP8SetError(VP8Decoder* const dec, VP8StatusCode error,
|
|
const char* const msg);
|
|
|
|
// in tree.c
|
|
void VP8ResetProba(VP8Proba* const proba);
|
|
void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec);
|
|
// parses one row of intra mode data in partition 0, returns !eof
|
|
int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec);
|
|
|
|
// in quant.c
|
|
void VP8ParseQuant(VP8Decoder* const dec);
|
|
|
|
// in frame.c
|
|
WEBP_NODISCARD int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io);
|
|
// Call io->setup() and finish setting up scan parameters.
|
|
// After this call returns, one must always call VP8ExitCritical() with the
|
|
// same parameters. Both functions should be used in pair. Returns VP8_STATUS_OK
|
|
// if ok, otherwise sets and returns the error status on *dec.
|
|
VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io);
|
|
// Must always be called in pair with VP8EnterCritical().
|
|
// Returns false in case of error.
|
|
WEBP_NODISCARD int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io);
|
|
// Return the multi-threading method to use (0=off), depending
|
|
// on options and bitstream size. Only for lossy decoding.
|
|
int VP8GetThreadMethod(const WebPDecoderOptions* const options,
|
|
const WebPHeaderStructure* const headers, int width,
|
|
int height);
|
|
// Initialize dithering post-process if needed.
|
|
void VP8InitDithering(const WebPDecoderOptions* const options,
|
|
VP8Decoder* const dec);
|
|
// Process the last decoded row (filtering + output).
|
|
WEBP_NODISCARD int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io);
|
|
// To be called at the start of a new scanline, to initialize predictors.
|
|
void VP8InitScanline(VP8Decoder* const dec);
|
|
// Decode one macroblock. Returns false if there is not enough data.
|
|
WEBP_NODISCARD int VP8DecodeMB(VP8Decoder* const dec,
|
|
VP8BitReader* const token_br);
|
|
|
|
// in alpha.c
|
|
const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec,
|
|
const VP8Io* const io, int row,
|
|
int num_rows);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DEC_VP8I_DEC_H_
|
|
/* >>> src/utils/quant_levels_dec_utils.h */
|
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Alpha plane de-quantization utility
|
|
//
|
|
// Author: Vikas Arora (vikasa@google.com)
|
|
|
|
#ifndef WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_
|
|
#define WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// Apply post-processing to input 'data' of size 'width'x'height' assuming that
|
|
// the source was quantized to a reduced number of levels. 'stride' is in bytes.
|
|
// Strength is in [0..100] and controls the amount of dithering applied.
|
|
// Returns false in case of error (data is NULL, invalid parameters,
|
|
// malloc failure, ...).
|
|
int WebPDequantizeLevels(uint8_t* WEBP_SIZED_BY((size_t)stride* height)
|
|
const data,
|
|
int width, int height, int stride, int strength);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_
|
|
/* >>> src/utils/utils.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Misc. common utility functions
|
|
//
|
|
// Authors: Skal (pascal.massimino@gmail.com)
|
|
// Urvang (urvang@google.com)
|
|
|
|
#ifndef WEBP_UTILS_UTILS_H_
|
|
#define WEBP_UTILS_UTILS_H_
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Memory allocation
|
|
|
|
// This is the maximum memory amount that libwebp will ever try to allocate.
|
|
#ifndef WEBP_MAX_ALLOCABLE_MEMORY
|
|
#if SIZE_MAX > (1ULL << 34)
|
|
#define WEBP_MAX_ALLOCABLE_MEMORY (1ULL << 34)
|
|
#else
|
|
// For 32-bit targets keep this below INT_MAX to avoid valgrind warnings.
|
|
#define WEBP_MAX_ALLOCABLE_MEMORY ((1ULL << 31) - (1 << 16))
|
|
#endif
|
|
#endif // WEBP_MAX_ALLOCABLE_MEMORY
|
|
|
|
static WEBP_INLINE int CheckSizeOverflow(uint64_t size) {
|
|
return size == (size_t)size;
|
|
}
|
|
|
|
// size-checking safe malloc/calloc: verify that the requested size is not too
|
|
// large, or return NULL. You don't need to call these for constructs like
|
|
// malloc(sizeof(foo)), but only if there's picture-dependent size involved
|
|
// somewhere (like: malloc(num_pixels * sizeof(*something))). That's why this
|
|
// safe malloc() borrows the signature from calloc(), pointing at the dangerous
|
|
// underlying multiply involved.
|
|
WEBP_EXTERN void* WEBP_SIZED_BY_OR_NULL(nmemb* size)
|
|
WebPSafeMalloc(uint64_t nmemb, size_t size);
|
|
// Note that WebPSafeCalloc() expects the second argument type to be 'size_t'
|
|
// in order to favor the "calloc(num_foo, sizeof(foo))" pattern.
|
|
WEBP_EXTERN void* WEBP_SIZED_BY_OR_NULL(nmemb* size)
|
|
WebPSafeCalloc(uint64_t nmemb, size_t size);
|
|
|
|
// Companion deallocation function to the above allocations.
|
|
WEBP_EXTERN void WebPSafeFree(void* const ptr);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Alignment
|
|
|
|
#define WEBP_ALIGN_CST 31
|
|
#define WEBP_ALIGN(PTR) \
|
|
(((uintptr_t)(PTR) + WEBP_ALIGN_CST) & ~(uintptr_t)WEBP_ALIGN_CST)
|
|
|
|
#include <string.h>
|
|
// memcpy() is the safe way of moving potentially unaligned 32b memory.
|
|
static WEBP_INLINE uint32_t WebPMemToUint32(const uint8_t* const ptr) {
|
|
uint32_t A;
|
|
WEBP_UNSAFE_MEMCPY(&A, ptr, sizeof(A));
|
|
return A;
|
|
}
|
|
|
|
static WEBP_INLINE int32_t WebPMemToInt32(const uint8_t* const ptr) {
|
|
return (int32_t)WebPMemToUint32(ptr);
|
|
}
|
|
|
|
static WEBP_INLINE void WebPUint32ToMem(uint8_t* const ptr, uint32_t val) {
|
|
WEBP_UNSAFE_MEMCPY(ptr, &val, sizeof(val));
|
|
}
|
|
|
|
static WEBP_INLINE void WebPInt32ToMem(uint8_t* const ptr, int val) {
|
|
WebPUint32ToMem(ptr, (uint32_t)val);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Reading/writing data.
|
|
|
|
// Read 16, 24 or 32 bits stored in little-endian order.
|
|
static WEBP_INLINE int GetLE16(const uint8_t* const WEBP_COUNTED_BY(2) data) {
|
|
return (int)(data[0] << 0) | (data[1] << 8);
|
|
}
|
|
|
|
static WEBP_INLINE int GetLE24(const uint8_t* const WEBP_COUNTED_BY(3) data) {
|
|
return GetLE16(data) | (data[2] << 16);
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t GetLE32(const uint8_t* const WEBP_COUNTED_BY(4)
|
|
data) {
|
|
return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16);
|
|
}
|
|
|
|
// Store 16, 24 or 32 bits in little-endian order.
|
|
static WEBP_INLINE void PutLE16(uint8_t* const WEBP_COUNTED_BY(2) data,
|
|
int val) {
|
|
assert(val < (1 << 16));
|
|
data[0] = (val >> 0) & 0xff;
|
|
data[1] = (val >> 8) & 0xff;
|
|
}
|
|
|
|
static WEBP_INLINE void PutLE24(uint8_t* const WEBP_COUNTED_BY(3) data,
|
|
int val) {
|
|
assert(val < (1 << 24));
|
|
PutLE16(data, val & 0xffff);
|
|
data[2] = (val >> 16) & 0xff;
|
|
}
|
|
|
|
static WEBP_INLINE void PutLE32(uint8_t* const WEBP_COUNTED_BY(4) data,
|
|
uint32_t val) {
|
|
PutLE16(data, (int)(val & 0xffff));
|
|
PutLE16(data + 2, (int)(val >> 16));
|
|
}
|
|
|
|
// use GNU builtins where available.
|
|
#if defined(__GNUC__) && \
|
|
((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ >= 4)
|
|
// Returns (int)floor(log2(n)). n must be > 0.
|
|
static WEBP_INLINE int BitsLog2Floor(uint32_t n) {
|
|
return 31 ^ __builtin_clz(n);
|
|
}
|
|
// counts the number of trailing zero
|
|
static WEBP_INLINE int BitsCtz(uint32_t n) { return __builtin_ctz(n); }
|
|
#elif defined(_MSC_VER) && _MSC_VER > 1310 && \
|
|
(defined(_M_X64) || defined(_M_IX86))
|
|
#include <intrin.h>
|
|
#pragma intrinsic(_BitScanReverse)
|
|
#pragma intrinsic(_BitScanForward)
|
|
|
|
static WEBP_INLINE int BitsLog2Floor(uint32_t n) {
|
|
unsigned long first_set_bit; // NOLINT (runtime/int)
|
|
_BitScanReverse(&first_set_bit, n);
|
|
return first_set_bit;
|
|
}
|
|
static WEBP_INLINE int BitsCtz(uint32_t n) {
|
|
unsigned long first_set_bit; // NOLINT (runtime/int)
|
|
_BitScanForward(&first_set_bit, n);
|
|
return first_set_bit;
|
|
}
|
|
#else // default: use the (slow) C-version.
|
|
#define WEBP_HAVE_SLOW_CLZ_CTZ // signal that the Clz/Ctz function are slow
|
|
// Returns 31 ^ clz(n) = log2(n). This is the default C-implementation, either
|
|
// based on table or not. Can be used as fallback if clz() is not available.
|
|
#define WEBP_NEED_LOG_TABLE_8BIT
|
|
extern const uint8_t WebPLogTable8bit[256];
|
|
static WEBP_INLINE int WebPLog2FloorC(uint32_t n) {
|
|
int log_value = 0;
|
|
while (n >= 256) {
|
|
log_value += 8;
|
|
n >>= 8;
|
|
}
|
|
return log_value + WebPLogTable8bit[n];
|
|
}
|
|
|
|
static WEBP_INLINE int BitsLog2Floor(uint32_t n) { return WebPLog2FloorC(n); }
|
|
|
|
static WEBP_INLINE int BitsCtz(uint32_t n) {
|
|
int i;
|
|
for (i = 0; i < 32; ++i, n >>= 1) {
|
|
if (n & 1) return i;
|
|
}
|
|
return 32;
|
|
}
|
|
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Pixel copying.
|
|
|
|
struct WebPPicture;
|
|
|
|
// Copy width x height pixels from 'src' to 'dst' honoring the strides.
|
|
WEBP_EXTERN void WebPCopyPlane(const uint8_t* src, int src_stride, uint8_t* dst,
|
|
int dst_stride, int width, int height);
|
|
|
|
// Copy ARGB pixels from 'src' to 'dst' honoring strides. 'src' and 'dst' are
|
|
// assumed to be already allocated and using ARGB data.
|
|
WEBP_EXTERN void WebPCopyPixels(const struct WebPPicture* const src,
|
|
struct WebPPicture* const dst);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Unique colors.
|
|
|
|
// Returns count of unique colors in 'pic', assuming pic->use_argb is true.
|
|
// If the unique color count is more than MAX_PALETTE_SIZE, returns
|
|
// MAX_PALETTE_SIZE+1.
|
|
// If 'palette' is not NULL and number of unique colors is less than or equal to
|
|
// MAX_PALETTE_SIZE, also outputs the actual unique colors into 'palette'.
|
|
// Note: 'palette' is assumed to be an array already allocated with at least
|
|
// MAX_PALETTE_SIZE elements.
|
|
// TODO(vrabaud) remove whenever we can break the ABI.
|
|
WEBP_EXTERN int WebPGetColorPalette(
|
|
const struct WebPPicture* const pic,
|
|
uint32_t* const WEBP_COUNTED_BY_OR_NULL(MAX_PALETTE_SIZE) palette);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_UTILS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// ALPHDecoder object.
|
|
|
|
// Allocates a new alpha decoder instance.
|
|
WEBP_NODISCARD static ALPHDecoder* ALPHNew(void) {
|
|
ALPHDecoder* const dec = (ALPHDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec));
|
|
return dec;
|
|
}
|
|
|
|
// Clears and deallocates an alpha decoder instance.
|
|
static void ALPHDelete(ALPHDecoder* const dec) {
|
|
if (dec != NULL) {
|
|
VP8LDelete(dec->vp8l_dec);
|
|
dec->vp8l_dec = NULL;
|
|
WebPSafeFree(dec);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Decoding.
|
|
|
|
// Initialize alpha decoding by parsing the alpha header and decoding the image
|
|
// header for alpha data stored using lossless compression.
|
|
// Returns false in case of error in alpha header (data too short, invalid
|
|
// compression method or filter, error in lossless header data etc).
|
|
WEBP_NODISCARD static int ALPHInit(ALPHDecoder* const dec, const uint8_t* data,
|
|
size_t data_size, const VP8Io* const src_io,
|
|
uint8_t* output) {
|
|
int ok = 0;
|
|
const uint8_t* const alpha_data = data + ALPHA_HEADER_LEN;
|
|
int rsrv;
|
|
VP8Io* const io = &dec->io;
|
|
|
|
assert(data != NULL && output != NULL && src_io != NULL);
|
|
|
|
VP8FiltersInit();
|
|
dec->output = output;
|
|
dec->width = src_io->width;
|
|
dec->height = src_io->height;
|
|
assert(dec->width > 0 && dec->height > 0);
|
|
|
|
if (data_size <= ALPHA_HEADER_LEN) {
|
|
return 0;
|
|
}
|
|
|
|
dec->method = (data[0] >> 0) & 0x03;
|
|
dec->filter = (WEBP_FILTER_TYPE)((data[0] >> 2) & 0x03);
|
|
dec->pre_processing = (data[0] >> 4) & 0x03;
|
|
rsrv = (data[0] >> 6) & 0x03;
|
|
if (dec->method < ALPHA_NO_COMPRESSION ||
|
|
dec->method > ALPHA_LOSSLESS_COMPRESSION ||
|
|
dec->filter >= WEBP_FILTER_LAST ||
|
|
dec->pre_processing > ALPHA_PREPROCESSED_LEVELS || rsrv != 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Copy the necessary parameters from src_io to io
|
|
if (!VP8InitIo(io)) {
|
|
return 0;
|
|
}
|
|
WebPInitCustomIo(NULL, io);
|
|
io->opaque = dec;
|
|
io->width = src_io->width;
|
|
io->height = src_io->height;
|
|
|
|
io->use_cropping = src_io->use_cropping;
|
|
io->crop_left = src_io->crop_left;
|
|
io->crop_right = src_io->crop_right;
|
|
io->crop_top = src_io->crop_top;
|
|
io->crop_bottom = src_io->crop_bottom;
|
|
// No need to copy the scaling parameters.
|
|
|
|
{
|
|
const size_t alpha_data_size = data_size - ALPHA_HEADER_LEN;
|
|
if (dec->method == ALPHA_NO_COMPRESSION) {
|
|
const size_t alpha_decoded_size = dec->width * dec->height;
|
|
ok = (alpha_data_size >= alpha_decoded_size);
|
|
} else {
|
|
assert(dec->method == ALPHA_LOSSLESS_COMPRESSION);
|
|
{
|
|
const uint8_t* WEBP_BIDI_INDEXABLE const bounded_alpha_data =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, alpha_data,
|
|
alpha_data_size);
|
|
ok = VP8LDecodeAlphaHeader(dec, bounded_alpha_data, alpha_data_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
// Decodes, unfilters and dequantizes *at least* 'num_rows' rows of alpha
|
|
// starting from row number 'row'. It assumes that rows up to (row - 1) have
|
|
// already been decoded.
|
|
// Returns false in case of bitstream error.
|
|
WEBP_NODISCARD static int ALPHDecode(VP8Decoder* const dec, int row,
|
|
int num_rows) {
|
|
ALPHDecoder* const alph_dec = dec->alph_dec;
|
|
const int width = alph_dec->width;
|
|
const int height = alph_dec->io.crop_bottom;
|
|
if (alph_dec->method == ALPHA_NO_COMPRESSION) {
|
|
int y;
|
|
const uint8_t* prev_line = dec->alpha_prev_line;
|
|
const uint8_t* deltas = dec->alpha_data + ALPHA_HEADER_LEN + row * width;
|
|
uint8_t* dst = dec->alpha_plane + row * width;
|
|
assert(deltas <= &dec->alpha_data[dec->alpha_data_size]);
|
|
assert(WebPUnfilters[alph_dec->filter] != NULL);
|
|
for (y = 0; y < num_rows; ++y) {
|
|
WebPUnfilters[alph_dec->filter](prev_line, deltas, dst, width);
|
|
prev_line = dst;
|
|
dst += width;
|
|
deltas += width;
|
|
}
|
|
dec->alpha_prev_line = prev_line;
|
|
} else { // alph_dec->method == ALPHA_LOSSLESS_COMPRESSION
|
|
assert(alph_dec->vp8l_dec != NULL);
|
|
if (!VP8LDecodeAlphaImageStream(alph_dec, row + num_rows)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (row + num_rows >= height) {
|
|
dec->is_alpha_decoded = 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
WEBP_NODISCARD static int AllocateAlphaPlane(VP8Decoder* const dec,
|
|
const VP8Io* const io) {
|
|
const int stride = io->width;
|
|
const int height = io->crop_bottom;
|
|
const uint64_t alpha_size = (uint64_t)stride * height;
|
|
assert(dec->alpha_plane_mem == NULL);
|
|
dec->alpha_plane_mem =
|
|
(uint8_t*)WebPSafeMalloc(alpha_size, sizeof(*dec->alpha_plane));
|
|
if (dec->alpha_plane_mem == NULL) {
|
|
return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY,
|
|
"Alpha decoder initialization failed.");
|
|
}
|
|
dec->alpha_plane = dec->alpha_plane_mem;
|
|
dec->alpha_prev_line = NULL;
|
|
return 1;
|
|
}
|
|
|
|
void WebPDeallocateAlphaMemory(VP8Decoder* const dec) {
|
|
assert(dec != NULL);
|
|
WebPSafeFree(dec->alpha_plane_mem);
|
|
dec->alpha_plane_mem = NULL;
|
|
dec->alpha_plane = NULL;
|
|
ALPHDelete(dec->alph_dec);
|
|
dec->alph_dec = NULL;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main entry point.
|
|
|
|
WEBP_NODISCARD const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec,
|
|
const VP8Io* const io,
|
|
int row, int num_rows) {
|
|
const int width = io->width;
|
|
const int height = io->crop_bottom;
|
|
|
|
assert(dec != NULL && io != NULL);
|
|
|
|
if (row < 0 || num_rows <= 0 || row + num_rows > height) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!dec->is_alpha_decoded) {
|
|
if (dec->alph_dec == NULL) { // Initialize decoder.
|
|
dec->alph_dec = ALPHNew();
|
|
if (dec->alph_dec == NULL) {
|
|
VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY,
|
|
"Alpha decoder initialization failed.");
|
|
return NULL;
|
|
}
|
|
if (!AllocateAlphaPlane(dec, io)) goto Error;
|
|
if (!ALPHInit(dec->alph_dec, dec->alpha_data, dec->alpha_data_size, io,
|
|
dec->alpha_plane)) {
|
|
VP8LDecoder* const vp8l_dec = dec->alph_dec->vp8l_dec;
|
|
VP8SetError(
|
|
dec,
|
|
(vp8l_dec == NULL) ? VP8_STATUS_OUT_OF_MEMORY : vp8l_dec->status,
|
|
"Alpha decoder initialization failed.");
|
|
goto Error;
|
|
}
|
|
// if we allowed use of alpha dithering, check whether it's needed at all
|
|
if (dec->alph_dec->pre_processing != ALPHA_PREPROCESSED_LEVELS) {
|
|
dec->alpha_dithering = 0; // disable dithering
|
|
} else {
|
|
num_rows = height - row; // decode everything in one pass
|
|
}
|
|
}
|
|
|
|
assert(dec->alph_dec != NULL);
|
|
assert(row + num_rows <= height);
|
|
if (!ALPHDecode(dec, row, num_rows)) goto Error;
|
|
|
|
if (dec->is_alpha_decoded) { // finished?
|
|
ALPHDelete(dec->alph_dec);
|
|
dec->alph_dec = NULL;
|
|
if (dec->alpha_dithering > 0) {
|
|
uint8_t* const alpha =
|
|
dec->alpha_plane + io->crop_top * width + io->crop_left;
|
|
uint8_t* WEBP_BIDI_INDEXABLE const bounded_alpha =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
uint8_t*, alpha,
|
|
(size_t)width*(io->crop_bottom - io->crop_top));
|
|
if (!WebPDequantizeLevels(bounded_alpha, io->crop_right - io->crop_left,
|
|
io->crop_bottom - io->crop_top, width,
|
|
dec->alpha_dithering)) {
|
|
goto Error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return a pointer to the current decoded row.
|
|
return dec->alpha_plane + row * width;
|
|
|
|
Error:
|
|
WebPDeallocateAlphaMemory(dec);
|
|
return NULL;
|
|
}
|
|
/* >>> src/dec/buffer_dec.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Everything about WebPDecBuffer
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPDecBuffer
|
|
|
|
// Number of bytes per pixel for the different color-spaces.
|
|
static const uint8_t kModeBpp[MODE_LAST] = {3, 4, 3, 4, 4, 2, 2, //
|
|
4, 4, 4, 2, // pre-multiplied modes
|
|
1, 1};
|
|
|
|
// Convert to an integer to handle both the unsigned/signed enum cases
|
|
// without the need for casting to remove type limit warnings.
|
|
int IsValidColorspace(int webp_csp_mode) {
|
|
return (webp_csp_mode >= MODE_RGB && webp_csp_mode < MODE_LAST);
|
|
}
|
|
|
|
// strictly speaking, the very last (or first, if flipped) row
|
|
// doesn't require padding.
|
|
#define MIN_BUFFER_SIZE(WIDTH, HEIGHT, STRIDE) \
|
|
((uint64_t)(STRIDE) * ((HEIGHT) - 1) + (WIDTH))
|
|
|
|
static VP8StatusCode CheckDecBuffer(const WebPDecBuffer* const buffer) {
|
|
int ok = 1;
|
|
const WEBP_CSP_MODE mode = buffer->colorspace;
|
|
const int width = buffer->width;
|
|
const int height = buffer->height;
|
|
if (!IsValidColorspace(mode)) {
|
|
ok = 0;
|
|
} else if (!WebPIsRGBMode(mode)) { // YUV checks
|
|
const WebPYUVABuffer* const buf = &buffer->u.YUVA;
|
|
const int uv_width = (width + 1) / 2;
|
|
const int uv_height = (height + 1) / 2;
|
|
const int y_stride = abs(buf->y_stride);
|
|
const int u_stride = abs(buf->u_stride);
|
|
const int v_stride = abs(buf->v_stride);
|
|
const int a_stride = abs(buf->a_stride);
|
|
const uint64_t y_size = MIN_BUFFER_SIZE(width, height, y_stride);
|
|
const uint64_t u_size = MIN_BUFFER_SIZE(uv_width, uv_height, u_stride);
|
|
const uint64_t v_size = MIN_BUFFER_SIZE(uv_width, uv_height, v_stride);
|
|
const uint64_t a_size = MIN_BUFFER_SIZE(width, height, a_stride);
|
|
ok &= (y_size <= buf->y_size);
|
|
ok &= (u_size <= buf->u_size);
|
|
ok &= (v_size <= buf->v_size);
|
|
ok &= (y_stride >= width);
|
|
ok &= (u_stride >= uv_width);
|
|
ok &= (v_stride >= uv_width);
|
|
ok &= (buf->y != NULL);
|
|
ok &= (buf->u != NULL);
|
|
ok &= (buf->v != NULL);
|
|
if (mode == MODE_YUVA) {
|
|
ok &= (a_stride >= width);
|
|
ok &= (a_size <= buf->a_size);
|
|
ok &= (buf->a != NULL);
|
|
}
|
|
} else { // RGB checks
|
|
const WebPRGBABuffer* const buf = &buffer->u.RGBA;
|
|
const int stride = abs(buf->stride);
|
|
const uint64_t size =
|
|
MIN_BUFFER_SIZE((uint64_t)width * kModeBpp[mode], height, stride);
|
|
ok &= (size <= buf->size);
|
|
ok &= (stride >= width * kModeBpp[mode]);
|
|
ok &= (buf->rgba != NULL);
|
|
}
|
|
return ok ? VP8_STATUS_OK : VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
#undef MIN_BUFFER_SIZE
|
|
|
|
static VP8StatusCode AllocateBuffer(WebPDecBuffer* const buffer) {
|
|
const int w = buffer->width;
|
|
const int h = buffer->height;
|
|
const WEBP_CSP_MODE mode = buffer->colorspace;
|
|
|
|
if (w <= 0 || h <= 0 || !IsValidColorspace(mode)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (buffer->is_external_memory <= 0 && buffer->private_memory == NULL) {
|
|
uint8_t* output;
|
|
int uv_stride = 0, a_stride = 0;
|
|
uint64_t uv_size = 0, a_size = 0, total_size;
|
|
// We need memory and it hasn't been allocated yet.
|
|
// => initialize output buffer, now that dimensions are known.
|
|
int stride;
|
|
uint64_t size;
|
|
|
|
if ((uint64_t)w * kModeBpp[mode] >= (1ull << 31)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
stride = w * kModeBpp[mode];
|
|
size = (uint64_t)stride * h;
|
|
if (!WebPIsRGBMode(mode)) {
|
|
uv_stride = (w + 1) / 2;
|
|
uv_size = (uint64_t)uv_stride * ((h + 1) / 2);
|
|
if (mode == MODE_YUVA) {
|
|
a_stride = w;
|
|
a_size = (uint64_t)a_stride * h;
|
|
}
|
|
}
|
|
total_size = size + 2 * uv_size + a_size;
|
|
|
|
output = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*output));
|
|
if (output == NULL) {
|
|
return VP8_STATUS_OUT_OF_MEMORY;
|
|
}
|
|
buffer->private_memory = output;
|
|
|
|
if (!WebPIsRGBMode(mode)) { // YUVA initialization
|
|
WebPYUVABuffer* const buf = &buffer->u.YUVA;
|
|
buf->y = output;
|
|
buf->y_stride = stride;
|
|
buf->y_size = (size_t)size;
|
|
buf->u = output + size;
|
|
buf->u_stride = uv_stride;
|
|
buf->u_size = (size_t)uv_size;
|
|
buf->v = output + size + uv_size;
|
|
buf->v_stride = uv_stride;
|
|
buf->v_size = (size_t)uv_size;
|
|
if (mode == MODE_YUVA) {
|
|
buf->a = output + size + 2 * uv_size;
|
|
}
|
|
buf->a_size = (size_t)a_size;
|
|
buf->a_stride = a_stride;
|
|
} else { // RGBA initialization
|
|
WebPRGBABuffer* const buf = &buffer->u.RGBA;
|
|
buf->rgba = output;
|
|
buf->stride = stride;
|
|
buf->size = (size_t)size;
|
|
}
|
|
}
|
|
return CheckDecBuffer(buffer);
|
|
}
|
|
|
|
VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer) {
|
|
if (buffer == NULL) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
if (WebPIsRGBMode(buffer->colorspace)) {
|
|
WebPRGBABuffer* const buf = &buffer->u.RGBA;
|
|
buf->rgba += (int64_t)(buffer->height - 1) * buf->stride;
|
|
buf->stride = -buf->stride;
|
|
} else {
|
|
WebPYUVABuffer* const buf = &buffer->u.YUVA;
|
|
const int64_t H = buffer->height;
|
|
buf->y += (H - 1) * buf->y_stride;
|
|
buf->y_stride = -buf->y_stride;
|
|
buf->u += ((H - 1) >> 1) * buf->u_stride;
|
|
buf->u_stride = -buf->u_stride;
|
|
buf->v += ((H - 1) >> 1) * buf->v_stride;
|
|
buf->v_stride = -buf->v_stride;
|
|
if (buf->a != NULL) {
|
|
buf->a += (H - 1) * buf->a_stride;
|
|
buf->a_stride = -buf->a_stride;
|
|
}
|
|
}
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
VP8StatusCode WebPAllocateDecBuffer(int width, int height,
|
|
const WebPDecoderOptions* const options,
|
|
WebPDecBuffer* const buffer) {
|
|
VP8StatusCode status;
|
|
if (buffer == NULL || width <= 0 || height <= 0) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
if (options != NULL) { // First, apply options if there is any.
|
|
if (options->use_cropping) {
|
|
const int cw = options->crop_width;
|
|
const int ch = options->crop_height;
|
|
const int x = options->crop_left & ~1;
|
|
const int y = options->crop_top & ~1;
|
|
if (!WebPCheckCropDimensions(width, height, x, y, cw, ch)) {
|
|
return VP8_STATUS_INVALID_PARAM; // out of frame boundary.
|
|
}
|
|
width = cw;
|
|
height = ch;
|
|
}
|
|
|
|
if (options->use_scaling) {
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
int scaled_width = options->scaled_width;
|
|
int scaled_height = options->scaled_height;
|
|
if (!WebPRescalerGetScaledDimensions(width, height, &scaled_width,
|
|
&scaled_height)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
width = scaled_width;
|
|
height = scaled_height;
|
|
#else
|
|
return VP8_STATUS_INVALID_PARAM; // rescaling not supported
|
|
#endif
|
|
}
|
|
}
|
|
buffer->width = width;
|
|
buffer->height = height;
|
|
|
|
// Then, allocate buffer for real.
|
|
status = AllocateBuffer(buffer);
|
|
if (status != VP8_STATUS_OK) return status;
|
|
|
|
// Use the stride trick if vertical flip is needed.
|
|
if (options != NULL && options->flip) {
|
|
status = WebPFlipBuffer(buffer);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// constructors / destructors
|
|
|
|
int WebPInitDecBufferInternal(WebPDecBuffer* buffer, int version) {
|
|
if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) {
|
|
return 0; // version mismatch
|
|
}
|
|
if (buffer == NULL) return 0;
|
|
WEBP_UNSAFE_MEMSET(buffer, 0, sizeof(*buffer));
|
|
return 1;
|
|
}
|
|
|
|
void WebPFreeDecBuffer(WebPDecBuffer* buffer) {
|
|
if (buffer != NULL) {
|
|
if (buffer->is_external_memory <= 0) {
|
|
WebPSafeFree(buffer->private_memory);
|
|
}
|
|
buffer->private_memory = NULL;
|
|
}
|
|
}
|
|
|
|
void WebPCopyDecBuffer(const WebPDecBuffer* const src,
|
|
WebPDecBuffer* const dst) {
|
|
if (src != NULL && dst != NULL) {
|
|
*dst = *src;
|
|
if (src->private_memory != NULL) {
|
|
dst->is_external_memory = 1; // dst buffer doesn't own the memory.
|
|
dst->private_memory = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy and transfer ownership from src to dst (beware of parameter order!)
|
|
void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst) {
|
|
if (src != NULL && dst != NULL) {
|
|
*dst = *src;
|
|
if (src->private_memory != NULL) {
|
|
src->is_external_memory = 1; // src relinquishes ownership
|
|
src->private_memory = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src_buf,
|
|
WebPDecBuffer* const dst_buf) {
|
|
assert(src_buf != NULL && dst_buf != NULL);
|
|
assert(src_buf->colorspace == dst_buf->colorspace);
|
|
|
|
dst_buf->width = src_buf->width;
|
|
dst_buf->height = src_buf->height;
|
|
if (CheckDecBuffer(dst_buf) != VP8_STATUS_OK) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
if (WebPIsRGBMode(src_buf->colorspace)) {
|
|
const WebPRGBABuffer* const src = &src_buf->u.RGBA;
|
|
const WebPRGBABuffer* const dst = &dst_buf->u.RGBA;
|
|
WebPCopyPlane(src->rgba, src->stride, dst->rgba, dst->stride,
|
|
src_buf->width * kModeBpp[src_buf->colorspace],
|
|
src_buf->height);
|
|
} else {
|
|
const WebPYUVABuffer* const src = &src_buf->u.YUVA;
|
|
const WebPYUVABuffer* const dst = &dst_buf->u.YUVA;
|
|
WebPCopyPlane(src->y, src->y_stride, dst->y, dst->y_stride, src_buf->width,
|
|
src_buf->height);
|
|
WebPCopyPlane(src->u, src->u_stride, dst->u, dst->u_stride,
|
|
(src_buf->width + 1) / 2, (src_buf->height + 1) / 2);
|
|
WebPCopyPlane(src->v, src->v_stride, dst->v, dst->v_stride,
|
|
(src_buf->width + 1) / 2, (src_buf->height + 1) / 2);
|
|
if (WebPIsAlphaMode(src_buf->colorspace)) {
|
|
WebPCopyPlane(src->a, src->a_stride, dst->a, dst->a_stride,
|
|
src_buf->width, src_buf->height);
|
|
}
|
|
}
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
int WebPAvoidSlowMemory(const WebPDecBuffer* const output,
|
|
const WebPBitstreamFeatures* const features) {
|
|
assert(output != NULL);
|
|
return (output->is_external_memory >= 2) &&
|
|
WebPIsPremultipliedMode(output->colorspace) &&
|
|
(features != NULL && features->has_alpha);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dec/frame_dec.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Frame-reconstruction function. Memory allocation.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main reconstruction function.
|
|
|
|
static const uint16_t kScan[16] = {
|
|
0 + 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS,
|
|
0 + 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS,
|
|
0 + 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS,
|
|
0 + 12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS};
|
|
|
|
static int CheckMode(int mb_x, int mb_y, int mode) {
|
|
if (mode == B_DC_PRED) {
|
|
if (mb_x == 0) {
|
|
return (mb_y == 0) ? B_DC_PRED_NOTOPLEFT : B_DC_PRED_NOLEFT;
|
|
} else {
|
|
return (mb_y == 0) ? B_DC_PRED_NOTOP : B_DC_PRED;
|
|
}
|
|
}
|
|
return mode;
|
|
}
|
|
|
|
static void Copy32b(uint8_t* const dst, const uint8_t* const src) {
|
|
WEBP_UNSAFE_MEMCPY(dst, src, 4);
|
|
}
|
|
|
|
static WEBP_INLINE void DoTransform(uint32_t bits, const int16_t* const src,
|
|
uint8_t* const dst) {
|
|
switch (bits >> 30) {
|
|
case 3:
|
|
VP8Transform(src, dst, 0);
|
|
break;
|
|
case 2:
|
|
VP8TransformAC3(src, dst);
|
|
break;
|
|
case 1:
|
|
VP8TransformDC(src, dst);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void DoUVTransform(uint32_t bits, const int16_t* const src,
|
|
uint8_t* const dst) {
|
|
if (bits & 0xff) { // any non-zero coeff at all?
|
|
if (bits & 0xaa) { // any non-zero AC coefficient?
|
|
VP8TransformUV(src, dst); // note we don't use the AC3 variant for U/V
|
|
} else {
|
|
VP8TransformDCUV(src, dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ReconstructRow(const VP8Decoder* const dec,
|
|
const VP8ThreadContext* ctx) {
|
|
int j;
|
|
int mb_x;
|
|
const int mb_y = ctx->mb_y;
|
|
const int cache_id = ctx->id;
|
|
uint8_t* const y_dst = dec->yuv_b + Y_OFF;
|
|
uint8_t* const u_dst = dec->yuv_b + U_OFF;
|
|
uint8_t* const v_dst = dec->yuv_b + V_OFF;
|
|
|
|
// Initialize left-most block.
|
|
for (j = 0; j < 16; ++j) {
|
|
y_dst[j * BPS - 1] = 129;
|
|
}
|
|
for (j = 0; j < 8; ++j) {
|
|
u_dst[j * BPS - 1] = 129;
|
|
v_dst[j * BPS - 1] = 129;
|
|
}
|
|
|
|
// Init top-left sample on left column too.
|
|
if (mb_y > 0) {
|
|
y_dst[-1 - BPS] = u_dst[-1 - BPS] = v_dst[-1 - BPS] = 129;
|
|
} else {
|
|
// we only need to do this init once at block (0,0).
|
|
// Afterward, it remains valid for the whole topmost row.
|
|
WEBP_UNSAFE_MEMSET(y_dst - BPS - 1, 127, 16 + 4 + 1);
|
|
WEBP_UNSAFE_MEMSET(u_dst - BPS - 1, 127, 8 + 1);
|
|
WEBP_UNSAFE_MEMSET(v_dst - BPS - 1, 127, 8 + 1);
|
|
}
|
|
|
|
// Reconstruct one row.
|
|
for (mb_x = 0; mb_x < dec->mb_w; ++mb_x) {
|
|
const VP8MBData* const block = ctx->mb_data + mb_x;
|
|
|
|
// Rotate in the left samples from previously decoded block. We move four
|
|
// pixels at a time for alignment reason, and because of in-loop filter.
|
|
if (mb_x > 0) {
|
|
for (j = -1; j < 16; ++j) {
|
|
Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]);
|
|
}
|
|
for (j = -1; j < 8; ++j) {
|
|
Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]);
|
|
Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]);
|
|
}
|
|
}
|
|
{
|
|
// bring top samples into the cache
|
|
VP8TopSamples* const top_yuv = dec->yuv_t + mb_x;
|
|
const int16_t* const coeffs = block->coeffs;
|
|
uint32_t bits = block->non_zero_y;
|
|
int n;
|
|
|
|
if (mb_y > 0) {
|
|
WEBP_UNSAFE_MEMCPY(y_dst - BPS, top_yuv[0].y, 16);
|
|
WEBP_UNSAFE_MEMCPY(u_dst - BPS, top_yuv[0].u, 8);
|
|
WEBP_UNSAFE_MEMCPY(v_dst - BPS, top_yuv[0].v, 8);
|
|
}
|
|
|
|
// predict and add residuals
|
|
if (block->is_i4x4) { // 4x4
|
|
uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16);
|
|
|
|
if (mb_y > 0) {
|
|
if (mb_x >= dec->mb_w - 1) { // on rightmost border
|
|
WEBP_UNSAFE_MEMSET(top_right, top_yuv[0].y[15], sizeof(*top_right));
|
|
} else {
|
|
WEBP_UNSAFE_MEMCPY(top_right, top_yuv[1].y, sizeof(*top_right));
|
|
}
|
|
}
|
|
// replicate the top-right pixels below
|
|
top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0];
|
|
|
|
// predict and add residuals for all 4x4 blocks in turn.
|
|
for (n = 0; n < 16; ++n, bits <<= 2) {
|
|
uint8_t* const dst = y_dst + kScan[n];
|
|
VP8PredLuma4[block->imodes[n]](dst);
|
|
DoTransform(bits, coeffs + n * 16, dst);
|
|
}
|
|
} else { // 16x16
|
|
const int pred_func = CheckMode(mb_x, mb_y, block->imodes[0]);
|
|
VP8PredLuma16[pred_func](y_dst);
|
|
if (bits != 0) {
|
|
for (n = 0; n < 16; ++n, bits <<= 2) {
|
|
DoTransform(bits, coeffs + n * 16, y_dst + kScan[n]);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
// Chroma
|
|
const uint32_t bits_uv = block->non_zero_uv;
|
|
const int pred_func = CheckMode(mb_x, mb_y, block->uvmode);
|
|
VP8PredChroma8[pred_func](u_dst);
|
|
VP8PredChroma8[pred_func](v_dst);
|
|
DoUVTransform(bits_uv >> 0, coeffs + 16 * 16, u_dst);
|
|
DoUVTransform(bits_uv >> 8, coeffs + 20 * 16, v_dst);
|
|
}
|
|
|
|
// stash away top samples for next block
|
|
if (mb_y < dec->mb_h - 1) {
|
|
WEBP_UNSAFE_MEMCPY(top_yuv[0].y, y_dst + 15 * BPS, 16);
|
|
WEBP_UNSAFE_MEMCPY(top_yuv[0].u, u_dst + 7 * BPS, 8);
|
|
WEBP_UNSAFE_MEMCPY(top_yuv[0].v, v_dst + 7 * BPS, 8);
|
|
}
|
|
}
|
|
// Transfer reconstructed samples from yuv_b cache to final destination.
|
|
{
|
|
const int y_offset = cache_id * 16 * dec->cache_y_stride;
|
|
const int uv_offset = cache_id * 8 * dec->cache_uv_stride;
|
|
uint8_t* const y_out = dec->cache_y + mb_x * 16 + y_offset;
|
|
uint8_t* const u_out = dec->cache_u + mb_x * 8 + uv_offset;
|
|
uint8_t* const v_out = dec->cache_v + mb_x * 8 + uv_offset;
|
|
for (j = 0; j < 16; ++j) {
|
|
WEBP_UNSAFE_MEMCPY(y_out + j * dec->cache_y_stride, y_dst + j * BPS,
|
|
16);
|
|
}
|
|
for (j = 0; j < 8; ++j) {
|
|
WEBP_UNSAFE_MEMCPY(u_out + j * dec->cache_uv_stride, u_dst + j * BPS,
|
|
8);
|
|
WEBP_UNSAFE_MEMCPY(v_out + j * dec->cache_uv_stride, v_dst + j * BPS,
|
|
8);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Filtering
|
|
|
|
// kFilterExtraRows[] = How many extra lines are needed on the MB boundary
|
|
// for caching, given a filtering level.
|
|
// Simple filter: up to 2 luma samples are read and 1 is written.
|
|
// Complex filter: up to 4 luma samples are read and 3 are written. Same for
|
|
// U/V, so it's 8 samples total (because of the 2x upsampling).
|
|
static const uint8_t kFilterExtraRows[3] = {0, 2, 8};
|
|
|
|
static void DoFilter(const VP8Decoder* const dec, int mb_x, int mb_y) {
|
|
const VP8ThreadContext* const ctx = &dec->thread_ctx;
|
|
const int cache_id = ctx->id;
|
|
const int y_bps = dec->cache_y_stride;
|
|
const VP8FInfo* const f_info = ctx->f_info + mb_x;
|
|
uint8_t* const y_dst = dec->cache_y + cache_id * 16 * y_bps + mb_x * 16;
|
|
const int ilevel = f_info->f_ilevel;
|
|
const int limit = f_info->f_limit;
|
|
if (limit == 0) {
|
|
return;
|
|
}
|
|
assert(limit >= 3);
|
|
if (dec->filter_type == 1) { // simple
|
|
if (mb_x > 0) {
|
|
VP8SimpleHFilter16(y_dst, y_bps, limit + 4);
|
|
}
|
|
if (f_info->f_inner) {
|
|
VP8SimpleHFilter16i(y_dst, y_bps, limit);
|
|
}
|
|
if (mb_y > 0) {
|
|
VP8SimpleVFilter16(y_dst, y_bps, limit + 4);
|
|
}
|
|
if (f_info->f_inner) {
|
|
VP8SimpleVFilter16i(y_dst, y_bps, limit);
|
|
}
|
|
} else { // complex
|
|
const int uv_bps = dec->cache_uv_stride;
|
|
uint8_t* const u_dst = dec->cache_u + cache_id * 8 * uv_bps + mb_x * 8;
|
|
uint8_t* const v_dst = dec->cache_v + cache_id * 8 * uv_bps + mb_x * 8;
|
|
const int hev_thresh = f_info->hev_thresh;
|
|
if (mb_x > 0) {
|
|
VP8HFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh);
|
|
VP8HFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh);
|
|
}
|
|
if (f_info->f_inner) {
|
|
VP8HFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh);
|
|
VP8HFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh);
|
|
}
|
|
if (mb_y > 0) {
|
|
VP8VFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh);
|
|
VP8VFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh);
|
|
}
|
|
if (f_info->f_inner) {
|
|
VP8VFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh);
|
|
VP8VFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter the decoded macroblock row (if needed)
|
|
static void FilterRow(const VP8Decoder* const dec) {
|
|
int mb_x;
|
|
const int mb_y = dec->thread_ctx.mb_y;
|
|
assert(dec->thread_ctx.filter_row);
|
|
for (mb_x = dec->tl_mb_x; mb_x < dec->br_mb_x; ++mb_x) {
|
|
DoFilter(dec, mb_x, mb_y);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Precompute the filtering strength for each segment and each i4x4/i16x16 mode.
|
|
|
|
static void PrecomputeFilterStrengths(VP8Decoder* const dec) {
|
|
if (dec->filter_type > 0) {
|
|
int s;
|
|
const VP8FilterHeader* const hdr = &dec->filter_hdr;
|
|
for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
|
|
int i4x4;
|
|
// First, compute the initial level
|
|
int base_level;
|
|
if (dec->segment_hdr.use_segment) {
|
|
base_level = dec->segment_hdr.filter_strength[s];
|
|
if (!dec->segment_hdr.absolute_delta) {
|
|
base_level += hdr->level;
|
|
}
|
|
} else {
|
|
base_level = hdr->level;
|
|
}
|
|
for (i4x4 = 0; i4x4 <= 1; ++i4x4) {
|
|
VP8FInfo* const info = &dec->fstrengths[s][i4x4];
|
|
int level = base_level;
|
|
if (hdr->use_lf_delta) {
|
|
level += hdr->ref_lf_delta[0];
|
|
if (i4x4) {
|
|
level += hdr->mode_lf_delta[0];
|
|
}
|
|
}
|
|
level = (level < 0) ? 0 : (level > 63) ? 63 : level;
|
|
if (level > 0) {
|
|
int ilevel = level;
|
|
if (hdr->sharpness > 0) {
|
|
if (hdr->sharpness > 4) {
|
|
ilevel >>= 2;
|
|
} else {
|
|
ilevel >>= 1;
|
|
}
|
|
if (ilevel > 9 - hdr->sharpness) {
|
|
ilevel = 9 - hdr->sharpness;
|
|
}
|
|
}
|
|
if (ilevel < 1) ilevel = 1;
|
|
info->f_ilevel = ilevel;
|
|
info->f_limit = 2 * level + ilevel;
|
|
info->hev_thresh = (level >= 40) ? 2 : (level >= 15) ? 1 : 0;
|
|
} else {
|
|
info->f_limit = 0; // no filtering
|
|
}
|
|
info->f_inner = i4x4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Dithering
|
|
|
|
// minimal amp that will provide a non-zero dithering effect
|
|
#define MIN_DITHER_AMP 4
|
|
|
|
#define DITHER_AMP_TAB_SIZE 12
|
|
static const uint8_t kQuantToDitherAmp[DITHER_AMP_TAB_SIZE] = {
|
|
// roughly, it's dqm->uv_mat[1]
|
|
8, 7, 6, 4, 4, 2, 2, 2, 1, 1, 1, 1};
|
|
|
|
void VP8InitDithering(const WebPDecoderOptions* const options,
|
|
VP8Decoder* const dec) {
|
|
assert(dec != NULL);
|
|
if (options != NULL) {
|
|
const int d = options->dithering_strength;
|
|
const int max_amp = (1 << VP8_RANDOM_DITHER_FIX) - 1;
|
|
const int f = (d < 0) ? 0 : (d > 100) ? max_amp : (d * max_amp / 100);
|
|
if (f > 0) {
|
|
int s;
|
|
int all_amp = 0;
|
|
for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
|
|
VP8QuantMatrix* const dqm = &dec->dqm[s];
|
|
if (dqm->uv_quant < DITHER_AMP_TAB_SIZE) {
|
|
const int idx = (dqm->uv_quant < 0) ? 0 : dqm->uv_quant;
|
|
dqm->dither = (f * kQuantToDitherAmp[idx]) >> 3;
|
|
}
|
|
all_amp |= dqm->dither;
|
|
}
|
|
if (all_amp != 0) {
|
|
VP8InitRandom(&dec->dithering_rg, 1.0f);
|
|
dec->dither = 1;
|
|
}
|
|
}
|
|
// potentially allow alpha dithering
|
|
dec->alpha_dithering = options->alpha_dithering_strength;
|
|
if (dec->alpha_dithering > 100) {
|
|
dec->alpha_dithering = 100;
|
|
} else if (dec->alpha_dithering < 0) {
|
|
dec->alpha_dithering = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert to range: [-2,2] for dither=50, [-4,4] for dither=100
|
|
static void Dither8x8(VP8Random* const rg, uint8_t* dst, int bps, int amp) {
|
|
uint8_t dither[64];
|
|
int i;
|
|
for (i = 0; i < 8 * 8; ++i) {
|
|
dither[i] = VP8RandomBits2(rg, VP8_DITHER_AMP_BITS + 1, amp);
|
|
}
|
|
VP8DitherCombine8x8(dither, dst, bps);
|
|
}
|
|
|
|
static void DitherRow(VP8Decoder* const dec) {
|
|
int mb_x;
|
|
assert(dec->dither);
|
|
for (mb_x = dec->tl_mb_x; mb_x < dec->br_mb_x; ++mb_x) {
|
|
const VP8ThreadContext* const ctx = &dec->thread_ctx;
|
|
const VP8MBData* const data = ctx->mb_data + mb_x;
|
|
const int cache_id = ctx->id;
|
|
const int uv_bps = dec->cache_uv_stride;
|
|
if (data->dither >= MIN_DITHER_AMP) {
|
|
uint8_t* const u_dst = dec->cache_u + cache_id * 8 * uv_bps + mb_x * 8;
|
|
uint8_t* const v_dst = dec->cache_v + cache_id * 8 * uv_bps + mb_x * 8;
|
|
Dither8x8(&dec->dithering_rg, u_dst, uv_bps, data->dither);
|
|
Dither8x8(&dec->dithering_rg, v_dst, uv_bps, data->dither);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// This function is called after a row of macroblocks is finished decoding.
|
|
// It also takes into account the following restrictions:
|
|
// * In case of in-loop filtering, we must hold off sending some of the bottom
|
|
// pixels as they are yet unfiltered. They will be when the next macroblock
|
|
// row is decoded. Meanwhile, we must preserve them by rotating them in the
|
|
// cache area. This doesn't hold for the very bottom row of the uncropped
|
|
// picture of course.
|
|
// * we must clip the remaining pixels against the cropping area. The VP8Io
|
|
// struct must have the following fields set correctly before calling put():
|
|
|
|
#define MACROBLOCK_VPOS(mb_y) ((mb_y) * 16) // vertical position of a MB
|
|
|
|
// Finalize and transmit a complete row. Return false in case of user-abort.
|
|
static int FinishRow(void* arg1, void* arg2) {
|
|
VP8Decoder* const dec = (VP8Decoder*)arg1;
|
|
VP8Io* const io = (VP8Io*)arg2;
|
|
int ok = 1;
|
|
const VP8ThreadContext* const ctx = &dec->thread_ctx;
|
|
const int cache_id = ctx->id;
|
|
const int extra_y_rows = kFilterExtraRows[dec->filter_type];
|
|
const int ysize = extra_y_rows * dec->cache_y_stride;
|
|
const int uvsize = (extra_y_rows / 2) * dec->cache_uv_stride;
|
|
const int y_offset = cache_id * 16 * dec->cache_y_stride;
|
|
const int uv_offset = cache_id * 8 * dec->cache_uv_stride;
|
|
uint8_t* const ydst = dec->cache_y - ysize + y_offset;
|
|
uint8_t* const udst = dec->cache_u - uvsize + uv_offset;
|
|
uint8_t* const vdst = dec->cache_v - uvsize + uv_offset;
|
|
const int mb_y = ctx->mb_y;
|
|
const int is_first_row = (mb_y == 0);
|
|
const int is_last_row = (mb_y >= dec->br_mb_y - 1);
|
|
|
|
if (dec->mt_method == 2) {
|
|
ReconstructRow(dec, ctx);
|
|
}
|
|
|
|
if (ctx->filter_row) {
|
|
FilterRow(dec);
|
|
}
|
|
|
|
if (dec->dither) {
|
|
DitherRow(dec);
|
|
}
|
|
|
|
if (io->put != NULL) {
|
|
int y_start = MACROBLOCK_VPOS(mb_y);
|
|
int y_end = MACROBLOCK_VPOS(mb_y + 1);
|
|
if (!is_first_row) {
|
|
y_start -= extra_y_rows;
|
|
io->y = ydst;
|
|
io->u = udst;
|
|
io->v = vdst;
|
|
} else {
|
|
io->y = dec->cache_y + y_offset;
|
|
io->u = dec->cache_u + uv_offset;
|
|
io->v = dec->cache_v + uv_offset;
|
|
}
|
|
|
|
if (!is_last_row) {
|
|
y_end -= extra_y_rows;
|
|
}
|
|
if (y_end > io->crop_bottom) {
|
|
y_end = io->crop_bottom; // make sure we don't overflow on last row.
|
|
}
|
|
// If dec->alpha_data is not NULL, we have some alpha plane present.
|
|
io->a = NULL;
|
|
if (dec->alpha_data != NULL && y_start < y_end) {
|
|
io->a = VP8DecompressAlphaRows(dec, io, y_start, y_end - y_start);
|
|
if (io->a == NULL) {
|
|
return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
|
|
"Could not decode alpha data.");
|
|
}
|
|
}
|
|
if (y_start < io->crop_top) {
|
|
const int delta_y = io->crop_top - y_start;
|
|
y_start = io->crop_top;
|
|
assert(!(delta_y & 1));
|
|
io->y += dec->cache_y_stride * delta_y;
|
|
io->u += dec->cache_uv_stride * (delta_y >> 1);
|
|
io->v += dec->cache_uv_stride * (delta_y >> 1);
|
|
if (io->a != NULL) {
|
|
io->a += io->width * delta_y;
|
|
}
|
|
}
|
|
if (y_start < y_end) {
|
|
io->y += io->crop_left;
|
|
io->u += io->crop_left >> 1;
|
|
io->v += io->crop_left >> 1;
|
|
if (io->a != NULL) {
|
|
io->a += io->crop_left;
|
|
}
|
|
io->mb_y = y_start - io->crop_top;
|
|
io->mb_w = io->crop_right - io->crop_left;
|
|
io->mb_h = y_end - y_start;
|
|
ok = io->put(io);
|
|
}
|
|
}
|
|
// rotate top samples if needed
|
|
if (cache_id + 1 == dec->num_caches) {
|
|
if (!is_last_row) {
|
|
WEBP_UNSAFE_MEMCPY(dec->cache_y - ysize, ydst + 16 * dec->cache_y_stride,
|
|
ysize);
|
|
WEBP_UNSAFE_MEMCPY(dec->cache_u - uvsize, udst + 8 * dec->cache_uv_stride,
|
|
uvsize);
|
|
WEBP_UNSAFE_MEMCPY(dec->cache_v - uvsize, vdst + 8 * dec->cache_uv_stride,
|
|
uvsize);
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
#undef MACROBLOCK_VPOS
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io) {
|
|
int ok = 1;
|
|
VP8ThreadContext* const ctx = &dec->thread_ctx;
|
|
const int filter_row = (dec->filter_type > 0) &&
|
|
(dec->mb_y >= dec->tl_mb_y) &&
|
|
(dec->mb_y <= dec->br_mb_y);
|
|
if (dec->mt_method == 0) {
|
|
// ctx->id and ctx->f_info are already set
|
|
ctx->mb_y = dec->mb_y;
|
|
ctx->filter_row = filter_row;
|
|
ReconstructRow(dec, ctx);
|
|
ok = FinishRow(dec, io);
|
|
} else {
|
|
WebPWorker* const worker = &dec->worker;
|
|
// Finish previous job *before* updating context
|
|
ok &= WebPGetWorkerInterface()->Sync(worker);
|
|
assert(worker->status == OK);
|
|
if (ok) { // spawn a new deblocking/output job
|
|
ctx->io = *io;
|
|
ctx->id = dec->cache_id;
|
|
ctx->mb_y = dec->mb_y;
|
|
ctx->filter_row = filter_row;
|
|
if (dec->mt_method == 2) { // swap macroblock data
|
|
VP8MBData* const tmp = ctx->mb_data;
|
|
ctx->mb_data = dec->mb_data;
|
|
dec->mb_data = tmp;
|
|
} else {
|
|
// perform reconstruction directly in main thread
|
|
ReconstructRow(dec, ctx);
|
|
}
|
|
if (filter_row) { // swap filter info
|
|
VP8FInfo* const tmp = ctx->f_info;
|
|
ctx->f_info = dec->f_info;
|
|
dec->f_info = tmp;
|
|
}
|
|
// (reconstruct)+filter in parallel
|
|
WebPGetWorkerInterface()->Launch(worker);
|
|
if (++dec->cache_id == dec->num_caches) {
|
|
dec->cache_id = 0;
|
|
}
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Finish setting up the decoding parameter once user's setup() is called.
|
|
|
|
VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) {
|
|
// Call setup() first. This may trigger additional decoding features on 'io'.
|
|
// Note: Afterward, we must call teardown() no matter what.
|
|
if (io->setup != NULL && !io->setup(io)) {
|
|
VP8SetError(dec, VP8_STATUS_INVALID_PARAM, "Frame setup failed");
|
|
return dec->status;
|
|
}
|
|
|
|
// Disable filtering per user request
|
|
if (io->bypass_filtering) {
|
|
dec->filter_type = 0;
|
|
}
|
|
|
|
// Define the area where we can skip in-loop filtering, in case of cropping.
|
|
//
|
|
// 'Simple' filter reads two luma samples outside of the macroblock
|
|
// and filters one. It doesn't filter the chroma samples. Hence, we can
|
|
// avoid doing the in-loop filtering before crop_top/crop_left position.
|
|
// For the 'Complex' filter, 3 samples are read and up to 3 are filtered.
|
|
// Means: there's a dependency chain that goes all the way up to the
|
|
// top-left corner of the picture (MB #0). We must filter all the previous
|
|
// macroblocks.
|
|
{
|
|
const int extra_pixels = kFilterExtraRows[dec->filter_type];
|
|
if (dec->filter_type == 2) {
|
|
// For complex filter, we need to preserve the dependency chain.
|
|
dec->tl_mb_x = 0;
|
|
dec->tl_mb_y = 0;
|
|
} else {
|
|
// For simple filter, we can filter only the cropped region.
|
|
// We include 'extra_pixels' on the other side of the boundary, since
|
|
// vertical or horizontal filtering of the previous macroblock can
|
|
// modify some abutting pixels.
|
|
dec->tl_mb_x = (io->crop_left - extra_pixels) >> 4;
|
|
dec->tl_mb_y = (io->crop_top - extra_pixels) >> 4;
|
|
if (dec->tl_mb_x < 0) dec->tl_mb_x = 0;
|
|
if (dec->tl_mb_y < 0) dec->tl_mb_y = 0;
|
|
}
|
|
// We need some 'extra' pixels on the right/bottom.
|
|
dec->br_mb_y = (io->crop_bottom + 15 + extra_pixels) >> 4;
|
|
dec->br_mb_x = (io->crop_right + 15 + extra_pixels) >> 4;
|
|
if (dec->br_mb_x > dec->mb_w) {
|
|
dec->br_mb_x = dec->mb_w;
|
|
}
|
|
if (dec->br_mb_y > dec->mb_h) {
|
|
dec->br_mb_y = dec->mb_h;
|
|
}
|
|
}
|
|
PrecomputeFilterStrengths(dec);
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io) {
|
|
int ok = 1;
|
|
if (dec->mt_method > 0) {
|
|
ok = WebPGetWorkerInterface()->Sync(&dec->worker);
|
|
}
|
|
|
|
if (io->teardown != NULL) {
|
|
io->teardown(io);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// For multi-threaded decoding we need to use 3 rows of 16 pixels as delay line.
|
|
//
|
|
// Reason is: the deblocking filter cannot deblock the bottom horizontal edges
|
|
// immediately, and needs to wait for first few rows of the next macroblock to
|
|
// be decoded. Hence, deblocking is lagging behind by 4 or 8 pixels (depending
|
|
// on strength).
|
|
// With two threads, the vertical positions of the rows being decoded are:
|
|
// Decode: [ 0..15][16..31][32..47][48..63][64..79][...
|
|
// Deblock: [ 0..11][12..27][28..43][44..59][...
|
|
// If we use two threads and two caches of 16 pixels, the sequence would be:
|
|
// Decode: [ 0..15][16..31][ 0..15!!][16..31][ 0..15][...
|
|
// Deblock: [ 0..11][12..27!!][-4..11][12..27][...
|
|
// The problem occurs during row [12..15!!] that both the decoding and
|
|
// deblocking threads are writing simultaneously.
|
|
// With 3 cache lines, one get a safe write pattern:
|
|
// Decode: [ 0..15][16..31][32..47][ 0..15][16..31][32..47][0..
|
|
// Deblock: [ 0..11][12..27][28..43][-4..11][12..27][28...
|
|
// Note that multi-threaded output _without_ deblocking can make use of two
|
|
// cache lines of 16 pixels only, since there's no lagging behind. The decoding
|
|
// and output process have non-concurrent writing:
|
|
// Decode: [ 0..15][16..31][ 0..15][16..31][...
|
|
// io->put: [ 0..15][16..31][ 0..15][...
|
|
|
|
#define MT_CACHE_LINES 3
|
|
#define ST_CACHE_LINES 1 // 1 cache row only for single-threaded case
|
|
|
|
// Initialize multi/single-thread worker
|
|
static int InitThreadContext(VP8Decoder* const dec) {
|
|
dec->cache_id = 0;
|
|
if (dec->mt_method > 0) {
|
|
WebPWorker* const worker = &dec->worker;
|
|
if (!WebPGetWorkerInterface()->Reset(worker)) {
|
|
return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY,
|
|
"thread initialization failed.");
|
|
}
|
|
worker->data1 = dec;
|
|
worker->data2 = (void*)&dec->thread_ctx.io;
|
|
worker->hook = FinishRow;
|
|
dec->num_caches =
|
|
(dec->filter_type > 0) ? MT_CACHE_LINES : MT_CACHE_LINES - 1;
|
|
} else {
|
|
dec->num_caches = ST_CACHE_LINES;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int VP8GetThreadMethod(const WebPDecoderOptions* const options,
|
|
const WebPHeaderStructure* const headers, int width,
|
|
int height) {
|
|
if (options == NULL || options->use_threads == 0) {
|
|
return 0;
|
|
}
|
|
(void)headers;
|
|
(void)width;
|
|
(void)height;
|
|
assert(headers == NULL || !headers->is_lossless);
|
|
#if defined(WEBP_USE_THREAD)
|
|
if (width >= MIN_WIDTH_FOR_THREADS) return 2;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#undef MT_CACHE_LINES
|
|
#undef ST_CACHE_LINES
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Memory setup
|
|
|
|
static int AllocateMemory(VP8Decoder* const dec) {
|
|
const int num_caches = dec->num_caches;
|
|
const int mb_w = dec->mb_w;
|
|
// Note: we use 'size_t' when there's no overflow risk, uint64_t otherwise.
|
|
const size_t intra_pred_mode_size = 4 * mb_w * sizeof(uint8_t);
|
|
const size_t top_size = sizeof(VP8TopSamples) * mb_w;
|
|
const size_t mb_info_size = (mb_w + 1) * sizeof(VP8MB);
|
|
const size_t f_info_size =
|
|
(dec->filter_type > 0)
|
|
? mb_w * (dec->mt_method > 0 ? 2 : 1) * sizeof(VP8FInfo)
|
|
: 0;
|
|
const size_t yuv_size = YUV_SIZE * sizeof(*dec->yuv_b);
|
|
const size_t mb_data_size =
|
|
(dec->mt_method == 2 ? 2 : 1) * mb_w * sizeof(*dec->mb_data);
|
|
const size_t cache_height =
|
|
(16 * num_caches + kFilterExtraRows[dec->filter_type]) * 3 / 2;
|
|
const size_t cache_size = top_size * cache_height;
|
|
// alpha_size is the only one that scales as width x height.
|
|
const uint64_t alpha_size =
|
|
(dec->alpha_data != NULL)
|
|
? (uint64_t)dec->pic_hdr.width * dec->pic_hdr.height
|
|
: 0ULL;
|
|
const uint64_t needed = (uint64_t)intra_pred_mode_size + top_size +
|
|
mb_info_size + f_info_size + yuv_size + mb_data_size +
|
|
cache_size + alpha_size + WEBP_ALIGN_CST;
|
|
uint8_t* mem;
|
|
|
|
if (!CheckSizeOverflow(needed)) return 0; // check for overflow
|
|
if (needed > dec->mem_size) {
|
|
WebPSafeFree(dec->mem);
|
|
dec->mem_size = 0;
|
|
dec->mem = WebPSafeMalloc(needed, sizeof(uint8_t));
|
|
if (dec->mem == NULL) {
|
|
return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY,
|
|
"no memory during frame initialization.");
|
|
}
|
|
// down-cast is ok, thanks to WebPSafeMalloc() above.
|
|
dec->mem_size = (size_t)needed;
|
|
}
|
|
|
|
mem = (uint8_t*)dec->mem;
|
|
dec->intra_t = mem;
|
|
mem += intra_pred_mode_size;
|
|
|
|
dec->yuv_t = (VP8TopSamples*)mem;
|
|
mem += top_size;
|
|
|
|
dec->mb_info = ((VP8MB*)mem) + 1;
|
|
mem += mb_info_size;
|
|
|
|
dec->f_info = f_info_size ? (VP8FInfo*)mem : NULL;
|
|
mem += f_info_size;
|
|
dec->thread_ctx.id = 0;
|
|
dec->thread_ctx.f_info = dec->f_info;
|
|
if (dec->filter_type > 0 && dec->mt_method > 0) {
|
|
// secondary cache line. The deblocking process need to make use of the
|
|
// filtering strength from previous macroblock row, while the new ones
|
|
// are being decoded in parallel. We'll just swap the pointers.
|
|
dec->thread_ctx.f_info += mb_w;
|
|
}
|
|
|
|
mem = (uint8_t*)WEBP_ALIGN(mem);
|
|
assert((yuv_size & WEBP_ALIGN_CST) == 0);
|
|
dec->yuv_b = mem;
|
|
mem += yuv_size;
|
|
|
|
dec->mb_data = (VP8MBData*)mem;
|
|
dec->thread_ctx.mb_data = (VP8MBData*)mem;
|
|
if (dec->mt_method == 2) {
|
|
dec->thread_ctx.mb_data += mb_w;
|
|
}
|
|
mem += mb_data_size;
|
|
|
|
dec->cache_y_stride = 16 * mb_w;
|
|
dec->cache_uv_stride = 8 * mb_w;
|
|
{
|
|
const int extra_rows = kFilterExtraRows[dec->filter_type];
|
|
const int extra_y = extra_rows * dec->cache_y_stride;
|
|
const int extra_uv = (extra_rows / 2) * dec->cache_uv_stride;
|
|
dec->cache_y = mem + extra_y;
|
|
dec->cache_u =
|
|
dec->cache_y + 16 * num_caches * dec->cache_y_stride + extra_uv;
|
|
dec->cache_v =
|
|
dec->cache_u + 8 * num_caches * dec->cache_uv_stride + extra_uv;
|
|
dec->cache_id = 0;
|
|
}
|
|
mem += cache_size;
|
|
|
|
// alpha plane
|
|
dec->alpha_plane = alpha_size ? mem : NULL;
|
|
mem += alpha_size;
|
|
assert(mem <= (uint8_t*)dec->mem + dec->mem_size);
|
|
|
|
// note: left/top-info is initialized once for all.
|
|
WEBP_UNSAFE_MEMSET(dec->mb_info - 1, 0, mb_info_size);
|
|
VP8InitScanline(dec); // initialize left too.
|
|
|
|
// initialize top
|
|
WEBP_UNSAFE_MEMSET(dec->intra_t, B_DC_PRED, intra_pred_mode_size);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void InitIo(VP8Decoder* const dec, VP8Io* io) {
|
|
// prepare 'io'
|
|
io->mb_y = 0;
|
|
io->y = dec->cache_y;
|
|
io->u = dec->cache_u;
|
|
io->v = dec->cache_v;
|
|
io->y_stride = dec->cache_y_stride;
|
|
io->uv_stride = dec->cache_uv_stride;
|
|
io->a = NULL;
|
|
}
|
|
|
|
int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io) {
|
|
if (!InitThreadContext(dec)) return 0; // call first. Sets dec->num_caches.
|
|
if (!AllocateMemory(dec)) return 0;
|
|
InitIo(dec, io);
|
|
VP8DspInit(); // Init critical function pointers and look-up tables.
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dec/idec_dec.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Incremental decoding
|
|
//
|
|
// Author: somnath@google.com (Somnath Banerjee)
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// In append mode, buffer allocations increase as multiples of this value.
|
|
// Needs to be a power of 2.
|
|
#define CHUNK_SIZE 4096
|
|
#define MAX_MB_SIZE 4096
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Data structures for memory and states
|
|
|
|
// Decoding states. State normally flows as:
|
|
// WEBP_HEADER->VP8_HEADER->VP8_PARTS0->VP8_DATA->DONE for a lossy image, and
|
|
// WEBP_HEADER->VP8L_HEADER->VP8L_DATA->DONE for a lossless image.
|
|
// If there is any error the decoder goes into state ERROR.
|
|
typedef enum {
|
|
STATE_WEBP_HEADER, // All the data before that of the VP8/VP8L chunk.
|
|
STATE_VP8_HEADER, // The VP8 Frame header (within the VP8 chunk).
|
|
STATE_VP8_PARTS0,
|
|
STATE_VP8_DATA,
|
|
STATE_VP8L_HEADER,
|
|
STATE_VP8L_DATA,
|
|
STATE_DONE,
|
|
STATE_ERROR
|
|
} DecState;
|
|
|
|
// Operating state for the MemBuffer
|
|
typedef enum { MEM_MODE_NONE = 0, MEM_MODE_APPEND, MEM_MODE_MAP } MemBufferMode;
|
|
|
|
// storage for partition #0 and partial data (in a rolling fashion)
|
|
typedef struct {
|
|
MemBufferMode mode; // Operation mode
|
|
size_t start; // start location of the data to be decoded
|
|
size_t end; // end location
|
|
size_t buf_size; // size of the allocated buffer
|
|
uint8_t* buf; // We don't own this buffer in case WebPIUpdate()
|
|
|
|
size_t part0_size; // size of partition #0
|
|
const uint8_t* part0_buf; // buffer to store partition #0
|
|
} MemBuffer;
|
|
|
|
struct WebPIDecoder {
|
|
DecState state; // current decoding state
|
|
WebPDecParams params; // Params to store output info
|
|
int is_lossless; // for down-casting 'dec'.
|
|
void* dec; // either a VP8Decoder or a VP8LDecoder instance
|
|
VP8Io io;
|
|
|
|
MemBuffer mem; // input memory buffer.
|
|
WebPDecBuffer output; // output buffer (when no external one is supplied,
|
|
// or if the external one has slow-memory)
|
|
WebPDecBuffer* final_output; // Slow-memory output to copy to eventually.
|
|
size_t chunk_size; // Compressed VP8/VP8L size extracted from Header.
|
|
|
|
int last_mb_y; // last row reached for intra-mode decoding
|
|
};
|
|
|
|
// MB context to restore in case VP8DecodeMB() fails
|
|
typedef struct {
|
|
VP8MB left;
|
|
VP8MB info;
|
|
VP8BitReader token_br;
|
|
} MBContext;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// MemBuffer: incoming data handling
|
|
|
|
static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) {
|
|
return (mem->end - mem->start);
|
|
}
|
|
|
|
// Check if we need to preserve the compressed alpha data, as it may not have
|
|
// been decoded yet.
|
|
static int NeedCompressedAlpha(const WebPIDecoder* const idec) {
|
|
if (idec->state == STATE_WEBP_HEADER) {
|
|
// We haven't parsed the headers yet, so we don't know whether the image is
|
|
// lossy or lossless. This also means that we haven't parsed the ALPH chunk.
|
|
return 0;
|
|
}
|
|
if (idec->is_lossless) {
|
|
return 0; // ALPH chunk is not present for lossless images.
|
|
} else {
|
|
const VP8Decoder* const dec = (VP8Decoder*)idec->dec;
|
|
assert(dec != NULL); // Must be true as idec->state != STATE_WEBP_HEADER.
|
|
return (dec->alpha_data != NULL) && !dec->is_alpha_decoded;
|
|
}
|
|
}
|
|
|
|
static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) {
|
|
MemBuffer* const mem = &idec->mem;
|
|
const uint8_t* const new_base = mem->buf + mem->start;
|
|
// note: for VP8, setting up idec->io is only really needed at the beginning
|
|
// of the decoding, till partition #0 is complete.
|
|
idec->io.data = new_base;
|
|
idec->io.data_size = MemDataSize(mem);
|
|
|
|
if (idec->dec != NULL) {
|
|
if (!idec->is_lossless) {
|
|
VP8Decoder* const dec = (VP8Decoder*)idec->dec;
|
|
const uint32_t last_part = dec->num_parts_minus_one;
|
|
if (offset != 0) {
|
|
uint32_t p;
|
|
for (p = 0; p <= last_part; ++p) {
|
|
VP8RemapBitReader(dec->parts + p, offset);
|
|
}
|
|
// Remap partition #0 data pointer to new offset, but only in MAP
|
|
// mode (in APPEND mode, partition #0 is copied into a fixed memory).
|
|
if (mem->mode == MEM_MODE_MAP) {
|
|
VP8RemapBitReader(&dec->br, offset);
|
|
}
|
|
}
|
|
{
|
|
const uint8_t* const last_start = dec->parts[last_part].buf;
|
|
// 'last_start' will be NULL when 'idec->state' is < STATE_VP8_PARTS0
|
|
// and through a portion of that state (when there isn't enough data to
|
|
// parse the partitions). The bitreader is only used meaningfully when
|
|
// there is enough data to begin parsing partition 0.
|
|
if (last_start != NULL) {
|
|
const size_t part_size = mem->buf + mem->end - last_start;
|
|
const uint8_t* WEBP_BIDI_INDEXABLE const bounded_last_start =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, last_start,
|
|
part_size);
|
|
VP8BitReaderSetBuffer(&dec->parts[last_part], bounded_last_start,
|
|
part_size);
|
|
}
|
|
}
|
|
if (NeedCompressedAlpha(idec)) {
|
|
ALPHDecoder* const alph_dec = dec->alph_dec;
|
|
dec->alpha_data += offset;
|
|
WEBP_SELF_ASSIGN(dec->alpha_data_size);
|
|
if (alph_dec != NULL && alph_dec->vp8l_dec != NULL) {
|
|
if (alph_dec->method == ALPHA_LOSSLESS_COMPRESSION) {
|
|
VP8LDecoder* const alph_vp8l_dec = alph_dec->vp8l_dec;
|
|
size_t data_size;
|
|
const uint8_t* WEBP_BIDI_INDEXABLE bounded_alpha_data;
|
|
|
|
assert(dec->alpha_data_size >= ALPHA_HEADER_LEN);
|
|
data_size = dec->alpha_data_size - ALPHA_HEADER_LEN;
|
|
bounded_alpha_data = WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
const uint8_t*, dec->alpha_data + ALPHA_HEADER_LEN, data_size);
|
|
VP8LBitReaderSetBuffer(&alph_vp8l_dec->br, bounded_alpha_data,
|
|
data_size);
|
|
} else { // alph_dec->method == ALPHA_NO_COMPRESSION
|
|
// Nothing special to do in this case.
|
|
}
|
|
}
|
|
}
|
|
} else { // Resize lossless bitreader
|
|
VP8LDecoder* const dec = (VP8LDecoder*)idec->dec;
|
|
const size_t data_size = MemDataSize(mem);
|
|
const uint8_t* WEBP_BIDI_INDEXABLE const bounded_new_base =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, new_base, data_size);
|
|
VP8LBitReaderSetBuffer(&dec->br, bounded_new_base, data_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Appends data to the end of MemBuffer->buf. It expands the allocated memory
|
|
// size if required and also updates VP8BitReader's if new memory is allocated.
|
|
WEBP_NODISCARD static int AppendToMemBuffer(WebPIDecoder* const idec,
|
|
const uint8_t* const data,
|
|
size_t data_size) {
|
|
VP8Decoder* const dec = (VP8Decoder*)idec->dec;
|
|
MemBuffer* const mem = &idec->mem;
|
|
const int need_compressed_alpha = NeedCompressedAlpha(idec);
|
|
const uint8_t* const old_start =
|
|
(mem->buf == NULL) ? NULL : mem->buf + mem->start;
|
|
const uint8_t* const old_base =
|
|
need_compressed_alpha ? dec->alpha_data : old_start;
|
|
assert(mem->buf != NULL || mem->start == 0);
|
|
assert(mem->mode == MEM_MODE_APPEND);
|
|
if (data_size > MAX_CHUNK_PAYLOAD) {
|
|
// security safeguard: trying to allocate more than what the format
|
|
// allows for a chunk should be considered a smoke smell.
|
|
return 0;
|
|
}
|
|
|
|
if (mem->end + data_size > mem->buf_size) { // Need some free memory
|
|
const size_t new_mem_start = old_start - old_base;
|
|
const size_t current_size = MemDataSize(mem) + new_mem_start;
|
|
const uint64_t new_size = (uint64_t)current_size + data_size;
|
|
const uint64_t extra_size = (new_size + CHUNK_SIZE - 1) & ~(CHUNK_SIZE - 1);
|
|
uint8_t* const new_buf =
|
|
(uint8_t*)WebPSafeMalloc(extra_size, sizeof(*new_buf));
|
|
if (new_buf == NULL) return 0;
|
|
if (old_base != NULL) WEBP_UNSAFE_MEMCPY(new_buf, old_base, current_size);
|
|
WebPSafeFree(mem->buf);
|
|
mem->buf = new_buf;
|
|
mem->buf_size = (size_t)extra_size;
|
|
mem->start = new_mem_start;
|
|
mem->end = current_size;
|
|
}
|
|
|
|
assert(mem->buf != NULL);
|
|
WEBP_UNSAFE_MEMCPY(mem->buf + mem->end, data, data_size);
|
|
mem->end += data_size;
|
|
assert(mem->end <= mem->buf_size);
|
|
|
|
DoRemap(idec, mem->buf + mem->start - old_start);
|
|
return 1;
|
|
}
|
|
|
|
WEBP_NODISCARD static int RemapMemBuffer(WebPIDecoder* const idec,
|
|
const uint8_t* const data,
|
|
size_t data_size) {
|
|
MemBuffer* const mem = &idec->mem;
|
|
const uint8_t* const old_buf = mem->buf;
|
|
const uint8_t* const old_start =
|
|
(old_buf == NULL) ? NULL : old_buf + mem->start;
|
|
assert(old_buf != NULL || mem->start == 0);
|
|
assert(mem->mode == MEM_MODE_MAP);
|
|
|
|
if (data_size < mem->buf_size) return 0; // can't remap to a shorter buffer!
|
|
|
|
mem->buf = (uint8_t*)data;
|
|
mem->end = mem->buf_size = data_size;
|
|
|
|
DoRemap(idec, mem->buf + mem->start - old_start);
|
|
return 1;
|
|
}
|
|
|
|
static void InitMemBuffer(MemBuffer* const mem) {
|
|
mem->mode = MEM_MODE_NONE;
|
|
mem->buf = NULL;
|
|
mem->buf_size = 0;
|
|
mem->part0_buf = NULL;
|
|
mem->part0_size = 0;
|
|
}
|
|
|
|
static void ClearMemBuffer(MemBuffer* const mem) {
|
|
assert(mem);
|
|
if (mem->mode == MEM_MODE_APPEND) {
|
|
WebPSafeFree(mem->buf);
|
|
WebPSafeFree((void*)mem->part0_buf);
|
|
}
|
|
}
|
|
|
|
WEBP_NODISCARD static int CheckMemBufferMode(MemBuffer* const mem,
|
|
MemBufferMode expected) {
|
|
if (mem->mode == MEM_MODE_NONE) {
|
|
mem->mode = expected; // switch to the expected mode
|
|
} else if (mem->mode != expected) {
|
|
return 0; // we mixed the modes => error
|
|
}
|
|
assert(mem->mode == expected); // mode is ok
|
|
return 1;
|
|
}
|
|
|
|
// To be called last.
|
|
WEBP_NODISCARD static VP8StatusCode FinishDecoding(WebPIDecoder* const idec) {
|
|
const WebPDecoderOptions* const options = idec->params.options;
|
|
WebPDecBuffer* const output = idec->params.output;
|
|
|
|
idec->state = STATE_DONE;
|
|
if (options != NULL && options->flip) {
|
|
const VP8StatusCode status = WebPFlipBuffer(output);
|
|
if (status != VP8_STATUS_OK) return status;
|
|
}
|
|
if (idec->final_output != NULL) {
|
|
const VP8StatusCode status = WebPCopyDecBufferPixels(
|
|
output, idec->final_output); // do the slow-copy
|
|
WebPFreeDecBuffer(&idec->output);
|
|
if (status != VP8_STATUS_OK) return status;
|
|
*output = *idec->final_output;
|
|
idec->final_output = NULL;
|
|
}
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Macroblock-decoding contexts
|
|
|
|
static void SaveContext(const VP8Decoder* dec, const VP8BitReader* token_br,
|
|
MBContext* const context) {
|
|
context->left = dec->mb_info[-1];
|
|
context->info = dec->mb_info[dec->mb_x];
|
|
context->token_br = *token_br;
|
|
}
|
|
|
|
static void RestoreContext(const MBContext* context, VP8Decoder* const dec,
|
|
VP8BitReader* const token_br) {
|
|
dec->mb_info[-1] = context->left;
|
|
dec->mb_info[dec->mb_x] = context->info;
|
|
*token_br = context->token_br;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static VP8StatusCode IDecError(WebPIDecoder* const idec, VP8StatusCode error) {
|
|
if (idec->state == STATE_VP8_DATA) {
|
|
// Synchronize the thread, clean-up and check for errors.
|
|
(void)VP8ExitCritical((VP8Decoder*)idec->dec, &idec->io);
|
|
}
|
|
idec->state = STATE_ERROR;
|
|
return error;
|
|
}
|
|
|
|
static void ChangeState(WebPIDecoder* const idec, DecState new_state,
|
|
size_t consumed_bytes) {
|
|
MemBuffer* const mem = &idec->mem;
|
|
idec->state = new_state;
|
|
mem->start += consumed_bytes;
|
|
assert(mem->start <= mem->end);
|
|
idec->io.data = mem->buf + mem->start;
|
|
idec->io.data_size = MemDataSize(mem);
|
|
}
|
|
|
|
// Headers
|
|
static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) {
|
|
MemBuffer* const mem = &idec->mem;
|
|
const uint8_t* data = mem->buf + mem->start;
|
|
size_t curr_size = MemDataSize(mem);
|
|
VP8StatusCode status;
|
|
WebPHeaderStructure headers;
|
|
|
|
headers.data =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, data, curr_size);
|
|
headers.data_size = curr_size;
|
|
headers.have_all_data = 0;
|
|
status = WebPParseHeaders(&headers);
|
|
if (status == VP8_STATUS_NOT_ENOUGH_DATA) {
|
|
return VP8_STATUS_SUSPENDED; // We haven't found a VP8 chunk yet.
|
|
} else if (status != VP8_STATUS_OK) {
|
|
return IDecError(idec, status);
|
|
}
|
|
|
|
idec->chunk_size = headers.compressed_size;
|
|
idec->is_lossless = headers.is_lossless;
|
|
if (!idec->is_lossless) {
|
|
VP8Decoder* const dec = VP8New();
|
|
if (dec == NULL) {
|
|
return VP8_STATUS_OUT_OF_MEMORY;
|
|
}
|
|
dec->incremental = 1;
|
|
idec->dec = dec;
|
|
dec->alpha_data = headers.alpha_data;
|
|
dec->alpha_data_size = headers.alpha_data_size;
|
|
ChangeState(idec, STATE_VP8_HEADER, headers.offset);
|
|
} else {
|
|
VP8LDecoder* const dec = VP8LNew();
|
|
if (dec == NULL) {
|
|
return VP8_STATUS_OUT_OF_MEMORY;
|
|
}
|
|
idec->dec = dec;
|
|
ChangeState(idec, STATE_VP8L_HEADER, headers.offset);
|
|
}
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
static VP8StatusCode DecodeVP8FrameHeader(WebPIDecoder* const idec) {
|
|
const uint8_t* data = idec->mem.buf + idec->mem.start;
|
|
const size_t curr_size = MemDataSize(&idec->mem);
|
|
int width, height;
|
|
uint32_t bits;
|
|
|
|
if (curr_size < VP8_FRAME_HEADER_SIZE) {
|
|
// Not enough data bytes to extract VP8 Frame Header.
|
|
return VP8_STATUS_SUSPENDED;
|
|
}
|
|
{
|
|
const uint8_t* WEBP_BIDI_INDEXABLE const bounded_data =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, data, curr_size);
|
|
if (!VP8GetInfo(bounded_data, curr_size, idec->chunk_size, &width,
|
|
&height)) {
|
|
return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
}
|
|
|
|
bits = data[0] | (data[1] << 8) | (data[2] << 16);
|
|
idec->mem.part0_size = (bits >> 5) + VP8_FRAME_HEADER_SIZE;
|
|
|
|
idec->io.data = data;
|
|
idec->io.data_size = curr_size;
|
|
idec->state = STATE_VP8_PARTS0;
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
// Partition #0
|
|
static VP8StatusCode CopyParts0Data(WebPIDecoder* const idec) {
|
|
VP8Decoder* const dec = (VP8Decoder*)idec->dec;
|
|
VP8BitReader* const br = &dec->br;
|
|
const size_t part_size = br->buf_end - br->buf;
|
|
MemBuffer* const mem = &idec->mem;
|
|
assert(!idec->is_lossless);
|
|
assert(mem->part0_buf == NULL);
|
|
// the following is a format limitation, no need for runtime check:
|
|
assert(part_size <= mem->part0_size);
|
|
if (part_size == 0) { // can't have zero-size partition #0
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
if (mem->mode == MEM_MODE_APPEND) {
|
|
// We copy and grab ownership of the partition #0 data.
|
|
uint8_t* WEBP_BIDI_INDEXABLE const part0_buf =
|
|
(uint8_t*)WebPSafeMalloc(1ULL, part_size);
|
|
if (part0_buf == NULL) {
|
|
return VP8_STATUS_OUT_OF_MEMORY;
|
|
}
|
|
WEBP_UNSAFE_MEMCPY(part0_buf, br->buf, part_size);
|
|
mem->part0_buf = part0_buf;
|
|
VP8BitReaderSetBuffer(br, part0_buf, part_size);
|
|
} else {
|
|
// Else: just keep pointers to the partition #0's data in dec->br.
|
|
}
|
|
mem->start += part_size;
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) {
|
|
VP8Decoder* const dec = (VP8Decoder*)idec->dec;
|
|
VP8Io* const io = &idec->io;
|
|
const WebPDecParams* const params = &idec->params;
|
|
WebPDecBuffer* const output = params->output;
|
|
|
|
// Wait till we have enough data for the whole partition #0
|
|
if (MemDataSize(&idec->mem) < idec->mem.part0_size) {
|
|
return VP8_STATUS_SUSPENDED;
|
|
}
|
|
|
|
if (!VP8GetHeaders(dec, io)) {
|
|
const VP8StatusCode status = dec->status;
|
|
if (status == VP8_STATUS_SUSPENDED ||
|
|
status == VP8_STATUS_NOT_ENOUGH_DATA) {
|
|
// treating NOT_ENOUGH_DATA as SUSPENDED state
|
|
return VP8_STATUS_SUSPENDED;
|
|
}
|
|
return IDecError(idec, status);
|
|
}
|
|
|
|
// Allocate/Verify output buffer now
|
|
dec->status =
|
|
WebPAllocateDecBuffer(io->width, io->height, params->options, output);
|
|
if (dec->status != VP8_STATUS_OK) {
|
|
return IDecError(idec, dec->status);
|
|
}
|
|
// This change must be done before calling VP8InitFrame()
|
|
dec->mt_method =
|
|
VP8GetThreadMethod(params->options, NULL, io->width, io->height);
|
|
VP8InitDithering(params->options, dec);
|
|
|
|
dec->status = CopyParts0Data(idec);
|
|
if (dec->status != VP8_STATUS_OK) {
|
|
return IDecError(idec, dec->status);
|
|
}
|
|
|
|
// Finish setting up the decoding parameters. Will call io->setup().
|
|
if (VP8EnterCritical(dec, io) != VP8_STATUS_OK) {
|
|
return IDecError(idec, dec->status);
|
|
}
|
|
|
|
// Note: past this point, teardown() must always be called
|
|
// in case of error.
|
|
idec->state = STATE_VP8_DATA;
|
|
// Allocate memory and prepare everything.
|
|
if (!VP8InitFrame(dec, io)) {
|
|
return IDecError(idec, dec->status);
|
|
}
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
// Remaining partitions
|
|
static VP8StatusCode DecodeRemaining(WebPIDecoder* const idec) {
|
|
VP8Decoder* const dec = (VP8Decoder*)idec->dec;
|
|
VP8Io* const io = &idec->io;
|
|
|
|
// Make sure partition #0 has been read before, to set dec to ready.
|
|
if (!dec->ready) {
|
|
return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
for (; dec->mb_y < dec->mb_h; ++dec->mb_y) {
|
|
if (idec->last_mb_y != dec->mb_y) {
|
|
if (!VP8ParseIntraModeRow(&dec->br, dec)) {
|
|
// note: normally, error shouldn't occur since we already have the whole
|
|
// partition0 available here in DecodeRemaining(). Reaching EOF while
|
|
// reading intra modes really means a BITSTREAM_ERROR.
|
|
return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
idec->last_mb_y = dec->mb_y;
|
|
}
|
|
for (; dec->mb_x < dec->mb_w; ++dec->mb_x) {
|
|
VP8BitReader* const token_br =
|
|
&dec->parts[dec->mb_y & dec->num_parts_minus_one];
|
|
MBContext context;
|
|
SaveContext(dec, token_br, &context);
|
|
if (!VP8DecodeMB(dec, token_br)) {
|
|
// We shouldn't fail when MAX_MB data was available
|
|
if (dec->num_parts_minus_one == 0 &&
|
|
MemDataSize(&idec->mem) > MAX_MB_SIZE) {
|
|
return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
// Synchronize the threads.
|
|
if (dec->mt_method > 0) {
|
|
if (!WebPGetWorkerInterface()->Sync(&dec->worker)) {
|
|
return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
}
|
|
RestoreContext(&context, dec, token_br);
|
|
return VP8_STATUS_SUSPENDED;
|
|
}
|
|
// Release buffer only if there is only one partition
|
|
if (dec->num_parts_minus_one == 0) {
|
|
idec->mem.start = token_br->buf - idec->mem.buf;
|
|
assert(idec->mem.start <= idec->mem.end);
|
|
}
|
|
}
|
|
VP8InitScanline(dec); // Prepare for next scanline
|
|
|
|
// Reconstruct, filter and emit the row.
|
|
if (!VP8ProcessRow(dec, io)) {
|
|
return IDecError(idec, VP8_STATUS_USER_ABORT);
|
|
}
|
|
}
|
|
// Synchronize the thread and check for errors.
|
|
if (!VP8ExitCritical(dec, io)) {
|
|
idec->state = STATE_ERROR; // prevent re-entry in IDecError
|
|
return IDecError(idec, VP8_STATUS_USER_ABORT);
|
|
}
|
|
dec->ready = 0;
|
|
return FinishDecoding(idec);
|
|
}
|
|
|
|
static VP8StatusCode ErrorStatusLossless(WebPIDecoder* const idec,
|
|
VP8StatusCode status) {
|
|
if (status == VP8_STATUS_SUSPENDED || status == VP8_STATUS_NOT_ENOUGH_DATA) {
|
|
return VP8_STATUS_SUSPENDED;
|
|
}
|
|
return IDecError(idec, status);
|
|
}
|
|
|
|
static VP8StatusCode DecodeVP8LHeader(WebPIDecoder* const idec) {
|
|
VP8Io* const io = &idec->io;
|
|
VP8LDecoder* const dec = (VP8LDecoder*)idec->dec;
|
|
const WebPDecParams* const params = &idec->params;
|
|
WebPDecBuffer* const output = params->output;
|
|
size_t curr_size = MemDataSize(&idec->mem);
|
|
assert(idec->is_lossless);
|
|
|
|
// Wait until there's enough data for decoding header.
|
|
if (curr_size < (idec->chunk_size >> 3)) {
|
|
dec->status = VP8_STATUS_SUSPENDED;
|
|
return ErrorStatusLossless(idec, dec->status);
|
|
}
|
|
|
|
if (!VP8LDecodeHeader(dec, io)) {
|
|
if (dec->status == VP8_STATUS_BITSTREAM_ERROR &&
|
|
curr_size < idec->chunk_size) {
|
|
dec->status = VP8_STATUS_SUSPENDED;
|
|
}
|
|
return ErrorStatusLossless(idec, dec->status);
|
|
}
|
|
// Allocate/verify output buffer now.
|
|
dec->status =
|
|
WebPAllocateDecBuffer(io->width, io->height, params->options, output);
|
|
if (dec->status != VP8_STATUS_OK) {
|
|
return IDecError(idec, dec->status);
|
|
}
|
|
|
|
idec->state = STATE_VP8L_DATA;
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
static VP8StatusCode DecodeVP8LData(WebPIDecoder* const idec) {
|
|
VP8LDecoder* const dec = (VP8LDecoder*)idec->dec;
|
|
const size_t curr_size = MemDataSize(&idec->mem);
|
|
assert(idec->is_lossless);
|
|
|
|
// Switch to incremental decoding if we don't have all the bytes available.
|
|
dec->incremental = (curr_size < idec->chunk_size);
|
|
|
|
if (!VP8LDecodeImage(dec)) {
|
|
return ErrorStatusLossless(idec, dec->status);
|
|
}
|
|
assert(dec->status == VP8_STATUS_OK || dec->status == VP8_STATUS_SUSPENDED);
|
|
return (dec->status == VP8_STATUS_SUSPENDED) ? dec->status
|
|
: FinishDecoding(idec);
|
|
}
|
|
|
|
// Main decoding loop
|
|
static VP8StatusCode IDecode(WebPIDecoder* idec) {
|
|
VP8StatusCode status = VP8_STATUS_SUSPENDED;
|
|
|
|
if (idec->state == STATE_WEBP_HEADER) {
|
|
status = DecodeWebPHeaders(idec);
|
|
} else {
|
|
if (idec->dec == NULL) {
|
|
return VP8_STATUS_SUSPENDED; // can't continue if we have no decoder.
|
|
}
|
|
}
|
|
if (idec->state == STATE_VP8_HEADER) {
|
|
status = DecodeVP8FrameHeader(idec);
|
|
}
|
|
if (idec->state == STATE_VP8_PARTS0) {
|
|
status = DecodePartition0(idec);
|
|
}
|
|
if (idec->state == STATE_VP8_DATA) {
|
|
const VP8Decoder* const dec = (VP8Decoder*)idec->dec;
|
|
if (dec == NULL) {
|
|
return VP8_STATUS_SUSPENDED; // can't continue if we have no decoder.
|
|
}
|
|
status = DecodeRemaining(idec);
|
|
}
|
|
if (idec->state == STATE_VP8L_HEADER) {
|
|
status = DecodeVP8LHeader(idec);
|
|
}
|
|
if (idec->state == STATE_VP8L_DATA) {
|
|
status = DecodeVP8LData(idec);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Internal constructor
|
|
|
|
WEBP_NODISCARD static WebPIDecoder* NewDecoder(
|
|
WebPDecBuffer* const output_buffer,
|
|
const WebPBitstreamFeatures* const features) {
|
|
WebPIDecoder* idec = (WebPIDecoder*)WebPSafeCalloc(1ULL, sizeof(*idec));
|
|
if (idec == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
idec->state = STATE_WEBP_HEADER;
|
|
idec->chunk_size = 0;
|
|
|
|
idec->last_mb_y = -1;
|
|
|
|
InitMemBuffer(&idec->mem);
|
|
if (!WebPInitDecBuffer(&idec->output) || !VP8InitIo(&idec->io)) {
|
|
WebPSafeFree(idec);
|
|
return NULL;
|
|
}
|
|
|
|
WebPResetDecParams(&idec->params);
|
|
if (output_buffer == NULL || WebPAvoidSlowMemory(output_buffer, features)) {
|
|
idec->params.output = &idec->output;
|
|
idec->final_output = output_buffer;
|
|
if (output_buffer != NULL) {
|
|
idec->params.output->colorspace = output_buffer->colorspace;
|
|
}
|
|
} else {
|
|
idec->params.output = output_buffer;
|
|
idec->final_output = NULL;
|
|
}
|
|
WebPInitCustomIo(&idec->params, &idec->io); // Plug the I/O functions.
|
|
|
|
return idec;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Public functions
|
|
|
|
WebPIDecoder* WebPINewDecoder(WebPDecBuffer* output_buffer) {
|
|
return NewDecoder(output_buffer, NULL);
|
|
}
|
|
|
|
WebPIDecoder* WebPIDecode(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, WebPDecoderConfig* config) {
|
|
WebPIDecoder* idec;
|
|
WebPBitstreamFeatures tmp_features;
|
|
WebPBitstreamFeatures* const features =
|
|
(config == NULL) ? &tmp_features : &config->input;
|
|
memset(&tmp_features, 0, sizeof(tmp_features));
|
|
|
|
// Parse the bitstream's features, if requested:
|
|
if (data != NULL && data_size > 0) {
|
|
if (WebPGetFeatures(data, data_size, features) != VP8_STATUS_OK) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Create an instance of the incremental decoder
|
|
idec = (config != NULL) ? NewDecoder(&config->output, features)
|
|
: NewDecoder(NULL, features);
|
|
if (idec == NULL) {
|
|
return NULL;
|
|
}
|
|
// Finish initialization
|
|
if (config != NULL) {
|
|
idec->params.options = &config->options;
|
|
}
|
|
return idec;
|
|
}
|
|
|
|
void WebPIDelete(WebPIDecoder* idec) {
|
|
if (idec == NULL) return;
|
|
if (idec->dec != NULL) {
|
|
if (!idec->is_lossless) {
|
|
if (idec->state == STATE_VP8_DATA) {
|
|
// Synchronize the thread, clean-up and check for errors.
|
|
// TODO(vrabaud) do we care about the return result?
|
|
(void)VP8ExitCritical((VP8Decoder*)idec->dec, &idec->io);
|
|
}
|
|
VP8Delete((VP8Decoder*)idec->dec);
|
|
} else {
|
|
VP8LDelete((VP8LDecoder*)idec->dec);
|
|
}
|
|
}
|
|
ClearMemBuffer(&idec->mem);
|
|
WebPFreeDecBuffer(&idec->output);
|
|
WebPSafeFree(idec);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Wrapper toward WebPINewDecoder
|
|
|
|
WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE csp,
|
|
uint8_t* WEBP_COUNTED_BY(output_buffer_size)
|
|
output_buffer,
|
|
size_t output_buffer_size, int output_stride) {
|
|
const int is_external_memory = (output_buffer != NULL) ? 1 : 0;
|
|
WebPIDecoder* idec;
|
|
|
|
if (csp >= MODE_YUV) return NULL;
|
|
if (is_external_memory == 0) { // Overwrite parameters to sane values.
|
|
output_buffer = NULL;
|
|
output_buffer_size = 0;
|
|
output_stride = 0;
|
|
} else { // A buffer was passed. Validate the other params.
|
|
if (output_stride == 0 || output_buffer_size == 0) {
|
|
return NULL; // invalid parameter.
|
|
}
|
|
}
|
|
idec = WebPINewDecoder(NULL);
|
|
if (idec == NULL) return NULL;
|
|
idec->output.colorspace = csp;
|
|
idec->output.is_external_memory = is_external_memory;
|
|
idec->output.u.RGBA.rgba = output_buffer;
|
|
idec->output.u.RGBA.stride = output_stride;
|
|
idec->output.u.RGBA.size = output_buffer_size;
|
|
return idec;
|
|
}
|
|
|
|
WebPIDecoder* WebPINewYUVA(uint8_t* WEBP_COUNTED_BY(luma_size) luma,
|
|
size_t luma_size, int luma_stride,
|
|
uint8_t* WEBP_COUNTED_BY(u_size) u, size_t u_size,
|
|
int u_stride, uint8_t* WEBP_COUNTED_BY(v_size) v,
|
|
size_t v_size, int v_stride,
|
|
uint8_t* WEBP_COUNTED_BY(a_size) a, size_t a_size,
|
|
int a_stride) {
|
|
const int is_external_memory = (luma != NULL) ? 1 : 0;
|
|
WebPIDecoder* idec;
|
|
WEBP_CSP_MODE colorspace;
|
|
|
|
if (is_external_memory == 0) { // Overwrite parameters to sane values.
|
|
luma = NULL;
|
|
luma_size = 0;
|
|
u = NULL;
|
|
u_size = 0;
|
|
v = NULL;
|
|
v_size = 0;
|
|
a = NULL;
|
|
a_size = 0;
|
|
luma_stride = u_stride = v_stride = a_stride = 0;
|
|
colorspace = MODE_YUVA;
|
|
} else { // A luma buffer was passed. Validate the other parameters.
|
|
if (u == NULL || v == NULL) return NULL;
|
|
if (luma_size == 0 || u_size == 0 || v_size == 0) return NULL;
|
|
if (luma_stride == 0 || u_stride == 0 || v_stride == 0) return NULL;
|
|
if (a != NULL) {
|
|
if (a_size == 0 || a_stride == 0) return NULL;
|
|
}
|
|
colorspace = (a == NULL) ? MODE_YUV : MODE_YUVA;
|
|
}
|
|
|
|
idec = WebPINewDecoder(NULL);
|
|
if (idec == NULL) return NULL;
|
|
|
|
idec->output.colorspace = colorspace;
|
|
idec->output.is_external_memory = is_external_memory;
|
|
idec->output.u.YUVA.y = luma;
|
|
idec->output.u.YUVA.y_stride = luma_stride;
|
|
idec->output.u.YUVA.y_size = luma_size;
|
|
idec->output.u.YUVA.u = u;
|
|
idec->output.u.YUVA.u_stride = u_stride;
|
|
idec->output.u.YUVA.u_size = u_size;
|
|
idec->output.u.YUVA.v = v;
|
|
idec->output.u.YUVA.v_stride = v_stride;
|
|
idec->output.u.YUVA.v_size = v_size;
|
|
idec->output.u.YUVA.a = a;
|
|
idec->output.u.YUVA.a_stride = a_stride;
|
|
idec->output.u.YUVA.a_size = a_size;
|
|
return idec;
|
|
}
|
|
|
|
WebPIDecoder* WebPINewYUV(uint8_t* WEBP_COUNTED_BY(luma_size) luma,
|
|
size_t luma_size, int luma_stride,
|
|
uint8_t* WEBP_COUNTED_BY(u_size) u, size_t u_size,
|
|
int u_stride, uint8_t* WEBP_COUNTED_BY(v_size) v,
|
|
size_t v_size, int v_stride) {
|
|
return WebPINewYUVA(luma, luma_size, luma_stride, u, u_size, u_stride, v,
|
|
v_size, v_stride, NULL, 0, 0);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static VP8StatusCode IDecCheckStatus(const WebPIDecoder* const idec) {
|
|
assert(idec);
|
|
if (idec->state == STATE_ERROR) {
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
if (idec->state == STATE_DONE) {
|
|
return VP8_STATUS_OK;
|
|
}
|
|
return VP8_STATUS_SUSPENDED;
|
|
}
|
|
|
|
VP8StatusCode WebPIAppend(WebPIDecoder* idec,
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size) {
|
|
VP8StatusCode status;
|
|
if (idec == NULL || data == NULL) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
status = IDecCheckStatus(idec);
|
|
if (status != VP8_STATUS_SUSPENDED) {
|
|
return status;
|
|
}
|
|
// Check mixed calls between RemapMemBuffer and AppendToMemBuffer.
|
|
if (!CheckMemBufferMode(&idec->mem, MEM_MODE_APPEND)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
// Append data to memory buffer
|
|
if (!AppendToMemBuffer(idec, data, data_size)) {
|
|
return VP8_STATUS_OUT_OF_MEMORY;
|
|
}
|
|
return IDecode(idec);
|
|
}
|
|
|
|
VP8StatusCode WebPIUpdate(WebPIDecoder* idec,
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size) {
|
|
VP8StatusCode status;
|
|
if (idec == NULL || data == NULL) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
status = IDecCheckStatus(idec);
|
|
if (status != VP8_STATUS_SUSPENDED) {
|
|
return status;
|
|
}
|
|
// Check mixed calls between RemapMemBuffer and AppendToMemBuffer.
|
|
if (!CheckMemBufferMode(&idec->mem, MEM_MODE_MAP)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
// Make the memory buffer point to the new buffer
|
|
if (!RemapMemBuffer(idec, data, data_size)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
return IDecode(idec);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static const WebPDecBuffer* GetOutputBuffer(const WebPIDecoder* const idec) {
|
|
if (idec == NULL || idec->dec == NULL) {
|
|
return NULL;
|
|
}
|
|
if (idec->state <= STATE_VP8_PARTS0) {
|
|
return NULL;
|
|
}
|
|
if (idec->final_output != NULL) {
|
|
return NULL; // not yet slow-copied
|
|
}
|
|
return idec->params.output;
|
|
}
|
|
|
|
const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* idec, int* left,
|
|
int* top, int* width, int* height) {
|
|
const WebPDecBuffer* const src = GetOutputBuffer(idec);
|
|
if (left != NULL) *left = 0;
|
|
if (top != NULL) *top = 0;
|
|
if (src != NULL) {
|
|
if (width != NULL) *width = src->width;
|
|
if (height != NULL) *height = idec->params.last_y;
|
|
} else {
|
|
if (width != NULL) *width = 0;
|
|
if (height != NULL) *height = 0;
|
|
}
|
|
return src;
|
|
}
|
|
|
|
WEBP_NODISCARD uint8_t* WebPIDecGetRGB(const WebPIDecoder* idec, int* last_y,
|
|
int* width, int* height, int* stride) {
|
|
const WebPDecBuffer* const src = GetOutputBuffer(idec);
|
|
if (src == NULL) return NULL;
|
|
if (src->colorspace >= MODE_YUV) {
|
|
return NULL;
|
|
}
|
|
|
|
if (last_y != NULL) *last_y = idec->params.last_y;
|
|
if (width != NULL) *width = src->width;
|
|
if (height != NULL) *height = src->height;
|
|
if (stride != NULL) *stride = src->u.RGBA.stride;
|
|
|
|
return src->u.RGBA.rgba;
|
|
}
|
|
|
|
WEBP_NODISCARD uint8_t* WebPIDecGetYUVA(const WebPIDecoder* idec, int* last_y,
|
|
uint8_t** u, uint8_t** v, uint8_t** a,
|
|
int* width, int* height, int* stride,
|
|
int* uv_stride, int* a_stride) {
|
|
const WebPDecBuffer* const src = GetOutputBuffer(idec);
|
|
if (src == NULL) return NULL;
|
|
if (src->colorspace < MODE_YUV) {
|
|
return NULL;
|
|
}
|
|
|
|
if (last_y != NULL) *last_y = idec->params.last_y;
|
|
if (u != NULL) *u = src->u.YUVA.u;
|
|
if (v != NULL) *v = src->u.YUVA.v;
|
|
if (a != NULL) *a = src->u.YUVA.a;
|
|
if (width != NULL) *width = src->width;
|
|
if (height != NULL) *height = src->height;
|
|
if (stride != NULL) *stride = src->u.YUVA.y_stride;
|
|
if (uv_stride != NULL) *uv_stride = src->u.YUVA.u_stride;
|
|
if (a_stride != NULL) *a_stride = src->u.YUVA.a_stride;
|
|
|
|
return src->u.YUVA.y;
|
|
}
|
|
|
|
int WebPISetIOHooks(WebPIDecoder* const idec, VP8IoPutHook put,
|
|
VP8IoSetupHook setup, VP8IoTeardownHook teardown,
|
|
void* user_data) {
|
|
if (idec == NULL || idec->state > STATE_WEBP_HEADER) {
|
|
return 0;
|
|
}
|
|
|
|
idec->io.put = put;
|
|
idec->io.setup = setup;
|
|
idec->io.teardown = teardown;
|
|
idec->io.opaque = user_data;
|
|
|
|
return 1;
|
|
}
|
|
/* >>> src/dec/io_dec.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// functions for sample output.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* >>> src/dsp/yuv.h */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// inline YUV<->RGB conversion function
|
|
//
|
|
// The exact naming is Y'CbCr, following the ITU-R BT.601 standard.
|
|
// More information at: https://en.wikipedia.org/wiki/YCbCr
|
|
// Y = 0.2568 * R + 0.5041 * G + 0.0979 * B + 16
|
|
// U = -0.1482 * R - 0.2910 * G + 0.4392 * B + 128
|
|
// V = 0.4392 * R - 0.3678 * G - 0.0714 * B + 128
|
|
// We use 16bit fixed point operations for RGB->YUV conversion (YUV_FIX).
|
|
//
|
|
// For the Y'CbCr to RGB conversion, the BT.601 specification reads:
|
|
// R = 1.164 * (Y-16) + 1.596 * (V-128)
|
|
// G = 1.164 * (Y-16) - 0.813 * (V-128) - 0.392 * (U-128)
|
|
// B = 1.164 * (Y-16) + 2.017 * (U-128)
|
|
// where Y is in the [16,235] range, and U/V in the [16,240] range.
|
|
//
|
|
// The fixed-point implementation used here is:
|
|
// R = (19077 . y + 26149 . v - 14234) >> 6
|
|
// G = (19077 . y - 6419 . u - 13320 . v + 8708) >> 6
|
|
// B = (19077 . y + 33050 . u - 17685) >> 6
|
|
// where the '.' operator is the mulhi_epu16 variant:
|
|
// a . b = ((a << 8) * b) >> 16
|
|
// that preserves 8 bits of fractional precision before final descaling.
|
|
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_DSP_YUV_H_
|
|
#define WEBP_DSP_YUV_H_
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// Macros to give the offset of each channel in a uint32_t containing ARGB.
|
|
#ifdef WORDS_BIGENDIAN
|
|
// uint32_t 0xff000000 is 0xff,00,00,00 in memory
|
|
#define CHANNEL_OFFSET(i) (i)
|
|
#else
|
|
// uint32_t 0xff000000 is 0x00,00,00,ff in memory
|
|
#define CHANNEL_OFFSET(i) (3 - (i))
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// YUV -> RGB conversion
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
enum {
|
|
YUV_FIX = 16, // fixed-point precision for RGB->YUV
|
|
YUV_HALF = 1 << (YUV_FIX - 1),
|
|
|
|
YUV_FIX2 = 6, // fixed-point precision for YUV->RGB
|
|
YUV_MASK2 = (256 << YUV_FIX2) - 1
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// slower on x86 by ~7-8%, but bit-exact with the SSE2/NEON version
|
|
|
|
static WEBP_INLINE int MultHi(int v, int coeff) { // _mm_mulhi_epu16 emulation
|
|
return (v * coeff) >> 8;
|
|
}
|
|
|
|
static WEBP_INLINE int VP8Clip8(int v) {
|
|
return ((v & ~YUV_MASK2) == 0) ? (v >> YUV_FIX2) : (v < 0) ? 0 : 255;
|
|
}
|
|
|
|
static WEBP_INLINE int VP8YUVToR(int y, int v) {
|
|
return VP8Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234);
|
|
}
|
|
|
|
static WEBP_INLINE int VP8YUVToG(int y, int u, int v) {
|
|
return VP8Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708);
|
|
}
|
|
|
|
static WEBP_INLINE int VP8YUVToB(int y, int u) {
|
|
return VP8Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685);
|
|
}
|
|
|
|
static WEBP_INLINE void VP8YuvToRgb(int y, int u, int v, uint8_t* const rgb) {
|
|
rgb[0] = VP8YUVToR(y, v);
|
|
rgb[1] = VP8YUVToG(y, u, v);
|
|
rgb[2] = VP8YUVToB(y, u);
|
|
}
|
|
|
|
static WEBP_INLINE void VP8YuvToBgr(int y, int u, int v, uint8_t* const bgr) {
|
|
bgr[0] = VP8YUVToB(y, u);
|
|
bgr[1] = VP8YUVToG(y, u, v);
|
|
bgr[2] = VP8YUVToR(y, v);
|
|
}
|
|
|
|
static WEBP_INLINE void VP8YuvToRgb565(int y, int u, int v,
|
|
uint8_t* const rgb) {
|
|
const int r = VP8YUVToR(y, v); // 5 usable bits
|
|
const int g = VP8YUVToG(y, u, v); // 6 usable bits
|
|
const int b = VP8YUVToB(y, u); // 5 usable bits
|
|
const int rg = (r & 0xf8) | (g >> 5);
|
|
const int gb = ((g << 3) & 0xe0) | (b >> 3);
|
|
#if (WEBP_SWAP_16BIT_CSP == 1)
|
|
rgb[0] = gb;
|
|
rgb[1] = rg;
|
|
#else
|
|
rgb[0] = rg;
|
|
rgb[1] = gb;
|
|
#endif
|
|
}
|
|
|
|
static WEBP_INLINE void VP8YuvToRgba4444(int y, int u, int v,
|
|
uint8_t* const argb) {
|
|
const int r = VP8YUVToR(y, v); // 4 usable bits
|
|
const int g = VP8YUVToG(y, u, v); // 4 usable bits
|
|
const int b = VP8YUVToB(y, u); // 4 usable bits
|
|
const int rg = (r & 0xf0) | (g >> 4);
|
|
const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits
|
|
#if (WEBP_SWAP_16BIT_CSP == 1)
|
|
argb[0] = ba;
|
|
argb[1] = rg;
|
|
#else
|
|
argb[0] = rg;
|
|
argb[1] = ba;
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Alpha handling variants
|
|
|
|
static WEBP_INLINE void VP8YuvToArgb(uint8_t y, uint8_t u, uint8_t v,
|
|
uint8_t* const argb) {
|
|
argb[0] = 0xff;
|
|
VP8YuvToRgb(y, u, v, argb + 1);
|
|
}
|
|
|
|
static WEBP_INLINE void VP8YuvToBgra(uint8_t y, uint8_t u, uint8_t v,
|
|
uint8_t* const bgra) {
|
|
VP8YuvToBgr(y, u, v, bgra);
|
|
bgra[3] = 0xff;
|
|
}
|
|
|
|
static WEBP_INLINE void VP8YuvToRgba(uint8_t y, uint8_t u, uint8_t v,
|
|
uint8_t* const rgba) {
|
|
VP8YuvToRgb(y, u, v, rgba);
|
|
rgba[3] = 0xff;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// SSE2 extra functions (mostly for upsampling_sse2.c)
|
|
|
|
#if defined(WEBP_USE_SSE2)
|
|
|
|
// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst.
|
|
void VP8YuvToRgba32_SSE2(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8YuvToRgb32_SSE2(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8YuvToBgra32_SSE2(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8YuvToBgr32_SSE2(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8YuvToArgb32_SSE2(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8YuvToRgba444432_SSE2(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8YuvToRgb56532_SSE2(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
|
|
#endif // WEBP_USE_SSE2
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// SSE41 extra functions (mostly for upsampling_sse41.c)
|
|
|
|
#if defined(WEBP_USE_SSE41)
|
|
|
|
// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst.
|
|
void VP8YuvToRgb32_SSE41(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8YuvToBgr32_SSE41(const uint8_t* WEBP_RESTRICT y,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
|
|
#endif // WEBP_USE_SSE41
|
|
|
|
//------------------------------------------------------------------------------
|
|
// RGB -> YUV conversion
|
|
|
|
// Stub functions that can be called with various rounding values:
|
|
static WEBP_INLINE int VP8ClipUV(int uv, int rounding) {
|
|
uv = (uv + rounding + (128 << (YUV_FIX + 2))) >> (YUV_FIX + 2);
|
|
return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255;
|
|
}
|
|
|
|
static WEBP_INLINE int VP8RGBToY(int r, int g, int b, int rounding) {
|
|
const int luma = 16839 * r + 33059 * g + 6420 * b;
|
|
return (luma + rounding + (16 << YUV_FIX)) >> YUV_FIX; // no need to clip
|
|
}
|
|
|
|
static WEBP_INLINE int VP8RGBToU(int r, int g, int b, int rounding) {
|
|
const int u = -9719 * r - 19081 * g + 28800 * b;
|
|
return VP8ClipUV(u, rounding);
|
|
}
|
|
|
|
static WEBP_INLINE int VP8RGBToV(int r, int g, int b, int rounding) {
|
|
const int v = +28800 * r - 24116 * g - 4684 * b;
|
|
return VP8ClipUV(v, rounding);
|
|
}
|
|
|
|
// has_alpha is true if there is an alpha value that is not 0xff.
|
|
extern void (*WebPImportYUVAFromRGBA)(
|
|
const uint8_t* r_ptr, const uint8_t* g_ptr, const uint8_t* b_ptr,
|
|
const uint8_t* a_ptr,
|
|
int step, // bytes per pixel
|
|
int rgb_stride, // bytes per scanline
|
|
int has_alpha, int width, int height, uint16_t* tmp_rgb, int y_stride,
|
|
int uv_stride, int a_stride, uint8_t* dst_y, uint8_t* dst_u, uint8_t* dst_v,
|
|
uint8_t* dst_a);
|
|
extern void (*WebPImportYUVAFromRGBALastLine)(
|
|
const uint8_t* r_ptr, const uint8_t* g_ptr, const uint8_t* b_ptr,
|
|
const uint8_t* a_ptr,
|
|
int step, // bytes per pixel
|
|
int has_alpha, int width, uint16_t* tmp_rgb, uint8_t* dst_y, uint8_t* dst_u,
|
|
uint8_t* dst_v, uint8_t* dst_a);
|
|
|
|
// Internal function to WebPImportYUVAFromRGBA* that can be reused.
|
|
void WebPAccumulateRGBA(const uint8_t* const r_ptr, const uint8_t* const g_ptr,
|
|
const uint8_t* const b_ptr, const uint8_t* const a_ptr,
|
|
int rgb_stride, uint16_t* dst, int width);
|
|
void WebPAccumulateRGB(const uint8_t* const r_ptr, const uint8_t* const g_ptr,
|
|
const uint8_t* const b_ptr, int step, int rgb_stride,
|
|
uint16_t* dst, int width);
|
|
// Must be called before calling WebPAccumulateRGB*.
|
|
void WebPInitGammaTables(void);
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DSP_YUV_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main YUV<->RGB conversion functions
|
|
|
|
static int EmitYUV(const VP8Io* const io, WebPDecParams* const p) {
|
|
WebPDecBuffer* output = p->output;
|
|
const WebPYUVABuffer* const buf = &output->u.YUVA;
|
|
uint8_t* const y_dst = buf->y + (ptrdiff_t)io->mb_y * buf->y_stride;
|
|
uint8_t* const u_dst = buf->u + (ptrdiff_t)(io->mb_y >> 1) * buf->u_stride;
|
|
uint8_t* const v_dst = buf->v + (ptrdiff_t)(io->mb_y >> 1) * buf->v_stride;
|
|
const int mb_w = io->mb_w;
|
|
const int mb_h = io->mb_h;
|
|
const int uv_w = (mb_w + 1) / 2;
|
|
const int uv_h = (mb_h + 1) / 2;
|
|
WebPCopyPlane(io->y, io->y_stride, y_dst, buf->y_stride, mb_w, mb_h);
|
|
WebPCopyPlane(io->u, io->uv_stride, u_dst, buf->u_stride, uv_w, uv_h);
|
|
WebPCopyPlane(io->v, io->uv_stride, v_dst, buf->v_stride, uv_w, uv_h);
|
|
return io->mb_h;
|
|
}
|
|
|
|
// Point-sampling U/V sampler.
|
|
static int EmitSampledRGB(const VP8Io* const io, WebPDecParams* const p) {
|
|
WebPDecBuffer* const output = p->output;
|
|
WebPRGBABuffer* const buf = &output->u.RGBA;
|
|
uint8_t* const dst = buf->rgba + (ptrdiff_t)io->mb_y * buf->stride;
|
|
WebPSamplerProcessPlane(io->y, io->y_stride, io->u, io->v, io->uv_stride, dst,
|
|
buf->stride, io->mb_w, io->mb_h,
|
|
WebPSamplers[output->colorspace]);
|
|
return io->mb_h;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Fancy upsampling
|
|
|
|
#ifdef FANCY_UPSAMPLING
|
|
static int EmitFancyRGB(const VP8Io* const io, WebPDecParams* const p) {
|
|
int num_lines_out = io->mb_h; // a priori guess
|
|
const WebPRGBABuffer* const buf = &p->output->u.RGBA;
|
|
uint8_t* dst = buf->rgba + (ptrdiff_t)io->mb_y * buf->stride;
|
|
WebPUpsampleLinePairFunc upsample = WebPUpsamplers[p->output->colorspace];
|
|
const uint8_t* cur_y = io->y;
|
|
const uint8_t* cur_u = io->u;
|
|
const uint8_t* cur_v = io->v;
|
|
const uint8_t* top_u = p->tmp_u;
|
|
const uint8_t* top_v = p->tmp_v;
|
|
int y = io->mb_y;
|
|
const int y_end = io->mb_y + io->mb_h;
|
|
const int mb_w = io->mb_w;
|
|
const int uv_w = (mb_w + 1) / 2;
|
|
|
|
if (y == 0) {
|
|
// First line is special cased. We mirror the u/v samples at boundary.
|
|
upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, mb_w);
|
|
} else {
|
|
// We can finish the left-over line from previous call.
|
|
upsample(p->tmp_y, cur_y, top_u, top_v, cur_u, cur_v, dst - buf->stride,
|
|
dst, mb_w);
|
|
++num_lines_out;
|
|
}
|
|
// Loop over each output pairs of row.
|
|
for (; y + 2 < y_end; y += 2) {
|
|
top_u = cur_u;
|
|
top_v = cur_v;
|
|
cur_u += io->uv_stride;
|
|
cur_v += io->uv_stride;
|
|
dst += 2 * buf->stride;
|
|
cur_y += 2 * io->y_stride;
|
|
upsample(cur_y - io->y_stride, cur_y, top_u, top_v, cur_u, cur_v,
|
|
dst - buf->stride, dst, mb_w);
|
|
}
|
|
// move to last row
|
|
cur_y += io->y_stride;
|
|
if (io->crop_top + y_end < io->crop_bottom) {
|
|
// Save the unfinished samples for next call (as we're not done yet).
|
|
WEBP_UNSAFE_MEMCPY(p->tmp_y, cur_y, mb_w * sizeof(*p->tmp_y));
|
|
WEBP_UNSAFE_MEMCPY(p->tmp_u, cur_u, uv_w * sizeof(*p->tmp_u));
|
|
WEBP_UNSAFE_MEMCPY(p->tmp_v, cur_v, uv_w * sizeof(*p->tmp_v));
|
|
// The fancy upsampler leaves a row unfinished behind
|
|
// (except for the very last row)
|
|
num_lines_out--;
|
|
} else {
|
|
// Process the very last row of even-sized picture
|
|
if (!(y_end & 1)) {
|
|
upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst + buf->stride, NULL,
|
|
mb_w);
|
|
}
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
#endif /* FANCY_UPSAMPLING */
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void FillAlphaPlane(uint8_t* dst, int w, int h, int stride) {
|
|
int j;
|
|
for (j = 0; j < h; ++j) {
|
|
WEBP_UNSAFE_MEMSET(dst, 0xff, w * sizeof(*dst));
|
|
dst += stride;
|
|
}
|
|
}
|
|
|
|
static int EmitAlphaYUV(const VP8Io* const io, WebPDecParams* const p,
|
|
int expected_num_lines_out) {
|
|
const uint8_t* alpha = io->a;
|
|
const WebPYUVABuffer* const buf = &p->output->u.YUVA;
|
|
const int mb_w = io->mb_w;
|
|
const int mb_h = io->mb_h;
|
|
uint8_t* dst = buf->a + (ptrdiff_t)io->mb_y * buf->a_stride;
|
|
int j;
|
|
(void)expected_num_lines_out;
|
|
assert(expected_num_lines_out == mb_h);
|
|
if (alpha != NULL) {
|
|
for (j = 0; j < mb_h; ++j) {
|
|
WEBP_UNSAFE_MEMCPY(dst, alpha, mb_w * sizeof(*dst));
|
|
alpha += io->width;
|
|
dst += buf->a_stride;
|
|
}
|
|
} else if (buf->a != NULL) {
|
|
// the user requested alpha, but there is none, set it to opaque.
|
|
FillAlphaPlane(dst, mb_w, mb_h, buf->a_stride);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int GetAlphaSourceRow(const VP8Io* const io, const uint8_t** alpha,
|
|
int* const num_rows) {
|
|
int start_y = io->mb_y;
|
|
*num_rows = io->mb_h;
|
|
|
|
// Compensate for the 1-line delay of the fancy upscaler.
|
|
// This is similar to EmitFancyRGB().
|
|
if (io->fancy_upsampling) {
|
|
if (start_y == 0) {
|
|
// We don't process the last row yet. It'll be done during the next call.
|
|
--*num_rows;
|
|
} else {
|
|
--start_y;
|
|
// Fortunately, *alpha data is persistent, so we can go back
|
|
// one row and finish alpha blending, now that the fancy upscaler
|
|
// completed the YUV->RGB interpolation.
|
|
*alpha -= io->width;
|
|
}
|
|
if (io->crop_top + io->mb_y + io->mb_h == io->crop_bottom) {
|
|
// If it's the very last call, we process all the remaining rows!
|
|
*num_rows = io->crop_bottom - io->crop_top - start_y;
|
|
}
|
|
}
|
|
return start_y;
|
|
}
|
|
|
|
static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p,
|
|
int expected_num_lines_out) {
|
|
const uint8_t* alpha = io->a;
|
|
if (alpha != NULL) {
|
|
const int mb_w = io->mb_w;
|
|
const WEBP_CSP_MODE colorspace = p->output->colorspace;
|
|
const int alpha_first =
|
|
(colorspace == MODE_ARGB || colorspace == MODE_Argb);
|
|
const WebPRGBABuffer* const buf = &p->output->u.RGBA;
|
|
int num_rows;
|
|
const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows);
|
|
uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)start_y * buf->stride;
|
|
uint8_t* const dst = base_rgba + (alpha_first ? 0 : 3);
|
|
const int has_alpha =
|
|
WebPDispatchAlpha(alpha, io->width, mb_w, num_rows, dst, buf->stride);
|
|
(void)expected_num_lines_out;
|
|
assert(expected_num_lines_out == num_rows);
|
|
// has_alpha is true if there's non-trivial alpha to premultiply with.
|
|
if (has_alpha && WebPIsPremultipliedMode(colorspace)) {
|
|
WebPApplyAlphaMultiply(base_rgba, alpha_first, mb_w, num_rows,
|
|
buf->stride);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p,
|
|
int expected_num_lines_out) {
|
|
const uint8_t* alpha = io->a;
|
|
if (alpha != NULL) {
|
|
const int mb_w = io->mb_w;
|
|
const WEBP_CSP_MODE colorspace = p->output->colorspace;
|
|
const WebPRGBABuffer* const buf = &p->output->u.RGBA;
|
|
int num_rows;
|
|
const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows);
|
|
uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)start_y * buf->stride;
|
|
#if (WEBP_SWAP_16BIT_CSP == 1)
|
|
uint8_t* alpha_dst = base_rgba;
|
|
#else
|
|
uint8_t* alpha_dst = base_rgba + 1;
|
|
#endif
|
|
uint32_t alpha_mask = 0x0f;
|
|
int i, j;
|
|
for (j = 0; j < num_rows; ++j) {
|
|
for (i = 0; i < mb_w; ++i) {
|
|
// Fill in the alpha value (converted to 4 bits).
|
|
const uint32_t alpha_value = alpha[i] >> 4;
|
|
alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value;
|
|
alpha_mask &= alpha_value;
|
|
}
|
|
alpha += io->width;
|
|
alpha_dst += buf->stride;
|
|
}
|
|
(void)expected_num_lines_out;
|
|
assert(expected_num_lines_out == num_rows);
|
|
if (alpha_mask != 0x0f && WebPIsPremultipliedMode(colorspace)) {
|
|
WebPApplyAlphaMultiply4444(base_rgba, mb_w, num_rows, buf->stride);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// YUV rescaling (no final RGB conversion needed)
|
|
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
static int Rescale(const uint8_t* src, int src_stride, int new_lines,
|
|
WebPRescaler* const wrk) {
|
|
int num_lines_out = 0;
|
|
while (new_lines > 0) { // import new contributions of source rows.
|
|
const int lines_in = WebPRescalerImport(wrk, new_lines, src, src_stride);
|
|
src += lines_in * src_stride;
|
|
new_lines -= lines_in;
|
|
num_lines_out += WebPRescalerExport(wrk); // emit output row(s)
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
static int EmitRescaledYUV(const VP8Io* const io, WebPDecParams* const p) {
|
|
const int mb_h = io->mb_h;
|
|
const int uv_mb_h = (mb_h + 1) >> 1;
|
|
WebPRescaler* const scaler = p->scaler_y;
|
|
int num_lines_out = 0;
|
|
if (WebPIsAlphaMode(p->output->colorspace) && io->a != NULL) {
|
|
// Before rescaling, we premultiply the luma directly into the io->y
|
|
// internal buffer. This is OK since these samples are not used for
|
|
// intra-prediction (the top samples are saved in cache_y/u/v).
|
|
// But we need to cast the const away, though.
|
|
WebPMultRows((uint8_t*)io->y, io->y_stride, io->a, io->width, io->mb_w,
|
|
mb_h, 0);
|
|
}
|
|
num_lines_out = Rescale(io->y, io->y_stride, mb_h, scaler);
|
|
Rescale(io->u, io->uv_stride, uv_mb_h, p->scaler_u);
|
|
Rescale(io->v, io->uv_stride, uv_mb_h, p->scaler_v);
|
|
return num_lines_out;
|
|
}
|
|
|
|
static int EmitRescaledAlphaYUV(const VP8Io* const io, WebPDecParams* const p,
|
|
int expected_num_lines_out) {
|
|
const WebPYUVABuffer* const buf = &p->output->u.YUVA;
|
|
uint8_t* const dst_a = buf->a + (ptrdiff_t)p->last_y * buf->a_stride;
|
|
if (io->a != NULL) {
|
|
uint8_t* const dst_y = buf->y + (ptrdiff_t)p->last_y * buf->y_stride;
|
|
const int num_lines_out = Rescale(io->a, io->width, io->mb_h, p->scaler_a);
|
|
assert(expected_num_lines_out == num_lines_out);
|
|
if (num_lines_out > 0) { // unmultiply the Y
|
|
WebPMultRows(dst_y, buf->y_stride, dst_a, buf->a_stride,
|
|
p->scaler_a->dst_width, num_lines_out, 1);
|
|
}
|
|
} else if (buf->a != NULL) {
|
|
// the user requested alpha, but there is none, set it to opaque.
|
|
assert(p->last_y + expected_num_lines_out <= io->scaled_height);
|
|
FillAlphaPlane(dst_a, io->scaled_width, expected_num_lines_out,
|
|
buf->a_stride);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int InitYUVRescaler(const VP8Io* const io, WebPDecParams* const p) {
|
|
const int has_alpha = WebPIsAlphaMode(p->output->colorspace);
|
|
const WebPYUVABuffer* const buf = &p->output->u.YUVA;
|
|
const int out_width = io->scaled_width;
|
|
const int out_height = io->scaled_height;
|
|
const int uv_out_width = (out_width + 1) >> 1;
|
|
const int uv_out_height = (out_height + 1) >> 1;
|
|
const int uv_in_width = (io->mb_w + 1) >> 1;
|
|
const int uv_in_height = (io->mb_h + 1) >> 1;
|
|
// scratch memory for luma rescaler
|
|
const size_t work_size = 2 * (size_t)out_width;
|
|
const size_t uv_work_size = 2 * uv_out_width; // and for each u/v ones
|
|
uint64_t total_size;
|
|
size_t rescaler_size;
|
|
rescaler_t* WEBP_BIDI_INDEXABLE work;
|
|
WebPRescaler* scalers;
|
|
const int num_rescalers = has_alpha ? 4 : 3;
|
|
|
|
total_size = ((uint64_t)work_size + 2 * uv_work_size) * sizeof(*work);
|
|
if (has_alpha) {
|
|
total_size += (uint64_t)work_size * sizeof(*work);
|
|
}
|
|
rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST;
|
|
total_size += rescaler_size;
|
|
if (!CheckSizeOverflow(total_size)) {
|
|
return 0;
|
|
}
|
|
|
|
work = (rescaler_t*)WebPSafeMalloc(1ULL, (size_t)total_size);
|
|
if (work == NULL) {
|
|
return 0; // memory error
|
|
}
|
|
p->memory = work;
|
|
|
|
scalers = (WebPRescaler*)WEBP_ALIGN((const uint8_t*)work + total_size -
|
|
rescaler_size);
|
|
p->scaler_y = &scalers[0];
|
|
p->scaler_u = &scalers[1];
|
|
p->scaler_v = &scalers[2];
|
|
p->scaler_a = has_alpha ? &scalers[3] : NULL;
|
|
|
|
if (!WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, buf->y, out_width,
|
|
out_height, buf->y_stride, 1, work) ||
|
|
!WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height, buf->u,
|
|
uv_out_width, uv_out_height, buf->u_stride, 1,
|
|
work + work_size) ||
|
|
!WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height, buf->v,
|
|
uv_out_width, uv_out_height, buf->v_stride, 1,
|
|
work + work_size + uv_work_size)) {
|
|
return 0;
|
|
}
|
|
p->emit = EmitRescaledYUV;
|
|
|
|
if (has_alpha) {
|
|
if (!WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, buf->a, out_width,
|
|
out_height, buf->a_stride, 1,
|
|
work + work_size + 2 * uv_work_size)) {
|
|
return 0;
|
|
}
|
|
p->emit_alpha = EmitRescaledAlphaYUV;
|
|
WebPInitAlphaProcessing();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// RGBA rescaling
|
|
|
|
static int ExportRGB(WebPDecParams* const p, int y_pos) {
|
|
const WebPYUV444Converter convert =
|
|
WebPYUV444Converters[p->output->colorspace];
|
|
const WebPRGBABuffer* const buf = &p->output->u.RGBA;
|
|
uint8_t* dst = buf->rgba + (ptrdiff_t)y_pos * buf->stride;
|
|
int num_lines_out = 0;
|
|
// For RGB rescaling, because of the YUV420, current scan position
|
|
// U/V can be +1/-1 line from the Y one. Hence the double test.
|
|
while (WebPRescalerHasPendingOutput(p->scaler_y) &&
|
|
WebPRescalerHasPendingOutput(p->scaler_u)) {
|
|
assert(y_pos + num_lines_out < p->output->height);
|
|
assert(p->scaler_u->y_accum == p->scaler_v->y_accum);
|
|
WebPRescalerExportRow(p->scaler_y);
|
|
WebPRescalerExportRow(p->scaler_u);
|
|
WebPRescalerExportRow(p->scaler_v);
|
|
convert(p->scaler_y->dst, p->scaler_u->dst, p->scaler_v->dst, dst,
|
|
p->scaler_y->dst_width);
|
|
dst += buf->stride;
|
|
++num_lines_out;
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
static int EmitRescaledRGB(const VP8Io* const io, WebPDecParams* const p) {
|
|
const int mb_h = io->mb_h;
|
|
const int uv_mb_h = (mb_h + 1) >> 1;
|
|
int j = 0, uv_j = 0;
|
|
int num_lines_out = 0;
|
|
while (j < mb_h) {
|
|
const int y_lines_in =
|
|
WebPRescalerImport(p->scaler_y, mb_h - j,
|
|
io->y + (ptrdiff_t)j * io->y_stride, io->y_stride);
|
|
j += y_lines_in;
|
|
if (WebPRescaleNeededLines(p->scaler_u, uv_mb_h - uv_j)) {
|
|
const int u_lines_in = WebPRescalerImport(
|
|
p->scaler_u, uv_mb_h - uv_j, io->u + (ptrdiff_t)uv_j * io->uv_stride,
|
|
io->uv_stride);
|
|
const int v_lines_in = WebPRescalerImport(
|
|
p->scaler_v, uv_mb_h - uv_j, io->v + (ptrdiff_t)uv_j * io->uv_stride,
|
|
io->uv_stride);
|
|
(void)v_lines_in; // remove a gcc warning
|
|
assert(u_lines_in == v_lines_in);
|
|
uv_j += u_lines_in;
|
|
}
|
|
num_lines_out += ExportRGB(p, p->last_y + num_lines_out);
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
static int ExportAlpha(WebPDecParams* const p, int y_pos, int max_lines_out) {
|
|
const WebPRGBABuffer* const buf = &p->output->u.RGBA;
|
|
uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)y_pos * buf->stride;
|
|
const WEBP_CSP_MODE colorspace = p->output->colorspace;
|
|
const int alpha_first = (colorspace == MODE_ARGB || colorspace == MODE_Argb);
|
|
uint8_t* dst = base_rgba + (alpha_first ? 0 : 3);
|
|
int num_lines_out = 0;
|
|
const int is_premult_alpha = WebPIsPremultipliedMode(colorspace);
|
|
uint32_t non_opaque = 0;
|
|
const int width = p->scaler_a->dst_width;
|
|
|
|
while (WebPRescalerHasPendingOutput(p->scaler_a) &&
|
|
num_lines_out < max_lines_out) {
|
|
assert(y_pos + num_lines_out < p->output->height);
|
|
WebPRescalerExportRow(p->scaler_a);
|
|
non_opaque |= WebPDispatchAlpha(p->scaler_a->dst, 0, width, 1, dst, 0);
|
|
dst += buf->stride;
|
|
++num_lines_out;
|
|
}
|
|
if (is_premult_alpha && non_opaque) {
|
|
WebPApplyAlphaMultiply(base_rgba, alpha_first, width, num_lines_out,
|
|
buf->stride);
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
static int ExportAlphaRGBA4444(WebPDecParams* const p, int y_pos,
|
|
int max_lines_out) {
|
|
const WebPRGBABuffer* const buf = &p->output->u.RGBA;
|
|
uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)y_pos * buf->stride;
|
|
#if (WEBP_SWAP_16BIT_CSP == 1)
|
|
uint8_t* alpha_dst = base_rgba;
|
|
#else
|
|
uint8_t* alpha_dst = base_rgba + 1;
|
|
#endif
|
|
int num_lines_out = 0;
|
|
const WEBP_CSP_MODE colorspace = p->output->colorspace;
|
|
const int width = p->scaler_a->dst_width;
|
|
const int is_premult_alpha = WebPIsPremultipliedMode(colorspace);
|
|
uint32_t alpha_mask = 0x0f;
|
|
|
|
while (WebPRescalerHasPendingOutput(p->scaler_a) &&
|
|
num_lines_out < max_lines_out) {
|
|
int i;
|
|
assert(y_pos + num_lines_out < p->output->height);
|
|
WebPRescalerExportRow(p->scaler_a);
|
|
for (i = 0; i < width; ++i) {
|
|
// Fill in the alpha value (converted to 4 bits).
|
|
const uint32_t alpha_value = p->scaler_a->dst[i] >> 4;
|
|
alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value;
|
|
alpha_mask &= alpha_value;
|
|
}
|
|
alpha_dst += buf->stride;
|
|
++num_lines_out;
|
|
}
|
|
if (is_premult_alpha && alpha_mask != 0x0f) {
|
|
WebPApplyAlphaMultiply4444(base_rgba, width, num_lines_out, buf->stride);
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
static int EmitRescaledAlphaRGB(const VP8Io* const io, WebPDecParams* const p,
|
|
int expected_num_out_lines) {
|
|
if (io->a != NULL) {
|
|
WebPRescaler* const scaler = p->scaler_a;
|
|
int lines_left = expected_num_out_lines;
|
|
const int y_end = p->last_y + lines_left;
|
|
while (lines_left > 0) {
|
|
const int64_t row_offset = (ptrdiff_t)scaler->src_y - io->mb_y;
|
|
WebPRescalerImport(scaler, io->mb_h + io->mb_y - scaler->src_y,
|
|
io->a + row_offset * io->width, io->width);
|
|
lines_left -= p->emit_alpha_row(p, y_end - lines_left, lines_left);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int InitRGBRescaler(const VP8Io* const io, WebPDecParams* const p) {
|
|
const int has_alpha = WebPIsAlphaMode(p->output->colorspace);
|
|
const int out_width = io->scaled_width;
|
|
const int out_height = io->scaled_height;
|
|
const int uv_in_width = (io->mb_w + 1) >> 1;
|
|
const int uv_in_height = (io->mb_h + 1) >> 1;
|
|
// scratch memory for one rescaler
|
|
const size_t work_size = 2 * (size_t)out_width;
|
|
rescaler_t* WEBP_BIDI_INDEXABLE work; // rescalers work area
|
|
uint8_t* WEBP_BIDI_INDEXABLE
|
|
tmp; // tmp storage for scaled YUV444 samples before RGB conversion
|
|
uint64_t tmp_size1, tmp_size2, total_size;
|
|
size_t rescaler_size;
|
|
WebPRescaler* scalers;
|
|
const int num_rescalers = has_alpha ? 4 : 3;
|
|
|
|
tmp_size1 = (uint64_t)num_rescalers * work_size;
|
|
tmp_size2 = (uint64_t)num_rescalers * out_width;
|
|
total_size = tmp_size1 * sizeof(*work) + tmp_size2 * sizeof(*tmp);
|
|
rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST;
|
|
total_size += rescaler_size;
|
|
if (!CheckSizeOverflow(total_size)) {
|
|
return 0;
|
|
}
|
|
|
|
work = (rescaler_t*)WebPSafeMalloc(1ULL, (size_t)total_size);
|
|
if (work == NULL) {
|
|
return 0; // memory error
|
|
}
|
|
p->memory = work;
|
|
tmp = (uint8_t*)(work + tmp_size1);
|
|
|
|
scalers = (WebPRescaler*)WEBP_ALIGN((const uint8_t*)work + total_size -
|
|
rescaler_size);
|
|
p->scaler_y = &scalers[0];
|
|
p->scaler_u = &scalers[1];
|
|
p->scaler_v = &scalers[2];
|
|
p->scaler_a = has_alpha ? &scalers[3] : NULL;
|
|
|
|
if (!WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, tmp + 0 * out_width,
|
|
out_width, out_height, 0, 1, work + 0 * work_size) ||
|
|
!WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height,
|
|
tmp + 1 * out_width, out_width, out_height, 0, 1,
|
|
work + 1 * work_size) ||
|
|
!WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height,
|
|
tmp + 2 * out_width, out_width, out_height, 0, 1,
|
|
work + 2 * work_size)) {
|
|
return 0;
|
|
}
|
|
p->emit = EmitRescaledRGB;
|
|
WebPInitYUV444Converters();
|
|
|
|
if (has_alpha) {
|
|
if (!WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, tmp + 3 * out_width,
|
|
out_width, out_height, 0, 1, work + 3 * work_size)) {
|
|
return 0;
|
|
}
|
|
p->emit_alpha = EmitRescaledAlphaRGB;
|
|
if (p->output->colorspace == MODE_RGBA_4444 ||
|
|
p->output->colorspace == MODE_rgbA_4444) {
|
|
p->emit_alpha_row = ExportAlphaRGBA4444;
|
|
} else {
|
|
p->emit_alpha_row = ExportAlpha;
|
|
}
|
|
WebPInitAlphaProcessing();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#endif // WEBP_REDUCE_SIZE
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Default custom functions
|
|
|
|
static int CustomSetup(VP8Io* io) {
|
|
WebPDecParams* const p = (WebPDecParams*)io->opaque;
|
|
const WEBP_CSP_MODE colorspace = p->output->colorspace;
|
|
const int is_rgb = WebPIsRGBMode(colorspace);
|
|
const int is_alpha = WebPIsAlphaMode(colorspace);
|
|
|
|
p->memory = NULL;
|
|
p->emit = NULL;
|
|
p->emit_alpha = NULL;
|
|
p->emit_alpha_row = NULL;
|
|
// Note: WebPIoInitFromOptions() does not distinguish between MODE_YUV and
|
|
// MODE_YUVA, only RGB vs YUV.
|
|
if (!WebPIoInitFromOptions(p->options, io, /*src_colorspace=*/MODE_YUV)) {
|
|
return 0;
|
|
}
|
|
if (is_alpha && WebPIsPremultipliedMode(colorspace)) {
|
|
WebPInitUpsamplers();
|
|
}
|
|
if (io->use_scaling) {
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
const int ok = is_rgb ? InitRGBRescaler(io, p) : InitYUVRescaler(io, p);
|
|
if (!ok) {
|
|
return 0; // memory error
|
|
}
|
|
#else
|
|
return 0; // rescaling support not compiled
|
|
#endif
|
|
} else {
|
|
if (is_rgb) {
|
|
WebPInitSamplers();
|
|
p->emit = EmitSampledRGB; // default
|
|
if (io->fancy_upsampling) {
|
|
#ifdef FANCY_UPSAMPLING
|
|
const int uv_width = (io->mb_w + 1) >> 1;
|
|
p->memory = WebPSafeMalloc(1ULL, (size_t)(io->mb_w + 2 * uv_width));
|
|
if (p->memory == NULL) {
|
|
return 0; // memory error.
|
|
}
|
|
p->tmp_y = (uint8_t*)p->memory;
|
|
p->tmp_u = p->tmp_y + io->mb_w;
|
|
p->tmp_v = p->tmp_u + uv_width;
|
|
p->emit = EmitFancyRGB;
|
|
WebPInitUpsamplers();
|
|
#endif
|
|
}
|
|
} else {
|
|
p->emit = EmitYUV;
|
|
}
|
|
if (is_alpha) { // need transparency output
|
|
p->emit_alpha =
|
|
(colorspace == MODE_RGBA_4444 || colorspace == MODE_rgbA_4444)
|
|
? EmitAlphaRGBA4444
|
|
: is_rgb ? EmitAlphaRGB
|
|
: EmitAlphaYUV;
|
|
if (is_rgb) {
|
|
WebPInitAlphaProcessing();
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static int CustomPut(const VP8Io* io) {
|
|
WebPDecParams* const p = (WebPDecParams*)io->opaque;
|
|
const int mb_w = io->mb_w;
|
|
const int mb_h = io->mb_h;
|
|
int num_lines_out;
|
|
assert(!(io->mb_y & 1));
|
|
|
|
if (mb_w <= 0 || mb_h <= 0) {
|
|
return 0;
|
|
}
|
|
num_lines_out = p->emit(io, p);
|
|
if (p->emit_alpha != NULL) {
|
|
p->emit_alpha(io, p, num_lines_out);
|
|
}
|
|
p->last_y += num_lines_out;
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void CustomTeardown(const VP8Io* io) {
|
|
WebPDecParams* const p = (WebPDecParams*)io->opaque;
|
|
WebPSafeFree(p->memory);
|
|
p->memory = NULL;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main entry point
|
|
|
|
void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io) {
|
|
io->put = CustomPut;
|
|
io->setup = CustomSetup;
|
|
io->teardown = CustomTeardown;
|
|
io->opaque = params;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dec/quant_dec.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Quantizer initialization
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
static WEBP_INLINE int clip(int v, int M) { return v < 0 ? 0 : v > M ? M : v; }
|
|
|
|
// Paragraph 14.1
|
|
static const uint8_t kDcTable[128] = {
|
|
4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17,
|
|
17, 18, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26,
|
|
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 37, 38, 39, 40,
|
|
41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54,
|
|
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
|
|
70, 71, 72, 73, 74, 75, 76, 76, 77, 78, 79, 80, 81, 82, 83,
|
|
84, 85, 86, 87, 88, 89, 91, 93, 95, 96, 98, 100, 101, 102, 104,
|
|
106, 108, 110, 112, 114, 116, 118, 122, 124, 126, 128, 130, 132, 134, 136,
|
|
138, 140, 143, 145, 148, 151, 154, 157};
|
|
|
|
static const uint16_t kAcTable[128] = {
|
|
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
|
|
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
|
|
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
|
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64, 66, 68,
|
|
70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98,
|
|
100, 102, 104, 106, 108, 110, 112, 114, 116, 119, 122, 125, 128, 131, 134,
|
|
137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173, 177, 181,
|
|
185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 234, 239, 245,
|
|
249, 254, 259, 264, 269, 274, 279, 284};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Paragraph 9.6
|
|
|
|
void VP8ParseQuant(VP8Decoder* const dec) {
|
|
VP8BitReader* const br = &dec->br;
|
|
const int base_q0 = VP8GetValue(br, 7, "global-header");
|
|
const int dqy1_dc = VP8Get(br, "global-header")
|
|
? VP8GetSignedValue(br, 4, "global-header")
|
|
: 0;
|
|
const int dqy2_dc = VP8Get(br, "global-header")
|
|
? VP8GetSignedValue(br, 4, "global-header")
|
|
: 0;
|
|
const int dqy2_ac = VP8Get(br, "global-header")
|
|
? VP8GetSignedValue(br, 4, "global-header")
|
|
: 0;
|
|
const int dquv_dc = VP8Get(br, "global-header")
|
|
? VP8GetSignedValue(br, 4, "global-header")
|
|
: 0;
|
|
const int dquv_ac = VP8Get(br, "global-header")
|
|
? VP8GetSignedValue(br, 4, "global-header")
|
|
: 0;
|
|
|
|
const VP8SegmentHeader* const hdr = &dec->segment_hdr;
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_MB_SEGMENTS; ++i) {
|
|
int q;
|
|
if (hdr->use_segment) {
|
|
q = hdr->quantizer[i];
|
|
if (!hdr->absolute_delta) {
|
|
q += base_q0;
|
|
}
|
|
} else {
|
|
if (i > 0) {
|
|
dec->dqm[i] = dec->dqm[0];
|
|
continue;
|
|
} else {
|
|
q = base_q0;
|
|
}
|
|
}
|
|
{
|
|
VP8QuantMatrix* const m = &dec->dqm[i];
|
|
m->y1_mat[0] = kDcTable[clip(q + dqy1_dc, 127)];
|
|
m->y1_mat[1] = kAcTable[clip(q + 0, 127)];
|
|
|
|
m->y2_mat[0] = kDcTable[clip(q + dqy2_dc, 127)] * 2;
|
|
// For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16.
|
|
// The smallest precision for that is '(x*6349) >> 12' but 16 is a good
|
|
// word size.
|
|
m->y2_mat[1] = (kAcTable[clip(q + dqy2_ac, 127)] * 101581) >> 16;
|
|
if (m->y2_mat[1] < 8) m->y2_mat[1] = 8;
|
|
|
|
m->uv_mat[0] = kDcTable[clip(q + dquv_dc, 117)];
|
|
m->uv_mat[1] = kAcTable[clip(q + dquv_ac, 127)];
|
|
|
|
m->uv_quant = q + dquv_ac; // for dithering strength evaluation
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dec/tree_dec.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Coding trees and probas
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <string.h>
|
|
|
|
/* >>> src/utils/bit_reader_inl_utils.h */
|
|
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Specific inlined methods for boolean decoder [VP8GetBit() ...]
|
|
// This file should be included by the .c sources that actually need to call
|
|
// these methods.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_UTILS_BIT_READER_INL_UTILS_H_
|
|
#define WEBP_UTILS_BIT_READER_INL_UTILS_H_
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <string.h> // for memcpy
|
|
|
|
/* >>> src/utils/endian_inl_utils.h */
|
|
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Endian related functions.
|
|
|
|
#ifndef WEBP_UTILS_ENDIAN_INL_UTILS_H_
|
|
#define WEBP_UTILS_ENDIAN_INL_UTILS_H_
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#if defined(WORDS_BIGENDIAN)
|
|
#define HToLE32 BSwap32
|
|
#define HToLE16 BSwap16
|
|
#else
|
|
#define HToLE32(x) (x)
|
|
#define HToLE16(x) (x)
|
|
#endif
|
|
|
|
#if !defined(HAVE_CONFIG_H)
|
|
#if LOCAL_GCC_PREREQ(4, 8) || __has_builtin(__builtin_bswap16)
|
|
#define HAVE_BUILTIN_BSWAP16
|
|
#endif
|
|
#if LOCAL_GCC_PREREQ(4, 3) || __has_builtin(__builtin_bswap32)
|
|
#define HAVE_BUILTIN_BSWAP32
|
|
#endif
|
|
#if LOCAL_GCC_PREREQ(4, 3) || __has_builtin(__builtin_bswap64)
|
|
#define HAVE_BUILTIN_BSWAP64
|
|
#endif
|
|
#endif // !HAVE_CONFIG_H
|
|
|
|
static WEBP_INLINE uint16_t BSwap16(uint16_t x) {
|
|
#if defined(HAVE_BUILTIN_BSWAP16)
|
|
return __builtin_bswap16(x);
|
|
#elif defined(_MSC_VER)
|
|
return _byteswap_ushort(x);
|
|
#else
|
|
// gcc will recognize a 'rorw $8, ...' here:
|
|
return (x >> 8) | ((x & 0xff) << 8);
|
|
#endif // HAVE_BUILTIN_BSWAP16
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t BSwap32(uint32_t x) {
|
|
#if defined(WEBP_USE_MIPS32_R2)
|
|
uint32_t ret;
|
|
__asm__ volatile(
|
|
"wsbh %[ret], %[x] \n\t"
|
|
"rotr %[ret], %[ret], 16 \n\t"
|
|
: [ret] "=r"(ret)
|
|
: [x] "r"(x));
|
|
return ret;
|
|
#elif defined(HAVE_BUILTIN_BSWAP32)
|
|
return __builtin_bswap32(x);
|
|
#elif defined(__i386__) || defined(__x86_64__)
|
|
uint32_t swapped_bytes;
|
|
__asm__ volatile("bswap %0" : "=r"(swapped_bytes) : "0"(x));
|
|
return swapped_bytes;
|
|
#elif defined(_MSC_VER)
|
|
return (uint32_t)_byteswap_ulong(x);
|
|
#else
|
|
return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24);
|
|
#endif // HAVE_BUILTIN_BSWAP32
|
|
}
|
|
|
|
static WEBP_INLINE uint64_t BSwap64(uint64_t x) {
|
|
#if defined(HAVE_BUILTIN_BSWAP64)
|
|
return __builtin_bswap64(x);
|
|
#elif defined(__x86_64__)
|
|
uint64_t swapped_bytes;
|
|
__asm__ volatile("bswapq %0" : "=r"(swapped_bytes) : "0"(x));
|
|
return swapped_bytes;
|
|
#elif defined(_MSC_VER)
|
|
return (uint64_t)_byteswap_uint64(x);
|
|
#else // generic code for swapping 64-bit values (suggested by bdb@)
|
|
x = ((x & 0xffffffff00000000ull) >> 32) | ((x & 0x00000000ffffffffull) << 32);
|
|
x = ((x & 0xffff0000ffff0000ull) >> 16) | ((x & 0x0000ffff0000ffffull) << 16);
|
|
x = ((x & 0xff00ff00ff00ff00ull) >> 8) | ((x & 0x00ff00ff00ff00ffull) << 8);
|
|
return x;
|
|
#endif // HAVE_BUILTIN_BSWAP64
|
|
}
|
|
|
|
#endif // WEBP_UTILS_ENDIAN_INL_UTILS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Derived type lbit_t = natural type for memory I/O
|
|
|
|
#if (BITS > 32)
|
|
typedef uint64_t lbit_t;
|
|
#elif (BITS > 16)
|
|
typedef uint32_t lbit_t;
|
|
#elif (BITS > 8)
|
|
typedef uint16_t lbit_t;
|
|
#else
|
|
typedef uint8_t lbit_t;
|
|
#endif
|
|
|
|
extern const uint8_t kVP8Log2Range[128];
|
|
extern const uint8_t kVP8NewRange[128];
|
|
|
|
// special case for the tail byte-reading
|
|
void VP8LoadFinalBytes(VP8BitReader* const br);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Inlined critical functions
|
|
|
|
// makes sure br->value has at least BITS bits worth of data
|
|
static WEBP_UBSAN_IGNORE_UNDEF WEBP_INLINE void VP8LoadNewBytes(
|
|
VP8BitReader* WEBP_RESTRICT const br) {
|
|
assert(br != NULL && br->buf != NULL);
|
|
// Read 'BITS' bits at a time if possible.
|
|
if (br->buf < br->buf_max) {
|
|
// convert memory type to register type (with some zero'ing!)
|
|
bit_t bits;
|
|
#if defined(WEBP_USE_MIPS32)
|
|
// This is needed because of un-aligned read.
|
|
lbit_t in_bits;
|
|
lbit_t* p_buf = (lbit_t*)br->buf;
|
|
__asm__ volatile(
|
|
".set push \n\t"
|
|
".set at \n\t"
|
|
".set macro \n\t"
|
|
"ulw %[in_bits], 0(%[p_buf]) \n\t"
|
|
".set pop \n\t"
|
|
: [in_bits] "=r"(in_bits)
|
|
: [p_buf] "r"(p_buf)
|
|
: "memory", "at");
|
|
#else
|
|
lbit_t in_bits;
|
|
WEBP_UNSAFE_MEMCPY(&in_bits, br->buf, sizeof(in_bits));
|
|
#endif
|
|
br->buf += BITS >> 3;
|
|
WEBP_SELF_ASSIGN(br->buf_end);
|
|
#if !defined(WORDS_BIGENDIAN)
|
|
#if (BITS > 32)
|
|
bits = BSwap64(in_bits);
|
|
bits >>= 64 - BITS;
|
|
#elif (BITS >= 24)
|
|
bits = BSwap32(in_bits);
|
|
bits >>= (32 - BITS);
|
|
#elif (BITS == 16)
|
|
bits = BSwap16(in_bits);
|
|
#else // BITS == 8
|
|
bits = (bit_t)in_bits;
|
|
#endif // BITS > 32
|
|
#else // WORDS_BIGENDIAN
|
|
bits = (bit_t)in_bits;
|
|
if (BITS != 8 * sizeof(bit_t)) bits >>= (8 * sizeof(bit_t) - BITS);
|
|
#endif
|
|
br->value = bits | (br->value << BITS);
|
|
br->bits += BITS;
|
|
} else {
|
|
VP8LoadFinalBytes(br); // no need to be inlined
|
|
}
|
|
}
|
|
|
|
// Read a bit with proba 'prob'. Speed-critical function!
|
|
static WEBP_INLINE int VP8GetBit(VP8BitReader* WEBP_RESTRICT const br, int prob,
|
|
const char label[]) {
|
|
// Don't move this declaration! It makes a big speed difference to store
|
|
// 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't
|
|
// alter br->range value.
|
|
range_t range = br->range;
|
|
if (br->bits < 0) {
|
|
VP8LoadNewBytes(br);
|
|
}
|
|
{
|
|
const int pos = br->bits;
|
|
const range_t split = (range * prob) >> 8;
|
|
const range_t value = (range_t)(br->value >> pos);
|
|
const int bit = (value > split);
|
|
if (bit) {
|
|
range -= split;
|
|
br->value -= (bit_t)(split + 1) << pos;
|
|
} else {
|
|
range = split + 1;
|
|
}
|
|
{
|
|
const int shift = 7 ^ BitsLog2Floor(range);
|
|
range <<= shift;
|
|
br->bits -= shift;
|
|
}
|
|
br->range = range - 1;
|
|
BT_TRACK(br);
|
|
return bit;
|
|
}
|
|
}
|
|
|
|
// simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here)
|
|
static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE int VP8GetSigned(
|
|
VP8BitReader* WEBP_RESTRICT const br, int v, const char label[]) {
|
|
if (br->bits < 0) {
|
|
VP8LoadNewBytes(br);
|
|
}
|
|
{
|
|
const int pos = br->bits;
|
|
const range_t split = br->range >> 1;
|
|
const range_t value = (range_t)(br->value >> pos);
|
|
const int32_t mask = (int32_t)(split - value) >> 31; // -1 or 0
|
|
br->bits -= 1;
|
|
br->range += (range_t)mask;
|
|
br->range |= 1;
|
|
br->value -= (bit_t)((split + 1) & (uint32_t)mask) << pos;
|
|
BT_TRACK(br);
|
|
return (v ^ mask) - mask;
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE int VP8GetBitAlt(VP8BitReader* WEBP_RESTRICT const br,
|
|
int prob, const char label[]) {
|
|
// Don't move this declaration! It makes a big speed difference to store
|
|
// 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't
|
|
// alter br->range value.
|
|
range_t range = br->range;
|
|
if (br->bits < 0) {
|
|
VP8LoadNewBytes(br);
|
|
}
|
|
{
|
|
const int pos = br->bits;
|
|
const range_t split = (range * prob) >> 8;
|
|
const range_t value = (range_t)(br->value >> pos);
|
|
int bit; // Don't use 'const int bit = (value > split);", it's slower.
|
|
if (value > split) {
|
|
range -= split + 1;
|
|
br->value -= (bit_t)(split + 1) << pos;
|
|
bit = 1;
|
|
} else {
|
|
range = split;
|
|
bit = 0;
|
|
}
|
|
if (range <= (range_t)0x7e) {
|
|
const int shift = kVP8Log2Range[range];
|
|
range = kVP8NewRange[range];
|
|
br->bits -= shift;
|
|
}
|
|
br->range = range;
|
|
BT_TRACK(br);
|
|
return bit;
|
|
}
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_UTILS_BIT_READER_INL_UTILS_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#if !defined(USE_GENERIC_TREE)
|
|
#if !defined(__arm__) && !defined(_M_ARM) && !WEBP_AARCH64 && !defined(__wasm__)
|
|
// using a table is ~1-2% slower on ARM. Prefer the coded-tree approach then.
|
|
#define USE_GENERIC_TREE 1 // ALTERNATE_CODE
|
|
#else
|
|
#define USE_GENERIC_TREE 0
|
|
#endif
|
|
#endif // USE_GENERIC_TREE
|
|
|
|
#if (USE_GENERIC_TREE == 1)
|
|
static const int8_t kYModesIntra4[18] = {
|
|
-B_DC_PRED, 1, -B_TM_PRED, 2, -B_VE_PRED, 3,
|
|
4, 6, -B_HE_PRED, 5, -B_RD_PRED, -B_VR_PRED,
|
|
-B_LD_PRED, 7, -B_VL_PRED, 8, -B_HD_PRED, -B_HU_PRED};
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Default probabilities
|
|
|
|
// Paragraph 13.5
|
|
static const uint8_t CoeffsProba0[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = {
|
|
{{{128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}},
|
|
{{253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128},
|
|
{189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128},
|
|
{106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128}},
|
|
{
|
|
{1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128},
|
|
{181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128},
|
|
{78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128},
|
|
},
|
|
{
|
|
{1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128},
|
|
{184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128},
|
|
{77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128},
|
|
},
|
|
{{1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128},
|
|
{170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128},
|
|
{37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128}},
|
|
{{1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128},
|
|
{207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128},
|
|
{102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128}},
|
|
{{1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128},
|
|
{177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128},
|
|
{80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128}},
|
|
{{1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}}},
|
|
{{{198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62},
|
|
{131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1},
|
|
{68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128}},
|
|
{{1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128},
|
|
{184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128},
|
|
{81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128}},
|
|
{{1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128},
|
|
{99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128},
|
|
{23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128}},
|
|
{{1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128},
|
|
{109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128},
|
|
{44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128}},
|
|
{{1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128},
|
|
{94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128},
|
|
{22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128}},
|
|
{{1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128},
|
|
{124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128},
|
|
{35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128}},
|
|
{{1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128},
|
|
{121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128},
|
|
{45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128}},
|
|
{{1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128},
|
|
{203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128},
|
|
{137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128}}},
|
|
{{{253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128},
|
|
{175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128},
|
|
{73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128}},
|
|
{{1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128},
|
|
{239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128},
|
|
{155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128}},
|
|
{{1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128},
|
|
{201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128},
|
|
{69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128}},
|
|
{{1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128},
|
|
{223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128},
|
|
{141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128}},
|
|
{{1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128},
|
|
{190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128},
|
|
{149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}},
|
|
{{1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128}},
|
|
{{1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128},
|
|
{213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128},
|
|
{55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128}},
|
|
{{128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}}},
|
|
{{{202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255},
|
|
{126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128},
|
|
{61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128}},
|
|
{{1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128},
|
|
{166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128},
|
|
{39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128}},
|
|
{{1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128},
|
|
{124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128},
|
|
{24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128}},
|
|
{{1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128},
|
|
{149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128},
|
|
{28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128}},
|
|
{{1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128},
|
|
{123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128},
|
|
{20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128}},
|
|
{{1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128},
|
|
{168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128},
|
|
{47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128}},
|
|
{{1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128},
|
|
{141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128},
|
|
{42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128}},
|
|
{{1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128},
|
|
{238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}}}};
|
|
|
|
// Paragraph 11.5
|
|
static const uint8_t kBModesProba[NUM_BMODES][NUM_BMODES][NUM_BMODES - 1] = {
|
|
{{231, 120, 48, 89, 115, 113, 120, 152, 112},
|
|
{152, 179, 64, 126, 170, 118, 46, 70, 95},
|
|
{175, 69, 143, 80, 85, 82, 72, 155, 103},
|
|
{56, 58, 10, 171, 218, 189, 17, 13, 152},
|
|
{114, 26, 17, 163, 44, 195, 21, 10, 173},
|
|
{121, 24, 80, 195, 26, 62, 44, 64, 85},
|
|
{144, 71, 10, 38, 171, 213, 144, 34, 26},
|
|
{170, 46, 55, 19, 136, 160, 33, 206, 71},
|
|
{63, 20, 8, 114, 114, 208, 12, 9, 226},
|
|
{81, 40, 11, 96, 182, 84, 29, 16, 36}},
|
|
{{134, 183, 89, 137, 98, 101, 106, 165, 148},
|
|
{72, 187, 100, 130, 157, 111, 32, 75, 80},
|
|
{66, 102, 167, 99, 74, 62, 40, 234, 128},
|
|
{41, 53, 9, 178, 241, 141, 26, 8, 107},
|
|
{74, 43, 26, 146, 73, 166, 49, 23, 157},
|
|
{65, 38, 105, 160, 51, 52, 31, 115, 128},
|
|
{104, 79, 12, 27, 217, 255, 87, 17, 7},
|
|
{87, 68, 71, 44, 114, 51, 15, 186, 23},
|
|
{47, 41, 14, 110, 182, 183, 21, 17, 194},
|
|
{66, 45, 25, 102, 197, 189, 23, 18, 22}},
|
|
{{88, 88, 147, 150, 42, 46, 45, 196, 205},
|
|
{43, 97, 183, 117, 85, 38, 35, 179, 61},
|
|
{39, 53, 200, 87, 26, 21, 43, 232, 171},
|
|
{56, 34, 51, 104, 114, 102, 29, 93, 77},
|
|
{39, 28, 85, 171, 58, 165, 90, 98, 64},
|
|
{34, 22, 116, 206, 23, 34, 43, 166, 73},
|
|
{107, 54, 32, 26, 51, 1, 81, 43, 31},
|
|
{68, 25, 106, 22, 64, 171, 36, 225, 114},
|
|
{34, 19, 21, 102, 132, 188, 16, 76, 124},
|
|
{62, 18, 78, 95, 85, 57, 50, 48, 51}},
|
|
{{193, 101, 35, 159, 215, 111, 89, 46, 111},
|
|
{60, 148, 31, 172, 219, 228, 21, 18, 111},
|
|
{112, 113, 77, 85, 179, 255, 38, 120, 114},
|
|
{40, 42, 1, 196, 245, 209, 10, 25, 109},
|
|
{88, 43, 29, 140, 166, 213, 37, 43, 154},
|
|
{61, 63, 30, 155, 67, 45, 68, 1, 209},
|
|
{100, 80, 8, 43, 154, 1, 51, 26, 71},
|
|
{142, 78, 78, 16, 255, 128, 34, 197, 171},
|
|
{41, 40, 5, 102, 211, 183, 4, 1, 221},
|
|
{51, 50, 17, 168, 209, 192, 23, 25, 82}},
|
|
{{138, 31, 36, 171, 27, 166, 38, 44, 229},
|
|
{67, 87, 58, 169, 82, 115, 26, 59, 179},
|
|
{63, 59, 90, 180, 59, 166, 93, 73, 154},
|
|
{40, 40, 21, 116, 143, 209, 34, 39, 175},
|
|
{47, 15, 16, 183, 34, 223, 49, 45, 183},
|
|
{46, 17, 33, 183, 6, 98, 15, 32, 183},
|
|
{57, 46, 22, 24, 128, 1, 54, 17, 37},
|
|
{65, 32, 73, 115, 28, 128, 23, 128, 205},
|
|
{40, 3, 9, 115, 51, 192, 18, 6, 223},
|
|
{87, 37, 9, 115, 59, 77, 64, 21, 47}},
|
|
{{104, 55, 44, 218, 9, 54, 53, 130, 226},
|
|
{64, 90, 70, 205, 40, 41, 23, 26, 57},
|
|
{54, 57, 112, 184, 5, 41, 38, 166, 213},
|
|
{30, 34, 26, 133, 152, 116, 10, 32, 134},
|
|
{39, 19, 53, 221, 26, 114, 32, 73, 255},
|
|
{31, 9, 65, 234, 2, 15, 1, 118, 73},
|
|
{75, 32, 12, 51, 192, 255, 160, 43, 51},
|
|
{88, 31, 35, 67, 102, 85, 55, 186, 85},
|
|
{56, 21, 23, 111, 59, 205, 45, 37, 192},
|
|
{55, 38, 70, 124, 73, 102, 1, 34, 98}},
|
|
{{125, 98, 42, 88, 104, 85, 117, 175, 82},
|
|
{95, 84, 53, 89, 128, 100, 113, 101, 45},
|
|
{75, 79, 123, 47, 51, 128, 81, 171, 1},
|
|
{57, 17, 5, 71, 102, 57, 53, 41, 49},
|
|
{38, 33, 13, 121, 57, 73, 26, 1, 85},
|
|
{41, 10, 67, 138, 77, 110, 90, 47, 114},
|
|
{115, 21, 2, 10, 102, 255, 166, 23, 6},
|
|
{101, 29, 16, 10, 85, 128, 101, 196, 26},
|
|
{57, 18, 10, 102, 102, 213, 34, 20, 43},
|
|
{117, 20, 15, 36, 163, 128, 68, 1, 26}},
|
|
{{102, 61, 71, 37, 34, 53, 31, 243, 192},
|
|
{69, 60, 71, 38, 73, 119, 28, 222, 37},
|
|
{68, 45, 128, 34, 1, 47, 11, 245, 171},
|
|
{62, 17, 19, 70, 146, 85, 55, 62, 70},
|
|
{37, 43, 37, 154, 100, 163, 85, 160, 1},
|
|
{63, 9, 92, 136, 28, 64, 32, 201, 85},
|
|
{75, 15, 9, 9, 64, 255, 184, 119, 16},
|
|
{86, 6, 28, 5, 64, 255, 25, 248, 1},
|
|
{56, 8, 17, 132, 137, 255, 55, 116, 128},
|
|
{58, 15, 20, 82, 135, 57, 26, 121, 40}},
|
|
{{164, 50, 31, 137, 154, 133, 25, 35, 218},
|
|
{51, 103, 44, 131, 131, 123, 31, 6, 158},
|
|
{86, 40, 64, 135, 148, 224, 45, 183, 128},
|
|
{22, 26, 17, 131, 240, 154, 14, 1, 209},
|
|
{45, 16, 21, 91, 64, 222, 7, 1, 197},
|
|
{56, 21, 39, 155, 60, 138, 23, 102, 213},
|
|
{83, 12, 13, 54, 192, 255, 68, 47, 28},
|
|
{85, 26, 85, 85, 128, 128, 32, 146, 171},
|
|
{18, 11, 7, 63, 144, 171, 4, 4, 246},
|
|
{35, 27, 10, 146, 174, 171, 12, 26, 128}},
|
|
{{190, 80, 35, 99, 180, 80, 126, 54, 45},
|
|
{85, 126, 47, 87, 176, 51, 41, 20, 32},
|
|
{101, 75, 128, 139, 118, 146, 116, 128, 85},
|
|
{56, 41, 15, 176, 236, 85, 37, 9, 62},
|
|
{71, 30, 17, 119, 118, 255, 17, 18, 138},
|
|
{101, 38, 60, 138, 55, 70, 43, 26, 142},
|
|
{146, 36, 19, 30, 171, 255, 97, 27, 20},
|
|
{138, 45, 61, 62, 219, 1, 81, 188, 64},
|
|
{32, 41, 20, 117, 151, 142, 20, 21, 163},
|
|
{112, 19, 12, 61, 195, 128, 48, 4, 24}}};
|
|
|
|
void VP8ResetProba(VP8Proba* const proba) {
|
|
WEBP_UNSAFE_MEMSET(proba->segments, 255u, sizeof(proba->segments));
|
|
// proba->bands[][] is initialized later
|
|
}
|
|
|
|
static void ParseIntraMode(VP8BitReader* const br, VP8Decoder* const dec,
|
|
int mb_x) {
|
|
uint8_t* const top = dec->intra_t + 4 * mb_x;
|
|
uint8_t* const left = dec->intra_l;
|
|
VP8MBData* const block = dec->mb_data + mb_x;
|
|
|
|
// Note: we don't save segment map (yet), as we don't expect
|
|
// to decode more than 1 keyframe.
|
|
if (dec->segment_hdr.update_map) {
|
|
// Hardcoded tree parsing
|
|
block->segment =
|
|
!VP8GetBit(br, dec->proba.segments[0], "segments")
|
|
? VP8GetBit(br, dec->proba.segments[1], "segments")
|
|
: VP8GetBit(br, dec->proba.segments[2], "segments") + 2;
|
|
} else {
|
|
block->segment = 0; // default for intra
|
|
}
|
|
if (dec->use_skip_proba) block->skip = VP8GetBit(br, dec->skip_p, "skip");
|
|
|
|
block->is_i4x4 = !VP8GetBit(br, 145, "block-size");
|
|
if (!block->is_i4x4) {
|
|
// Hardcoded 16x16 intra-mode decision tree.
|
|
const int ymode =
|
|
VP8GetBit(br, 156, "pred-modes")
|
|
? (VP8GetBit(br, 128, "pred-modes") ? TM_PRED : H_PRED)
|
|
: (VP8GetBit(br, 163, "pred-modes") ? V_PRED : DC_PRED);
|
|
block->imodes[0] = ymode;
|
|
WEBP_UNSAFE_MEMSET(top, ymode, 4 * sizeof(*top));
|
|
WEBP_UNSAFE_MEMSET(left, ymode, 4 * sizeof(*left));
|
|
} else {
|
|
uint8_t* modes = block->imodes;
|
|
int y;
|
|
for (y = 0; y < 4; ++y) {
|
|
int ymode = left[y];
|
|
int x;
|
|
for (x = 0; x < 4; ++x) {
|
|
const uint8_t* const prob = kBModesProba[top[x]][ymode];
|
|
#if (USE_GENERIC_TREE == 1)
|
|
// Generic tree-parsing
|
|
int i = kYModesIntra4[VP8GetBit(br, prob[0], "pred-modes")];
|
|
while (i > 0) {
|
|
i = kYModesIntra4[2 * i + VP8GetBit(br, prob[i], "pred-modes")];
|
|
}
|
|
ymode = -i;
|
|
#else
|
|
// Hardcoded tree parsing
|
|
ymode =
|
|
!VP8GetBit(br, prob[0], "pred-modes") ? B_DC_PRED
|
|
: !VP8GetBit(br, prob[1], "pred-modes") ? B_TM_PRED
|
|
: !VP8GetBit(br, prob[2], "pred-modes") ? B_VE_PRED
|
|
: !VP8GetBit(br, prob[3], "pred-modes")
|
|
? (!VP8GetBit(br, prob[4], "pred-modes")
|
|
? B_HE_PRED
|
|
: (!VP8GetBit(br, prob[5], "pred-modes") ? B_RD_PRED
|
|
: B_VR_PRED))
|
|
: (!VP8GetBit(br, prob[6], "pred-modes")
|
|
? B_LD_PRED
|
|
: (!VP8GetBit(br, prob[7], "pred-modes")
|
|
? B_VL_PRED
|
|
: (!VP8GetBit(br, prob[8], "pred-modes")
|
|
? B_HD_PRED
|
|
: B_HU_PRED)));
|
|
#endif // USE_GENERIC_TREE
|
|
top[x] = ymode;
|
|
}
|
|
WEBP_UNSAFE_MEMCPY(modes, top, 4 * sizeof(*top));
|
|
modes += 4;
|
|
left[y] = ymode;
|
|
}
|
|
}
|
|
// Hardcoded UVMode decision tree
|
|
block->uvmode = !VP8GetBit(br, 142, "pred-modes-uv") ? DC_PRED
|
|
: !VP8GetBit(br, 114, "pred-modes-uv") ? V_PRED
|
|
: VP8GetBit(br, 183, "pred-modes-uv") ? TM_PRED
|
|
: H_PRED;
|
|
}
|
|
|
|
int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec) {
|
|
int mb_x;
|
|
for (mb_x = 0; mb_x < dec->mb_w; ++mb_x) {
|
|
ParseIntraMode(br, dec, mb_x);
|
|
}
|
|
return !dec->br.eof;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Paragraph 13
|
|
|
|
static const uint8_t
|
|
CoeffsUpdateProba[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = {
|
|
{{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255},
|
|
{250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255},
|
|
{254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}},
|
|
{{{217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255},
|
|
{234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255}},
|
|
{{255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}},
|
|
{{{186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255},
|
|
{251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255}},
|
|
{{255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}},
|
|
{{{248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255},
|
|
{248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
{{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
|
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}}}};
|
|
|
|
// Paragraph 9.9
|
|
|
|
static const uint8_t kBands[16 + 1] = {
|
|
0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7,
|
|
0 // extra entry as sentinel
|
|
};
|
|
|
|
void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec) {
|
|
VP8Proba* const proba = &dec->proba;
|
|
int t, b, c, p;
|
|
for (t = 0; t < NUM_TYPES; ++t) {
|
|
for (b = 0; b < NUM_BANDS; ++b) {
|
|
for (c = 0; c < NUM_CTX; ++c) {
|
|
for (p = 0; p < NUM_PROBAS; ++p) {
|
|
const int v =
|
|
VP8GetBit(br, CoeffsUpdateProba[t][b][c][p], "global-header")
|
|
? VP8GetValue(br, 8, "global-header")
|
|
: CoeffsProba0[t][b][c][p];
|
|
proba->bands[t][b].probas[c][p] = v;
|
|
}
|
|
}
|
|
}
|
|
for (b = 0; b < 16 + 1; ++b) {
|
|
proba->bands_ptr[t][b] = &proba->bands[t][kBands[b]];
|
|
}
|
|
}
|
|
dec->use_skip_proba = VP8Get(br, "global-header");
|
|
if (dec->use_skip_proba) {
|
|
dec->skip_p = VP8GetValue(br, 8, "global-header");
|
|
}
|
|
}
|
|
/* >>> src/dec/vp8_dec.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// main entry for the decoder
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int WebPGetDecoderVersion(void) {
|
|
return (DEC_MAJ_VERSION << 16) | (DEC_MIN_VERSION << 8) | DEC_REV_VERSION;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Signature and pointer-to-function for GetCoeffs() variants below.
|
|
|
|
typedef int (*GetCoeffsFunc)(VP8BitReader* const br,
|
|
const VP8BandProbas* const prob[], int ctx,
|
|
const quant_t dq, int n, int16_t* out);
|
|
static volatile GetCoeffsFunc GetCoeffs = NULL;
|
|
|
|
static void InitGetCoeffs(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// VP8Decoder
|
|
|
|
static void SetOk(VP8Decoder* const dec) {
|
|
dec->status = VP8_STATUS_OK;
|
|
dec->error_msg = "OK";
|
|
}
|
|
|
|
int VP8InitIoInternal(VP8Io* const io, int version) {
|
|
if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) {
|
|
return 0; // mismatch error
|
|
}
|
|
if (io != NULL) {
|
|
WEBP_UNSAFE_MEMSET(io, 0, sizeof(*io));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
VP8Decoder* VP8New(void) {
|
|
VP8Decoder* const dec = (VP8Decoder*)WebPSafeCalloc(1ULL, sizeof(*dec));
|
|
if (dec != NULL) {
|
|
SetOk(dec);
|
|
WebPGetWorkerInterface()->Init(&dec->worker);
|
|
dec->ready = 0;
|
|
dec->num_parts_minus_one = 0;
|
|
InitGetCoeffs();
|
|
}
|
|
return dec;
|
|
}
|
|
|
|
VP8StatusCode VP8Status(VP8Decoder* const dec) {
|
|
if (!dec) return VP8_STATUS_INVALID_PARAM;
|
|
return dec->status;
|
|
}
|
|
|
|
const char* VP8StatusMessage(VP8Decoder* const dec) {
|
|
if (dec == NULL) return "no object";
|
|
if (!dec->error_msg) return "OK";
|
|
return dec->error_msg;
|
|
}
|
|
|
|
void VP8Delete(VP8Decoder* const dec) {
|
|
if (dec != NULL) {
|
|
VP8Clear(dec);
|
|
WebPSafeFree(dec);
|
|
}
|
|
}
|
|
|
|
int VP8SetError(VP8Decoder* const dec, VP8StatusCode error,
|
|
const char* const msg) {
|
|
// VP8_STATUS_SUSPENDED is only meaningful in incremental decoding.
|
|
assert(dec->incremental || error != VP8_STATUS_SUSPENDED);
|
|
// The oldest error reported takes precedence over the new one.
|
|
if (dec->status == VP8_STATUS_OK) {
|
|
dec->status = error;
|
|
dec->error_msg = msg;
|
|
dec->ready = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int VP8CheckSignature(const uint8_t* const WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size) {
|
|
return (data_size >= 3 && data[0] == 0x9d && data[1] == 0x01 &&
|
|
data[2] == 0x2a);
|
|
}
|
|
|
|
int VP8GetInfo(const uint8_t* WEBP_COUNTED_BY(data_size) data, size_t data_size,
|
|
size_t chunk_size, int* const width, int* const height) {
|
|
if (data == NULL || data_size < VP8_FRAME_HEADER_SIZE) {
|
|
return 0; // not enough data
|
|
}
|
|
// check signature
|
|
if (!VP8CheckSignature(data + 3, data_size - 3)) {
|
|
return 0; // Wrong signature.
|
|
} else {
|
|
const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16);
|
|
const int key_frame = !(bits & 1);
|
|
const int w = ((data[7] << 8) | data[6]) & 0x3fff;
|
|
const int h = ((data[9] << 8) | data[8]) & 0x3fff;
|
|
|
|
if (!key_frame) { // Not a keyframe.
|
|
return 0;
|
|
}
|
|
|
|
if (((bits >> 1) & 7) > 3) {
|
|
return 0; // unknown profile
|
|
}
|
|
if (!((bits >> 4) & 1)) {
|
|
return 0; // first frame is invisible!
|
|
}
|
|
if (((bits >> 5)) >= chunk_size) { // partition_length
|
|
return 0; // inconsistent size information.
|
|
}
|
|
if (w == 0 || h == 0) {
|
|
return 0; // We don't support both width and height to be zero.
|
|
}
|
|
|
|
if (width) {
|
|
*width = w;
|
|
}
|
|
if (height) {
|
|
*height = h;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Header parsing
|
|
|
|
static void ResetSegmentHeader(VP8SegmentHeader* const hdr) {
|
|
assert(hdr != NULL);
|
|
hdr->use_segment = 0;
|
|
hdr->update_map = 0;
|
|
hdr->absolute_delta = 1;
|
|
WEBP_UNSAFE_MEMSET(hdr->quantizer, 0, sizeof(hdr->quantizer));
|
|
WEBP_UNSAFE_MEMSET(hdr->filter_strength, 0, sizeof(hdr->filter_strength));
|
|
}
|
|
|
|
// Paragraph 9.3
|
|
static int ParseSegmentHeader(VP8BitReader* br, VP8SegmentHeader* hdr,
|
|
VP8Proba* proba) {
|
|
assert(br != NULL);
|
|
assert(hdr != NULL);
|
|
hdr->use_segment = VP8Get(br, "global-header");
|
|
if (hdr->use_segment) {
|
|
hdr->update_map = VP8Get(br, "global-header");
|
|
if (VP8Get(br, "global-header")) { // update data
|
|
int s;
|
|
hdr->absolute_delta = VP8Get(br, "global-header");
|
|
for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
|
|
hdr->quantizer[s] = VP8Get(br, "global-header")
|
|
? VP8GetSignedValue(br, 7, "global-header")
|
|
: 0;
|
|
}
|
|
for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
|
|
hdr->filter_strength[s] =
|
|
VP8Get(br, "global-header")
|
|
? VP8GetSignedValue(br, 6, "global-header")
|
|
: 0;
|
|
}
|
|
}
|
|
if (hdr->update_map) {
|
|
int s;
|
|
for (s = 0; s < MB_FEATURE_TREE_PROBS; ++s) {
|
|
proba->segments[s] = VP8Get(br, "global-header")
|
|
? VP8GetValue(br, 8, "global-header")
|
|
: 255u;
|
|
}
|
|
}
|
|
} else {
|
|
hdr->update_map = 0;
|
|
}
|
|
return !br->eof;
|
|
}
|
|
|
|
// Paragraph 9.5
|
|
// If we don't have all the necessary data in 'buf', this function returns
|
|
// VP8_STATUS_SUSPENDED in incremental decoding, VP8_STATUS_NOT_ENOUGH_DATA
|
|
// otherwise.
|
|
// In incremental decoding, this case is not necessarily an error. Still, no
|
|
// bitreader is ever initialized to make it possible to read unavailable memory.
|
|
// If we don't even have the partitions' sizes, then VP8_STATUS_NOT_ENOUGH_DATA
|
|
// is returned, and this is an unrecoverable error.
|
|
// If the partitions were positioned ok, VP8_STATUS_OK is returned.
|
|
static VP8StatusCode ParsePartitions(VP8Decoder* const dec,
|
|
const uint8_t* WEBP_COUNTED_BY(size) buf,
|
|
size_t size) {
|
|
VP8BitReader* const br = &dec->br;
|
|
const uint8_t* WEBP_BIDI_INDEXABLE sz = buf;
|
|
const uint8_t* buf_end = buf + size;
|
|
const uint8_t* WEBP_BIDI_INDEXABLE part_start;
|
|
size_t size_left = size;
|
|
size_t last_part;
|
|
size_t p;
|
|
|
|
dec->num_parts_minus_one = (1 << VP8GetValue(br, 2, "global-header")) - 1;
|
|
last_part = dec->num_parts_minus_one;
|
|
if (size < 3 * last_part) {
|
|
// we can't even read the sizes with sz[]! That's a failure.
|
|
return VP8_STATUS_NOT_ENOUGH_DATA;
|
|
}
|
|
part_start = buf + last_part * 3;
|
|
size_left -= last_part * 3;
|
|
for (p = 0; p < last_part; ++p) {
|
|
size_t psize = sz[0] | (sz[1] << 8) | (sz[2] << 16);
|
|
if (psize > size_left) psize = size_left;
|
|
VP8InitBitReader(dec->parts + p, part_start, psize);
|
|
part_start += psize;
|
|
size_left -= psize;
|
|
sz += 3;
|
|
}
|
|
VP8InitBitReader(dec->parts + last_part, part_start, size_left);
|
|
if (part_start < buf_end) return VP8_STATUS_OK;
|
|
return dec->incremental
|
|
? VP8_STATUS_SUSPENDED // Init is ok, but there's not enough data
|
|
: VP8_STATUS_NOT_ENOUGH_DATA;
|
|
}
|
|
|
|
// Paragraph 9.4
|
|
static int ParseFilterHeader(VP8BitReader* br, VP8Decoder* const dec) {
|
|
VP8FilterHeader* const hdr = &dec->filter_hdr;
|
|
hdr->simple = VP8Get(br, "global-header");
|
|
hdr->level = VP8GetValue(br, 6, "global-header");
|
|
hdr->sharpness = VP8GetValue(br, 3, "global-header");
|
|
hdr->use_lf_delta = VP8Get(br, "global-header");
|
|
if (hdr->use_lf_delta) {
|
|
if (VP8Get(br, "global-header")) { // update lf-delta?
|
|
int i;
|
|
for (i = 0; i < NUM_REF_LF_DELTAS; ++i) {
|
|
if (VP8Get(br, "global-header")) {
|
|
hdr->ref_lf_delta[i] = VP8GetSignedValue(br, 6, "global-header");
|
|
}
|
|
}
|
|
for (i = 0; i < NUM_MODE_LF_DELTAS; ++i) {
|
|
if (VP8Get(br, "global-header")) {
|
|
hdr->mode_lf_delta[i] = VP8GetSignedValue(br, 6, "global-header");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dec->filter_type = (hdr->level == 0) ? 0 : hdr->simple ? 1 : 2;
|
|
return !br->eof;
|
|
}
|
|
|
|
// Topmost call
|
|
int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) {
|
|
size_t buf_size;
|
|
const uint8_t* WEBP_COUNTED_BY(buf_size) buf;
|
|
VP8FrameHeader* frm_hdr;
|
|
VP8PictureHeader* pic_hdr;
|
|
VP8BitReader* br;
|
|
VP8StatusCode status;
|
|
|
|
if (dec == NULL) {
|
|
return 0;
|
|
}
|
|
SetOk(dec);
|
|
if (io == NULL) {
|
|
return VP8SetError(dec, VP8_STATUS_INVALID_PARAM,
|
|
"null VP8Io passed to VP8GetHeaders()");
|
|
}
|
|
buf_size = io->data_size;
|
|
buf =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, io->data, io->data_size);
|
|
if (buf_size < 4) {
|
|
return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, "Truncated header.");
|
|
}
|
|
|
|
// Paragraph 9.1
|
|
{
|
|
const uint32_t bits = buf[0] | (buf[1] << 8) | (buf[2] << 16);
|
|
frm_hdr = &dec->frm_hdr;
|
|
frm_hdr->key_frame = !(bits & 1);
|
|
frm_hdr->profile = (bits >> 1) & 7;
|
|
frm_hdr->show = (bits >> 4) & 1;
|
|
frm_hdr->partition_length = (bits >> 5);
|
|
if (frm_hdr->profile > 3) {
|
|
return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
|
|
"Incorrect keyframe parameters.");
|
|
}
|
|
if (!frm_hdr->show) {
|
|
return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE,
|
|
"Frame not displayable.");
|
|
}
|
|
buf += 3;
|
|
buf_size -= 3;
|
|
}
|
|
|
|
pic_hdr = &dec->pic_hdr;
|
|
if (frm_hdr->key_frame) {
|
|
// Paragraph 9.2
|
|
if (buf_size < 7) {
|
|
return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
|
|
"cannot parse picture header");
|
|
}
|
|
if (!VP8CheckSignature(buf, buf_size)) {
|
|
return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, "Bad code word");
|
|
}
|
|
pic_hdr->width = ((buf[4] << 8) | buf[3]) & 0x3fff;
|
|
pic_hdr->xscale = buf[4] >> 6; // ratio: 1, 5/4 5/3 or 2
|
|
pic_hdr->height = ((buf[6] << 8) | buf[5]) & 0x3fff;
|
|
pic_hdr->yscale = buf[6] >> 6;
|
|
buf += 7;
|
|
buf_size -= 7;
|
|
|
|
dec->mb_w = (pic_hdr->width + 15) >> 4;
|
|
dec->mb_h = (pic_hdr->height + 15) >> 4;
|
|
|
|
// Setup default output area (can be later modified during io->setup())
|
|
io->width = pic_hdr->width;
|
|
io->height = pic_hdr->height;
|
|
// IMPORTANT! use some sane dimensions in crop* and scaled* fields.
|
|
// So they can be used interchangeably without always testing for
|
|
// 'use_cropping'.
|
|
io->use_cropping = 0;
|
|
io->crop_top = 0;
|
|
io->crop_left = 0;
|
|
io->crop_right = io->width;
|
|
io->crop_bottom = io->height;
|
|
io->use_scaling = 0;
|
|
io->scaled_width = io->width;
|
|
io->scaled_height = io->height;
|
|
|
|
io->mb_w = io->width; // for soundness
|
|
io->mb_h = io->height; // ditto
|
|
|
|
VP8ResetProba(&dec->proba);
|
|
ResetSegmentHeader(&dec->segment_hdr);
|
|
}
|
|
|
|
// Check if we have all the partition #0 available, and initialize dec->br
|
|
// to read this partition (and this partition only).
|
|
if (frm_hdr->partition_length > buf_size) {
|
|
return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, "bad partition length");
|
|
}
|
|
|
|
br = &dec->br;
|
|
VP8InitBitReader(br, buf, frm_hdr->partition_length);
|
|
buf += frm_hdr->partition_length;
|
|
buf_size -= frm_hdr->partition_length;
|
|
|
|
if (frm_hdr->key_frame) {
|
|
pic_hdr->colorspace = VP8Get(br, "global-header");
|
|
pic_hdr->clamp_type = VP8Get(br, "global-header");
|
|
}
|
|
if (!ParseSegmentHeader(br, &dec->segment_hdr, &dec->proba)) {
|
|
return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
|
|
"cannot parse segment header");
|
|
}
|
|
// Filter specs
|
|
if (!ParseFilterHeader(br, dec)) {
|
|
return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
|
|
"cannot parse filter header");
|
|
}
|
|
status = ParsePartitions(dec, buf, buf_size);
|
|
if (status != VP8_STATUS_OK) {
|
|
return VP8SetError(dec, status, "cannot parse partitions");
|
|
}
|
|
|
|
// quantizer change
|
|
VP8ParseQuant(dec);
|
|
|
|
// Frame buffer marking
|
|
if (!frm_hdr->key_frame) {
|
|
return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE, "Not a key frame.");
|
|
}
|
|
|
|
VP8Get(br, "global-header"); // ignore the value of 'update_proba'
|
|
|
|
VP8ParseProba(br, dec);
|
|
|
|
// sanitized state
|
|
dec->ready = 1;
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Residual decoding (Paragraph 13.2 / 13.3)
|
|
|
|
static const uint8_t kCat3[] = {173, 148, 140, 0};
|
|
static const uint8_t kCat4[] = {176, 155, 140, 135, 0};
|
|
static const uint8_t kCat5[] = {180, 157, 141, 134, 130, 0};
|
|
static const uint8_t kCat6[] = {254, 254, 243, 230, 196, 177,
|
|
153, 140, 133, 130, 129, 0};
|
|
static const uint8_t* const kCat3456[] = {kCat3, kCat4, kCat5, kCat6};
|
|
static const uint8_t kZigzag[16] = {0, 1, 4, 8, 5, 2, 3, 6,
|
|
9, 12, 13, 10, 7, 11, 14, 15};
|
|
|
|
// See section 13-2: https://datatracker.ietf.org/doc/html/rfc6386#section-13.2
|
|
static int GetLargeValue(VP8BitReader* const br, const uint8_t* const p) {
|
|
int v;
|
|
if (!VP8GetBit(br, p[3], "coeffs")) {
|
|
if (!VP8GetBit(br, p[4], "coeffs")) {
|
|
v = 2;
|
|
} else {
|
|
v = 3 + VP8GetBit(br, p[5], "coeffs");
|
|
}
|
|
} else {
|
|
if (!VP8GetBit(br, p[6], "coeffs")) {
|
|
if (!VP8GetBit(br, p[7], "coeffs")) {
|
|
v = 5 + VP8GetBit(br, 159, "coeffs");
|
|
} else {
|
|
v = 7 + 2 * VP8GetBit(br, 165, "coeffs");
|
|
v += VP8GetBit(br, 145, "coeffs");
|
|
}
|
|
} else {
|
|
const uint8_t* tab;
|
|
const int bit1 = VP8GetBit(br, p[8], "coeffs");
|
|
const int bit0 = VP8GetBit(br, p[9 + bit1], "coeffs");
|
|
const int cat = 2 * bit1 + bit0;
|
|
v = 0;
|
|
for (tab = kCat3456[cat]; *tab; ++tab) {
|
|
v += v + VP8GetBit(br, *tab, "coeffs");
|
|
}
|
|
v += 3 + (8 << cat);
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
// Returns the position of the last non-zero coeff plus one
|
|
static int GetCoeffsFast(VP8BitReader* const br,
|
|
const VP8BandProbas* const prob[], int ctx,
|
|
const quant_t dq, int n, int16_t* out) {
|
|
const uint8_t* p = prob[n]->probas[ctx];
|
|
for (; n < 16; ++n) {
|
|
if (!VP8GetBit(br, p[0], "coeffs")) {
|
|
return n; // previous coeff was last non-zero coeff
|
|
}
|
|
while (!VP8GetBit(br, p[1], "coeffs")) { // sequence of zero coeffs
|
|
p = prob[++n]->probas[0];
|
|
if (n == 16) return 16;
|
|
}
|
|
{ // non zero coeff
|
|
const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas[0];
|
|
int v;
|
|
if (!VP8GetBit(br, p[2], "coeffs")) {
|
|
v = 1;
|
|
p = p_ctx[1];
|
|
} else {
|
|
v = GetLargeValue(br, p);
|
|
p = p_ctx[2];
|
|
}
|
|
out[kZigzag[n]] = VP8GetSigned(br, v, "coeffs") * dq[n > 0];
|
|
}
|
|
}
|
|
return 16;
|
|
}
|
|
|
|
// This version of GetCoeffs() uses VP8GetBitAlt() which is an alternate version
|
|
// of VP8GetBitAlt() targeting specific platforms.
|
|
static int GetCoeffsAlt(VP8BitReader* const br,
|
|
const VP8BandProbas* const prob[], int ctx,
|
|
const quant_t dq, int n, int16_t* out) {
|
|
const uint8_t* p = prob[n]->probas[ctx];
|
|
for (; n < 16; ++n) {
|
|
if (!VP8GetBitAlt(br, p[0], "coeffs")) {
|
|
return n; // previous coeff was last non-zero coeff
|
|
}
|
|
while (!VP8GetBitAlt(br, p[1], "coeffs")) { // sequence of zero coeffs
|
|
p = prob[++n]->probas[0];
|
|
if (n == 16) return 16;
|
|
}
|
|
{ // non zero coeff
|
|
const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas[0];
|
|
int v;
|
|
if (!VP8GetBitAlt(br, p[2], "coeffs")) {
|
|
v = 1;
|
|
p = p_ctx[1];
|
|
} else {
|
|
v = GetLargeValue(br, p);
|
|
p = p_ctx[2];
|
|
}
|
|
out[kZigzag[n]] = VP8GetSigned(br, v, "coeffs") * dq[n > 0];
|
|
}
|
|
}
|
|
return 16;
|
|
}
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
|
|
WEBP_DSP_INIT_FUNC(InitGetCoeffs) {
|
|
if (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kSlowSSSE3)) {
|
|
GetCoeffs = GetCoeffsAlt;
|
|
} else {
|
|
GetCoeffs = GetCoeffsFast;
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t NzCodeBits(uint32_t nz_coeffs, int nz, int dc_nz) {
|
|
nz_coeffs <<= 2;
|
|
nz_coeffs |= (nz > 3) ? 3 : (nz > 1) ? 2 : dc_nz;
|
|
return nz_coeffs;
|
|
}
|
|
|
|
static int ParseResiduals(VP8Decoder* const dec, VP8MB* const mb,
|
|
VP8BitReader* const token_br) {
|
|
const VP8BandProbas*(*const bands)[16 + 1] = dec->proba.bands_ptr;
|
|
const VP8BandProbas* const* ac_proba;
|
|
VP8MBData* const block = dec->mb_data + dec->mb_x;
|
|
const VP8QuantMatrix* const q = &dec->dqm[block->segment];
|
|
int16_t* dst = block->coeffs;
|
|
VP8MB* const left_mb = dec->mb_info - 1;
|
|
uint8_t tnz, lnz;
|
|
uint32_t non_zero_y = 0;
|
|
uint32_t non_zero_uv = 0;
|
|
int x, y, ch;
|
|
uint32_t out_t_nz, out_l_nz;
|
|
int first;
|
|
|
|
WEBP_UNSAFE_MEMSET(dst, 0, 384 * sizeof(*dst));
|
|
if (!block->is_i4x4) { // parse DC
|
|
int16_t dc[16] = {0};
|
|
const int ctx = mb->nz_dc + left_mb->nz_dc;
|
|
const int nz = GetCoeffs(token_br, bands[1], ctx, q->y2_mat, 0, dc);
|
|
mb->nz_dc = left_mb->nz_dc = (nz > 0);
|
|
if (nz > 1) { // more than just the DC -> perform the full transform
|
|
VP8TransformWHT(dc, dst);
|
|
} else { // only DC is non-zero -> inlined simplified transform
|
|
int i;
|
|
const int dc0 = (dc[0] + 3) >> 3;
|
|
for (i = 0; i < 16 * 16; i += 16) dst[i] = dc0;
|
|
}
|
|
first = 1;
|
|
ac_proba = bands[0];
|
|
} else {
|
|
first = 0;
|
|
ac_proba = bands[3];
|
|
}
|
|
|
|
tnz = mb->nz & 0x0f;
|
|
lnz = left_mb->nz & 0x0f;
|
|
for (y = 0; y < 4; ++y) {
|
|
int l = lnz & 1;
|
|
uint32_t nz_coeffs = 0;
|
|
for (x = 0; x < 4; ++x) {
|
|
const int ctx = l + (tnz & 1);
|
|
const int nz = GetCoeffs(token_br, ac_proba, ctx, q->y1_mat, first, dst);
|
|
l = (nz > first);
|
|
tnz = (tnz >> 1) | (l << 7);
|
|
nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0);
|
|
dst += 16;
|
|
}
|
|
tnz >>= 4;
|
|
lnz = (lnz >> 1) | (l << 7);
|
|
non_zero_y = (non_zero_y << 8) | nz_coeffs;
|
|
}
|
|
out_t_nz = tnz;
|
|
out_l_nz = lnz >> 4;
|
|
|
|
for (ch = 0; ch < 4; ch += 2) {
|
|
uint32_t nz_coeffs = 0;
|
|
tnz = mb->nz >> (4 + ch);
|
|
lnz = left_mb->nz >> (4 + ch);
|
|
for (y = 0; y < 2; ++y) {
|
|
int l = lnz & 1;
|
|
for (x = 0; x < 2; ++x) {
|
|
const int ctx = l + (tnz & 1);
|
|
const int nz = GetCoeffs(token_br, bands[2], ctx, q->uv_mat, 0, dst);
|
|
l = (nz > 0);
|
|
tnz = (tnz >> 1) | (l << 3);
|
|
nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0);
|
|
dst += 16;
|
|
}
|
|
tnz >>= 2;
|
|
lnz = (lnz >> 1) | (l << 5);
|
|
}
|
|
// Note: we don't really need the per-4x4 details for U/V blocks.
|
|
non_zero_uv |= nz_coeffs << (4 * ch);
|
|
out_t_nz |= (tnz << 4) << ch;
|
|
out_l_nz |= (lnz & 0xf0) << ch;
|
|
}
|
|
mb->nz = out_t_nz;
|
|
left_mb->nz = out_l_nz;
|
|
|
|
block->non_zero_y = non_zero_y;
|
|
block->non_zero_uv = non_zero_uv;
|
|
|
|
// We look at the mode-code of each block and check if some blocks have less
|
|
// than three non-zero coeffs (code < 2). This is to avoid dithering flat and
|
|
// empty blocks.
|
|
block->dither = (non_zero_uv & 0xaaaa) ? 0 : q->dither;
|
|
|
|
return !(non_zero_y | non_zero_uv); // will be used for further optimization
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main loop
|
|
|
|
int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br) {
|
|
VP8MB* const left = dec->mb_info - 1;
|
|
VP8MB* const mb = dec->mb_info + dec->mb_x;
|
|
VP8MBData* const block = dec->mb_data + dec->mb_x;
|
|
int skip = dec->use_skip_proba ? block->skip : 0;
|
|
|
|
if (!skip) {
|
|
skip = ParseResiduals(dec, mb, token_br);
|
|
} else {
|
|
left->nz = mb->nz = 0;
|
|
if (!block->is_i4x4) {
|
|
left->nz_dc = mb->nz_dc = 0;
|
|
}
|
|
block->non_zero_y = 0;
|
|
block->non_zero_uv = 0;
|
|
block->dither = 0;
|
|
}
|
|
|
|
if (dec->filter_type > 0) { // store filter info
|
|
VP8FInfo* const finfo = dec->f_info + dec->mb_x;
|
|
*finfo = dec->fstrengths[block->segment][block->is_i4x4];
|
|
finfo->f_inner |= !skip;
|
|
}
|
|
|
|
return !token_br->eof;
|
|
}
|
|
|
|
void VP8InitScanline(VP8Decoder* const dec) {
|
|
VP8MB* const left = dec->mb_info - 1;
|
|
left->nz = 0;
|
|
left->nz_dc = 0;
|
|
WEBP_UNSAFE_MEMSET(dec->intra_l, B_DC_PRED, sizeof(dec->intra_l));
|
|
dec->mb_x = 0;
|
|
}
|
|
|
|
static int ParseFrame(VP8Decoder* const dec, VP8Io* io) {
|
|
for (dec->mb_y = 0; dec->mb_y < dec->br_mb_y; ++dec->mb_y) {
|
|
// Parse bitstream for this row.
|
|
VP8BitReader* const token_br =
|
|
&dec->parts[dec->mb_y & dec->num_parts_minus_one];
|
|
if (!VP8ParseIntraModeRow(&dec->br, dec)) {
|
|
return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
|
|
"Premature end-of-partition0 encountered.");
|
|
}
|
|
for (; dec->mb_x < dec->mb_w; ++dec->mb_x) {
|
|
if (!VP8DecodeMB(dec, token_br)) {
|
|
return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
|
|
"Premature end-of-file encountered.");
|
|
}
|
|
}
|
|
VP8InitScanline(dec); // Prepare for next scanline
|
|
|
|
// Reconstruct, filter and emit the row.
|
|
if (!VP8ProcessRow(dec, io)) {
|
|
return VP8SetError(dec, VP8_STATUS_USER_ABORT, "Output aborted.");
|
|
}
|
|
}
|
|
if (dec->mt_method > 0) {
|
|
if (!WebPGetWorkerInterface()->Sync(&dec->worker)) return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Main entry point
|
|
int VP8Decode(VP8Decoder* const dec, VP8Io* const io) {
|
|
int ok = 0;
|
|
if (dec == NULL) {
|
|
return 0;
|
|
}
|
|
if (io == NULL) {
|
|
return VP8SetError(dec, VP8_STATUS_INVALID_PARAM,
|
|
"NULL VP8Io parameter in VP8Decode().");
|
|
}
|
|
|
|
if (!dec->ready) {
|
|
if (!VP8GetHeaders(dec, io)) {
|
|
return 0;
|
|
}
|
|
}
|
|
assert(dec->ready);
|
|
|
|
// Finish setting up the decoding parameter. Will call io->setup().
|
|
ok = (VP8EnterCritical(dec, io) == VP8_STATUS_OK);
|
|
if (ok) { // good to go.
|
|
// Will allocate memory and prepare everything.
|
|
if (ok) ok = VP8InitFrame(dec, io);
|
|
|
|
// Main decoding loop
|
|
if (ok) ok = ParseFrame(dec, io);
|
|
|
|
// Exit.
|
|
ok &= VP8ExitCritical(dec, io);
|
|
}
|
|
|
|
if (!ok) {
|
|
VP8Clear(dec);
|
|
return 0;
|
|
}
|
|
|
|
dec->ready = 0;
|
|
return ok;
|
|
}
|
|
|
|
void VP8Clear(VP8Decoder* const dec) {
|
|
if (dec == NULL) {
|
|
return;
|
|
}
|
|
WebPGetWorkerInterface()->End(&dec->worker);
|
|
WebPDeallocateAlphaMemory(dec);
|
|
WebPSafeFree(dec->mem);
|
|
dec->mem = NULL;
|
|
dec->mem_size = 0;
|
|
WEBP_UNSAFE_MEMSET(&dec->br, 0, sizeof(dec->br));
|
|
dec->ready = 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dec/vp8l_dec.c */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// main entry for the decoder
|
|
//
|
|
// Authors: Vikas Arora (vikaas.arora@gmail.com)
|
|
// Jyrki Alakuijala (jyrki@google.com)
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* >>> src/dsp/lossless.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Image transforms and color space conversion methods for lossless decoder.
|
|
//
|
|
// Authors: Vikas Arora (vikaas.arora@gmail.com)
|
|
// Jyrki Alakuijala (jyrki@google.com)
|
|
|
|
#ifndef WEBP_DSP_LOSSLESS_H_
|
|
#define WEBP_DSP_LOSSLESS_H_
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Decoding
|
|
|
|
typedef uint32_t (*VP8LPredictorFunc)(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
extern VP8LPredictorFunc VP8LPredictors[16];
|
|
|
|
uint32_t VP8LPredictor2_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor3_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor4_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor5_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor6_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor7_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor8_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor9_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor10_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor11_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor12_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
uint32_t VP8LPredictor13_C(const uint32_t* const left,
|
|
const uint32_t* const top);
|
|
|
|
// These Add/Sub function expects upper[-1] and out[-1] to be readable.
|
|
typedef void (*VP8LPredictorAddSubFunc)(const uint32_t* in,
|
|
const uint32_t* upper, int num_pixels,
|
|
uint32_t* WEBP_RESTRICT out);
|
|
extern VP8LPredictorAddSubFunc VP8LPredictorsAdd[16];
|
|
extern VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16];
|
|
extern VP8LPredictorAddSubFunc VP8LPredictorsAdd_SSE[16];
|
|
|
|
typedef void (*VP8LProcessDecBlueAndRedFunc)(const uint32_t* src,
|
|
int num_pixels, uint32_t* dst);
|
|
extern VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed;
|
|
extern VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed_SSE;
|
|
|
|
typedef struct {
|
|
// Note: the members are uint8_t, so that any negative values are
|
|
// automatically converted to "mod 256" values.
|
|
uint8_t green_to_red;
|
|
uint8_t green_to_blue;
|
|
uint8_t red_to_blue;
|
|
} VP8LMultipliers;
|
|
typedef void (*VP8LTransformColorInverseFunc)(const VP8LMultipliers* const m,
|
|
const uint32_t* src,
|
|
int num_pixels, uint32_t* dst);
|
|
extern VP8LTransformColorInverseFunc VP8LTransformColorInverse;
|
|
extern VP8LTransformColorInverseFunc VP8LTransformColorInverse_SSE;
|
|
|
|
struct VP8LTransform; // Defined in dec/vp8li.h.
|
|
|
|
// Performs inverse transform of data given transform information, start and end
|
|
// rows. Transform will be applied to rows [row_start, row_end[.
|
|
// The *in and *out pointers refer to source and destination data respectively
|
|
// corresponding to the intermediate row (row_start).
|
|
void VP8LInverseTransform(const struct VP8LTransform* const transform,
|
|
int row_start, int row_end, const uint32_t* const in,
|
|
uint32_t* const out);
|
|
|
|
// Color space conversion.
|
|
typedef void (*VP8LConvertFunc)(const uint32_t* WEBP_RESTRICT src,
|
|
int num_pixels, uint8_t* WEBP_RESTRICT dst);
|
|
extern VP8LConvertFunc VP8LConvertBGRAToRGB;
|
|
extern VP8LConvertFunc VP8LConvertBGRAToRGBA;
|
|
extern VP8LConvertFunc VP8LConvertBGRAToRGBA4444;
|
|
extern VP8LConvertFunc VP8LConvertBGRAToRGB565;
|
|
extern VP8LConvertFunc VP8LConvertBGRAToBGR;
|
|
extern VP8LConvertFunc VP8LConvertBGRAToRGB_SSE;
|
|
extern VP8LConvertFunc VP8LConvertBGRAToRGBA_SSE;
|
|
|
|
// Converts from BGRA to other color spaces.
|
|
void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels,
|
|
WEBP_CSP_MODE out_colorspace, uint8_t* const rgba);
|
|
|
|
typedef void (*VP8LMapARGBFunc)(const uint32_t* src,
|
|
const uint32_t* const color_map, uint32_t* dst,
|
|
int y_start, int y_end, int width);
|
|
typedef void (*VP8LMapAlphaFunc)(const uint8_t* src,
|
|
const uint32_t* const color_map, uint8_t* dst,
|
|
int y_start, int y_end, int width);
|
|
|
|
extern VP8LMapARGBFunc VP8LMapColor32b;
|
|
extern VP8LMapAlphaFunc VP8LMapColor8b;
|
|
|
|
// Similar to the static method ColorIndexInverseTransform() that is part of
|
|
// lossless.c, but used only for alpha decoding. It takes uint8_t (rather than
|
|
// uint32_t) arguments for 'src' and 'dst'.
|
|
void VP8LColorIndexInverseTransformAlpha(
|
|
const struct VP8LTransform* const transform, int y_start, int y_end,
|
|
const uint8_t* src, uint8_t* dst);
|
|
|
|
// Expose some C-only fallback functions
|
|
void VP8LTransformColorInverse_C(const VP8LMultipliers* const m,
|
|
const uint32_t* src, int num_pixels,
|
|
uint32_t* dst);
|
|
|
|
void VP8LConvertBGRAToRGB_C(const uint32_t* WEBP_RESTRICT src, int num_pixels,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8LConvertBGRAToRGBA_C(const uint32_t* WEBP_RESTRICT src, int num_pixels,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8LConvertBGRAToRGBA4444_C(const uint32_t* WEBP_RESTRICT src,
|
|
int num_pixels, uint8_t* WEBP_RESTRICT dst);
|
|
void VP8LConvertBGRAToRGB565_C(const uint32_t* WEBP_RESTRICT src,
|
|
int num_pixels, uint8_t* WEBP_RESTRICT dst);
|
|
void VP8LConvertBGRAToBGR_C(const uint32_t* WEBP_RESTRICT src, int num_pixels,
|
|
uint8_t* WEBP_RESTRICT dst);
|
|
void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels,
|
|
uint32_t* dst);
|
|
|
|
// Must be called before calling any of the above methods.
|
|
void VP8LDspInit(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Encoding
|
|
|
|
typedef void (*VP8LProcessEncBlueAndRedFunc)(uint32_t* dst, int num_pixels);
|
|
extern VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed;
|
|
extern VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed_SSE;
|
|
typedef void (*VP8LTransformColorFunc)(
|
|
const VP8LMultipliers* WEBP_RESTRICT const m, uint32_t* WEBP_RESTRICT dst,
|
|
int num_pixels);
|
|
extern VP8LTransformColorFunc VP8LTransformColor;
|
|
extern VP8LTransformColorFunc VP8LTransformColor_SSE;
|
|
typedef void (*VP8LCollectColorBlueTransformsFunc)(
|
|
const uint32_t* WEBP_RESTRICT argb, int stride, int tile_width,
|
|
int tile_height, int green_to_blue, int red_to_blue, uint32_t histo[]);
|
|
extern VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms;
|
|
extern VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms_SSE;
|
|
|
|
typedef void (*VP8LCollectColorRedTransformsFunc)(
|
|
const uint32_t* WEBP_RESTRICT argb, int stride, int tile_width,
|
|
int tile_height, int green_to_red, uint32_t histo[]);
|
|
extern VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms;
|
|
extern VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms_SSE;
|
|
|
|
// Expose some C-only fallback functions
|
|
void VP8LTransformColor_C(const VP8LMultipliers* WEBP_RESTRICT const m,
|
|
uint32_t* WEBP_RESTRICT data, int num_pixels);
|
|
void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels);
|
|
void VP8LCollectColorRedTransforms_C(const uint32_t* WEBP_RESTRICT argb,
|
|
int stride, int tile_width,
|
|
int tile_height, int green_to_red,
|
|
uint32_t histo[]);
|
|
void VP8LCollectColorBlueTransforms_C(const uint32_t* WEBP_RESTRICT argb,
|
|
int stride, int tile_width,
|
|
int tile_height, int green_to_blue,
|
|
int red_to_blue, uint32_t histo[]);
|
|
|
|
extern VP8LPredictorAddSubFunc VP8LPredictorsSub[16];
|
|
extern VP8LPredictorAddSubFunc VP8LPredictorsSub_C[16];
|
|
extern VP8LPredictorAddSubFunc VP8LPredictorsSub_SSE[16];
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Huffman-cost related functions.
|
|
|
|
typedef uint32_t (*VP8LCostFunc)(const uint32_t* population, int length);
|
|
typedef uint64_t (*VP8LCombinedShannonEntropyFunc)(const uint32_t X[256],
|
|
const uint32_t Y[256]);
|
|
typedef uint64_t (*VP8LShannonEntropyFunc)(const uint32_t* X, int length);
|
|
|
|
extern VP8LCostFunc VP8LExtraCost;
|
|
extern VP8LCombinedShannonEntropyFunc VP8LCombinedShannonEntropy;
|
|
extern VP8LShannonEntropyFunc VP8LShannonEntropy;
|
|
|
|
typedef struct { // small struct to hold counters
|
|
int counts[2]; // index: 0=zero streak, 1=non-zero streak
|
|
int streaks[2][2]; // [zero/non-zero][streak<3 / streak>=3]
|
|
} VP8LStreaks;
|
|
|
|
typedef struct { // small struct to hold bit entropy results
|
|
uint64_t entropy; // entropy
|
|
uint32_t sum; // sum of the population
|
|
int nonzeros; // number of non-zero elements in the population
|
|
uint32_t max_val; // maximum value in the population
|
|
uint32_t nonzero_code; // index of the last non-zero in the population
|
|
} VP8LBitEntropy;
|
|
|
|
void VP8LBitEntropyInit(VP8LBitEntropy* const entropy);
|
|
|
|
// Get the combined symbol bit entropy and Huffman cost stats for the
|
|
// distributions 'X' and 'Y'. Those results can then be refined according to
|
|
// codec specific heuristics.
|
|
typedef void (*VP8LGetCombinedEntropyUnrefinedFunc)(
|
|
const uint32_t X[], const uint32_t Y[], int length,
|
|
VP8LBitEntropy* WEBP_RESTRICT const bit_entropy,
|
|
VP8LStreaks* WEBP_RESTRICT const stats);
|
|
extern VP8LGetCombinedEntropyUnrefinedFunc VP8LGetCombinedEntropyUnrefined;
|
|
|
|
// Get the entropy for the distribution 'X'.
|
|
typedef void (*VP8LGetEntropyUnrefinedFunc)(
|
|
const uint32_t X[], int length,
|
|
VP8LBitEntropy* WEBP_RESTRICT const bit_entropy,
|
|
VP8LStreaks* WEBP_RESTRICT const stats);
|
|
extern VP8LGetEntropyUnrefinedFunc VP8LGetEntropyUnrefined;
|
|
|
|
void VP8LBitsEntropyUnrefined(const uint32_t* WEBP_RESTRICT const array, int n,
|
|
VP8LBitEntropy* WEBP_RESTRICT const entropy);
|
|
|
|
typedef void (*VP8LAddVectorFunc)(const uint32_t* WEBP_RESTRICT a,
|
|
const uint32_t* WEBP_RESTRICT b,
|
|
uint32_t* WEBP_RESTRICT out, int size);
|
|
extern VP8LAddVectorFunc VP8LAddVector;
|
|
typedef void (*VP8LAddVectorEqFunc)(const uint32_t* WEBP_RESTRICT a,
|
|
uint32_t* WEBP_RESTRICT out, int size);
|
|
extern VP8LAddVectorEqFunc VP8LAddVectorEq;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// PrefixEncode()
|
|
|
|
typedef int (*VP8LVectorMismatchFunc)(const uint32_t* const array1,
|
|
const uint32_t* const array2, int length);
|
|
// Returns the first index where array1 and array2 are different.
|
|
extern VP8LVectorMismatchFunc VP8LVectorMismatch;
|
|
|
|
typedef void (*VP8LBundleColorMapFunc)(const uint8_t* WEBP_RESTRICT const row,
|
|
int width, int xbits,
|
|
uint32_t* WEBP_RESTRICT dst);
|
|
extern VP8LBundleColorMapFunc VP8LBundleColorMap;
|
|
extern VP8LBundleColorMapFunc VP8LBundleColorMap_SSE;
|
|
void VP8LBundleColorMap_C(const uint8_t* WEBP_RESTRICT const row, int width,
|
|
int xbits, uint32_t* WEBP_RESTRICT dst);
|
|
|
|
// Must be called before calling any of the above methods.
|
|
void VP8LEncDspInit(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DSP_LOSSLESS_H_
|
|
/* >>> src/dsp/lossless_common.h */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Image transforms and color space conversion methods for lossless decoder.
|
|
//
|
|
// Authors: Vikas Arora (vikaas.arora@gmail.com)
|
|
// Jyrki Alakuijala (jyrki@google.com)
|
|
// Vincent Rabaud (vrabaud@google.com)
|
|
|
|
#ifndef WEBP_DSP_LOSSLESS_COMMON_H_
|
|
#define WEBP_DSP_LOSSLESS_COMMON_H_
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Decoding
|
|
|
|
// color mapping related functions.
|
|
static WEBP_INLINE uint32_t VP8GetARGBIndex(uint32_t idx) {
|
|
return (idx >> 8) & 0xff;
|
|
}
|
|
|
|
static WEBP_INLINE uint8_t VP8GetAlphaIndex(uint8_t idx) { return idx; }
|
|
|
|
static WEBP_INLINE uint32_t VP8GetARGBValue(uint32_t val) { return val; }
|
|
|
|
static WEBP_INLINE uint8_t VP8GetAlphaValue(uint32_t val) {
|
|
return (val >> 8) & 0xff;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Misc methods.
|
|
|
|
// Computes sampled size of 'size' when sampling using 'sampling bits'.
|
|
static WEBP_INLINE uint32_t VP8LSubSampleSize(uint32_t size,
|
|
uint32_t sampling_bits) {
|
|
return (size + (1 << sampling_bits) - 1) >> sampling_bits;
|
|
}
|
|
|
|
// Converts near lossless quality into max number of bits shaved off.
|
|
static WEBP_INLINE int VP8LNearLosslessBits(int near_lossless_quality) {
|
|
// 100 -> 0
|
|
// 80..99 -> 1
|
|
// 60..79 -> 2
|
|
// 40..59 -> 3
|
|
// 20..39 -> 4
|
|
// 0..19 -> 5
|
|
return 5 - near_lossless_quality / 20;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Faster logarithm for integers. Small values use a look-up table.
|
|
|
|
// The threshold till approximate version of log_2 can be used.
|
|
// Practically, we can get rid of the call to log() as the two values match to
|
|
// very high degree (the ratio of these two is 0.99999x).
|
|
// Keeping a high threshold for now.
|
|
#define APPROX_LOG_WITH_CORRECTION_MAX 65536
|
|
#define APPROX_LOG_MAX 4096
|
|
// VP8LFastLog2 and VP8LFastSLog2 are used on elements from image histograms.
|
|
// The histogram values cannot exceed the maximum number of pixels, which
|
|
// is (1 << 14) * (1 << 14). Therefore S * log(S) < (1 << 33).
|
|
// No more than 32 bits of precision should be chosen.
|
|
// To match the original float implementation, 23 bits of precision are used.
|
|
#define LOG_2_PRECISION_BITS 23
|
|
#define LOG_2_RECIPROCAL 1.44269504088896338700465094007086
|
|
// LOG_2_RECIPROCAL * (1 << LOG_2_PRECISION_BITS)
|
|
#define LOG_2_RECIPROCAL_FIXED_DOUBLE 12102203.161561485379934310913085937500
|
|
#define LOG_2_RECIPROCAL_FIXED ((uint64_t)12102203)
|
|
#define LOG_LOOKUP_IDX_MAX 256
|
|
extern const uint32_t kLog2Table[LOG_LOOKUP_IDX_MAX];
|
|
extern const uint64_t kSLog2Table[LOG_LOOKUP_IDX_MAX];
|
|
typedef uint32_t (*VP8LFastLog2SlowFunc)(uint32_t v);
|
|
typedef uint64_t (*VP8LFastSLog2SlowFunc)(uint32_t v);
|
|
|
|
extern VP8LFastLog2SlowFunc VP8LFastLog2Slow;
|
|
extern VP8LFastSLog2SlowFunc VP8LFastSLog2Slow;
|
|
|
|
static WEBP_INLINE uint32_t VP8LFastLog2(uint32_t v) {
|
|
return (v < LOG_LOOKUP_IDX_MAX) ? kLog2Table[v] : VP8LFastLog2Slow(v);
|
|
}
|
|
// Fast calculation of v * log2(v) for integer input.
|
|
static WEBP_INLINE uint64_t VP8LFastSLog2(uint32_t v) {
|
|
return (v < LOG_LOOKUP_IDX_MAX) ? kSLog2Table[v] : VP8LFastSLog2Slow(v);
|
|
}
|
|
|
|
static WEBP_INLINE uint64_t RightShiftRound(uint64_t v, uint32_t shift) {
|
|
return (v + (1ull << shift >> 1)) >> shift;
|
|
}
|
|
|
|
static WEBP_INLINE int64_t DivRound(int64_t a, int64_t b) {
|
|
return ((a < 0) == (b < 0)) ? ((a + b / 2) / b) : ((a - b / 2) / b);
|
|
}
|
|
|
|
#define WEBP_INT64_MAX ((int64_t)((1ull << 63) - 1))
|
|
#define WEBP_UINT64_MAX (~0ull)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// PrefixEncode()
|
|
|
|
// Splitting of distance and length codes into prefixes and
|
|
// extra bits. The prefixes are encoded with an entropy code
|
|
// while the extra bits are stored just as normal bits.
|
|
static WEBP_INLINE void VP8LPrefixEncodeBitsNoLUT(int distance, int* const code,
|
|
int* const extra_bits) {
|
|
const int highest_bit = BitsLog2Floor(--distance);
|
|
const int second_highest_bit = (distance >> (highest_bit - 1)) & 1;
|
|
*extra_bits = highest_bit - 1;
|
|
*code = 2 * highest_bit + second_highest_bit;
|
|
}
|
|
|
|
static WEBP_INLINE void VP8LPrefixEncodeNoLUT(int distance, int* const code,
|
|
int* const extra_bits,
|
|
int* const extra_bits_value) {
|
|
const int highest_bit = BitsLog2Floor(--distance);
|
|
const int second_highest_bit = (distance >> (highest_bit - 1)) & 1;
|
|
*extra_bits = highest_bit - 1;
|
|
*extra_bits_value = distance & ((1 << *extra_bits) - 1);
|
|
*code = 2 * highest_bit + second_highest_bit;
|
|
}
|
|
|
|
#define PREFIX_LOOKUP_IDX_MAX 512
|
|
typedef struct {
|
|
int8_t code;
|
|
int8_t extra_bits;
|
|
} VP8LPrefixCode;
|
|
|
|
// These tables are derived using VP8LPrefixEncodeNoLUT.
|
|
extern const VP8LPrefixCode kPrefixEncodeCode[PREFIX_LOOKUP_IDX_MAX];
|
|
extern const uint8_t kPrefixEncodeExtraBitsValue[PREFIX_LOOKUP_IDX_MAX];
|
|
static WEBP_INLINE void VP8LPrefixEncodeBits(int distance, int* const code,
|
|
int* const extra_bits) {
|
|
if (distance < PREFIX_LOOKUP_IDX_MAX) {
|
|
const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance];
|
|
*code = prefix_code.code;
|
|
*extra_bits = prefix_code.extra_bits;
|
|
} else {
|
|
VP8LPrefixEncodeBitsNoLUT(distance, code, extra_bits);
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE void VP8LPrefixEncode(int distance, int* const code,
|
|
int* const extra_bits,
|
|
int* const extra_bits_value) {
|
|
if (distance < PREFIX_LOOKUP_IDX_MAX) {
|
|
const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance];
|
|
*code = prefix_code.code;
|
|
*extra_bits = prefix_code.extra_bits;
|
|
*extra_bits_value = kPrefixEncodeExtraBitsValue[distance];
|
|
} else {
|
|
VP8LPrefixEncodeNoLUT(distance, code, extra_bits, extra_bits_value);
|
|
}
|
|
}
|
|
|
|
// Sum of each component, mod 256.
|
|
static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE uint32_t
|
|
VP8LAddPixels(uint32_t a, uint32_t b) {
|
|
const uint32_t alpha_and_green = (a & 0xff00ff00u) + (b & 0xff00ff00u);
|
|
const uint32_t red_and_blue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu);
|
|
return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu);
|
|
}
|
|
|
|
// Difference of each component, mod 256.
|
|
static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE uint32_t
|
|
VP8LSubPixels(uint32_t a, uint32_t b) {
|
|
const uint32_t alpha_and_green =
|
|
0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u);
|
|
const uint32_t red_and_blue =
|
|
0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu);
|
|
return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Transform-related functions used in both encoding and decoding.
|
|
|
|
// Macros used to create a batch predictor that iteratively uses a
|
|
// one-pixel predictor.
|
|
|
|
// The predictor is added to the output pixel (which
|
|
// is therefore considered as a residual) to get the final prediction.
|
|
#define GENERATE_PREDICTOR_ADD(PREDICTOR, PREDICTOR_ADD) \
|
|
static void PREDICTOR_ADD(const uint32_t* in, const uint32_t* upper, \
|
|
int num_pixels, uint32_t* WEBP_RESTRICT out) { \
|
|
int x; \
|
|
assert(upper != NULL); \
|
|
for (x = 0; x < num_pixels; ++x) { \
|
|
const uint32_t pred = (PREDICTOR)(&out[x - 1], upper + x); \
|
|
out[x] = VP8LAddPixels(in[x], pred); \
|
|
} \
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_DSP_LOSSLESS_COMMON_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#define NUM_ARGB_CACHE_ROWS 16
|
|
|
|
static const int kCodeLengthLiterals = 16;
|
|
static const int kCodeLengthRepeatCode = 16;
|
|
static const uint8_t kCodeLengthExtraBits[3] = {2, 3, 7};
|
|
static const uint8_t kCodeLengthRepeatOffsets[3] = {3, 3, 11};
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Five Huffman codes are used at each meta code:
|
|
// 1. green + length prefix codes + color cache codes,
|
|
// 2. alpha,
|
|
// 3. red,
|
|
// 4. blue, and,
|
|
// 5. distance prefix codes.
|
|
typedef enum { GREEN = 0, RED = 1, BLUE = 2, ALPHA = 3, DIST = 4 } HuffIndex;
|
|
|
|
static const uint16_t kAlphabetSize[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 kLiteralMap[HUFFMAN_CODES_PER_META_CODE] = {0, 1, 1, 1, 0};
|
|
|
|
#define NUM_CODE_LENGTH_CODES 19
|
|
static const uint8_t kCodeLengthCodeOrder[NUM_CODE_LENGTH_CODES] = {
|
|
17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
|
|
|
#define CODE_TO_PLANE_CODES 120
|
|
static const uint8_t kCodeToPlane[CODE_TO_PLANE_CODES] = {
|
|
0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, 0x26, 0x2a,
|
|
0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, 0x25, 0x2b, 0x48, 0x04,
|
|
0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45,
|
|
0x4b, 0x34, 0x3c, 0x03, 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d,
|
|
0x44, 0x4c, 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e,
|
|
0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, 0x32, 0x3e,
|
|
0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, 0x64, 0x6c, 0x42, 0x4e,
|
|
0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e,
|
|
0x00, 0x74, 0x7c, 0x41, 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d,
|
|
0x51, 0x5f, 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70};
|
|
|
|
// Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha
|
|
// and distance alphabets are constant (256 for red, blue and alpha, 40 for
|
|
// distance) and lookup table sizes for them in worst case are 630 and 410
|
|
// respectively. Size of green alphabet depends on color cache size and is equal
|
|
// to 256 (green component values) + 24 (length prefix values)
|
|
// + color_cache_size (between 0 and 2048).
|
|
// All values computed for 8-bit first level lookup with Mark Adler's tool:
|
|
// https://github.com/madler/zlib/blob/v1.2.5/examples/enough.c
|
|
#define FIXED_TABLE_SIZE (630 * 3 + 410)
|
|
static const uint16_t kTableSize[12] = {
|
|
FIXED_TABLE_SIZE + 654, FIXED_TABLE_SIZE + 656, FIXED_TABLE_SIZE + 658,
|
|
FIXED_TABLE_SIZE + 662, FIXED_TABLE_SIZE + 670, FIXED_TABLE_SIZE + 686,
|
|
FIXED_TABLE_SIZE + 718, FIXED_TABLE_SIZE + 782, FIXED_TABLE_SIZE + 912,
|
|
FIXED_TABLE_SIZE + 1168, FIXED_TABLE_SIZE + 1680, FIXED_TABLE_SIZE + 2704};
|
|
|
|
static int VP8LSetError(VP8LDecoder* const dec, VP8StatusCode error) {
|
|
// The oldest error reported takes precedence over the new one.
|
|
if (dec->status == VP8_STATUS_OK || dec->status == VP8_STATUS_SUSPENDED) {
|
|
dec->status = error;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int DecodeImageStream(int xsize, int ysize, int is_level0,
|
|
VP8LDecoder* const dec,
|
|
uint32_t** const decoded_data);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int VP8LCheckSignature(const uint8_t* const WEBP_COUNTED_BY(size) data,
|
|
size_t size) {
|
|
return (size >= VP8L_FRAME_HEADER_SIZE && data[0] == VP8L_MAGIC_BYTE &&
|
|
(data[4] >> 5) == 0); // version
|
|
}
|
|
|
|
static int ReadImageInfo(VP8LBitReader* const br, int* const width,
|
|
int* const height, int* const has_alpha) {
|
|
if (VP8LReadBits(br, 8) != VP8L_MAGIC_BYTE) return 0;
|
|
*width = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1;
|
|
*height = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1;
|
|
*has_alpha = VP8LReadBits(br, 1);
|
|
if (VP8LReadBits(br, VP8L_VERSION_BITS) != 0) return 0;
|
|
return !br->eos;
|
|
}
|
|
|
|
int VP8LGetInfo(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* const width, int* const height,
|
|
int* const has_alpha) {
|
|
if (data == NULL || data_size < VP8L_FRAME_HEADER_SIZE) {
|
|
return 0; // not enough data
|
|
} else if (!VP8LCheckSignature(data, data_size)) {
|
|
return 0; // bad signature
|
|
} else {
|
|
int w, h, a;
|
|
VP8LBitReader br;
|
|
VP8LInitBitReader(&br, data, data_size);
|
|
if (!ReadImageInfo(&br, &w, &h, &a)) {
|
|
return 0;
|
|
}
|
|
if (width != NULL) *width = w;
|
|
if (height != NULL) *height = h;
|
|
if (has_alpha != NULL) *has_alpha = a;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static WEBP_INLINE int GetCopyDistance(int distance_symbol,
|
|
VP8LBitReader* const br) {
|
|
int extra_bits, offset;
|
|
if (distance_symbol < 4) {
|
|
return distance_symbol + 1;
|
|
}
|
|
extra_bits = (distance_symbol - 2) >> 1;
|
|
offset = (2 + (distance_symbol & 1)) << extra_bits;
|
|
return offset + VP8LReadBits(br, extra_bits) + 1;
|
|
}
|
|
|
|
static WEBP_INLINE int GetCopyLength(int length_symbol,
|
|
VP8LBitReader* const br) {
|
|
// Length and distance prefixes are encoded the same way.
|
|
return GetCopyDistance(length_symbol, br);
|
|
}
|
|
|
|
static WEBP_INLINE int PlaneCodeToDistance(int xsize, int plane_code) {
|
|
if (plane_code > CODE_TO_PLANE_CODES) {
|
|
return plane_code - CODE_TO_PLANE_CODES;
|
|
} else {
|
|
const int dist_code = kCodeToPlane[plane_code - 1];
|
|
const int yoffset = dist_code >> 4;
|
|
const int xoffset = 8 - (dist_code & 0xf);
|
|
const int dist = yoffset * xsize + xoffset;
|
|
return (dist >= 1) ? dist : 1; // dist<1 can happen if xsize is very small
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Decodes the next Huffman code from bit-stream.
|
|
// VP8LFillBitWindow(br) needs to be called at minimum every second call
|
|
// to ReadSymbol, in order to pre-fetch enough bits.
|
|
static WEBP_INLINE int ReadSymbol(const HuffmanCode* table,
|
|
VP8LBitReader* const br) {
|
|
int nbits;
|
|
uint32_t val = VP8LPrefetchBits(br);
|
|
table += val & HUFFMAN_TABLE_MASK;
|
|
nbits = table->bits - HUFFMAN_TABLE_BITS;
|
|
if (nbits > 0) {
|
|
VP8LSetBitPos(br, br->bit_pos + HUFFMAN_TABLE_BITS);
|
|
val = VP8LPrefetchBits(br);
|
|
table += table->value;
|
|
table += val & ((1 << nbits) - 1);
|
|
}
|
|
VP8LSetBitPos(br, br->bit_pos + table->bits);
|
|
return table->value;
|
|
}
|
|
|
|
// Reads packed symbol depending on GREEN channel
|
|
#define BITS_SPECIAL_MARKER 0x100 // something large enough (and a bit-mask)
|
|
#define PACKED_NON_LITERAL_CODE 0 // must be < NUM_LITERAL_CODES
|
|
static WEBP_INLINE int ReadPackedSymbols(const HTreeGroup* group,
|
|
VP8LBitReader* const br,
|
|
uint32_t* const dst) {
|
|
const uint32_t val = VP8LPrefetchBits(br) & (HUFFMAN_PACKED_TABLE_SIZE - 1);
|
|
const HuffmanCode32 code = group->packed_table[val];
|
|
assert(group->use_packed_table);
|
|
if (code.bits < BITS_SPECIAL_MARKER) {
|
|
VP8LSetBitPos(br, br->bit_pos + code.bits);
|
|
*dst = code.value;
|
|
return PACKED_NON_LITERAL_CODE;
|
|
} else {
|
|
VP8LSetBitPos(br, br->bit_pos + code.bits - BITS_SPECIAL_MARKER);
|
|
assert(code.value >= NUM_LITERAL_CODES);
|
|
return code.value;
|
|
}
|
|
}
|
|
|
|
static int AccumulateHCode(HuffmanCode hcode, int shift,
|
|
HuffmanCode32* const huff) {
|
|
huff->bits += hcode.bits;
|
|
huff->value |= (uint32_t)hcode.value << shift;
|
|
assert(huff->bits <= HUFFMAN_TABLE_BITS);
|
|
return hcode.bits;
|
|
}
|
|
|
|
static void BuildPackedTable(HTreeGroup* const htree_group) {
|
|
uint32_t code;
|
|
for (code = 0; code < HUFFMAN_PACKED_TABLE_SIZE; ++code) {
|
|
uint32_t bits = code;
|
|
HuffmanCode32* const huff = &htree_group->packed_table[bits];
|
|
HuffmanCode hcode = htree_group->htrees[GREEN][bits];
|
|
if (hcode.value >= NUM_LITERAL_CODES) {
|
|
huff->bits = hcode.bits + BITS_SPECIAL_MARKER;
|
|
huff->value = hcode.value;
|
|
} else {
|
|
huff->bits = 0;
|
|
huff->value = 0;
|
|
bits >>= AccumulateHCode(hcode, 8, huff);
|
|
bits >>= AccumulateHCode(htree_group->htrees[RED][bits], 16, huff);
|
|
bits >>= AccumulateHCode(htree_group->htrees[BLUE][bits], 0, huff);
|
|
bits >>= AccumulateHCode(htree_group->htrees[ALPHA][bits], 24, huff);
|
|
(void)bits;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int ReadHuffmanCodeLengths(VP8LDecoder* const dec,
|
|
const int* const code_length_code_lengths,
|
|
int num_symbols, int* const code_lengths) {
|
|
int ok = 0;
|
|
VP8LBitReader* const br = &dec->br;
|
|
int symbol;
|
|
int max_symbol;
|
|
int prev_code_len = DEFAULT_CODE_LENGTH;
|
|
HuffmanTables tables;
|
|
const int* WEBP_BIDI_INDEXABLE const bounded_code_lengths =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
const int*, code_length_code_lengths,
|
|
NUM_CODE_LENGTH_CODES * sizeof(*code_length_code_lengths));
|
|
|
|
if (!VP8LHuffmanTablesAllocate(1 << LENGTHS_TABLE_BITS, &tables) ||
|
|
!VP8LBuildHuffmanTable(&tables, LENGTHS_TABLE_BITS, bounded_code_lengths,
|
|
NUM_CODE_LENGTH_CODES)) {
|
|
goto End;
|
|
}
|
|
|
|
if (VP8LReadBits(br, 1)) { // use length
|
|
const int length_nbits = 2 + 2 * VP8LReadBits(br, 3);
|
|
max_symbol = 2 + VP8LReadBits(br, length_nbits);
|
|
if (max_symbol > num_symbols) {
|
|
goto End;
|
|
}
|
|
} else {
|
|
max_symbol = num_symbols;
|
|
}
|
|
|
|
symbol = 0;
|
|
while (symbol < num_symbols) {
|
|
const HuffmanCode* p;
|
|
int code_len;
|
|
if (max_symbol-- == 0) break;
|
|
VP8LFillBitWindow(br);
|
|
p = &tables.curr_segment->start[VP8LPrefetchBits(br) & LENGTHS_TABLE_MASK];
|
|
VP8LSetBitPos(br, br->bit_pos + p->bits);
|
|
code_len = p->value;
|
|
if (code_len < kCodeLengthLiterals) {
|
|
code_lengths[symbol++] = code_len;
|
|
if (code_len != 0) prev_code_len = code_len;
|
|
} else {
|
|
const int use_prev = (code_len == kCodeLengthRepeatCode);
|
|
const int slot = code_len - kCodeLengthLiterals;
|
|
const int extra_bits = kCodeLengthExtraBits[slot];
|
|
const int repeat_offset = kCodeLengthRepeatOffsets[slot];
|
|
int repeat = VP8LReadBits(br, extra_bits) + repeat_offset;
|
|
if (symbol + repeat > num_symbols) {
|
|
goto End;
|
|
} else {
|
|
const int length = use_prev ? prev_code_len : 0;
|
|
while (repeat-- > 0) code_lengths[symbol++] = length;
|
|
}
|
|
}
|
|
}
|
|
ok = 1;
|
|
|
|
End:
|
|
VP8LHuffmanTablesDeallocate(&tables);
|
|
if (!ok) return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR);
|
|
return ok;
|
|
}
|
|
|
|
// 'code_lengths' is pre-allocated temporary buffer, used for creating Huffman
|
|
// tree.
|
|
static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec,
|
|
int* const code_lengths,
|
|
HuffmanTables* const table) {
|
|
int ok = 0;
|
|
int size = 0;
|
|
VP8LBitReader* const br = &dec->br;
|
|
const int simple_code = VP8LReadBits(br, 1);
|
|
|
|
WEBP_UNSAFE_MEMSET(code_lengths, 0, alphabet_size * sizeof(*code_lengths));
|
|
|
|
if (simple_code) { // Read symbols, codes & code lengths directly.
|
|
const int num_symbols = VP8LReadBits(br, 1) + 1;
|
|
const int first_symbol_len_code = VP8LReadBits(br, 1);
|
|
// The first code is either 1 bit or 8 bit code.
|
|
int symbol = VP8LReadBits(br, (first_symbol_len_code == 0) ? 1 : 8);
|
|
code_lengths[symbol] = 1;
|
|
// The second code (if present), is always 8 bits long.
|
|
if (num_symbols == 2) {
|
|
symbol = VP8LReadBits(br, 8);
|
|
code_lengths[symbol] = 1;
|
|
}
|
|
ok = 1;
|
|
} else { // Decode Huffman-coded code lengths.
|
|
int i;
|
|
int code_length_code_lengths[NUM_CODE_LENGTH_CODES] = {0};
|
|
const int num_codes = VP8LReadBits(br, 4) + 4;
|
|
assert(num_codes <= NUM_CODE_LENGTH_CODES);
|
|
|
|
for (i = 0; i < num_codes; ++i) {
|
|
code_length_code_lengths[kCodeLengthCodeOrder[i]] = VP8LReadBits(br, 3);
|
|
}
|
|
ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths, alphabet_size,
|
|
code_lengths);
|
|
}
|
|
|
|
ok = ok && !br->eos;
|
|
if (ok) {
|
|
const int* WEBP_BIDI_INDEXABLE const bounded_code_lengths =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const int*, code_lengths,
|
|
alphabet_size * sizeof(int));
|
|
size = VP8LBuildHuffmanTable(table, HUFFMAN_TABLE_BITS,
|
|
bounded_code_lengths, alphabet_size);
|
|
}
|
|
if (!ok || size == 0) {
|
|
return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,
|
|
int color_cache_bits, int allow_recursion) {
|
|
int i;
|
|
VP8LBitReader* const br = &dec->br;
|
|
VP8LMetadata* const hdr = &dec->hdr;
|
|
uint32_t* huffman_image = NULL;
|
|
HTreeGroup* htree_groups = NULL;
|
|
HuffmanTables* huffman_tables = &hdr->huffman_tables;
|
|
int num_htree_groups = 1;
|
|
int num_htree_groups_max = 1;
|
|
int* mapping = NULL;
|
|
int ok = 0;
|
|
|
|
// Check the table has been 0 initialized (through InitMetadata).
|
|
assert(huffman_tables->root.start == NULL);
|
|
assert(huffman_tables->curr_segment == NULL);
|
|
|
|
if (allow_recursion && VP8LReadBits(br, 1)) {
|
|
// use meta Huffman codes.
|
|
const int huffman_precision =
|
|
MIN_HUFFMAN_BITS + VP8LReadBits(br, NUM_HUFFMAN_BITS);
|
|
const int huffman_xsize = VP8LSubSampleSize(xsize, huffman_precision);
|
|
const int huffman_ysize = VP8LSubSampleSize(ysize, huffman_precision);
|
|
const int huffman_pixs = huffman_xsize * huffman_ysize;
|
|
if (!DecodeImageStream(huffman_xsize, huffman_ysize, /*is_level0=*/0, dec,
|
|
&huffman_image)) {
|
|
goto Error;
|
|
}
|
|
hdr->huffman_subsample_bits = huffman_precision;
|
|
for (i = 0; i < huffman_pixs; ++i) {
|
|
// The huffman data is stored in red and green bytes.
|
|
const int group = (huffman_image[i] >> 8) & 0xffff;
|
|
huffman_image[i] = group;
|
|
if (group >= num_htree_groups_max) {
|
|
num_htree_groups_max = group + 1;
|
|
}
|
|
}
|
|
// Check the validity of num_htree_groups_max. If it seems too big, use a
|
|
// smaller value for later. This will prevent big memory allocations to end
|
|
// up with a bad bitstream anyway.
|
|
// The value of 1000 is totally arbitrary. We know that num_htree_groups_max
|
|
// is smaller than (1 << 16) and should be smaller than the number of pixels
|
|
// (though the format allows it to be bigger).
|
|
if (num_htree_groups_max > 1000 || num_htree_groups_max > xsize * ysize) {
|
|
// Create a mapping from the used indices to the minimal set of used
|
|
// values [0, num_htree_groups)
|
|
mapping = (int*)WebPSafeMalloc(num_htree_groups_max, sizeof(*mapping));
|
|
if (mapping == NULL) {
|
|
VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
goto Error;
|
|
}
|
|
// -1 means a value is unmapped, and therefore unused in the Huffman
|
|
// image.
|
|
WEBP_UNSAFE_MEMSET(mapping, 0xff,
|
|
num_htree_groups_max * sizeof(*mapping));
|
|
for (num_htree_groups = 0, i = 0; i < huffman_pixs; ++i) {
|
|
// Get the current mapping for the group and remap the Huffman image.
|
|
int* const mapped_group = &mapping[huffman_image[i]];
|
|
if (*mapped_group == -1) *mapped_group = num_htree_groups++;
|
|
huffman_image[i] = *mapped_group;
|
|
}
|
|
} else {
|
|
num_htree_groups = num_htree_groups_max;
|
|
}
|
|
}
|
|
|
|
if (br->eos) goto Error;
|
|
|
|
if (!ReadHuffmanCodesHelper(color_cache_bits, num_htree_groups,
|
|
num_htree_groups_max, mapping, dec,
|
|
huffman_tables, &htree_groups)) {
|
|
goto Error;
|
|
}
|
|
ok = 1;
|
|
|
|
// All OK. Finalize pointers.
|
|
hdr->huffman_image = huffman_image;
|
|
hdr->num_htree_groups = num_htree_groups;
|
|
hdr->htree_groups = htree_groups;
|
|
|
|
Error:
|
|
WebPSafeFree(mapping);
|
|
if (!ok) {
|
|
WebPSafeFree(huffman_image);
|
|
VP8LHuffmanTablesDeallocate(huffman_tables);
|
|
VP8LHtreeGroupsFree(htree_groups);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
int ReadHuffmanCodesHelper(int color_cache_bits, int num_htree_groups,
|
|
int num_htree_groups_max, const int* const mapping,
|
|
VP8LDecoder* const dec,
|
|
HuffmanTables* const huffman_tables,
|
|
HTreeGroup** const htree_groups) {
|
|
int i, j, ok = 0;
|
|
const int max_alphabet_size =
|
|
kAlphabetSize[0] + ((color_cache_bits > 0) ? 1 << color_cache_bits : 0);
|
|
const int table_size = kTableSize[color_cache_bits];
|
|
int* code_lengths = NULL;
|
|
int total_huffman_table_size;
|
|
|
|
if ((mapping == NULL && num_htree_groups != num_htree_groups_max) ||
|
|
num_htree_groups > num_htree_groups_max) {
|
|
goto Error;
|
|
}
|
|
|
|
code_lengths =
|
|
(int*)WebPSafeCalloc((uint64_t)max_alphabet_size, sizeof(*code_lengths));
|
|
*htree_groups = VP8LHtreeGroupsNew(num_htree_groups);
|
|
|
|
// MAX_HUFF_IMAGE_SIZE is above what the libwebp encoder allows so something
|
|
// fishy might be happening. Do not allocate too much yet.
|
|
total_huffman_table_size =
|
|
(num_htree_groups_max > MAX_HUFF_IMAGE_SIZE ? MAX_HUFF_IMAGE_SIZE
|
|
: num_htree_groups) *
|
|
table_size;
|
|
if (*htree_groups == NULL || code_lengths == NULL ||
|
|
!VP8LHuffmanTablesAllocate(total_huffman_table_size, huffman_tables)) {
|
|
VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
goto Error;
|
|
}
|
|
|
|
for (i = 0; i < num_htree_groups_max; ++i) {
|
|
// If the index "i" is unused in the Huffman image, just make sure the
|
|
// coefficients are valid but do not store them.
|
|
if (mapping != NULL && mapping[i] == -1) {
|
|
for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
|
|
int alphabet_size = kAlphabetSize[j];
|
|
if (j == 0 && color_cache_bits > 0) {
|
|
alphabet_size += (1 << color_cache_bits);
|
|
}
|
|
// Passing in NULL so that nothing gets filled.
|
|
if (!ReadHuffmanCode(alphabet_size, dec, code_lengths, NULL)) {
|
|
goto Error;
|
|
}
|
|
}
|
|
} else {
|
|
HTreeGroup* const htree_group =
|
|
&(*htree_groups)[(mapping == NULL) ? i : mapping[i]];
|
|
HuffmanCode** const htrees = htree_group->htrees;
|
|
int size;
|
|
int total_size = 0;
|
|
int is_trivial_literal = 1;
|
|
int max_bits = 0;
|
|
for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
|
|
int alphabet_size = kAlphabetSize[j];
|
|
if (j == 0 && color_cache_bits > 0) {
|
|
alphabet_size += (1 << color_cache_bits);
|
|
}
|
|
size =
|
|
ReadHuffmanCode(alphabet_size, dec, code_lengths, huffman_tables);
|
|
htrees[j] = huffman_tables->curr_segment->curr_table;
|
|
if (size == 0) {
|
|
goto Error;
|
|
}
|
|
if (is_trivial_literal && kLiteralMap[j] == 1) {
|
|
is_trivial_literal = (htrees[j]->bits == 0);
|
|
}
|
|
total_size += htrees[j]->bits;
|
|
huffman_tables->curr_segment->curr_table += size;
|
|
if (j <= ALPHA) {
|
|
int local_max_bits = code_lengths[0];
|
|
int k;
|
|
for (k = 1; k < alphabet_size; ++k) {
|
|
if (code_lengths[k] > local_max_bits) {
|
|
local_max_bits = code_lengths[k];
|
|
}
|
|
}
|
|
max_bits += local_max_bits;
|
|
}
|
|
}
|
|
htree_group->is_trivial_literal = is_trivial_literal;
|
|
htree_group->is_trivial_code = 0;
|
|
if (is_trivial_literal) {
|
|
const int red = htrees[RED][0].value;
|
|
const int blue = htrees[BLUE][0].value;
|
|
const int alpha = htrees[ALPHA][0].value;
|
|
htree_group->literal_arb = ((uint32_t)alpha << 24) | (red << 16) | blue;
|
|
if (total_size == 0 && htrees[GREEN][0].value < NUM_LITERAL_CODES) {
|
|
htree_group->is_trivial_code = 1;
|
|
htree_group->literal_arb |= htrees[GREEN][0].value << 8;
|
|
}
|
|
}
|
|
htree_group->use_packed_table =
|
|
!htree_group->is_trivial_code && (max_bits < HUFFMAN_PACKED_BITS);
|
|
if (htree_group->use_packed_table) BuildPackedTable(htree_group);
|
|
}
|
|
}
|
|
ok = 1;
|
|
|
|
Error:
|
|
WebPSafeFree(code_lengths);
|
|
if (!ok) {
|
|
VP8LHuffmanTablesDeallocate(huffman_tables);
|
|
VP8LHtreeGroupsFree(*htree_groups);
|
|
*htree_groups = NULL;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Scaling.
|
|
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
static int AllocateAndInitRescaler(VP8LDecoder* const dec, VP8Io* const io) {
|
|
const int num_channels = 4;
|
|
const int in_width = io->mb_w;
|
|
const int out_width = io->scaled_width;
|
|
const int in_height = io->mb_h;
|
|
const int out_height = io->scaled_height;
|
|
const uint64_t work_size = 2 * num_channels * (uint64_t)out_width;
|
|
rescaler_t* WEBP_BIDI_INDEXABLE work; // Rescaler work area.
|
|
const uint64_t scaled_data_size = (uint64_t)out_width;
|
|
uint32_t* WEBP_BIDI_INDEXABLE
|
|
scaled_data; // Temporary storage for scaled BGRA data.
|
|
const uint64_t memory_size = sizeof(*dec->rescaler) +
|
|
work_size * sizeof(*work) +
|
|
scaled_data_size * sizeof(*scaled_data);
|
|
uint8_t* WEBP_BIDI_INDEXABLE memory =
|
|
(uint8_t*)WebPSafeMalloc(memory_size, sizeof(*memory));
|
|
if (memory == NULL) {
|
|
return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
}
|
|
assert(dec->rescaler_memory == NULL);
|
|
dec->rescaler_memory = memory;
|
|
|
|
dec->rescaler = (WebPRescaler*)memory;
|
|
memory += sizeof(*dec->rescaler);
|
|
work = (rescaler_t*)memory;
|
|
memory += work_size * sizeof(*work);
|
|
scaled_data = (uint32_t*)memory;
|
|
|
|
if (!WebPRescalerInit(dec->rescaler, in_width, in_height,
|
|
(uint8_t*)scaled_data, out_width, out_height, 0,
|
|
num_channels, work)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
#endif // WEBP_REDUCE_SIZE
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Export to ARGB
|
|
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
|
|
// We have special "export" function since we need to convert from BGRA
|
|
static int Export(WebPRescaler* const rescaler, WEBP_CSP_MODE colorspace,
|
|
int rgba_stride, uint8_t* const rgba) {
|
|
uint32_t* const src = (uint32_t*)rescaler->dst;
|
|
uint8_t* dst = rgba;
|
|
const int dst_width = rescaler->dst_width;
|
|
int num_lines_out = 0;
|
|
while (WebPRescalerHasPendingOutput(rescaler)) {
|
|
WebPRescalerExportRow(rescaler);
|
|
WebPMultARGBRow(src, dst_width, 1);
|
|
VP8LConvertFromBGRA(src, dst_width, colorspace, dst);
|
|
dst += rgba_stride;
|
|
++num_lines_out;
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
// Emit scaled rows.
|
|
static int EmitRescaledRowsRGBA(const VP8LDecoder* const dec, uint8_t* in,
|
|
int in_stride, int mb_h, uint8_t* const out,
|
|
int out_stride) {
|
|
const WEBP_CSP_MODE colorspace = dec->output->colorspace;
|
|
int num_lines_in = 0;
|
|
int num_lines_out = 0;
|
|
while (num_lines_in < mb_h) {
|
|
uint8_t* const row_in = in + (ptrdiff_t)num_lines_in * in_stride;
|
|
uint8_t* const row_out = out + (ptrdiff_t)num_lines_out * out_stride;
|
|
const int lines_left = mb_h - num_lines_in;
|
|
const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left);
|
|
int lines_imported;
|
|
assert(needed_lines > 0 && needed_lines <= lines_left);
|
|
WebPMultARGBRows(row_in, in_stride, dec->rescaler->src_width, needed_lines,
|
|
0);
|
|
lines_imported =
|
|
WebPRescalerImport(dec->rescaler, lines_left, row_in, in_stride);
|
|
assert(lines_imported == needed_lines);
|
|
num_lines_in += lines_imported;
|
|
num_lines_out += Export(dec->rescaler, colorspace, out_stride, row_out);
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
#endif // WEBP_REDUCE_SIZE
|
|
|
|
// Emit rows without any scaling.
|
|
static int EmitRows(WEBP_CSP_MODE colorspace, const uint8_t* row_in,
|
|
int in_stride, int mb_w, int mb_h, uint8_t* const out,
|
|
int out_stride) {
|
|
int lines = mb_h;
|
|
uint8_t* row_out = out;
|
|
while (lines-- > 0) {
|
|
VP8LConvertFromBGRA((const uint32_t*)row_in, mb_w, colorspace, row_out);
|
|
row_in += in_stride;
|
|
row_out += out_stride;
|
|
}
|
|
return mb_h; // Num rows out == num rows in.
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Export to YUVA
|
|
|
|
static void ConvertToYUVA(const uint32_t* const src, int width, int y_pos,
|
|
const WebPDecBuffer* const output) {
|
|
const WebPYUVABuffer* const buf = &output->u.YUVA;
|
|
|
|
// first, the luma plane
|
|
WebPConvertARGBToY(src, buf->y + (ptrdiff_t)y_pos * buf->y_stride, width);
|
|
|
|
// then U/V planes
|
|
{
|
|
uint8_t* const u = buf->u + (ptrdiff_t)(y_pos >> 1) * buf->u_stride;
|
|
uint8_t* const v = buf->v + (ptrdiff_t)(y_pos >> 1) * buf->v_stride;
|
|
// even lines: store values
|
|
// odd lines: average with previous values
|
|
WebPConvertARGBToUV(src, u, v, width, !(y_pos & 1));
|
|
}
|
|
// Lastly, store alpha if needed.
|
|
if (buf->a != NULL) {
|
|
uint8_t* const a = buf->a + (ptrdiff_t)y_pos * buf->a_stride;
|
|
#if defined(WORDS_BIGENDIAN)
|
|
WebPExtractAlpha((uint8_t*)src + 0, 0, width, 1, a, 0);
|
|
#else
|
|
WebPExtractAlpha((uint8_t*)src + 3, 0, width, 1, a, 0);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static int ExportYUVA(const VP8LDecoder* const dec, int y_pos) {
|
|
WebPRescaler* const rescaler = dec->rescaler;
|
|
uint32_t* const src = (uint32_t*)rescaler->dst;
|
|
const int dst_width = rescaler->dst_width;
|
|
int num_lines_out = 0;
|
|
while (WebPRescalerHasPendingOutput(rescaler)) {
|
|
WebPRescalerExportRow(rescaler);
|
|
WebPMultARGBRow(src, dst_width, 1);
|
|
ConvertToYUVA(src, dst_width, y_pos, dec->output);
|
|
++y_pos;
|
|
++num_lines_out;
|
|
}
|
|
return num_lines_out;
|
|
}
|
|
|
|
static int EmitRescaledRowsYUVA(const VP8LDecoder* const dec, uint8_t* in,
|
|
int in_stride, int mb_h) {
|
|
int num_lines_in = 0;
|
|
int y_pos = dec->last_out_row;
|
|
while (num_lines_in < mb_h) {
|
|
const int lines_left = mb_h - num_lines_in;
|
|
const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left);
|
|
int lines_imported;
|
|
WebPMultARGBRows(in, in_stride, dec->rescaler->src_width, needed_lines, 0);
|
|
lines_imported =
|
|
WebPRescalerImport(dec->rescaler, lines_left, in, in_stride);
|
|
assert(lines_imported == needed_lines);
|
|
num_lines_in += lines_imported;
|
|
in += (ptrdiff_t)needed_lines * in_stride;
|
|
y_pos += ExportYUVA(dec, y_pos);
|
|
}
|
|
return y_pos;
|
|
}
|
|
|
|
// Returns true if alpha[] has non-0xff values.
|
|
static int CheckNonOpaque(const uint8_t* alpha, int width, int height,
|
|
int y_step) {
|
|
WebPInitAlphaProcessing();
|
|
for (; height-- > 0; alpha += y_step) {
|
|
if (WebPHasAlpha8b(alpha, width)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int EmitRowsYUVA(const uint8_t* const in, const VP8Io* const io,
|
|
int in_stride, uint16_t* tmp_rgb,
|
|
VP8LDecoder* const dec) {
|
|
int y_pos = dec->last_out_row;
|
|
const int width = io->mb_w;
|
|
int num_rows = io->mb_h;
|
|
const int y_pos_final = y_pos + num_rows;
|
|
const int y_stride = dec->output->u.YUVA.y_stride;
|
|
const int uv_stride = dec->output->u.YUVA.u_stride;
|
|
const int a_stride = dec->output->u.YUVA.a_stride;
|
|
uint8_t* dst_a = dec->output->u.YUVA.a;
|
|
uint8_t* dst_y = dec->output->u.YUVA.y + (ptrdiff_t)y_pos * y_stride;
|
|
uint8_t* dst_u = dec->output->u.YUVA.u + (ptrdiff_t)(y_pos >> 1) * uv_stride;
|
|
uint8_t* dst_v = dec->output->u.YUVA.v + (ptrdiff_t)(y_pos >> 1) * uv_stride;
|
|
const uint8_t* r_ptr = in + CHANNEL_OFFSET(1);
|
|
const uint8_t* g_ptr = in + CHANNEL_OFFSET(2);
|
|
const uint8_t* b_ptr = in + CHANNEL_OFFSET(3);
|
|
const uint8_t* a_ptr = NULL;
|
|
int has_alpha = 0;
|
|
|
|
// Make sure the lines are processed two by two from the start.
|
|
assert(y_pos % 2 == 0);
|
|
|
|
// Make sure num_rows is even. y_pos_final will check if it not.
|
|
num_rows &= ~1;
|
|
|
|
if (dst_a) {
|
|
dst_a += (ptrdiff_t)y_pos * a_stride;
|
|
a_ptr = in + CHANNEL_OFFSET(0);
|
|
has_alpha = CheckNonOpaque(a_ptr, width, num_rows, in_stride);
|
|
}
|
|
// Process pairs of lines.
|
|
WebPImportYUVAFromRGBA(r_ptr, g_ptr, b_ptr, a_ptr, /*step=*/4, in_stride,
|
|
has_alpha, width, num_rows, tmp_rgb, y_stride,
|
|
uv_stride, a_stride, dst_y, dst_u, dst_v, dst_a);
|
|
|
|
y_pos += num_rows;
|
|
if (y_pos_final == io->crop_bottom - io->crop_top && y_pos < y_pos_final) {
|
|
assert(y_pos + 1 == y_pos_final);
|
|
// If we output the last line of an image with odd height.
|
|
dst_y += (ptrdiff_t)num_rows * y_stride;
|
|
dst_u += (ptrdiff_t)(num_rows >> 1) * uv_stride;
|
|
dst_v += (ptrdiff_t)(num_rows >> 1) * uv_stride;
|
|
r_ptr += (ptrdiff_t)num_rows * in_stride;
|
|
g_ptr += (ptrdiff_t)num_rows * in_stride;
|
|
b_ptr += (ptrdiff_t)num_rows * in_stride;
|
|
if (dst_a) {
|
|
dst_a += (ptrdiff_t)num_rows * a_stride;
|
|
a_ptr += (ptrdiff_t)num_rows * in_stride;
|
|
has_alpha = CheckNonOpaque(a_ptr, width, /*height=*/1, in_stride);
|
|
}
|
|
WebPImportYUVAFromRGBALastLine(r_ptr, g_ptr, b_ptr, a_ptr, /*step=*/4,
|
|
has_alpha, width, tmp_rgb, dst_y, dst_u,
|
|
dst_v, dst_a);
|
|
y_pos = y_pos_final;
|
|
}
|
|
return y_pos;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Cropping.
|
|
|
|
// Sets io->mb_y, io->mb_h & io->mb_w according to start row, end row and
|
|
// crop options. Also updates the input data pointer, so that it points to the
|
|
// start of the cropped window. Note that pixels are in ARGB format even if
|
|
// 'in_data' is uint8_t*.
|
|
// Returns true if the crop window is not empty.
|
|
static int SetCropWindow(VP8Io* const io, int y_start, int y_end,
|
|
uint8_t** const in_data, int pixel_stride) {
|
|
assert(y_start < y_end);
|
|
assert(io->crop_left < io->crop_right);
|
|
if (y_end > io->crop_bottom) {
|
|
y_end = io->crop_bottom; // make sure we don't overflow on last row.
|
|
}
|
|
if (y_start < io->crop_top) {
|
|
const int delta = io->crop_top - y_start;
|
|
y_start = io->crop_top;
|
|
*in_data += (ptrdiff_t)delta * pixel_stride;
|
|
}
|
|
if (y_start >= y_end) return 0; // Crop window is empty.
|
|
|
|
*in_data += io->crop_left * sizeof(uint32_t);
|
|
|
|
io->mb_y = y_start - io->crop_top;
|
|
io->mb_w = io->crop_right - io->crop_left;
|
|
io->mb_h = y_end - y_start;
|
|
return 1; // Non-empty crop window.
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static WEBP_INLINE int GetMetaIndex(const uint32_t* const image, int xsize,
|
|
int bits, int x, int y) {
|
|
if (bits == 0) return 0;
|
|
return image[xsize * (y >> bits) + (x >> bits)];
|
|
}
|
|
|
|
static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr,
|
|
int x, int y) {
|
|
const int meta_index = GetMetaIndex(hdr->huffman_image, hdr->huffman_xsize,
|
|
hdr->huffman_subsample_bits, x, y);
|
|
assert(meta_index < hdr->num_htree_groups);
|
|
return hdr->htree_groups + meta_index;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main loop, with custom row-processing function
|
|
|
|
// If 'wait_for_biggest_batch' is true, wait for enough data to fill the
|
|
// argb_cache as much as possible (usually NUM_ARGB_CACHE_ROWS).
|
|
typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row,
|
|
int wait_for_biggest_batch);
|
|
|
|
static void ApplyInverseTransforms(VP8LDecoder* const dec, int start_row,
|
|
int num_rows, const uint32_t* const rows) {
|
|
int n = dec->next_transform;
|
|
const int cache_pixs = dec->width * num_rows;
|
|
const int end_row = start_row + num_rows;
|
|
const uint32_t* rows_in = rows;
|
|
uint32_t* const rows_out = dec->argb_cache;
|
|
|
|
// Inverse transforms.
|
|
while (n-- > 0) {
|
|
VP8LTransform* const transform = &dec->transforms[n];
|
|
VP8LInverseTransform(transform, start_row, end_row, rows_in, rows_out);
|
|
rows_in = rows_out;
|
|
}
|
|
if (rows_in != rows_out) {
|
|
// No transform called, hence just copy.
|
|
WEBP_UNSAFE_MEMCPY(rows_out, rows_in, cache_pixs * sizeof(*rows_out));
|
|
}
|
|
}
|
|
|
|
// Processes (transforms, scales & color-converts) the rows decoded after the
|
|
// last call.
|
|
static void ProcessRows(VP8LDecoder* const dec, int row,
|
|
int wait_for_biggest_batch) {
|
|
const uint32_t* const rows = dec->pixels + dec->width * dec->last_row;
|
|
int num_rows;
|
|
|
|
// In case of YUV conversion and if we do not need to get to the last row.
|
|
if (wait_for_biggest_batch) {
|
|
// In case of YUV conversion, and if we do not use the whole cropping
|
|
// region.
|
|
if (!WebPIsRGBMode(dec->output->colorspace) && row >= dec->io->crop_top &&
|
|
row < dec->io->crop_bottom) {
|
|
// Make sure the number of rows to process is even.
|
|
if ((row - dec->io->crop_top) % 2 != 0) return;
|
|
// Make sure the cache is as full as possible.
|
|
if (row % NUM_ARGB_CACHE_ROWS != 0 &&
|
|
(row + 1) % NUM_ARGB_CACHE_ROWS != 0) {
|
|
return;
|
|
}
|
|
} else {
|
|
if (row % NUM_ARGB_CACHE_ROWS != 0) return;
|
|
}
|
|
}
|
|
num_rows = row - dec->last_row;
|
|
assert(row <= dec->io->crop_bottom);
|
|
// We can't process more than NUM_ARGB_CACHE_ROWS at a time (that's the size
|
|
// of argb_cache), but we currently don't need more than that.
|
|
assert(num_rows <= NUM_ARGB_CACHE_ROWS);
|
|
if (num_rows > 0) { // Emit output.
|
|
VP8Io* const io = dec->io;
|
|
uint8_t* rows_data = (uint8_t*)dec->argb_cache;
|
|
const int in_stride = io->width * sizeof(uint32_t); // in unit of RGBA
|
|
ApplyInverseTransforms(dec, dec->last_row, num_rows, rows);
|
|
if (!SetCropWindow(io, dec->last_row, row, &rows_data, in_stride)) {
|
|
// Nothing to output (this time).
|
|
} else {
|
|
const WebPDecBuffer* const output = dec->output;
|
|
if (WebPIsRGBMode(output->colorspace)) { // convert to RGBA
|
|
const WebPRGBABuffer* const buf = &output->u.RGBA;
|
|
uint8_t* const rgba =
|
|
buf->rgba + (ptrdiff_t)dec->last_out_row * buf->stride;
|
|
const int num_rows_out =
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
io->use_scaling ? EmitRescaledRowsRGBA(dec, rows_data, in_stride,
|
|
io->mb_h, rgba, buf->stride)
|
|
:
|
|
#endif // WEBP_REDUCE_SIZE
|
|
EmitRows(output->colorspace, rows_data, in_stride,
|
|
io->mb_w, io->mb_h, rgba, buf->stride);
|
|
// Update 'last_out_row'.
|
|
dec->last_out_row += num_rows_out;
|
|
} else { // convert to YUVA
|
|
dec->last_out_row =
|
|
io->use_scaling
|
|
? EmitRescaledRowsYUVA(dec, rows_data, in_stride, io->mb_h)
|
|
: EmitRowsYUVA(rows_data, io, in_stride,
|
|
dec->accumulated_rgb_pixels, dec);
|
|
}
|
|
assert(dec->last_out_row <= output->height);
|
|
}
|
|
}
|
|
|
|
// Update 'last_row'.
|
|
dec->last_row = row;
|
|
assert(dec->last_row <= dec->height);
|
|
}
|
|
|
|
// Row-processing for the special case when alpha data contains only one
|
|
// transform (color indexing), and trivial non-green literals.
|
|
static int Is8bOptimizable(const VP8LMetadata* const hdr) {
|
|
int i;
|
|
if (hdr->color_cache_size > 0) return 0;
|
|
// When the Huffman tree contains only one symbol, we can skip the
|
|
// call to ReadSymbol() for red/blue/alpha channels.
|
|
for (i = 0; i < hdr->num_htree_groups; ++i) {
|
|
HuffmanCode** const htrees = hdr->htree_groups[i].htrees;
|
|
if (htrees[RED][0].bits > 0) return 0;
|
|
if (htrees[BLUE][0].bits > 0) return 0;
|
|
if (htrees[ALPHA][0].bits > 0) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void AlphaApplyFilter(ALPHDecoder* const alph_dec, int first_row,
|
|
int last_row, uint8_t* out, int stride) {
|
|
if (alph_dec->filter != WEBP_FILTER_NONE) {
|
|
int y;
|
|
const uint8_t* prev_line = alph_dec->prev_line;
|
|
assert(WebPUnfilters[alph_dec->filter] != NULL);
|
|
for (y = first_row; y < last_row; ++y) {
|
|
WebPUnfilters[alph_dec->filter](prev_line, out, out, stride);
|
|
prev_line = out;
|
|
out += stride;
|
|
}
|
|
alph_dec->prev_line = prev_line;
|
|
}
|
|
}
|
|
|
|
static void ExtractPalettedAlphaRows(VP8LDecoder* const dec, int last_row) {
|
|
// For vertical and gradient filtering, we need to decode the part above the
|
|
// crop_top row, in order to have the correct spatial predictors.
|
|
ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io->opaque;
|
|
const int top_row = (alph_dec->filter == WEBP_FILTER_NONE ||
|
|
alph_dec->filter == WEBP_FILTER_HORIZONTAL)
|
|
? dec->io->crop_top
|
|
: dec->last_row;
|
|
const int first_row = (dec->last_row < top_row) ? top_row : dec->last_row;
|
|
assert(last_row <= dec->io->crop_bottom);
|
|
if (last_row > first_row) {
|
|
// Special method for paletted alpha data. We only process the cropped area.
|
|
const int width = dec->io->width;
|
|
uint8_t* out = alph_dec->output + width * first_row;
|
|
const uint8_t* const in = (uint8_t*)dec->pixels + dec->width * first_row;
|
|
VP8LTransform* const transform = &dec->transforms[0];
|
|
assert(dec->next_transform == 1);
|
|
assert(transform->type == COLOR_INDEXING_TRANSFORM);
|
|
VP8LColorIndexInverseTransformAlpha(transform, first_row, last_row, in,
|
|
out);
|
|
AlphaApplyFilter(alph_dec, first_row, last_row, out, width);
|
|
}
|
|
dec->last_row = dec->last_out_row = last_row;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Helper functions for fast pattern copy (8b and 32b)
|
|
|
|
// cyclic rotation of pattern word
|
|
static WEBP_INLINE uint32_t Rotate8b(uint32_t V) {
|
|
#if defined(WORDS_BIGENDIAN)
|
|
return ((V & 0xff000000u) >> 24) | (V << 8);
|
|
#else
|
|
return ((V & 0xffu) << 24) | (V >> 8);
|
|
#endif
|
|
}
|
|
|
|
// copy 1, 2 or 4-bytes pattern
|
|
static WEBP_INLINE void CopySmallPattern8b(const uint8_t* src, uint8_t* dst,
|
|
int length, uint32_t pattern) {
|
|
int i;
|
|
// align 'dst' to 4-bytes boundary. Adjust the pattern along the way.
|
|
while ((uintptr_t)dst & 3) {
|
|
*dst++ = *src++;
|
|
pattern = Rotate8b(pattern);
|
|
--length;
|
|
}
|
|
// Copy the pattern 4 bytes at a time.
|
|
for (i = 0; i < (length >> 2); ++i) {
|
|
((uint32_t*)dst)[i] = pattern;
|
|
}
|
|
// Finish with left-overs. 'pattern' is still correctly positioned,
|
|
// so no Rotate8b() call is needed.
|
|
for (i <<= 2; i < length; ++i) {
|
|
dst[i] = src[i];
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE void CopyBlock8b(uint8_t* const dst, int dist, int length) {
|
|
const uint8_t* src = dst - dist;
|
|
if (length >= 8) {
|
|
uint32_t pattern = 0;
|
|
switch (dist) {
|
|
case 1:
|
|
pattern = src[0];
|
|
#if defined(__arm__) || defined(_M_ARM) // arm doesn't like multiply that much
|
|
pattern |= pattern << 8;
|
|
pattern |= pattern << 16;
|
|
#elif defined(WEBP_USE_MIPS_DSP_R2)
|
|
__asm__ volatile("replv.qb %0, %0" : "+r"(pattern));
|
|
#else
|
|
pattern = 0x01010101u * pattern;
|
|
#endif
|
|
break;
|
|
case 2:
|
|
#if !defined(WORDS_BIGENDIAN)
|
|
WEBP_UNSAFE_MEMCPY(&pattern, src, sizeof(uint16_t));
|
|
#else
|
|
pattern = ((uint32_t)src[0] << 8) | src[1];
|
|
#endif
|
|
#if defined(__arm__) || defined(_M_ARM)
|
|
pattern |= pattern << 16;
|
|
#elif defined(WEBP_USE_MIPS_DSP_R2)
|
|
__asm__ volatile("replv.ph %0, %0" : "+r"(pattern));
|
|
#else
|
|
pattern = 0x00010001u * pattern;
|
|
#endif
|
|
break;
|
|
case 4:
|
|
WEBP_UNSAFE_MEMCPY(&pattern, src, sizeof(uint32_t));
|
|
break;
|
|
default:
|
|
goto Copy;
|
|
}
|
|
CopySmallPattern8b(src, dst, length, pattern);
|
|
return;
|
|
}
|
|
Copy:
|
|
if (dist >= length) { // no overlap -> use WEBP_UNSAFE_MEMCPY()
|
|
WEBP_UNSAFE_MEMCPY(dst, src, length * sizeof(*dst));
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < length; ++i) dst[i] = src[i];
|
|
}
|
|
}
|
|
|
|
// copy pattern of 1 or 2 uint32_t's
|
|
static WEBP_INLINE void CopySmallPattern32b(const uint32_t* src, uint32_t* dst,
|
|
int length, uint64_t pattern) {
|
|
int i;
|
|
if ((uintptr_t)dst & 4) { // Align 'dst' to 8-bytes boundary.
|
|
*dst++ = *src++;
|
|
pattern = (pattern >> 32) | (pattern << 32);
|
|
--length;
|
|
}
|
|
assert(0 == ((uintptr_t)dst & 7));
|
|
for (i = 0; i < (length >> 1); ++i) {
|
|
((uint64_t*)dst)[i] = pattern; // Copy the pattern 8 bytes at a time.
|
|
}
|
|
if (length & 1) { // Finish with left-over.
|
|
dst[i << 1] = src[i << 1];
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE void CopyBlock32b(uint32_t* const dst, int dist,
|
|
int length) {
|
|
const uint32_t* const src = dst - dist;
|
|
if (dist <= 2 && length >= 4 && ((uintptr_t)dst & 3) == 0) {
|
|
uint64_t pattern;
|
|
if (dist == 1) {
|
|
pattern = (uint64_t)src[0];
|
|
pattern |= pattern << 32;
|
|
} else {
|
|
WEBP_UNSAFE_MEMCPY(&pattern, src, sizeof(pattern));
|
|
}
|
|
CopySmallPattern32b(src, dst, length, pattern);
|
|
} else if (dist >= length) { // no overlap
|
|
WEBP_UNSAFE_MEMCPY(dst, src, length * sizeof(*dst));
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < length; ++i) dst[i] = src[i];
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static int DecodeAlphaData(VP8LDecoder* const dec, uint8_t* const data,
|
|
int width, int height, int last_row) {
|
|
int ok = 1;
|
|
int row = dec->last_pixel / width;
|
|
int col = dec->last_pixel % width;
|
|
VP8LBitReader* const br = &dec->br;
|
|
VP8LMetadata* const hdr = &dec->hdr;
|
|
uint8_t* src = data + dec->last_pixel;
|
|
// End of data.
|
|
const uint8_t* const src_end = data + width * height;
|
|
// Last pixel to decode.
|
|
const uint8_t* const src_last = data + width * last_row;
|
|
const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES;
|
|
const int mask = hdr->huffman_mask;
|
|
assert(src <= src_end);
|
|
assert(last_row <= height);
|
|
assert(Is8bOptimizable(hdr));
|
|
|
|
while (!br->eos && src < src_last) {
|
|
const HTreeGroup* htree_group = GetHtreeGroupForPos(hdr, col, row);
|
|
// Beginning of a block or inside a block if we reached it through a
|
|
// backward reference.
|
|
const uint8_t* const block_start = src;
|
|
const uint8_t* block_end;
|
|
if (mask == ~0) {
|
|
// No block, we decode until src_last.
|
|
block_end = src_last;
|
|
} else {
|
|
const uint32_t block_size_left = mask + 1 - (col & mask);
|
|
const uint32_t line_size_left = width - col;
|
|
// End of the block if it is full, or end of the line.
|
|
block_end = src + (block_size_left < line_size_left ? block_size_left
|
|
: line_size_left);
|
|
}
|
|
for (; !br->eos && src < block_end;) {
|
|
int code;
|
|
VP8LFillBitWindow(br);
|
|
code = ReadSymbol(htree_group->htrees[GREEN], br);
|
|
if (code < NUM_LITERAL_CODES) { // Literal
|
|
*src = code;
|
|
++src;
|
|
} else if (code < len_code_limit) { // Backward reference
|
|
int dist_code, dist;
|
|
const int length_sym = code - NUM_LITERAL_CODES;
|
|
const int length = GetCopyLength(length_sym, br);
|
|
const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br);
|
|
VP8LFillBitWindow(br);
|
|
dist_code = GetCopyDistance(dist_symbol, br);
|
|
dist = PlaneCodeToDistance(width, dist_code);
|
|
if (src - data >= (ptrdiff_t)dist &&
|
|
src_end - src >= (ptrdiff_t)length) {
|
|
CopyBlock8b(src, dist, length);
|
|
} else {
|
|
ok = 0;
|
|
goto End;
|
|
}
|
|
src += length;
|
|
} else { // Not reached
|
|
ok = 0;
|
|
goto End;
|
|
}
|
|
br->eos = VP8LIsEndOfStream(br);
|
|
}
|
|
col += (int)(src - block_start);
|
|
while (col >= width) {
|
|
col -= width;
|
|
++row;
|
|
if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) {
|
|
ExtractPalettedAlphaRows(dec, row);
|
|
}
|
|
}
|
|
}
|
|
// Process the remaining rows corresponding to last row-block.
|
|
ExtractPalettedAlphaRows(dec, row > last_row ? last_row : row);
|
|
|
|
End:
|
|
br->eos = VP8LIsEndOfStream(br);
|
|
if (!ok || (br->eos && src < src_end)) {
|
|
return VP8LSetError(
|
|
dec, br->eos ? VP8_STATUS_SUSPENDED : VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
dec->last_pixel = (int)(src - data);
|
|
return ok;
|
|
}
|
|
|
|
static void SaveState(VP8LDecoder* const dec, int last_pixel) {
|
|
assert(dec->incremental);
|
|
dec->saved_br = dec->br;
|
|
dec->saved_last_pixel = last_pixel;
|
|
if (dec->hdr.color_cache_size > 0) {
|
|
VP8LColorCacheCopy(&dec->hdr.color_cache, &dec->hdr.saved_color_cache);
|
|
}
|
|
}
|
|
|
|
static void RestoreState(VP8LDecoder* const dec) {
|
|
assert(dec->br.eos);
|
|
dec->status = VP8_STATUS_SUSPENDED;
|
|
dec->br = dec->saved_br;
|
|
dec->last_pixel = dec->saved_last_pixel;
|
|
if (dec->hdr.color_cache_size > 0) {
|
|
VP8LColorCacheCopy(&dec->hdr.saved_color_cache, &dec->hdr.color_cache);
|
|
}
|
|
}
|
|
|
|
#define SYNC_EVERY_N_ROWS 8 // minimum number of rows between check-points
|
|
static int DecodeImageData(VP8LDecoder* const dec, uint32_t* const data,
|
|
int width, int height, int last_row,
|
|
ProcessRowsFunc process_func) {
|
|
int row = dec->last_pixel / width;
|
|
int col = dec->last_pixel % width;
|
|
VP8LBitReader* const br = &dec->br;
|
|
VP8LMetadata* const hdr = &dec->hdr;
|
|
uint32_t* src = data + dec->last_pixel;
|
|
uint32_t* last_cached = src;
|
|
uint32_t* const src_end = data + width * height; // End of data
|
|
uint32_t* const src_last = data + width * last_row; // Last pixel to decode
|
|
const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES;
|
|
const int color_cache_limit = len_code_limit + hdr->color_cache_size;
|
|
int next_sync_row = dec->incremental ? row : 1 << 24;
|
|
VP8LColorCache* const color_cache =
|
|
(hdr->color_cache_size > 0) ? &hdr->color_cache : NULL;
|
|
const int mask = hdr->huffman_mask;
|
|
const HTreeGroup* htree_group =
|
|
(src < src_last) ? GetHtreeGroupForPos(hdr, col, row) : NULL;
|
|
assert(dec->last_row < last_row);
|
|
assert(src_last <= src_end);
|
|
|
|
while (src < src_last) {
|
|
int code;
|
|
if (row >= next_sync_row) {
|
|
SaveState(dec, (int)(src - data));
|
|
next_sync_row = row + SYNC_EVERY_N_ROWS;
|
|
}
|
|
// Only update when changing tile. Note we could use this test:
|
|
// if "((((prev_col ^ col) | prev_row ^ row)) > mask)" -> tile changed
|
|
// but that's actually slower and needs storing the previous col/row.
|
|
if ((col & mask) == 0) {
|
|
htree_group = GetHtreeGroupForPos(hdr, col, row);
|
|
}
|
|
assert(htree_group != NULL);
|
|
if (htree_group->is_trivial_code) {
|
|
*src = htree_group->literal_arb;
|
|
goto AdvanceByOne;
|
|
}
|
|
VP8LFillBitWindow(br);
|
|
if (htree_group->use_packed_table) {
|
|
code = ReadPackedSymbols(htree_group, br, src);
|
|
if (VP8LIsEndOfStream(br)) break;
|
|
if (code == PACKED_NON_LITERAL_CODE) goto AdvanceByOne;
|
|
} else {
|
|
code = ReadSymbol(htree_group->htrees[GREEN], br);
|
|
}
|
|
if (code < NUM_LITERAL_CODES) { // Literal
|
|
if (htree_group->is_trivial_literal) {
|
|
if (VP8LIsEndOfStream(br)) break;
|
|
*src = htree_group->literal_arb | (code << 8);
|
|
} else {
|
|
int red, blue, alpha;
|
|
red = ReadSymbol(htree_group->htrees[RED], br);
|
|
VP8LFillBitWindow(br);
|
|
blue = ReadSymbol(htree_group->htrees[BLUE], br);
|
|
alpha = ReadSymbol(htree_group->htrees[ALPHA], br);
|
|
if (VP8LIsEndOfStream(br)) break;
|
|
*src = ((uint32_t)alpha << 24) | (red << 16) | (code << 8) | blue;
|
|
}
|
|
AdvanceByOne:
|
|
++src;
|
|
++col;
|
|
if (col >= width) {
|
|
col = 0;
|
|
++row;
|
|
if (process_func != NULL) {
|
|
if (row <= last_row) {
|
|
process_func(dec, row, /*wait_for_biggest_batch=*/1);
|
|
}
|
|
}
|
|
if (color_cache != NULL) {
|
|
while (last_cached < src) {
|
|
VP8LColorCacheInsert(color_cache, *last_cached++);
|
|
}
|
|
}
|
|
}
|
|
} else if (code < len_code_limit) { // Backward reference
|
|
int dist_code, dist;
|
|
const int length_sym = code - NUM_LITERAL_CODES;
|
|
const int length = GetCopyLength(length_sym, br);
|
|
const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br);
|
|
VP8LFillBitWindow(br);
|
|
dist_code = GetCopyDistance(dist_symbol, br);
|
|
dist = PlaneCodeToDistance(width, dist_code);
|
|
|
|
if (VP8LIsEndOfStream(br)) break;
|
|
if (src - data < (ptrdiff_t)dist || src_end - src < (ptrdiff_t)length) {
|
|
goto Error;
|
|
} else {
|
|
CopyBlock32b(src, dist, length);
|
|
}
|
|
src += length;
|
|
col += length;
|
|
while (col >= width) {
|
|
col -= width;
|
|
++row;
|
|
if (process_func != NULL) {
|
|
if (row <= last_row) {
|
|
process_func(dec, row, /*wait_for_biggest_batch=*/1);
|
|
}
|
|
}
|
|
}
|
|
// Because of the check done above (before 'src' was incremented by
|
|
// 'length'), the following holds true.
|
|
assert(src <= src_end);
|
|
if (col & mask) htree_group = GetHtreeGroupForPos(hdr, col, row);
|
|
if (color_cache != NULL) {
|
|
while (last_cached < src) {
|
|
VP8LColorCacheInsert(color_cache, *last_cached++);
|
|
}
|
|
}
|
|
} else if (code < color_cache_limit) { // Color cache
|
|
const int key = code - len_code_limit;
|
|
assert(color_cache != NULL);
|
|
if (VP8LIsEndOfStream(br)) break;
|
|
while (last_cached < src) {
|
|
VP8LColorCacheInsert(color_cache, *last_cached++);
|
|
}
|
|
*src = VP8LColorCacheLookup(color_cache, key);
|
|
goto AdvanceByOne;
|
|
} else { // Not reached
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
br->eos = VP8LIsEndOfStream(br);
|
|
// In incremental decoding:
|
|
// br->eos && src < src_last: if 'br' reached the end of the buffer and
|
|
// 'src_last' has not been reached yet, there is not enough data. 'dec' has to
|
|
// be reset until there is more data.
|
|
// !br->eos && src < src_last: this cannot happen as either the buffer is
|
|
// fully read, either enough has been read to reach 'src_last'.
|
|
// src >= src_last: 'src_last' is reached, all is fine. 'src' can actually go
|
|
// beyond 'src_last' in case the image is cropped and an LZ77 goes further.
|
|
// The buffer might have been enough or there is some left. 'br->eos' does
|
|
// not matter.
|
|
assert(!dec->incremental || (br->eos && src < src_last) || src >= src_last);
|
|
if (dec->incremental && br->eos && src < src_last) {
|
|
RestoreState(dec);
|
|
} else if ((dec->incremental && src >= src_last) || !br->eos) {
|
|
// Process the remaining rows corresponding to last row-block.
|
|
if (process_func != NULL) {
|
|
process_func(dec, row > last_row ? last_row : row,
|
|
/*wait_for_biggest_batch=*/0);
|
|
}
|
|
dec->status = VP8_STATUS_OK;
|
|
dec->last_pixel = (int)(src - data); // end-of-scan marker
|
|
} else {
|
|
// if not incremental, and we are past the end of buffer (eos=1), then this
|
|
// is a real bitstream error.
|
|
goto Error;
|
|
}
|
|
return 1;
|
|
|
|
Error:
|
|
return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// VP8LTransform
|
|
|
|
static void ClearTransform(VP8LTransform* const transform) {
|
|
WebPSafeFree(transform->data);
|
|
transform->data = NULL;
|
|
}
|
|
|
|
// For security reason, we need to remap the color map to span
|
|
// the total possible bundled values, and not just the num_colors.
|
|
static int ExpandColorMap(int num_colors, VP8LTransform* const transform) {
|
|
int i;
|
|
const int final_num_colors = 1 << (8 >> transform->bits);
|
|
uint32_t* const new_color_map = (uint32_t*)WebPSafeMalloc(
|
|
(uint64_t)final_num_colors, sizeof(*new_color_map));
|
|
if (new_color_map == NULL) {
|
|
return 0;
|
|
} else {
|
|
uint8_t* const data = (uint8_t*)transform->data;
|
|
uint8_t* const new_data = (uint8_t*)new_color_map;
|
|
new_color_map[0] = transform->data[0];
|
|
for (i = 4; i < 4 * num_colors; ++i) {
|
|
// Equivalent to VP8LAddPixels(), on a byte-basis.
|
|
new_data[i] = (data[i] + new_data[i - 4]) & 0xff;
|
|
}
|
|
for (; i < 4 * final_num_colors; ++i) {
|
|
new_data[i] = 0; // black tail.
|
|
}
|
|
WebPSafeFree(transform->data);
|
|
transform->data = new_color_map;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int ReadTransform(int* const xsize, int const* ysize,
|
|
VP8LDecoder* const dec) {
|
|
int ok = 1;
|
|
VP8LBitReader* const br = &dec->br;
|
|
VP8LTransform* transform = &dec->transforms[dec->next_transform];
|
|
const VP8LImageTransformType type =
|
|
(VP8LImageTransformType)VP8LReadBits(br, 2);
|
|
|
|
// Each transform type can only be present once in the stream.
|
|
if (dec->transforms_seen & (1U << type)) {
|
|
return 0; // Already there, let's not accept the second same transform.
|
|
}
|
|
dec->transforms_seen |= (1U << type);
|
|
|
|
transform->type = type;
|
|
transform->xsize = *xsize;
|
|
transform->ysize = *ysize;
|
|
transform->data = NULL;
|
|
++dec->next_transform;
|
|
assert(dec->next_transform <= NUM_TRANSFORMS);
|
|
|
|
switch (type) {
|
|
case PREDICTOR_TRANSFORM:
|
|
case CROSS_COLOR_TRANSFORM:
|
|
transform->bits =
|
|
MIN_TRANSFORM_BITS + VP8LReadBits(br, NUM_TRANSFORM_BITS);
|
|
ok = DecodeImageStream(
|
|
VP8LSubSampleSize(transform->xsize, transform->bits),
|
|
VP8LSubSampleSize(transform->ysize, transform->bits),
|
|
/*is_level0=*/0, dec, &transform->data);
|
|
break;
|
|
case COLOR_INDEXING_TRANSFORM: {
|
|
const int num_colors = VP8LReadBits(br, 8) + 1;
|
|
const int bits = (num_colors > 16) ? 0
|
|
: (num_colors > 4) ? 1
|
|
: (num_colors > 2) ? 2
|
|
: 3;
|
|
*xsize = VP8LSubSampleSize(transform->xsize, bits);
|
|
transform->bits = bits;
|
|
ok = DecodeImageStream(num_colors, /*ysize=*/1, /*is_level0=*/0, dec,
|
|
&transform->data);
|
|
if (ok && !ExpandColorMap(num_colors, transform)) {
|
|
return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
}
|
|
break;
|
|
}
|
|
case SUBTRACT_GREEN_TRANSFORM:
|
|
break;
|
|
default:
|
|
assert(0); // can't happen
|
|
break;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// VP8LMetadata
|
|
|
|
static void InitMetadata(VP8LMetadata* const hdr) {
|
|
assert(hdr != NULL);
|
|
WEBP_UNSAFE_MEMSET(hdr, 0, sizeof(*hdr));
|
|
}
|
|
|
|
static void ClearMetadata(VP8LMetadata* const hdr) {
|
|
assert(hdr != NULL);
|
|
|
|
WebPSafeFree(hdr->huffman_image);
|
|
VP8LHuffmanTablesDeallocate(&hdr->huffman_tables);
|
|
VP8LHtreeGroupsFree(hdr->htree_groups);
|
|
VP8LColorCacheClear(&hdr->color_cache);
|
|
VP8LColorCacheClear(&hdr->saved_color_cache);
|
|
InitMetadata(hdr);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// VP8LDecoder
|
|
|
|
VP8LDecoder* VP8LNew(void) {
|
|
VP8LDecoder* const dec = (VP8LDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec));
|
|
if (dec == NULL) return NULL;
|
|
dec->status = VP8_STATUS_OK;
|
|
dec->state = READ_DIM;
|
|
|
|
VP8LDspInit(); // Init critical function pointers.
|
|
|
|
return dec;
|
|
}
|
|
|
|
// Resets the decoder in its initial state, reclaiming memory.
|
|
// Preserves the dec->status value.
|
|
static void VP8LClear(VP8LDecoder* const dec) {
|
|
int i;
|
|
if (dec == NULL) return;
|
|
ClearMetadata(&dec->hdr);
|
|
|
|
WebPSafeFree(dec->pixels);
|
|
dec->pixels = NULL;
|
|
for (i = 0; i < dec->next_transform; ++i) {
|
|
ClearTransform(&dec->transforms[i]);
|
|
}
|
|
dec->next_transform = 0;
|
|
dec->transforms_seen = 0;
|
|
|
|
WebPSafeFree(dec->rescaler_memory);
|
|
dec->rescaler_memory = NULL;
|
|
|
|
dec->output = NULL; // leave no trace behind
|
|
}
|
|
|
|
void VP8LDelete(VP8LDecoder* const dec) {
|
|
if (dec != NULL) {
|
|
VP8LClear(dec);
|
|
WebPSafeFree(dec);
|
|
}
|
|
}
|
|
|
|
static void UpdateDecoder(VP8LDecoder* const dec, int width, int height) {
|
|
VP8LMetadata* const hdr = &dec->hdr;
|
|
const int num_bits = hdr->huffman_subsample_bits;
|
|
dec->width = width;
|
|
dec->height = height;
|
|
|
|
hdr->huffman_xsize = VP8LSubSampleSize(width, num_bits);
|
|
hdr->huffman_mask = (num_bits == 0) ? ~0 : (1 << num_bits) - 1;
|
|
}
|
|
|
|
static int DecodeImageStream(int xsize, int ysize, int is_level0,
|
|
VP8LDecoder* const dec,
|
|
uint32_t** const decoded_data) {
|
|
int ok = 1;
|
|
int transform_xsize = xsize;
|
|
int transform_ysize = ysize;
|
|
VP8LBitReader* const br = &dec->br;
|
|
VP8LMetadata* const hdr = &dec->hdr;
|
|
uint32_t* data = NULL;
|
|
int color_cache_bits = 0;
|
|
|
|
// Read the transforms (may recurse).
|
|
if (is_level0) {
|
|
while (ok && VP8LReadBits(br, 1)) {
|
|
ok = ReadTransform(&transform_xsize, &transform_ysize, dec);
|
|
}
|
|
}
|
|
|
|
// Color cache
|
|
if (ok && VP8LReadBits(br, 1)) {
|
|
color_cache_bits = VP8LReadBits(br, 4);
|
|
ok = (color_cache_bits >= 1 && color_cache_bits <= MAX_CACHE_BITS);
|
|
if (!ok) {
|
|
VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR);
|
|
goto End;
|
|
}
|
|
}
|
|
|
|
// Read the Huffman codes (may recurse).
|
|
ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize,
|
|
color_cache_bits, is_level0);
|
|
if (!ok) {
|
|
VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR);
|
|
goto End;
|
|
}
|
|
|
|
// Finish setting up the color-cache
|
|
if (color_cache_bits > 0) {
|
|
hdr->color_cache_size = 1 << color_cache_bits;
|
|
if (!VP8LColorCacheInit(&hdr->color_cache, color_cache_bits)) {
|
|
ok = VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
goto End;
|
|
}
|
|
} else {
|
|
hdr->color_cache_size = 0;
|
|
}
|
|
UpdateDecoder(dec, transform_xsize, transform_ysize);
|
|
|
|
if (is_level0) { // level 0 complete
|
|
dec->state = READ_HDR;
|
|
goto End;
|
|
}
|
|
|
|
{
|
|
const uint64_t total_size = (uint64_t)transform_xsize * transform_ysize;
|
|
data = (uint32_t*)WebPSafeMalloc(total_size, sizeof(*data));
|
|
if (data == NULL) {
|
|
ok = VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
goto End;
|
|
}
|
|
}
|
|
|
|
// Use the Huffman trees to decode the LZ77 encoded data.
|
|
ok = DecodeImageData(dec, data, transform_xsize, transform_ysize,
|
|
transform_ysize, NULL);
|
|
ok = ok && !br->eos;
|
|
|
|
End:
|
|
if (!ok) {
|
|
WebPSafeFree(data);
|
|
ClearMetadata(hdr);
|
|
} else {
|
|
if (decoded_data != NULL) {
|
|
*decoded_data = data;
|
|
} else {
|
|
// We allocate image data in this function only for transforms. At level 0
|
|
// (that is: not the transforms), we shouldn't have allocated anything.
|
|
assert(data == NULL);
|
|
assert(is_level0);
|
|
}
|
|
dec->last_pixel = 0; // Reset for future DECODE_DATA_FUNC() calls.
|
|
if (!is_level0) ClearMetadata(hdr); // Clean up temporary data behind.
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Allocate internal buffers dec->pixels and dec->argb_cache.
|
|
static int AllocateInternalBuffers32b(VP8LDecoder* const dec, int final_width) {
|
|
const uint64_t num_pixels = (uint64_t)dec->width * dec->height;
|
|
// Scratch buffer corresponding to top-prediction row for transforming the
|
|
// first row in the row-blocks. Not needed for paletted alpha.
|
|
const uint64_t cache_top_pixels = (uint16_t)final_width;
|
|
// Scratch buffer for temporary BGRA storage. Not needed for paletted alpha.
|
|
const uint64_t cache_pixels = (uint64_t)final_width * NUM_ARGB_CACHE_ROWS;
|
|
// Scratch buffer to accumulate RGBA values (hence 4*)for YUV conversion.
|
|
uint64_t accumulated_rgb_pixels = 0;
|
|
uint64_t total_num_pixels;
|
|
if (dec->output != NULL && !WebPIsRGBMode(dec->output->colorspace)) {
|
|
const int uv_width = (dec->io->crop_right - dec->io->crop_left + 1) >> 1;
|
|
accumulated_rgb_pixels =
|
|
4 * uv_width * sizeof(*dec->accumulated_rgb_pixels) / sizeof(uint32_t);
|
|
}
|
|
total_num_pixels =
|
|
num_pixels + cache_top_pixels + cache_pixels + accumulated_rgb_pixels;
|
|
assert(dec->width <= final_width);
|
|
dec->pixels = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint32_t));
|
|
if (dec->pixels == NULL) {
|
|
dec->argb_cache = NULL; // for soundness
|
|
return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
}
|
|
dec->argb_cache = dec->pixels + num_pixels + cache_top_pixels;
|
|
dec->accumulated_rgb_pixels =
|
|
accumulated_rgb_pixels == 0
|
|
? NULL
|
|
: (uint16_t*)(dec->pixels + num_pixels + cache_top_pixels +
|
|
cache_pixels);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int AllocateInternalBuffers8b(VP8LDecoder* const dec) {
|
|
const uint64_t total_num_pixels = (uint64_t)dec->width * dec->height;
|
|
dec->argb_cache = NULL; // for soundness
|
|
dec->pixels = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint8_t));
|
|
if (dec->pixels == NULL) {
|
|
return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Special row-processing that only stores the alpha data.
|
|
static void ExtractAlphaRows(VP8LDecoder* const dec, int last_row,
|
|
int wait_for_biggest_batch) {
|
|
int cur_row = dec->last_row;
|
|
int num_rows = last_row - cur_row;
|
|
const uint32_t* in = dec->pixels + dec->width * cur_row;
|
|
|
|
if (wait_for_biggest_batch && last_row % NUM_ARGB_CACHE_ROWS != 0) {
|
|
return;
|
|
}
|
|
assert(last_row <= dec->io->crop_bottom);
|
|
while (num_rows > 0) {
|
|
const int num_rows_to_process =
|
|
(num_rows > NUM_ARGB_CACHE_ROWS) ? NUM_ARGB_CACHE_ROWS : num_rows;
|
|
// Extract alpha (which is stored in the green plane).
|
|
ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io->opaque;
|
|
uint8_t* const output = alph_dec->output;
|
|
const int width = dec->io->width; // the final width (!= dec->width)
|
|
const int cache_pixs = width * num_rows_to_process;
|
|
uint8_t* const dst = output + width * cur_row;
|
|
const uint32_t* const src = dec->argb_cache;
|
|
ApplyInverseTransforms(dec, cur_row, num_rows_to_process, in);
|
|
WebPExtractGreen(src, dst, cache_pixs);
|
|
AlphaApplyFilter(alph_dec, cur_row, cur_row + num_rows_to_process, dst,
|
|
width);
|
|
num_rows -= num_rows_to_process;
|
|
in += num_rows_to_process * dec->width;
|
|
cur_row += num_rows_to_process;
|
|
}
|
|
assert(cur_row == last_row);
|
|
dec->last_row = dec->last_out_row = last_row;
|
|
}
|
|
|
|
int VP8LDecodeAlphaHeader(ALPHDecoder* const alph_dec,
|
|
const uint8_t* const WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size) {
|
|
int ok = 0;
|
|
VP8LDecoder* dec = VP8LNew();
|
|
|
|
if (dec == NULL) return 0;
|
|
|
|
assert(alph_dec != NULL);
|
|
|
|
dec->width = alph_dec->width;
|
|
dec->height = alph_dec->height;
|
|
dec->io = &alph_dec->io;
|
|
dec->io->opaque = alph_dec;
|
|
dec->io->width = alph_dec->width;
|
|
dec->io->height = alph_dec->height;
|
|
|
|
dec->status = VP8_STATUS_OK;
|
|
VP8LInitBitReader(&dec->br, data, data_size);
|
|
|
|
if (!DecodeImageStream(alph_dec->width, alph_dec->height, /*is_level0=*/1,
|
|
dec, /*decoded_data=*/NULL)) {
|
|
goto Err;
|
|
}
|
|
|
|
// Special case: if alpha data uses only the color indexing transform and
|
|
// doesn't use color cache (a frequent case), we will use DecodeAlphaData()
|
|
// method that only needs allocation of 1 byte per pixel (alpha channel).
|
|
if (dec->next_transform == 1 &&
|
|
dec->transforms[0].type == COLOR_INDEXING_TRANSFORM &&
|
|
Is8bOptimizable(&dec->hdr)) {
|
|
alph_dec->use_8b_decode = 1;
|
|
ok = AllocateInternalBuffers8b(dec);
|
|
} else {
|
|
// Allocate internal buffers (note that dec->width may have changed here).
|
|
alph_dec->use_8b_decode = 0;
|
|
ok = AllocateInternalBuffers32b(dec, alph_dec->width);
|
|
}
|
|
|
|
if (!ok) goto Err;
|
|
|
|
// Only set here, once we are sure it is valid (to avoid thread races).
|
|
alph_dec->vp8l_dec = dec;
|
|
return 1;
|
|
|
|
Err:
|
|
VP8LDelete(dec);
|
|
return 0;
|
|
}
|
|
|
|
int VP8LDecodeAlphaImageStream(ALPHDecoder* const alph_dec, int last_row) {
|
|
VP8LDecoder* const dec = alph_dec->vp8l_dec;
|
|
assert(dec != NULL);
|
|
assert(last_row <= dec->height);
|
|
|
|
if (dec->last_row >= last_row) {
|
|
return 1; // done
|
|
}
|
|
|
|
if (!alph_dec->use_8b_decode) WebPInitAlphaProcessing();
|
|
|
|
// Decode (with special row processing).
|
|
return alph_dec->use_8b_decode
|
|
? DecodeAlphaData(dec, (uint8_t*)dec->pixels, dec->width,
|
|
dec->height, last_row)
|
|
: DecodeImageData(dec, dec->pixels, dec->width, dec->height,
|
|
last_row, ExtractAlphaRows);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) {
|
|
int width, height, has_alpha;
|
|
|
|
if (dec == NULL) return 0;
|
|
if (io == NULL) {
|
|
return VP8LSetError(dec, VP8_STATUS_INVALID_PARAM);
|
|
}
|
|
|
|
dec->io = io;
|
|
dec->status = VP8_STATUS_OK;
|
|
{
|
|
const uint8_t* WEBP_BIDI_INDEXABLE const bounded_data =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, io->data,
|
|
io->data_size);
|
|
VP8LInitBitReader(&dec->br, bounded_data, io->data_size);
|
|
}
|
|
if (!ReadImageInfo(&dec->br, &width, &height, &has_alpha)) {
|
|
VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR);
|
|
goto Error;
|
|
}
|
|
dec->state = READ_DIM;
|
|
io->width = width;
|
|
io->height = height;
|
|
|
|
if (!DecodeImageStream(width, height, /*is_level0=*/1, dec,
|
|
/*decoded_data=*/NULL)) {
|
|
goto Error;
|
|
}
|
|
return 1;
|
|
|
|
Error:
|
|
VP8LClear(dec);
|
|
assert(dec->status != VP8_STATUS_OK);
|
|
return 0;
|
|
}
|
|
|
|
int VP8LDecodeImage(VP8LDecoder* const dec) {
|
|
VP8Io* io = NULL;
|
|
WebPDecParams* params = NULL;
|
|
|
|
if (dec == NULL) return 0;
|
|
|
|
assert(dec->hdr.huffman_tables.root.start != NULL);
|
|
assert(dec->hdr.htree_groups != NULL);
|
|
assert(dec->hdr.num_htree_groups > 0);
|
|
|
|
io = dec->io;
|
|
assert(io != NULL);
|
|
params = (WebPDecParams*)io->opaque;
|
|
assert(params != NULL);
|
|
|
|
// Initialization.
|
|
if (dec->state != READ_DATA) {
|
|
dec->output = params->output;
|
|
assert(dec->output != NULL);
|
|
|
|
if (!WebPIoInitFromOptions(params->options, io, MODE_BGRA)) {
|
|
VP8LSetError(dec, VP8_STATUS_INVALID_PARAM);
|
|
goto Err;
|
|
}
|
|
|
|
if (!AllocateInternalBuffers32b(dec, io->width)) goto Err;
|
|
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err;
|
|
#else
|
|
if (io->use_scaling) {
|
|
VP8LSetError(dec, VP8_STATUS_INVALID_PARAM);
|
|
goto Err;
|
|
}
|
|
#endif
|
|
if (io->use_scaling || WebPIsPremultipliedMode(dec->output->colorspace)) {
|
|
// need the alpha-multiply functions for premultiplied output or rescaling
|
|
WebPInitAlphaProcessing();
|
|
}
|
|
|
|
if (!WebPIsRGBMode(dec->output->colorspace)) {
|
|
WebPInitConvertARGBToYUV();
|
|
if (dec->output->u.YUVA.a != NULL) WebPInitAlphaProcessing();
|
|
}
|
|
if (dec->incremental) {
|
|
if (dec->hdr.color_cache_size > 0 &&
|
|
dec->hdr.saved_color_cache.colors == NULL) {
|
|
if (!VP8LColorCacheInit(&dec->hdr.saved_color_cache,
|
|
dec->hdr.color_cache.hash_bits)) {
|
|
VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY);
|
|
goto Err;
|
|
}
|
|
}
|
|
}
|
|
dec->state = READ_DATA;
|
|
}
|
|
|
|
// Decode.
|
|
if (!DecodeImageData(dec, dec->pixels, dec->width, dec->height,
|
|
io->crop_bottom, ProcessRows)) {
|
|
goto Err;
|
|
}
|
|
|
|
params->last_y = dec->last_out_row;
|
|
return 1;
|
|
|
|
Err:
|
|
VP8LClear(dec);
|
|
assert(dec->status != VP8_STATUS_OK);
|
|
return 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dec/webp_dec.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Main decoding functions for WEBP images.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// RIFF layout is:
|
|
// Offset tag
|
|
// 0...3 "RIFF" 4-byte tag
|
|
// 4...7 size of image data (including metadata) starting at offset 8
|
|
// 8...11 "WEBP" our form-type signature
|
|
// The RIFF container (12 bytes) is followed by appropriate chunks:
|
|
// 12..15 "VP8 ": 4-bytes tags, signaling the use of VP8 video format
|
|
// 16..19 size of the raw VP8 image data, starting at offset 20
|
|
// 20.... the VP8 bytes
|
|
// Or,
|
|
// 12..15 "VP8L": 4-bytes tags, signaling the use of VP8L lossless format
|
|
// 16..19 size of the raw VP8L image data, starting at offset 20
|
|
// 20.... the VP8L bytes
|
|
// Or,
|
|
// 12..15 "VP8X": 4-bytes tags, describing the extended-VP8 chunk.
|
|
// 16..19 size of the VP8X chunk starting at offset 20.
|
|
// 20..23 VP8X flags bit-map corresponding to the chunk-types present.
|
|
// 24..26 Width of the Canvas Image.
|
|
// 27..29 Height of the Canvas Image.
|
|
// There can be extra chunks after the "VP8X" chunk (ICCP, ANMF, VP8, VP8L,
|
|
// XMP, EXIF ...)
|
|
// All sizes are in little-endian order.
|
|
// Note: chunk data size must be padded to multiple of 2 when written.
|
|
|
|
// Validates the RIFF container (if detected) and skips over it.
|
|
// If a RIFF container is detected, returns:
|
|
// VP8_STATUS_BITSTREAM_ERROR for invalid header,
|
|
// VP8_STATUS_NOT_ENOUGH_DATA for truncated data if have_all_data is true,
|
|
// and VP8_STATUS_OK otherwise.
|
|
// In case there are not enough bytes (partial RIFF container), return 0 for
|
|
// *riff_size. Else return the RIFF size extracted from the header.
|
|
static VP8StatusCode ParseRIFF(const uint8_t* WEBP_COUNTED_BY(*data_size) *
|
|
WEBP_SINGLE const data,
|
|
size_t* WEBP_SINGLE const data_size,
|
|
int have_all_data,
|
|
size_t* WEBP_SINGLE const riff_size) {
|
|
assert(data != NULL);
|
|
assert(data_size != NULL);
|
|
assert(riff_size != NULL);
|
|
|
|
*riff_size = 0; // Default: no RIFF present.
|
|
if (*data_size >= RIFF_HEADER_SIZE && !memcmp(*data, "RIFF", TAG_SIZE)) {
|
|
if (memcmp(*data + 8, "WEBP", TAG_SIZE)) {
|
|
return VP8_STATUS_BITSTREAM_ERROR; // Wrong image file signature.
|
|
} else {
|
|
const uint32_t size = GetLE32(*data + TAG_SIZE);
|
|
// Check that we have at least one chunk (i.e "WEBP" + "VP8?nnnn").
|
|
if (size < TAG_SIZE + CHUNK_HEADER_SIZE) {
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
if (size > MAX_CHUNK_PAYLOAD) {
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) {
|
|
return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream.
|
|
}
|
|
// We have a RIFF container. Skip it.
|
|
*riff_size = size;
|
|
*data_size -= RIFF_HEADER_SIZE;
|
|
*data += RIFF_HEADER_SIZE;
|
|
}
|
|
}
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
// Validates the VP8X header and skips over it.
|
|
// Returns VP8_STATUS_BITSTREAM_ERROR for invalid VP8X header,
|
|
// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and
|
|
// VP8_STATUS_OK otherwise.
|
|
// If a VP8X chunk is found, found_vp8x is set to true and *width_ptr,
|
|
// *height_ptr and *flags_ptr are set to the corresponding values extracted
|
|
// from the VP8X chunk.
|
|
static VP8StatusCode ParseVP8X(const uint8_t* WEBP_COUNTED_BY(*data_size) *
|
|
WEBP_SINGLE const data,
|
|
size_t* WEBP_SINGLE const data_size,
|
|
int* WEBP_SINGLE const found_vp8x,
|
|
int* WEBP_SINGLE const width_ptr,
|
|
int* WEBP_SINGLE const height_ptr,
|
|
uint32_t* WEBP_SINGLE const flags_ptr) {
|
|
const uint32_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;
|
|
assert(data != NULL);
|
|
assert(data_size != NULL);
|
|
assert(found_vp8x != NULL);
|
|
|
|
*found_vp8x = 0;
|
|
|
|
if (*data_size < CHUNK_HEADER_SIZE) {
|
|
return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data.
|
|
}
|
|
|
|
if (!memcmp(*data, "VP8X", TAG_SIZE)) {
|
|
int width, height;
|
|
uint32_t flags;
|
|
const uint32_t chunk_size = GetLE32(*data + TAG_SIZE);
|
|
if (chunk_size != VP8X_CHUNK_SIZE) {
|
|
return VP8_STATUS_BITSTREAM_ERROR; // Wrong chunk size.
|
|
}
|
|
|
|
// Verify if enough data is available to validate the VP8X chunk.
|
|
if (*data_size < vp8x_size) {
|
|
return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data.
|
|
}
|
|
flags = GetLE32(*data + 8);
|
|
width = 1 + GetLE24(*data + 12);
|
|
height = 1 + GetLE24(*data + 15);
|
|
if (width * (uint64_t)height >= MAX_IMAGE_AREA) {
|
|
return VP8_STATUS_BITSTREAM_ERROR; // image is too large
|
|
}
|
|
|
|
if (flags_ptr != NULL) *flags_ptr = flags;
|
|
if (width_ptr != NULL) *width_ptr = width;
|
|
if (height_ptr != NULL) *height_ptr = height;
|
|
// Skip over VP8X header bytes.
|
|
*data_size -= vp8x_size;
|
|
*data += vp8x_size;
|
|
*found_vp8x = 1;
|
|
}
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
// Skips to the next VP8/VP8L chunk header in the data given the size of the
|
|
// RIFF chunk 'riff_size'.
|
|
// Returns VP8_STATUS_BITSTREAM_ERROR if any invalid chunk size is encountered,
|
|
// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and
|
|
// VP8_STATUS_OK otherwise.
|
|
// If an alpha chunk is found, *alpha_data and *alpha_size are set
|
|
// appropriately.
|
|
static VP8StatusCode ParseOptionalChunks(
|
|
const uint8_t* WEBP_COUNTED_BY(*data_size) * WEBP_SINGLE const data,
|
|
size_t* WEBP_SINGLE const data_size, size_t const riff_size,
|
|
const uint8_t* WEBP_COUNTED_BY(*alpha_size) * WEBP_SINGLE const alpha_data,
|
|
size_t* WEBP_SINGLE const alpha_size) {
|
|
size_t buf_size;
|
|
const uint8_t* WEBP_COUNTED_BY(buf_size) buf;
|
|
uint64_t total_size = TAG_SIZE + // "WEBP".
|
|
CHUNK_HEADER_SIZE + // "VP8Xnnnn".
|
|
VP8X_CHUNK_SIZE; // data.
|
|
assert(data != NULL);
|
|
assert(data_size != NULL);
|
|
buf = *data;
|
|
buf_size = *data_size;
|
|
|
|
assert(alpha_data != NULL);
|
|
assert(alpha_size != NULL);
|
|
*alpha_data = NULL;
|
|
*alpha_size = 0;
|
|
|
|
while (1) {
|
|
uint32_t chunk_size;
|
|
uint32_t disk_chunk_size; // chunk_size with padding
|
|
|
|
*data_size = buf_size;
|
|
*data = buf;
|
|
|
|
if (buf_size < CHUNK_HEADER_SIZE) { // Insufficient data.
|
|
return VP8_STATUS_NOT_ENOUGH_DATA;
|
|
}
|
|
|
|
chunk_size = GetLE32(buf + TAG_SIZE);
|
|
if (chunk_size > MAX_CHUNK_PAYLOAD) {
|
|
return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size.
|
|
}
|
|
// For odd-sized chunk-payload, there's one byte padding at the end.
|
|
disk_chunk_size = (CHUNK_HEADER_SIZE + chunk_size + 1) & ~1u;
|
|
total_size += disk_chunk_size;
|
|
|
|
// Check that total bytes skipped so far does not exceed riff_size.
|
|
if (riff_size > 0 && (total_size > riff_size)) {
|
|
return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size.
|
|
}
|
|
|
|
// Start of a (possibly incomplete) VP8/VP8L chunk implies that we have
|
|
// parsed all the optional chunks.
|
|
// Note: This check must occur before the check 'buf_size < disk_chunk_size'
|
|
// below to allow incomplete VP8/VP8L chunks.
|
|
if (!memcmp(buf, "VP8 ", TAG_SIZE) || !memcmp(buf, "VP8L", TAG_SIZE)) {
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
if (buf_size < disk_chunk_size) { // Insufficient data.
|
|
return VP8_STATUS_NOT_ENOUGH_DATA;
|
|
}
|
|
|
|
if (!memcmp(buf, "ALPH", TAG_SIZE)) { // A valid ALPH header.
|
|
*alpha_data = buf + CHUNK_HEADER_SIZE;
|
|
*alpha_size = chunk_size;
|
|
}
|
|
|
|
// We have a full and valid chunk; skip it.
|
|
buf += disk_chunk_size;
|
|
buf_size -= disk_chunk_size;
|
|
}
|
|
}
|
|
|
|
// Validates the VP8/VP8L Header ("VP8 nnnn" or "VP8L nnnn") and skips over it.
|
|
// Returns VP8_STATUS_BITSTREAM_ERROR for invalid (chunk larger than
|
|
// riff_size) VP8/VP8L header,
|
|
// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and
|
|
// VP8_STATUS_OK otherwise.
|
|
// If a VP8/VP8L chunk is found, *chunk_size is set to the total number of bytes
|
|
// extracted from the VP8/VP8L chunk header.
|
|
// The flag '*is_lossless' is set to 1 in case of VP8L chunk / raw VP8L data.
|
|
static VP8StatusCode ParseVP8Header(const uint8_t* WEBP_COUNTED_BY(*data_size) *
|
|
WEBP_SINGLE const data_ptr,
|
|
size_t* WEBP_SINGLE const data_size,
|
|
int have_all_data, size_t riff_size,
|
|
size_t* WEBP_SINGLE const chunk_size,
|
|
int* WEBP_SINGLE const is_lossless) {
|
|
const size_t local_data_size = *data_size;
|
|
const uint8_t* WEBP_COUNTED_BY(local_data_size) const data = *data_ptr;
|
|
const int is_vp8 = !memcmp(data, "VP8 ", TAG_SIZE);
|
|
const int is_vp8l = !memcmp(data, "VP8L", TAG_SIZE);
|
|
const uint32_t minimal_size =
|
|
TAG_SIZE + CHUNK_HEADER_SIZE; // "WEBP" + "VP8 nnnn" OR
|
|
// "WEBP" + "VP8Lnnnn"
|
|
(void)local_data_size;
|
|
assert(data != NULL);
|
|
assert(data_size != NULL);
|
|
assert(chunk_size != NULL);
|
|
assert(is_lossless != NULL);
|
|
|
|
if (*data_size < CHUNK_HEADER_SIZE) {
|
|
return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data.
|
|
}
|
|
|
|
if (is_vp8 || is_vp8l) {
|
|
// Bitstream contains VP8/VP8L header.
|
|
const uint32_t size = GetLE32(data + TAG_SIZE);
|
|
if ((riff_size >= minimal_size) && (size > riff_size - minimal_size)) {
|
|
return VP8_STATUS_BITSTREAM_ERROR; // Inconsistent size information.
|
|
}
|
|
if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) {
|
|
return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream.
|
|
}
|
|
// Skip over CHUNK_HEADER_SIZE bytes from VP8/VP8L Header.
|
|
*chunk_size = size;
|
|
*data_size -= CHUNK_HEADER_SIZE;
|
|
*data_ptr += CHUNK_HEADER_SIZE;
|
|
*is_lossless = is_vp8l;
|
|
} else {
|
|
// Raw VP8/VP8L bitstream (no header).
|
|
*is_lossless = VP8LCheckSignature(data, *data_size);
|
|
*chunk_size = *data_size;
|
|
}
|
|
|
|
return VP8_STATUS_OK;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Fetch '*width', '*height', '*has_alpha' and fill out 'headers' based on
|
|
// 'data'. All the output parameters may be NULL. If 'headers' is NULL only the
|
|
// minimal amount will be read to fetch the remaining parameters.
|
|
// If 'headers' is non-NULL this function will attempt to locate both alpha
|
|
// data (with or without a VP8X chunk) and the bitstream chunk (VP8/VP8L).
|
|
// Note: The following chunk sequences (before the raw VP8/VP8L data) are
|
|
// considered valid by this function:
|
|
// RIFF + VP8(L)
|
|
// RIFF + VP8X + (optional chunks) + VP8(L)
|
|
// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose.
|
|
// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose.
|
|
static VP8StatusCode ParseHeadersInternal(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size_param) data_param,
|
|
size_t data_size_param, int* const width, int* const height,
|
|
int* const has_alpha, int* const has_animation, int* const format,
|
|
WebPHeaderStructure* const headers) {
|
|
size_t data_size = data_size_param;
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) data = data_param;
|
|
int canvas_width = 0;
|
|
int canvas_height = 0;
|
|
int image_width = 0;
|
|
int image_height = 0;
|
|
int found_riff = 0;
|
|
int found_vp8x = 0;
|
|
int animation_present = 0;
|
|
const int have_all_data = (headers != NULL) ? headers->have_all_data : 0;
|
|
|
|
VP8StatusCode status;
|
|
WebPHeaderStructure hdrs;
|
|
|
|
if (data == NULL || data_size < RIFF_HEADER_SIZE) {
|
|
return VP8_STATUS_NOT_ENOUGH_DATA;
|
|
}
|
|
WEBP_UNSAFE_MEMSET(&hdrs, 0, sizeof(hdrs));
|
|
hdrs.data = data;
|
|
hdrs.data_size = data_size;
|
|
|
|
// Skip over RIFF header.
|
|
status = ParseRIFF(&data, &data_size, have_all_data, &hdrs.riff_size);
|
|
if (status != VP8_STATUS_OK) {
|
|
return status; // Wrong RIFF header / insufficient data.
|
|
}
|
|
found_riff = (hdrs.riff_size > 0);
|
|
|
|
// Skip over VP8X.
|
|
{
|
|
uint32_t flags = 0;
|
|
status = ParseVP8X(&data, &data_size, &found_vp8x, &canvas_width,
|
|
&canvas_height, &flags);
|
|
if (status != VP8_STATUS_OK) {
|
|
return status; // Wrong VP8X / insufficient data.
|
|
}
|
|
animation_present = !!(flags & ANIMATION_FLAG);
|
|
if (!found_riff && found_vp8x) {
|
|
// Note: This restriction may be removed in the future, if it becomes
|
|
// necessary to send VP8X chunk to the decoder.
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
if (has_alpha != NULL) *has_alpha = !!(flags & ALPHA_FLAG);
|
|
if (has_animation != NULL) *has_animation = animation_present;
|
|
if (format != NULL) *format = 0; // default = undefined
|
|
|
|
image_width = canvas_width;
|
|
image_height = canvas_height;
|
|
if (found_vp8x && animation_present && headers == NULL) {
|
|
status = VP8_STATUS_OK;
|
|
goto ReturnWidthHeight; // Just return features from VP8X header.
|
|
}
|
|
}
|
|
|
|
if (data_size < TAG_SIZE) {
|
|
status = VP8_STATUS_NOT_ENOUGH_DATA;
|
|
goto ReturnWidthHeight;
|
|
}
|
|
|
|
// Skip over optional chunks if data started with "RIFF + VP8X" or "ALPH".
|
|
if ((found_riff && found_vp8x) ||
|
|
(!found_riff && !found_vp8x && !memcmp(data, "ALPH", TAG_SIZE))) {
|
|
size_t local_alpha_data_size = 0;
|
|
const uint8_t* WEBP_COUNTED_BY(local_alpha_data_size) local_alpha_data =
|
|
NULL;
|
|
status = ParseOptionalChunks(&data, &data_size, hdrs.riff_size,
|
|
&local_alpha_data, &local_alpha_data_size);
|
|
if (status != VP8_STATUS_OK) {
|
|
goto ReturnWidthHeight; // Invalid chunk size / insufficient data.
|
|
}
|
|
hdrs.alpha_data = local_alpha_data;
|
|
hdrs.alpha_data_size = local_alpha_data_size;
|
|
}
|
|
|
|
// Skip over VP8/VP8L header.
|
|
status = ParseVP8Header(&data, &data_size, have_all_data, hdrs.riff_size,
|
|
&hdrs.compressed_size, &hdrs.is_lossless);
|
|
if (status != VP8_STATUS_OK) {
|
|
goto ReturnWidthHeight; // Wrong VP8/VP8L chunk-header / insufficient data.
|
|
}
|
|
if (hdrs.compressed_size > MAX_CHUNK_PAYLOAD) {
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
|
|
if (format != NULL && !animation_present) {
|
|
*format = hdrs.is_lossless ? 2 : 1;
|
|
}
|
|
|
|
if (!hdrs.is_lossless) {
|
|
if (data_size < VP8_FRAME_HEADER_SIZE) {
|
|
status = VP8_STATUS_NOT_ENOUGH_DATA;
|
|
goto ReturnWidthHeight;
|
|
}
|
|
// Validates raw VP8 data.
|
|
if (!VP8GetInfo(data, data_size, (uint32_t)hdrs.compressed_size,
|
|
&image_width, &image_height)) {
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
} else {
|
|
if (data_size < VP8L_FRAME_HEADER_SIZE) {
|
|
status = VP8_STATUS_NOT_ENOUGH_DATA;
|
|
goto ReturnWidthHeight;
|
|
}
|
|
// Validates raw VP8L data.
|
|
if (!VP8LGetInfo(data, data_size, &image_width, &image_height, has_alpha)) {
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
}
|
|
// Validates image size coherency.
|
|
if (found_vp8x) {
|
|
if (canvas_width != image_width || canvas_height != image_height) {
|
|
return VP8_STATUS_BITSTREAM_ERROR;
|
|
}
|
|
}
|
|
if (headers != NULL) {
|
|
*headers = hdrs;
|
|
headers->offset = data - headers->data;
|
|
assert((uint64_t)(data - headers->data) < MAX_CHUNK_PAYLOAD);
|
|
assert(headers->offset == headers->data_size - data_size);
|
|
}
|
|
ReturnWidthHeight:
|
|
if (status == VP8_STATUS_OK ||
|
|
(status == VP8_STATUS_NOT_ENOUGH_DATA && found_vp8x && headers == NULL)) {
|
|
if (has_alpha != NULL) {
|
|
// If the data did not contain a VP8X/VP8L chunk the only definitive way
|
|
// to set this is by looking for alpha data (from an ALPH chunk).
|
|
*has_alpha |= (hdrs.alpha_data != NULL);
|
|
}
|
|
if (width != NULL) *width = image_width;
|
|
if (height != NULL) *height = image_height;
|
|
return VP8_STATUS_OK;
|
|
} else {
|
|
return status;
|
|
}
|
|
}
|
|
|
|
VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers) {
|
|
// status is marked volatile as a workaround for a clang-3.8 (aarch64) bug
|
|
volatile VP8StatusCode status;
|
|
int has_animation = 0;
|
|
assert(headers != NULL);
|
|
// fill out headers, ignore width/height/has_alpha.
|
|
{
|
|
const uint8_t* WEBP_BIDI_INDEXABLE const bounded_data =
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(const uint8_t*, headers->data,
|
|
headers->data_size);
|
|
status = ParseHeadersInternal(bounded_data, headers->data_size, NULL, NULL,
|
|
NULL, &has_animation, NULL, headers);
|
|
}
|
|
if (status == VP8_STATUS_OK || status == VP8_STATUS_NOT_ENOUGH_DATA) {
|
|
// The WebPDemux API + libwebp can be used to decode individual
|
|
// uncomposited frames or the WebPAnimDecoder can be used to fully
|
|
// reconstruct them (see webp/demux.h).
|
|
if (has_animation) {
|
|
status = VP8_STATUS_UNSUPPORTED_FEATURE;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPDecParams
|
|
|
|
void WebPResetDecParams(WebPDecParams* const params) {
|
|
if (params != NULL) {
|
|
WEBP_UNSAFE_MEMSET(params, 0, sizeof(*params));
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// "Into" decoding variants
|
|
|
|
// Main flow
|
|
WEBP_NODISCARD static VP8StatusCode DecodeInto(
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) const data, size_t data_size,
|
|
WebPDecParams* const params) {
|
|
VP8StatusCode status;
|
|
VP8Io io;
|
|
WebPHeaderStructure headers;
|
|
|
|
headers.data = data;
|
|
headers.data_size = data_size;
|
|
headers.have_all_data = 1;
|
|
status = WebPParseHeaders(&headers); // Process Pre-VP8 chunks.
|
|
if (status != VP8_STATUS_OK) {
|
|
return status;
|
|
}
|
|
|
|
assert(params != NULL);
|
|
if (!VP8InitIo(&io)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
io.data = headers.data + headers.offset;
|
|
io.data_size = headers.data_size - headers.offset;
|
|
WebPInitCustomIo(params, &io); // Plug the I/O functions.
|
|
|
|
if (!headers.is_lossless) {
|
|
VP8Decoder* const dec = VP8New();
|
|
if (dec == NULL) {
|
|
return VP8_STATUS_OUT_OF_MEMORY;
|
|
}
|
|
dec->alpha_data = headers.alpha_data;
|
|
dec->alpha_data_size = headers.alpha_data_size;
|
|
|
|
// Decode bitstream header, update io->width/io->height.
|
|
if (!VP8GetHeaders(dec, &io)) {
|
|
status = dec->status; // An error occurred. Grab error status.
|
|
} else {
|
|
// Allocate/check output buffers.
|
|
status = WebPAllocateDecBuffer(io.width, io.height, params->options,
|
|
params->output);
|
|
if (status == VP8_STATUS_OK) { // Decode
|
|
// This change must be done before calling VP8Decode()
|
|
dec->mt_method =
|
|
VP8GetThreadMethod(params->options, &headers, io.width, io.height);
|
|
VP8InitDithering(params->options, dec);
|
|
if (!VP8Decode(dec, &io)) {
|
|
status = dec->status;
|
|
}
|
|
}
|
|
}
|
|
VP8Delete(dec);
|
|
} else {
|
|
VP8LDecoder* const dec = VP8LNew();
|
|
if (dec == NULL) {
|
|
return VP8_STATUS_OUT_OF_MEMORY;
|
|
}
|
|
if (!VP8LDecodeHeader(dec, &io)) {
|
|
status = dec->status; // An error occurred. Grab error status.
|
|
} else {
|
|
// Allocate/check output buffers.
|
|
status = WebPAllocateDecBuffer(io.width, io.height, params->options,
|
|
params->output);
|
|
if (status == VP8_STATUS_OK) { // Decode
|
|
if (!VP8LDecodeImage(dec)) {
|
|
status = dec->status;
|
|
}
|
|
}
|
|
}
|
|
VP8LDelete(dec);
|
|
}
|
|
|
|
if (status != VP8_STATUS_OK) {
|
|
WebPFreeDecBuffer(params->output);
|
|
} else {
|
|
if (params->options != NULL && params->options->flip) {
|
|
// This restores the original stride values if options->flip was used
|
|
// during the call to WebPAllocateDecBuffer above.
|
|
status = WebPFlipBuffer(params->output);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Helpers
|
|
WEBP_NODISCARD static uint8_t* DecodeIntoRGBABuffer(
|
|
WEBP_CSP_MODE colorspace,
|
|
const uint8_t* WEBP_COUNTED_BY(data_size) const data, size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(size) const rgba, int stride, size_t size) {
|
|
WebPDecParams params;
|
|
WebPDecBuffer buf;
|
|
if (rgba == NULL || !WebPInitDecBuffer(&buf)) {
|
|
return NULL;
|
|
}
|
|
WebPResetDecParams(¶ms);
|
|
params.output = &buf;
|
|
buf.colorspace = colorspace;
|
|
buf.u.RGBA.rgba = rgba;
|
|
buf.u.RGBA.stride = stride;
|
|
buf.u.RGBA.size = size;
|
|
buf.is_external_memory = 1;
|
|
if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) {
|
|
return NULL;
|
|
}
|
|
return rgba;
|
|
}
|
|
|
|
uint8_t* WebPDecodeRGBInto(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(size) output, size_t size,
|
|
int stride) {
|
|
return DecodeIntoRGBABuffer(MODE_RGB, data, data_size, output, stride, size);
|
|
}
|
|
|
|
uint8_t* WebPDecodeRGBAInto(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(size) output, size_t size,
|
|
int stride) {
|
|
return DecodeIntoRGBABuffer(MODE_RGBA, data, data_size, output, stride, size);
|
|
}
|
|
|
|
uint8_t* WebPDecodeARGBInto(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(size) output, size_t size,
|
|
int stride) {
|
|
return DecodeIntoRGBABuffer(MODE_ARGB, data, data_size, output, stride, size);
|
|
}
|
|
|
|
uint8_t* WebPDecodeBGRInto(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(size) output, size_t size,
|
|
int stride) {
|
|
return DecodeIntoRGBABuffer(MODE_BGR, data, data_size, output, stride, size);
|
|
}
|
|
|
|
uint8_t* WebPDecodeBGRAInto(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(size) output, size_t size,
|
|
int stride) {
|
|
return DecodeIntoRGBABuffer(MODE_BGRA, data, data_size, output, stride, size);
|
|
}
|
|
|
|
uint8_t* WebPDecodeYUVInto(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size,
|
|
uint8_t* WEBP_COUNTED_BY(luma_size) luma,
|
|
size_t luma_size, int luma_stride,
|
|
uint8_t* WEBP_COUNTED_BY(u_size) u, size_t u_size,
|
|
int u_stride, uint8_t* WEBP_COUNTED_BY(v_size) v,
|
|
size_t v_size, int v_stride) {
|
|
WebPDecParams params;
|
|
WebPDecBuffer output;
|
|
if (luma == NULL || !WebPInitDecBuffer(&output)) return NULL;
|
|
WebPResetDecParams(¶ms);
|
|
params.output = &output;
|
|
output.colorspace = MODE_YUV;
|
|
output.u.YUVA.y = luma;
|
|
output.u.YUVA.y_stride = luma_stride;
|
|
output.u.YUVA.y_size = luma_size;
|
|
output.u.YUVA.u = u;
|
|
output.u.YUVA.u_stride = u_stride;
|
|
output.u.YUVA.u_size = u_size;
|
|
output.u.YUVA.v = v;
|
|
output.u.YUVA.v_stride = v_stride;
|
|
output.u.YUVA.v_size = v_size;
|
|
output.is_external_memory = 1;
|
|
if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) {
|
|
return NULL;
|
|
}
|
|
return luma;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
WEBP_NODISCARD static uint8_t* Decode(WEBP_CSP_MODE mode,
|
|
const uint8_t* WEBP_COUNTED_BY(data_size)
|
|
const data,
|
|
size_t data_size, int* const width,
|
|
int* const height,
|
|
WebPDecBuffer* const keep_info) {
|
|
WebPDecParams params;
|
|
WebPDecBuffer output;
|
|
|
|
if (!WebPInitDecBuffer(&output)) {
|
|
return NULL;
|
|
}
|
|
WebPResetDecParams(¶ms);
|
|
params.output = &output;
|
|
output.colorspace = mode;
|
|
|
|
// Retrieve (and report back) the required dimensions from bitstream.
|
|
if (!WebPGetInfo(data, data_size, &output.width, &output.height)) {
|
|
return NULL;
|
|
}
|
|
if (width != NULL) *width = output.width;
|
|
if (height != NULL) *height = output.height;
|
|
|
|
// Decode
|
|
if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) {
|
|
return NULL;
|
|
}
|
|
if (keep_info != NULL) { // keep track of the side-info
|
|
WebPCopyDecBuffer(&output, keep_info);
|
|
}
|
|
// return decoded samples (don't clear 'output'!)
|
|
return WebPIsRGBMode(mode) ? output.u.RGBA.rgba : output.u.YUVA.y;
|
|
}
|
|
|
|
uint8_t* WebPDecodeRGB(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* width, int* height) {
|
|
return Decode(MODE_RGB, data, data_size, width, height, NULL);
|
|
}
|
|
|
|
uint8_t* WebPDecodeRGBA(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* width, int* height) {
|
|
return Decode(MODE_RGBA, data, data_size, width, height, NULL);
|
|
}
|
|
|
|
uint8_t* WebPDecodeARGB(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* width, int* height) {
|
|
return Decode(MODE_ARGB, data, data_size, width, height, NULL);
|
|
}
|
|
|
|
uint8_t* WebPDecodeBGR(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* width, int* height) {
|
|
return Decode(MODE_BGR, data, data_size, width, height, NULL);
|
|
}
|
|
|
|
uint8_t* WebPDecodeBGRA(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* width, int* height) {
|
|
return Decode(MODE_BGRA, data, data_size, width, height, NULL);
|
|
}
|
|
|
|
uint8_t* WebPDecodeYUV(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* width, int* height, uint8_t** u,
|
|
uint8_t** v, int* stride, int* uv_stride) {
|
|
// data, width and height are checked by Decode().
|
|
if (u == NULL || v == NULL || stride == NULL || uv_stride == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
{
|
|
WebPDecBuffer output; // only to preserve the side-infos
|
|
uint8_t* const out =
|
|
Decode(MODE_YUV, data, data_size, width, height, &output);
|
|
|
|
if (out != NULL) {
|
|
const WebPYUVABuffer* const buf = &output.u.YUVA;
|
|
*u = buf->u;
|
|
*v = buf->v;
|
|
*stride = buf->y_stride;
|
|
*uv_stride = buf->u_stride;
|
|
assert(buf->u_stride == buf->v_stride);
|
|
}
|
|
return out;
|
|
}
|
|
}
|
|
|
|
static void DefaultFeatures(WebPBitstreamFeatures* const features) {
|
|
assert(features != NULL);
|
|
WEBP_UNSAFE_MEMSET(features, 0, sizeof(*features));
|
|
}
|
|
|
|
static VP8StatusCode GetFeatures(const uint8_t* WEBP_COUNTED_BY(data_size)
|
|
const data,
|
|
size_t data_size,
|
|
WebPBitstreamFeatures* const features) {
|
|
if (features == NULL || data == NULL) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
DefaultFeatures(features);
|
|
|
|
// Only parse enough of the data to retrieve the features.
|
|
return ParseHeadersInternal(
|
|
data, data_size, &features->width, &features->height,
|
|
&features->has_alpha, &features->has_animation, &features->format, NULL);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPGetInfo()
|
|
|
|
int WebPGetInfo(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, int* width, int* height) {
|
|
WebPBitstreamFeatures features;
|
|
|
|
if (GetFeatures(data, data_size, &features) != VP8_STATUS_OK) {
|
|
return 0;
|
|
}
|
|
|
|
if (width != NULL) {
|
|
*width = features.width;
|
|
}
|
|
if (height != NULL) {
|
|
*height = features.height;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Advance decoding API
|
|
|
|
int WebPInitDecoderConfigInternal(WebPDecoderConfig* config, int version) {
|
|
if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) {
|
|
return 0; // version mismatch
|
|
}
|
|
if (config == NULL) {
|
|
return 0;
|
|
}
|
|
WEBP_UNSAFE_MEMSET(config, 0, sizeof(*config));
|
|
DefaultFeatures(&config->input);
|
|
if (!WebPInitDecBuffer(&config->output)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int WebPCheckCropDimensionsBasic(int x, int y, int w, int h) {
|
|
return !(x < 0 || y < 0 || w <= 0 || h <= 0);
|
|
}
|
|
|
|
int WebPValidateDecoderConfig(const WebPDecoderConfig* config) {
|
|
const WebPDecoderOptions* options;
|
|
if (config == NULL) return 0;
|
|
if (!IsValidColorspace(config->output.colorspace)) {
|
|
return 0;
|
|
}
|
|
|
|
options = &config->options;
|
|
// bypass_filtering, no_fancy_upsampling, use_cropping, use_scaling,
|
|
// use_threads, flip can be any integer and are interpreted as boolean.
|
|
|
|
// Check for cropping.
|
|
if (options->use_cropping && !WebPCheckCropDimensionsBasic(
|
|
options->crop_left, options->crop_top,
|
|
options->crop_width, options->crop_height)) {
|
|
return 0;
|
|
}
|
|
// Check for scaling.
|
|
if (options->use_scaling &&
|
|
(options->scaled_width < 0 || options->scaled_height < 0 ||
|
|
(options->scaled_width == 0 && options->scaled_height == 0))) {
|
|
return 0;
|
|
}
|
|
|
|
// In case the WebPBitstreamFeatures has been filled in, check further.
|
|
if (config->input.width > 0 || config->input.height > 0) {
|
|
int scaled_width = options->scaled_width;
|
|
int scaled_height = options->scaled_height;
|
|
if (options->use_cropping &&
|
|
!WebPCheckCropDimensions(config->input.width, config->input.height,
|
|
options->crop_left, options->crop_top,
|
|
options->crop_width, options->crop_height)) {
|
|
return 0;
|
|
}
|
|
if (options->use_scaling && !WebPRescalerGetScaledDimensions(
|
|
config->input.width, config->input.height,
|
|
&scaled_width, &scaled_height)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Check for dithering.
|
|
if (options->dithering_strength < 0 || options->dithering_strength > 100 ||
|
|
options->alpha_dithering_strength < 0 ||
|
|
options->alpha_dithering_strength > 100) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
VP8StatusCode WebPGetFeaturesInternal(const uint8_t* WEBP_COUNTED_BY(data_size)
|
|
data,
|
|
size_t data_size,
|
|
WebPBitstreamFeatures* features,
|
|
int version) {
|
|
if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) {
|
|
return VP8_STATUS_INVALID_PARAM; // version mismatch
|
|
}
|
|
if (features == NULL) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
return GetFeatures(data, data_size, features);
|
|
}
|
|
|
|
VP8StatusCode WebPDecode(const uint8_t* WEBP_COUNTED_BY(data_size) data,
|
|
size_t data_size, WebPDecoderConfig* config) {
|
|
WebPDecParams params;
|
|
VP8StatusCode status;
|
|
|
|
if (config == NULL) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
status = GetFeatures(data, data_size, &config->input);
|
|
if (status != VP8_STATUS_OK) {
|
|
if (status == VP8_STATUS_NOT_ENOUGH_DATA) {
|
|
return VP8_STATUS_BITSTREAM_ERROR; // Not-enough-data treated as error.
|
|
}
|
|
return status;
|
|
}
|
|
|
|
WebPResetDecParams(¶ms);
|
|
params.options = &config->options;
|
|
params.output = &config->output;
|
|
if (WebPAvoidSlowMemory(params.output, &config->input)) {
|
|
// decoding to slow memory: use a temporary in-mem buffer to decode into.
|
|
WebPDecBuffer in_mem_buffer;
|
|
if (!WebPInitDecBuffer(&in_mem_buffer)) {
|
|
return VP8_STATUS_INVALID_PARAM;
|
|
}
|
|
in_mem_buffer.colorspace = config->output.colorspace;
|
|
in_mem_buffer.width = config->input.width;
|
|
in_mem_buffer.height = config->input.height;
|
|
params.output = &in_mem_buffer;
|
|
status = DecodeInto(data, data_size, ¶ms);
|
|
if (status == VP8_STATUS_OK) { // do the slow-copy
|
|
status = WebPCopyDecBufferPixels(&in_mem_buffer, &config->output);
|
|
}
|
|
WebPFreeDecBuffer(&in_mem_buffer);
|
|
} else {
|
|
status = DecodeInto(data, data_size, ¶ms);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Cropping and rescaling.
|
|
|
|
int WebPCheckCropDimensions(int image_width, int image_height, int x, int y,
|
|
int w, int h) {
|
|
return WebPCheckCropDimensionsBasic(x, y, w, h) &&
|
|
!(x >= image_width || w > image_width || w > image_width - x ||
|
|
y >= image_height || h > image_height || h > image_height - y);
|
|
}
|
|
|
|
int WebPIoInitFromOptions(const WebPDecoderOptions* const options,
|
|
VP8Io* const io, WEBP_CSP_MODE src_colorspace) {
|
|
const int W = io->width;
|
|
const int H = io->height;
|
|
int x = 0, y = 0, w = W, h = H;
|
|
|
|
// Cropping
|
|
io->use_cropping = (options != NULL) && options->use_cropping;
|
|
if (io->use_cropping) {
|
|
w = options->crop_width;
|
|
h = options->crop_height;
|
|
x = options->crop_left;
|
|
y = options->crop_top;
|
|
if (!WebPIsRGBMode(src_colorspace)) { // only snap for YUV420
|
|
x &= ~1;
|
|
y &= ~1;
|
|
}
|
|
if (!WebPCheckCropDimensions(W, H, x, y, w, h)) {
|
|
return 0; // out of frame boundary error
|
|
}
|
|
}
|
|
io->crop_left = x;
|
|
io->crop_top = y;
|
|
io->crop_right = x + w;
|
|
io->crop_bottom = y + h;
|
|
io->mb_w = w;
|
|
io->mb_h = h;
|
|
|
|
// Scaling
|
|
io->use_scaling = (options != NULL) && options->use_scaling;
|
|
if (io->use_scaling) {
|
|
int scaled_width = options->scaled_width;
|
|
int scaled_height = options->scaled_height;
|
|
if (!WebPRescalerGetScaledDimensions(w, h, &scaled_width, &scaled_height)) {
|
|
return 0;
|
|
}
|
|
io->scaled_width = scaled_width;
|
|
io->scaled_height = scaled_height;
|
|
}
|
|
|
|
// Filter
|
|
io->bypass_filtering = (options != NULL) && options->bypass_filtering;
|
|
|
|
// Fancy upsampler
|
|
#ifdef FANCY_UPSAMPLING
|
|
io->fancy_upsampling = (options == NULL) || (!options->no_fancy_upsampling);
|
|
#endif
|
|
|
|
if (io->use_scaling) {
|
|
// disable filter (only for large downscaling ratio).
|
|
io->bypass_filtering |=
|
|
(io->scaled_width < W * 3 / 4) && (io->scaled_height < H * 3 / 4);
|
|
io->fancy_upsampling = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dsp/alpha_processing.c */
|
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Utilities for processing transparent channel.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
|
|
// Tables can be faster on some platform but incur some extra binary size (~2k).
|
|
#if !defined(USE_TABLES_FOR_ALPHA_MULT)
|
|
#define USE_TABLES_FOR_ALPHA_MULT 0 // ALTERNATE_CODE
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#define MFIX 24 // 24bit fixed-point arithmetic
|
|
#define HALF ((1u << MFIX) >> 1)
|
|
#define KINV_255 ((1u << MFIX) / 255u)
|
|
|
|
static uint32_t Mult(uint8_t x, uint32_t mult) {
|
|
const uint32_t v = (x * mult + HALF) >> MFIX;
|
|
assert(v <= 255); // <- 24bit precision is enough to ensure that.
|
|
return v;
|
|
}
|
|
|
|
#if (USE_TABLES_FOR_ALPHA_MULT == 1)
|
|
|
|
static const uint32_t kMultTables[2][256] = {
|
|
// (255u << MFIX) / alpha
|
|
{0x00000000, 0xff000000, 0x7f800000, 0x55000000, 0x3fc00000, 0x33000000,
|
|
0x2a800000, 0x246db6db, 0x1fe00000, 0x1c555555, 0x19800000, 0x172e8ba2,
|
|
0x15400000, 0x139d89d8, 0x1236db6d, 0x11000000, 0x0ff00000, 0x0f000000,
|
|
0x0e2aaaaa, 0x0d6bca1a, 0x0cc00000, 0x0c249249, 0x0b9745d1, 0x0b1642c8,
|
|
0x0aa00000, 0x0a333333, 0x09cec4ec, 0x0971c71c, 0x091b6db6, 0x08cb08d3,
|
|
0x08800000, 0x0839ce73, 0x07f80000, 0x07ba2e8b, 0x07800000, 0x07492492,
|
|
0x07155555, 0x06e45306, 0x06b5e50d, 0x0689d89d, 0x06600000, 0x063831f3,
|
|
0x06124924, 0x05ee23b8, 0x05cba2e8, 0x05aaaaaa, 0x058b2164, 0x056cefa8,
|
|
0x05500000, 0x05343eb1, 0x05199999, 0x05000000, 0x04e76276, 0x04cfb2b7,
|
|
0x04b8e38e, 0x04a2e8ba, 0x048db6db, 0x0479435e, 0x04658469, 0x045270d0,
|
|
0x04400000, 0x042e29f7, 0x041ce739, 0x040c30c3, 0x03fc0000, 0x03ec4ec4,
|
|
0x03dd1745, 0x03ce540f, 0x03c00000, 0x03b21642, 0x03a49249, 0x03976fc6,
|
|
0x038aaaaa, 0x037e3f1f, 0x03722983, 0x03666666, 0x035af286, 0x034fcace,
|
|
0x0344ec4e, 0x033a5440, 0x03300000, 0x0325ed09, 0x031c18f9, 0x0312818a,
|
|
0x03092492, 0x03000000, 0x02f711dc, 0x02ee5846, 0x02e5d174, 0x02dd7baf,
|
|
0x02d55555, 0x02cd5cd5, 0x02c590b2, 0x02bdef7b, 0x02b677d4, 0x02af286b,
|
|
0x02a80000, 0x02a0fd5c, 0x029a1f58, 0x029364d9, 0x028ccccc, 0x0286562d,
|
|
0x02800000, 0x0279c952, 0x0273b13b, 0x026db6db, 0x0267d95b, 0x026217ec,
|
|
0x025c71c7, 0x0256e62a, 0x0251745d, 0x024c1bac, 0x0246db6d, 0x0241b2f9,
|
|
0x023ca1af, 0x0237a6f4, 0x0232c234, 0x022df2df, 0x02293868, 0x02249249,
|
|
0x02200000, 0x021b810e, 0x021714fb, 0x0212bb51, 0x020e739c, 0x020a3d70,
|
|
0x02061861, 0x02020408, 0x01fe0000, 0x01fa0be8, 0x01f62762, 0x01f25213,
|
|
0x01ee8ba2, 0x01ead3ba, 0x01e72a07, 0x01e38e38, 0x01e00000, 0x01dc7f10,
|
|
0x01d90b21, 0x01d5a3e9, 0x01d24924, 0x01cefa8d, 0x01cbb7e3, 0x01c880e5,
|
|
0x01c55555, 0x01c234f7, 0x01bf1f8f, 0x01bc14e5, 0x01b914c1, 0x01b61eed,
|
|
0x01b33333, 0x01b05160, 0x01ad7943, 0x01aaaaaa, 0x01a7e567, 0x01a5294a,
|
|
0x01a27627, 0x019fcbd2, 0x019d2a20, 0x019a90e7, 0x01980000, 0x01957741,
|
|
0x0192f684, 0x01907da4, 0x018e0c7c, 0x018ba2e8, 0x018940c5, 0x0186e5f0,
|
|
0x01849249, 0x018245ae, 0x01800000, 0x017dc11f, 0x017b88ee, 0x0179574e,
|
|
0x01772c23, 0x01750750, 0x0172e8ba, 0x0170d045, 0x016ebdd7, 0x016cb157,
|
|
0x016aaaaa, 0x0168a9b9, 0x0166ae6a, 0x0164b8a7, 0x0162c859, 0x0160dd67,
|
|
0x015ef7bd, 0x015d1745, 0x015b3bea, 0x01596596, 0x01579435, 0x0155c7b4,
|
|
0x01540000, 0x01523d03, 0x01507eae, 0x014ec4ec, 0x014d0fac, 0x014b5edc,
|
|
0x0149b26c, 0x01480a4a, 0x01466666, 0x0144c6af, 0x01432b16, 0x0141938b,
|
|
0x01400000, 0x013e7063, 0x013ce4a9, 0x013b5cc0, 0x0139d89d, 0x01385830,
|
|
0x0136db6d, 0x01356246, 0x0133ecad, 0x01327a97, 0x01310bf6, 0x012fa0be,
|
|
0x012e38e3, 0x012cd459, 0x012b7315, 0x012a150a, 0x0128ba2e, 0x01276276,
|
|
0x01260dd6, 0x0124bc44, 0x01236db6, 0x01222222, 0x0120d97c, 0x011f93bc,
|
|
0x011e50d7, 0x011d10c4, 0x011bd37a, 0x011a98ef, 0x0119611a, 0x01182bf2,
|
|
0x0116f96f, 0x0115c988, 0x01149c34, 0x0113716a, 0x01124924, 0x01112358,
|
|
0x01100000, 0x010edf12, 0x010dc087, 0x010ca458, 0x010b8a7d, 0x010a72f0,
|
|
0x01095da8, 0x01084a9f, 0x010739ce, 0x01062b2e, 0x01051eb8, 0x01041465,
|
|
0x01030c30, 0x01020612, 0x01010204, 0x01000000},
|
|
// alpha * KINV_255
|
|
{0x00000000, 0x00010101, 0x00020202, 0x00030303, 0x00040404, 0x00050505,
|
|
0x00060606, 0x00070707, 0x00080808, 0x00090909, 0x000a0a0a, 0x000b0b0b,
|
|
0x000c0c0c, 0x000d0d0d, 0x000e0e0e, 0x000f0f0f, 0x00101010, 0x00111111,
|
|
0x00121212, 0x00131313, 0x00141414, 0x00151515, 0x00161616, 0x00171717,
|
|
0x00181818, 0x00191919, 0x001a1a1a, 0x001b1b1b, 0x001c1c1c, 0x001d1d1d,
|
|
0x001e1e1e, 0x001f1f1f, 0x00202020, 0x00212121, 0x00222222, 0x00232323,
|
|
0x00242424, 0x00252525, 0x00262626, 0x00272727, 0x00282828, 0x00292929,
|
|
0x002a2a2a, 0x002b2b2b, 0x002c2c2c, 0x002d2d2d, 0x002e2e2e, 0x002f2f2f,
|
|
0x00303030, 0x00313131, 0x00323232, 0x00333333, 0x00343434, 0x00353535,
|
|
0x00363636, 0x00373737, 0x00383838, 0x00393939, 0x003a3a3a, 0x003b3b3b,
|
|
0x003c3c3c, 0x003d3d3d, 0x003e3e3e, 0x003f3f3f, 0x00404040, 0x00414141,
|
|
0x00424242, 0x00434343, 0x00444444, 0x00454545, 0x00464646, 0x00474747,
|
|
0x00484848, 0x00494949, 0x004a4a4a, 0x004b4b4b, 0x004c4c4c, 0x004d4d4d,
|
|
0x004e4e4e, 0x004f4f4f, 0x00505050, 0x00515151, 0x00525252, 0x00535353,
|
|
0x00545454, 0x00555555, 0x00565656, 0x00575757, 0x00585858, 0x00595959,
|
|
0x005a5a5a, 0x005b5b5b, 0x005c5c5c, 0x005d5d5d, 0x005e5e5e, 0x005f5f5f,
|
|
0x00606060, 0x00616161, 0x00626262, 0x00636363, 0x00646464, 0x00656565,
|
|
0x00666666, 0x00676767, 0x00686868, 0x00696969, 0x006a6a6a, 0x006b6b6b,
|
|
0x006c6c6c, 0x006d6d6d, 0x006e6e6e, 0x006f6f6f, 0x00707070, 0x00717171,
|
|
0x00727272, 0x00737373, 0x00747474, 0x00757575, 0x00767676, 0x00777777,
|
|
0x00787878, 0x00797979, 0x007a7a7a, 0x007b7b7b, 0x007c7c7c, 0x007d7d7d,
|
|
0x007e7e7e, 0x007f7f7f, 0x00808080, 0x00818181, 0x00828282, 0x00838383,
|
|
0x00848484, 0x00858585, 0x00868686, 0x00878787, 0x00888888, 0x00898989,
|
|
0x008a8a8a, 0x008b8b8b, 0x008c8c8c, 0x008d8d8d, 0x008e8e8e, 0x008f8f8f,
|
|
0x00909090, 0x00919191, 0x00929292, 0x00939393, 0x00949494, 0x00959595,
|
|
0x00969696, 0x00979797, 0x00989898, 0x00999999, 0x009a9a9a, 0x009b9b9b,
|
|
0x009c9c9c, 0x009d9d9d, 0x009e9e9e, 0x009f9f9f, 0x00a0a0a0, 0x00a1a1a1,
|
|
0x00a2a2a2, 0x00a3a3a3, 0x00a4a4a4, 0x00a5a5a5, 0x00a6a6a6, 0x00a7a7a7,
|
|
0x00a8a8a8, 0x00a9a9a9, 0x00aaaaaa, 0x00ababab, 0x00acacac, 0x00adadad,
|
|
0x00aeaeae, 0x00afafaf, 0x00b0b0b0, 0x00b1b1b1, 0x00b2b2b2, 0x00b3b3b3,
|
|
0x00b4b4b4, 0x00b5b5b5, 0x00b6b6b6, 0x00b7b7b7, 0x00b8b8b8, 0x00b9b9b9,
|
|
0x00bababa, 0x00bbbbbb, 0x00bcbcbc, 0x00bdbdbd, 0x00bebebe, 0x00bfbfbf,
|
|
0x00c0c0c0, 0x00c1c1c1, 0x00c2c2c2, 0x00c3c3c3, 0x00c4c4c4, 0x00c5c5c5,
|
|
0x00c6c6c6, 0x00c7c7c7, 0x00c8c8c8, 0x00c9c9c9, 0x00cacaca, 0x00cbcbcb,
|
|
0x00cccccc, 0x00cdcdcd, 0x00cecece, 0x00cfcfcf, 0x00d0d0d0, 0x00d1d1d1,
|
|
0x00d2d2d2, 0x00d3d3d3, 0x00d4d4d4, 0x00d5d5d5, 0x00d6d6d6, 0x00d7d7d7,
|
|
0x00d8d8d8, 0x00d9d9d9, 0x00dadada, 0x00dbdbdb, 0x00dcdcdc, 0x00dddddd,
|
|
0x00dedede, 0x00dfdfdf, 0x00e0e0e0, 0x00e1e1e1, 0x00e2e2e2, 0x00e3e3e3,
|
|
0x00e4e4e4, 0x00e5e5e5, 0x00e6e6e6, 0x00e7e7e7, 0x00e8e8e8, 0x00e9e9e9,
|
|
0x00eaeaea, 0x00ebebeb, 0x00ececec, 0x00ededed, 0x00eeeeee, 0x00efefef,
|
|
0x00f0f0f0, 0x00f1f1f1, 0x00f2f2f2, 0x00f3f3f3, 0x00f4f4f4, 0x00f5f5f5,
|
|
0x00f6f6f6, 0x00f7f7f7, 0x00f8f8f8, 0x00f9f9f9, 0x00fafafa, 0x00fbfbfb,
|
|
0x00fcfcfc, 0x00fdfdfd, 0x00fefefe, 0x00ffffff}};
|
|
|
|
static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) {
|
|
return kMultTables[!inverse][a];
|
|
}
|
|
|
|
#else
|
|
|
|
static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) {
|
|
return inverse ? (255u << MFIX) / a : a * KINV_255;
|
|
}
|
|
|
|
#endif // USE_TABLES_FOR_ALPHA_MULT
|
|
|
|
void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse) {
|
|
int x;
|
|
for (x = 0; x < width; ++x) {
|
|
const uint32_t argb = ptr[x];
|
|
if (argb < 0xff000000u) { // alpha < 255
|
|
if (argb <= 0x00ffffffu) { // alpha == 0
|
|
ptr[x] = 0;
|
|
} else {
|
|
const uint32_t alpha = (argb >> 24) & 0xff;
|
|
const uint32_t scale = GetScale(alpha, inverse);
|
|
uint32_t out = argb & 0xff000000u;
|
|
out |= Mult(argb >> 0, scale) << 0;
|
|
out |= Mult(argb >> 8, scale) << 8;
|
|
out |= Mult(argb >> 16, scale) << 16;
|
|
ptr[x] = out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebPMultRow_C(uint8_t* WEBP_RESTRICT const ptr,
|
|
const uint8_t* WEBP_RESTRICT const alpha, int width,
|
|
int inverse) {
|
|
int x;
|
|
for (x = 0; x < width; ++x) {
|
|
const uint32_t a = alpha[x];
|
|
if (a != 255) {
|
|
if (a == 0) {
|
|
ptr[x] = 0;
|
|
} else {
|
|
const uint32_t scale = GetScale(a, inverse);
|
|
ptr[x] = Mult(ptr[x], scale);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef KINV_255
|
|
#undef HALF
|
|
#undef MFIX
|
|
|
|
void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse);
|
|
void (*WebPMultRow)(uint8_t* WEBP_RESTRICT const ptr,
|
|
const uint8_t* WEBP_RESTRICT const alpha, int width,
|
|
int inverse);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Generic per-plane calls
|
|
|
|
void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows,
|
|
int inverse) {
|
|
int n;
|
|
for (n = 0; n < num_rows; ++n) {
|
|
WebPMultARGBRow((uint32_t*)ptr, width, inverse);
|
|
ptr += stride;
|
|
}
|
|
}
|
|
|
|
void WebPMultRows(uint8_t* WEBP_RESTRICT ptr, int stride,
|
|
const uint8_t* WEBP_RESTRICT alpha, int alpha_stride,
|
|
int width, int num_rows, int inverse) {
|
|
int n;
|
|
for (n = 0; n < num_rows; ++n) {
|
|
WebPMultRow(ptr, alpha, width, inverse);
|
|
ptr += stride;
|
|
alpha += alpha_stride;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Premultiplied modes
|
|
|
|
// non dithered-modes
|
|
|
|
// (x * a * 32897) >> 23 is bit-wise equivalent to (int)(x * a / 255.)
|
|
// for all 8bit x or a. For bit-wise equivalence to (int)(x * a / 255. + .5),
|
|
// one can use instead: (x * a * 65793 + (1 << 23)) >> 24
|
|
#if 1 // (int)(x * a / 255.)
|
|
#define MULTIPLIER(a) ((a) * 32897U)
|
|
#define PREMULTIPLY(x, m) (((x) * (m)) >> 23)
|
|
#else // (int)(x * a / 255. + .5)
|
|
#define MULTIPLIER(a) ((a) * 65793U)
|
|
#define PREMULTIPLY(x, m) (((x) * (m) + (1U << 23)) >> 24)
|
|
#endif
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void ApplyAlphaMultiply_C(uint8_t* rgba, int alpha_first, int w, int h,
|
|
int stride) {
|
|
while (h-- > 0) {
|
|
uint8_t* const rgb = rgba + (alpha_first ? 1 : 0);
|
|
const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3);
|
|
int i;
|
|
for (i = 0; i < w; ++i) {
|
|
const uint32_t a = alpha[4 * i];
|
|
if (a != 0xff) {
|
|
const uint32_t mult = MULTIPLIER(a);
|
|
rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult);
|
|
rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult);
|
|
rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult);
|
|
}
|
|
}
|
|
rgba += stride;
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
#undef MULTIPLIER
|
|
#undef PREMULTIPLY
|
|
|
|
// rgbA4444
|
|
|
|
#define MULTIPLIER(a) ((a) * 0x1111) // 0x1111 ~= (1 << 16) / 15
|
|
|
|
static WEBP_INLINE uint8_t dither_hi(uint8_t x) {
|
|
return (x & 0xf0) | (x >> 4);
|
|
}
|
|
|
|
static WEBP_INLINE uint8_t dither_lo(uint8_t x) {
|
|
return (x & 0x0f) | (x << 4);
|
|
}
|
|
|
|
static WEBP_INLINE uint8_t multiply(uint8_t x, uint32_t m) {
|
|
return (x * m) >> 16;
|
|
}
|
|
|
|
static WEBP_INLINE void ApplyAlphaMultiply4444_C(uint8_t* rgba4444, int w,
|
|
int h, int stride,
|
|
int rg_byte_pos /* 0 or 1 */) {
|
|
while (h-- > 0) {
|
|
int i;
|
|
for (i = 0; i < w; ++i) {
|
|
const uint32_t rg = rgba4444[2 * i + rg_byte_pos];
|
|
const uint32_t ba = rgba4444[2 * i + (rg_byte_pos ^ 1)];
|
|
const uint8_t a = ba & 0x0f;
|
|
const uint32_t mult = MULTIPLIER(a);
|
|
const uint8_t r = multiply(dither_hi(rg), mult);
|
|
const uint8_t g = multiply(dither_lo(rg), mult);
|
|
const uint8_t b = multiply(dither_hi(ba), mult);
|
|
rgba4444[2 * i + rg_byte_pos] = (r & 0xf0) | ((g >> 4) & 0x0f);
|
|
rgba4444[2 * i + (rg_byte_pos ^ 1)] = (b & 0xf0) | a;
|
|
}
|
|
rgba4444 += stride;
|
|
}
|
|
}
|
|
#undef MULTIPLIER
|
|
|
|
static void ApplyAlphaMultiply_16b_C(uint8_t* rgba4444, int w, int h,
|
|
int stride) {
|
|
#if (WEBP_SWAP_16BIT_CSP == 1)
|
|
ApplyAlphaMultiply4444_C(rgba4444, w, h, stride, 1);
|
|
#else
|
|
ApplyAlphaMultiply4444_C(rgba4444, w, h, stride, 0);
|
|
#endif
|
|
}
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static int DispatchAlpha_C(const uint8_t* WEBP_RESTRICT alpha, int alpha_stride,
|
|
int width, int height, uint8_t* WEBP_RESTRICT dst,
|
|
int dst_stride) {
|
|
uint32_t alpha_mask = 0xff;
|
|
int i, j;
|
|
|
|
for (j = 0; j < height; ++j) {
|
|
for (i = 0; i < width; ++i) {
|
|
const uint32_t alpha_value = alpha[i];
|
|
dst[4 * i] = alpha_value;
|
|
alpha_mask &= alpha_value;
|
|
}
|
|
alpha += alpha_stride;
|
|
dst += dst_stride;
|
|
}
|
|
|
|
return (alpha_mask != 0xff);
|
|
}
|
|
|
|
static void DispatchAlphaToGreen_C(const uint8_t* WEBP_RESTRICT alpha,
|
|
int alpha_stride, int width, int height,
|
|
uint32_t* WEBP_RESTRICT dst,
|
|
int dst_stride) {
|
|
int i, j;
|
|
for (j = 0; j < height; ++j) {
|
|
for (i = 0; i < width; ++i) {
|
|
dst[i] = alpha[i] << 8; // leave A/R/B channels zero'd.
|
|
}
|
|
alpha += alpha_stride;
|
|
dst += dst_stride;
|
|
}
|
|
}
|
|
|
|
static int ExtractAlpha_C(const uint8_t* WEBP_RESTRICT argb, int argb_stride,
|
|
int width, int height, uint8_t* WEBP_RESTRICT alpha,
|
|
int alpha_stride) {
|
|
uint8_t alpha_mask = 0xff;
|
|
int i, j;
|
|
|
|
for (j = 0; j < height; ++j) {
|
|
for (i = 0; i < width; ++i) {
|
|
const uint8_t alpha_value = argb[4 * i];
|
|
alpha[i] = alpha_value;
|
|
alpha_mask &= alpha_value;
|
|
}
|
|
argb += argb_stride;
|
|
alpha += alpha_stride;
|
|
}
|
|
return (alpha_mask == 0xff);
|
|
}
|
|
|
|
static void ExtractGreen_C(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT alpha, int size) {
|
|
int i;
|
|
for (i = 0; i < size; ++i) alpha[i] = argb[i] >> 8;
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static int HasAlpha8b_C(const uint8_t* src, int length) {
|
|
while (length-- > 0) {
|
|
if (*src++ != 0xff) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int HasAlpha32b_C(const uint8_t* src, int length) {
|
|
int x;
|
|
for (x = 0; length-- > 0; x += 4) {
|
|
if (src[x] != 0xff) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void AlphaReplace_C(uint32_t* src, int length, uint32_t color) {
|
|
int x;
|
|
for (x = 0; x < length; ++x) {
|
|
if ((src[x] >> 24) == 0) src[x] = color;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Simple channel manipulations.
|
|
|
|
static WEBP_INLINE uint32_t MakeARGB32(int a, int r, int g, int b) {
|
|
return (((uint32_t)a << 24) | (r << 16) | (g << 8) | b);
|
|
}
|
|
|
|
#ifdef WORDS_BIGENDIAN
|
|
static void PackARGB_C(const uint8_t* WEBP_RESTRICT a,
|
|
const uint8_t* WEBP_RESTRICT r,
|
|
const uint8_t* WEBP_RESTRICT g,
|
|
const uint8_t* WEBP_RESTRICT b, int len,
|
|
uint32_t* WEBP_RESTRICT out) {
|
|
int i;
|
|
for (i = 0; i < len; ++i) {
|
|
out[i] = MakeARGB32(a[4 * i], r[4 * i], g[4 * i], b[4 * i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void PackRGB_C(const uint8_t* WEBP_RESTRICT r,
|
|
const uint8_t* WEBP_RESTRICT g,
|
|
const uint8_t* WEBP_RESTRICT b, int len, int step,
|
|
uint32_t* WEBP_RESTRICT out) {
|
|
int i, offset = 0;
|
|
for (i = 0; i < len; ++i) {
|
|
out[i] = MakeARGB32(0xff, r[offset], g[offset], b[offset]);
|
|
offset += step;
|
|
}
|
|
}
|
|
|
|
void (*WebPApplyAlphaMultiply)(uint8_t*, int, int, int, int);
|
|
void (*WebPApplyAlphaMultiply4444)(uint8_t*, int, int, int);
|
|
int (*WebPDispatchAlpha)(const uint8_t* WEBP_RESTRICT, int, int, int,
|
|
uint8_t* WEBP_RESTRICT, int);
|
|
void (*WebPDispatchAlphaToGreen)(const uint8_t* WEBP_RESTRICT, int, int, int,
|
|
uint32_t* WEBP_RESTRICT, int);
|
|
int (*WebPExtractAlpha)(const uint8_t* WEBP_RESTRICT, int, int, int,
|
|
uint8_t* WEBP_RESTRICT, int);
|
|
void (*WebPExtractGreen)(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT alpha, int size);
|
|
#ifdef WORDS_BIGENDIAN
|
|
void (*WebPPackARGB)(const uint8_t* a, const uint8_t* r, const uint8_t* g,
|
|
const uint8_t* b, int, uint32_t*);
|
|
#endif
|
|
void (*WebPPackRGB)(const uint8_t* WEBP_RESTRICT r,
|
|
const uint8_t* WEBP_RESTRICT g,
|
|
const uint8_t* WEBP_RESTRICT b, int len, int step,
|
|
uint32_t* WEBP_RESTRICT out);
|
|
|
|
int (*WebPHasAlpha8b)(const uint8_t* src, int length);
|
|
int (*WebPHasAlpha32b)(const uint8_t* src, int length);
|
|
void (*WebPAlphaReplace)(uint32_t* src, int length, uint32_t color);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Init function
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
extern void WebPInitAlphaProcessingMIPSdspR2(void);
|
|
extern void WebPInitAlphaProcessingSSE2(void);
|
|
extern void WebPInitAlphaProcessingSSE41(void);
|
|
extern void WebPInitAlphaProcessingNEON(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(WebPInitAlphaProcessing) {
|
|
WebPMultARGBRow = WebPMultARGBRow_C;
|
|
WebPMultRow = WebPMultRow_C;
|
|
WebPApplyAlphaMultiply4444 = ApplyAlphaMultiply_16b_C;
|
|
|
|
#ifdef WORDS_BIGENDIAN
|
|
WebPPackARGB = PackARGB_C;
|
|
#endif
|
|
WebPPackRGB = PackRGB_C;
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
WebPApplyAlphaMultiply = ApplyAlphaMultiply_C;
|
|
WebPDispatchAlpha = DispatchAlpha_C;
|
|
WebPDispatchAlphaToGreen = DispatchAlphaToGreen_C;
|
|
WebPExtractAlpha = ExtractAlpha_C;
|
|
WebPExtractGreen = ExtractGreen_C;
|
|
#endif
|
|
|
|
WebPHasAlpha8b = HasAlpha8b_C;
|
|
WebPHasAlpha32b = HasAlpha32b_C;
|
|
WebPAlphaReplace = AlphaReplace_C;
|
|
|
|
// If defined, use CPUInfo() to overwrite some pointers with faster versions.
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
WebPInitAlphaProcessingSSE2();
|
|
#if defined(WEBP_HAVE_SSE41)
|
|
if (VP8GetCPUInfo(kSSE4_1)) {
|
|
WebPInitAlphaProcessingSSE41();
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
WebPInitAlphaProcessingMIPSdspR2();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(WEBP_HAVE_NEON)
|
|
if (WEBP_NEON_OMIT_C_CODE ||
|
|
(VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) {
|
|
WebPInitAlphaProcessingNEON();
|
|
}
|
|
#endif
|
|
|
|
assert(WebPMultARGBRow != NULL);
|
|
assert(WebPMultRow != NULL);
|
|
assert(WebPApplyAlphaMultiply != NULL);
|
|
assert(WebPApplyAlphaMultiply4444 != NULL);
|
|
assert(WebPDispatchAlpha != NULL);
|
|
assert(WebPDispatchAlphaToGreen != NULL);
|
|
assert(WebPExtractAlpha != NULL);
|
|
assert(WebPExtractGreen != NULL);
|
|
#ifdef WORDS_BIGENDIAN
|
|
assert(WebPPackARGB != NULL);
|
|
#endif
|
|
assert(WebPPackRGB != NULL);
|
|
assert(WebPHasAlpha8b != NULL);
|
|
assert(WebPHasAlpha32b != NULL);
|
|
assert(WebPAlphaReplace != NULL);
|
|
}
|
|
/* >>> src/dsp/cpu.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// CPU detection
|
|
//
|
|
// Author: Christian Duvivier (cduvivier@google.com)
|
|
|
|
|
|
#if defined(WEBP_HAVE_NEON_RTCD)
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#if defined(WEBP_ANDROID_NEON)
|
|
#include <cpu-features.h>
|
|
#endif
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// SSE2 detection.
|
|
//
|
|
|
|
// apple/darwin gcc-4.0.1 defines __PIC__, but not __pic__ with -fPIC.
|
|
#if (defined(__pic__) || defined(__PIC__)) && defined(__i386__)
|
|
static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) {
|
|
__asm__ volatile(
|
|
"mov %%ebx, %%edi\n"
|
|
"cpuid\n"
|
|
"xchg %%edi, %%ebx\n"
|
|
: "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]),
|
|
"=d"(cpu_info[3])
|
|
: "a"(info_type), "c"(0));
|
|
}
|
|
#elif defined(__i386__) || defined(__x86_64__)
|
|
static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) {
|
|
__asm__ volatile("cpuid\n"
|
|
: "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]),
|
|
"=d"(cpu_info[3])
|
|
: "a"(info_type), "c"(0));
|
|
}
|
|
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
|
|
|
|
#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 150030729 // >= VS2008 SP1
|
|
#include <intrin.h>
|
|
#define GetCPUInfo(info, type) __cpuidex(info, type, 0) // set ecx=0
|
|
#define WEBP_HAVE_MSC_CPUID
|
|
#elif _MSC_VER > 1310
|
|
#include <intrin.h>
|
|
#define GetCPUInfo __cpuid
|
|
#define WEBP_HAVE_MSC_CPUID
|
|
#endif
|
|
|
|
#endif
|
|
|
|
// NaCl has no support for xgetbv or the raw opcode.
|
|
#if !defined(__native_client__) && (defined(__i386__) || defined(__x86_64__))
|
|
static WEBP_INLINE uint64_t xgetbv(void) {
|
|
const uint32_t ecx = 0;
|
|
uint32_t eax, edx;
|
|
// Use the raw opcode for xgetbv for compatibility with older toolchains.
|
|
__asm__ volatile(".byte 0x0f, 0x01, 0xd0\n"
|
|
: "=a"(eax), "=d"(edx)
|
|
: "c"(ecx));
|
|
return ((uint64_t)edx << 32) | eax;
|
|
}
|
|
#elif (defined(_M_X64) || defined(_M_IX86)) && defined(_MSC_FULL_VER) && \
|
|
_MSC_FULL_VER >= 160040219 // >= VS2010 SP1
|
|
#include <immintrin.h>
|
|
#define xgetbv() _xgetbv(0)
|
|
#elif defined(_MSC_VER) && defined(_M_IX86)
|
|
static WEBP_INLINE uint64_t xgetbv(void) {
|
|
uint32_t eax_, edx_;
|
|
__asm {
|
|
xor ecx, ecx // ecx = 0
|
|
// Use the raw opcode for xgetbv for compatibility with older toolchains.
|
|
__asm _emit 0x0f __asm _emit 0x01 __asm _emit 0xd0
|
|
mov eax_, eax
|
|
mov edx_, edx
|
|
}
|
|
return ((uint64_t)edx_ << 32) | eax_;
|
|
}
|
|
#else
|
|
#define xgetbv() 0U // no AVX for older x64 or unrecognized toolchains.
|
|
#endif
|
|
|
|
#if defined(__i386__) || defined(__x86_64__) || defined(WEBP_HAVE_MSC_CPUID)
|
|
|
|
// helper function for run-time detection of slow SSSE3 platforms
|
|
static int CheckSlowModel(int info) {
|
|
// Table listing display models with longer latencies for the bsr instruction
|
|
// (ie 2 cycles vs 10/16 cycles) and some SSSE3 instructions like pshufb.
|
|
// Refer to Intel 64 and IA-32 Architectures Optimization Reference Manual.
|
|
static const uint8_t kSlowModels[] = {
|
|
0x37, 0x4a, 0x4d, // Silvermont Microarchitecture
|
|
0x1c, 0x26, 0x27 // Atom Microarchitecture
|
|
};
|
|
const uint32_t model = ((info & 0xf0000) >> 12) | ((info >> 4) & 0xf);
|
|
const uint32_t family = (info >> 8) & 0xf;
|
|
if (family == 0x06) {
|
|
size_t i;
|
|
for (i = 0; i < sizeof(kSlowModels) / sizeof(kSlowModels[0]); ++i) {
|
|
if (model == kSlowModels[i]) return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int x86CPUInfo(CPUFeature feature) {
|
|
int max_cpuid_value;
|
|
int cpu_info[4];
|
|
int is_intel = 0;
|
|
|
|
// get the highest feature value cpuid supports
|
|
GetCPUInfo(cpu_info, 0);
|
|
max_cpuid_value = cpu_info[0];
|
|
if (max_cpuid_value < 1) {
|
|
return 0;
|
|
} else {
|
|
const int VENDOR_ID_INTEL_EBX = 0x756e6547; // uneG
|
|
const int VENDOR_ID_INTEL_EDX = 0x49656e69; // Ieni
|
|
const int VENDOR_ID_INTEL_ECX = 0x6c65746e; // letn
|
|
is_intel = (cpu_info[1] == VENDOR_ID_INTEL_EBX &&
|
|
cpu_info[2] == VENDOR_ID_INTEL_ECX &&
|
|
cpu_info[3] == VENDOR_ID_INTEL_EDX); // genuine Intel?
|
|
}
|
|
|
|
GetCPUInfo(cpu_info, 1);
|
|
if (feature == kSSE2) {
|
|
return !!(cpu_info[3] & (1 << 26));
|
|
}
|
|
if (feature == kSSE3) {
|
|
return !!(cpu_info[2] & (1 << 0));
|
|
}
|
|
if (feature == kSlowSSSE3) {
|
|
if (is_intel && (cpu_info[2] & (1 << 9))) { // SSSE3?
|
|
return CheckSlowModel(cpu_info[0]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (feature == kSSE4_1) {
|
|
return !!(cpu_info[2] & (1 << 19));
|
|
}
|
|
if (feature == kAVX) {
|
|
// bits 27 (OSXSAVE) & 28 (256-bit AVX)
|
|
if ((cpu_info[2] & 0x18000000) == 0x18000000) {
|
|
// XMM state and YMM state enabled by the OS.
|
|
return (xgetbv() & 0x6) == 0x6;
|
|
}
|
|
}
|
|
if (feature == kAVX2) {
|
|
if (x86CPUInfo(kAVX) && max_cpuid_value >= 7) {
|
|
GetCPUInfo(cpu_info, 7);
|
|
return !!(cpu_info[1] & (1 << 5));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo;
|
|
VP8CPUInfo VP8GetCPUInfo = x86CPUInfo;
|
|
#elif defined(WEBP_ANDROID_NEON) // NB: needs to be before generic NEON test.
|
|
static int AndroidCPUInfo(CPUFeature feature) {
|
|
const AndroidCpuFamily cpu_family = android_getCpuFamily();
|
|
const uint64_t cpu_features = android_getCpuFeatures();
|
|
if (feature == kNEON) {
|
|
return cpu_family == ANDROID_CPU_FAMILY_ARM &&
|
|
(cpu_features & ANDROID_CPU_ARM_FEATURE_NEON) != 0;
|
|
}
|
|
return 0;
|
|
}
|
|
WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo;
|
|
VP8CPUInfo VP8GetCPUInfo = AndroidCPUInfo;
|
|
#elif defined(__EMSCRIPTEN__) // also needs to be before generic NEON test
|
|
// Use compile flags as an indicator of SIMD support instead of a runtime check.
|
|
static int wasmCPUInfo(CPUFeature feature) {
|
|
switch (feature) {
|
|
#ifdef WEBP_HAVE_SSE2
|
|
case kSSE2:
|
|
return 1;
|
|
#endif
|
|
#ifdef WEBP_HAVE_SSE41
|
|
case kSSE3:
|
|
case kSlowSSSE3:
|
|
case kSSE4_1:
|
|
return 1;
|
|
#endif
|
|
#ifdef WEBP_HAVE_AVX2
|
|
case kAVX2:
|
|
return 1;
|
|
#endif
|
|
#ifdef WEBP_HAVE_NEON
|
|
case kNEON:
|
|
return 1;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo;
|
|
VP8CPUInfo VP8GetCPUInfo = wasmCPUInfo;
|
|
#elif defined(WEBP_HAVE_NEON)
|
|
// In most cases this function doesn't check for NEON support (it's assumed by
|
|
// the configuration), but enables turning off NEON at runtime, for testing
|
|
// purposes, by setting VP8GetCPUInfo = NULL.
|
|
static int armCPUInfo(CPUFeature feature) {
|
|
if (feature != kNEON) return 0;
|
|
#if defined(__linux__) && defined(WEBP_HAVE_NEON_RTCD)
|
|
{
|
|
int has_neon = 0;
|
|
char line[200];
|
|
FILE* const cpuinfo = fopen("/proc/cpuinfo", "r");
|
|
if (cpuinfo == NULL) return 0;
|
|
while (fgets(line, sizeof(line), cpuinfo)) {
|
|
if (!strncmp(line, "Features", 8)) {
|
|
if (strstr(line, " neon ") != NULL) {
|
|
has_neon = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fclose(cpuinfo);
|
|
return has_neon;
|
|
}
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo;
|
|
VP8CPUInfo VP8GetCPUInfo = armCPUInfo;
|
|
#elif defined(WEBP_USE_MIPS32) || defined(WEBP_USE_MIPS_DSP_R2) || \
|
|
defined(WEBP_USE_MSA)
|
|
static int mipsCPUInfo(CPUFeature feature) {
|
|
if ((feature == kMIPS32) || (feature == kMIPSdspR2) || (feature == kMSA)) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo;
|
|
VP8CPUInfo VP8GetCPUInfo = mipsCPUInfo;
|
|
#else
|
|
WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo;
|
|
VP8CPUInfo VP8GetCPUInfo = NULL;
|
|
#endif
|
|
/* >>> src/dsp/dec.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Speed-critical decoding functions, default plain-C implementations.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static WEBP_INLINE uint8_t clip_8b(int v) {
|
|
return (!(v & ~0xff)) ? v : (v < 0) ? 0 : 255;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Transforms (Paragraph 14.4)
|
|
|
|
#define STORE(x, y, v) \
|
|
dst[(x) + (y) * BPS] = clip_8b(dst[(x) + (y) * BPS] + ((v) >> 3))
|
|
|
|
#define STORE2(y, dc, d, c) \
|
|
do { \
|
|
const int DC = (dc); \
|
|
STORE(0, y, DC + (d)); \
|
|
STORE(1, y, DC + (c)); \
|
|
STORE(2, y, DC - (c)); \
|
|
STORE(3, y, DC - (d)); \
|
|
} while (0)
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void TransformOne_C(const int16_t* WEBP_RESTRICT in,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
int C[4 * 4], *tmp;
|
|
int i;
|
|
tmp = C;
|
|
for (i = 0; i < 4; ++i) { // vertical pass
|
|
const int a = in[0] + in[8]; // [-4096, 4094]
|
|
const int b = in[0] - in[8]; // [-4095, 4095]
|
|
const int c = WEBP_TRANSFORM_AC3_MUL2(in[4]) -
|
|
WEBP_TRANSFORM_AC3_MUL1(in[12]); // [-3783, 3783]
|
|
const int d = WEBP_TRANSFORM_AC3_MUL1(in[4]) +
|
|
WEBP_TRANSFORM_AC3_MUL2(in[12]); // [-3785, 3781]
|
|
tmp[0] = a + d; // [-7881, 7875]
|
|
tmp[1] = b + c; // [-7878, 7878]
|
|
tmp[2] = b - c; // [-7878, 7878]
|
|
tmp[3] = a - d; // [-7877, 7879]
|
|
tmp += 4;
|
|
in++;
|
|
}
|
|
// Each pass is expanding the dynamic range by ~3.85 (upper bound).
|
|
// The exact value is (2. + (20091 + 35468) / 65536).
|
|
// After the second pass, maximum interval is [-3794, 3794], assuming
|
|
// an input in [-2048, 2047] interval. We then need to add a dst value
|
|
// in the [0, 255] range.
|
|
// In the worst case scenario, the input to clip_8b() can be as large as
|
|
// [-60713, 60968].
|
|
tmp = C;
|
|
for (i = 0; i < 4; ++i) { // horizontal pass
|
|
const int dc = tmp[0] + 4;
|
|
const int a = dc + tmp[8];
|
|
const int b = dc - tmp[8];
|
|
const int c =
|
|
WEBP_TRANSFORM_AC3_MUL2(tmp[4]) - WEBP_TRANSFORM_AC3_MUL1(tmp[12]);
|
|
const int d =
|
|
WEBP_TRANSFORM_AC3_MUL1(tmp[4]) + WEBP_TRANSFORM_AC3_MUL2(tmp[12]);
|
|
STORE(0, 0, a + d);
|
|
STORE(1, 0, b + c);
|
|
STORE(2, 0, b - c);
|
|
STORE(3, 0, a - d);
|
|
tmp++;
|
|
dst += BPS;
|
|
}
|
|
}
|
|
|
|
// Simplified transform when only in[0], in[1] and in[4] are non-zero
|
|
static void TransformAC3_C(const int16_t* WEBP_RESTRICT in,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
const int a = in[0] + 4;
|
|
const int c4 = WEBP_TRANSFORM_AC3_MUL2(in[4]);
|
|
const int d4 = WEBP_TRANSFORM_AC3_MUL1(in[4]);
|
|
const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]);
|
|
const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]);
|
|
STORE2(0, a + d4, d1, c1);
|
|
STORE2(1, a + c4, d1, c1);
|
|
STORE2(2, a - c4, d1, c1);
|
|
STORE2(3, a - d4, d1, c1);
|
|
}
|
|
#undef STORE2
|
|
|
|
static void TransformTwo_C(const int16_t* WEBP_RESTRICT in,
|
|
uint8_t* WEBP_RESTRICT dst, int do_two) {
|
|
TransformOne_C(in, dst);
|
|
if (do_two) {
|
|
TransformOne_C(in + 16, dst + 4);
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
static void TransformUV_C(const int16_t* WEBP_RESTRICT in,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
VP8Transform(in + 0 * 16, dst, 1);
|
|
VP8Transform(in + 2 * 16, dst + 4 * BPS, 1);
|
|
}
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void TransformDC_C(const int16_t* WEBP_RESTRICT in,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
const int DC = in[0] + 4;
|
|
int i, j;
|
|
for (j = 0; j < 4; ++j) {
|
|
for (i = 0; i < 4; ++i) {
|
|
STORE(i, j, DC);
|
|
}
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
static void TransformDCUV_C(const int16_t* WEBP_RESTRICT in,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
if (in[0 * 16]) VP8TransformDC(in + 0 * 16, dst);
|
|
if (in[1 * 16]) VP8TransformDC(in + 1 * 16, dst + 4);
|
|
if (in[2 * 16]) VP8TransformDC(in + 2 * 16, dst + 4 * BPS);
|
|
if (in[3 * 16]) VP8TransformDC(in + 3 * 16, dst + 4 * BPS + 4);
|
|
}
|
|
|
|
#undef STORE
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Paragraph 14.3
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void TransformWHT_C(const int16_t* WEBP_RESTRICT in,
|
|
int16_t* WEBP_RESTRICT out) {
|
|
int tmp[16];
|
|
int i;
|
|
for (i = 0; i < 4; ++i) {
|
|
const int a0 = in[0 + i] + in[12 + i];
|
|
const int a1 = in[4 + i] + in[8 + i];
|
|
const int a2 = in[4 + i] - in[8 + i];
|
|
const int a3 = in[0 + i] - in[12 + i];
|
|
tmp[0 + i] = a0 + a1;
|
|
tmp[8 + i] = a0 - a1;
|
|
tmp[4 + i] = a3 + a2;
|
|
tmp[12 + i] = a3 - a2;
|
|
}
|
|
for (i = 0; i < 4; ++i) {
|
|
const int dc = tmp[0 + i * 4] + 3; // w/ rounder
|
|
const int a0 = dc + tmp[3 + i * 4];
|
|
const int a1 = tmp[1 + i * 4] + tmp[2 + i * 4];
|
|
const int a2 = tmp[1 + i * 4] - tmp[2 + i * 4];
|
|
const int a3 = dc - tmp[3 + i * 4];
|
|
out[0] = (a0 + a1) >> 3;
|
|
out[16] = (a3 + a2) >> 3;
|
|
out[32] = (a0 - a1) >> 3;
|
|
out[48] = (a3 - a2) >> 3;
|
|
out += 64;
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
VP8WHT VP8TransformWHT;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Intra predictions
|
|
|
|
#define DST(x, y) dst[(x) + (y) * BPS]
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static WEBP_INLINE void TrueMotion(uint8_t* dst, int size) {
|
|
const uint8_t* top = dst - BPS;
|
|
const uint8_t* const clip0 = VP8kclip1 - top[-1];
|
|
int y;
|
|
for (y = 0; y < size; ++y) {
|
|
const uint8_t* const clip = clip0 + dst[-1];
|
|
int x;
|
|
for (x = 0; x < size; ++x) {
|
|
dst[x] = clip[top[x]];
|
|
}
|
|
dst += BPS;
|
|
}
|
|
}
|
|
static void TM4_C(uint8_t* dst) { TrueMotion(dst, 4); }
|
|
static void TM8uv_C(uint8_t* dst) { TrueMotion(dst, 8); }
|
|
static void TM16_C(uint8_t* dst) { TrueMotion(dst, 16); }
|
|
|
|
//------------------------------------------------------------------------------
|
|
// 16x16
|
|
|
|
static void VE16_C(uint8_t* dst) { // vertical
|
|
int j;
|
|
for (j = 0; j < 16; ++j) {
|
|
memcpy(dst + j * BPS, dst - BPS, 16);
|
|
}
|
|
}
|
|
|
|
static void HE16_C(uint8_t* dst) { // horizontal
|
|
int j;
|
|
for (j = 16; j > 0; --j) {
|
|
memset(dst, dst[-1], 16);
|
|
dst += BPS;
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE void Put16(int v, uint8_t* dst) {
|
|
int j;
|
|
for (j = 0; j < 16; ++j) {
|
|
memset(dst + j * BPS, v, 16);
|
|
}
|
|
}
|
|
|
|
static void DC16_C(uint8_t* dst) { // DC
|
|
int DC = 16;
|
|
int j;
|
|
for (j = 0; j < 16; ++j) {
|
|
DC += dst[-1 + j * BPS] + dst[j - BPS];
|
|
}
|
|
Put16(DC >> 5, dst);
|
|
}
|
|
|
|
static void DC16NoTop_C(uint8_t* dst) { // DC with top samples not available
|
|
int DC = 8;
|
|
int j;
|
|
for (j = 0; j < 16; ++j) {
|
|
DC += dst[-1 + j * BPS];
|
|
}
|
|
Put16(DC >> 4, dst);
|
|
}
|
|
|
|
static void DC16NoLeft_C(uint8_t* dst) { // DC with left samples not available
|
|
int DC = 8;
|
|
int i;
|
|
for (i = 0; i < 16; ++i) {
|
|
DC += dst[i - BPS];
|
|
}
|
|
Put16(DC >> 4, dst);
|
|
}
|
|
|
|
static void DC16NoTopLeft_C(uint8_t* dst) { // DC with no top and left samples
|
|
Put16(0x80, dst);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
VP8PredFunc VP8PredLuma16[NUM_B_DC_MODES];
|
|
|
|
//------------------------------------------------------------------------------
|
|
// 4x4
|
|
|
|
#define AVG3(a, b, c) ((uint8_t)(((a) + 2 * (b) + (c) + 2) >> 2))
|
|
#define AVG2(a, b) (((a) + (b) + 1) >> 1)
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void VE4_C(uint8_t* dst) { // vertical
|
|
const uint8_t* top = dst - BPS;
|
|
const uint8_t vals[4] = {
|
|
AVG3(top[-1], top[0], top[1]),
|
|
AVG3(top[0], top[1], top[2]),
|
|
AVG3(top[1], top[2], top[3]),
|
|
AVG3(top[2], top[3], top[4]),
|
|
};
|
|
int i;
|
|
for (i = 0; i < 4; ++i) {
|
|
memcpy(dst + i * BPS, vals, sizeof(vals));
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
static void HE4_C(uint8_t* dst) { // horizontal
|
|
const int A = dst[-1 - BPS];
|
|
const int B = dst[-1];
|
|
const int C = dst[-1 + BPS];
|
|
const int D = dst[-1 + 2 * BPS];
|
|
const int E = dst[-1 + 3 * BPS];
|
|
WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(A, B, C));
|
|
WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(B, C, D));
|
|
WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(C, D, E));
|
|
WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(D, E, E));
|
|
}
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void DC4_C(uint8_t* dst) { // DC
|
|
uint32_t dc = 4;
|
|
int i;
|
|
for (i = 0; i < 4; ++i) dc += dst[i - BPS] + dst[-1 + i * BPS];
|
|
dc >>= 3;
|
|
for (i = 0; i < 4; ++i) memset(dst + i * BPS, dc, 4);
|
|
}
|
|
|
|
static void RD4_C(uint8_t* dst) { // Down-right
|
|
const int I = dst[-1 + 0 * BPS];
|
|
const int J = dst[-1 + 1 * BPS];
|
|
const int K = dst[-1 + 2 * BPS];
|
|
const int L = dst[-1 + 3 * BPS];
|
|
const int X = dst[-1 - BPS];
|
|
const int A = dst[0 - BPS];
|
|
const int B = dst[1 - BPS];
|
|
const int C = dst[2 - BPS];
|
|
const int D = dst[3 - BPS];
|
|
DST(0, 3) = AVG3(J, K, L);
|
|
DST(1, 3) = DST(0, 2) = AVG3(I, J, K);
|
|
DST(2, 3) = DST(1, 2) = DST(0, 1) = AVG3(X, I, J);
|
|
DST(3, 3) = DST(2, 2) = DST(1, 1) = DST(0, 0) = AVG3(A, X, I);
|
|
DST(3, 2) = DST(2, 1) = DST(1, 0) = AVG3(B, A, X);
|
|
DST(3, 1) = DST(2, 0) = AVG3(C, B, A);
|
|
DST(3, 0) = AVG3(D, C, B);
|
|
}
|
|
|
|
static void LD4_C(uint8_t* dst) { // Down-Left
|
|
const int A = dst[0 - BPS];
|
|
const int B = dst[1 - BPS];
|
|
const int C = dst[2 - BPS];
|
|
const int D = dst[3 - BPS];
|
|
const int E = dst[4 - BPS];
|
|
const int F = dst[5 - BPS];
|
|
const int G = dst[6 - BPS];
|
|
const int H = dst[7 - BPS];
|
|
DST(0, 0) = AVG3(A, B, C);
|
|
DST(1, 0) = DST(0, 1) = AVG3(B, C, D);
|
|
DST(2, 0) = DST(1, 1) = DST(0, 2) = AVG3(C, D, E);
|
|
DST(3, 0) = DST(2, 1) = DST(1, 2) = DST(0, 3) = AVG3(D, E, F);
|
|
DST(3, 1) = DST(2, 2) = DST(1, 3) = AVG3(E, F, G);
|
|
DST(3, 2) = DST(2, 3) = AVG3(F, G, H);
|
|
DST(3, 3) = AVG3(G, H, H);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
static void VR4_C(uint8_t* dst) { // Vertical-Right
|
|
const int I = dst[-1 + 0 * BPS];
|
|
const int J = dst[-1 + 1 * BPS];
|
|
const int K = dst[-1 + 2 * BPS];
|
|
const int X = dst[-1 - BPS];
|
|
const int A = dst[0 - BPS];
|
|
const int B = dst[1 - BPS];
|
|
const int C = dst[2 - BPS];
|
|
const int D = dst[3 - BPS];
|
|
DST(0, 0) = DST(1, 2) = AVG2(X, A);
|
|
DST(1, 0) = DST(2, 2) = AVG2(A, B);
|
|
DST(2, 0) = DST(3, 2) = AVG2(B, C);
|
|
DST(3, 0) = AVG2(C, D);
|
|
|
|
DST(0, 3) = AVG3(K, J, I);
|
|
DST(0, 2) = AVG3(J, I, X);
|
|
DST(0, 1) = DST(1, 3) = AVG3(I, X, A);
|
|
DST(1, 1) = DST(2, 3) = AVG3(X, A, B);
|
|
DST(2, 1) = DST(3, 3) = AVG3(A, B, C);
|
|
DST(3, 1) = AVG3(B, C, D);
|
|
}
|
|
|
|
static void VL4_C(uint8_t* dst) { // Vertical-Left
|
|
const int A = dst[0 - BPS];
|
|
const int B = dst[1 - BPS];
|
|
const int C = dst[2 - BPS];
|
|
const int D = dst[3 - BPS];
|
|
const int E = dst[4 - BPS];
|
|
const int F = dst[5 - BPS];
|
|
const int G = dst[6 - BPS];
|
|
const int H = dst[7 - BPS];
|
|
DST(0, 0) = AVG2(A, B);
|
|
DST(1, 0) = DST(0, 2) = AVG2(B, C);
|
|
DST(2, 0) = DST(1, 2) = AVG2(C, D);
|
|
DST(3, 0) = DST(2, 2) = AVG2(D, E);
|
|
|
|
DST(0, 1) = AVG3(A, B, C);
|
|
DST(1, 1) = DST(0, 3) = AVG3(B, C, D);
|
|
DST(2, 1) = DST(1, 3) = AVG3(C, D, E);
|
|
DST(3, 1) = DST(2, 3) = AVG3(D, E, F);
|
|
DST(3, 2) = AVG3(E, F, G);
|
|
DST(3, 3) = AVG3(F, G, H);
|
|
}
|
|
|
|
static void HU4_C(uint8_t* dst) { // Horizontal-Up
|
|
const int I = dst[-1 + 0 * BPS];
|
|
const int J = dst[-1 + 1 * BPS];
|
|
const int K = dst[-1 + 2 * BPS];
|
|
const int L = dst[-1 + 3 * BPS];
|
|
DST(0, 0) = AVG2(I, J);
|
|
DST(2, 0) = DST(0, 1) = AVG2(J, K);
|
|
DST(2, 1) = DST(0, 2) = AVG2(K, L);
|
|
DST(1, 0) = AVG3(I, J, K);
|
|
DST(3, 0) = DST(1, 1) = AVG3(J, K, L);
|
|
DST(3, 1) = DST(1, 2) = AVG3(K, L, L);
|
|
DST(3, 2) = DST(2, 2) = DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L;
|
|
}
|
|
|
|
static void HD4_C(uint8_t* dst) { // Horizontal-Down
|
|
const int I = dst[-1 + 0 * BPS];
|
|
const int J = dst[-1 + 1 * BPS];
|
|
const int K = dst[-1 + 2 * BPS];
|
|
const int L = dst[-1 + 3 * BPS];
|
|
const int X = dst[-1 - BPS];
|
|
const int A = dst[0 - BPS];
|
|
const int B = dst[1 - BPS];
|
|
const int C = dst[2 - BPS];
|
|
|
|
DST(0, 0) = DST(2, 1) = AVG2(I, X);
|
|
DST(0, 1) = DST(2, 2) = AVG2(J, I);
|
|
DST(0, 2) = DST(2, 3) = AVG2(K, J);
|
|
DST(0, 3) = AVG2(L, K);
|
|
|
|
DST(3, 0) = AVG3(A, B, C);
|
|
DST(2, 0) = AVG3(X, A, B);
|
|
DST(1, 0) = DST(3, 1) = AVG3(I, X, A);
|
|
DST(1, 1) = DST(3, 2) = AVG3(J, I, X);
|
|
DST(1, 2) = DST(3, 3) = AVG3(K, J, I);
|
|
DST(1, 3) = AVG3(L, K, J);
|
|
}
|
|
|
|
#undef DST
|
|
#undef AVG3
|
|
#undef AVG2
|
|
|
|
VP8PredFunc VP8PredLuma4[NUM_BMODES];
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Chroma
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void VE8uv_C(uint8_t* dst) { // vertical
|
|
int j;
|
|
for (j = 0; j < 8; ++j) {
|
|
memcpy(dst + j * BPS, dst - BPS, 8);
|
|
}
|
|
}
|
|
|
|
static void HE8uv_C(uint8_t* dst) { // horizontal
|
|
int j;
|
|
for (j = 0; j < 8; ++j) {
|
|
memset(dst, dst[-1], 8);
|
|
dst += BPS;
|
|
}
|
|
}
|
|
|
|
// helper for chroma-DC predictions
|
|
static WEBP_INLINE void Put8x8uv(uint8_t value, uint8_t* dst) {
|
|
int j;
|
|
for (j = 0; j < 8; ++j) {
|
|
memset(dst + j * BPS, value, 8);
|
|
}
|
|
}
|
|
|
|
static void DC8uv_C(uint8_t* dst) { // DC
|
|
int dc0 = 8;
|
|
int i;
|
|
for (i = 0; i < 8; ++i) {
|
|
dc0 += dst[i - BPS] + dst[-1 + i * BPS];
|
|
}
|
|
Put8x8uv(dc0 >> 4, dst);
|
|
}
|
|
|
|
static void DC8uvNoLeft_C(uint8_t* dst) { // DC with no left samples
|
|
int dc0 = 4;
|
|
int i;
|
|
for (i = 0; i < 8; ++i) {
|
|
dc0 += dst[i - BPS];
|
|
}
|
|
Put8x8uv(dc0 >> 3, dst);
|
|
}
|
|
|
|
static void DC8uvNoTop_C(uint8_t* dst) { // DC with no top samples
|
|
int dc0 = 4;
|
|
int i;
|
|
for (i = 0; i < 8; ++i) {
|
|
dc0 += dst[-1 + i * BPS];
|
|
}
|
|
Put8x8uv(dc0 >> 3, dst);
|
|
}
|
|
|
|
static void DC8uvNoTopLeft_C(uint8_t* dst) { // DC with nothing
|
|
Put8x8uv(0x80, dst);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
VP8PredFunc VP8PredChroma8[NUM_B_DC_MODES];
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Edge filtering functions
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
// 4 pixels in, 2 pixels out
|
|
static WEBP_INLINE void DoFilter2_C(uint8_t* p, int step) {
|
|
const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step];
|
|
const int a = 3 * (q0 - p0) + VP8ksclip1[p1 - q1]; // in [-893,892]
|
|
const int a1 = VP8ksclip2[(a + 4) >> 3]; // in [-16,15]
|
|
const int a2 = VP8ksclip2[(a + 3) >> 3];
|
|
p[-step] = VP8kclip1[p0 + a2];
|
|
p[0] = VP8kclip1[q0 - a1];
|
|
}
|
|
|
|
// 4 pixels in, 4 pixels out
|
|
static WEBP_INLINE void DoFilter4_C(uint8_t* p, int step) {
|
|
const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step];
|
|
const int a = 3 * (q0 - p0);
|
|
const int a1 = VP8ksclip2[(a + 4) >> 3];
|
|
const int a2 = VP8ksclip2[(a + 3) >> 3];
|
|
const int a3 = (a1 + 1) >> 1;
|
|
p[-2 * step] = VP8kclip1[p1 + a3];
|
|
p[-step] = VP8kclip1[p0 + a2];
|
|
p[0] = VP8kclip1[q0 - a1];
|
|
p[step] = VP8kclip1[q1 - a3];
|
|
}
|
|
|
|
// 6 pixels in, 6 pixels out
|
|
static WEBP_INLINE void DoFilter6_C(uint8_t* p, int step) {
|
|
const int p2 = p[-3 * step], p1 = p[-2 * step], p0 = p[-step];
|
|
const int q0 = p[0], q1 = p[step], q2 = p[2 * step];
|
|
const int a = VP8ksclip1[3 * (q0 - p0) + VP8ksclip1[p1 - q1]];
|
|
// a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9]
|
|
const int a1 = (27 * a + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7
|
|
const int a2 = (18 * a + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7
|
|
const int a3 = (9 * a + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7
|
|
p[-3 * step] = VP8kclip1[p2 + a3];
|
|
p[-2 * step] = VP8kclip1[p1 + a2];
|
|
p[-step] = VP8kclip1[p0 + a1];
|
|
p[0] = VP8kclip1[q0 - a1];
|
|
p[step] = VP8kclip1[q1 - a2];
|
|
p[2 * step] = VP8kclip1[q2 - a3];
|
|
}
|
|
|
|
static WEBP_INLINE int Hev(const uint8_t* p, int step, int thresh) {
|
|
const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step];
|
|
return (VP8kabs0[p1 - p0] > thresh) || (VP8kabs0[q1 - q0] > thresh);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static WEBP_INLINE int NeedsFilter_C(const uint8_t* p, int step, int t) {
|
|
const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step];
|
|
return ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) <= t);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
static WEBP_INLINE int NeedsFilter2_C(const uint8_t* p, int step, int t,
|
|
int it) {
|
|
const int p3 = p[-4 * step], p2 = p[-3 * step], p1 = p[-2 * step];
|
|
const int p0 = p[-step], q0 = p[0];
|
|
const int q1 = p[step], q2 = p[2 * step], q3 = p[3 * step];
|
|
if ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) > t) return 0;
|
|
return VP8kabs0[p3 - p2] <= it && VP8kabs0[p2 - p1] <= it &&
|
|
VP8kabs0[p1 - p0] <= it && VP8kabs0[q3 - q2] <= it &&
|
|
VP8kabs0[q2 - q1] <= it && VP8kabs0[q1 - q0] <= it;
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Simple In-loop filtering (Paragraph 15.2)
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void SimpleVFilter16_C(uint8_t* p, int stride, int thresh) {
|
|
int i;
|
|
const int thresh2 = 2 * thresh + 1;
|
|
for (i = 0; i < 16; ++i) {
|
|
if (NeedsFilter_C(p + i, stride, thresh2)) {
|
|
DoFilter2_C(p + i, stride);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SimpleHFilter16_C(uint8_t* p, int stride, int thresh) {
|
|
int i;
|
|
const int thresh2 = 2 * thresh + 1;
|
|
for (i = 0; i < 16; ++i) {
|
|
if (NeedsFilter_C(p + i * stride, 1, thresh2)) {
|
|
DoFilter2_C(p + i * stride, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SimpleVFilter16i_C(uint8_t* p, int stride, int thresh) {
|
|
int k;
|
|
for (k = 3; k > 0; --k) {
|
|
p += 4 * stride;
|
|
SimpleVFilter16_C(p, stride, thresh);
|
|
}
|
|
}
|
|
|
|
static void SimpleHFilter16i_C(uint8_t* p, int stride, int thresh) {
|
|
int k;
|
|
for (k = 3; k > 0; --k) {
|
|
p += 4;
|
|
SimpleHFilter16_C(p, stride, thresh);
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Complex In-loop filtering (Paragraph 15.3)
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
static WEBP_INLINE void FilterLoop26_C(uint8_t* p, int hstride, int vstride,
|
|
int size, int thresh, int ithresh,
|
|
int hev_thresh) {
|
|
const int thresh2 = 2 * thresh + 1;
|
|
while (size-- > 0) {
|
|
if (NeedsFilter2_C(p, hstride, thresh2, ithresh)) {
|
|
if (Hev(p, hstride, hev_thresh)) {
|
|
DoFilter2_C(p, hstride);
|
|
} else {
|
|
DoFilter6_C(p, hstride);
|
|
}
|
|
}
|
|
p += vstride;
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE void FilterLoop24_C(uint8_t* p, int hstride, int vstride,
|
|
int size, int thresh, int ithresh,
|
|
int hev_thresh) {
|
|
const int thresh2 = 2 * thresh + 1;
|
|
while (size-- > 0) {
|
|
if (NeedsFilter2_C(p, hstride, thresh2, ithresh)) {
|
|
if (Hev(p, hstride, hev_thresh)) {
|
|
DoFilter2_C(p, hstride);
|
|
} else {
|
|
DoFilter4_C(p, hstride);
|
|
}
|
|
}
|
|
p += vstride;
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
// on macroblock edges
|
|
static void VFilter16_C(uint8_t* p, int stride, int thresh, int ithresh,
|
|
int hev_thresh) {
|
|
FilterLoop26_C(p, stride, 1, 16, thresh, ithresh, hev_thresh);
|
|
}
|
|
|
|
static void HFilter16_C(uint8_t* p, int stride, int thresh, int ithresh,
|
|
int hev_thresh) {
|
|
FilterLoop26_C(p, 1, stride, 16, thresh, ithresh, hev_thresh);
|
|
}
|
|
|
|
// on three inner edges
|
|
static void VFilter16i_C(uint8_t* p, int stride, int thresh, int ithresh,
|
|
int hev_thresh) {
|
|
int k;
|
|
for (k = 3; k > 0; --k) {
|
|
p += 4 * stride;
|
|
FilterLoop24_C(p, stride, 1, 16, thresh, ithresh, hev_thresh);
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
static void HFilter16i_C(uint8_t* p, int stride, int thresh, int ithresh,
|
|
int hev_thresh) {
|
|
int k;
|
|
for (k = 3; k > 0; --k) {
|
|
p += 4;
|
|
FilterLoop24_C(p, 1, stride, 16, thresh, ithresh, hev_thresh);
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
// 8-pixels wide variant, for chroma filtering
|
|
static void VFilter8_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v,
|
|
int stride, int thresh, int ithresh, int hev_thresh) {
|
|
FilterLoop26_C(u, stride, 1, 8, thresh, ithresh, hev_thresh);
|
|
FilterLoop26_C(v, stride, 1, 8, thresh, ithresh, hev_thresh);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
static void HFilter8_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v,
|
|
int stride, int thresh, int ithresh, int hev_thresh) {
|
|
FilterLoop26_C(u, 1, stride, 8, thresh, ithresh, hev_thresh);
|
|
FilterLoop26_C(v, 1, stride, 8, thresh, ithresh, hev_thresh);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void VFilter8i_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v,
|
|
int stride, int thresh, int ithresh, int hev_thresh) {
|
|
FilterLoop24_C(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh);
|
|
FilterLoop24_C(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
static void HFilter8i_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v,
|
|
int stride, int thresh, int ithresh, int hev_thresh) {
|
|
FilterLoop24_C(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh);
|
|
FilterLoop24_C(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void DitherCombine8x8_C(const uint8_t* WEBP_RESTRICT dither,
|
|
uint8_t* WEBP_RESTRICT dst, int dst_stride) {
|
|
int i, j;
|
|
for (j = 0; j < 8; ++j) {
|
|
for (i = 0; i < 8; ++i) {
|
|
const int delta0 = dither[i] - VP8_DITHER_AMP_CENTER;
|
|
const int delta1 =
|
|
(delta0 + VP8_DITHER_DESCALE_ROUNDER) >> VP8_DITHER_DESCALE;
|
|
dst[i] = clip_8b((int)dst[i] + delta1);
|
|
}
|
|
dst += dst_stride;
|
|
dither += 8;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
VP8DecIdct2 VP8Transform;
|
|
VP8DecIdct VP8TransformAC3;
|
|
VP8DecIdct VP8TransformUV;
|
|
VP8DecIdct VP8TransformDC;
|
|
VP8DecIdct VP8TransformDCUV;
|
|
|
|
VP8LumaFilterFunc VP8VFilter16;
|
|
VP8LumaFilterFunc VP8HFilter16;
|
|
VP8ChromaFilterFunc VP8VFilter8;
|
|
VP8ChromaFilterFunc VP8HFilter8;
|
|
VP8LumaFilterFunc VP8VFilter16i;
|
|
VP8LumaFilterFunc VP8HFilter16i;
|
|
VP8ChromaFilterFunc VP8VFilter8i;
|
|
VP8ChromaFilterFunc VP8HFilter8i;
|
|
VP8SimpleFilterFunc VP8SimpleVFilter16;
|
|
VP8SimpleFilterFunc VP8SimpleHFilter16;
|
|
VP8SimpleFilterFunc VP8SimpleVFilter16i;
|
|
VP8SimpleFilterFunc VP8SimpleHFilter16i;
|
|
|
|
void (*VP8DitherCombine8x8)(const uint8_t* WEBP_RESTRICT dither,
|
|
uint8_t* WEBP_RESTRICT dst, int dst_stride);
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
extern void VP8DspInitSSE2(void);
|
|
extern void VP8DspInitSSE41(void);
|
|
extern void VP8DspInitNEON(void);
|
|
extern void VP8DspInitMIPS32(void);
|
|
extern void VP8DspInitMIPSdspR2(void);
|
|
extern void VP8DspInitMSA(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(VP8DspInit) {
|
|
VP8InitClipTables();
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
VP8TransformWHT = TransformWHT_C;
|
|
VP8Transform = TransformTwo_C;
|
|
VP8TransformDC = TransformDC_C;
|
|
VP8TransformAC3 = TransformAC3_C;
|
|
#endif
|
|
VP8TransformUV = TransformUV_C;
|
|
VP8TransformDCUV = TransformDCUV_C;
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
VP8VFilter16 = VFilter16_C;
|
|
VP8VFilter16i = VFilter16i_C;
|
|
VP8HFilter16 = HFilter16_C;
|
|
VP8VFilter8 = VFilter8_C;
|
|
VP8VFilter8i = VFilter8i_C;
|
|
VP8SimpleVFilter16 = SimpleVFilter16_C;
|
|
VP8SimpleHFilter16 = SimpleHFilter16_C;
|
|
VP8SimpleVFilter16i = SimpleVFilter16i_C;
|
|
VP8SimpleHFilter16i = SimpleHFilter16i_C;
|
|
#endif
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC
|
|
VP8HFilter16i = HFilter16i_C;
|
|
VP8HFilter8 = HFilter8_C;
|
|
VP8HFilter8i = HFilter8i_C;
|
|
#endif
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
VP8PredLuma4[0] = DC4_C;
|
|
VP8PredLuma4[1] = TM4_C;
|
|
VP8PredLuma4[2] = VE4_C;
|
|
VP8PredLuma4[4] = RD4_C;
|
|
VP8PredLuma4[6] = LD4_C;
|
|
#endif
|
|
|
|
VP8PredLuma4[3] = HE4_C;
|
|
VP8PredLuma4[5] = VR4_C;
|
|
VP8PredLuma4[7] = VL4_C;
|
|
VP8PredLuma4[8] = HD4_C;
|
|
VP8PredLuma4[9] = HU4_C;
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
VP8PredLuma16[0] = DC16_C;
|
|
VP8PredLuma16[1] = TM16_C;
|
|
VP8PredLuma16[2] = VE16_C;
|
|
VP8PredLuma16[3] = HE16_C;
|
|
VP8PredLuma16[4] = DC16NoTop_C;
|
|
VP8PredLuma16[5] = DC16NoLeft_C;
|
|
VP8PredLuma16[6] = DC16NoTopLeft_C;
|
|
|
|
VP8PredChroma8[0] = DC8uv_C;
|
|
VP8PredChroma8[1] = TM8uv_C;
|
|
VP8PredChroma8[2] = VE8uv_C;
|
|
VP8PredChroma8[3] = HE8uv_C;
|
|
VP8PredChroma8[4] = DC8uvNoTop_C;
|
|
VP8PredChroma8[5] = DC8uvNoLeft_C;
|
|
VP8PredChroma8[6] = DC8uvNoTopLeft_C;
|
|
#endif
|
|
|
|
VP8DitherCombine8x8 = DitherCombine8x8_C;
|
|
|
|
// If defined, use CPUInfo() to overwrite some pointers with faster versions.
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
VP8DspInitSSE2();
|
|
#if defined(WEBP_HAVE_SSE41)
|
|
if (VP8GetCPUInfo(kSSE4_1)) {
|
|
VP8DspInitSSE41();
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS32)
|
|
if (VP8GetCPUInfo(kMIPS32)) {
|
|
VP8DspInitMIPS32();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
VP8DspInitMIPSdspR2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MSA)
|
|
if (VP8GetCPUInfo(kMSA)) {
|
|
VP8DspInitMSA();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(WEBP_HAVE_NEON)
|
|
if (WEBP_NEON_OMIT_C_CODE ||
|
|
(VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) {
|
|
VP8DspInitNEON();
|
|
}
|
|
#endif
|
|
|
|
assert(VP8TransformWHT != NULL);
|
|
assert(VP8Transform != NULL);
|
|
assert(VP8TransformDC != NULL);
|
|
assert(VP8TransformAC3 != NULL);
|
|
assert(VP8TransformUV != NULL);
|
|
assert(VP8TransformDCUV != NULL);
|
|
assert(VP8VFilter16 != NULL);
|
|
assert(VP8HFilter16 != NULL);
|
|
assert(VP8VFilter8 != NULL);
|
|
assert(VP8HFilter8 != NULL);
|
|
assert(VP8VFilter16i != NULL);
|
|
assert(VP8HFilter16i != NULL);
|
|
assert(VP8VFilter8i != NULL);
|
|
assert(VP8HFilter8i != NULL);
|
|
assert(VP8SimpleVFilter16 != NULL);
|
|
assert(VP8SimpleHFilter16 != NULL);
|
|
assert(VP8SimpleVFilter16i != NULL);
|
|
assert(VP8SimpleHFilter16i != NULL);
|
|
assert(VP8PredLuma4[0] != NULL);
|
|
assert(VP8PredLuma4[1] != NULL);
|
|
assert(VP8PredLuma4[2] != NULL);
|
|
assert(VP8PredLuma4[3] != NULL);
|
|
assert(VP8PredLuma4[4] != NULL);
|
|
assert(VP8PredLuma4[5] != NULL);
|
|
assert(VP8PredLuma4[6] != NULL);
|
|
assert(VP8PredLuma4[7] != NULL);
|
|
assert(VP8PredLuma4[8] != NULL);
|
|
assert(VP8PredLuma4[9] != NULL);
|
|
assert(VP8PredLuma16[0] != NULL);
|
|
assert(VP8PredLuma16[1] != NULL);
|
|
assert(VP8PredLuma16[2] != NULL);
|
|
assert(VP8PredLuma16[3] != NULL);
|
|
assert(VP8PredLuma16[4] != NULL);
|
|
assert(VP8PredLuma16[5] != NULL);
|
|
assert(VP8PredLuma16[6] != NULL);
|
|
assert(VP8PredChroma8[0] != NULL);
|
|
assert(VP8PredChroma8[1] != NULL);
|
|
assert(VP8PredChroma8[2] != NULL);
|
|
assert(VP8PredChroma8[3] != NULL);
|
|
assert(VP8PredChroma8[4] != NULL);
|
|
assert(VP8PredChroma8[5] != NULL);
|
|
assert(VP8PredChroma8[6] != NULL);
|
|
assert(VP8DitherCombine8x8 != NULL);
|
|
}
|
|
/* >>> src/dsp/dec_clip_tables.c */
|
|
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Clipping tables for filtering
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
// define to 0 to have run-time table initialization
|
|
#if !defined(USE_STATIC_TABLES)
|
|
#define USE_STATIC_TABLES 1 // ALTERNATE_CODE
|
|
#endif
|
|
|
|
#if (USE_STATIC_TABLES == 1)
|
|
|
|
static const uint8_t abs0[255 + 255 + 1] = {
|
|
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4,
|
|
0xf3, 0xf2, 0xf1, 0xf0, 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8,
|
|
0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf, 0xde, 0xdd, 0xdc,
|
|
0xdb, 0xda, 0xd9, 0xd8, 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0,
|
|
0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, 0xc7, 0xc6, 0xc5, 0xc4,
|
|
0xc3, 0xc2, 0xc1, 0xc0, 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
|
|
0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, 0xaf, 0xae, 0xad, 0xac,
|
|
0xab, 0xaa, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0,
|
|
0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94,
|
|
0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88,
|
|
0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x7f, 0x7e, 0x7d, 0x7c,
|
|
0x7b, 0x7a, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70,
|
|
0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64,
|
|
0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58,
|
|
0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4c,
|
|
0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40,
|
|
0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34,
|
|
0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28,
|
|
0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c,
|
|
0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
|
|
0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04,
|
|
0x03, 0x02, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
|
|
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
|
|
0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
|
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
|
|
0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
|
|
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c,
|
|
0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74,
|
|
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80,
|
|
0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c,
|
|
0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
|
|
0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4,
|
|
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0,
|
|
0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc,
|
|
0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
|
|
0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4,
|
|
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0,
|
|
0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec,
|
|
0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
|
0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff};
|
|
|
|
static const uint8_t sclip1[893 + 892 + 1] = {
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
|
|
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x82,
|
|
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
|
|
0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
|
|
0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
|
|
0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
|
|
0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
|
|
0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
|
|
0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
|
0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2,
|
|
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee,
|
|
0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
|
|
0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
|
|
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
|
|
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
|
|
0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
|
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
|
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
|
|
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
|
|
0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
|
|
0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
|
0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
|
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
|
|
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f};
|
|
|
|
static const uint8_t sclip2[112 + 112 + 1] = {
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
|
|
0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f};
|
|
|
|
static const uint8_t clip1[255 + 511 + 1] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
|
|
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
|
|
0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
|
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
|
|
0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
|
|
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c,
|
|
0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74,
|
|
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80,
|
|
0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c,
|
|
0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
|
|
0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4,
|
|
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0,
|
|
0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc,
|
|
0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
|
|
0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4,
|
|
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0,
|
|
0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec,
|
|
0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
|
0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
|
|
|
#else
|
|
|
|
// uninitialized tables
|
|
static uint8_t abs0[255 + 255 + 1];
|
|
static int8_t sclip1[893 + 892 + 1];
|
|
static int8_t sclip2[112 + 112 + 1];
|
|
static uint8_t clip1[255 + 511 + 1];
|
|
|
|
// We declare this variable 'volatile' to prevent instruction reordering
|
|
// and make sure it's set to true _last_ (so as to be thread-safe)
|
|
static volatile int tables_ok = 0;
|
|
|
|
#endif // USE_STATIC_TABLES
|
|
|
|
const int8_t* const VP8ksclip1 = (const int8_t*)&sclip1[893];
|
|
const int8_t* const VP8ksclip2 = (const int8_t*)&sclip2[112];
|
|
const uint8_t* const VP8kclip1 = &clip1[255];
|
|
const uint8_t* const VP8kabs0 = &abs0[255];
|
|
|
|
WEBP_TSAN_IGNORE_FUNCTION void VP8InitClipTables(void) {
|
|
#if (USE_STATIC_TABLES == 0)
|
|
int i;
|
|
if (!tables_ok) {
|
|
for (i = -255; i <= 255; ++i) {
|
|
abs0[255 + i] = (i < 0) ? -i : i;
|
|
}
|
|
for (i = -893; i <= 892; ++i) {
|
|
sclip1[893 + i] = (i < -128) ? -128 : (i > 127) ? 127 : i;
|
|
}
|
|
for (i = -112; i <= 112; ++i) {
|
|
sclip2[112 + i] = (i < -16) ? -16 : (i > 15) ? 15 : i;
|
|
}
|
|
for (i = -255; i <= 511; ++i) {
|
|
clip1[255 + i] = (i < 0) ? 0 : (i > 255) ? 255 : i;
|
|
}
|
|
tables_ok = 1;
|
|
}
|
|
#endif // USE_STATIC_TABLES
|
|
}
|
|
/* >>> src/dsp/filters.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Spatial prediction using various filters
|
|
//
|
|
// Author: Urvang (urvang@google.com)
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Helpful macro.
|
|
|
|
#define DCHECK(in, out) \
|
|
do { \
|
|
assert((in) != NULL); \
|
|
assert((out) != NULL); \
|
|
assert((in) != (out)); \
|
|
assert(width > 0); \
|
|
assert(height > 0); \
|
|
assert(stride >= width); \
|
|
} while (0)
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static WEBP_INLINE void PredictLine_C(const uint8_t* WEBP_RESTRICT src,
|
|
const uint8_t* WEBP_RESTRICT pred,
|
|
uint8_t* WEBP_RESTRICT dst, int length) {
|
|
int i;
|
|
for (i = 0; i < length; ++i) dst[i] = (uint8_t)(src[i] - pred[i]);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Horizontal filter.
|
|
|
|
static WEBP_INLINE void DoHorizontalFilter_C(const uint8_t* WEBP_RESTRICT in,
|
|
int width, int height, int stride,
|
|
uint8_t* WEBP_RESTRICT out) {
|
|
const uint8_t* preds = in;
|
|
int row;
|
|
DCHECK(in, out);
|
|
|
|
// Leftmost pixel is the same as input for topmost scanline.
|
|
out[0] = in[0];
|
|
PredictLine_C(in + 1, preds, out + 1, width - 1);
|
|
preds += stride;
|
|
in += stride;
|
|
out += stride;
|
|
|
|
// Filter line-by-line.
|
|
for (row = 1; row < height; ++row) {
|
|
// Leftmost pixel is predicted from above.
|
|
PredictLine_C(in, preds - stride, out, 1);
|
|
PredictLine_C(in + 1, preds, out + 1, width - 1);
|
|
preds += stride;
|
|
in += stride;
|
|
out += stride;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Vertical filter.
|
|
|
|
static WEBP_INLINE void DoVerticalFilter_C(const uint8_t* WEBP_RESTRICT in,
|
|
int width, int height, int stride,
|
|
uint8_t* WEBP_RESTRICT out) {
|
|
const uint8_t* preds = in;
|
|
int row;
|
|
DCHECK(in, out);
|
|
|
|
// Very first top-left pixel is copied.
|
|
out[0] = in[0];
|
|
// Rest of top scan-line is left-predicted.
|
|
PredictLine_C(in + 1, preds, out + 1, width - 1);
|
|
in += stride;
|
|
out += stride;
|
|
|
|
// Filter line-by-line.
|
|
for (row = 1; row < height; ++row) {
|
|
PredictLine_C(in, preds, out, width);
|
|
preds += stride;
|
|
in += stride;
|
|
out += stride;
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Gradient filter.
|
|
|
|
static WEBP_INLINE int GradientPredictor_C(uint8_t a, uint8_t b, uint8_t c) {
|
|
const int g = a + b - c;
|
|
return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit
|
|
}
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static WEBP_INLINE void DoGradientFilter_C(const uint8_t* WEBP_RESTRICT in,
|
|
int width, int height, int stride,
|
|
uint8_t* WEBP_RESTRICT out) {
|
|
const uint8_t* preds = in;
|
|
int row;
|
|
DCHECK(in, out);
|
|
|
|
// left prediction for top scan-line
|
|
out[0] = in[0];
|
|
PredictLine_C(in + 1, preds, out + 1, width - 1);
|
|
preds += stride;
|
|
in += stride;
|
|
out += stride;
|
|
|
|
// Filter line-by-line.
|
|
for (row = 1; row < height; ++row) {
|
|
int w;
|
|
// leftmost pixel: predict from above.
|
|
PredictLine_C(in, preds - stride, out, 1);
|
|
for (w = 1; w < width; ++w) {
|
|
const int pred = GradientPredictor_C(preds[w - 1], preds[w - stride],
|
|
preds[w - stride - 1]);
|
|
out[w] = (uint8_t)(in[w] - pred);
|
|
}
|
|
preds += stride;
|
|
in += stride;
|
|
out += stride;
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
#undef DCHECK
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void HorizontalFilter_C(const uint8_t* WEBP_RESTRICT data, int width,
|
|
int height, int stride,
|
|
uint8_t* WEBP_RESTRICT filtered_data) {
|
|
DoHorizontalFilter_C(data, width, height, stride, filtered_data);
|
|
}
|
|
|
|
static void VerticalFilter_C(const uint8_t* WEBP_RESTRICT data, int width,
|
|
int height, int stride,
|
|
uint8_t* WEBP_RESTRICT filtered_data) {
|
|
DoVerticalFilter_C(data, width, height, stride, filtered_data);
|
|
}
|
|
|
|
static void GradientFilter_C(const uint8_t* WEBP_RESTRICT data, int width,
|
|
int height, int stride,
|
|
uint8_t* WEBP_RESTRICT filtered_data) {
|
|
DoGradientFilter_C(data, width, height, stride, filtered_data);
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void NoneUnfilter_C(const uint8_t* prev, const uint8_t* in, uint8_t* out,
|
|
int width) {
|
|
(void)prev;
|
|
if (out != in) memcpy(out, in, width * sizeof(*out));
|
|
}
|
|
|
|
static void HorizontalUnfilter_C(const uint8_t* prev, const uint8_t* in,
|
|
uint8_t* out, int width) {
|
|
uint8_t pred = (prev == NULL) ? 0 : prev[0];
|
|
int i;
|
|
for (i = 0; i < width; ++i) {
|
|
out[i] = (uint8_t)(pred + in[i]);
|
|
pred = out[i];
|
|
}
|
|
}
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
static void VerticalUnfilter_C(const uint8_t* prev, const uint8_t* in,
|
|
uint8_t* out, int width) {
|
|
if (prev == NULL) {
|
|
HorizontalUnfilter_C(NULL, in, out, width);
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < width; ++i) out[i] = (uint8_t)(prev[i] + in[i]);
|
|
}
|
|
}
|
|
#endif // !WEBP_NEON_OMIT_C_CODE
|
|
|
|
static void GradientUnfilter_C(const uint8_t* prev, const uint8_t* in,
|
|
uint8_t* out, int width) {
|
|
if (prev == NULL) {
|
|
HorizontalUnfilter_C(NULL, in, out, width);
|
|
} else {
|
|
uint8_t top = prev[0], top_left = top, left = top;
|
|
int i;
|
|
for (i = 0; i < width; ++i) {
|
|
top = prev[i]; // need to read this first, in case prev==out
|
|
left = (uint8_t)(in[i] + GradientPredictor_C(left, top, top_left));
|
|
top_left = top;
|
|
out[i] = left;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Init function
|
|
|
|
WebPFilterFunc WebPFilters[WEBP_FILTER_LAST];
|
|
WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST];
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
extern void VP8FiltersInitMIPSdspR2(void);
|
|
extern void VP8FiltersInitMSA(void);
|
|
extern void VP8FiltersInitNEON(void);
|
|
extern void VP8FiltersInitSSE2(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(VP8FiltersInit) {
|
|
WebPUnfilters[WEBP_FILTER_NONE] = NoneUnfilter_C;
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_C;
|
|
WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_C;
|
|
#endif
|
|
WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_C;
|
|
|
|
WebPFilters[WEBP_FILTER_NONE] = NULL;
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_C;
|
|
WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_C;
|
|
WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_C;
|
|
#endif
|
|
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
VP8FiltersInitSSE2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
VP8FiltersInitMIPSdspR2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MSA)
|
|
if (VP8GetCPUInfo(kMSA)) {
|
|
VP8FiltersInitMSA();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(WEBP_HAVE_NEON)
|
|
if (WEBP_NEON_OMIT_C_CODE ||
|
|
(VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) {
|
|
VP8FiltersInitNEON();
|
|
}
|
|
#endif
|
|
|
|
assert(WebPUnfilters[WEBP_FILTER_NONE] != NULL);
|
|
assert(WebPUnfilters[WEBP_FILTER_HORIZONTAL] != NULL);
|
|
assert(WebPUnfilters[WEBP_FILTER_VERTICAL] != NULL);
|
|
assert(WebPUnfilters[WEBP_FILTER_GRADIENT] != NULL);
|
|
assert(WebPFilters[WEBP_FILTER_HORIZONTAL] != NULL);
|
|
assert(WebPFilters[WEBP_FILTER_VERTICAL] != NULL);
|
|
assert(WebPFilters[WEBP_FILTER_GRADIENT] != NULL);
|
|
}
|
|
/* >>> src/dsp/lossless.c */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Image transforms and color space conversion methods for lossless decoder.
|
|
//
|
|
// Authors: Vikas Arora (vikaas.arora@gmail.com)
|
|
// Jyrki Alakuijala (jyrki@google.com)
|
|
// Urvang Joshi (urvang@google.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Image transforms.
|
|
|
|
static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) {
|
|
return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1);
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) {
|
|
return Average2(Average2(a0, a2), a1);
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, uint32_t a2,
|
|
uint32_t a3) {
|
|
return Average2(Average2(a0, a1), Average2(a2, a3));
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t Clip255(uint32_t a) {
|
|
if (a < 256) {
|
|
return a;
|
|
}
|
|
// return 0, when a is a negative integer.
|
|
// return 255, when a is positive.
|
|
return ~a >> 24;
|
|
}
|
|
|
|
static WEBP_INLINE int AddSubtractComponentFull(int a, int b, int c) {
|
|
return Clip255((uint32_t)(a + b - c));
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1,
|
|
uint32_t c2) {
|
|
const int a = AddSubtractComponentFull(c0 >> 24, c1 >> 24, c2 >> 24);
|
|
const int r = AddSubtractComponentFull((c0 >> 16) & 0xff, (c1 >> 16) & 0xff,
|
|
(c2 >> 16) & 0xff);
|
|
const int g = AddSubtractComponentFull((c0 >> 8) & 0xff, (c1 >> 8) & 0xff,
|
|
(c2 >> 8) & 0xff);
|
|
const int b = AddSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff);
|
|
return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b;
|
|
}
|
|
|
|
static WEBP_INLINE int AddSubtractComponentHalf(int a, int b) {
|
|
return Clip255((uint32_t)(a + (a - b) / 2));
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1,
|
|
uint32_t c2) {
|
|
const uint32_t ave = Average2(c0, c1);
|
|
const int a = AddSubtractComponentHalf(ave >> 24, c2 >> 24);
|
|
const int r = AddSubtractComponentHalf((ave >> 16) & 0xff, (c2 >> 16) & 0xff);
|
|
const int g = AddSubtractComponentHalf((ave >> 8) & 0xff, (c2 >> 8) & 0xff);
|
|
const int b = AddSubtractComponentHalf((ave >> 0) & 0xff, (c2 >> 0) & 0xff);
|
|
return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b;
|
|
}
|
|
|
|
// gcc <= 4.9 on ARM generates incorrect code in Select() when Sub3() is
|
|
// inlined.
|
|
#if defined(__arm__) && defined(__GNUC__) && LOCAL_GCC_VERSION <= 0x409
|
|
#define LOCAL_INLINE __attribute__((noinline))
|
|
#else
|
|
#define LOCAL_INLINE WEBP_INLINE
|
|
#endif
|
|
|
|
static LOCAL_INLINE int Sub3(int a, int b, int c) {
|
|
const int pb = b - c;
|
|
const int pa = a - c;
|
|
return abs(pb) - abs(pa);
|
|
}
|
|
|
|
#undef LOCAL_INLINE
|
|
|
|
static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) {
|
|
const int pa_minus_pb =
|
|
Sub3((a >> 24), (b >> 24), (c >> 24)) +
|
|
Sub3((a >> 16) & 0xff, (b >> 16) & 0xff, (c >> 16) & 0xff) +
|
|
Sub3((a >> 8) & 0xff, (b >> 8) & 0xff, (c >> 8) & 0xff) +
|
|
Sub3((a) & 0xff, (b) & 0xff, (c) & 0xff);
|
|
return (pa_minus_pb <= 0) ? a : b;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Predictors
|
|
|
|
static uint32_t VP8LPredictor0_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
(void)top;
|
|
(void)left;
|
|
return ARGB_BLACK;
|
|
}
|
|
static uint32_t VP8LPredictor1_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
(void)top;
|
|
return *left;
|
|
}
|
|
uint32_t VP8LPredictor2_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
(void)left;
|
|
return top[0];
|
|
}
|
|
uint32_t VP8LPredictor3_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
(void)left;
|
|
return top[1];
|
|
}
|
|
uint32_t VP8LPredictor4_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
(void)left;
|
|
return top[-1];
|
|
}
|
|
uint32_t VP8LPredictor5_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = Average3(*left, top[0], top[1]);
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor6_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = Average2(*left, top[-1]);
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor7_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = Average2(*left, top[0]);
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor8_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = Average2(top[-1], top[0]);
|
|
(void)left;
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor9_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = Average2(top[0], top[1]);
|
|
(void)left;
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor10_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = Average4(*left, top[-1], top[0], top[1]);
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor11_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = Select(top[0], *left, top[-1]);
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor12_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = ClampedAddSubtractFull(*left, top[0], top[-1]);
|
|
return pred;
|
|
}
|
|
uint32_t VP8LPredictor13_C(const uint32_t* const left,
|
|
const uint32_t* const top) {
|
|
const uint32_t pred = ClampedAddSubtractHalf(*left, top[0], top[-1]);
|
|
return pred;
|
|
}
|
|
|
|
static void PredictorAdd0_C(const uint32_t* in, const uint32_t* upper,
|
|
int num_pixels, uint32_t* WEBP_RESTRICT out) {
|
|
int x;
|
|
(void)upper;
|
|
for (x = 0; x < num_pixels; ++x) out[x] = VP8LAddPixels(in[x], ARGB_BLACK);
|
|
}
|
|
static void PredictorAdd1_C(const uint32_t* in, const uint32_t* upper,
|
|
int num_pixels, uint32_t* WEBP_RESTRICT out) {
|
|
int i;
|
|
uint32_t left = out[-1];
|
|
(void)upper;
|
|
for (i = 0; i < num_pixels; ++i) {
|
|
out[i] = left = VP8LAddPixels(in[i], left);
|
|
}
|
|
}
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor2_C, PredictorAdd2_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor3_C, PredictorAdd3_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor4_C, PredictorAdd4_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor5_C, PredictorAdd5_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor6_C, PredictorAdd6_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor7_C, PredictorAdd7_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor8_C, PredictorAdd8_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor9_C, PredictorAdd9_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor10_C, PredictorAdd10_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor11_C, PredictorAdd11_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor12_C, PredictorAdd12_C)
|
|
GENERATE_PREDICTOR_ADD(VP8LPredictor13_C, PredictorAdd13_C)
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Inverse prediction.
|
|
static void PredictorInverseTransform_C(const VP8LTransform* const transform,
|
|
int y_start, int y_end,
|
|
const uint32_t* in, uint32_t* out) {
|
|
const int width = transform->xsize;
|
|
if (y_start == 0) { // First Row follows the L (mode=1) mode.
|
|
PredictorAdd0_C(in, NULL, 1, out);
|
|
PredictorAdd1_C(in + 1, NULL, width - 1, out + 1);
|
|
in += width;
|
|
out += width;
|
|
++y_start;
|
|
}
|
|
|
|
{
|
|
int y = y_start;
|
|
const int tile_width = 1 << transform->bits;
|
|
const int mask = tile_width - 1;
|
|
const int tiles_per_row = VP8LSubSampleSize(width, transform->bits);
|
|
const uint32_t* pred_mode_base =
|
|
transform->data + (y >> transform->bits) * tiles_per_row;
|
|
|
|
while (y < y_end) {
|
|
const uint32_t* pred_mode_src = pred_mode_base;
|
|
int x = 1;
|
|
// First pixel follows the T (mode=2) mode.
|
|
PredictorAdd2_C(in, out - width, 1, out);
|
|
// .. the rest:
|
|
while (x < width) {
|
|
const VP8LPredictorAddSubFunc pred_func =
|
|
VP8LPredictorsAdd[((*pred_mode_src++) >> 8) & 0xf];
|
|
int x_end = (x & ~mask) + tile_width;
|
|
if (x_end > width) x_end = width;
|
|
pred_func(in + x, out + x - width, x_end - x, out + x);
|
|
x = x_end;
|
|
}
|
|
in += width;
|
|
out += width;
|
|
++y;
|
|
if ((y & mask) == 0) { // Use the same mask, since tiles are squares.
|
|
pred_mode_base += tiles_per_row;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add green to blue and red channels (i.e. perform the inverse transform of
|
|
// 'subtract green').
|
|
void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels,
|
|
uint32_t* dst) {
|
|
int i;
|
|
for (i = 0; i < num_pixels; ++i) {
|
|
const uint32_t argb = src[i];
|
|
const uint32_t green = ((argb >> 8) & 0xff);
|
|
uint32_t red_blue = (argb & 0x00ff00ffu);
|
|
red_blue += (green << 16) | green;
|
|
red_blue &= 0x00ff00ffu;
|
|
dst[i] = (argb & 0xff00ff00u) | red_blue;
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE int ColorTransformDelta(int8_t color_pred, int8_t color) {
|
|
return ((int)color_pred * color) >> 5;
|
|
}
|
|
|
|
static WEBP_INLINE void ColorCodeToMultipliers(uint32_t color_code,
|
|
VP8LMultipliers* const m) {
|
|
m->green_to_red = (color_code >> 0) & 0xff;
|
|
m->green_to_blue = (color_code >> 8) & 0xff;
|
|
m->red_to_blue = (color_code >> 16) & 0xff;
|
|
}
|
|
|
|
void VP8LTransformColorInverse_C(const VP8LMultipliers* const m,
|
|
const uint32_t* src, int num_pixels,
|
|
uint32_t* dst) {
|
|
int i;
|
|
for (i = 0; i < num_pixels; ++i) {
|
|
const uint32_t argb = src[i];
|
|
const int8_t green = (int8_t)(argb >> 8);
|
|
const uint32_t red = argb >> 16;
|
|
int new_red = red & 0xff;
|
|
int new_blue = argb & 0xff;
|
|
new_red += ColorTransformDelta((int8_t)m->green_to_red, green);
|
|
new_red &= 0xff;
|
|
new_blue += ColorTransformDelta((int8_t)m->green_to_blue, green);
|
|
new_blue += ColorTransformDelta((int8_t)m->red_to_blue, (int8_t)new_red);
|
|
new_blue &= 0xff;
|
|
dst[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue);
|
|
}
|
|
}
|
|
|
|
// Color space inverse transform.
|
|
static void ColorSpaceInverseTransform_C(const VP8LTransform* const transform,
|
|
int y_start, int y_end,
|
|
const uint32_t* src, uint32_t* dst) {
|
|
const int width = transform->xsize;
|
|
const int tile_width = 1 << transform->bits;
|
|
const int mask = tile_width - 1;
|
|
const int safe_width = width & ~mask;
|
|
const int remaining_width = width - safe_width;
|
|
const int tiles_per_row = VP8LSubSampleSize(width, transform->bits);
|
|
int y = y_start;
|
|
const uint32_t* pred_row =
|
|
transform->data + (y >> transform->bits) * tiles_per_row;
|
|
|
|
while (y < y_end) {
|
|
const uint32_t* pred = pred_row;
|
|
VP8LMultipliers m = {0, 0, 0};
|
|
const uint32_t* const src_safe_end = src + safe_width;
|
|
const uint32_t* const src_end = src + width;
|
|
while (src < src_safe_end) {
|
|
ColorCodeToMultipliers(*pred++, &m);
|
|
VP8LTransformColorInverse(&m, src, tile_width, dst);
|
|
src += tile_width;
|
|
dst += tile_width;
|
|
}
|
|
if (src < src_end) { // Left-overs using C-version.
|
|
ColorCodeToMultipliers(*pred++, &m);
|
|
VP8LTransformColorInverse(&m, src, remaining_width, dst);
|
|
src += remaining_width;
|
|
dst += remaining_width;
|
|
}
|
|
++y;
|
|
if ((y & mask) == 0) pred_row += tiles_per_row;
|
|
}
|
|
}
|
|
|
|
// Separate out pixels packed together using pixel-bundling.
|
|
// We define two methods for ARGB data (uint32_t) and alpha-only data (uint8_t).
|
|
// clang-format off
|
|
#define COLOR_INDEX_INVERSE(FUNC_NAME, F_NAME, STATIC_DECL, TYPE, BIT_SUFFIX, \
|
|
GET_INDEX, GET_VALUE) \
|
|
static void F_NAME(const TYPE* src, const uint32_t* const color_map, \
|
|
TYPE* dst, int y_start, int y_end, int width) { \
|
|
int y; \
|
|
for (y = y_start; y < y_end; ++y) { \
|
|
int x; \
|
|
for (x = 0; x < width; ++x) { \
|
|
*dst++ = GET_VALUE(color_map[GET_INDEX(*src++)]); \
|
|
} \
|
|
} \
|
|
} \
|
|
STATIC_DECL void FUNC_NAME(const VP8LTransform* const transform, \
|
|
int y_start, int y_end, const TYPE* src, \
|
|
TYPE* dst) { \
|
|
int y; \
|
|
const int bits_per_pixel = 8 >> transform->bits; \
|
|
const int width = transform->xsize; \
|
|
const uint32_t* const color_map = transform->data; \
|
|
if (bits_per_pixel < 8) { \
|
|
const int pixels_per_byte = 1 << transform->bits; \
|
|
const uint32_t bit_mask = (1 << bits_per_pixel) - 1; \
|
|
for (y = y_start; y < y_end; ++y) { \
|
|
int x; \
|
|
for (x = 0; x + pixels_per_byte <= width; x += pixels_per_byte) { \
|
|
uint32_t packed = GET_INDEX(*src++); \
|
|
if (bits_per_pixel == 1) { \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
packed >>= 1; \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
packed >>= 1; \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
packed >>= 1; \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
packed >>= 1; \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
packed >>= 1; \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
packed >>= 1; \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
packed >>= 1; \
|
|
*dst++ = GET_VALUE(color_map[packed & 1]); \
|
|
} else if (bits_per_pixel == 2) { \
|
|
*dst++ = GET_VALUE(color_map[packed & 3]); \
|
|
packed >>= 2; \
|
|
*dst++ = GET_VALUE(color_map[packed & 3]); \
|
|
packed >>= 2; \
|
|
*dst++ = GET_VALUE(color_map[packed & 3]); \
|
|
packed >>= 2; \
|
|
*dst++ = GET_VALUE(color_map[packed & 3]); \
|
|
} else { \
|
|
*dst++ = GET_VALUE(color_map[packed & 15]); \
|
|
packed >>= 4; \
|
|
*dst++ = GET_VALUE(color_map[packed & 15]); \
|
|
} \
|
|
} \
|
|
if (x < width) { \
|
|
uint32_t packed = GET_INDEX(*src++); \
|
|
for (; x < width; ++x) { \
|
|
*dst++ = GET_VALUE(color_map[packed & bit_mask]); \
|
|
packed >>= bits_per_pixel; \
|
|
} \
|
|
} \
|
|
} \
|
|
} else { \
|
|
VP8LMapColor##BIT_SUFFIX(src, color_map, dst, y_start, y_end, width); \
|
|
} \
|
|
}
|
|
// clang-format on
|
|
|
|
COLOR_INDEX_INVERSE(ColorIndexInverseTransform_C, MapARGB_C, static, uint32_t,
|
|
32b, VP8GetARGBIndex, VP8GetARGBValue)
|
|
COLOR_INDEX_INVERSE(VP8LColorIndexInverseTransformAlpha, MapAlpha_C, , uint8_t,
|
|
8b, VP8GetAlphaIndex, VP8GetAlphaValue)
|
|
|
|
#undef COLOR_INDEX_INVERSE
|
|
|
|
void VP8LInverseTransform(const VP8LTransform* const transform, int row_start,
|
|
int row_end, const uint32_t* const in,
|
|
uint32_t* const out) {
|
|
const int width = transform->xsize;
|
|
assert(row_start < row_end);
|
|
assert(row_end <= transform->ysize);
|
|
switch (transform->type) {
|
|
case SUBTRACT_GREEN_TRANSFORM:
|
|
VP8LAddGreenToBlueAndRed(in, (row_end - row_start) * width, out);
|
|
break;
|
|
case PREDICTOR_TRANSFORM:
|
|
PredictorInverseTransform_C(transform, row_start, row_end, in, out);
|
|
if (row_end != transform->ysize) {
|
|
// The last predicted row in this iteration will be the top-pred row
|
|
// for the first row in next iteration.
|
|
memcpy(out - width, out + (row_end - row_start - 1) * width,
|
|
width * sizeof(*out));
|
|
}
|
|
break;
|
|
case CROSS_COLOR_TRANSFORM:
|
|
ColorSpaceInverseTransform_C(transform, row_start, row_end, in, out);
|
|
break;
|
|
case COLOR_INDEXING_TRANSFORM:
|
|
if (in == out && transform->bits > 0) {
|
|
// Move packed pixels to the end of unpacked region, so that unpacking
|
|
// can occur seamlessly.
|
|
// Also, note that this is the only transform that applies on
|
|
// the effective width of VP8LSubSampleSize(xsize, bits). All other
|
|
// transforms work on effective width of 'xsize'.
|
|
const int out_stride = (row_end - row_start) * width;
|
|
const int in_stride =
|
|
(row_end - row_start) *
|
|
VP8LSubSampleSize(transform->xsize, transform->bits);
|
|
uint32_t* const src = out + out_stride - in_stride;
|
|
memmove(src, out, in_stride * sizeof(*src));
|
|
ColorIndexInverseTransform_C(transform, row_start, row_end, src, out);
|
|
} else {
|
|
ColorIndexInverseTransform_C(transform, row_start, row_end, in, out);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Color space conversion.
|
|
|
|
static int is_big_endian(void) {
|
|
static const union {
|
|
uint16_t w;
|
|
uint8_t b[2];
|
|
} tmp = {1};
|
|
return (tmp.b[0] != 1);
|
|
}
|
|
|
|
void VP8LConvertBGRAToRGB_C(const uint32_t* WEBP_RESTRICT src, int num_pixels,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
const uint32_t* const src_end = src + num_pixels;
|
|
while (src < src_end) {
|
|
const uint32_t argb = *src++;
|
|
*dst++ = (argb >> 16) & 0xff;
|
|
*dst++ = (argb >> 8) & 0xff;
|
|
*dst++ = (argb >> 0) & 0xff;
|
|
}
|
|
}
|
|
|
|
void VP8LConvertBGRAToRGBA_C(const uint32_t* WEBP_RESTRICT src, int num_pixels,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
const uint32_t* const src_end = src + num_pixels;
|
|
while (src < src_end) {
|
|
const uint32_t argb = *src++;
|
|
*dst++ = (argb >> 16) & 0xff;
|
|
*dst++ = (argb >> 8) & 0xff;
|
|
*dst++ = (argb >> 0) & 0xff;
|
|
*dst++ = (argb >> 24) & 0xff;
|
|
}
|
|
}
|
|
|
|
void VP8LConvertBGRAToRGBA4444_C(const uint32_t* WEBP_RESTRICT src,
|
|
int num_pixels, uint8_t* WEBP_RESTRICT dst) {
|
|
const uint32_t* const src_end = src + num_pixels;
|
|
while (src < src_end) {
|
|
const uint32_t argb = *src++;
|
|
const uint8_t rg = ((argb >> 16) & 0xf0) | ((argb >> 12) & 0xf);
|
|
const uint8_t ba = ((argb >> 0) & 0xf0) | ((argb >> 28) & 0xf);
|
|
#if (WEBP_SWAP_16BIT_CSP == 1)
|
|
*dst++ = ba;
|
|
*dst++ = rg;
|
|
#else
|
|
*dst++ = rg;
|
|
*dst++ = ba;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void VP8LConvertBGRAToRGB565_C(const uint32_t* WEBP_RESTRICT src,
|
|
int num_pixels, uint8_t* WEBP_RESTRICT dst) {
|
|
const uint32_t* const src_end = src + num_pixels;
|
|
while (src < src_end) {
|
|
const uint32_t argb = *src++;
|
|
const uint8_t rg = ((argb >> 16) & 0xf8) | ((argb >> 13) & 0x7);
|
|
const uint8_t gb = ((argb >> 5) & 0xe0) | ((argb >> 3) & 0x1f);
|
|
#if (WEBP_SWAP_16BIT_CSP == 1)
|
|
*dst++ = gb;
|
|
*dst++ = rg;
|
|
#else
|
|
*dst++ = rg;
|
|
*dst++ = gb;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void VP8LConvertBGRAToBGR_C(const uint32_t* WEBP_RESTRICT src, int num_pixels,
|
|
uint8_t* WEBP_RESTRICT dst) {
|
|
const uint32_t* const src_end = src + num_pixels;
|
|
while (src < src_end) {
|
|
const uint32_t argb = *src++;
|
|
*dst++ = (argb >> 0) & 0xff;
|
|
*dst++ = (argb >> 8) & 0xff;
|
|
*dst++ = (argb >> 16) & 0xff;
|
|
}
|
|
}
|
|
|
|
static void CopyOrSwap(const uint32_t* WEBP_RESTRICT src, int num_pixels,
|
|
uint8_t* WEBP_RESTRICT dst, int swap_on_big_endian) {
|
|
if (is_big_endian() == swap_on_big_endian) {
|
|
const uint32_t* const src_end = src + num_pixels;
|
|
while (src < src_end) {
|
|
const uint32_t argb = *src++;
|
|
WebPUint32ToMem(dst, BSwap32(argb));
|
|
dst += sizeof(argb);
|
|
}
|
|
} else {
|
|
memcpy(dst, src, num_pixels * sizeof(*src));
|
|
}
|
|
}
|
|
|
|
void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels,
|
|
WEBP_CSP_MODE out_colorspace, uint8_t* const rgba) {
|
|
switch (out_colorspace) {
|
|
case MODE_RGB:
|
|
VP8LConvertBGRAToRGB(in_data, num_pixels, rgba);
|
|
break;
|
|
case MODE_RGBA:
|
|
VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba);
|
|
break;
|
|
case MODE_rgbA:
|
|
VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba);
|
|
WebPApplyAlphaMultiply(rgba, 0, num_pixels, 1, 0);
|
|
break;
|
|
case MODE_BGR:
|
|
VP8LConvertBGRAToBGR(in_data, num_pixels, rgba);
|
|
break;
|
|
case MODE_BGRA:
|
|
CopyOrSwap(in_data, num_pixels, rgba, 1);
|
|
break;
|
|
case MODE_bgrA:
|
|
CopyOrSwap(in_data, num_pixels, rgba, 1);
|
|
WebPApplyAlphaMultiply(rgba, 0, num_pixels, 1, 0);
|
|
break;
|
|
case MODE_ARGB:
|
|
CopyOrSwap(in_data, num_pixels, rgba, 0);
|
|
break;
|
|
case MODE_Argb:
|
|
CopyOrSwap(in_data, num_pixels, rgba, 0);
|
|
WebPApplyAlphaMultiply(rgba, 1, num_pixels, 1, 0);
|
|
break;
|
|
case MODE_RGBA_4444:
|
|
VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba);
|
|
break;
|
|
case MODE_rgbA_4444:
|
|
VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba);
|
|
WebPApplyAlphaMultiply4444(rgba, num_pixels, 1, 0);
|
|
break;
|
|
case MODE_RGB_565:
|
|
VP8LConvertBGRAToRGB565(in_data, num_pixels, rgba);
|
|
break;
|
|
default:
|
|
assert(0); // Code flow should not reach here.
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed;
|
|
VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed_SSE;
|
|
VP8LPredictorAddSubFunc VP8LPredictorsAdd[16];
|
|
VP8LPredictorAddSubFunc VP8LPredictorsAdd_SSE[16];
|
|
VP8LPredictorFunc VP8LPredictors[16];
|
|
|
|
// exposed plain-C implementations
|
|
VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16];
|
|
|
|
VP8LTransformColorInverseFunc VP8LTransformColorInverse;
|
|
VP8LTransformColorInverseFunc VP8LTransformColorInverse_SSE;
|
|
|
|
VP8LConvertFunc VP8LConvertBGRAToRGB;
|
|
VP8LConvertFunc VP8LConvertBGRAToRGB_SSE;
|
|
VP8LConvertFunc VP8LConvertBGRAToRGBA;
|
|
VP8LConvertFunc VP8LConvertBGRAToRGBA_SSE;
|
|
VP8LConvertFunc VP8LConvertBGRAToRGBA4444;
|
|
VP8LConvertFunc VP8LConvertBGRAToRGB565;
|
|
VP8LConvertFunc VP8LConvertBGRAToBGR;
|
|
|
|
VP8LMapARGBFunc VP8LMapColor32b;
|
|
VP8LMapAlphaFunc VP8LMapColor8b;
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
extern void VP8LDspInitSSE2(void);
|
|
extern void VP8LDspInitSSE41(void);
|
|
extern void VP8LDspInitAVX2(void);
|
|
extern void VP8LDspInitNEON(void);
|
|
extern void VP8LDspInitMIPSdspR2(void);
|
|
extern void VP8LDspInitMSA(void);
|
|
|
|
#define COPY_PREDICTOR_ARRAY(IN, OUT) \
|
|
do { \
|
|
(OUT)[0] = IN##0_C; \
|
|
(OUT)[1] = IN##1_C; \
|
|
(OUT)[2] = IN##2_C; \
|
|
(OUT)[3] = IN##3_C; \
|
|
(OUT)[4] = IN##4_C; \
|
|
(OUT)[5] = IN##5_C; \
|
|
(OUT)[6] = IN##6_C; \
|
|
(OUT)[7] = IN##7_C; \
|
|
(OUT)[8] = IN##8_C; \
|
|
(OUT)[9] = IN##9_C; \
|
|
(OUT)[10] = IN##10_C; \
|
|
(OUT)[11] = IN##11_C; \
|
|
(OUT)[12] = IN##12_C; \
|
|
(OUT)[13] = IN##13_C; \
|
|
(OUT)[14] = IN##0_C; /* <- padding security sentinels*/ \
|
|
(OUT)[15] = IN##0_C; \
|
|
} while (0);
|
|
|
|
WEBP_DSP_INIT_FUNC(VP8LDspInit) {
|
|
COPY_PREDICTOR_ARRAY(VP8LPredictor, VP8LPredictors)
|
|
COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd)
|
|
COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd_C)
|
|
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
VP8LAddGreenToBlueAndRed = VP8LAddGreenToBlueAndRed_C;
|
|
|
|
VP8LTransformColorInverse = VP8LTransformColorInverse_C;
|
|
|
|
VP8LConvertBGRAToRGBA = VP8LConvertBGRAToRGBA_C;
|
|
VP8LConvertBGRAToRGB = VP8LConvertBGRAToRGB_C;
|
|
VP8LConvertBGRAToBGR = VP8LConvertBGRAToBGR_C;
|
|
#endif
|
|
|
|
VP8LConvertBGRAToRGBA4444 = VP8LConvertBGRAToRGBA4444_C;
|
|
VP8LConvertBGRAToRGB565 = VP8LConvertBGRAToRGB565_C;
|
|
|
|
VP8LMapColor32b = MapARGB_C;
|
|
VP8LMapColor8b = MapAlpha_C;
|
|
|
|
// If defined, use CPUInfo() to overwrite some pointers with faster versions.
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
VP8LDspInitSSE2();
|
|
#if defined(WEBP_HAVE_SSE41)
|
|
if (VP8GetCPUInfo(kSSE4_1)) {
|
|
VP8LDspInitSSE41();
|
|
#if defined(WEBP_HAVE_AVX2)
|
|
if (VP8GetCPUInfo(kAVX2)) {
|
|
VP8LDspInitAVX2();
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
VP8LDspInitMIPSdspR2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MSA)
|
|
if (VP8GetCPUInfo(kMSA)) {
|
|
VP8LDspInitMSA();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(WEBP_HAVE_NEON)
|
|
if (WEBP_NEON_OMIT_C_CODE ||
|
|
(VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) {
|
|
VP8LDspInitNEON();
|
|
}
|
|
#endif
|
|
|
|
assert(VP8LAddGreenToBlueAndRed != NULL);
|
|
assert(VP8LTransformColorInverse != NULL);
|
|
assert(VP8LConvertBGRAToRGBA != NULL);
|
|
assert(VP8LConvertBGRAToRGB != NULL);
|
|
assert(VP8LConvertBGRAToBGR != NULL);
|
|
assert(VP8LConvertBGRAToRGBA4444 != NULL);
|
|
assert(VP8LConvertBGRAToRGB565 != NULL);
|
|
assert(VP8LMapColor32b != NULL);
|
|
assert(VP8LMapColor8b != NULL);
|
|
}
|
|
#undef COPY_PREDICTOR_ARRAY
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dsp/rescaler.c */
|
|
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Rescaling functions
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Implementations of critical functions ImportRow / ExportRow
|
|
|
|
#define ROUNDER (WEBP_RESCALER_ONE >> 1)
|
|
#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX)
|
|
#define MULT_FIX_FLOOR(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX)
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Row import
|
|
WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW void WebPRescalerImportRowExpand_C(
|
|
WebPRescaler* WEBP_RESTRICT const wrk, const uint8_t* WEBP_RESTRICT src) {
|
|
const int x_stride = wrk->num_channels;
|
|
const int x_out_max = wrk->dst_width * wrk->num_channels;
|
|
int channel;
|
|
assert(!WebPRescalerInputDone(wrk));
|
|
assert(wrk->x_expand);
|
|
for (channel = 0; channel < x_stride; ++channel) {
|
|
int x_in = channel;
|
|
int x_out = channel;
|
|
// simple bilinear interpolation
|
|
int accum = wrk->x_add;
|
|
rescaler_t left = (rescaler_t)src[x_in];
|
|
rescaler_t right =
|
|
(wrk->src_width > 1) ? (rescaler_t)src[x_in + x_stride] : left;
|
|
x_in += x_stride;
|
|
while (1) {
|
|
wrk->frow[x_out] = right * wrk->x_add + (left - right) * accum;
|
|
x_out += x_stride;
|
|
if (x_out >= x_out_max) break;
|
|
accum -= wrk->x_sub;
|
|
if (accum < 0) {
|
|
left = right;
|
|
x_in += x_stride;
|
|
assert(x_in < wrk->src_width * x_stride);
|
|
right = (rescaler_t)src[x_in];
|
|
accum += wrk->x_add;
|
|
}
|
|
}
|
|
assert(wrk->x_sub == 0 /* <- special case for src_width=1 */ || accum == 0);
|
|
}
|
|
}
|
|
|
|
void WebPRescalerImportRowShrink_C(WebPRescaler* WEBP_RESTRICT const wrk,
|
|
const uint8_t* WEBP_RESTRICT src) {
|
|
const int x_stride = wrk->num_channels;
|
|
const int x_out_max = wrk->dst_width * wrk->num_channels;
|
|
int channel;
|
|
assert(!WebPRescalerInputDone(wrk));
|
|
assert(!wrk->x_expand);
|
|
for (channel = 0; channel < x_stride; ++channel) {
|
|
int x_in = channel;
|
|
int x_out = channel;
|
|
uint32_t sum = 0;
|
|
int accum = 0;
|
|
while (x_out < x_out_max) {
|
|
uint32_t base = 0;
|
|
accum += wrk->x_add;
|
|
while (accum > 0) {
|
|
accum -= wrk->x_sub;
|
|
assert(x_in < wrk->src_width * x_stride);
|
|
base = src[x_in];
|
|
sum += base;
|
|
x_in += x_stride;
|
|
}
|
|
{ // Emit next horizontal pixel.
|
|
const rescaler_t frac = base * (-accum);
|
|
wrk->frow[x_out] = sum * wrk->x_sub - frac;
|
|
// fresh fractional start for next pixel
|
|
sum = (int)MULT_FIX(frac, wrk->fx_scale);
|
|
}
|
|
x_out += x_stride;
|
|
}
|
|
assert(accum == 0);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Row export
|
|
|
|
void WebPRescalerExportRowExpand_C(WebPRescaler* const wrk) {
|
|
int x_out;
|
|
uint8_t* const dst = wrk->dst;
|
|
rescaler_t* const irow = wrk->irow;
|
|
const int x_out_max = wrk->dst_width * wrk->num_channels;
|
|
const rescaler_t* const frow = wrk->frow;
|
|
assert(!WebPRescalerOutputDone(wrk));
|
|
assert(wrk->y_accum <= 0);
|
|
assert(wrk->y_expand);
|
|
assert(wrk->y_sub != 0);
|
|
if (wrk->y_accum == 0) {
|
|
for (x_out = 0; x_out < x_out_max; ++x_out) {
|
|
const uint32_t J = frow[x_out];
|
|
const int v = (int)MULT_FIX(J, wrk->fy_scale);
|
|
dst[x_out] = (v > 255) ? 255u : (uint8_t)v;
|
|
}
|
|
} else {
|
|
const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub);
|
|
const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B);
|
|
for (x_out = 0; x_out < x_out_max; ++x_out) {
|
|
const uint64_t I = (uint64_t)A * frow[x_out] + (uint64_t)B * irow[x_out];
|
|
const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX);
|
|
const int v = (int)MULT_FIX(J, wrk->fy_scale);
|
|
dst[x_out] = (v > 255) ? 255u : (uint8_t)v;
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebPRescalerExportRowShrink_C(WebPRescaler* const wrk) {
|
|
int x_out;
|
|
uint8_t* const dst = wrk->dst;
|
|
rescaler_t* const irow = wrk->irow;
|
|
const int x_out_max = wrk->dst_width * wrk->num_channels;
|
|
const rescaler_t* const frow = wrk->frow;
|
|
const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum);
|
|
assert(!WebPRescalerOutputDone(wrk));
|
|
assert(wrk->y_accum <= 0);
|
|
assert(!wrk->y_expand);
|
|
if (yscale) {
|
|
for (x_out = 0; x_out < x_out_max; ++x_out) {
|
|
const uint32_t frac = (uint32_t)MULT_FIX_FLOOR(frow[x_out], yscale);
|
|
const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale);
|
|
dst[x_out] = (v > 255) ? 255u : (uint8_t)v;
|
|
irow[x_out] = frac; // new fractional start
|
|
}
|
|
} else {
|
|
for (x_out = 0; x_out < x_out_max; ++x_out) {
|
|
const int v = (int)MULT_FIX(irow[x_out], wrk->fxy_scale);
|
|
dst[x_out] = (v > 255) ? 255u : (uint8_t)v;
|
|
irow[x_out] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef MULT_FIX_FLOOR
|
|
#undef MULT_FIX
|
|
#undef ROUNDER
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main entry calls
|
|
|
|
void WebPRescalerImportRow(WebPRescaler* WEBP_RESTRICT const wrk,
|
|
const uint8_t* WEBP_RESTRICT src) {
|
|
assert(!WebPRescalerInputDone(wrk));
|
|
if (!wrk->x_expand) {
|
|
WebPRescalerImportRowShrink(wrk, src);
|
|
} else {
|
|
WebPRescalerImportRowExpand(wrk, src);
|
|
}
|
|
}
|
|
|
|
void WebPRescalerExportRow(WebPRescaler* const wrk) {
|
|
if (wrk->y_accum <= 0) {
|
|
assert(!WebPRescalerOutputDone(wrk));
|
|
if (wrk->y_expand) {
|
|
WebPRescalerExportRowExpand(wrk);
|
|
} else if (wrk->fxy_scale) {
|
|
WebPRescalerExportRowShrink(wrk);
|
|
} else { // special case
|
|
int i;
|
|
assert(wrk->src_height == wrk->dst_height && wrk->x_add == 1);
|
|
assert(wrk->src_width == 1 && wrk->dst_width <= 2);
|
|
for (i = 0; i < wrk->num_channels * wrk->dst_width; ++i) {
|
|
wrk->dst[i] = wrk->irow[i];
|
|
wrk->irow[i] = 0;
|
|
}
|
|
}
|
|
wrk->y_accum += wrk->y_add;
|
|
wrk->dst += wrk->dst_stride;
|
|
++wrk->dst_y;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
WebPRescalerImportRowFunc WebPRescalerImportRowExpand;
|
|
WebPRescalerImportRowFunc WebPRescalerImportRowShrink;
|
|
|
|
WebPRescalerExportRowFunc WebPRescalerExportRowExpand;
|
|
WebPRescalerExportRowFunc WebPRescalerExportRowShrink;
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
extern void WebPRescalerDspInitSSE2(void);
|
|
extern void WebPRescalerDspInitMIPS32(void);
|
|
extern void WebPRescalerDspInitMIPSdspR2(void);
|
|
extern void WebPRescalerDspInitMSA(void);
|
|
extern void WebPRescalerDspInitNEON(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(WebPRescalerDspInit) {
|
|
#if !defined(WEBP_REDUCE_SIZE)
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
WebPRescalerExportRowExpand = WebPRescalerExportRowExpand_C;
|
|
WebPRescalerExportRowShrink = WebPRescalerExportRowShrink_C;
|
|
#endif
|
|
|
|
WebPRescalerImportRowExpand = WebPRescalerImportRowExpand_C;
|
|
WebPRescalerImportRowShrink = WebPRescalerImportRowShrink_C;
|
|
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
WebPRescalerDspInitSSE2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS32)
|
|
if (VP8GetCPUInfo(kMIPS32)) {
|
|
WebPRescalerDspInitMIPS32();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
WebPRescalerDspInitMIPSdspR2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MSA)
|
|
if (VP8GetCPUInfo(kMSA)) {
|
|
WebPRescalerDspInitMSA();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(WEBP_HAVE_NEON)
|
|
if (WEBP_NEON_OMIT_C_CODE ||
|
|
(VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) {
|
|
WebPRescalerDspInitNEON();
|
|
}
|
|
#endif
|
|
|
|
assert(WebPRescalerExportRowExpand != NULL);
|
|
assert(WebPRescalerExportRowShrink != NULL);
|
|
assert(WebPRescalerImportRowExpand != NULL);
|
|
assert(WebPRescalerImportRowShrink != NULL);
|
|
#endif // WEBP_REDUCE_SIZE
|
|
}
|
|
/* >>> src/dsp/upsampling.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// YUV to RGB upsampling functions.
|
|
//
|
|
// Author: somnath@google.com (Somnath Banerjee)
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Fancy upsampler
|
|
|
|
#ifdef FANCY_UPSAMPLING
|
|
|
|
// Fancy upsampling functions to convert YUV to RGB
|
|
WebPUpsampleLinePairFunc WebPUpsamplers[MODE_LAST];
|
|
|
|
// Given samples laid out in a square as:
|
|
// [a b]
|
|
// [c d]
|
|
// we interpolate u/v as:
|
|
// ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16
|
|
// ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16
|
|
|
|
// We process u and v together stashed into 32bit (16bit each).
|
|
#define LOAD_UV(u, v) ((u) | ((v) << 16))
|
|
|
|
#define UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \
|
|
static void FUNC_NAME( \
|
|
const uint8_t* WEBP_RESTRICT top_y, \
|
|
const uint8_t* WEBP_RESTRICT bottom_y, \
|
|
const uint8_t* WEBP_RESTRICT top_u, const uint8_t* WEBP_RESTRICT top_v, \
|
|
const uint8_t* WEBP_RESTRICT cur_u, const uint8_t* WEBP_RESTRICT cur_v, \
|
|
uint8_t* WEBP_RESTRICT top_dst, uint8_t* WEBP_RESTRICT bottom_dst, \
|
|
int len) { \
|
|
int x; \
|
|
const int last_pixel_pair = (len - 1) >> 1; \
|
|
uint32_t tl_uv = LOAD_UV(top_u[0], top_v[0]); /* top-left sample */ \
|
|
uint32_t l_uv = LOAD_UV(cur_u[0], cur_v[0]); /* left-sample */ \
|
|
assert(top_y != NULL); \
|
|
{ \
|
|
const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \
|
|
FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \
|
|
} \
|
|
if (bottom_y != NULL) { \
|
|
const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \
|
|
FUNC(bottom_y[0], uv0 & 0xff, (uv0 >> 16), bottom_dst); \
|
|
} \
|
|
for (x = 1; x <= last_pixel_pair; ++x) { \
|
|
const uint32_t t_uv = LOAD_UV(top_u[x], top_v[x]); /* top sample */ \
|
|
const uint32_t uv = LOAD_UV(cur_u[x], cur_v[x]); /* sample */ \
|
|
/* precompute invariant values associated with first and second \
|
|
* diagonals*/ \
|
|
const uint32_t avg = tl_uv + t_uv + l_uv + uv + 0x00080008u; \
|
|
const uint32_t diag_12 = (avg + 2 * (t_uv + l_uv)) >> 3; \
|
|
const uint32_t diag_03 = (avg + 2 * (tl_uv + uv)) >> 3; \
|
|
{ \
|
|
const uint32_t uv0 = (diag_12 + tl_uv) >> 1; \
|
|
const uint32_t uv1 = (diag_03 + t_uv) >> 1; \
|
|
FUNC(top_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \
|
|
top_dst + (2 * x - 1) * (XSTEP)); \
|
|
FUNC(top_y[2 * x - 0], uv1 & 0xff, (uv1 >> 16), \
|
|
top_dst + (2 * x - 0) * (XSTEP)); \
|
|
} \
|
|
if (bottom_y != NULL) { \
|
|
const uint32_t uv0 = (diag_03 + l_uv) >> 1; \
|
|
const uint32_t uv1 = (diag_12 + uv) >> 1; \
|
|
FUNC(bottom_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \
|
|
bottom_dst + (2 * x - 1) * (XSTEP)); \
|
|
FUNC(bottom_y[2 * x + 0], uv1 & 0xff, (uv1 >> 16), \
|
|
bottom_dst + (2 * x + 0) * (XSTEP)); \
|
|
} \
|
|
tl_uv = t_uv; \
|
|
l_uv = uv; \
|
|
} \
|
|
if (!(len & 1)) { \
|
|
{ \
|
|
const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \
|
|
FUNC(top_y[len - 1], uv0 & 0xff, (uv0 >> 16), \
|
|
top_dst + (len - 1) * (XSTEP)); \
|
|
} \
|
|
if (bottom_y != NULL) { \
|
|
const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \
|
|
FUNC(bottom_y[len - 1], uv0 & 0xff, (uv0 >> 16), \
|
|
bottom_dst + (len - 1) * (XSTEP)); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
// All variants implemented.
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
UPSAMPLE_FUNC(UpsampleRgbaLinePair_C, VP8YuvToRgba, 4)
|
|
UPSAMPLE_FUNC(UpsampleBgraLinePair_C, VP8YuvToBgra, 4)
|
|
#if !defined(WEBP_REDUCE_CSP)
|
|
UPSAMPLE_FUNC(UpsampleArgbLinePair_C, VP8YuvToArgb, 4)
|
|
UPSAMPLE_FUNC(UpsampleRgbLinePair_C, VP8YuvToRgb, 3)
|
|
UPSAMPLE_FUNC(UpsampleBgrLinePair_C, VP8YuvToBgr, 3)
|
|
UPSAMPLE_FUNC(UpsampleRgba4444LinePair_C, VP8YuvToRgba4444, 2)
|
|
UPSAMPLE_FUNC(UpsampleRgb565LinePair_C, VP8YuvToRgb565, 2)
|
|
#else
|
|
static void EmptyUpsampleFunc(const uint8_t* top_y, const uint8_t* bottom_y,
|
|
const uint8_t* top_u, const uint8_t* top_v,
|
|
const uint8_t* cur_u, const uint8_t* cur_v,
|
|
uint8_t* top_dst, uint8_t* bottom_dst, int len) {
|
|
(void)top_y;
|
|
(void)bottom_y;
|
|
(void)top_u;
|
|
(void)top_v;
|
|
(void)cur_u;
|
|
(void)cur_v;
|
|
(void)top_dst;
|
|
(void)bottom_dst;
|
|
(void)len;
|
|
assert(0); // COLORSPACE SUPPORT NOT COMPILED
|
|
}
|
|
#define UpsampleArgbLinePair_C EmptyUpsampleFunc
|
|
#define UpsampleRgbLinePair_C EmptyUpsampleFunc
|
|
#define UpsampleBgrLinePair_C EmptyUpsampleFunc
|
|
#define UpsampleRgba4444LinePair_C EmptyUpsampleFunc
|
|
#define UpsampleRgb565LinePair_C EmptyUpsampleFunc
|
|
#endif // WEBP_REDUCE_CSP
|
|
|
|
#endif
|
|
|
|
#undef LOAD_UV
|
|
#undef UPSAMPLE_FUNC
|
|
|
|
#endif // FANCY_UPSAMPLING
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#if !defined(FANCY_UPSAMPLING)
|
|
#define DUAL_SAMPLE_FUNC(FUNC_NAME, FUNC) \
|
|
static void FUNC_NAME( \
|
|
const uint8_t* WEBP_RESTRICT top_y, const uint8_t* WEBP_RESTRICT bot_y, \
|
|
const uint8_t* WEBP_RESTRICT top_u, const uint8_t* WEBP_RESTRICT top_v, \
|
|
const uint8_t* WEBP_RESTRICT bot_u, const uint8_t* WEBP_RESTRICT bot_v, \
|
|
uint8_t* WEBP_RESTRICT top_dst, uint8_t* WEBP_RESTRICT bot_dst, \
|
|
int len) { \
|
|
const int half_len = len >> 1; \
|
|
int x; \
|
|
assert(top_dst != NULL); \
|
|
{ \
|
|
for (x = 0; x < half_len; ++x) { \
|
|
FUNC(top_y[2 * x + 0], top_u[x], top_v[x], top_dst + 8 * x + 0); \
|
|
FUNC(top_y[2 * x + 1], top_u[x], top_v[x], top_dst + 8 * x + 4); \
|
|
} \
|
|
if (len & 1) \
|
|
FUNC(top_y[2 * x + 0], top_u[x], top_v[x], top_dst + 8 * x); \
|
|
} \
|
|
if (bot_dst != NULL) { \
|
|
for (x = 0; x < half_len; ++x) { \
|
|
FUNC(bot_y[2 * x + 0], bot_u[x], bot_v[x], bot_dst + 8 * x + 0); \
|
|
FUNC(bot_y[2 * x + 1], bot_u[x], bot_v[x], bot_dst + 8 * x + 4); \
|
|
} \
|
|
if (len & 1) \
|
|
FUNC(bot_y[2 * x + 0], bot_u[x], bot_v[x], bot_dst + 8 * x); \
|
|
} \
|
|
}
|
|
|
|
DUAL_SAMPLE_FUNC(DualLineSamplerBGRA, VP8YuvToBgra)
|
|
DUAL_SAMPLE_FUNC(DualLineSamplerARGB, VP8YuvToArgb)
|
|
#undef DUAL_SAMPLE_FUNC
|
|
|
|
#endif // !FANCY_UPSAMPLING
|
|
|
|
WebPUpsampleLinePairFunc WebPGetLinePairConverter(int alpha_is_last) {
|
|
WebPInitUpsamplers();
|
|
#ifdef FANCY_UPSAMPLING
|
|
return WebPUpsamplers[alpha_is_last ? MODE_BGRA : MODE_ARGB];
|
|
#else
|
|
return (alpha_is_last ? DualLineSamplerBGRA : DualLineSamplerARGB);
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// YUV444 converter
|
|
|
|
#define YUV444_FUNC(FUNC_NAME, FUNC, XSTEP) \
|
|
extern void FUNC_NAME( \
|
|
const uint8_t* WEBP_RESTRICT y, const uint8_t* WEBP_RESTRICT u, \
|
|
const uint8_t* WEBP_RESTRICT v, uint8_t* WEBP_RESTRICT dst, int len); \
|
|
void FUNC_NAME( \
|
|
const uint8_t* WEBP_RESTRICT y, const uint8_t* WEBP_RESTRICT u, \
|
|
const uint8_t* WEBP_RESTRICT v, uint8_t* WEBP_RESTRICT dst, int len) { \
|
|
int i; \
|
|
for (i = 0; i < len; ++i) FUNC(y[i], u[i], v[i], &dst[i * (XSTEP)]); \
|
|
}
|
|
|
|
YUV444_FUNC(WebPYuv444ToRgba_C, VP8YuvToRgba, 4)
|
|
YUV444_FUNC(WebPYuv444ToBgra_C, VP8YuvToBgra, 4)
|
|
#if !defined(WEBP_REDUCE_CSP)
|
|
YUV444_FUNC(WebPYuv444ToRgb_C, VP8YuvToRgb, 3)
|
|
YUV444_FUNC(WebPYuv444ToBgr_C, VP8YuvToBgr, 3)
|
|
YUV444_FUNC(WebPYuv444ToArgb_C, VP8YuvToArgb, 4)
|
|
YUV444_FUNC(WebPYuv444ToRgba4444_C, VP8YuvToRgba4444, 2)
|
|
YUV444_FUNC(WebPYuv444ToRgb565_C, VP8YuvToRgb565, 2)
|
|
#else
|
|
static void EmptyYuv444Func(const uint8_t* y, const uint8_t* u,
|
|
const uint8_t* v, uint8_t* dst, int len) {
|
|
(void)y;
|
|
(void)u;
|
|
(void)v;
|
|
(void)dst;
|
|
(void)len;
|
|
}
|
|
#define WebPYuv444ToRgb_C EmptyYuv444Func
|
|
#define WebPYuv444ToBgr_C EmptyYuv444Func
|
|
#define WebPYuv444ToArgb_C EmptyYuv444Func
|
|
#define WebPYuv444ToRgba4444_C EmptyYuv444Func
|
|
#define WebPYuv444ToRgb565_C EmptyYuv444Func
|
|
#endif // WEBP_REDUCE_CSP
|
|
|
|
#undef YUV444_FUNC
|
|
|
|
WebPYUV444Converter WebPYUV444Converters[MODE_LAST];
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
extern void WebPInitYUV444ConvertersMIPSdspR2(void);
|
|
extern void WebPInitYUV444ConvertersSSE2(void);
|
|
extern void WebPInitYUV444ConvertersSSE41(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(WebPInitYUV444Converters) {
|
|
WebPYUV444Converters[MODE_RGBA] = WebPYuv444ToRgba_C;
|
|
WebPYUV444Converters[MODE_BGRA] = WebPYuv444ToBgra_C;
|
|
WebPYUV444Converters[MODE_RGB] = WebPYuv444ToRgb_C;
|
|
WebPYUV444Converters[MODE_BGR] = WebPYuv444ToBgr_C;
|
|
WebPYUV444Converters[MODE_ARGB] = WebPYuv444ToArgb_C;
|
|
WebPYUV444Converters[MODE_RGBA_4444] = WebPYuv444ToRgba4444_C;
|
|
WebPYUV444Converters[MODE_RGB_565] = WebPYuv444ToRgb565_C;
|
|
WebPYUV444Converters[MODE_rgbA] = WebPYuv444ToRgba_C;
|
|
WebPYUV444Converters[MODE_bgrA] = WebPYuv444ToBgra_C;
|
|
WebPYUV444Converters[MODE_Argb] = WebPYuv444ToArgb_C;
|
|
WebPYUV444Converters[MODE_rgbA_4444] = WebPYuv444ToRgba4444_C;
|
|
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
WebPInitYUV444ConvertersSSE2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_HAVE_SSE41)
|
|
if (VP8GetCPUInfo(kSSE4_1)) {
|
|
WebPInitYUV444ConvertersSSE41();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
WebPInitYUV444ConvertersMIPSdspR2();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main calls
|
|
|
|
extern void WebPInitUpsamplersSSE2(void);
|
|
extern void WebPInitUpsamplersSSE41(void);
|
|
extern void WebPInitUpsamplersNEON(void);
|
|
extern void WebPInitUpsamplersMIPSdspR2(void);
|
|
extern void WebPInitUpsamplersMSA(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(WebPInitUpsamplers) {
|
|
#ifdef FANCY_UPSAMPLING
|
|
#if !WEBP_NEON_OMIT_C_CODE
|
|
WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_C;
|
|
WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair_C;
|
|
WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair_C;
|
|
WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair_C;
|
|
WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_C;
|
|
WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_C;
|
|
WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair_C;
|
|
WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair_C;
|
|
WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair_C;
|
|
WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair_C;
|
|
WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair_C;
|
|
#endif
|
|
|
|
// If defined, use CPUInfo() to overwrite some pointers with faster versions.
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
WebPInitUpsamplersSSE2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_HAVE_SSE41)
|
|
if (VP8GetCPUInfo(kSSE4_1)) {
|
|
WebPInitUpsamplersSSE41();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
WebPInitUpsamplersMIPSdspR2();
|
|
}
|
|
#endif
|
|
#if defined(WEBP_USE_MSA)
|
|
if (VP8GetCPUInfo(kMSA)) {
|
|
WebPInitUpsamplersMSA();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(WEBP_HAVE_NEON)
|
|
if (WEBP_NEON_OMIT_C_CODE ||
|
|
(VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) {
|
|
WebPInitUpsamplersNEON();
|
|
}
|
|
#endif
|
|
|
|
assert(WebPUpsamplers[MODE_RGBA] != NULL);
|
|
assert(WebPUpsamplers[MODE_BGRA] != NULL);
|
|
assert(WebPUpsamplers[MODE_rgbA] != NULL);
|
|
assert(WebPUpsamplers[MODE_bgrA] != NULL);
|
|
#if !defined(WEBP_REDUCE_CSP) || !WEBP_NEON_OMIT_C_CODE
|
|
assert(WebPUpsamplers[MODE_RGB] != NULL);
|
|
assert(WebPUpsamplers[MODE_BGR] != NULL);
|
|
assert(WebPUpsamplers[MODE_ARGB] != NULL);
|
|
assert(WebPUpsamplers[MODE_RGBA_4444] != NULL);
|
|
assert(WebPUpsamplers[MODE_RGB_565] != NULL);
|
|
assert(WebPUpsamplers[MODE_Argb] != NULL);
|
|
assert(WebPUpsamplers[MODE_rgbA_4444] != NULL);
|
|
#endif
|
|
|
|
#endif // FANCY_UPSAMPLING
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/dsp/yuv.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// YUV->RGB conversion functions
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
// Uncomment to disable gamma-compression during RGB->U/V averaging
|
|
#define USE_GAMMA_COMPRESSION
|
|
|
|
// If defined, use table to compute x / alpha.
|
|
#define USE_INVERSE_ALPHA_TABLE
|
|
|
|
#ifdef USE_GAMMA_COMPRESSION
|
|
#include <math.h>
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Plain-C version
|
|
|
|
#define ROW_FUNC(FUNC_NAME, FUNC, XSTEP) \
|
|
static void FUNC_NAME( \
|
|
const uint8_t* WEBP_RESTRICT y, const uint8_t* WEBP_RESTRICT u, \
|
|
const uint8_t* WEBP_RESTRICT v, uint8_t* WEBP_RESTRICT dst, int len) { \
|
|
const uint8_t* const end = dst + (len & ~1) * (XSTEP); \
|
|
while (dst != end) { \
|
|
FUNC(y[0], u[0], v[0], dst); \
|
|
FUNC(y[1], u[0], v[0], dst + (XSTEP)); \
|
|
y += 2; \
|
|
++u; \
|
|
++v; \
|
|
dst += 2 * (XSTEP); \
|
|
} \
|
|
if (len & 1) { \
|
|
FUNC(y[0], u[0], v[0], dst); \
|
|
} \
|
|
}
|
|
|
|
// All variants implemented.
|
|
ROW_FUNC(YuvToRgbRow, VP8YuvToRgb, 3)
|
|
ROW_FUNC(YuvToBgrRow, VP8YuvToBgr, 3)
|
|
ROW_FUNC(YuvToRgbaRow, VP8YuvToRgba, 4)
|
|
ROW_FUNC(YuvToBgraRow, VP8YuvToBgra, 4)
|
|
ROW_FUNC(YuvToArgbRow, VP8YuvToArgb, 4)
|
|
ROW_FUNC(YuvToRgba4444Row, VP8YuvToRgba4444, 2)
|
|
ROW_FUNC(YuvToRgb565Row, VP8YuvToRgb565, 2)
|
|
|
|
#undef ROW_FUNC
|
|
|
|
// Main call for processing a plane with a WebPSamplerRowFunc function:
|
|
void WebPSamplerProcessPlane(const uint8_t* WEBP_RESTRICT y, int y_stride,
|
|
const uint8_t* WEBP_RESTRICT u,
|
|
const uint8_t* WEBP_RESTRICT v, int uv_stride,
|
|
uint8_t* WEBP_RESTRICT dst, int dst_stride,
|
|
int width, int height, WebPSamplerRowFunc func) {
|
|
int j;
|
|
for (j = 0; j < height; ++j) {
|
|
func(y, u, v, dst, width);
|
|
y += y_stride;
|
|
if (j & 1) {
|
|
u += uv_stride;
|
|
v += uv_stride;
|
|
}
|
|
dst += dst_stride;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Main call
|
|
|
|
WebPSamplerRowFunc WebPSamplers[MODE_LAST];
|
|
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
extern void WebPInitSamplersSSE2(void);
|
|
extern void WebPInitSamplersSSE41(void);
|
|
extern void WebPInitSamplersMIPS32(void);
|
|
extern void WebPInitSamplersMIPSdspR2(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(WebPInitSamplers) {
|
|
WebPSamplers[MODE_RGB] = YuvToRgbRow;
|
|
WebPSamplers[MODE_RGBA] = YuvToRgbaRow;
|
|
WebPSamplers[MODE_BGR] = YuvToBgrRow;
|
|
WebPSamplers[MODE_BGRA] = YuvToBgraRow;
|
|
WebPSamplers[MODE_ARGB] = YuvToArgbRow;
|
|
WebPSamplers[MODE_RGBA_4444] = YuvToRgba4444Row;
|
|
WebPSamplers[MODE_RGB_565] = YuvToRgb565Row;
|
|
WebPSamplers[MODE_rgbA] = YuvToRgbaRow;
|
|
WebPSamplers[MODE_bgrA] = YuvToBgraRow;
|
|
WebPSamplers[MODE_Argb] = YuvToArgbRow;
|
|
WebPSamplers[MODE_rgbA_4444] = YuvToRgba4444Row;
|
|
|
|
// If defined, use CPUInfo() to overwrite some pointers with faster versions.
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
WebPInitSamplersSSE2();
|
|
}
|
|
#endif // WEBP_HAVE_SSE2
|
|
#if defined(WEBP_HAVE_SSE41)
|
|
if (VP8GetCPUInfo(kSSE4_1)) {
|
|
WebPInitSamplersSSE41();
|
|
}
|
|
#endif // WEBP_HAVE_SSE41
|
|
#if defined(WEBP_USE_MIPS32)
|
|
if (VP8GetCPUInfo(kMIPS32)) {
|
|
WebPInitSamplersMIPS32();
|
|
}
|
|
#endif // WEBP_USE_MIPS32
|
|
#if defined(WEBP_USE_MIPS_DSP_R2)
|
|
if (VP8GetCPUInfo(kMIPSdspR2)) {
|
|
WebPInitSamplersMIPSdspR2();
|
|
}
|
|
#endif // WEBP_USE_MIPS_DSP_R2
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ARGB -> YUV converters
|
|
|
|
static void ConvertARGBToY_C(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT y, int width) {
|
|
int i;
|
|
for (i = 0; i < width; ++i) {
|
|
const uint32_t p = argb[i];
|
|
y[i] =
|
|
VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, YUV_HALF);
|
|
}
|
|
}
|
|
|
|
void WebPConvertARGBToUV_C(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v,
|
|
int src_width, int do_store) {
|
|
// No rounding. Last pixel is dealt with separately.
|
|
const int uv_width = src_width >> 1;
|
|
int i;
|
|
for (i = 0; i < uv_width; ++i) {
|
|
const uint32_t v0 = argb[2 * i + 0];
|
|
const uint32_t v1 = argb[2 * i + 1];
|
|
// VP8RGBToU/V expects four accumulated pixels. Hence we need to
|
|
// scale r/g/b value by a factor 2. We just shift v0/v1 one bit less.
|
|
const int r = ((v0 >> 15) & 0x1fe) + ((v1 >> 15) & 0x1fe);
|
|
const int g = ((v0 >> 7) & 0x1fe) + ((v1 >> 7) & 0x1fe);
|
|
const int b = ((v0 << 1) & 0x1fe) + ((v1 << 1) & 0x1fe);
|
|
const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2);
|
|
const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2);
|
|
if (do_store) {
|
|
u[i] = tmp_u;
|
|
v[i] = tmp_v;
|
|
} else {
|
|
// Approximated average-of-four. But it's an acceptable diff.
|
|
u[i] = (u[i] + tmp_u + 1) >> 1;
|
|
v[i] = (v[i] + tmp_v + 1) >> 1;
|
|
}
|
|
}
|
|
if (src_width & 1) { // last pixel
|
|
const uint32_t v0 = argb[2 * i + 0];
|
|
const int r = (v0 >> 14) & 0x3fc;
|
|
const int g = (v0 >> 6) & 0x3fc;
|
|
const int b = (v0 << 2) & 0x3fc;
|
|
const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2);
|
|
const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2);
|
|
if (do_store) {
|
|
u[i] = tmp_u;
|
|
v[i] = tmp_v;
|
|
} else {
|
|
u[i] = (u[i] + tmp_u + 1) >> 1;
|
|
v[i] = (v[i] + tmp_v + 1) >> 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static void ConvertRGBToY_C(const uint8_t* WEBP_RESTRICT rgb,
|
|
uint8_t* WEBP_RESTRICT y, int width, int step) {
|
|
int i;
|
|
for (i = 0; i < width; ++i, rgb += step) {
|
|
y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF);
|
|
}
|
|
}
|
|
|
|
static void ConvertBGRToY_C(const uint8_t* WEBP_RESTRICT bgr,
|
|
uint8_t* WEBP_RESTRICT y, int width, int step) {
|
|
int i;
|
|
for (i = 0; i < width; ++i, bgr += step) {
|
|
y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF);
|
|
}
|
|
}
|
|
|
|
void WebPConvertRGBA32ToUV_C(const uint16_t* WEBP_RESTRICT rgb,
|
|
uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v,
|
|
int width) {
|
|
int i;
|
|
for (i = 0; i < width; i += 1, rgb += 4) {
|
|
const int r = rgb[0], g = rgb[1], b = rgb[2];
|
|
u[i] = VP8RGBToU(r, g, b, YUV_HALF << 2);
|
|
v[i] = VP8RGBToV(r, g, b, YUV_HALF << 2);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Code for gamma correction
|
|
|
|
#if defined(USE_GAMMA_COMPRESSION)
|
|
|
|
// Gamma correction compensates loss of resolution during chroma subsampling.
|
|
#define GAMMA_FIX 12 // fixed-point precision for linear values
|
|
#define GAMMA_TAB_FIX 7 // fixed-point fractional bits precision
|
|
#define GAMMA_TAB_SIZE (1 << (GAMMA_FIX - GAMMA_TAB_FIX))
|
|
static const double kGamma = 0.80;
|
|
static const int kGammaScale = ((1 << GAMMA_FIX) - 1);
|
|
static const int kGammaTabScale = (1 << GAMMA_TAB_FIX);
|
|
static const int kGammaTabRounder = (1 << GAMMA_TAB_FIX >> 1);
|
|
|
|
static int kLinearToGammaTab[GAMMA_TAB_SIZE + 1];
|
|
static uint16_t kGammaToLinearTab[256];
|
|
static volatile int kGammaTablesOk = 0;
|
|
extern VP8CPUInfo VP8GetCPUInfo;
|
|
|
|
WEBP_DSP_INIT_FUNC(WebPInitGammaTables) {
|
|
if (!kGammaTablesOk) {
|
|
int v;
|
|
const double scale = (double)(1 << GAMMA_TAB_FIX) / kGammaScale;
|
|
const double norm = 1. / 255.;
|
|
for (v = 0; v <= 255; ++v) {
|
|
kGammaToLinearTab[v] =
|
|
(uint16_t)(pow(norm * v, kGamma) * kGammaScale + .5);
|
|
}
|
|
for (v = 0; v <= GAMMA_TAB_SIZE; ++v) {
|
|
kLinearToGammaTab[v] = (int)(255. * pow(scale * v, 1. / kGamma) + .5);
|
|
}
|
|
kGammaTablesOk = 1;
|
|
}
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t GammaToLinear(uint8_t v) {
|
|
return kGammaToLinearTab[v];
|
|
}
|
|
|
|
static WEBP_INLINE int Interpolate(int v) {
|
|
const int tab_pos = v >> (GAMMA_TAB_FIX + 2); // integer part
|
|
const int x = v & ((kGammaTabScale << 2) - 1); // fractional part
|
|
const int v0 = kLinearToGammaTab[tab_pos];
|
|
const int v1 = kLinearToGammaTab[tab_pos + 1];
|
|
const int y = v1 * x + v0 * ((kGammaTabScale << 2) - x); // interpolate
|
|
assert(tab_pos + 1 < GAMMA_TAB_SIZE + 1);
|
|
return y;
|
|
}
|
|
|
|
// Convert a linear value 'v' to YUV_FIX+2 fixed-point precision
|
|
// U/V value, suitable for RGBToU/V calls.
|
|
static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) {
|
|
const int y = Interpolate(base_value << shift); // final uplifted value
|
|
return (y + kGammaTabRounder) >> GAMMA_TAB_FIX; // descale
|
|
}
|
|
|
|
#else
|
|
|
|
void WebPInitGammaTables(void) {}
|
|
static WEBP_INLINE uint32_t GammaToLinear(uint8_t v) { return v; }
|
|
static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) {
|
|
return (int)(base_value << shift);
|
|
}
|
|
|
|
#endif // USE_GAMMA_COMPRESSION
|
|
|
|
#define SUM4(ptr, step) \
|
|
LinearToGamma(GammaToLinear((ptr)[0]) + GammaToLinear((ptr)[(step)]) + \
|
|
GammaToLinear((ptr)[rgb_stride]) + \
|
|
GammaToLinear((ptr)[rgb_stride + (step)]), \
|
|
0)
|
|
|
|
#define SUM2(ptr) \
|
|
LinearToGamma(GammaToLinear((ptr)[0]) + GammaToLinear((ptr)[rgb_stride]), 1)
|
|
|
|
//------------------------------------------------------------------------------
|
|
// "Fast" regular RGB->YUV
|
|
|
|
#define SUM4(ptr, step) \
|
|
LinearToGamma(GammaToLinear((ptr)[0]) + GammaToLinear((ptr)[(step)]) + \
|
|
GammaToLinear((ptr)[rgb_stride]) + \
|
|
GammaToLinear((ptr)[rgb_stride + (step)]), \
|
|
0)
|
|
|
|
#define SUM2(ptr) \
|
|
LinearToGamma(GammaToLinear((ptr)[0]) + GammaToLinear((ptr)[rgb_stride]), 1)
|
|
|
|
#define SUM2ALPHA(ptr) ((ptr)[0] + (ptr)[rgb_stride])
|
|
#define SUM4ALPHA(ptr) (SUM2ALPHA(ptr) + SUM2ALPHA((ptr) + 4))
|
|
|
|
#if defined(USE_INVERSE_ALPHA_TABLE)
|
|
|
|
static const int kAlphaFix = 19;
|
|
// Following table is (1 << kAlphaFix) / a. The (v * kInvAlpha[a]) >> kAlphaFix
|
|
// formula is then equal to v / a in most (99.6%) cases. Note that this table
|
|
// and constant are adjusted very tightly to fit 32b arithmetic.
|
|
// In particular, they use the fact that the operands for 'v / a' are actually
|
|
// derived as v = (a0.p0 + a1.p1 + a2.p2 + a3.p3) and a = a0 + a1 + a2 + a3
|
|
// with ai in [0..255] and pi in [0..1<<GAMMA_FIX). The constraint to avoid
|
|
// overflow is: GAMMA_FIX + kAlphaFix <= 31.
|
|
static const uint32_t kInvAlpha[4 * 0xff + 1] = {
|
|
0, /* alpha = 0 */
|
|
524288, 262144, 174762, 131072, 104857, 87381, 74898, 65536, 58254, 52428,
|
|
47662, 43690, 40329, 37449, 34952, 32768, 30840, 29127, 27594, 26214,
|
|
24966, 23831, 22795, 21845, 20971, 20164, 19418, 18724, 18078, 17476,
|
|
16912, 16384, 15887, 15420, 14979, 14563, 14169, 13797, 13443, 13107,
|
|
12787, 12483, 12192, 11915, 11650, 11397, 11155, 10922, 10699, 10485,
|
|
10280, 10082, 9892, 9709, 9532, 9362, 9198, 9039, 8886, 8738,
|
|
8594, 8456, 8322, 8192, 8065, 7943, 7825, 7710, 7598, 7489,
|
|
7384, 7281, 7182, 7084, 6990, 6898, 6808, 6721, 6636, 6553,
|
|
6472, 6393, 6316, 6241, 6168, 6096, 6026, 5957, 5890, 5825,
|
|
5761, 5698, 5637, 5577, 5518, 5461, 5405, 5349, 5295, 5242,
|
|
5190, 5140, 5090, 5041, 4993, 4946, 4899, 4854, 4809, 4766,
|
|
4723, 4681, 4639, 4599, 4559, 4519, 4481, 4443, 4405, 4369,
|
|
4332, 4297, 4262, 4228, 4194, 4161, 4128, 4096, 4064, 4032,
|
|
4002, 3971, 3942, 3912, 3883, 3855, 3826, 3799, 3771, 3744,
|
|
3718, 3692, 3666, 3640, 3615, 3591, 3566, 3542, 3518, 3495,
|
|
3472, 3449, 3426, 3404, 3382, 3360, 3339, 3318, 3297, 3276,
|
|
3256, 3236, 3216, 3196, 3177, 3158, 3139, 3120, 3102, 3084,
|
|
3066, 3048, 3030, 3013, 2995, 2978, 2962, 2945, 2928, 2912,
|
|
2896, 2880, 2864, 2849, 2833, 2818, 2803, 2788, 2774, 2759,
|
|
2744, 2730, 2716, 2702, 2688, 2674, 2661, 2647, 2634, 2621,
|
|
2608, 2595, 2582, 2570, 2557, 2545, 2532, 2520, 2508, 2496,
|
|
2484, 2473, 2461, 2449, 2438, 2427, 2416, 2404, 2394, 2383,
|
|
2372, 2361, 2351, 2340, 2330, 2319, 2309, 2299, 2289, 2279,
|
|
2269, 2259, 2250, 2240, 2231, 2221, 2212, 2202, 2193, 2184,
|
|
2175, 2166, 2157, 2148, 2139, 2131, 2122, 2114, 2105, 2097,
|
|
2088, 2080, 2072, 2064, 2056, 2048, 2040, 2032, 2024, 2016,
|
|
2008, 2001, 1993, 1985, 1978, 1971, 1963, 1956, 1949, 1941,
|
|
1934, 1927, 1920, 1913, 1906, 1899, 1892, 1885, 1879, 1872,
|
|
1865, 1859, 1852, 1846, 1839, 1833, 1826, 1820, 1814, 1807,
|
|
1801, 1795, 1789, 1783, 1777, 1771, 1765, 1759, 1753, 1747,
|
|
1741, 1736, 1730, 1724, 1718, 1713, 1707, 1702, 1696, 1691,
|
|
1685, 1680, 1675, 1669, 1664, 1659, 1653, 1648, 1643, 1638,
|
|
1633, 1628, 1623, 1618, 1613, 1608, 1603, 1598, 1593, 1588,
|
|
1583, 1579, 1574, 1569, 1565, 1560, 1555, 1551, 1546, 1542,
|
|
1537, 1533, 1528, 1524, 1519, 1515, 1510, 1506, 1502, 1497,
|
|
1493, 1489, 1485, 1481, 1476, 1472, 1468, 1464, 1460, 1456,
|
|
1452, 1448, 1444, 1440, 1436, 1432, 1428, 1424, 1420, 1416,
|
|
1413, 1409, 1405, 1401, 1398, 1394, 1390, 1387, 1383, 1379,
|
|
1376, 1372, 1368, 1365, 1361, 1358, 1354, 1351, 1347, 1344,
|
|
1340, 1337, 1334, 1330, 1327, 1323, 1320, 1317, 1314, 1310,
|
|
1307, 1304, 1300, 1297, 1294, 1291, 1288, 1285, 1281, 1278,
|
|
1275, 1272, 1269, 1266, 1263, 1260, 1257, 1254, 1251, 1248,
|
|
1245, 1242, 1239, 1236, 1233, 1230, 1227, 1224, 1222, 1219,
|
|
1216, 1213, 1210, 1208, 1205, 1202, 1199, 1197, 1194, 1191,
|
|
1188, 1186, 1183, 1180, 1178, 1175, 1172, 1170, 1167, 1165,
|
|
1162, 1159, 1157, 1154, 1152, 1149, 1147, 1144, 1142, 1139,
|
|
1137, 1134, 1132, 1129, 1127, 1125, 1122, 1120, 1117, 1115,
|
|
1113, 1110, 1108, 1106, 1103, 1101, 1099, 1096, 1094, 1092,
|
|
1089, 1087, 1085, 1083, 1081, 1078, 1076, 1074, 1072, 1069,
|
|
1067, 1065, 1063, 1061, 1059, 1057, 1054, 1052, 1050, 1048,
|
|
1046, 1044, 1042, 1040, 1038, 1036, 1034, 1032, 1030, 1028,
|
|
1026, 1024, 1022, 1020, 1018, 1016, 1014, 1012, 1010, 1008,
|
|
1006, 1004, 1002, 1000, 998, 996, 994, 992, 991, 989,
|
|
987, 985, 983, 981, 979, 978, 976, 974, 972, 970,
|
|
969, 967, 965, 963, 961, 960, 958, 956, 954, 953,
|
|
951, 949, 948, 946, 944, 942, 941, 939, 937, 936,
|
|
934, 932, 931, 929, 927, 926, 924, 923, 921, 919,
|
|
918, 916, 914, 913, 911, 910, 908, 907, 905, 903,
|
|
902, 900, 899, 897, 896, 894, 893, 891, 890, 888,
|
|
887, 885, 884, 882, 881, 879, 878, 876, 875, 873,
|
|
872, 870, 869, 868, 866, 865, 863, 862, 860, 859,
|
|
858, 856, 855, 853, 852, 851, 849, 848, 846, 845,
|
|
844, 842, 841, 840, 838, 837, 836, 834, 833, 832,
|
|
830, 829, 828, 826, 825, 824, 823, 821, 820, 819,
|
|
817, 816, 815, 814, 812, 811, 810, 809, 807, 806,
|
|
805, 804, 802, 801, 800, 799, 798, 796, 795, 794,
|
|
793, 791, 790, 789, 788, 787, 786, 784, 783, 782,
|
|
781, 780, 779, 777, 776, 775, 774, 773, 772, 771,
|
|
769, 768, 767, 766, 765, 764, 763, 762, 760, 759,
|
|
758, 757, 756, 755, 754, 753, 752, 751, 750, 748,
|
|
747, 746, 745, 744, 743, 742, 741, 740, 739, 738,
|
|
737, 736, 735, 734, 733, 732, 731, 730, 729, 728,
|
|
727, 726, 725, 724, 723, 722, 721, 720, 719, 718,
|
|
717, 716, 715, 714, 713, 712, 711, 710, 709, 708,
|
|
707, 706, 705, 704, 703, 702, 701, 700, 699, 699,
|
|
698, 697, 696, 695, 694, 693, 692, 691, 690, 689,
|
|
688, 688, 687, 686, 685, 684, 683, 682, 681, 680,
|
|
680, 679, 678, 677, 676, 675, 674, 673, 673, 672,
|
|
671, 670, 669, 668, 667, 667, 666, 665, 664, 663,
|
|
662, 661, 661, 660, 659, 658, 657, 657, 656, 655,
|
|
654, 653, 652, 652, 651, 650, 649, 648, 648, 647,
|
|
646, 645, 644, 644, 643, 642, 641, 640, 640, 639,
|
|
638, 637, 637, 636, 635, 634, 633, 633, 632, 631,
|
|
630, 630, 629, 628, 627, 627, 626, 625, 624, 624,
|
|
623, 622, 621, 621, 620, 619, 618, 618, 617, 616,
|
|
616, 615, 614, 613, 613, 612, 611, 611, 610, 609,
|
|
608, 608, 607, 606, 606, 605, 604, 604, 603, 602,
|
|
601, 601, 600, 599, 599, 598, 597, 597, 596, 595,
|
|
595, 594, 593, 593, 592, 591, 591, 590, 589, 589,
|
|
588, 587, 587, 586, 585, 585, 584, 583, 583, 582,
|
|
581, 581, 580, 579, 579, 578, 578, 577, 576, 576,
|
|
575, 574, 574, 573, 572, 572, 571, 571, 570, 569,
|
|
569, 568, 568, 567, 566, 566, 565, 564, 564, 563,
|
|
563, 562, 561, 561, 560, 560, 559, 558, 558, 557,
|
|
557, 556, 555, 555, 554, 554, 553, 553, 552, 551,
|
|
551, 550, 550, 549, 548, 548, 547, 547, 546, 546,
|
|
545, 544, 544, 543, 543, 542, 542, 541, 541, 540,
|
|
539, 539, 538, 538, 537, 537, 536, 536, 535, 534,
|
|
534, 533, 533, 532, 532, 531, 531, 530, 530, 529,
|
|
529, 528, 527, 527, 526, 526, 525, 525, 524, 524,
|
|
523, 523, 522, 522, 521, 521, 520, 520, 519, 519,
|
|
518, 518, 517, 517, 516, 516, 515, 515, 514, 514};
|
|
|
|
// Note that LinearToGamma() expects the values to be premultiplied by 4,
|
|
// so we incorporate this factor 4 inside the DIVIDE_BY_ALPHA macro directly.
|
|
#define DIVIDE_BY_ALPHA(sum, a) (((sum) * kInvAlpha[(a)]) >> (kAlphaFix - 2))
|
|
|
|
#else
|
|
|
|
#define DIVIDE_BY_ALPHA(sum, a) (4 * (sum) / (a))
|
|
|
|
#endif // USE_INVERSE_ALPHA_TABLE
|
|
|
|
static WEBP_INLINE int LinearToGammaWeighted(const uint8_t* src,
|
|
const uint8_t* a_ptr,
|
|
uint32_t total_a, int step,
|
|
int rgb_stride) {
|
|
const uint32_t sum =
|
|
a_ptr[0] * GammaToLinear(src[0]) +
|
|
a_ptr[step] * GammaToLinear(src[step]) +
|
|
a_ptr[rgb_stride] * GammaToLinear(src[rgb_stride]) +
|
|
a_ptr[rgb_stride + step] * GammaToLinear(src[rgb_stride + step]);
|
|
assert(total_a > 0 && total_a <= 4 * 0xff);
|
|
#if defined(USE_INVERSE_ALPHA_TABLE)
|
|
assert((uint64_t)sum * kInvAlpha[total_a] < ((uint64_t)1 << 32));
|
|
#endif
|
|
return LinearToGamma(DIVIDE_BY_ALPHA(sum, total_a), 0);
|
|
}
|
|
|
|
void WebPAccumulateRGBA(const uint8_t* const r_ptr, const uint8_t* const g_ptr,
|
|
const uint8_t* const b_ptr, const uint8_t* const a_ptr,
|
|
int rgb_stride, uint16_t* dst, int width) {
|
|
int i, j;
|
|
// we loop over 2x2 blocks and produce one R/G/B/A value for each.
|
|
for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2 * 4, dst += 4) {
|
|
const uint32_t a = SUM4ALPHA(a_ptr + j);
|
|
int r, g, b;
|
|
if (a == 4 * 0xff || a == 0) {
|
|
r = SUM4(r_ptr + j, 4);
|
|
g = SUM4(g_ptr + j, 4);
|
|
b = SUM4(b_ptr + j, 4);
|
|
} else {
|
|
r = LinearToGammaWeighted(r_ptr + j, a_ptr + j, a, 4, rgb_stride);
|
|
g = LinearToGammaWeighted(g_ptr + j, a_ptr + j, a, 4, rgb_stride);
|
|
b = LinearToGammaWeighted(b_ptr + j, a_ptr + j, a, 4, rgb_stride);
|
|
}
|
|
dst[0] = r;
|
|
dst[1] = g;
|
|
dst[2] = b;
|
|
dst[3] = a;
|
|
}
|
|
if (width & 1) {
|
|
const uint32_t a = 2u * SUM2ALPHA(a_ptr + j);
|
|
int r, g, b;
|
|
if (a == 4 * 0xff || a == 0) {
|
|
r = SUM2(r_ptr + j);
|
|
g = SUM2(g_ptr + j);
|
|
b = SUM2(b_ptr + j);
|
|
} else {
|
|
r = LinearToGammaWeighted(r_ptr + j, a_ptr + j, a, 0, rgb_stride);
|
|
g = LinearToGammaWeighted(g_ptr + j, a_ptr + j, a, 0, rgb_stride);
|
|
b = LinearToGammaWeighted(b_ptr + j, a_ptr + j, a, 0, rgb_stride);
|
|
}
|
|
dst[0] = r;
|
|
dst[1] = g;
|
|
dst[2] = b;
|
|
dst[3] = a;
|
|
}
|
|
}
|
|
|
|
void WebPAccumulateRGB(const uint8_t* const r_ptr, const uint8_t* const g_ptr,
|
|
const uint8_t* const b_ptr, int step, int rgb_stride,
|
|
uint16_t* dst, int width) {
|
|
int i, j;
|
|
for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2 * step, dst += 4) {
|
|
dst[0] = SUM4(r_ptr + j, step);
|
|
dst[1] = SUM4(g_ptr + j, step);
|
|
dst[2] = SUM4(b_ptr + j, step);
|
|
// MemorySanitizer may raise false positives with data that passes through
|
|
// RGBA32PackedToPlanar_16b_SSE41() due to incorrect modeling of shuffles.
|
|
// See https://crbug.com/webp/573.
|
|
#ifdef WEBP_MSAN
|
|
dst[3] = 0;
|
|
#endif
|
|
}
|
|
if (width & 1) {
|
|
dst[0] = SUM2(r_ptr + j);
|
|
dst[1] = SUM2(g_ptr + j);
|
|
dst[2] = SUM2(b_ptr + j);
|
|
#ifdef WEBP_MSAN
|
|
dst[3] = 0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void ImportYUVAFromRGBA_C(const uint8_t* r_ptr, const uint8_t* g_ptr,
|
|
const uint8_t* b_ptr, const uint8_t* a_ptr,
|
|
int step, // bytes per pixel
|
|
int rgb_stride, // bytes per scanline
|
|
int has_alpha, int width, int height,
|
|
uint16_t* tmp_rgb, int y_stride, int uv_stride,
|
|
int a_stride, uint8_t* dst_y, uint8_t* dst_u,
|
|
uint8_t* dst_v, uint8_t* dst_a) {
|
|
int y;
|
|
const int is_rgb = (r_ptr < b_ptr); // otherwise it's bgr
|
|
const int uv_width = (width + 1) >> 1;
|
|
|
|
has_alpha &= dst_a != NULL;
|
|
if (has_alpha) {
|
|
#if defined(USE_GAMMA_COMPRESSION) && defined(USE_INVERSE_ALPHA_TABLE)
|
|
assert(kAlphaFix + GAMMA_FIX <= 31);
|
|
#endif
|
|
}
|
|
|
|
WebPInitGammaTables();
|
|
|
|
// Downsample Y/U/V planes, two rows at a time
|
|
for (y = 0; y < (height >> 1); ++y) {
|
|
int rows_have_alpha = has_alpha;
|
|
if (is_rgb) {
|
|
WebPConvertRGBToY(r_ptr, dst_y, width, step);
|
|
WebPConvertRGBToY(r_ptr + rgb_stride, dst_y + y_stride, width, step);
|
|
} else {
|
|
WebPConvertBGRToY(b_ptr, dst_y, width, step);
|
|
WebPConvertBGRToY(b_ptr + rgb_stride, dst_y + y_stride, width, step);
|
|
}
|
|
dst_y += 2 * y_stride;
|
|
if (has_alpha) {
|
|
rows_have_alpha &=
|
|
!WebPExtractAlpha(a_ptr, rgb_stride, width, 2, dst_a, a_stride);
|
|
dst_a += 2 * a_stride;
|
|
} else if (dst_a != NULL) {
|
|
int i;
|
|
for (i = 0; i < 2; ++i, dst_a += a_stride) {
|
|
memset(dst_a, 0xff, width);
|
|
}
|
|
}
|
|
|
|
// Collect averaged R/G/B(/A)
|
|
if (!rows_have_alpha) {
|
|
WebPAccumulateRGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, tmp_rgb, width);
|
|
} else {
|
|
WebPAccumulateRGBA(r_ptr, g_ptr, b_ptr, a_ptr, rgb_stride, tmp_rgb,
|
|
width);
|
|
}
|
|
// Convert to U/V
|
|
WebPConvertRGBA32ToUV(tmp_rgb, dst_u, dst_v, uv_width);
|
|
dst_u += uv_stride;
|
|
dst_v += uv_stride;
|
|
r_ptr += 2 * rgb_stride;
|
|
b_ptr += 2 * rgb_stride;
|
|
g_ptr += 2 * rgb_stride;
|
|
if (has_alpha) a_ptr += 2 * rgb_stride;
|
|
}
|
|
}
|
|
|
|
static void ImportYUVAFromRGBALastLine_C(
|
|
const uint8_t* r_ptr, const uint8_t* g_ptr, const uint8_t* b_ptr,
|
|
const uint8_t* a_ptr,
|
|
int step, // bytes per pixel
|
|
int has_alpha, int width, uint16_t* tmp_rgb, uint8_t* dst_y, uint8_t* dst_u,
|
|
uint8_t* dst_v, uint8_t* dst_a) {
|
|
const int is_rgb = (r_ptr < b_ptr); // otherwise it's bgr
|
|
const int uv_width = (width + 1) >> 1;
|
|
int row_has_alpha = has_alpha && dst_a != NULL;
|
|
|
|
if (is_rgb) {
|
|
WebPConvertRGBToY(r_ptr, dst_y, width, step);
|
|
} else {
|
|
WebPConvertBGRToY(b_ptr, dst_y, width, step);
|
|
}
|
|
if (row_has_alpha) {
|
|
row_has_alpha &= !WebPExtractAlpha(a_ptr, 0, width, 1, dst_a, 0);
|
|
} else if (dst_a != NULL) {
|
|
memset(dst_a, 0xff, width);
|
|
}
|
|
|
|
// Collect averaged R/G/B(/A)
|
|
if (!row_has_alpha) {
|
|
// Collect averaged R/G/B
|
|
WebPAccumulateRGB(r_ptr, g_ptr, b_ptr, step, /*rgb_stride=*/0, tmp_rgb,
|
|
width);
|
|
} else {
|
|
WebPAccumulateRGBA(r_ptr, g_ptr, b_ptr, a_ptr, /*rgb_stride=*/0, tmp_rgb,
|
|
width);
|
|
}
|
|
WebPConvertRGBA32ToUV(tmp_rgb, dst_u, dst_v, uv_width);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void (*WebPConvertRGBToY)(const uint8_t* WEBP_RESTRICT rgb,
|
|
uint8_t* WEBP_RESTRICT y, int width, int step);
|
|
void (*WebPConvertBGRToY)(const uint8_t* WEBP_RESTRICT bgr,
|
|
uint8_t* WEBP_RESTRICT y, int width, int step);
|
|
void (*WebPConvertRGBA32ToUV)(const uint16_t* WEBP_RESTRICT rgb,
|
|
uint8_t* WEBP_RESTRICT u,
|
|
uint8_t* WEBP_RESTRICT v, int width);
|
|
|
|
void (*WebPImportYUVAFromRGBA)(const uint8_t* r_ptr, const uint8_t* g_ptr,
|
|
const uint8_t* b_ptr, const uint8_t* a_ptr,
|
|
int step, // bytes per pixel
|
|
int rgb_stride, // bytes per scanline
|
|
int has_alpha, int width, int height,
|
|
uint16_t* tmp_rgb, int y_stride, int uv_stride,
|
|
int a_stride, uint8_t* dst_y, uint8_t* dst_u,
|
|
uint8_t* dst_v, uint8_t* dst_a);
|
|
void (*WebPImportYUVAFromRGBALastLine)(
|
|
const uint8_t* r_ptr, const uint8_t* g_ptr, const uint8_t* b_ptr,
|
|
const uint8_t* a_ptr,
|
|
int step, // bytes per pixel
|
|
int has_alpha, int width, uint16_t* tmp_rgb, uint8_t* dst_y, uint8_t* dst_u,
|
|
uint8_t* dst_v, uint8_t* dst_a);
|
|
|
|
void (*WebPConvertARGBToY)(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT y, int width);
|
|
void (*WebPConvertARGBToUV)(const uint32_t* WEBP_RESTRICT argb,
|
|
uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v,
|
|
int src_width, int do_store);
|
|
|
|
extern void WebPInitConvertARGBToYUVSSE2(void);
|
|
extern void WebPInitConvertARGBToYUVSSE41(void);
|
|
extern void WebPInitConvertARGBToYUVNEON(void);
|
|
|
|
WEBP_DSP_INIT_FUNC(WebPInitConvertARGBToYUV) {
|
|
WebPConvertARGBToY = ConvertARGBToY_C;
|
|
WebPConvertARGBToUV = WebPConvertARGBToUV_C;
|
|
|
|
WebPConvertRGBToY = ConvertRGBToY_C;
|
|
WebPConvertBGRToY = ConvertBGRToY_C;
|
|
|
|
WebPConvertRGBA32ToUV = WebPConvertRGBA32ToUV_C;
|
|
|
|
WebPImportYUVAFromRGBA = ImportYUVAFromRGBA_C;
|
|
WebPImportYUVAFromRGBALastLine = ImportYUVAFromRGBALastLine_C;
|
|
|
|
if (VP8GetCPUInfo != NULL) {
|
|
#if defined(WEBP_HAVE_SSE2)
|
|
if (VP8GetCPUInfo(kSSE2)) {
|
|
WebPInitConvertARGBToYUVSSE2();
|
|
}
|
|
#endif // WEBP_HAVE_SSE2
|
|
#if defined(WEBP_HAVE_SSE41)
|
|
if (VP8GetCPUInfo(kSSE4_1)) {
|
|
WebPInitConvertARGBToYUVSSE41();
|
|
}
|
|
#endif // WEBP_HAVE_SSE41
|
|
}
|
|
|
|
#if defined(WEBP_HAVE_NEON)
|
|
if (WEBP_NEON_OMIT_C_CODE ||
|
|
(VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) {
|
|
WebPInitConvertARGBToYUVNEON();
|
|
}
|
|
#endif // WEBP_HAVE_NEON
|
|
|
|
assert(WebPConvertARGBToY != NULL);
|
|
assert(WebPConvertARGBToUV != NULL);
|
|
assert(WebPConvertRGBToY != NULL);
|
|
assert(WebPConvertBGRToY != NULL);
|
|
assert(WebPConvertRGBA32ToUV != NULL);
|
|
}
|
|
/* >>> src/utils/bit_reader_utils.c */
|
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Boolean decoder non-inlined methods
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// VP8BitReader
|
|
|
|
void VP8BitReaderSetBuffer(VP8BitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(size) start,
|
|
size_t size) {
|
|
assert(start != NULL);
|
|
br->buf = start;
|
|
br->buf_end = start + size;
|
|
br->buf_max =
|
|
(size >= sizeof(lbit_t)) ? start + size - sizeof(lbit_t) + 1 : start;
|
|
}
|
|
|
|
void VP8InitBitReader(VP8BitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(size) start,
|
|
size_t size) {
|
|
assert(br != NULL);
|
|
assert(start != NULL);
|
|
assert(size < (1u << 31)); // limit ensured by format and upstream checks
|
|
br->range = 255 - 1;
|
|
br->value = 0;
|
|
br->bits = -8; // to load the very first 8bits
|
|
br->eof = 0;
|
|
VP8BitReaderSetBuffer(br, start, size);
|
|
VP8LoadNewBytes(br);
|
|
}
|
|
|
|
void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset) {
|
|
if (br->buf != NULL) {
|
|
br->buf += offset;
|
|
br->buf_end += offset;
|
|
br->buf_max += offset;
|
|
}
|
|
}
|
|
|
|
const uint8_t kVP8Log2Range[128] = {
|
|
7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3,
|
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0};
|
|
|
|
// range = ((range + 1) << kVP8Log2Range[range]) - 1
|
|
const uint8_t kVP8NewRange[128] = {
|
|
127, 127, 191, 127, 159, 191, 223, 127, 143, 159, 175, 191, 207, 223, 239,
|
|
127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239,
|
|
247, 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179,
|
|
183, 187, 191, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239,
|
|
243, 247, 251, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149,
|
|
151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179,
|
|
181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209,
|
|
211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239,
|
|
241, 243, 245, 247, 249, 251, 253, 127};
|
|
|
|
void VP8LoadFinalBytes(VP8BitReader* const br) {
|
|
assert(br != NULL && br->buf != NULL);
|
|
// Only read 8bits at a time
|
|
if (br->buf < br->buf_end) {
|
|
br->bits += 8;
|
|
br->value = (bit_t)(*br->buf++) | (br->value << 8);
|
|
WEBP_SELF_ASSIGN(br->buf_end);
|
|
} else if (!br->eof) {
|
|
br->value <<= 8;
|
|
br->bits += 8;
|
|
br->eof = 1;
|
|
} else {
|
|
br->bits = 0; // This is to avoid undefined behaviour with shifts.
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Higher-level calls
|
|
|
|
uint32_t VP8GetValue(VP8BitReader* const br, int bits, const char label[]) {
|
|
uint32_t v = 0;
|
|
while (bits-- > 0) {
|
|
v |= VP8GetBit(br, 0x80, label) << bits;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
int32_t VP8GetSignedValue(VP8BitReader* const br, int bits,
|
|
const char label[]) {
|
|
const int value = VP8GetValue(br, bits, label);
|
|
return VP8Get(br, label) ? -value : value;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// VP8LBitReader
|
|
|
|
#define VP8L_LOG8_WBITS 4 // Number of bytes needed to store VP8L_WBITS bits.
|
|
|
|
#if defined(__arm__) || defined(_M_ARM) || WEBP_AARCH64 || \
|
|
defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || \
|
|
defined(_M_X64) || defined(__wasm__)
|
|
#define VP8L_USE_FAST_LOAD
|
|
#endif
|
|
|
|
static const uint32_t kBitMask[VP8L_MAX_NUM_BIT_READ + 1] = {
|
|
0, 0x000001, 0x000003, 0x000007, 0x00000f, 0x00001f, 0x00003f,
|
|
0x00007f, 0x0000ff, 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, 0x001fff,
|
|
0x003fff, 0x007fff, 0x00ffff, 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff,
|
|
0x1fffff, 0x3fffff, 0x7fffff, 0xffffff};
|
|
|
|
void VP8LInitBitReader(VP8LBitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(length) start,
|
|
size_t length) {
|
|
size_t i;
|
|
vp8l_val_t value = 0;
|
|
assert(br != NULL);
|
|
assert(start != NULL);
|
|
assert(length < 0xfffffff8u); // can't happen with a RIFF chunk.
|
|
|
|
br->buf = start;
|
|
br->len = length;
|
|
br->bit_pos = 0;
|
|
br->eos = 0;
|
|
|
|
if (length > sizeof(br->val)) {
|
|
length = sizeof(br->val);
|
|
}
|
|
for (i = 0; i < length; ++i) {
|
|
value |= (vp8l_val_t)start[i] << (8 * i);
|
|
}
|
|
br->val = value;
|
|
br->pos = length;
|
|
}
|
|
|
|
void VP8LBitReaderSetBuffer(VP8LBitReader* const br,
|
|
const uint8_t* const WEBP_COUNTED_BY(len) buf,
|
|
size_t len) {
|
|
assert(br != NULL);
|
|
assert(buf != NULL);
|
|
assert(len < 0xfffffff8u); // can't happen with a RIFF chunk.
|
|
br->buf = buf;
|
|
br->len = len;
|
|
// 'pos' > 'len' should be considered a param error.
|
|
br->eos = (br->pos > br->len) || VP8LIsEndOfStream(br);
|
|
}
|
|
|
|
static void VP8LSetEndOfStream(VP8LBitReader* const br) {
|
|
br->eos = 1;
|
|
br->bit_pos = 0; // To avoid undefined behaviour with shifts.
|
|
}
|
|
|
|
// If not at EOS, reload up to VP8L_LBITS byte-by-byte
|
|
static void ShiftBytes(VP8LBitReader* const br) {
|
|
while (br->bit_pos >= 8 && br->pos < br->len) {
|
|
br->val >>= 8;
|
|
br->val |= ((vp8l_val_t)br->buf[br->pos]) << (VP8L_LBITS - 8);
|
|
++br->pos;
|
|
br->bit_pos -= 8;
|
|
}
|
|
if (VP8LIsEndOfStream(br)) {
|
|
VP8LSetEndOfStream(br);
|
|
}
|
|
}
|
|
|
|
void VP8LDoFillBitWindow(VP8LBitReader* const br) {
|
|
assert(br->bit_pos >= VP8L_WBITS);
|
|
#if defined(VP8L_USE_FAST_LOAD)
|
|
if (br->pos + sizeof(br->val) < br->len) {
|
|
br->val >>= VP8L_WBITS;
|
|
br->bit_pos -= VP8L_WBITS;
|
|
br->val |= (vp8l_val_t)HToLE32(WebPMemToUint32(br->buf + br->pos))
|
|
<< (VP8L_LBITS - VP8L_WBITS);
|
|
br->pos += VP8L_LOG8_WBITS;
|
|
return;
|
|
}
|
|
#endif
|
|
ShiftBytes(br); // Slow path.
|
|
}
|
|
|
|
uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits) {
|
|
assert(n_bits >= 0);
|
|
// Flag an error if end_of_stream or n_bits is more than allowed limit.
|
|
if (!br->eos && n_bits <= VP8L_MAX_NUM_BIT_READ) {
|
|
const uint32_t val = VP8LPrefetchBits(br) & kBitMask[n_bits];
|
|
const int new_bits = br->bit_pos + n_bits;
|
|
br->bit_pos = new_bits;
|
|
ShiftBytes(br);
|
|
return val;
|
|
} else {
|
|
VP8LSetEndOfStream(br);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Bit-tracing tool
|
|
|
|
#if (BITTRACE > 0)
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h> // for atexit()
|
|
#include <string.h>
|
|
|
|
#define MAX_NUM_LABELS 32
|
|
static struct {
|
|
const char* label;
|
|
int size;
|
|
int count;
|
|
} kLabels[MAX_NUM_LABELS];
|
|
|
|
static int last_label = 0;
|
|
static int last_pos = 0;
|
|
static const uint8_t* buf_start = NULL;
|
|
static int init_done = 0;
|
|
|
|
static void PrintBitTraces(void) {
|
|
int i;
|
|
int scale = 1;
|
|
int total = 0;
|
|
const char* units = "bits";
|
|
#if (BITTRACE == 2)
|
|
scale = 8;
|
|
units = "bytes";
|
|
#endif
|
|
for (i = 0; i < last_label; ++i) total += kLabels[i].size;
|
|
if (total < 1) total = 1; // avoid rounding errors
|
|
printf("=== Bit traces ===\n");
|
|
for (i = 0; i < last_label; ++i) {
|
|
const int skip = 16 - (int)strlen(kLabels[i].label);
|
|
const int value = (kLabels[i].size + scale - 1) / scale;
|
|
assert(skip > 0);
|
|
printf("%s \%*s: %6d %s \t[%5.2f%%] [count: %7d]\n", kLabels[i].label,
|
|
skip, "", value, units, 100.f * kLabels[i].size / total,
|
|
kLabels[i].count);
|
|
}
|
|
total = (total + scale - 1) / scale;
|
|
printf("Total: %d %s\n", total, units);
|
|
}
|
|
|
|
void BitTrace(const struct VP8BitReader* const br, const char label[]) {
|
|
int i, pos;
|
|
if (!init_done) {
|
|
WEBP_UNSAFE_MEMSET(kLabels, 0, sizeof(kLabels));
|
|
atexit(PrintBitTraces);
|
|
buf_start = br->buf;
|
|
init_done = 1;
|
|
}
|
|
pos = (int)(br->buf - buf_start) * 8 - br->bits;
|
|
// if there's a too large jump, we've changed partition -> reset counter
|
|
if (abs(pos - last_pos) > 32) {
|
|
buf_start = br->buf;
|
|
pos = 0;
|
|
last_pos = 0;
|
|
}
|
|
if (br->range >= 0x7f) pos += kVP8Log2Range[br->range - 0x7f];
|
|
for (i = 0; i < last_label; ++i) {
|
|
if (!strcmp(label, kLabels[i].label)) break;
|
|
}
|
|
if (i == MAX_NUM_LABELS) abort(); // overflow!
|
|
kLabels[i].label = label;
|
|
kLabels[i].size += pos - last_pos;
|
|
kLabels[i].count += 1;
|
|
if (i == last_label) ++last_label;
|
|
last_pos = pos;
|
|
}
|
|
|
|
#endif // BITTRACE > 0
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/utils/color_cache_utils.c */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Color Cache for WebP Lossless
|
|
//
|
|
// Author: Jyrki Alakuijala (jyrki@google.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
// VP8LColorCache.
|
|
|
|
int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits) {
|
|
const int hash_size = 1 << hash_bits;
|
|
uint32_t* colors = (uint32_t*)WebPSafeCalloc((uint64_t)hash_size,
|
|
sizeof(*color_cache->colors));
|
|
assert(color_cache != NULL);
|
|
assert(hash_bits > 0);
|
|
if (colors == NULL) {
|
|
color_cache->colors = NULL;
|
|
WEBP_SELF_ASSIGN(color_cache->hash_bits);
|
|
return 0;
|
|
}
|
|
color_cache->hash_shift = 32 - hash_bits;
|
|
color_cache->hash_bits = hash_bits;
|
|
color_cache->colors = WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
uint32_t*, colors, (size_t)hash_size * sizeof(*color_cache->colors));
|
|
return 1;
|
|
}
|
|
|
|
void VP8LColorCacheClear(VP8LColorCache* const color_cache) {
|
|
if (color_cache != NULL) {
|
|
WebPSafeFree(color_cache->colors);
|
|
color_cache->colors = NULL;
|
|
WEBP_SELF_ASSIGN(color_cache->hash_bits);
|
|
}
|
|
}
|
|
|
|
void VP8LColorCacheCopy(const VP8LColorCache* const src,
|
|
VP8LColorCache* const dst) {
|
|
assert(src != NULL);
|
|
assert(dst != NULL);
|
|
assert(src->hash_bits == dst->hash_bits);
|
|
WEBP_UNSAFE_MEMCPY(dst->colors, src->colors,
|
|
((size_t)1u << dst->hash_bits) * sizeof(*dst->colors));
|
|
}
|
|
/* >>> src/utils/filters_utils.c */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// filter estimation
|
|
//
|
|
// Author: Urvang (urvang@google.com)
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Quick estimate of a potentially interesting filter mode to try.
|
|
|
|
#define SMAX 16
|
|
#define SDIFF(a, b) (abs((a) - (b)) >> 4) // Scoring diff, in [0..SMAX)
|
|
|
|
static WEBP_INLINE int GradientPredictor(uint8_t a, uint8_t b, uint8_t c) {
|
|
const int g = a + b - c;
|
|
return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit
|
|
}
|
|
|
|
WEBP_FILTER_TYPE WebPEstimateBestFilter(
|
|
const uint8_t* WEBP_COUNTED_BY((size_t)width* height) data, int width,
|
|
int height) {
|
|
int i, j;
|
|
int bins[WEBP_FILTER_LAST][SMAX];
|
|
WEBP_UNSAFE_MEMSET(bins, 0, sizeof(bins));
|
|
|
|
// We only sample every other pixels. That's enough.
|
|
for (j = 2; j < height - 1; j += 2) {
|
|
const uint8_t* const p = data + j * width;
|
|
int mean = p[0];
|
|
for (i = 2; i < width - 1; i += 2) {
|
|
const int diff0 = SDIFF(p[i], mean);
|
|
const int diff1 = SDIFF(p[i], p[i - 1]);
|
|
const int diff2 = SDIFF(p[i], p[i - width]);
|
|
const int grad_pred =
|
|
GradientPredictor(p[i - 1], p[i - width], p[i - width - 1]);
|
|
const int diff3 = SDIFF(p[i], grad_pred);
|
|
bins[WEBP_FILTER_NONE][diff0] = 1;
|
|
bins[WEBP_FILTER_HORIZONTAL][diff1] = 1;
|
|
bins[WEBP_FILTER_VERTICAL][diff2] = 1;
|
|
bins[WEBP_FILTER_GRADIENT][diff3] = 1;
|
|
mean = (3 * mean + p[i] + 2) >> 2;
|
|
}
|
|
}
|
|
{
|
|
int filter;
|
|
WEBP_FILTER_TYPE best_filter = WEBP_FILTER_NONE;
|
|
int best_score = 0x7fffffff;
|
|
for (filter = WEBP_FILTER_NONE; filter < WEBP_FILTER_LAST; ++filter) {
|
|
int score = 0;
|
|
for (i = 0; i < SMAX; ++i) {
|
|
if (bins[filter][i] > 0) {
|
|
score += i;
|
|
}
|
|
}
|
|
if (score < best_score) {
|
|
best_score = score;
|
|
best_filter = (WEBP_FILTER_TYPE)filter;
|
|
}
|
|
}
|
|
return best_filter;
|
|
}
|
|
}
|
|
|
|
#undef SMAX
|
|
#undef SDIFF
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/utils/huffman_utils.c */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Utilities for building and looking up Huffman trees.
|
|
//
|
|
// Author: Urvang Joshi (urvang@google.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// Huffman data read via DecodeImageStream is represented in two (red and green)
|
|
// bytes.
|
|
#define MAX_HTREE_GROUPS 0x10000
|
|
|
|
HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups) {
|
|
HTreeGroup* const htree_groups =
|
|
(HTreeGroup*)WebPSafeMalloc(num_htree_groups, sizeof(*htree_groups));
|
|
if (htree_groups == NULL) {
|
|
return NULL;
|
|
}
|
|
assert(num_htree_groups <= MAX_HTREE_GROUPS);
|
|
return htree_groups;
|
|
}
|
|
|
|
void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups) {
|
|
if (htree_groups != NULL) {
|
|
WebPSafeFree(htree_groups);
|
|
}
|
|
}
|
|
|
|
// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the
|
|
// bit-wise reversal of the len least significant bits of key.
|
|
static WEBP_INLINE uint32_t GetNextKey(uint32_t key, int len) {
|
|
uint32_t step = 1 << (len - 1);
|
|
while (key & step) {
|
|
step >>= 1;
|
|
}
|
|
return step ? (key & (step - 1)) + step : key;
|
|
}
|
|
|
|
// Stores code in table[0], table[step], table[2*step], ..., table[end-step].
|
|
// Assumes that end is an integer multiple of step.
|
|
static WEBP_INLINE void ReplicateValue(HuffmanCode* WEBP_COUNTED_BY(end - step +
|
|
1) table,
|
|
int step, int end, HuffmanCode code) {
|
|
int current_end = end;
|
|
assert(current_end % step == 0);
|
|
do {
|
|
current_end -= step;
|
|
table[current_end] = code;
|
|
} while (current_end > 0);
|
|
}
|
|
|
|
// Returns the table width of the next 2nd level table. count is the histogram
|
|
// of bit lengths for the remaining symbols, len is the code length of the next
|
|
// processed symbol
|
|
static WEBP_INLINE int NextTableBitSize(
|
|
const int* const WEBP_COUNTED_BY(MAX_ALLOWED_CODE_LENGTH + 1) count,
|
|
int len, int root_bits) {
|
|
int left = 1 << (len - root_bits);
|
|
while (len < MAX_ALLOWED_CODE_LENGTH) {
|
|
left -= count[len];
|
|
if (left <= 0) break;
|
|
++len;
|
|
left <<= 1;
|
|
}
|
|
return len - root_bits;
|
|
}
|
|
|
|
// sorted[code_lengths_size] is a pre-allocated array for sorting symbols
|
|
// by code length.
|
|
static int BuildHuffmanTable(HuffmanCode* const WEBP_BIDI_INDEXABLE root_table,
|
|
int root_bits, const int code_lengths[],
|
|
int code_lengths_size,
|
|
uint16_t WEBP_COUNTED_BY_OR_NULL(code_lengths_size)
|
|
sorted[]) {
|
|
// next available space in table
|
|
HuffmanCode* WEBP_BIDI_INDEXABLE table = root_table;
|
|
int total_size = 1 << root_bits; // total size root table + 2nd level table
|
|
int len; // current code length
|
|
int symbol; // symbol index in original or sorted table
|
|
// number of codes of each length:
|
|
int count[MAX_ALLOWED_CODE_LENGTH + 1] = {0};
|
|
// offsets in sorted table for each length:
|
|
int offset[MAX_ALLOWED_CODE_LENGTH + 1];
|
|
|
|
assert(code_lengths_size != 0);
|
|
assert(code_lengths != NULL);
|
|
assert((root_table != NULL && sorted != NULL) ||
|
|
(root_table == NULL && sorted == NULL));
|
|
assert(root_bits > 0);
|
|
|
|
// Build histogram of code lengths.
|
|
for (symbol = 0; symbol < code_lengths_size; ++symbol) {
|
|
if (code_lengths[symbol] > MAX_ALLOWED_CODE_LENGTH) {
|
|
return 0;
|
|
}
|
|
++count[code_lengths[symbol]];
|
|
}
|
|
|
|
// Error, all code lengths are zeros.
|
|
if (count[0] == code_lengths_size) {
|
|
return 0;
|
|
}
|
|
|
|
// Generate offsets into sorted symbol table by code length.
|
|
offset[1] = 0;
|
|
for (len = 1; len < MAX_ALLOWED_CODE_LENGTH; ++len) {
|
|
if (count[len] > (1 << len)) {
|
|
return 0;
|
|
}
|
|
offset[len + 1] = offset[len] + count[len];
|
|
}
|
|
|
|
// Sort symbols by length, by symbol order within each length.
|
|
for (symbol = 0; symbol < code_lengths_size; ++symbol) {
|
|
const int symbol_code_length = code_lengths[symbol];
|
|
if (code_lengths[symbol] > 0) {
|
|
if (sorted != NULL) {
|
|
assert(offset[symbol_code_length] < code_lengths_size);
|
|
// The following check is not redundant with the assert. It prevents a
|
|
// potential buffer overflow that the optimizer might not be able to
|
|
// rule out on its own.
|
|
if (offset[symbol_code_length] >= code_lengths_size) {
|
|
return 0;
|
|
}
|
|
sorted[offset[symbol_code_length]++] = symbol;
|
|
} else {
|
|
offset[symbol_code_length]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Special case code with only one value.
|
|
if (offset[MAX_ALLOWED_CODE_LENGTH] == 1) {
|
|
if (sorted != NULL) {
|
|
HuffmanCode code;
|
|
code.bits = 0;
|
|
code.value = (uint16_t)sorted[0];
|
|
ReplicateValue(table, 1, total_size, code);
|
|
}
|
|
return total_size;
|
|
}
|
|
|
|
{
|
|
int step; // step size to replicate values in current table
|
|
uint32_t low = 0xffffffffu; // low bits for current root entry
|
|
uint32_t mask = total_size - 1; // mask for low bits
|
|
uint32_t key = 0; // reversed prefix code
|
|
int num_nodes = 1; // number of Huffman tree nodes
|
|
int num_open = 1; // number of open branches in current tree level
|
|
int table_bits = root_bits; // key length of current table
|
|
int table_size = 1 << table_bits; // size of current table
|
|
symbol = 0;
|
|
// Fill in root table.
|
|
for (len = 1, step = 2; len <= root_bits; ++len, step <<= 1) {
|
|
num_open <<= 1;
|
|
num_nodes += num_open;
|
|
num_open -= count[len];
|
|
if (num_open < 0) {
|
|
return 0;
|
|
}
|
|
if (root_table == NULL) continue;
|
|
for (; count[len] > 0; --count[len]) {
|
|
HuffmanCode code;
|
|
code.bits = (uint8_t)len;
|
|
code.value = (uint16_t)sorted[symbol++];
|
|
ReplicateValue(&table[key], step, table_size, code);
|
|
key = GetNextKey(key, len);
|
|
}
|
|
}
|
|
|
|
// Fill in 2nd level tables and add pointers to root table.
|
|
for (len = root_bits + 1, step = 2; len <= MAX_ALLOWED_CODE_LENGTH;
|
|
++len, step <<= 1) {
|
|
num_open <<= 1;
|
|
num_nodes += num_open;
|
|
num_open -= count[len];
|
|
if (num_open < 0) {
|
|
return 0;
|
|
}
|
|
for (; count[len] > 0; --count[len]) {
|
|
HuffmanCode code;
|
|
if ((key & mask) != low) {
|
|
if (root_table != NULL) table += table_size;
|
|
table_bits = NextTableBitSize(count, len, root_bits);
|
|
table_size = 1 << table_bits;
|
|
total_size += table_size;
|
|
low = key & mask;
|
|
if (root_table != NULL) {
|
|
root_table[low].bits = (uint8_t)(table_bits + root_bits);
|
|
root_table[low].value = (uint16_t)((table - root_table) - low);
|
|
}
|
|
}
|
|
if (root_table != NULL) {
|
|
code.bits = (uint8_t)(len - root_bits);
|
|
code.value = (uint16_t)sorted[symbol++];
|
|
ReplicateValue(&table[key >> root_bits], step, table_size, code);
|
|
}
|
|
key = GetNextKey(key, len);
|
|
}
|
|
}
|
|
|
|
// Check if tree is full.
|
|
if (num_nodes != 2 * offset[MAX_ALLOWED_CODE_LENGTH] - 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return total_size;
|
|
}
|
|
|
|
// Maximum code_lengths_size is 2328 (reached for 11-bit color_cache_bits).
|
|
// More commonly, the value is around ~280.
|
|
#define MAX_CODE_LENGTHS_SIZE \
|
|
((1 << MAX_CACHE_BITS) + NUM_LITERAL_CODES + NUM_LENGTH_CODES)
|
|
// Cut-off value for switching between heap and stack allocation.
|
|
#define SORTED_SIZE_CUTOFF 512
|
|
int VP8LBuildHuffmanTable(HuffmanTables* const root_table, int root_bits,
|
|
const int WEBP_COUNTED_BY(code_lengths_size)
|
|
code_lengths[],
|
|
int code_lengths_size) {
|
|
const int total_size =
|
|
BuildHuffmanTable(NULL, root_bits, code_lengths, code_lengths_size, NULL);
|
|
assert(code_lengths_size <= MAX_CODE_LENGTHS_SIZE);
|
|
if (total_size == 0 || root_table == NULL) return total_size;
|
|
|
|
if (root_table->curr_segment->curr_table + total_size >=
|
|
root_table->curr_segment->start + root_table->curr_segment->size) {
|
|
// If 'root_table' does not have enough memory, allocate a new segment.
|
|
// The available part of root_table->curr_segment is left unused because we
|
|
// need a contiguous buffer.
|
|
const int segment_size = root_table->curr_segment->size;
|
|
struct HuffmanTablesSegment* next =
|
|
(HuffmanTablesSegment*)WebPSafeMalloc(1, sizeof(*next));
|
|
if (next == NULL) return 0;
|
|
// Fill the new segment.
|
|
// We need at least 'total_size' but if that value is small, it is better to
|
|
// allocate a big chunk to prevent more allocations later. 'segment_size' is
|
|
// therefore chosen (any other arbitrary value could be chosen).
|
|
{
|
|
const int next_size =
|
|
total_size > segment_size ? total_size : segment_size;
|
|
HuffmanCode* WEBP_BIDI_INDEXABLE const next_start =
|
|
(HuffmanCode*)WebPSafeMalloc(next_size, sizeof(*next_start));
|
|
if (next_start == NULL) {
|
|
WebPSafeFree(next);
|
|
return 0;
|
|
}
|
|
next->size = next_size;
|
|
next->start = next_start;
|
|
}
|
|
next->curr_table = next->start;
|
|
next->next = NULL;
|
|
// Point to the new segment.
|
|
root_table->curr_segment->next = next;
|
|
root_table->curr_segment = next;
|
|
}
|
|
if (code_lengths_size <= SORTED_SIZE_CUTOFF) {
|
|
// use local stack-allocated array.
|
|
uint16_t sorted[SORTED_SIZE_CUTOFF];
|
|
BuildHuffmanTable(
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
HuffmanCode*, root_table->curr_segment->curr_table,
|
|
total_size * sizeof(*root_table->curr_segment->curr_table)),
|
|
root_bits, code_lengths, code_lengths_size, sorted);
|
|
} else { // rare case. Use heap allocation.
|
|
uint16_t* const sorted =
|
|
(uint16_t*)WebPSafeMalloc(code_lengths_size, sizeof(*sorted));
|
|
if (sorted == NULL) return 0;
|
|
BuildHuffmanTable(
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
HuffmanCode*, root_table->curr_segment->curr_table,
|
|
total_size * sizeof(*root_table->curr_segment->curr_table)),
|
|
root_bits, code_lengths, code_lengths_size,
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
uint16_t*, sorted, (size_t)code_lengths_size * sizeof(*sorted)));
|
|
WebPSafeFree(sorted);
|
|
}
|
|
return total_size;
|
|
}
|
|
|
|
int VP8LHuffmanTablesAllocate(int size, HuffmanTables* huffman_tables) {
|
|
// Have 'segment' point to the first segment for now, 'root'.
|
|
HuffmanTablesSegment* const root = &huffman_tables->root;
|
|
huffman_tables->curr_segment = root;
|
|
root->next = NULL;
|
|
// Allocate root.
|
|
{
|
|
HuffmanCode* WEBP_BIDI_INDEXABLE const start =
|
|
(HuffmanCode*)WebPSafeMalloc(size, sizeof(*root->start));
|
|
if (start == NULL) {
|
|
root->start = NULL;
|
|
root->size = 0;
|
|
return 0;
|
|
}
|
|
root->size = size;
|
|
root->start = start;
|
|
}
|
|
root->curr_table = root->start;
|
|
return 1;
|
|
}
|
|
|
|
void VP8LHuffmanTablesDeallocate(HuffmanTables* const huffman_tables) {
|
|
HuffmanTablesSegment *current, *next;
|
|
if (huffman_tables == NULL) return;
|
|
// Free the root node.
|
|
current = &huffman_tables->root;
|
|
next = current->next;
|
|
WebPSafeFree(current->start);
|
|
current->start = NULL;
|
|
current->size = 0;
|
|
current->next = NULL;
|
|
current = next;
|
|
// Free the following nodes.
|
|
while (current != NULL) {
|
|
next = current->next;
|
|
WebPSafeFree(current->start);
|
|
WebPSafeFree(current);
|
|
current = next;
|
|
}
|
|
}
|
|
/* >>> src/utils/palette.c */
|
|
// Copyright 2023 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Utilities for palette analysis.
|
|
//
|
|
// Author: Vincent Rabaud (vrabaud@google.com)
|
|
|
|
/* >>> src/utils/palette.h */
|
|
// Copyright 2023 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Utilities for palette analysis.
|
|
//
|
|
// Author: Vincent Rabaud (vrabaud@google.com)
|
|
|
|
#ifndef WEBP_UTILS_PALETTE_H_
|
|
#define WEBP_UTILS_PALETTE_H_
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
struct WebPPicture;
|
|
|
|
// The different ways a palette can be sorted.
|
|
typedef enum PaletteSorting {
|
|
kSortedDefault = 0,
|
|
// Sorts by minimizing L1 deltas between consecutive colors, giving more
|
|
// weight to RGB colors.
|
|
kMinimizeDelta = 1,
|
|
// Implements the modified Zeng method from "A Survey on Palette Reordering
|
|
// Methods for Improving the Compression of Color-Indexed Images" by Armando
|
|
// J. Pinho and Antonio J. R. Neves.
|
|
kModifiedZeng = 2,
|
|
kUnusedPalette = 3,
|
|
kPaletteSortingNum = 4
|
|
} PaletteSorting;
|
|
|
|
// Returns the index of 'color' in the sorted palette 'sorted' of size
|
|
// 'num_colors'.
|
|
int SearchColorNoIdx(const uint32_t WEBP_COUNTED_BY(num_colors) sorted[],
|
|
uint32_t color, int num_colors);
|
|
|
|
// Sort palette in increasing order and prepare an inverse mapping array.
|
|
void PrepareMapToPalette(const uint32_t WEBP_COUNTED_BY(num_colors) palette[],
|
|
uint32_t num_colors,
|
|
uint32_t WEBP_COUNTED_BY(num_colors) sorted[],
|
|
uint32_t WEBP_COUNTED_BY(num_colors) idx_map[]);
|
|
|
|
// Returns count of unique colors in 'pic', assuming pic->use_argb is true.
|
|
// If the unique color count is more than MAX_PALETTE_SIZE, returns
|
|
// MAX_PALETTE_SIZE+1.
|
|
// If 'palette' is not NULL and the number of unique colors is less than or
|
|
// equal to MAX_PALETTE_SIZE, also outputs the actual unique colors into
|
|
// 'palette' in a sorted order. Note: 'palette' is assumed to be an array
|
|
// already allocated with at least MAX_PALETTE_SIZE elements.
|
|
int GetColorPalette(const struct WebPPicture* const pic,
|
|
uint32_t* const WEBP_COUNTED_BY_OR_NULL(MAX_PALETTE_SIZE)
|
|
palette);
|
|
|
|
// Sorts the palette according to the criterion defined by 'method'.
|
|
// 'palette_sorted' is the input palette sorted lexicographically, as done in
|
|
// PrepareMapToPalette. Returns 0 on memory allocation error.
|
|
// For kSortedDefault and kMinimizeDelta methods, 0 (if present) is set as the
|
|
// last element to optimize later storage.
|
|
int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic,
|
|
const uint32_t* const WEBP_COUNTED_BY(num_colors)
|
|
palette_sorted,
|
|
uint32_t num_colors,
|
|
uint32_t* const WEBP_COUNTED_BY(num_colors) palette);
|
|
|
|
#endif // WEBP_UTILS_PALETTE_H_
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* >>> src/webp/encode.h */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// WebP encoder: main interface
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
#ifndef WEBP_WEBP_ENCODE_H_
|
|
#define WEBP_WEBP_ENCODE_H_
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define WEBP_ENCODER_ABI_VERSION 0x0210 // MAJOR(8b) + MINOR(8b)
|
|
|
|
// Note: forward declaring enumerations is not allowed in (strict) C and C++,
|
|
// the types are left here for reference.
|
|
// typedef enum WebPImageHint WebPImageHint;
|
|
// typedef enum WebPEncCSP WebPEncCSP;
|
|
// typedef enum WebPPreset WebPPreset;
|
|
// typedef enum WebPEncodingError WebPEncodingError;
|
|
typedef struct WebPConfig WebPConfig;
|
|
typedef struct WebPPicture WebPPicture; // main structure for I/O
|
|
typedef struct WebPAuxStats WebPAuxStats;
|
|
typedef struct WebPMemoryWriter WebPMemoryWriter;
|
|
|
|
// Return the encoder's version number, packed in hexadecimal using 8bits for
|
|
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
|
WEBP_EXTERN int WebPGetEncoderVersion(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// One-stop-shop call! No questions asked:
|
|
|
|
// Returns the size of the compressed data (pointed to by *output), or 0 if
|
|
// an error occurred. The compressed data must be released by the caller
|
|
// using the call 'WebPFree(*output)'.
|
|
// These functions compress using the lossy format, and the quality_factor
|
|
// can go from 0 (smaller output, lower quality) to 100 (best quality,
|
|
// larger output).
|
|
WEBP_EXTERN size_t WebPEncodeRGB(const uint8_t* rgb, int width, int height,
|
|
int stride, float quality_factor,
|
|
uint8_t** output);
|
|
WEBP_EXTERN size_t WebPEncodeBGR(const uint8_t* bgr, int width, int height,
|
|
int stride, float quality_factor,
|
|
uint8_t** output);
|
|
WEBP_EXTERN size_t WebPEncodeRGBA(const uint8_t* rgba, int width, int height,
|
|
int stride, float quality_factor,
|
|
uint8_t** output);
|
|
WEBP_EXTERN size_t WebPEncodeBGRA(const uint8_t* bgra, int width, int height,
|
|
int stride, float quality_factor,
|
|
uint8_t** output);
|
|
|
|
// These functions are the equivalent of the above, but compressing in a
|
|
// lossless manner. Files are usually larger than lossy format, but will
|
|
// not suffer any compression loss.
|
|
// Note these functions, like the lossy versions, use the library's default
|
|
// settings. For lossless this means 'exact' is disabled. RGB values in fully
|
|
// transparent areas (that is, areas with alpha values equal to 0) will be
|
|
// modified to improve compression. To avoid this, use WebPEncode() and set
|
|
// WebPConfig::exact to 1.
|
|
WEBP_EXTERN size_t WebPEncodeLosslessRGB(const uint8_t* rgb, int width,
|
|
int height, int stride,
|
|
uint8_t** output);
|
|
WEBP_EXTERN size_t WebPEncodeLosslessBGR(const uint8_t* bgr, int width,
|
|
int height, int stride,
|
|
uint8_t** output);
|
|
WEBP_EXTERN size_t WebPEncodeLosslessRGBA(const uint8_t* rgba, int width,
|
|
int height, int stride,
|
|
uint8_t** output);
|
|
WEBP_EXTERN size_t WebPEncodeLosslessBGRA(const uint8_t* bgra, int width,
|
|
int height, int stride,
|
|
uint8_t** output);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Coding parameters
|
|
|
|
// Image characteristics hint for the underlying encoder.
|
|
typedef enum WebPImageHint {
|
|
WEBP_HINT_DEFAULT = 0, // default preset.
|
|
WEBP_HINT_PICTURE, // digital picture, like portrait, inner shot
|
|
WEBP_HINT_PHOTO, // outdoor photograph, with natural lighting
|
|
WEBP_HINT_GRAPH, // Discrete tone image (graph, map-tile etc).
|
|
WEBP_HINT_LAST
|
|
} WebPImageHint;
|
|
|
|
// Compression parameters.
|
|
struct WebPConfig {
|
|
int lossless; // Lossless encoding (0=lossy(default), 1=lossless).
|
|
float quality; // between 0 and 100. For lossy, 0 gives the smallest
|
|
// size and 100 the largest. For lossless, this
|
|
// parameter is the amount of effort put into the
|
|
// compression: 0 is the fastest but gives larger
|
|
// files compared to the slowest, but best, 100.
|
|
int method; // quality/speed trade-off (0=fast, 6=slower-better)
|
|
|
|
WebPImageHint image_hint; // Hint for image type (lossless only for now).
|
|
|
|
int target_size; // if non-zero, set the desired target size in bytes.
|
|
// Takes precedence over the 'compression' parameter.
|
|
float target_PSNR; // if non-zero, specifies the minimal distortion to
|
|
// try to achieve. Takes precedence over target_size.
|
|
int segments; // maximum number of segments to use, in [1..4]
|
|
int sns_strength; // Spatial Noise Shaping. 0=off, 100=maximum.
|
|
int filter_strength; // range: [0 = off .. 100 = strongest]
|
|
int filter_sharpness; // range: [0 = off .. 7 = least sharp]
|
|
int filter_type; // filtering type: 0 = simple, 1 = strong (only used
|
|
// if filter_strength > 0 or autofilter > 0)
|
|
int autofilter; // Auto adjust filter's strength [0 = off, 1 = on]
|
|
int alpha_compression; // Algorithm for encoding the alpha plane (0 = none,
|
|
// 1 = compressed with WebP lossless). Default is 1.
|
|
int alpha_filtering; // Predictive filtering method for alpha plane.
|
|
// 0: none, 1: fast, 2: best. Default if 1.
|
|
int alpha_quality; // Between 0 (smallest size) and 100 (lossless).
|
|
// Default is 100.
|
|
int pass; // number of entropy-analysis passes (in [1..10]).
|
|
|
|
int show_compressed; // if true, export the compressed picture back.
|
|
// In-loop filtering is not applied.
|
|
int preprocessing; // preprocessing filter:
|
|
// 0=none, 1=segment-smooth, 2=pseudo-random dithering
|
|
int partitions; // log2(number of token partitions) in [0..3]. Default
|
|
// is set to 0 for easier progressive decoding.
|
|
int partition_limit; // quality degradation allowed to fit the 512k limit
|
|
// on prediction modes coding (0: no degradation,
|
|
// 100: maximum possible degradation).
|
|
int emulate_jpeg_size; // If true, compression parameters will be remapped
|
|
// to better match the expected output size from
|
|
// JPEG compression. Generally, the output size will
|
|
// be similar but the degradation will be lower.
|
|
int thread_level; // If non-zero, try and use multi-threaded encoding.
|
|
int low_memory; // If set, reduce memory usage (but increase CPU use).
|
|
|
|
int near_lossless; // Near lossless encoding [0 = max loss .. 100 = off
|
|
// (default)].
|
|
int exact; // if non-zero, preserve the exact RGB values under
|
|
// transparent area. Otherwise, discard this invisible
|
|
// RGB information for better compression. The default
|
|
// value is 0.
|
|
|
|
int use_delta_palette; // reserved
|
|
int use_sharp_yuv; // if needed, use sharp (and slow) RGB->YUV conversion
|
|
|
|
int qmin; // minimum permissible quality factor
|
|
int qmax; // maximum permissible quality factor
|
|
};
|
|
|
|
// Enumerate some predefined settings for WebPConfig, depending on the type
|
|
// of source picture. These presets are used when calling WebPConfigPreset().
|
|
typedef enum WebPPreset {
|
|
WEBP_PRESET_DEFAULT = 0, // default preset.
|
|
WEBP_PRESET_PICTURE, // digital picture, like portrait, inner shot
|
|
WEBP_PRESET_PHOTO, // outdoor photograph, with natural lighting
|
|
WEBP_PRESET_DRAWING, // hand or line drawing, with high-contrast details
|
|
WEBP_PRESET_ICON, // small-sized colorful images
|
|
WEBP_PRESET_TEXT // text-like
|
|
} WebPPreset;
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPConfigInitInternal(WebPConfig*, WebPPreset,
|
|
float, int);
|
|
|
|
// Should always be called, to initialize a fresh WebPConfig structure before
|
|
// modification. Returns false in case of version mismatch. WebPConfigInit()
|
|
// must have succeeded before using the 'config' object.
|
|
// Note that the default values are lossless=0 and quality=75.
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPConfigInit(WebPConfig* config) {
|
|
return WebPConfigInitInternal(config, WEBP_PRESET_DEFAULT, 75.f,
|
|
WEBP_ENCODER_ABI_VERSION);
|
|
}
|
|
|
|
// This function will initialize the configuration according to a predefined
|
|
// set of parameters (referred to by 'preset') and a given quality factor.
|
|
// This function can be called as a replacement to WebPConfigInit(). Will
|
|
// return false in case of error.
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPConfigPreset(WebPConfig* config,
|
|
WebPPreset preset,
|
|
float quality) {
|
|
return WebPConfigInitInternal(config, preset, quality,
|
|
WEBP_ENCODER_ABI_VERSION);
|
|
}
|
|
|
|
// Activate the lossless compression mode with the desired efficiency level
|
|
// between 0 (fastest, lowest compression) and 9 (slower, best compression).
|
|
// A good default level is '6', providing a fair tradeoff between compression
|
|
// speed and final compressed size.
|
|
// This function will overwrite several fields from config: 'method', 'quality'
|
|
// and 'lossless'. Returns false in case of parameter error.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPConfigLosslessPreset(WebPConfig* config,
|
|
int level);
|
|
|
|
// Returns true if 'config' is non-NULL and all configuration parameters are
|
|
// within their valid ranges.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPValidateConfig(const WebPConfig* config);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Input / Output
|
|
// Structure for storing auxiliary statistics.
|
|
|
|
struct WebPAuxStats {
|
|
int coded_size; // final size
|
|
|
|
float PSNR[5]; // peak-signal-to-noise ratio for Y/U/V/All/Alpha
|
|
int block_count[3]; // number of intra4/intra16/skipped macroblocks
|
|
int header_bytes[2]; // approximate number of bytes spent for header
|
|
// and mode-partition #0
|
|
int residual_bytes[3][4]; // approximate number of bytes spent for
|
|
// DC/AC/uv coefficients for each (0..3) segments.
|
|
int segment_size[4]; // number of macroblocks in each segments
|
|
int segment_quant[4]; // quantizer values for each segments
|
|
int segment_level[4]; // filtering strength for each segments [0..63]
|
|
|
|
int alpha_data_size; // size of the transparency data
|
|
int layer_data_size; // size of the enhancement layer data
|
|
|
|
// lossless encoder statistics
|
|
uint32_t lossless_features; // bit0:predictor bit1:cross-color transform
|
|
// bit2:subtract-green bit3:color indexing
|
|
int histogram_bits; // number of precision bits of histogram
|
|
int transform_bits; // precision bits for predictor transform
|
|
int cache_bits; // number of bits for color cache lookup
|
|
int palette_size; // number of color in palette, if used
|
|
int lossless_size; // final lossless size
|
|
int lossless_hdr_size; // lossless header (transform, huffman etc) size
|
|
int lossless_data_size; // lossless image data size
|
|
int cross_color_transform_bits; // precision bits for cross-color transform
|
|
|
|
uint32_t pad[1]; // padding for later use
|
|
};
|
|
|
|
// Signature for output function. Should return true if writing was successful.
|
|
// data/data_size is the segment of data to write, and 'picture' is for
|
|
// reference (and so one can make use of picture->custom_ptr).
|
|
typedef int (*WebPWriterFunction)(const uint8_t* data, size_t data_size,
|
|
const WebPPicture* picture);
|
|
|
|
// WebPMemoryWrite: a special WebPWriterFunction that writes to memory using
|
|
// the following WebPMemoryWriter object (to be set as a custom_ptr).
|
|
struct WebPMemoryWriter {
|
|
uint8_t* mem; // final buffer (of size 'max_size', larger than 'size').
|
|
size_t size; // final size
|
|
size_t max_size; // total capacity
|
|
uint32_t pad[1]; // padding for later use
|
|
};
|
|
|
|
// The following must be called first before any use.
|
|
WEBP_EXTERN void WebPMemoryWriterInit(WebPMemoryWriter* writer);
|
|
|
|
// The following must be called to deallocate writer->mem memory. The 'writer'
|
|
// object itself is not deallocated.
|
|
WEBP_EXTERN void WebPMemoryWriterClear(WebPMemoryWriter* writer);
|
|
// The custom writer to be used with WebPMemoryWriter as custom_ptr. Upon
|
|
// completion, writer.mem and writer.size will hold the coded data.
|
|
// writer.mem must be freed by calling WebPMemoryWriterClear.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPMemoryWrite(const uint8_t* data,
|
|
size_t data_size,
|
|
const WebPPicture* picture);
|
|
|
|
// Progress hook, called from time to time to report progress. It can return
|
|
// false to request an abort of the encoding process, or true otherwise if
|
|
// everything is OK.
|
|
typedef int (*WebPProgressHook)(int percent, const WebPPicture* picture);
|
|
|
|
// Color spaces.
|
|
typedef enum WebPEncCSP {
|
|
// chroma sampling
|
|
WEBP_YUV420 = 0, // 4:2:0
|
|
WEBP_YUV420A = 4, // alpha channel variant
|
|
WEBP_CSP_UV_MASK = 3, // bit-mask to get the UV sampling factors
|
|
WEBP_CSP_ALPHA_BIT = 4 // bit that is set if alpha is present
|
|
} WebPEncCSP;
|
|
|
|
// Encoding error conditions.
|
|
typedef enum WebPEncodingError {
|
|
VP8_ENC_OK = 0,
|
|
VP8_ENC_ERROR_OUT_OF_MEMORY, // memory error allocating objects
|
|
VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY, // memory error while flushing bits
|
|
VP8_ENC_ERROR_NULL_PARAMETER, // a pointer parameter is NULL
|
|
VP8_ENC_ERROR_INVALID_CONFIGURATION, // configuration is invalid
|
|
VP8_ENC_ERROR_BAD_DIMENSION, // picture has invalid width/height
|
|
VP8_ENC_ERROR_PARTITION0_OVERFLOW, // partition is bigger than 512k
|
|
VP8_ENC_ERROR_PARTITION_OVERFLOW, // partition is bigger than 16M
|
|
VP8_ENC_ERROR_BAD_WRITE, // error while flushing bytes
|
|
VP8_ENC_ERROR_FILE_TOO_BIG, // file is bigger than 4G
|
|
VP8_ENC_ERROR_USER_ABORT, // abort request by user
|
|
VP8_ENC_ERROR_LAST // list terminator. always last.
|
|
} WebPEncodingError;
|
|
|
|
// maximum width/height allowed (inclusive), in pixels
|
|
#define WEBP_MAX_DIMENSION 16383
|
|
|
|
// Main exchange structure (input samples, output bytes, statistics)
|
|
//
|
|
// Once WebPPictureInit() has been called, it's ok to make all the INPUT fields
|
|
// (use_argb, y/u/v, argb, ...) point to user-owned data, even if
|
|
// WebPPictureAlloc() has been called. Depending on the value use_argb,
|
|
// it's guaranteed that either *argb or *y/*u/*v content will be kept untouched.
|
|
struct WebPPicture {
|
|
// INPUT
|
|
//////////////
|
|
// Main flag for encoder selecting between ARGB or YUV input.
|
|
// It is recommended to use ARGB input (*argb, argb_stride) for lossless
|
|
// compression, and YUV input (*y, *u, *v, etc.) for lossy compression
|
|
// since these are the respective native colorspace for these formats.
|
|
int use_argb;
|
|
|
|
// YUV input (mostly used for input to lossy compression)
|
|
WebPEncCSP colorspace; // colorspace: should be YUV420 for now (=Y'CbCr).
|
|
int width, height; // dimensions (less or equal to WEBP_MAX_DIMENSION)
|
|
uint8_t *y, *u, *v; // pointers to luma/chroma planes.
|
|
int y_stride, uv_stride; // luma/chroma strides.
|
|
uint8_t* a; // pointer to the alpha plane
|
|
int a_stride; // stride of the alpha plane
|
|
uint32_t pad1[2]; // padding for later use
|
|
|
|
// ARGB input (mostly used for input to lossless compression)
|
|
uint32_t* argb; // Pointer to argb (32 bit) plane.
|
|
int argb_stride; // This is stride in pixels units, not bytes.
|
|
uint32_t pad2[3]; // padding for later use
|
|
|
|
// OUTPUT
|
|
///////////////
|
|
// Byte-emission hook, to store compressed bytes as they are ready.
|
|
WebPWriterFunction writer; // can be NULL
|
|
void* custom_ptr; // can be used by the writer.
|
|
|
|
// map for extra information (only for lossy compression mode)
|
|
int extra_info_type; // 1: intra type, 2: segment, 3: quant
|
|
// 4: intra-16 prediction mode,
|
|
// 5: chroma prediction mode,
|
|
// 6: bit cost, 7: distortion
|
|
uint8_t* extra_info; // if not NULL, points to an array of size
|
|
// ((width + 15) / 16) * ((height + 15) / 16) that
|
|
// will be filled with a macroblock map, depending
|
|
// on extra_info_type.
|
|
|
|
// STATS AND REPORTS
|
|
///////////////////////////
|
|
// Pointer to side statistics (updated only if not NULL)
|
|
WebPAuxStats* stats;
|
|
|
|
// Error code for the latest error encountered during encoding
|
|
WebPEncodingError error_code;
|
|
|
|
// If not NULL, report progress during encoding.
|
|
WebPProgressHook progress_hook;
|
|
|
|
void* user_data; // this field is free to be set to any value and
|
|
// used during callbacks (like progress-report e.g.).
|
|
|
|
uint32_t pad3[3]; // padding for later use
|
|
|
|
// Unused for now
|
|
uint8_t *pad4, *pad5;
|
|
uint32_t pad6[8]; // padding for later use
|
|
|
|
// PRIVATE FIELDS
|
|
////////////////////
|
|
void* memory_; // row chunk of memory for yuva planes
|
|
void* memory_argb_; // and for argb too.
|
|
void* pad7[2]; // padding for later use
|
|
};
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureInitInternal(WebPPicture*, int);
|
|
|
|
// Should always be called, to initialize the structure. Returns false in case
|
|
// of version mismatch. WebPPictureInit() must have succeeded before using the
|
|
// 'picture' object.
|
|
// Note that, by default, use_argb is false and colorspace is WEBP_YUV420.
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPPictureInit(WebPPicture* picture) {
|
|
return WebPPictureInitInternal(picture, WEBP_ENCODER_ABI_VERSION);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPPicture utils
|
|
|
|
// Convenience allocation / deallocation based on picture->width/height:
|
|
// Allocate y/u/v buffers as per colorspace/width/height specification.
|
|
// Note! This function will free the previous buffer if needed.
|
|
// Returns false in case of memory error.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureAlloc(WebPPicture* picture);
|
|
|
|
// Release the memory allocated by WebPPictureAlloc() or WebPPictureImport*().
|
|
// Note that this function does _not_ free the memory used by the 'picture'
|
|
// object itself.
|
|
// Besides memory (which is reclaimed) all other fields of 'picture' are
|
|
// preserved.
|
|
WEBP_EXTERN void WebPPictureFree(WebPPicture* picture);
|
|
|
|
// Copy the pixels of *src into *dst, using WebPPictureAlloc. Upon return, *dst
|
|
// will fully own the copied pixels (this is not a view). The 'dst' picture need
|
|
// not be initialized as its content is overwritten.
|
|
// Returns false in case of memory allocation error.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureCopy(const WebPPicture* src,
|
|
WebPPicture* dst);
|
|
|
|
// Compute the single distortion for packed planes of samples.
|
|
// 'src' will be compared to 'ref', and the raw distortion stored into
|
|
// '*distortion'. The refined metric (log(MSE), log(1 - ssim),...' will be
|
|
// stored in '*result'.
|
|
// 'x_step' is the horizontal stride (in bytes) between samples.
|
|
// 'src/ref_stride' is the byte distance between rows.
|
|
// Returns false in case of error (bad parameter, memory allocation error, ...).
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPlaneDistortion(
|
|
const uint8_t* src, size_t src_stride, const uint8_t* ref,
|
|
size_t ref_stride, int width, int height, size_t x_step,
|
|
int type, // 0 = PSNR, 1 = SSIM, 2 = LSIM
|
|
float* distortion, float* result);
|
|
|
|
// Compute PSNR, SSIM or LSIM distortion metric between two pictures. Results
|
|
// are in dB, stored in result[] in the B/G/R/A/All order. The distortion is
|
|
// always performed using ARGB samples. Hence if the input is YUV(A), the
|
|
// picture will be internally converted to ARGB (just for the measurement).
|
|
// Warning: this function is rather CPU-intensive.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureDistortion(
|
|
const WebPPicture* src, const WebPPicture* ref,
|
|
int metric_type, // 0 = PSNR, 1 = SSIM, 2 = LSIM
|
|
float result[5]);
|
|
|
|
// self-crops a picture to the rectangle defined by top/left/width/height.
|
|
// Returns false in case of memory allocation error, or if the rectangle is
|
|
// outside of the source picture.
|
|
// The rectangle for the view is defined by the top-left corner pixel
|
|
// coordinates (left, top) as well as its width and height. This rectangle
|
|
// must be fully be comprised inside the 'src' source picture. If the source
|
|
// picture uses the YUV420 colorspace, the top and left coordinates will be
|
|
// snapped to even values.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureCrop(WebPPicture* picture, int left,
|
|
int top, int width, int height);
|
|
|
|
// Extracts a view from 'src' picture into 'dst'. The rectangle for the view
|
|
// is defined by the top-left corner pixel coordinates (left, top) as well
|
|
// as its width and height. This rectangle must be fully be comprised inside
|
|
// the 'src' source picture. If the source picture uses the YUV420 colorspace,
|
|
// the top and left coordinates will be snapped to even values.
|
|
// Picture 'src' must out-live 'dst' picture. Self-extraction of view is allowed
|
|
// ('src' equal to 'dst') as a mean of fast-cropping (but note that doing so,
|
|
// the original dimension will be lost). Picture 'dst' need not be initialized
|
|
// with WebPPictureInit() if it is different from 'src', since its content will
|
|
// be overwritten.
|
|
// Returns false in case of invalid parameters.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureView(const WebPPicture* src, int left,
|
|
int top, int width, int height,
|
|
WebPPicture* dst);
|
|
|
|
// Returns true if the 'picture' is actually a view and therefore does
|
|
// not own the memory for pixels.
|
|
WEBP_EXTERN int WebPPictureIsView(const WebPPicture* picture);
|
|
|
|
// Rescale a picture to new dimension width x height.
|
|
// If either 'width' or 'height' (but not both) is 0 the corresponding
|
|
// dimension will be calculated preserving the aspect ratio.
|
|
// No gamma correction is applied.
|
|
// Returns false in case of error (invalid parameter or insufficient memory).
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureRescale(WebPPicture* picture,
|
|
int width, int height);
|
|
|
|
// Colorspace conversion function to import RGB samples.
|
|
// Previous buffer will be free'd, if any.
|
|
// *rgb buffer should have a size of at least height * rgb_stride.
|
|
// Returns false in case of memory error.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGB(WebPPicture* picture,
|
|
const uint8_t* rgb,
|
|
int rgb_stride);
|
|
// Same, but for RGBA buffer.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGBA(WebPPicture* picture,
|
|
const uint8_t* rgba,
|
|
int rgba_stride);
|
|
// Same, but for RGBA buffer. Imports the RGB direct from the 32-bit format
|
|
// input buffer ignoring the alpha channel. Avoids needing to copy the data
|
|
// to a temporary 24-bit RGB buffer to import the RGB only.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGBX(WebPPicture* picture,
|
|
const uint8_t* rgbx,
|
|
int rgbx_stride);
|
|
|
|
// Variants of the above, but taking BGR(A|X) input.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGR(WebPPicture* picture,
|
|
const uint8_t* bgr,
|
|
int bgr_stride);
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGRA(WebPPicture* picture,
|
|
const uint8_t* bgra,
|
|
int bgra_stride);
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGRX(WebPPicture* picture,
|
|
const uint8_t* bgrx,
|
|
int bgrx_stride);
|
|
|
|
// Converts picture->argb data to the YUV420A format. The 'colorspace'
|
|
// parameter is deprecated and should be equal to WEBP_YUV420.
|
|
// Upon return, picture->use_argb is set to false. The presence of real
|
|
// non-opaque transparent values is detected, and 'colorspace' will be
|
|
// adjusted accordingly. Note that this method is lossy.
|
|
// Returns false in case of error.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureARGBToYUVA(
|
|
WebPPicture* picture, WebPEncCSP /*colorspace = WEBP_YUV420*/);
|
|
|
|
// Same as WebPPictureARGBToYUVA(), but the conversion is done using
|
|
// pseudo-random dithering with a strength 'dithering' between
|
|
// 0.0 (no dithering) and 1.0 (maximum dithering). This is useful
|
|
// for photographic picture.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureARGBToYUVADithered(
|
|
WebPPicture* picture, WebPEncCSP colorspace, float dithering);
|
|
|
|
// Performs 'sharp' RGBA->YUVA420 downsampling and colorspace conversion
|
|
// Downsampling is handled with extra care in case of color clipping. This
|
|
// method is roughly 2x slower than WebPPictureARGBToYUVA() but produces better
|
|
// and sharper YUV representation.
|
|
// Returns false in case of error.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureSharpARGBToYUVA(WebPPicture* picture);
|
|
// kept for backward compatibility:
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureSmartARGBToYUVA(WebPPicture* picture);
|
|
|
|
// Converts picture->yuv to picture->argb and sets picture->use_argb to true.
|
|
// The input format must be YUV_420 or YUV_420A. The conversion from YUV420 to
|
|
// ARGB incurs a small loss too.
|
|
// Note that the use of this colorspace is discouraged if one has access to the
|
|
// raw ARGB samples, since using YUV420 is comparatively lossy.
|
|
// Returns false in case of error.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPPictureYUVAToARGB(WebPPicture* picture);
|
|
|
|
// Helper function: given a width x height plane of RGBA or YUV(A) samples
|
|
// clean-up or smoothen the YUV or RGB samples under fully transparent area,
|
|
// to help compressibility (no guarantee, though).
|
|
WEBP_EXTERN void WebPCleanupTransparentArea(WebPPicture* picture);
|
|
|
|
// Scan the picture 'picture' for the presence of non fully opaque alpha values.
|
|
// Returns true in such case. Otherwise returns false (indicating that the
|
|
// alpha plane can be ignored altogether e.g.).
|
|
WEBP_EXTERN int WebPPictureHasTransparency(const WebPPicture* picture);
|
|
|
|
// Remove the transparency information (if present) by blending the color with
|
|
// the background color 'background_rgb' (specified as 24bit RGB triplet).
|
|
// After this call, all alpha values are reset to 0xff.
|
|
WEBP_EXTERN void WebPBlendAlpha(WebPPicture* picture, uint32_t background_rgb);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main call
|
|
|
|
// Main encoding call, after config and picture have been initialized.
|
|
// 'picture' must be less than 16384x16384 in dimension (cf WEBP_MAX_DIMENSION),
|
|
// and the 'config' object must be a valid one.
|
|
// Returns false in case of error, true otherwise.
|
|
// In case of error, picture->error_code is updated accordingly.
|
|
// 'picture' can hold the source samples in both YUV(A) or ARGB input, depending
|
|
// on the value of 'picture->use_argb'. It is highly recommended to use
|
|
// the former for lossy encoding, and the latter for lossless encoding
|
|
// (when config.lossless is true). Automatic conversion from one format to
|
|
// another is provided but they both incur some loss.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPEncode(const WebPConfig* config,
|
|
WebPPicture* picture);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_WEBP_ENCODE_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Palette reordering for smaller sum of deltas (and for smaller storage).
|
|
|
|
static int PaletteCompareColorsForQsort(const void* p1, const void* p2) {
|
|
const uint32_t a = WebPMemToUint32((uint8_t*)p1);
|
|
const uint32_t b = WebPMemToUint32((uint8_t*)p2);
|
|
assert(a != b);
|
|
return (a < b) ? -1 : 1;
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) {
|
|
return (v <= 128) ? v : (256 - v);
|
|
}
|
|
|
|
// Computes a value that is related to the entropy created by the
|
|
// palette entry diff.
|
|
//
|
|
// Note that the last & 0xff is a no-operation in the next statement, but
|
|
// removed by most compilers and is here only for regularity of the code.
|
|
static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) {
|
|
const uint32_t diff = VP8LSubPixels(col1, col2);
|
|
const int kMoreWeightForRGBThanForAlpha = 9;
|
|
uint32_t score;
|
|
score = PaletteComponentDistance((diff >> 0) & 0xff);
|
|
score += PaletteComponentDistance((diff >> 8) & 0xff);
|
|
score += PaletteComponentDistance((diff >> 16) & 0xff);
|
|
score *= kMoreWeightForRGBThanForAlpha;
|
|
score += PaletteComponentDistance((diff >> 24) & 0xff);
|
|
return score;
|
|
}
|
|
|
|
static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) {
|
|
const uint32_t tmp = *col1;
|
|
*col1 = *col2;
|
|
*col2 = tmp;
|
|
}
|
|
|
|
int SearchColorNoIdx(const uint32_t WEBP_COUNTED_BY(num_colors) sorted[],
|
|
uint32_t color, int num_colors) {
|
|
int low = 0, hi = num_colors;
|
|
if (sorted[low] == color) return low; // loop invariant: sorted[low] != color
|
|
while (1) {
|
|
const int mid = (low + hi) >> 1;
|
|
if (sorted[mid] == color) {
|
|
return mid;
|
|
} else if (sorted[mid] < color) {
|
|
low = mid;
|
|
} else {
|
|
hi = mid;
|
|
}
|
|
}
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
|
|
void PrepareMapToPalette(const uint32_t WEBP_COUNTED_BY(num_colors) palette[],
|
|
uint32_t num_colors,
|
|
uint32_t WEBP_COUNTED_BY(num_colors) sorted[],
|
|
uint32_t WEBP_COUNTED_BY(num_colors) idx_map[]) {
|
|
uint32_t i;
|
|
memcpy(sorted, palette, num_colors * sizeof(*sorted));
|
|
qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort);
|
|
for (i = 0; i < num_colors; ++i) {
|
|
idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4)
|
|
#define COLOR_HASH_RIGHT_SHIFT 22 // 32 - log2(COLOR_HASH_SIZE).
|
|
|
|
int GetColorPalette(const WebPPicture* const pic,
|
|
uint32_t* const WEBP_COUNTED_BY_OR_NULL(MAX_PALETTE_SIZE)
|
|
palette) {
|
|
int i;
|
|
int x, y;
|
|
int num_colors = 0;
|
|
uint8_t in_use[COLOR_HASH_SIZE] = {0};
|
|
uint32_t colors[COLOR_HASH_SIZE] = {0};
|
|
const uint32_t* argb = pic->argb;
|
|
const int width = pic->width;
|
|
const int height = pic->height;
|
|
uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0]
|
|
assert(pic != NULL);
|
|
assert(pic->use_argb);
|
|
|
|
for (y = 0; y < height; ++y) {
|
|
for (x = 0; x < width; ++x) {
|
|
int key;
|
|
if (argb[x] == last_pix) {
|
|
continue;
|
|
}
|
|
last_pix = argb[x];
|
|
key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT);
|
|
while (1) {
|
|
if (!in_use[key]) {
|
|
colors[key] = last_pix;
|
|
in_use[key] = 1;
|
|
++num_colors;
|
|
if (num_colors > MAX_PALETTE_SIZE) {
|
|
return MAX_PALETTE_SIZE + 1; // Exact count not needed.
|
|
}
|
|
break;
|
|
} else if (colors[key] == last_pix) {
|
|
break; // The color is already there.
|
|
} else {
|
|
// Some other color sits here, so do linear conflict resolution.
|
|
++key;
|
|
key &= (COLOR_HASH_SIZE - 1); // Key mask.
|
|
}
|
|
}
|
|
}
|
|
argb += pic->argb_stride;
|
|
}
|
|
|
|
if (palette != NULL) { // Fill the colors into palette.
|
|
num_colors = 0;
|
|
for (i = 0; i < COLOR_HASH_SIZE; ++i) {
|
|
if (in_use[i]) {
|
|
palette[num_colors] = colors[i];
|
|
++num_colors;
|
|
}
|
|
}
|
|
qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort);
|
|
}
|
|
return num_colors;
|
|
}
|
|
|
|
#undef COLOR_HASH_SIZE
|
|
#undef COLOR_HASH_RIGHT_SHIFT
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// The palette has been sorted by alpha. This function checks if the other
|
|
// components of the palette have a monotonic development with regards to
|
|
// position in the palette. If all have monotonic development, there is
|
|
// no benefit to re-organize them greedily. A monotonic development
|
|
// would be spotted in green-only situations (like lossy alpha) or gray-scale
|
|
// images.
|
|
static int PaletteHasNonMonotonousDeltas(
|
|
const uint32_t* const WEBP_COUNTED_BY(num_colors) palette, int num_colors) {
|
|
uint32_t predict = 0x000000;
|
|
int i;
|
|
uint8_t sign_found = 0x00;
|
|
for (i = 0; i < num_colors; ++i) {
|
|
const uint32_t diff = VP8LSubPixels(palette[i], predict);
|
|
const uint8_t rd = (diff >> 16) & 0xff;
|
|
const uint8_t gd = (diff >> 8) & 0xff;
|
|
const uint8_t bd = (diff >> 0) & 0xff;
|
|
if (rd != 0x00) {
|
|
sign_found |= (rd < 0x80) ? 1 : 2;
|
|
}
|
|
if (gd != 0x00) {
|
|
sign_found |= (gd < 0x80) ? 8 : 16;
|
|
}
|
|
if (bd != 0x00) {
|
|
sign_found |= (bd < 0x80) ? 64 : 128;
|
|
}
|
|
predict = palette[i];
|
|
}
|
|
return (sign_found & (sign_found << 1)) != 0; // two consequent signs.
|
|
}
|
|
|
|
static void PaletteSortMinimizeDeltas(
|
|
const uint32_t* const WEBP_COUNTED_BY(num_colors) palette_sorted,
|
|
int num_colors, uint32_t* const WEBP_COUNTED_BY(num_colors) palette) {
|
|
uint32_t predict = 0x00000000;
|
|
int i, k;
|
|
memcpy(palette, palette_sorted, num_colors * sizeof(*palette));
|
|
if (!PaletteHasNonMonotonousDeltas(palette_sorted, num_colors)) return;
|
|
// Find greedily always the closest color of the predicted color to minimize
|
|
// deltas in the palette. This reduces storage needs since the
|
|
// palette is stored with delta encoding.
|
|
if (num_colors > 17) {
|
|
if (palette[0] == 0) {
|
|
--num_colors;
|
|
SwapColor(&palette[num_colors], &palette[0]);
|
|
}
|
|
}
|
|
for (i = 0; i < num_colors; ++i) {
|
|
int best_ix = i;
|
|
uint32_t best_score = ~0U;
|
|
for (k = i; k < num_colors; ++k) {
|
|
const uint32_t cur_score = PaletteColorDistance(palette[k], predict);
|
|
if (best_score > cur_score) {
|
|
best_score = cur_score;
|
|
best_ix = k;
|
|
}
|
|
}
|
|
SwapColor(&palette[best_ix], &palette[i]);
|
|
predict = palette[i];
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Modified Zeng method from "A Survey on Palette Reordering
|
|
// Methods for Improving the Compression of Color-Indexed Images" by Armando J.
|
|
// Pinho and Antonio J. R. Neves.
|
|
|
|
// Finds the biggest cooccurrence in the matrix.
|
|
static void CoOccurrenceFindMax(
|
|
const uint32_t* const WEBP_COUNTED_BY(num_colors* num_colors) cooccurrence,
|
|
uint32_t num_colors, uint8_t* const c1, uint8_t* const c2) {
|
|
// Find the index that is most frequently located adjacent to other
|
|
// (different) indexes.
|
|
uint32_t best_sum = 0u;
|
|
uint32_t i, j, best_cooccurrence;
|
|
*c1 = 0u;
|
|
for (i = 0; i < num_colors; ++i) {
|
|
uint32_t sum = 0;
|
|
for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j];
|
|
if (sum > best_sum) {
|
|
best_sum = sum;
|
|
*c1 = i;
|
|
}
|
|
}
|
|
// Find the index that is most frequently found adjacent to *c1.
|
|
*c2 = 0u;
|
|
best_cooccurrence = 0u;
|
|
for (i = 0; i < num_colors; ++i) {
|
|
if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) {
|
|
best_cooccurrence = cooccurrence[*c1 * num_colors + i];
|
|
*c2 = i;
|
|
}
|
|
}
|
|
assert(*c1 != *c2);
|
|
}
|
|
|
|
// Builds the cooccurrence matrix
|
|
static int CoOccurrenceBuild(const WebPPicture* const pic,
|
|
const uint32_t* const WEBP_COUNTED_BY(num_colors)
|
|
palette,
|
|
uint32_t num_colors,
|
|
uint32_t* WEBP_COUNTED_BY(num_colors* num_colors)
|
|
cooccurrence) {
|
|
uint32_t *lines, *line_top, *line_current, *line_tmp;
|
|
int x, y;
|
|
const uint32_t* src = pic->argb;
|
|
uint32_t prev_pix = ~src[0];
|
|
uint32_t prev_idx = 0u;
|
|
uint32_t idx_map[MAX_PALETTE_SIZE] = {0};
|
|
uint32_t palette_sorted[MAX_PALETTE_SIZE];
|
|
lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines));
|
|
if (lines == NULL) {
|
|
return 0;
|
|
}
|
|
line_top = &lines[0];
|
|
line_current = &lines[pic->width];
|
|
PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map);
|
|
for (y = 0; y < pic->height; ++y) {
|
|
for (x = 0; x < pic->width; ++x) {
|
|
const uint32_t pix = src[x];
|
|
if (pix != prev_pix) {
|
|
prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)];
|
|
prev_pix = pix;
|
|
}
|
|
line_current[x] = prev_idx;
|
|
// 4-connectivity is what works best as mentioned in "On the relation
|
|
// between Memon's and the modified Zeng's palette reordering methods".
|
|
if (x > 0 && prev_idx != line_current[x - 1]) {
|
|
const uint32_t left_idx = line_current[x - 1];
|
|
++cooccurrence[prev_idx * num_colors + left_idx];
|
|
++cooccurrence[left_idx * num_colors + prev_idx];
|
|
}
|
|
if (y > 0 && prev_idx != line_top[x]) {
|
|
const uint32_t top_idx = line_top[x];
|
|
++cooccurrence[prev_idx * num_colors + top_idx];
|
|
++cooccurrence[top_idx * num_colors + prev_idx];
|
|
}
|
|
}
|
|
line_tmp = line_top;
|
|
line_top = line_current;
|
|
line_current = line_tmp;
|
|
src += pic->argb_stride;
|
|
}
|
|
WebPSafeFree(lines);
|
|
return 1;
|
|
}
|
|
|
|
struct Sum {
|
|
uint8_t index;
|
|
uint32_t sum;
|
|
};
|
|
|
|
static int PaletteSortModifiedZeng(
|
|
const WebPPicture* const pic,
|
|
const uint32_t* const WEBP_COUNTED_BY(num_colors) palette_in,
|
|
uint32_t num_colors, uint32_t* const WEBP_COUNTED_BY(num_colors) palette) {
|
|
uint32_t i, j, ind;
|
|
uint8_t remapping[MAX_PALETTE_SIZE];
|
|
uint32_t* cooccurrence;
|
|
struct Sum sums[MAX_PALETTE_SIZE];
|
|
uint32_t first, last;
|
|
uint32_t num_sums;
|
|
// TODO(vrabaud) check whether one color images should use palette or not.
|
|
if (num_colors <= 1) return 1;
|
|
// Build the co-occurrence matrix.
|
|
cooccurrence =
|
|
(uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence));
|
|
if (cooccurrence == NULL) {
|
|
return 0;
|
|
}
|
|
if (!CoOccurrenceBuild(pic, palette_in, num_colors,
|
|
WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
uint32_t*, cooccurrence,
|
|
num_colors* num_colors * sizeof(*cooccurrence)))) {
|
|
WebPSafeFree(cooccurrence);
|
|
return 0;
|
|
}
|
|
|
|
// Initialize the mapping list with the two best indices.
|
|
CoOccurrenceFindMax(WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
const uint32_t*, cooccurrence,
|
|
num_colors* num_colors * sizeof(*cooccurrence)),
|
|
num_colors, &remapping[0], &remapping[1]);
|
|
|
|
// We need to append and prepend to the list of remapping. To this end, we
|
|
// actually define the next start/end of the list as indices in a vector (with
|
|
// a wrap around when the end is reached).
|
|
first = 0;
|
|
last = 1;
|
|
num_sums = num_colors - 2; // -2 because we know the first two values
|
|
if (num_sums > 0) {
|
|
// Initialize the sums with the first two remappings and find the best one
|
|
struct Sum* best_sum = &sums[0];
|
|
best_sum->index = 0u;
|
|
best_sum->sum = 0u;
|
|
for (i = 0, j = 0; i < num_colors; ++i) {
|
|
if (i == remapping[0] || i == remapping[1]) continue;
|
|
sums[j].index = i;
|
|
sums[j].sum = cooccurrence[i * num_colors + remapping[0]] +
|
|
cooccurrence[i * num_colors + remapping[1]];
|
|
if (sums[j].sum > best_sum->sum) best_sum = &sums[j];
|
|
++j;
|
|
}
|
|
|
|
while (num_sums > 0) {
|
|
const uint8_t best_index = best_sum->index;
|
|
// Compute delta to know if we need to prepend or append the best index.
|
|
int32_t delta = 0;
|
|
const int32_t n = num_colors - num_sums;
|
|
for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) {
|
|
const uint16_t l_j = remapping[(ind + j) % num_colors];
|
|
delta += (n - 1 - 2 * (int32_t)j) *
|
|
(int32_t)cooccurrence[best_index * num_colors + l_j];
|
|
}
|
|
if (delta > 0) {
|
|
first = (first == 0) ? num_colors - 1 : first - 1;
|
|
remapping[first] = best_index;
|
|
} else {
|
|
++last;
|
|
remapping[last] = best_index;
|
|
}
|
|
// Remove best_sum from sums.
|
|
*best_sum = sums[num_sums - 1];
|
|
--num_sums;
|
|
// Update all the sums and find the best one.
|
|
best_sum = &sums[0];
|
|
for (i = 0; i < num_sums; ++i) {
|
|
sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index];
|
|
if (sums[i].sum > best_sum->sum) best_sum = &sums[i];
|
|
}
|
|
}
|
|
}
|
|
assert((last + 1) % num_colors == first);
|
|
WebPSafeFree(cooccurrence);
|
|
|
|
// Re-map the palette.
|
|
for (i = 0; i < num_colors; ++i) {
|
|
palette[i] = palette_in[remapping[(first + i) % num_colors]];
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic,
|
|
const uint32_t* const WEBP_COUNTED_BY(num_colors)
|
|
palette_sorted,
|
|
uint32_t num_colors,
|
|
uint32_t* const WEBP_COUNTED_BY(num_colors) palette) {
|
|
switch (method) {
|
|
case kSortedDefault:
|
|
if (palette_sorted[0] == 0 && num_colors > 17) {
|
|
memcpy(palette, palette_sorted + 1,
|
|
(num_colors - 1) * sizeof(*palette_sorted));
|
|
palette[num_colors - 1] = 0;
|
|
} else {
|
|
memcpy(palette, palette_sorted, num_colors * sizeof(*palette));
|
|
}
|
|
return 1;
|
|
case kMinimizeDelta:
|
|
PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette);
|
|
return 1;
|
|
case kModifiedZeng:
|
|
return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette);
|
|
case kUnusedPalette:
|
|
case kPaletteSortingNum:
|
|
break;
|
|
}
|
|
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
/* >>> src/utils/quant_levels_dec_utils.c */
|
|
#define clip_8b webpdec_priv_clip_8b
|
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Implement gradient smoothing: we replace a current alpha value by its
|
|
// surrounding average if it's close enough (that is: the change will be less
|
|
// than the minimum distance between two quantized level).
|
|
// We use sliding window for computing the 2d moving average.
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
#include <string.h> // for memset
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// #define USE_DITHERING // uncomment to enable ordered dithering (not vital)
|
|
|
|
#define FIX 16 // fix-point precision for averaging
|
|
#define LFIX 2 // extra precision for look-up table
|
|
#define LUT_SIZE ((1 << (8 + LFIX)) - 1) // look-up table size
|
|
#define CORRECTION_LUT_SIZE (1 + 2 * LUT_SIZE)
|
|
|
|
#if defined(USE_DITHERING)
|
|
|
|
#define DFIX 4 // extra precision for ordered dithering
|
|
#define DSIZE 4 // dithering size (must be a power of two)
|
|
// cf. https://en.wikipedia.org/wiki/Ordered_dithering
|
|
static const uint8_t kOrderedDither[DSIZE][DSIZE] = {
|
|
{0, 8, 2, 10}, // coefficients are in DFIX fixed-point precision
|
|
{12, 4, 14, 6},
|
|
{3, 11, 1, 9},
|
|
{15, 7, 13, 5}};
|
|
|
|
#else
|
|
#define DFIX 0
|
|
#endif
|
|
|
|
typedef struct {
|
|
int width, height; // dimension
|
|
int stride; // stride in bytes
|
|
int row; // current input row being processed
|
|
uint8_t* WEBP_INDEXABLE src; // input pointer
|
|
uint8_t* WEBP_INDEXABLE dst; // output pointer
|
|
|
|
int radius; // filter radius (=delay)
|
|
int scale; // normalization factor, in FIX bits precision
|
|
|
|
void* mem; // all memory
|
|
|
|
// various scratch buffers
|
|
uint16_t* WEBP_INDEXABLE start;
|
|
uint16_t* WEBP_INDEXABLE cur;
|
|
uint16_t* WEBP_BIDI_INDEXABLE end;
|
|
uint16_t* WEBP_INDEXABLE top;
|
|
uint16_t* WEBP_COUNTED_BY(width) average;
|
|
|
|
// input levels distribution
|
|
int num_levels; // number of quantized levels
|
|
int min, max; // min and max level values
|
|
int min_level_dist; // smallest distance between two consecutive levels
|
|
|
|
// size = 1 + 2*LUT_SIZE -> ~4k memory
|
|
int16_t* WEBP_COUNTED_BY_OR_NULL(CORRECTION_LUT_SIZE) correction;
|
|
} SmoothParams;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#define CLIP_8b_MASK (int)(~0U << (8 + DFIX))
|
|
static WEBP_INLINE uint8_t clip_8b(int v) {
|
|
return (!(v & CLIP_8b_MASK)) ? (uint8_t)(v >> DFIX) : (v < 0) ? 0u : 255u;
|
|
}
|
|
#undef CLIP_8b_MASK
|
|
|
|
// vertical accumulation
|
|
static void VFilter(SmoothParams* const p) {
|
|
const uint8_t* WEBP_INDEXABLE src = p->src;
|
|
const int w = p->width;
|
|
uint16_t* const WEBP_INDEXABLE cur = p->cur;
|
|
const uint16_t* const WEBP_INDEXABLE top = p->top;
|
|
uint16_t* const WEBP_INDEXABLE out = p->end;
|
|
uint16_t sum = 0; // all arithmetic is modulo 16bit
|
|
int x;
|
|
|
|
for (x = 0; x < w; ++x) {
|
|
uint16_t new_value;
|
|
sum += src[x];
|
|
new_value = top[x] + sum;
|
|
out[x] = new_value - cur[x]; // vertical sum of 'r' pixels.
|
|
cur[x] = new_value;
|
|
}
|
|
// move input pointers one row down
|
|
p->top = p->cur;
|
|
p->cur += w;
|
|
if (p->cur == p->end) p->cur = p->start; // roll-over
|
|
// We replicate edges, as it's somewhat easier as a boundary condition.
|
|
// That's why we don't update the 'src' pointer on top/bottom area:
|
|
if (p->row >= 0 && p->row < p->height - 1) {
|
|
p->src += p->stride;
|
|
}
|
|
}
|
|
|
|
// horizontal accumulation. We use mirror replication of missing pixels, as it's
|
|
// a little easier to implement (surprisingly).
|
|
static void HFilter(SmoothParams* const p) {
|
|
const uint16_t* const WEBP_INDEXABLE in = p->end;
|
|
uint16_t* const WEBP_INDEXABLE out = p->average;
|
|
const uint32_t scale = p->scale;
|
|
const int w = p->width;
|
|
const int r = p->radius;
|
|
|
|
int x;
|
|
for (x = 0; x <= r; ++x) { // left mirroring
|
|
const uint16_t delta = in[x + r - 1] + in[r - x];
|
|
out[x] = (delta * scale) >> FIX;
|
|
}
|
|
for (; x < w - r; ++x) { // bulk middle run
|
|
const uint16_t delta = in[x + r] - in[x - r - 1];
|
|
out[x] = (delta * scale) >> FIX;
|
|
}
|
|
for (; x < w; ++x) { // right mirroring
|
|
const uint16_t delta =
|
|
2 * in[w - 1] - in[2 * w - 2 - r - x] - in[x - r - 1];
|
|
out[x] = (delta * scale) >> FIX;
|
|
}
|
|
}
|
|
|
|
// emit one filtered output row
|
|
static void ApplyFilter(SmoothParams* const p) {
|
|
const uint16_t* const WEBP_INDEXABLE average = p->average;
|
|
const int w = p->width;
|
|
// correction is WEBP_COUNTED_BY, pointing to the start of the LUT.
|
|
// We need the middle pointer for negative indexing.
|
|
const int16_t* const WEBP_BIDI_INDEXABLE correction =
|
|
p->correction + LUT_SIZE;
|
|
#if defined(USE_DITHERING)
|
|
const uint8_t* const dither = kOrderedDither[p->row % DSIZE];
|
|
#endif
|
|
uint8_t* const WEBP_INDEXABLE dst = p->dst;
|
|
int x;
|
|
for (x = 0; x < w; ++x) {
|
|
const int v = dst[x];
|
|
if (v < p->max && v > p->min) {
|
|
const int c = (v << DFIX) + correction[average[x] - (v << LFIX)];
|
|
#if defined(USE_DITHERING)
|
|
dst[x] = clip_8b(c + dither[x % DSIZE]);
|
|
#else
|
|
dst[x] = clip_8b(c);
|
|
#endif
|
|
}
|
|
}
|
|
p->dst += p->stride; // advance output pointer
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Initialize correction table
|
|
|
|
static void InitCorrectionLUT(
|
|
int16_t* const WEBP_COUNTED_BY(CORRECTION_LUT_SIZE) lut_ptr, int min_dist) {
|
|
// The correction curve is:
|
|
// f(x) = x for x <= threshold2
|
|
// f(x) = 0 for x >= threshold1
|
|
// and a linear interpolation for range x=[threshold2, threshold1]
|
|
// (along with f(-x) = -f(x) symmetry).
|
|
// Note that: threshold2 = 3/4 * threshold1
|
|
const int threshold1 = min_dist << LFIX;
|
|
const int threshold2 = (3 * threshold1) >> 2;
|
|
const int max_threshold = threshold2 << DFIX;
|
|
const int delta = threshold1 - threshold2;
|
|
// lut_ptr is WEBP_COUNTED_BY, pointing to the start of the LUT.
|
|
// We need the middle pointer (lut) for negative indexing.
|
|
int16_t* const WEBP_BIDI_INDEXABLE lut = lut_ptr + LUT_SIZE;
|
|
int i;
|
|
for (i = 1; i <= LUT_SIZE; ++i) {
|
|
int c = (i <= threshold2) ? (i << DFIX)
|
|
: (i < threshold1) ? max_threshold * (threshold1 - i) / delta
|
|
: 0;
|
|
c >>= LFIX;
|
|
lut[+i] = +c;
|
|
lut[-i] = -c;
|
|
}
|
|
lut[0] = 0;
|
|
}
|
|
|
|
static void CountLevels(SmoothParams* const p) {
|
|
int i, j, last_level;
|
|
uint8_t used_levels[256] = {0};
|
|
const uint8_t* WEBP_INDEXABLE data = p->src;
|
|
p->min = 255;
|
|
p->max = 0;
|
|
for (j = 0; j < p->height; ++j) {
|
|
for (i = 0; i < p->width; ++i) {
|
|
const int v = data[i];
|
|
if (v < p->min) p->min = v;
|
|
if (v > p->max) p->max = v;
|
|
used_levels[v] = 1;
|
|
}
|
|
data += p->stride;
|
|
}
|
|
// Compute the mininum distance between two non-zero levels.
|
|
p->min_level_dist = p->max - p->min;
|
|
last_level = -1;
|
|
for (i = 0; i < 256; ++i) {
|
|
if (used_levels[i]) {
|
|
++p->num_levels;
|
|
if (last_level >= 0) {
|
|
const int level_dist = i - last_level;
|
|
if (level_dist < p->min_level_dist) {
|
|
p->min_level_dist = level_dist;
|
|
}
|
|
}
|
|
last_level = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize all params.
|
|
static int InitParams(uint8_t* WEBP_SIZED_BY((size_t)stride* height) const data,
|
|
int width, int height, int stride, int radius,
|
|
SmoothParams* const p) {
|
|
const int R = 2 * radius + 1; // total size of the kernel
|
|
|
|
const size_t size_scratch_m = (R + 1) * width * sizeof(*p->start);
|
|
const size_t size_m = width * sizeof(*p->average);
|
|
const size_t size_lut = CORRECTION_LUT_SIZE * sizeof(*p->correction);
|
|
const size_t total_size = size_scratch_m + size_m + size_lut;
|
|
uint8_t* WEBP_BIDI_INDEXABLE mem = (uint8_t*)WebPSafeMalloc(1U, total_size);
|
|
|
|
if (mem == NULL) return 0;
|
|
p->mem = (void*)mem;
|
|
|
|
p->start = (uint16_t*)mem;
|
|
p->cur = p->start;
|
|
p->end = p->start + R * width;
|
|
p->top = p->end - width;
|
|
WEBP_UNSAFE_MEMSET(p->top, 0, width * sizeof(*p->top));
|
|
mem += size_scratch_m;
|
|
|
|
p->width = width;
|
|
p->average = (uint16_t*)mem;
|
|
mem += size_m;
|
|
|
|
p->height = height;
|
|
p->stride = stride;
|
|
p->src = data;
|
|
p->dst = data;
|
|
p->radius = radius;
|
|
p->scale = (1 << (FIX + LFIX)) / (R * R); // normalization constant
|
|
p->row = -radius;
|
|
|
|
// analyze the input distribution so we can best-fit the threshold
|
|
CountLevels(p);
|
|
|
|
// correction table. p->correction is WEBP_COUNTED_BY(CORRECTION_LUT_SIZE).
|
|
// It points to the start of the buffer.
|
|
p->correction = ((int16_t*)mem);
|
|
InitCorrectionLUT(p->correction, p->min_level_dist);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void CleanupParams(SmoothParams* const p) { WebPSafeFree(p->mem); }
|
|
|
|
int WebPDequantizeLevels(uint8_t* WEBP_SIZED_BY((size_t)stride* height)
|
|
const data,
|
|
int width, int height, int stride, int strength) {
|
|
int radius = 4 * strength / 100;
|
|
|
|
if (strength < 0 || strength > 100) return 0;
|
|
if (data == NULL || width <= 0 || height <= 0) return 0; // bad params
|
|
|
|
// limit the filter size to not exceed the image dimensions
|
|
if (2 * radius + 1 > width) radius = (width - 1) >> 1;
|
|
if (2 * radius + 1 > height) radius = (height - 1) >> 1;
|
|
|
|
if (radius > 0) {
|
|
SmoothParams p;
|
|
WEBP_UNSAFE_MEMSET(&p, 0, sizeof(p));
|
|
if (!InitParams(data, width, height, stride, radius, &p)) return 0;
|
|
if (p.num_levels > 2) {
|
|
for (; p.row < p.height; ++p.row) {
|
|
VFilter(&p); // accumulate average of input
|
|
// Need to wait few rows in order to prime the filter,
|
|
// before emitting some output.
|
|
if (p.row >= p.radius) {
|
|
HFilter(&p);
|
|
ApplyFilter(&p);
|
|
}
|
|
}
|
|
}
|
|
CleanupParams(&p);
|
|
}
|
|
return 1;
|
|
}
|
|
#undef clip_8b
|
|
/* >>> src/utils/random_utils.c */
|
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Pseudo-random utilities
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// 31b-range values
|
|
static const uint32_t kRandomTable[VP8_RANDOM_TABLE_SIZE] = {
|
|
0x0de15230, 0x03b31886, 0x775faccb, 0x1c88626a, 0x68385c55, 0x14b3b828,
|
|
0x4a85fef8, 0x49ddb84b, 0x64fcf397, 0x5c550289, 0x4a290000, 0x0d7ec1da,
|
|
0x5940b7ab, 0x5492577d, 0x4e19ca72, 0x38d38c69, 0x0c01ee65, 0x32a1755f,
|
|
0x5437f652, 0x5abb2c32, 0x0faa57b1, 0x73f533e7, 0x685feeda, 0x7563cce2,
|
|
0x6e990e83, 0x4730a7ed, 0x4fc0d9c6, 0x496b153c, 0x4f1403fa, 0x541afb0c,
|
|
0x73990b32, 0x26d7cb1c, 0x6fcc3706, 0x2cbb77d8, 0x75762f2a, 0x6425ccdd,
|
|
0x24b35461, 0x0a7d8715, 0x220414a8, 0x141ebf67, 0x56b41583, 0x73e502e3,
|
|
0x44cab16f, 0x28264d42, 0x73baaefb, 0x0a50ebed, 0x1d6ab6fb, 0x0d3ad40b,
|
|
0x35db3b68, 0x2b081e83, 0x77ce6b95, 0x5181e5f0, 0x78853bbc, 0x009f9494,
|
|
0x27e5ed3c};
|
|
|
|
void VP8InitRandom(VP8Random* const rg, float dithering) {
|
|
WEBP_UNSAFE_MEMCPY(rg->tab, kRandomTable, sizeof(rg->tab));
|
|
rg->index1 = 0;
|
|
rg->index2 = 31;
|
|
rg->amp = (dithering < 0.0) ? 0
|
|
: (dithering > 1.0)
|
|
? (1 << VP8_RANDOM_DITHER_FIX)
|
|
: (uint32_t)((1 << VP8_RANDOM_DITHER_FIX) * dithering);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/utils/rescaler_utils.c */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Rescaling functions
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int WebPRescalerInit(WebPRescaler* const rescaler, int src_width,
|
|
int src_height, uint8_t* const dst, int dst_width,
|
|
int dst_height, int dst_stride, int num_channels,
|
|
rescaler_t* const WEBP_COUNTED_BY(2ULL * dst_width *
|
|
num_channels) work) {
|
|
const int x_add = src_width, x_sub = dst_width;
|
|
const int y_add = src_height, y_sub = dst_height;
|
|
const uint64_t total_size = 2ull * dst_width * num_channels * sizeof(*work);
|
|
if (!CheckSizeOverflow(total_size)) return 0;
|
|
|
|
rescaler->x_expand = (src_width < dst_width);
|
|
rescaler->y_expand = (src_height < dst_height);
|
|
rescaler->src_width = src_width;
|
|
rescaler->src_height = src_height;
|
|
rescaler->dst_width = dst_width;
|
|
rescaler->dst_height = dst_height;
|
|
rescaler->src_y = 0;
|
|
rescaler->dst_y = 0;
|
|
rescaler->dst = dst;
|
|
rescaler->dst_stride = dst_stride;
|
|
rescaler->num_channels = num_channels;
|
|
rescaler->irow = work;
|
|
rescaler->frow = work + num_channels * dst_width;
|
|
memset(work, 0, (size_t)total_size);
|
|
|
|
// for 'x_expand', we use bilinear interpolation
|
|
rescaler->x_add = rescaler->x_expand ? (x_sub - 1) : x_add;
|
|
rescaler->x_sub = rescaler->x_expand ? (x_add - 1) : x_sub;
|
|
if (!rescaler->x_expand) { // fx_scale is not used otherwise
|
|
rescaler->fx_scale = WEBP_RESCALER_FRAC(1, rescaler->x_sub);
|
|
}
|
|
// vertical scaling parameters
|
|
rescaler->y_add = rescaler->y_expand ? y_add - 1 : y_add;
|
|
rescaler->y_sub = rescaler->y_expand ? y_sub - 1 : y_sub;
|
|
rescaler->y_accum = rescaler->y_expand ? rescaler->y_sub : rescaler->y_add;
|
|
if (!rescaler->y_expand) {
|
|
// This is WEBP_RESCALER_FRAC(dst_height, x_add * y_add) without the cast.
|
|
// Its value is <= WEBP_RESCALER_ONE, because dst_height <= rescaler->y_add
|
|
// and rescaler->x_add >= 1;
|
|
const uint64_t num = (uint64_t)dst_height * WEBP_RESCALER_ONE;
|
|
const uint64_t den = (uint64_t)rescaler->x_add * rescaler->y_add;
|
|
const uint64_t ratio = num / den;
|
|
if (ratio != (uint32_t)ratio) {
|
|
// When ratio == WEBP_RESCALER_ONE, we can't represent the ratio with the
|
|
// current fixed-point precision. This happens when src_height ==
|
|
// rescaler->y_add (which == src_height), and rescaler->x_add == 1.
|
|
// => We special-case fxy_scale = 0, in WebPRescalerExportRow().
|
|
rescaler->fxy_scale = 0;
|
|
} else {
|
|
rescaler->fxy_scale = (uint32_t)ratio;
|
|
}
|
|
rescaler->fy_scale = WEBP_RESCALER_FRAC(1, rescaler->y_sub);
|
|
} else {
|
|
rescaler->fy_scale = WEBP_RESCALER_FRAC(1, rescaler->x_add);
|
|
// rescaler->fxy_scale is unused here.
|
|
}
|
|
|
|
WebPRescalerDspInit();
|
|
return 1;
|
|
}
|
|
|
|
int WebPRescalerGetScaledDimensions(int src_width, int src_height,
|
|
int* const scaled_width,
|
|
int* const scaled_height) {
|
|
assert(scaled_width != NULL);
|
|
assert(scaled_height != NULL);
|
|
if (src_width < 0 || src_height < 0 || *scaled_width < 0 ||
|
|
*scaled_height < 0) {
|
|
return 0;
|
|
}
|
|
{
|
|
int width = *scaled_width;
|
|
int height = *scaled_height;
|
|
const int max_size = INT_MAX / 2;
|
|
|
|
// if width is unspecified, scale original proportionally to height ratio.
|
|
if (width == 0 && src_height > 0) {
|
|
width =
|
|
(int)(((uint64_t)src_width * height + src_height - 1) / src_height);
|
|
}
|
|
// if height is unspecified, scale original proportionally to width ratio.
|
|
if (height == 0 && src_width > 0) {
|
|
height =
|
|
(int)(((uint64_t)src_height * width + src_width - 1) / src_width);
|
|
}
|
|
// Check if the overall dimensions still make sense.
|
|
if (width <= 0 || height <= 0 || width > max_size || height > max_size) {
|
|
return 0;
|
|
}
|
|
|
|
*scaled_width = width;
|
|
*scaled_height = height;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// all-in-one calls
|
|
|
|
int WebPRescaleNeededLines(const WebPRescaler* const rescaler,
|
|
int max_num_lines) {
|
|
const int num_lines =
|
|
(rescaler->y_accum + rescaler->y_sub - 1) / rescaler->y_sub;
|
|
return (num_lines > max_num_lines) ? max_num_lines : num_lines;
|
|
}
|
|
|
|
int WebPRescalerImport(WebPRescaler* const rescaler, int num_lines,
|
|
const uint8_t* src, int src_stride) {
|
|
int total_imported = 0;
|
|
while (total_imported < num_lines &&
|
|
!WebPRescalerHasPendingOutput(rescaler)) {
|
|
if (rescaler->y_expand) {
|
|
rescaler_t* const tmp = rescaler->irow;
|
|
rescaler->irow = rescaler->frow;
|
|
rescaler->frow = WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(
|
|
rescaler_t*, tmp,
|
|
rescaler->num_channels * rescaler->dst_width * sizeof(*tmp));
|
|
WEBP_SELF_ASSIGN(rescaler->dst_width);
|
|
WEBP_SELF_ASSIGN(rescaler->num_channels);
|
|
}
|
|
WebPRescalerImportRow(rescaler, src);
|
|
if (!rescaler->y_expand) { // Accumulate the contribution of the new row.
|
|
int x;
|
|
for (x = 0; x < rescaler->num_channels * rescaler->dst_width; ++x) {
|
|
rescaler->irow[x] += rescaler->frow[x];
|
|
}
|
|
}
|
|
++rescaler->src_y;
|
|
src += src_stride;
|
|
++total_imported;
|
|
rescaler->y_accum -= rescaler->y_sub;
|
|
}
|
|
return total_imported;
|
|
}
|
|
|
|
int WebPRescalerExport(WebPRescaler* const rescaler) {
|
|
int total_exported = 0;
|
|
while (WebPRescalerHasPendingOutput(rescaler)) {
|
|
WebPRescalerExportRow(rescaler);
|
|
++total_exported;
|
|
}
|
|
return total_exported;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/utils/thread_utils.c */
|
|
#define ChangeState webpdec_priv_ChangeState
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Multi-threaded worker
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <string.h> // for memset()
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#ifdef WEBP_USE_THREAD
|
|
|
|
#if defined(_WIN32)
|
|
|
|
#include <windows.h>
|
|
typedef HANDLE pthread_t;
|
|
|
|
#if _WIN32_WINNT < 0x0600
|
|
#error _WIN32_WINNT must target Windows Vista / Server 2008 or newer.
|
|
#endif
|
|
typedef SRWLOCK pthread_mutex_t;
|
|
typedef CONDITION_VARIABLE pthread_cond_t;
|
|
|
|
#ifndef WINAPI_FAMILY_PARTITION
|
|
#define WINAPI_PARTITION_DESKTOP 1
|
|
#define WINAPI_FAMILY_PARTITION(x) x
|
|
#endif
|
|
|
|
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
|
#define USE_CREATE_THREAD
|
|
#endif
|
|
|
|
#else // !_WIN32
|
|
|
|
#include <pthread.h>
|
|
|
|
#endif // _WIN32
|
|
|
|
typedef struct {
|
|
pthread_mutex_t mutex;
|
|
pthread_cond_t condition;
|
|
pthread_t thread;
|
|
} WebPWorkerImpl;
|
|
|
|
#if defined(_WIN32)
|
|
|
|
//------------------------------------------------------------------------------
|
|
// simplistic pthread emulation layer
|
|
|
|
#include <process.h>
|
|
|
|
// _beginthreadex requires __stdcall
|
|
#define THREADFN unsigned int __stdcall
|
|
#define THREAD_RETURN(val) (unsigned int)((DWORD_PTR)val)
|
|
|
|
static int pthread_create(pthread_t* const thread, const void* attr,
|
|
unsigned int(__stdcall* start)(void*), void* arg) {
|
|
(void)attr;
|
|
#ifdef USE_CREATE_THREAD
|
|
*thread = CreateThread(/*lpThreadAttributes=*/NULL,
|
|
/*dwStackSize=*/0, start, arg, /*dwStackSize=*/0,
|
|
/*lpThreadId=*/NULL);
|
|
#else
|
|
*thread =
|
|
(pthread_t)_beginthreadex(/*security=*/NULL,
|
|
/*stack_size=*/0, start, arg, /*initflag=*/0,
|
|
/*thrdaddr=*/NULL);
|
|
#endif
|
|
if (*thread == NULL) return 1;
|
|
SetThreadPriority(*thread, THREAD_PRIORITY_ABOVE_NORMAL);
|
|
return 0;
|
|
}
|
|
|
|
static int pthread_join(pthread_t thread, void** value_ptr) {
|
|
(void)value_ptr;
|
|
return (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0 ||
|
|
CloseHandle(thread) == 0);
|
|
}
|
|
|
|
// Mutex
|
|
static int pthread_mutex_init(pthread_mutex_t* const mutex, void* mutexattr) {
|
|
(void)mutexattr;
|
|
InitializeSRWLock(mutex);
|
|
return 0;
|
|
}
|
|
|
|
static int pthread_mutex_lock(pthread_mutex_t* const mutex) {
|
|
AcquireSRWLockExclusive(mutex);
|
|
return 0;
|
|
}
|
|
|
|
static int pthread_mutex_unlock(pthread_mutex_t* const mutex) {
|
|
ReleaseSRWLockExclusive(mutex);
|
|
return 0;
|
|
}
|
|
|
|
static int pthread_mutex_destroy(pthread_mutex_t* const mutex) {
|
|
(void)mutex;
|
|
return 0;
|
|
}
|
|
|
|
// Condition
|
|
static int pthread_cond_destroy(pthread_cond_t* const condition) {
|
|
(void)condition;
|
|
return 0;
|
|
}
|
|
|
|
static int pthread_cond_init(pthread_cond_t* const condition, void* cond_attr) {
|
|
(void)cond_attr;
|
|
InitializeConditionVariable(condition);
|
|
return 0;
|
|
}
|
|
|
|
static int pthread_cond_signal(pthread_cond_t* const condition) {
|
|
WakeConditionVariable(condition);
|
|
return 0;
|
|
}
|
|
|
|
static int pthread_cond_wait(pthread_cond_t* const condition,
|
|
pthread_mutex_t* const mutex) {
|
|
const int ok = SleepConditionVariableSRW(condition, mutex, INFINITE, 0);
|
|
return !ok;
|
|
}
|
|
|
|
#else // !_WIN32
|
|
#define THREADFN void*
|
|
#define THREAD_RETURN(val) val
|
|
#endif // _WIN32
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static THREADFN ThreadLoop(void* ptr) {
|
|
WebPWorker* const worker = (WebPWorker*)ptr;
|
|
WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl;
|
|
int done = 0;
|
|
while (!done) {
|
|
pthread_mutex_lock(&impl->mutex);
|
|
while (worker->status == OK) { // wait in idling mode
|
|
pthread_cond_wait(&impl->condition, &impl->mutex);
|
|
}
|
|
if (worker->status == WORK) {
|
|
WebPGetWorkerInterface()->Execute(worker);
|
|
worker->status = OK;
|
|
} else if (worker->status == NOT_OK) { // finish the worker
|
|
done = 1;
|
|
}
|
|
// signal to the main thread that we're done (for Sync())
|
|
// Note the associated mutex does not need to be held when signaling the
|
|
// condition. Unlocking the mutex first may improve performance in some
|
|
// implementations, avoiding the case where the waiting thread can't
|
|
// reacquire the mutex when woken.
|
|
pthread_mutex_unlock(&impl->mutex);
|
|
pthread_cond_signal(&impl->condition);
|
|
}
|
|
return THREAD_RETURN(NULL); // Thread is finished
|
|
}
|
|
|
|
// main thread state control
|
|
static void ChangeState(WebPWorker* const worker, WebPWorkerStatus new_status) {
|
|
// No-op when attempting to change state on a thread that didn't come up.
|
|
// Checking 'status' without acquiring the lock first would result in a data
|
|
// race.
|
|
WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl;
|
|
if (impl == NULL) return;
|
|
|
|
pthread_mutex_lock(&impl->mutex);
|
|
if (worker->status >= OK) {
|
|
// wait for the worker to finish
|
|
while (worker->status != OK) {
|
|
pthread_cond_wait(&impl->condition, &impl->mutex);
|
|
}
|
|
// assign new status and release the working thread if needed
|
|
if (new_status != OK) {
|
|
worker->status = new_status;
|
|
// Note the associated mutex does not need to be held when signaling the
|
|
// condition. Unlocking the mutex first may improve performance in some
|
|
// implementations, avoiding the case where the waiting thread can't
|
|
// reacquire the mutex when woken.
|
|
pthread_mutex_unlock(&impl->mutex);
|
|
pthread_cond_signal(&impl->condition);
|
|
return;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&impl->mutex);
|
|
}
|
|
|
|
#endif // WEBP_USE_THREAD
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void Init(WebPWorker* const worker) {
|
|
WEBP_UNSAFE_MEMSET(worker, 0, sizeof(*worker));
|
|
worker->status = NOT_OK;
|
|
}
|
|
|
|
static int Sync(WebPWorker* const worker) {
|
|
#ifdef WEBP_USE_THREAD
|
|
ChangeState(worker, OK);
|
|
#endif
|
|
assert(worker->status <= OK);
|
|
return !worker->had_error;
|
|
}
|
|
|
|
static int Reset(WebPWorker* const worker) {
|
|
int ok = 1;
|
|
worker->had_error = 0;
|
|
if (worker->status < OK) {
|
|
#ifdef WEBP_USE_THREAD
|
|
WebPWorkerImpl* const impl =
|
|
(WebPWorkerImpl*)WebPSafeCalloc(1, sizeof(WebPWorkerImpl));
|
|
worker->impl = (void*)impl;
|
|
if (worker->impl == NULL) {
|
|
return 0;
|
|
}
|
|
if (pthread_mutex_init(&impl->mutex, NULL)) {
|
|
goto Error;
|
|
}
|
|
if (pthread_cond_init(&impl->condition, NULL)) {
|
|
pthread_mutex_destroy(&impl->mutex);
|
|
goto Error;
|
|
}
|
|
pthread_mutex_lock(&impl->mutex);
|
|
ok = !pthread_create(&impl->thread, NULL, ThreadLoop, worker);
|
|
if (ok) worker->status = OK;
|
|
pthread_mutex_unlock(&impl->mutex);
|
|
if (!ok) {
|
|
pthread_mutex_destroy(&impl->mutex);
|
|
pthread_cond_destroy(&impl->condition);
|
|
Error:
|
|
WebPSafeFree(impl);
|
|
worker->impl = NULL;
|
|
return 0;
|
|
}
|
|
#else
|
|
worker->status = OK;
|
|
#endif
|
|
} else if (worker->status > OK) {
|
|
ok = Sync(worker);
|
|
}
|
|
assert(!ok || (worker->status == OK));
|
|
return ok;
|
|
}
|
|
|
|
static void Execute(WebPWorker* const worker) {
|
|
if (worker->hook != NULL) {
|
|
worker->had_error |= !worker->hook(worker->data1, worker->data2);
|
|
}
|
|
}
|
|
|
|
static void Launch(WebPWorker* const worker) {
|
|
#ifdef WEBP_USE_THREAD
|
|
ChangeState(worker, WORK);
|
|
#else
|
|
Execute(worker);
|
|
#endif
|
|
}
|
|
|
|
static void End(WebPWorker* const worker) {
|
|
#ifdef WEBP_USE_THREAD
|
|
if (worker->impl != NULL) {
|
|
WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl;
|
|
ChangeState(worker, NOT_OK);
|
|
pthread_join(impl->thread, NULL);
|
|
pthread_mutex_destroy(&impl->mutex);
|
|
pthread_cond_destroy(&impl->condition);
|
|
WebPSafeFree(impl);
|
|
worker->impl = NULL;
|
|
}
|
|
#else
|
|
worker->status = NOT_OK;
|
|
assert(worker->impl == NULL);
|
|
#endif
|
|
assert(worker->status == NOT_OK);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
static WebPWorkerInterface g_worker_interface = {Init, Reset, Sync,
|
|
Launch, Execute, End};
|
|
|
|
int WebPSetWorkerInterface(const WebPWorkerInterface* const winterface) {
|
|
if (winterface == NULL || winterface->Init == NULL ||
|
|
winterface->Reset == NULL || winterface->Sync == NULL ||
|
|
winterface->Launch == NULL || winterface->Execute == NULL ||
|
|
winterface->End == NULL) {
|
|
return 0;
|
|
}
|
|
g_worker_interface = *winterface;
|
|
return 1;
|
|
}
|
|
|
|
const WebPWorkerInterface* WebPGetWorkerInterface(void) {
|
|
return &g_worker_interface;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
#undef ChangeState
|
|
/* >>> src/utils/utils.c */
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Misc. common utility functions
|
|
//
|
|
// Author: Skal (pascal.massimino@gmail.com)
|
|
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h> // for memcpy()
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
// If PRINT_MEM_INFO is defined, extra info (like total memory used, number of
|
|
// alloc/free etc) is printed. For debugging/tuning purpose only (it's slow,
|
|
// and not multi-thread safe!).
|
|
// An interesting alternative is valgrind's 'massif' tool:
|
|
// https://valgrind.org/docs/manual/ms-manual.html
|
|
// Here is an example command line:
|
|
/* valgrind --tool=massif --massif-out-file=massif.out \
|
|
--stacks=yes --alloc-fn=WebPSafeMalloc --alloc-fn=WebPSafeCalloc
|
|
ms_print massif.out
|
|
*/
|
|
// In addition:
|
|
// * if PRINT_MEM_TRAFFIC is defined, all the details of the malloc/free cycles
|
|
// are printed.
|
|
// * if MALLOC_FAIL_AT is defined, the global environment variable
|
|
// $MALLOC_FAIL_AT is used to simulate a memory error when calloc or malloc
|
|
// is called for the nth time. Example usage:
|
|
// export MALLOC_FAIL_AT=50 && ./examples/cwebp input.png
|
|
// * if MALLOC_LIMIT is defined, the global environment variable $MALLOC_LIMIT
|
|
// sets the maximum amount of memory (in bytes) made available to libwebp.
|
|
// This can be used to emulate environment with very limited memory.
|
|
// Example: export MALLOC_LIMIT=64000000 && ./examples/dwebp picture.webp
|
|
|
|
// #define PRINT_MEM_INFO
|
|
// #define PRINT_MEM_TRAFFIC
|
|
// #define MALLOC_FAIL_AT
|
|
// #define MALLOC_LIMIT
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Checked memory allocation
|
|
|
|
#if defined(PRINT_MEM_INFO)
|
|
|
|
#include <stdio.h>
|
|
|
|
static int num_malloc_calls = 0;
|
|
static int num_calloc_calls = 0;
|
|
static int num_free_calls = 0;
|
|
static int countdown_to_fail = 0; // 0 = off
|
|
|
|
typedef struct MemBlock MemBlock;
|
|
struct MemBlock {
|
|
void* ptr;
|
|
size_t size;
|
|
MemBlock* next;
|
|
};
|
|
|
|
static MemBlock* all_blocks = NULL;
|
|
static size_t total_mem = 0;
|
|
static size_t total_mem_allocated = 0;
|
|
static size_t high_water_mark = 0;
|
|
static size_t mem_limit = 0;
|
|
|
|
static int exit_registered = 0;
|
|
|
|
static void PrintMemInfo(void) {
|
|
fprintf(stderr, "\nMEMORY INFO:\n");
|
|
fprintf(stderr, "num calls to: malloc = %4d\n", num_malloc_calls);
|
|
fprintf(stderr, " calloc = %4d\n", num_calloc_calls);
|
|
fprintf(stderr, " free = %4d\n", num_free_calls);
|
|
fprintf(stderr, "total_mem: %u\n", (uint32_t)total_mem);
|
|
fprintf(stderr, "total_mem allocated: %u\n", (uint32_t)total_mem_allocated);
|
|
fprintf(stderr, "high-water mark: %u\n", (uint32_t)high_water_mark);
|
|
while (all_blocks != NULL) {
|
|
MemBlock* b = all_blocks;
|
|
all_blocks = b->next;
|
|
free(b);
|
|
}
|
|
}
|
|
|
|
static void Increment(int* const v) {
|
|
if (!exit_registered) {
|
|
#if defined(MALLOC_FAIL_AT)
|
|
{
|
|
const char* const malloc_fail_at_str = getenv("MALLOC_FAIL_AT");
|
|
if (malloc_fail_at_str != NULL) {
|
|
countdown_to_fail = atoi(malloc_fail_at_str);
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(MALLOC_LIMIT)
|
|
{
|
|
const char* const malloc_limit_str = getenv("MALLOC_LIMIT");
|
|
#if MALLOC_LIMIT > 1
|
|
mem_limit = (size_t)MALLOC_LIMIT;
|
|
#endif
|
|
if (malloc_limit_str != NULL) {
|
|
mem_limit = atoi(malloc_limit_str);
|
|
}
|
|
}
|
|
#endif
|
|
(void)countdown_to_fail;
|
|
(void)mem_limit;
|
|
atexit(PrintMemInfo);
|
|
exit_registered = 1;
|
|
}
|
|
++*v;
|
|
}
|
|
|
|
static void AddMem(void* ptr, size_t size) {
|
|
if (ptr != NULL) {
|
|
MemBlock* const b = (MemBlock*)malloc(sizeof(*b));
|
|
if (b == NULL) abort();
|
|
b->next = all_blocks;
|
|
all_blocks = b;
|
|
b->ptr = ptr;
|
|
b->size = size;
|
|
total_mem += size;
|
|
total_mem_allocated += size;
|
|
#if defined(PRINT_MEM_TRAFFIC)
|
|
#if defined(MALLOC_FAIL_AT)
|
|
fprintf(stderr, "fail-count: %5d [mem=%u]\n",
|
|
num_malloc_calls + num_calloc_calls, (uint32_t)total_mem);
|
|
#else
|
|
fprintf(stderr, "Mem: %u (+%u)\n", (uint32_t)total_mem, (uint32_t)size);
|
|
#endif
|
|
#endif
|
|
if (total_mem > high_water_mark) high_water_mark = total_mem;
|
|
}
|
|
}
|
|
|
|
static void SubMem(void* ptr) {
|
|
if (ptr != NULL) {
|
|
MemBlock** b = &all_blocks;
|
|
// Inefficient search, but that's just for debugging.
|
|
while (*b != NULL && (*b)->ptr != ptr) b = &(*b)->next;
|
|
if (*b == NULL) {
|
|
fprintf(stderr, "Invalid pointer free! (%p)\n", ptr);
|
|
abort();
|
|
}
|
|
{
|
|
MemBlock* const block = *b;
|
|
*b = block->next;
|
|
total_mem -= block->size;
|
|
#if defined(PRINT_MEM_TRAFFIC)
|
|
fprintf(stderr, "Mem: %u (-%u)\n", (uint32_t)total_mem,
|
|
(uint32_t)block->size);
|
|
#endif
|
|
free(block);
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
#define Increment(v) \
|
|
do { \
|
|
} while (0)
|
|
#define AddMem(p, s) \
|
|
do { \
|
|
} while (0)
|
|
#define SubMem(p) \
|
|
do { \
|
|
} while (0)
|
|
#endif
|
|
|
|
// Returns 0 in case of overflow of nmemb * size.
|
|
static int CheckSizeArgumentsOverflow(uint64_t nmemb, size_t size) {
|
|
const uint64_t total_size = nmemb * size;
|
|
if (nmemb == 0) return 1;
|
|
if ((uint64_t)size > WEBP_MAX_ALLOCABLE_MEMORY / nmemb) return 0;
|
|
if (!CheckSizeOverflow(total_size)) return 0;
|
|
#if defined(PRINT_MEM_INFO) && defined(MALLOC_FAIL_AT)
|
|
if (countdown_to_fail > 0 && --countdown_to_fail == 0) {
|
|
return 0; // fake fail!
|
|
}
|
|
#endif
|
|
#if defined(PRINT_MEM_INFO) && defined(MALLOC_LIMIT)
|
|
if (mem_limit > 0) {
|
|
const uint64_t new_total_mem = (uint64_t)total_mem + total_size;
|
|
if (!CheckSizeOverflow(new_total_mem) || new_total_mem > mem_limit) {
|
|
return 0; // fake fail!
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
void* WEBP_SIZED_BY_OR_NULL(nmemb* size)
|
|
WebPSafeMalloc(uint64_t nmemb, size_t size) {
|
|
void* ptr;
|
|
Increment(&num_malloc_calls);
|
|
if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL;
|
|
assert(nmemb * size > 0);
|
|
ptr = malloc((size_t)(nmemb * size));
|
|
AddMem(ptr, (size_t)(nmemb * size));
|
|
return WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(void*, ptr, (size_t)(nmemb * size));
|
|
}
|
|
|
|
void* WEBP_SIZED_BY_OR_NULL(nmemb* size)
|
|
WebPSafeCalloc(uint64_t nmemb, size_t size) {
|
|
void* ptr;
|
|
Increment(&num_calloc_calls);
|
|
if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL;
|
|
assert(nmemb * size > 0);
|
|
ptr = calloc((size_t)nmemb, size);
|
|
AddMem(ptr, (size_t)(nmemb * size));
|
|
return WEBP_UNSAFE_FORGE_BIDI_INDEXABLE(void*, ptr, (size_t)(nmemb * size));
|
|
}
|
|
|
|
void WebPSafeFree(void* const ptr) {
|
|
if (ptr != NULL) {
|
|
Increment(&num_free_calls);
|
|
SubMem(ptr);
|
|
}
|
|
free(ptr);
|
|
}
|
|
|
|
// Public API functions.
|
|
|
|
void* WEBP_SINGLE WebPMalloc(size_t size) {
|
|
// Currently WebPMalloc/WebPFree are declared in src/webp/types.h, which does
|
|
// not include bounds_safety.h. As such, the "default" annotation for the
|
|
// pointers they accept/return is __single.
|
|
//
|
|
// All callers will need to immediately cast the returned pointer to
|
|
// WEBP_BIDI_INDEXABLE or WEBP_INDEXABLE via
|
|
// WEBP_UNSAFE_FORGE_BIDI_INDEXABLE.
|
|
//
|
|
// TODO: https://issues.webmproject.org/432511225 - Remove this once we can
|
|
// annotate WebPMalloc/WebPFree.
|
|
return WEBP_UNSAFE_FORGE_SINGLE(void*, WebPSafeMalloc(1, size));
|
|
}
|
|
|
|
void WebPFree(void* WEBP_SINGLE ptr) { WebPSafeFree(ptr); }
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void WebPCopyPlane(const uint8_t* src, int src_stride, uint8_t* dst,
|
|
int dst_stride, int width, int height) {
|
|
assert(src != NULL && dst != NULL);
|
|
assert(abs(src_stride) >= width && abs(dst_stride) >= width);
|
|
while (height-- > 0) {
|
|
WEBP_UNSAFE_MEMCPY(dst, src, width);
|
|
src += src_stride;
|
|
dst += dst_stride;
|
|
}
|
|
}
|
|
|
|
void WebPCopyPixels(const WebPPicture* const src, WebPPicture* const dst) {
|
|
assert(src != NULL && dst != NULL);
|
|
assert(src->width == dst->width && src->height == dst->height);
|
|
assert(src->use_argb && dst->use_argb);
|
|
WebPCopyPlane((uint8_t*)src->argb, 4 * src->argb_stride, (uint8_t*)dst->argb,
|
|
4 * dst->argb_stride, 4 * src->width, src->height);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int WebPGetColorPalette(
|
|
const WebPPicture* const pic,
|
|
uint32_t* const WEBP_COUNTED_BY_OR_NULL(MAX_PALETTE_SIZE) palette) {
|
|
return GetColorPalette(pic, palette);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#if defined(WEBP_NEED_LOG_TABLE_8BIT)
|
|
const uint8_t WebPLogTable8bit[256] = { // 31 ^ clz(i)
|
|
0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7};
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
/* >>> src/demux/demux.c */
|
|
#define MemBuffer webpdec_priv_MemBuffer
|
|
#define InitMemBuffer webpdec_priv_InitMemBuffer
|
|
#define MemDataSize webpdec_priv_MemDataSize
|
|
#define RemapMemBuffer webpdec_priv_RemapMemBuffer
|
|
#define ParseVP8X webpdec_priv_ParseVP8X
|
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// WebP container demux.
|
|
//
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* >>> src/webp/mux.h */
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// RIFF container manipulation and encoding for WebP images.
|
|
//
|
|
// Authors: Urvang (urvang@google.com)
|
|
// Vikas (vikasa@google.com)
|
|
|
|
#ifndef WEBP_WEBP_MUX_H_
|
|
#define WEBP_WEBP_MUX_H_
|
|
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define WEBP_MUX_ABI_VERSION 0x0109 // MAJOR(8b) + MINOR(8b)
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Mux API
|
|
//
|
|
// This API allows manipulation of WebP container images containing features
|
|
// like color profile, metadata, animation.
|
|
//
|
|
// Code Example#1: Create a WebPMux object with image data, color profile and
|
|
// XMP metadata.
|
|
/*
|
|
int copy_data = 0;
|
|
WebPMux* mux = WebPMuxNew();
|
|
// ... (Prepare image data).
|
|
WebPMuxSetImage(mux, &image, copy_data);
|
|
// ... (Prepare ICCP color profile data).
|
|
WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
|
|
// ... (Prepare XMP metadata).
|
|
WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data);
|
|
// Get data from mux in WebP RIFF format.
|
|
WebPMuxAssemble(mux, &output_data);
|
|
WebPMuxDelete(mux);
|
|
// ... (Consume output_data; e.g. write output_data.bytes to file).
|
|
WebPDataClear(&output_data);
|
|
*/
|
|
|
|
// Code Example#2: Get image and color profile data from a WebP file.
|
|
/*
|
|
int copy_data = 0;
|
|
// ... (Read data from file).
|
|
WebPMux* mux = WebPMuxCreate(&data, copy_data);
|
|
WebPMuxGetFrame(mux, 1, &image);
|
|
// ... (Consume image; e.g. call WebPDecode() to decode the data).
|
|
WebPMuxGetChunk(mux, "ICCP", &icc_profile);
|
|
// ... (Consume icc_data).
|
|
WebPMuxDelete(mux);
|
|
WebPFree(data);
|
|
*/
|
|
|
|
// Note: forward declaring enumerations is not allowed in (strict) C and C++,
|
|
// the types are left here for reference.
|
|
// typedef enum WebPMuxError WebPMuxError;
|
|
// typedef enum WebPChunkId WebPChunkId;
|
|
typedef struct WebPMux WebPMux; // main opaque object.
|
|
typedef struct WebPMuxFrameInfo WebPMuxFrameInfo;
|
|
typedef struct WebPMuxAnimParams WebPMuxAnimParams;
|
|
typedef struct WebPAnimEncoderOptions WebPAnimEncoderOptions;
|
|
|
|
// Error codes
|
|
typedef enum WEBP_NODISCARD WebPMuxError {
|
|
WEBP_MUX_OK = 1,
|
|
WEBP_MUX_NOT_FOUND = 0,
|
|
WEBP_MUX_INVALID_ARGUMENT = -1,
|
|
WEBP_MUX_BAD_DATA = -2,
|
|
WEBP_MUX_MEMORY_ERROR = -3,
|
|
WEBP_MUX_NOT_ENOUGH_DATA = -4
|
|
} WebPMuxError;
|
|
|
|
// IDs for different types of chunks.
|
|
typedef enum WebPChunkId {
|
|
WEBP_CHUNK_VP8X, // VP8X
|
|
WEBP_CHUNK_ICCP, // ICCP
|
|
WEBP_CHUNK_ANIM, // ANIM
|
|
WEBP_CHUNK_ANMF, // ANMF
|
|
WEBP_CHUNK_DEPRECATED, // (deprecated from FRGM)
|
|
WEBP_CHUNK_ALPHA, // ALPH
|
|
WEBP_CHUNK_IMAGE, // VP8/VP8L
|
|
WEBP_CHUNK_EXIF, // EXIF
|
|
WEBP_CHUNK_XMP, // XMP
|
|
WEBP_CHUNK_UNKNOWN, // Other chunks.
|
|
WEBP_CHUNK_NIL
|
|
} WebPChunkId;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Returns the version number of the mux library, packed in hexadecimal using
|
|
// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
|
WEBP_EXTERN int WebPGetMuxVersion(void);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Life of a Mux object
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD WEBP_EXTERN WebPMux* WebPNewInternal(int);
|
|
|
|
// Creates an empty mux object.
|
|
// Returns:
|
|
// A pointer to the newly created empty mux object.
|
|
// Or NULL in case of memory error.
|
|
WEBP_NODISCARD static WEBP_INLINE WebPMux* WebPMuxNew(void) {
|
|
return WebPNewInternal(WEBP_MUX_ABI_VERSION);
|
|
}
|
|
|
|
// Deletes the mux object.
|
|
// Parameters:
|
|
// mux - (in/out) object to be deleted
|
|
WEBP_EXTERN void WebPMuxDelete(WebPMux* mux);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Mux creation.
|
|
|
|
// Internal, version-checked, entry point
|
|
WEBP_NODISCARD WEBP_EXTERN WebPMux* WebPMuxCreateInternal(const WebPData*, int,
|
|
int);
|
|
|
|
// Creates a mux object from raw data given in WebP RIFF format.
|
|
// Parameters:
|
|
// bitstream - (in) the bitstream data in WebP RIFF format
|
|
// copy_data - (in) value 1 indicates given data WILL be copied to the mux
|
|
// object and value 0 indicates data will NOT be copied. If the
|
|
// data is not copied, it must exist for the lifetime of the
|
|
// mux object.
|
|
// Returns:
|
|
// A pointer to the mux object created from given data - on success.
|
|
// NULL - In case of invalid data or memory error.
|
|
WEBP_NODISCARD static WEBP_INLINE WebPMux* WebPMuxCreate(
|
|
const WebPData* bitstream, int copy_data) {
|
|
return WebPMuxCreateInternal(bitstream, copy_data, WEBP_MUX_ABI_VERSION);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Non-image chunks.
|
|
|
|
// Note: Only non-image related chunks should be managed through chunk APIs.
|
|
// (Image related chunks are: "ANMF", "VP8 ", "VP8L" and "ALPH").
|
|
// To add, get and delete images, use WebPMuxSetImage(), WebPMuxPushFrame(),
|
|
// WebPMuxGetFrame() and WebPMuxDeleteFrame().
|
|
|
|
// Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object.
|
|
// Any existing chunk(s) with the same id will be removed.
|
|
// Parameters:
|
|
// mux - (in/out) object to which the chunk is to be added
|
|
// fourcc - (in) a character array containing the fourcc of the given chunk;
|
|
// e.g., "ICCP", "XMP ", "EXIF" etc.
|
|
// chunk_data - (in) the chunk data to be added
|
|
// copy_data - (in) value 1 indicates given data WILL be copied to the mux
|
|
// object and value 0 indicates data will NOT be copied. If the
|
|
// data is not copied, it must exist until a call to
|
|
// WebPMuxAssemble() is made.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
|
|
// or if fourcc corresponds to an image chunk.
|
|
// WEBP_MUX_MEMORY_ERROR - on memory allocation error.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],
|
|
const WebPData* chunk_data,
|
|
int copy_data);
|
|
|
|
// Gets a reference to the data of the chunk with id 'fourcc' in the mux object.
|
|
// The caller should NOT free the returned data.
|
|
// Parameters:
|
|
// mux - (in) object from which the chunk data is to be fetched
|
|
// fourcc - (in) a character array containing the fourcc of the chunk;
|
|
// e.g., "ICCP", "XMP ", "EXIF" etc.
|
|
// chunk_data - (out) returned chunk data
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
|
|
// or if fourcc corresponds to an image chunk.
|
|
// WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given id.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxGetChunk(const WebPMux* mux,
|
|
const char fourcc[4],
|
|
WebPData* chunk_data);
|
|
|
|
// Deletes the chunk with the given 'fourcc' from the mux object.
|
|
// Parameters:
|
|
// mux - (in/out) object from which the chunk is to be deleted
|
|
// fourcc - (in) a character array containing the fourcc of the chunk;
|
|
// e.g., "ICCP", "XMP ", "EXIF" etc.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux or fourcc is NULL
|
|
// or if fourcc corresponds to an image chunk.
|
|
// WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given fourcc.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Images.
|
|
|
|
// Encapsulates data about a single frame.
|
|
struct WebPMuxFrameInfo {
|
|
WebPData bitstream; // image data: can be a raw VP8/VP8L bitstream
|
|
// or a single-image WebP file.
|
|
int x_offset; // x-offset of the frame.
|
|
int y_offset; // y-offset of the frame.
|
|
int duration; // duration of the frame (in milliseconds).
|
|
|
|
WebPChunkId id; // frame type: should be one of WEBP_CHUNK_ANMF
|
|
// or WEBP_CHUNK_IMAGE
|
|
WebPMuxAnimDispose dispose_method; // Disposal method for the frame.
|
|
WebPMuxAnimBlend blend_method; // Blend operation for the frame.
|
|
uint32_t pad[1]; // padding for later use
|
|
};
|
|
|
|
// Sets the (non-animated) image in the mux object.
|
|
// Note: Any existing images (including frames) will be removed.
|
|
// Parameters:
|
|
// mux - (in/out) object in which the image is to be set
|
|
// bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image
|
|
// WebP file (non-animated)
|
|
// copy_data - (in) value 1 indicates given data WILL be copied to the mux
|
|
// object and value 0 indicates data will NOT be copied. If the
|
|
// data is not copied, it must exist until a call to
|
|
// WebPMuxAssemble() is made.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL.
|
|
// WEBP_MUX_MEMORY_ERROR - on memory allocation error.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxSetImage(WebPMux* mux,
|
|
const WebPData* bitstream,
|
|
int copy_data);
|
|
|
|
// Adds a frame at the end of the mux object.
|
|
// Notes: (1) frame.id should be WEBP_CHUNK_ANMF
|
|
// (2) For setting a non-animated image, use WebPMuxSetImage() instead.
|
|
// (3) Type of frame being pushed must be same as the frames in mux.
|
|
// (4) As WebP only supports even offsets, any odd offset will be snapped
|
|
// to an even location using: offset &= ~1
|
|
// Parameters:
|
|
// mux - (in/out) object to which the frame is to be added
|
|
// frame - (in) frame data.
|
|
// copy_data - (in) value 1 indicates given data WILL be copied to the mux
|
|
// object and value 0 indicates data will NOT be copied. If the
|
|
// data is not copied, it must exist until a call to
|
|
// WebPMuxAssemble() is made.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL
|
|
// or if content of 'frame' is invalid.
|
|
// WEBP_MUX_MEMORY_ERROR - on memory allocation error.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxPushFrame(WebPMux* mux,
|
|
const WebPMuxFrameInfo* frame,
|
|
int copy_data);
|
|
|
|
// Gets the nth frame from the mux object.
|
|
// The content of 'frame->bitstream' is allocated using WebPMalloc(), and NOT
|
|
// owned by the 'mux' object. It MUST be deallocated by the caller by calling
|
|
// WebPDataClear().
|
|
// nth=0 has a special meaning - last position.
|
|
// Parameters:
|
|
// mux - (in) object from which the info is to be fetched
|
|
// nth - (in) index of the frame in the mux object
|
|
// frame - (out) data of the returned frame
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL.
|
|
// WEBP_MUX_NOT_FOUND - if there are less than nth frames in the mux object.
|
|
// WEBP_MUX_BAD_DATA - if nth frame chunk in mux is invalid.
|
|
// WEBP_MUX_MEMORY_ERROR - on memory allocation error.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxGetFrame(const WebPMux* mux, uint32_t nth,
|
|
WebPMuxFrameInfo* frame);
|
|
|
|
// Deletes a frame from the mux object.
|
|
// nth=0 has a special meaning - last position.
|
|
// Parameters:
|
|
// mux - (in/out) object from which a frame is to be deleted
|
|
// nth - (in) The position from which the frame is to be deleted
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL.
|
|
// WEBP_MUX_NOT_FOUND - If there are less than nth frames in the mux object
|
|
// before deletion.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Animation.
|
|
|
|
// Animation parameters.
|
|
struct WebPMuxAnimParams {
|
|
uint32_t bgcolor; // Background color of the canvas stored (in MSB order) as:
|
|
// Bits 00 to 07: Alpha.
|
|
// Bits 08 to 15: Red.
|
|
// Bits 16 to 23: Green.
|
|
// Bits 24 to 31: Blue.
|
|
int loop_count; // Number of times to repeat the animation [0 = infinite].
|
|
};
|
|
|
|
// Sets the animation parameters in the mux object. Any existing ANIM chunks
|
|
// will be removed.
|
|
// Parameters:
|
|
// mux - (in/out) object in which ANIM chunk is to be set/added
|
|
// params - (in) animation parameters.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL.
|
|
// WEBP_MUX_MEMORY_ERROR - on memory allocation error.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError
|
|
WebPMuxSetAnimationParams(WebPMux* mux, const WebPMuxAnimParams* params);
|
|
|
|
// Gets the animation parameters from the mux object.
|
|
// Parameters:
|
|
// mux - (in) object from which the animation parameters to be fetched
|
|
// params - (out) animation parameters extracted from the ANIM chunk
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL.
|
|
// WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
|
|
WebPMuxAnimParams* params);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Misc Utilities.
|
|
|
|
// Sets the canvas size for the mux object. The width and height can be
|
|
// specified explicitly or left as zero (0, 0).
|
|
// * When width and height are specified explicitly, then this frame bound is
|
|
// enforced during subsequent calls to WebPMuxAssemble() and an error is
|
|
// reported if any animated frame does not completely fit within the canvas.
|
|
// * When unspecified (0, 0), the constructed canvas will get the frame bounds
|
|
// from the bounding-box over all frames after calling WebPMuxAssemble().
|
|
// Parameters:
|
|
// mux - (in) object to which the canvas size is to be set
|
|
// width - (in) canvas width
|
|
// height - (in) canvas height
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL; or
|
|
// width or height are invalid or out of bounds
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux, int width,
|
|
int height);
|
|
|
|
// Gets the canvas size from the mux object.
|
|
// Note: This method assumes that the VP8X chunk, if present, is up-to-date.
|
|
// That is, the mux object hasn't been modified since the last call to
|
|
// WebPMuxAssemble() or WebPMuxCreate().
|
|
// Parameters:
|
|
// mux - (in) object from which the canvas size is to be fetched
|
|
// width - (out) canvas width
|
|
// height - (out) canvas height
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux, width or height is NULL.
|
|
// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width,
|
|
int* height);
|
|
|
|
// Gets the feature flags from the mux object.
|
|
// Note: This method assumes that the VP8X chunk, if present, is up-to-date.
|
|
// That is, the mux object hasn't been modified since the last call to
|
|
// WebPMuxAssemble() or WebPMuxCreate().
|
|
// Parameters:
|
|
// mux - (in) object from which the features are to be fetched
|
|
// flags - (out) the flags specifying which features are present in the
|
|
// mux object. This will be an OR of various flag values.
|
|
// Enum 'WebPFeatureFlags' can be used to test individual flag values.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux or flags is NULL.
|
|
// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxGetFeatures(const WebPMux* mux,
|
|
uint32_t* flags);
|
|
|
|
// Gets number of chunks with the given 'id' in the mux object.
|
|
// Parameters:
|
|
// mux - (in) object from which the info is to be fetched
|
|
// id - (in) chunk id specifying the type of chunk
|
|
// num_elements - (out) number of chunks with the given chunk id
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux, or num_elements is NULL.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxNumChunks(const WebPMux* mux, WebPChunkId id,
|
|
int* num_elements);
|
|
|
|
// Assembles all chunks in WebP RIFF format and returns in 'assembled_data'.
|
|
// This function also validates the mux object.
|
|
// Note: The content of 'assembled_data' will be ignored and overwritten.
|
|
// Also, the content of 'assembled_data' is allocated using WebPMalloc(), and
|
|
// NOT owned by the 'mux' object. It MUST be deallocated by the caller by
|
|
// calling WebPDataClear(). It's always safe to call WebPDataClear() upon
|
|
// return, even in case of error.
|
|
// Parameters:
|
|
// mux - (in/out) object whose chunks are to be assembled
|
|
// assembled_data - (out) assembled WebP data
|
|
// Returns:
|
|
// WEBP_MUX_BAD_DATA - if mux object is invalid.
|
|
// WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL.
|
|
// WEBP_MUX_MEMORY_ERROR - on memory allocation error.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPMuxAssemble(WebPMux* mux,
|
|
WebPData* assembled_data);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// WebPAnimEncoder API
|
|
//
|
|
// This API allows encoding (possibly) animated WebP images.
|
|
//
|
|
// Code Example:
|
|
/*
|
|
WebPAnimEncoderOptions enc_options;
|
|
WebPAnimEncoderOptionsInit(&enc_options);
|
|
// Tune 'enc_options' as needed.
|
|
WebPAnimEncoder* enc = WebPAnimEncoderNew(width, height, &enc_options);
|
|
while(<there are more frames>) {
|
|
WebPConfig config;
|
|
WebPConfigInit(&config);
|
|
// Tune 'config' as needed.
|
|
WebPAnimEncoderAdd(enc, frame, timestamp_ms, &config);
|
|
}
|
|
WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL);
|
|
WebPAnimEncoderAssemble(enc, webp_data);
|
|
WebPAnimEncoderDelete(enc);
|
|
// Write the 'webp_data' to a file, or re-mux it further.
|
|
*/
|
|
|
|
typedef struct WebPAnimEncoder WebPAnimEncoder; // Main opaque object.
|
|
|
|
// Forward declarations. Defined in encode.h.
|
|
struct WebPPicture;
|
|
struct WebPConfig;
|
|
|
|
// Global options.
|
|
struct WebPAnimEncoderOptions {
|
|
WebPMuxAnimParams anim_params; // Animation parameters.
|
|
int minimize_size; // If true, minimize the output size (slow). Implicitly
|
|
// disables key-frame insertion.
|
|
int kmin;
|
|
int kmax; // Minimum and maximum distance between consecutive key
|
|
// frames in the output. The library may insert some key
|
|
// frames as needed to satisfy this criteria.
|
|
// Note that these conditions should hold: kmax > kmin
|
|
// and kmin >= kmax / 2 + 1. Also, if kmax <= 0, then
|
|
// key-frame insertion is disabled; and if kmax == 1,
|
|
// then all frames will be key-frames (kmin value does
|
|
// not matter for these special cases).
|
|
int allow_mixed; // If true, use mixed compression mode; may choose
|
|
// either lossy and lossless for each frame.
|
|
int verbose; // If true, print info and warning messages to stderr.
|
|
|
|
uint32_t padding[4]; // Padding for later use.
|
|
};
|
|
|
|
// Internal, version-checked, entry point.
|
|
WEBP_EXTERN int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions*,
|
|
int);
|
|
|
|
// Should always be called, to initialize a fresh WebPAnimEncoderOptions
|
|
// structure before modification. Returns false in case of version mismatch.
|
|
// WebPAnimEncoderOptionsInit() must have succeeded before using the
|
|
// 'enc_options' object.
|
|
WEBP_NODISCARD static WEBP_INLINE int WebPAnimEncoderOptionsInit(
|
|
WebPAnimEncoderOptions* enc_options) {
|
|
return WebPAnimEncoderOptionsInitInternal(enc_options, WEBP_MUX_ABI_VERSION);
|
|
}
|
|
|
|
// Internal, version-checked, entry point.
|
|
WEBP_EXTERN WebPAnimEncoder* WebPAnimEncoderNewInternal(
|
|
int, int, const WebPAnimEncoderOptions*, int);
|
|
|
|
// Creates and initializes a WebPAnimEncoder object.
|
|
// Parameters:
|
|
// width/height - (in) canvas width and height of the animation.
|
|
// enc_options - (in) encoding options; can be passed NULL to pick
|
|
// reasonable defaults.
|
|
// Returns:
|
|
// A pointer to the newly created WebPAnimEncoder object.
|
|
// Or NULL in case of memory error.
|
|
static WEBP_INLINE WebPAnimEncoder* WebPAnimEncoderNew(
|
|
int width, int height, const WebPAnimEncoderOptions* enc_options) {
|
|
return WebPAnimEncoderNewInternal(width, height, enc_options,
|
|
WEBP_MUX_ABI_VERSION);
|
|
}
|
|
|
|
// Optimize the given frame for WebP, encode it and add it to the
|
|
// WebPAnimEncoder object.
|
|
// The last call to 'WebPAnimEncoderAdd' should be with frame = NULL, which
|
|
// indicates that no more frames are to be added. This call is also used to
|
|
// determine the duration of the last frame.
|
|
// Parameters:
|
|
// enc - (in/out) object to which the frame is to be added.
|
|
// frame - (in/out) frame data in ARGB or YUV(A) format. If it is in YUV(A)
|
|
// format, it will be converted to ARGB, which incurs a small loss.
|
|
// timestamp_ms - (in) timestamp of this frame in milliseconds.
|
|
// Duration of a frame would be calculated as
|
|
// "timestamp of next frame - timestamp of this frame".
|
|
// Hence, timestamps should be in non-decreasing order.
|
|
// config - (in) encoding options; can be passed NULL to pick
|
|
// reasonable defaults.
|
|
// Returns:
|
|
// On error, returns false and frame->error_code is set appropriately.
|
|
// Otherwise, returns true.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPAnimEncoderAdd(
|
|
WebPAnimEncoder* enc, struct WebPPicture* frame, int timestamp_ms,
|
|
const struct WebPConfig* config);
|
|
|
|
// Assemble all frames added so far into a WebP bitstream.
|
|
// This call should be preceded by a call to 'WebPAnimEncoderAdd' with
|
|
// frame = NULL; if not, the duration of the last frame will be internally
|
|
// estimated.
|
|
// Parameters:
|
|
// enc - (in/out) object from which the frames are to be assembled.
|
|
// webp_data - (out) generated WebP bitstream.
|
|
// Returns:
|
|
// True on success.
|
|
WEBP_NODISCARD WEBP_EXTERN int WebPAnimEncoderAssemble(WebPAnimEncoder* enc,
|
|
WebPData* webp_data);
|
|
|
|
// Get error string corresponding to the most recent call using 'enc'. The
|
|
// returned string is owned by 'enc' and is valid only until the next call to
|
|
// WebPAnimEncoderAdd() or WebPAnimEncoderAssemble() or WebPAnimEncoderDelete().
|
|
// Parameters:
|
|
// enc - (in/out) object from which the error string is to be fetched.
|
|
// Returns:
|
|
// NULL if 'enc' is NULL. Otherwise, returns the error string if the last call
|
|
// to 'enc' had an error, or an empty string if the last call was a success.
|
|
WEBP_EXTERN const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc);
|
|
|
|
// Deletes the WebPAnimEncoder object.
|
|
// Parameters:
|
|
// enc - (in/out) object to be deleted
|
|
WEBP_EXTERN void WebPAnimEncoderDelete(WebPAnimEncoder* enc);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Non-image chunks.
|
|
|
|
// Note: Only non-image related chunks should be managed through chunk APIs.
|
|
// (Image related chunks are: "ANMF", "VP8 ", "VP8L" and "ALPH").
|
|
|
|
// Adds a chunk with id 'fourcc' and data 'chunk_data' in the enc object.
|
|
// Any existing chunk(s) with the same id will be removed.
|
|
// Parameters:
|
|
// enc - (in/out) object to which the chunk is to be added
|
|
// fourcc - (in) a character array containing the fourcc of the given chunk;
|
|
// e.g., "ICCP", "XMP ", "EXIF", etc.
|
|
// chunk_data - (in) the chunk data to be added
|
|
// copy_data - (in) value 1 indicates given data WILL be copied to the enc
|
|
// object and value 0 indicates data will NOT be copied. If the
|
|
// data is not copied, it must exist until a call to
|
|
// WebPAnimEncoderAssemble() is made.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if enc, fourcc or chunk_data is NULL.
|
|
// WEBP_MUX_MEMORY_ERROR - on memory allocation error.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPAnimEncoderSetChunk(WebPAnimEncoder* enc,
|
|
const char fourcc[4],
|
|
const WebPData* chunk_data,
|
|
int copy_data);
|
|
|
|
// Gets a reference to the data of the chunk with id 'fourcc' in the enc object.
|
|
// The caller should NOT free the returned data.
|
|
// Parameters:
|
|
// enc - (in) object from which the chunk data is to be fetched
|
|
// fourcc - (in) a character array containing the fourcc of the chunk;
|
|
// e.g., "ICCP", "XMP ", "EXIF", etc.
|
|
// chunk_data - (out) returned chunk data
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if enc, fourcc or chunk_data is NULL.
|
|
// WEBP_MUX_NOT_FOUND - If enc does not contain a chunk with the given id.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPAnimEncoderGetChunk(const WebPAnimEncoder* enc,
|
|
const char fourcc[4],
|
|
WebPData* chunk_data);
|
|
|
|
// Deletes the chunk with the given 'fourcc' from the enc object.
|
|
// Parameters:
|
|
// enc - (in/out) object from which the chunk is to be deleted
|
|
// fourcc - (in) a character array containing the fourcc of the chunk;
|
|
// e.g., "ICCP", "XMP ", "EXIF", etc.
|
|
// Returns:
|
|
// WEBP_MUX_INVALID_ARGUMENT - if enc or fourcc is NULL.
|
|
// WEBP_MUX_NOT_FOUND - If enc does not contain a chunk with the given fourcc.
|
|
// WEBP_MUX_OK - on success.
|
|
WEBP_EXTERN WebPMuxError WebPAnimEncoderDeleteChunk(WebPAnimEncoder* enc,
|
|
const char fourcc[4]);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif // WEBP_WEBP_MUX_H_
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#define DMUX_MAJ_VERSION 1
|
|
#define DMUX_MIN_VERSION 6
|
|
#define DMUX_REV_VERSION 0
|
|
|
|
typedef struct {
|
|
size_t start; // start location of the data
|
|
size_t end; // end location
|
|
size_t riff_end; // riff chunk end location, can be > end.
|
|
size_t buf_size; // size of the buffer
|
|
const uint8_t* buf;
|
|
} MemBuffer;
|
|
|
|
typedef struct {
|
|
size_t offset;
|
|
size_t size;
|
|
} ChunkData;
|
|
|
|
typedef struct Frame {
|
|
int x_offset, y_offset;
|
|
int width, height;
|
|
int has_alpha;
|
|
int duration;
|
|
WebPMuxAnimDispose dispose_method;
|
|
WebPMuxAnimBlend blend_method;
|
|
int frame_num;
|
|
int complete; // img_components contains a full image.
|
|
ChunkData img_components[2]; // 0=VP8{,L} 1=ALPH
|
|
struct Frame* next;
|
|
} Frame;
|
|
|
|
typedef struct Chunk {
|
|
ChunkData data;
|
|
struct Chunk* next;
|
|
} Chunk;
|
|
|
|
struct WebPDemuxer {
|
|
MemBuffer mem;
|
|
WebPDemuxState state;
|
|
int is_ext_format;
|
|
uint32_t feature_flags;
|
|
int canvas_width, canvas_height;
|
|
int loop_count;
|
|
uint32_t bgcolor;
|
|
int num_frames;
|
|
Frame* frames;
|
|
Frame** frames_tail;
|
|
Chunk* chunks; // non-image chunks
|
|
Chunk** chunks_tail;
|
|
};
|
|
|
|
typedef enum { PARSE_OK, PARSE_NEED_MORE_DATA, PARSE_ERROR } ParseStatus;
|
|
|
|
typedef struct ChunkParser {
|
|
uint8_t id[4];
|
|
ParseStatus (*parse)(WebPDemuxer* const dmux);
|
|
int (*valid)(const WebPDemuxer* const dmux);
|
|
} ChunkParser;
|
|
|
|
static ParseStatus ParseSingleImage(WebPDemuxer* const dmux);
|
|
static ParseStatus ParseVP8X(WebPDemuxer* const dmux);
|
|
static int IsValidSimpleFormat(const WebPDemuxer* const dmux);
|
|
static int IsValidExtendedFormat(const WebPDemuxer* const dmux);
|
|
|
|
static const ChunkParser kMasterChunks[] = {
|
|
{{'V', 'P', '8', ' '}, ParseSingleImage, IsValidSimpleFormat},
|
|
{{'V', 'P', '8', 'L'}, ParseSingleImage, IsValidSimpleFormat},
|
|
{{'V', 'P', '8', 'X'}, ParseVP8X, IsValidExtendedFormat},
|
|
{{'0', '0', '0', '0'}, NULL, NULL},
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int WebPGetDemuxVersion(void) {
|
|
return (DMUX_MAJ_VERSION << 16) | (DMUX_MIN_VERSION << 8) | DMUX_REV_VERSION;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// MemBuffer
|
|
|
|
static int RemapMemBuffer(MemBuffer* const mem, const uint8_t* data,
|
|
size_t size) {
|
|
if (size < mem->buf_size) return 0; // can't remap to a shorter buffer!
|
|
|
|
mem->buf = data;
|
|
mem->end = mem->buf_size = size;
|
|
return 1;
|
|
}
|
|
|
|
static int InitMemBuffer(MemBuffer* const mem, const uint8_t* data,
|
|
size_t size) {
|
|
WEBP_UNSAFE_MEMSET(mem, 0, sizeof(*mem));
|
|
return RemapMemBuffer(mem, data, size);
|
|
}
|
|
|
|
// Return the remaining data size available in 'mem'.
|
|
static WEBP_INLINE size_t MemDataSize(const MemBuffer* const mem) {
|
|
return (mem->end - mem->start);
|
|
}
|
|
|
|
// Return true if 'size' exceeds the end of the RIFF chunk.
|
|
static WEBP_INLINE int SizeIsInvalid(const MemBuffer* const mem, size_t size) {
|
|
return (size > mem->riff_end - mem->start);
|
|
}
|
|
|
|
static WEBP_INLINE void Skip(MemBuffer* const mem, size_t size) {
|
|
mem->start += size;
|
|
}
|
|
|
|
static WEBP_INLINE void Rewind(MemBuffer* const mem, size_t size) {
|
|
mem->start -= size;
|
|
}
|
|
|
|
static WEBP_INLINE const uint8_t* GetBuffer(MemBuffer* const mem) {
|
|
return mem->buf + mem->start;
|
|
}
|
|
|
|
// Read from 'mem' and skip the read bytes.
|
|
static WEBP_INLINE uint8_t ReadByte(MemBuffer* const mem) {
|
|
const uint8_t byte = mem->buf[mem->start];
|
|
Skip(mem, 1);
|
|
return byte;
|
|
}
|
|
|
|
static WEBP_INLINE int ReadLE16s(MemBuffer* const mem) {
|
|
const uint8_t* const data = mem->buf + mem->start;
|
|
const int val = GetLE16(data);
|
|
Skip(mem, 2);
|
|
return val;
|
|
}
|
|
|
|
static WEBP_INLINE int ReadLE24s(MemBuffer* const mem) {
|
|
const uint8_t* const data = mem->buf + mem->start;
|
|
const int val = GetLE24(data);
|
|
Skip(mem, 3);
|
|
return val;
|
|
}
|
|
|
|
static WEBP_INLINE uint32_t ReadLE32(MemBuffer* const mem) {
|
|
const uint8_t* const data = mem->buf + mem->start;
|
|
const uint32_t val = GetLE32(data);
|
|
Skip(mem, 4);
|
|
return val;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Secondary chunk parsing
|
|
|
|
static void AddChunk(WebPDemuxer* const dmux, Chunk* const chunk) {
|
|
*dmux->chunks_tail = chunk;
|
|
chunk->next = NULL;
|
|
dmux->chunks_tail = &chunk->next;
|
|
}
|
|
|
|
// Add a frame to the end of the list, ensuring the last frame is complete.
|
|
// Returns true on success, false otherwise.
|
|
static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) {
|
|
const Frame* const last_frame = *dmux->frames_tail;
|
|
if (last_frame != NULL && !last_frame->complete) return 0;
|
|
|
|
*dmux->frames_tail = frame;
|
|
frame->next = NULL;
|
|
dmux->frames_tail = &frame->next;
|
|
return 1;
|
|
}
|
|
|
|
static void SetFrameInfo(size_t start_offset, size_t size, int frame_num,
|
|
int complete,
|
|
const WebPBitstreamFeatures* const features,
|
|
Frame* const frame) {
|
|
frame->img_components[0].offset = start_offset;
|
|
frame->img_components[0].size = size;
|
|
frame->width = features->width;
|
|
frame->height = features->height;
|
|
frame->has_alpha |= features->has_alpha;
|
|
frame->frame_num = frame_num;
|
|
frame->complete = complete;
|
|
}
|
|
|
|
// Store image bearing chunks to 'frame'. 'min_size' is an optional size
|
|
// requirement, it may be zero.
|
|
static ParseStatus StoreFrame(int frame_num, uint32_t min_size,
|
|
MemBuffer* const mem, Frame* const frame) {
|
|
int alpha_chunks = 0;
|
|
int image_chunks = 0;
|
|
int done =
|
|
(MemDataSize(mem) < CHUNK_HEADER_SIZE || MemDataSize(mem) < min_size);
|
|
ParseStatus status = PARSE_OK;
|
|
|
|
if (done) return PARSE_NEED_MORE_DATA;
|
|
|
|
do {
|
|
const size_t chunk_start_offset = mem->start;
|
|
const uint32_t fourcc = ReadLE32(mem);
|
|
const uint32_t payload_size = ReadLE32(mem);
|
|
uint32_t payload_size_padded;
|
|
size_t payload_available;
|
|
size_t chunk_size;
|
|
|
|
if (payload_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR;
|
|
|
|
payload_size_padded = payload_size + (payload_size & 1);
|
|
payload_available = (payload_size_padded > MemDataSize(mem))
|
|
? MemDataSize(mem)
|
|
: payload_size_padded;
|
|
chunk_size = CHUNK_HEADER_SIZE + payload_available;
|
|
if (SizeIsInvalid(mem, payload_size_padded)) return PARSE_ERROR;
|
|
if (payload_size_padded > MemDataSize(mem)) status = PARSE_NEED_MORE_DATA;
|
|
|
|
switch (fourcc) {
|
|
case MKFOURCC('A', 'L', 'P', 'H'):
|
|
if (alpha_chunks == 0) {
|
|
++alpha_chunks;
|
|
frame->img_components[1].offset = chunk_start_offset;
|
|
frame->img_components[1].size = chunk_size;
|
|
frame->has_alpha = 1;
|
|
frame->frame_num = frame_num;
|
|
Skip(mem, payload_available);
|
|
} else {
|
|
goto Done;
|
|
}
|
|
break;
|
|
case MKFOURCC('V', 'P', '8', 'L'):
|
|
if (alpha_chunks > 0) return PARSE_ERROR; // VP8L has its own alpha
|
|
// fall through
|
|
case MKFOURCC('V', 'P', '8', ' '):
|
|
if (image_chunks == 0) {
|
|
// Extract the bitstream features, tolerating failures when the data
|
|
// is incomplete.
|
|
WebPBitstreamFeatures features;
|
|
const VP8StatusCode vp8_status = WebPGetFeatures(
|
|
mem->buf + chunk_start_offset, chunk_size, &features);
|
|
if (status == PARSE_NEED_MORE_DATA &&
|
|
vp8_status == VP8_STATUS_NOT_ENOUGH_DATA) {
|
|
return PARSE_NEED_MORE_DATA;
|
|
} else if (vp8_status != VP8_STATUS_OK) {
|
|
// We have enough data, and yet WebPGetFeatures() failed.
|
|
return PARSE_ERROR;
|
|
}
|
|
++image_chunks;
|
|
SetFrameInfo(chunk_start_offset, chunk_size, frame_num,
|
|
status == PARSE_OK, &features, frame);
|
|
Skip(mem, payload_available);
|
|
} else {
|
|
goto Done;
|
|
}
|
|
break;
|
|
Done:
|
|
default:
|
|
// Restore fourcc/size when moving up one level in parsing.
|
|
Rewind(mem, CHUNK_HEADER_SIZE);
|
|
done = 1;
|
|
break;
|
|
}
|
|
|
|
if (mem->start == mem->riff_end) {
|
|
done = 1;
|
|
} else if (MemDataSize(mem) < CHUNK_HEADER_SIZE) {
|
|
status = PARSE_NEED_MORE_DATA;
|
|
}
|
|
} while (!done && status == PARSE_OK);
|
|
|
|
return status;
|
|
}
|
|
|
|
// Creates a new Frame if 'actual_size' is within bounds and 'mem' contains
|
|
// enough data ('min_size') to parse the payload.
|
|
// Returns PARSE_OK on success with *frame pointing to the new Frame.
|
|
// Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise.
|
|
static ParseStatus NewFrame(const MemBuffer* const mem, uint32_t min_size,
|
|
uint32_t actual_size, Frame** frame) {
|
|
if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR;
|
|
if (actual_size < min_size) return PARSE_ERROR;
|
|
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
|
|
|
|
*frame = (Frame*)WebPSafeCalloc(1ULL, sizeof(**frame));
|
|
return (*frame == NULL) ? PARSE_ERROR : PARSE_OK;
|
|
}
|
|
|
|
// Parse a 'ANMF' chunk and any image bearing chunks that immediately follow.
|
|
// 'frame_chunk_size' is the previously validated, padded chunk size.
|
|
static ParseStatus ParseAnimationFrame(WebPDemuxer* const dmux,
|
|
uint32_t frame_chunk_size) {
|
|
const int is_animation = !!(dmux->feature_flags & ANIMATION_FLAG);
|
|
int added_frame = 0;
|
|
int bits;
|
|
MemBuffer* const mem = &dmux->mem;
|
|
Frame* frame;
|
|
size_t start_offset;
|
|
ParseStatus status = NewFrame(mem, ANMF_CHUNK_SIZE, frame_chunk_size, &frame);
|
|
if (status != PARSE_OK) return status;
|
|
|
|
frame->x_offset = 2 * ReadLE24s(mem);
|
|
frame->y_offset = 2 * ReadLE24s(mem);
|
|
frame->width = 1 + ReadLE24s(mem);
|
|
frame->height = 1 + ReadLE24s(mem);
|
|
frame->duration = ReadLE24s(mem);
|
|
bits = ReadByte(mem);
|
|
frame->dispose_method =
|
|
(bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE;
|
|
frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND;
|
|
if (frame->width * (uint64_t)frame->height >= MAX_IMAGE_AREA) {
|
|
WebPSafeFree(frame);
|
|
return PARSE_ERROR;
|
|
}
|
|
|
|
// Store a frame only if the animation flag is set there is some data for
|
|
// this frame is available.
|
|
start_offset = mem->start;
|
|
{
|
|
const uint32_t anmf_payload_size = frame_chunk_size - ANMF_CHUNK_SIZE;
|
|
status = StoreFrame(dmux->num_frames + 1, anmf_payload_size, mem, frame);
|
|
if (status != PARSE_ERROR &&
|
|
mem->start - start_offset > anmf_payload_size) {
|
|
status = PARSE_ERROR;
|
|
}
|
|
}
|
|
if (status != PARSE_ERROR && is_animation && frame->frame_num > 0) {
|
|
added_frame = AddFrame(dmux, frame);
|
|
if (added_frame) {
|
|
++dmux->num_frames;
|
|
} else {
|
|
status = PARSE_ERROR;
|
|
}
|
|
}
|
|
|
|
if (!added_frame) WebPSafeFree(frame);
|
|
return status;
|
|
}
|
|
|
|
// General chunk storage, starting with the header at 'start_offset', allowing
|
|
// the user to request the payload via a fourcc string. 'size' includes the
|
|
// header and the unpadded payload size.
|
|
// Returns true on success, false otherwise.
|
|
static int StoreChunk(WebPDemuxer* const dmux, size_t start_offset,
|
|
uint32_t size) {
|
|
Chunk* const chunk = (Chunk*)WebPSafeCalloc(1ULL, sizeof(*chunk));
|
|
if (chunk == NULL) return 0;
|
|
|
|
chunk->data.offset = start_offset;
|
|
chunk->data.size = size;
|
|
AddChunk(dmux, chunk);
|
|
return 1;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Primary chunk parsing
|
|
|
|
static ParseStatus ReadHeader(MemBuffer* const mem) {
|
|
const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE;
|
|
uint32_t riff_size;
|
|
|
|
// Basic file level validation.
|
|
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
|
|
if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) ||
|
|
memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) {
|
|
return PARSE_ERROR;
|
|
}
|
|
|
|
riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE);
|
|
if (riff_size < CHUNK_HEADER_SIZE) return PARSE_ERROR;
|
|
if (riff_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR;
|
|
|
|
// There's no point in reading past the end of the RIFF chunk
|
|
mem->riff_end = riff_size + CHUNK_HEADER_SIZE;
|
|
if (mem->buf_size > mem->riff_end) {
|
|
mem->buf_size = mem->end = mem->riff_end;
|
|
}
|
|
|
|
Skip(mem, RIFF_HEADER_SIZE);
|
|
return PARSE_OK;
|
|
}
|
|
|
|
static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) {
|
|
const size_t min_size = CHUNK_HEADER_SIZE;
|
|
MemBuffer* const mem = &dmux->mem;
|
|
Frame* frame;
|
|
ParseStatus status;
|
|
int image_added = 0;
|
|
|
|
if (dmux->frames != NULL) return PARSE_ERROR;
|
|
if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR;
|
|
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
|
|
|
|
frame = (Frame*)WebPSafeCalloc(1ULL, sizeof(*frame));
|
|
if (frame == NULL) return PARSE_ERROR;
|
|
|
|
// For the single image case we allow parsing of a partial frame, so no
|
|
// minimum size is imposed here.
|
|
status = StoreFrame(1, 0, &dmux->mem, frame);
|
|
if (status != PARSE_ERROR) {
|
|
const int has_alpha = !!(dmux->feature_flags & ALPHA_FLAG);
|
|
// Clear any alpha when the alpha flag is missing.
|
|
if (!has_alpha && frame->img_components[1].size > 0) {
|
|
frame->img_components[1].offset = 0;
|
|
frame->img_components[1].size = 0;
|
|
frame->has_alpha = 0;
|
|
}
|
|
|
|
// Use the frame width/height as the canvas values for non-vp8x files.
|
|
// Also, set ALPHA_FLAG if this is a lossless image with alpha.
|
|
if (!dmux->is_ext_format && frame->width > 0 && frame->height > 0) {
|
|
dmux->state = WEBP_DEMUX_PARSED_HEADER;
|
|
dmux->canvas_width = frame->width;
|
|
dmux->canvas_height = frame->height;
|
|
dmux->feature_flags |= frame->has_alpha ? ALPHA_FLAG : 0;
|
|
}
|
|
if (!AddFrame(dmux, frame)) {
|
|
status = PARSE_ERROR; // last frame was left incomplete
|
|
} else {
|
|
image_added = 1;
|
|
dmux->num_frames = 1;
|
|
}
|
|
}
|
|
|
|
if (!image_added) WebPSafeFree(frame);
|
|
return status;
|
|
}
|
|
|
|
static ParseStatus ParseVP8XChunks(WebPDemuxer* const dmux) {
|
|
const int is_animation = !!(dmux->feature_flags & ANIMATION_FLAG);
|
|
MemBuffer* const mem = &dmux->mem;
|
|
int anim_chunks = 0;
|
|
ParseStatus status = PARSE_OK;
|
|
|
|
do {
|
|
int store_chunk = 1;
|
|
const size_t chunk_start_offset = mem->start;
|
|
const uint32_t fourcc = ReadLE32(mem);
|
|
const uint32_t chunk_size = ReadLE32(mem);
|
|
uint32_t chunk_size_padded;
|
|
|
|
if (chunk_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR;
|
|
|
|
chunk_size_padded = chunk_size + (chunk_size & 1);
|
|
if (SizeIsInvalid(mem, chunk_size_padded)) return PARSE_ERROR;
|
|
|
|
switch (fourcc) {
|
|
case MKFOURCC('V', 'P', '8', 'X'): {
|
|
return PARSE_ERROR;
|
|
}
|
|
case MKFOURCC('A', 'L', 'P', 'H'):
|
|
case MKFOURCC('V', 'P', '8', ' '):
|
|
case MKFOURCC('V', 'P', '8', 'L'): {
|
|
// check that this isn't an animation (all frames should be in an ANMF).
|
|
if (anim_chunks > 0 || is_animation) return PARSE_ERROR;
|
|
|
|
Rewind(mem, CHUNK_HEADER_SIZE);
|
|
status = ParseSingleImage(dmux);
|
|
break;
|
|
}
|
|
case MKFOURCC('A', 'N', 'I', 'M'): {
|
|
if (chunk_size_padded < ANIM_CHUNK_SIZE) return PARSE_ERROR;
|
|
|
|
if (MemDataSize(mem) < chunk_size_padded) {
|
|
status = PARSE_NEED_MORE_DATA;
|
|
} else if (anim_chunks == 0) {
|
|
++anim_chunks;
|
|
dmux->bgcolor = ReadLE32(mem);
|
|
dmux->loop_count = ReadLE16s(mem);
|
|
Skip(mem, chunk_size_padded - ANIM_CHUNK_SIZE);
|
|
} else {
|
|
store_chunk = 0;
|
|
goto Skip;
|
|
}
|
|
break;
|
|
}
|
|
case MKFOURCC('A', 'N', 'M', 'F'): {
|
|
if (anim_chunks == 0) return PARSE_ERROR; // 'ANIM' precedes frames.
|
|
status = ParseAnimationFrame(dmux, chunk_size_padded);
|
|
break;
|
|
}
|
|
case MKFOURCC('I', 'C', 'C', 'P'): {
|
|
store_chunk = !!(dmux->feature_flags & ICCP_FLAG);
|
|
goto Skip;
|
|
}
|
|
case MKFOURCC('E', 'X', 'I', 'F'): {
|
|
store_chunk = !!(dmux->feature_flags & EXIF_FLAG);
|
|
goto Skip;
|
|
}
|
|
case MKFOURCC('X', 'M', 'P', ' '): {
|
|
store_chunk = !!(dmux->feature_flags & XMP_FLAG);
|
|
goto Skip;
|
|
}
|
|
Skip:
|
|
default: {
|
|
if (chunk_size_padded <= MemDataSize(mem)) {
|
|
if (store_chunk) {
|
|
// Store only the chunk header and unpadded size as only the payload
|
|
// will be returned to the user.
|
|
if (!StoreChunk(dmux, chunk_start_offset,
|
|
CHUNK_HEADER_SIZE + chunk_size)) {
|
|
return PARSE_ERROR;
|
|
}
|
|
}
|
|
Skip(mem, chunk_size_padded);
|
|
} else {
|
|
status = PARSE_NEED_MORE_DATA;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mem->start == mem->riff_end) {
|
|
break;
|
|
} else if (MemDataSize(mem) < CHUNK_HEADER_SIZE) {
|
|
status = PARSE_NEED_MORE_DATA;
|
|
}
|
|
} while (status == PARSE_OK);
|
|
|
|
return status;
|
|
}
|
|
|
|
static ParseStatus ParseVP8X(WebPDemuxer* const dmux) {
|
|
MemBuffer* const mem = &dmux->mem;
|
|
uint32_t vp8x_size;
|
|
|
|
if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA;
|
|
|
|
dmux->is_ext_format = 1;
|
|
Skip(mem, TAG_SIZE); // VP8X
|
|
vp8x_size = ReadLE32(mem);
|
|
if (vp8x_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR;
|
|
if (vp8x_size < VP8X_CHUNK_SIZE) return PARSE_ERROR;
|
|
vp8x_size += vp8x_size & 1;
|
|
if (SizeIsInvalid(mem, vp8x_size)) return PARSE_ERROR;
|
|
if (MemDataSize(mem) < vp8x_size) return PARSE_NEED_MORE_DATA;
|
|
|
|
dmux->feature_flags = ReadByte(mem);
|
|
Skip(mem, 3); // Reserved.
|
|
dmux->canvas_width = 1 + ReadLE24s(mem);
|
|
dmux->canvas_height = 1 + ReadLE24s(mem);
|
|
if (dmux->canvas_width * (uint64_t)dmux->canvas_height >= MAX_IMAGE_AREA) {
|
|
return PARSE_ERROR; // image final dimension is too large
|
|
}
|
|
Skip(mem, vp8x_size - VP8X_CHUNK_SIZE); // skip any trailing data.
|
|
dmux->state = WEBP_DEMUX_PARSED_HEADER;
|
|
|
|
if (SizeIsInvalid(mem, CHUNK_HEADER_SIZE)) return PARSE_ERROR;
|
|
if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA;
|
|
|
|
return ParseVP8XChunks(dmux);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Format validation
|
|
|
|
static int IsValidSimpleFormat(const WebPDemuxer* const dmux) {
|
|
const Frame* const frame = dmux->frames;
|
|
if (dmux->state == WEBP_DEMUX_PARSING_HEADER) return 1;
|
|
|
|
if (dmux->canvas_width <= 0 || dmux->canvas_height <= 0) return 0;
|
|
if (dmux->state == WEBP_DEMUX_DONE && frame == NULL) return 0;
|
|
|
|
if (frame->width <= 0 || frame->height <= 0) return 0;
|
|
return 1;
|
|
}
|
|
|
|
// If 'exact' is true, check that the image resolution matches the canvas.
|
|
// If 'exact' is false, check that the x/y offsets do not exceed the canvas.
|
|
static int CheckFrameBounds(const Frame* const frame, int exact,
|
|
int canvas_width, int canvas_height) {
|
|
if (exact) {
|
|
if (frame->x_offset != 0 || frame->y_offset != 0) {
|
|
return 0;
|
|
}
|
|
if (frame->width != canvas_width || frame->height != canvas_height) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (frame->x_offset < 0 || frame->y_offset < 0) return 0;
|
|
if (frame->width + frame->x_offset > canvas_width) return 0;
|
|
if (frame->height + frame->y_offset > canvas_height) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int IsValidExtendedFormat(const WebPDemuxer* const dmux) {
|
|
const int is_animation = !!(dmux->feature_flags & ANIMATION_FLAG);
|
|
const Frame* f = dmux->frames;
|
|
|
|
if (dmux->state == WEBP_DEMUX_PARSING_HEADER) return 1;
|
|
|
|
if (dmux->canvas_width <= 0 || dmux->canvas_height <= 0) return 0;
|
|
if (dmux->loop_count < 0) return 0;
|
|
if (dmux->state == WEBP_DEMUX_DONE && dmux->frames == NULL) return 0;
|
|
if (dmux->feature_flags & ~ALL_VALID_FLAGS) return 0; // invalid bitstream
|
|
|
|
while (f != NULL) {
|
|
const int cur_frame_set = f->frame_num;
|
|
|
|
// Check frame properties.
|
|
for (; f != NULL && f->frame_num == cur_frame_set; f = f->next) {
|
|
const ChunkData* const image = f->img_components;
|
|
const ChunkData* const alpha = f->img_components + 1;
|
|
|
|
if (!is_animation && f->frame_num > 1) return 0;
|
|
|
|
if (f->complete) {
|
|
if (alpha->size == 0 && image->size == 0) return 0;
|
|
// Ensure alpha precedes image bitstream.
|
|
if (alpha->size > 0 && alpha->offset > image->offset) {
|
|
return 0;
|
|
}
|
|
|
|
if (f->width <= 0 || f->height <= 0) return 0;
|
|
} else {
|
|
// There shouldn't be a partial frame in a complete file.
|
|
if (dmux->state == WEBP_DEMUX_DONE) return 0;
|
|
|
|
// Ensure alpha precedes image bitstream.
|
|
if (alpha->size > 0 && image->size > 0 &&
|
|
alpha->offset > image->offset) {
|
|
return 0;
|
|
}
|
|
// There shouldn't be any frames after an incomplete one.
|
|
if (f->next != NULL) return 0;
|
|
}
|
|
|
|
if (f->width > 0 && f->height > 0 &&
|
|
!CheckFrameBounds(f, !is_animation, dmux->canvas_width,
|
|
dmux->canvas_height)) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// WebPDemuxer object
|
|
|
|
static void InitDemux(WebPDemuxer* const dmux, const MemBuffer* const mem) {
|
|
dmux->state = WEBP_DEMUX_PARSING_HEADER;
|
|
dmux->loop_count = 1;
|
|
dmux->bgcolor = 0xFFFFFFFF; // White background by default.
|
|
dmux->canvas_width = -1;
|
|
dmux->canvas_height = -1;
|
|
dmux->frames_tail = &dmux->frames;
|
|
dmux->chunks_tail = &dmux->chunks;
|
|
dmux->mem = *mem;
|
|
}
|
|
|
|
static ParseStatus CreateRawImageDemuxer(MemBuffer* const mem,
|
|
WebPDemuxer** demuxer) {
|
|
WebPBitstreamFeatures features;
|
|
const VP8StatusCode status =
|
|
WebPGetFeatures(mem->buf, mem->buf_size, &features);
|
|
*demuxer = NULL;
|
|
if (status != VP8_STATUS_OK) {
|
|
return (status == VP8_STATUS_NOT_ENOUGH_DATA) ? PARSE_NEED_MORE_DATA
|
|
: PARSE_ERROR;
|
|
}
|
|
|
|
{
|
|
WebPDemuxer* const dmux = (WebPDemuxer*)WebPSafeCalloc(1ULL, sizeof(*dmux));
|
|
Frame* const frame = (Frame*)WebPSafeCalloc(1ULL, sizeof(*frame));
|
|
if (dmux == NULL || frame == NULL) goto Error;
|
|
InitDemux(dmux, mem);
|
|
SetFrameInfo(0, mem->buf_size, 1 /*frame_num*/, 1 /*complete*/, &features,
|
|
frame);
|
|
if (!AddFrame(dmux, frame)) goto Error;
|
|
dmux->state = WEBP_DEMUX_DONE;
|
|
dmux->canvas_width = frame->width;
|
|
dmux->canvas_height = frame->height;
|
|
dmux->feature_flags |= frame->has_alpha ? ALPHA_FLAG : 0;
|
|
dmux->num_frames = 1;
|
|
assert(IsValidSimpleFormat(dmux));
|
|
*demuxer = dmux;
|
|
return PARSE_OK;
|
|
|
|
Error:
|
|
WebPSafeFree(dmux);
|
|
WebPSafeFree(frame);
|
|
return PARSE_ERROR;
|
|
}
|
|
}
|
|
|
|
WebPDemuxer* WebPDemuxInternal(const WebPData* data, int allow_partial,
|
|
WebPDemuxState* state, int version) {
|
|
const ChunkParser* parser;
|
|
int partial;
|
|
ParseStatus status = PARSE_ERROR;
|
|
MemBuffer mem;
|
|
WebPDemuxer* dmux;
|
|
|
|
if (state != NULL) *state = WEBP_DEMUX_PARSE_ERROR;
|
|
|
|
if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DEMUX_ABI_VERSION)) return NULL;
|
|
if (data == NULL || data->bytes == NULL || data->size == 0) return NULL;
|
|
|
|
if (!InitMemBuffer(&mem, data->bytes, data->size)) return NULL;
|
|
status = ReadHeader(&mem);
|
|
if (status != PARSE_OK) {
|
|
// If parsing of the webp file header fails attempt to handle a raw
|
|
// VP8/VP8L frame. Note 'allow_partial' is ignored in this case.
|
|
if (status == PARSE_ERROR) {
|
|
status = CreateRawImageDemuxer(&mem, &dmux);
|
|
if (status == PARSE_OK) {
|
|
if (state != NULL) *state = WEBP_DEMUX_DONE;
|
|
return dmux;
|
|
}
|
|
}
|
|
if (state != NULL) {
|
|
*state = (status == PARSE_NEED_MORE_DATA) ? WEBP_DEMUX_PARSING_HEADER
|
|
: WEBP_DEMUX_PARSE_ERROR;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
partial = (mem.buf_size < mem.riff_end);
|
|
if (!allow_partial && partial) return NULL;
|
|
|
|
dmux = (WebPDemuxer*)WebPSafeCalloc(1ULL, sizeof(*dmux));
|
|
if (dmux == NULL) return NULL;
|
|
InitDemux(dmux, &mem);
|
|
|
|
status = PARSE_ERROR;
|
|
for (parser = kMasterChunks; parser->parse != NULL; ++parser) {
|
|
if (!memcmp(parser->id, GetBuffer(&dmux->mem), TAG_SIZE)) {
|
|
status = parser->parse(dmux);
|
|
if (status == PARSE_OK) dmux->state = WEBP_DEMUX_DONE;
|
|
if (status == PARSE_NEED_MORE_DATA && !partial) status = PARSE_ERROR;
|
|
if (status != PARSE_ERROR && !parser->valid(dmux)) status = PARSE_ERROR;
|
|
if (status == PARSE_ERROR) dmux->state = WEBP_DEMUX_PARSE_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
if (state != NULL) *state = dmux->state;
|
|
|
|
if (status == PARSE_ERROR) {
|
|
WebPDemuxDelete(dmux);
|
|
return NULL;
|
|
}
|
|
return dmux;
|
|
}
|
|
|
|
void WebPDemuxDelete(WebPDemuxer* dmux) {
|
|
Chunk* c;
|
|
Frame* f;
|
|
if (dmux == NULL) return;
|
|
|
|
for (f = dmux->frames; f != NULL;) {
|
|
Frame* const cur_frame = f;
|
|
f = f->next;
|
|
WebPSafeFree(cur_frame);
|
|
}
|
|
for (c = dmux->chunks; c != NULL;) {
|
|
Chunk* const cur_chunk = c;
|
|
c = c->next;
|
|
WebPSafeFree(cur_chunk);
|
|
}
|
|
WebPSafeFree(dmux);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
uint32_t WebPDemuxGetI(const WebPDemuxer* dmux, WebPFormatFeature feature) {
|
|
if (dmux == NULL) return 0;
|
|
|
|
switch (feature) {
|
|
case WEBP_FF_FORMAT_FLAGS:
|
|
return dmux->feature_flags;
|
|
case WEBP_FF_CANVAS_WIDTH:
|
|
return (uint32_t)dmux->canvas_width;
|
|
case WEBP_FF_CANVAS_HEIGHT:
|
|
return (uint32_t)dmux->canvas_height;
|
|
case WEBP_FF_LOOP_COUNT:
|
|
return (uint32_t)dmux->loop_count;
|
|
case WEBP_FF_BACKGROUND_COLOR:
|
|
return dmux->bgcolor;
|
|
case WEBP_FF_FRAME_COUNT:
|
|
return (uint32_t)dmux->num_frames;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Frame iteration
|
|
|
|
static const Frame* GetFrame(const WebPDemuxer* const dmux, int frame_num) {
|
|
const Frame* f;
|
|
for (f = dmux->frames; f != NULL; f = f->next) {
|
|
if (frame_num == f->frame_num) break;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
static const uint8_t* GetFramePayload(const uint8_t* const mem_buf,
|
|
const Frame* const frame,
|
|
size_t* const data_size) {
|
|
*data_size = 0;
|
|
if (frame != NULL) {
|
|
const ChunkData* const image = frame->img_components;
|
|
const ChunkData* const alpha = frame->img_components + 1;
|
|
size_t start_offset = image->offset;
|
|
*data_size = image->size;
|
|
|
|
// if alpha exists it precedes image, update the size allowing for
|
|
// intervening chunks.
|
|
if (alpha->size > 0) {
|
|
const size_t inter_size =
|
|
(image->offset > 0) ? image->offset - (alpha->offset + alpha->size)
|
|
: 0;
|
|
start_offset = alpha->offset;
|
|
*data_size += alpha->size + inter_size;
|
|
}
|
|
return mem_buf + start_offset;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Create a whole 'frame' from VP8 (+ alpha) or lossless.
|
|
static int SynthesizeFrame(const WebPDemuxer* const dmux,
|
|
const Frame* const frame, WebPIterator* const iter) {
|
|
const uint8_t* const mem_buf = dmux->mem.buf;
|
|
size_t payload_size = 0;
|
|
const uint8_t* const payload = GetFramePayload(mem_buf, frame, &payload_size);
|
|
if (payload == NULL) return 0;
|
|
assert(frame != NULL);
|
|
|
|
iter->frame_num = frame->frame_num;
|
|
iter->num_frames = dmux->num_frames;
|
|
iter->x_offset = frame->x_offset;
|
|
iter->y_offset = frame->y_offset;
|
|
iter->width = frame->width;
|
|
iter->height = frame->height;
|
|
iter->has_alpha = frame->has_alpha;
|
|
iter->duration = frame->duration;
|
|
iter->dispose_method = frame->dispose_method;
|
|
iter->blend_method = frame->blend_method;
|
|
iter->complete = frame->complete;
|
|
iter->fragment.bytes = payload;
|
|
iter->fragment.size = payload_size;
|
|
return 1;
|
|
}
|
|
|
|
static int SetFrame(int frame_num, WebPIterator* const iter) {
|
|
const Frame* frame;
|
|
const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_;
|
|
if (dmux == NULL || frame_num < 0) return 0;
|
|
if (frame_num > dmux->num_frames) return 0;
|
|
if (frame_num == 0) frame_num = dmux->num_frames;
|
|
|
|
frame = GetFrame(dmux, frame_num);
|
|
if (frame == NULL) return 0;
|
|
|
|
return SynthesizeFrame(dmux, frame, iter);
|
|
}
|
|
|
|
int WebPDemuxGetFrame(const WebPDemuxer* dmux, int frame, WebPIterator* iter) {
|
|
if (iter == NULL) return 0;
|
|
|
|
WEBP_UNSAFE_MEMSET(iter, 0, sizeof(*iter));
|
|
iter->private_ = (void*)dmux;
|
|
return SetFrame(frame, iter);
|
|
}
|
|
|
|
int WebPDemuxNextFrame(WebPIterator* iter) {
|
|
if (iter == NULL) return 0;
|
|
return SetFrame(iter->frame_num + 1, iter);
|
|
}
|
|
|
|
int WebPDemuxPrevFrame(WebPIterator* iter) {
|
|
if (iter == NULL) return 0;
|
|
if (iter->frame_num <= 1) return 0;
|
|
return SetFrame(iter->frame_num - 1, iter);
|
|
}
|
|
|
|
void WebPDemuxReleaseIterator(WebPIterator* iter) { (void)iter; }
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Chunk iteration
|
|
|
|
static int ChunkCount(const WebPDemuxer* const dmux, const char fourcc[4]) {
|
|
const uint8_t* const mem_buf = dmux->mem.buf;
|
|
const Chunk* c;
|
|
int count = 0;
|
|
for (c = dmux->chunks; c != NULL; c = c->next) {
|
|
const uint8_t* const header = mem_buf + c->data.offset;
|
|
if (!memcmp(header, fourcc, TAG_SIZE)) ++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static const Chunk* GetChunk(const WebPDemuxer* const dmux,
|
|
const char fourcc[4], int chunk_num) {
|
|
const uint8_t* const mem_buf = dmux->mem.buf;
|
|
const Chunk* c;
|
|
int count = 0;
|
|
for (c = dmux->chunks; c != NULL; c = c->next) {
|
|
const uint8_t* const header = mem_buf + c->data.offset;
|
|
if (!memcmp(header, fourcc, TAG_SIZE)) ++count;
|
|
if (count == chunk_num) break;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static int SetChunk(const char fourcc[4], int chunk_num,
|
|
WebPChunkIterator* const iter) {
|
|
const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_;
|
|
int count;
|
|
|
|
if (dmux == NULL || fourcc == NULL || chunk_num < 0) return 0;
|
|
count = ChunkCount(dmux, fourcc);
|
|
if (count == 0) return 0;
|
|
if (chunk_num == 0) chunk_num = count;
|
|
|
|
if (chunk_num <= count) {
|
|
const uint8_t* const mem_buf = dmux->mem.buf;
|
|
const Chunk* const chunk = GetChunk(dmux, fourcc, chunk_num);
|
|
iter->chunk.bytes = mem_buf + chunk->data.offset + CHUNK_HEADER_SIZE;
|
|
iter->chunk.size = chunk->data.size - CHUNK_HEADER_SIZE;
|
|
iter->num_chunks = count;
|
|
iter->chunk_num = chunk_num;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int WebPDemuxGetChunk(const WebPDemuxer* dmux, const char fourcc[4],
|
|
int chunk_num, WebPChunkIterator* iter) {
|
|
if (iter == NULL) return 0;
|
|
|
|
WEBP_UNSAFE_MEMSET(iter, 0, sizeof(*iter));
|
|
iter->private_ = (void*)dmux;
|
|
return SetChunk(fourcc, chunk_num, iter);
|
|
}
|
|
|
|
int WebPDemuxNextChunk(WebPChunkIterator* iter) {
|
|
if (iter != NULL) {
|
|
const char* const fourcc =
|
|
(const char*)iter->chunk.bytes - CHUNK_HEADER_SIZE;
|
|
return SetChunk(fourcc, iter->chunk_num + 1, iter);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int WebPDemuxPrevChunk(WebPChunkIterator* iter) {
|
|
if (iter != NULL && iter->chunk_num > 1) {
|
|
const char* const fourcc =
|
|
(const char*)iter->chunk.bytes - CHUNK_HEADER_SIZE;
|
|
return SetChunk(fourcc, iter->chunk_num - 1, iter);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter) { (void)iter; }
|
|
#undef MemBuffer
|
|
#undef InitMemBuffer
|
|
#undef MemDataSize
|
|
#undef RemapMemBuffer
|
|
#undef ParseVP8X
|
|
/* >>> src/demux/anim_decode.c */
|
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// AnimDecoder implementation.
|
|
//
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
|
|
WEBP_ASSUME_UNSAFE_INDEXABLE_ABI
|
|
|
|
#define NUM_CHANNELS 4
|
|
|
|
// Channel extraction from a uint32_t representation of a uint8_t RGBA/BGRA
|
|
// buffer.
|
|
#ifdef WORDS_BIGENDIAN
|
|
#define CHANNEL_SHIFT(i) (24 - (i) * 8)
|
|
#else
|
|
#define CHANNEL_SHIFT(i) ((i) * 8)
|
|
#endif
|
|
|
|
typedef void (*BlendRowFunc)(uint32_t* const, const uint32_t* const, int);
|
|
static void BlendPixelRowNonPremult(uint32_t* const src,
|
|
const uint32_t* const dst, int num_pixels);
|
|
static void BlendPixelRowPremult(uint32_t* const src, const uint32_t* const dst,
|
|
int num_pixels);
|
|
|
|
struct WebPAnimDecoder {
|
|
WebPDemuxer* demux; // Demuxer created from given WebP bitstream.
|
|
WebPDecoderConfig config; // Decoder config.
|
|
// Note: we use a pointer to a function blending multiple pixels at a time to
|
|
// allow possible inlining of per-pixel blending function.
|
|
BlendRowFunc blend_func; // Pointer to the chose blend row function.
|
|
WebPAnimInfo info; // Global info about the animation.
|
|
uint8_t* curr_frame; // Current canvas (not disposed).
|
|
uint8_t* prev_frame_disposed; // Previous canvas (properly disposed).
|
|
int prev_frame_timestamp; // Previous frame timestamp (milliseconds).
|
|
WebPIterator prev_iter; // Iterator object for previous frame.
|
|
int prev_frame_was_keyframe; // True if previous frame was a keyframe.
|
|
int next_frame; // Index of the next frame to be decoded
|
|
// (starting from 1).
|
|
};
|
|
|
|
static void DefaultDecoderOptions(WebPAnimDecoderOptions* const dec_options) {
|
|
dec_options->color_mode = MODE_RGBA;
|
|
dec_options->use_threads = 0;
|
|
}
|
|
|
|
int WebPAnimDecoderOptionsInitInternal(WebPAnimDecoderOptions* dec_options,
|
|
int abi_version) {
|
|
if (dec_options == NULL ||
|
|
WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_DEMUX_ABI_VERSION)) {
|
|
return 0;
|
|
}
|
|
DefaultDecoderOptions(dec_options);
|
|
return 1;
|
|
}
|
|
|
|
WEBP_NODISCARD static int ApplyDecoderOptions(
|
|
const WebPAnimDecoderOptions* const dec_options,
|
|
WebPAnimDecoder* const dec) {
|
|
WEBP_CSP_MODE mode;
|
|
WebPDecoderConfig* config = &dec->config;
|
|
assert(dec_options != NULL);
|
|
|
|
mode = dec_options->color_mode;
|
|
if (mode != MODE_RGBA && mode != MODE_BGRA && mode != MODE_rgbA &&
|
|
mode != MODE_bgrA) {
|
|
return 0;
|
|
}
|
|
dec->blend_func = (mode == MODE_RGBA || mode == MODE_BGRA)
|
|
? &BlendPixelRowNonPremult
|
|
: &BlendPixelRowPremult;
|
|
if (!WebPInitDecoderConfig(config)) {
|
|
return 0;
|
|
}
|
|
config->output.colorspace = mode;
|
|
config->output.is_external_memory = 1;
|
|
config->options.use_threads = dec_options->use_threads;
|
|
// Note: config->output.u.RGBA is set at the time of decoding each frame.
|
|
return 1;
|
|
}
|
|
|
|
WebPAnimDecoder* WebPAnimDecoderNewInternal(
|
|
const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options,
|
|
int abi_version) {
|
|
WebPAnimDecoderOptions options;
|
|
WebPAnimDecoder* dec = NULL;
|
|
WebPBitstreamFeatures features;
|
|
if (webp_data == NULL ||
|
|
WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_DEMUX_ABI_VERSION)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Validate the bitstream before doing expensive allocations. The demuxer may
|
|
// be more tolerant than the decoder.
|
|
if (WebPGetFeatures(webp_data->bytes, webp_data->size, &features) !=
|
|
VP8_STATUS_OK) {
|
|
return NULL;
|
|
}
|
|
|
|
// Note: calloc() so that the pointer members are initialized to NULL.
|
|
dec = (WebPAnimDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec));
|
|
if (dec == NULL) goto Error;
|
|
|
|
if (dec_options != NULL) {
|
|
options = *dec_options;
|
|
} else {
|
|
DefaultDecoderOptions(&options);
|
|
}
|
|
if (!ApplyDecoderOptions(&options, dec)) goto Error;
|
|
|
|
dec->demux = WebPDemux(webp_data);
|
|
if (dec->demux == NULL) goto Error;
|
|
|
|
dec->info.canvas_width = WebPDemuxGetI(dec->demux, WEBP_FF_CANVAS_WIDTH);
|
|
dec->info.canvas_height = WebPDemuxGetI(dec->demux, WEBP_FF_CANVAS_HEIGHT);
|
|
dec->info.loop_count = WebPDemuxGetI(dec->demux, WEBP_FF_LOOP_COUNT);
|
|
dec->info.bgcolor = WebPDemuxGetI(dec->demux, WEBP_FF_BACKGROUND_COLOR);
|
|
dec->info.frame_count = WebPDemuxGetI(dec->demux, WEBP_FF_FRAME_COUNT);
|
|
|
|
// Note: calloc() because we fill frame with zeroes as well.
|
|
dec->curr_frame = (uint8_t*)WebPSafeCalloc(
|
|
dec->info.canvas_width * NUM_CHANNELS, dec->info.canvas_height);
|
|
if (dec->curr_frame == NULL) goto Error;
|
|
dec->prev_frame_disposed = (uint8_t*)WebPSafeCalloc(
|
|
dec->info.canvas_width * NUM_CHANNELS, dec->info.canvas_height);
|
|
if (dec->prev_frame_disposed == NULL) goto Error;
|
|
|
|
WebPAnimDecoderReset(dec);
|
|
return dec;
|
|
|
|
Error:
|
|
WebPAnimDecoderDelete(dec);
|
|
return NULL;
|
|
}
|
|
|
|
int WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, WebPAnimInfo* info) {
|
|
if (dec == NULL || info == NULL) return 0;
|
|
*info = dec->info;
|
|
return 1;
|
|
}
|
|
|
|
// Returns true if the frame covers the full canvas.
|
|
static int IsFullFrame(int width, int height, int canvas_width,
|
|
int canvas_height) {
|
|
return (width == canvas_width && height == canvas_height);
|
|
}
|
|
|
|
// Clear the canvas to transparent.
|
|
WEBP_NODISCARD static int ZeroFillCanvas(uint8_t* buf, uint32_t canvas_width,
|
|
uint32_t canvas_height) {
|
|
const uint64_t size =
|
|
(uint64_t)canvas_width * canvas_height * NUM_CHANNELS * sizeof(*buf);
|
|
if (!CheckSizeOverflow(size)) return 0;
|
|
WEBP_UNSAFE_MEMSET(buf, 0, (size_t)size);
|
|
return 1;
|
|
}
|
|
|
|
// Clear given frame rectangle to transparent.
|
|
static void ZeroFillFrameRect(uint8_t* buf, int buf_stride, int x_offset,
|
|
int y_offset, int width, int height) {
|
|
int j;
|
|
const uint32_t x = (uint32_t)x_offset * NUM_CHANNELS; // 26 bits
|
|
const uint64_t y = (uint64_t)y_offset * buf_stride;
|
|
assert(width * NUM_CHANNELS <= buf_stride);
|
|
buf += y + x;
|
|
for (j = 0; j < height; ++j) {
|
|
WEBP_UNSAFE_MEMSET(buf, 0, width * NUM_CHANNELS);
|
|
buf += buf_stride;
|
|
}
|
|
}
|
|
|
|
// Copy width * height pixels from 'src' to 'dst'.
|
|
WEBP_NODISCARD static int CopyCanvas(const uint8_t* src, uint8_t* dst,
|
|
uint32_t width, uint32_t height) {
|
|
const uint64_t size = (uint64_t)width * height * NUM_CHANNELS;
|
|
if (!CheckSizeOverflow(size)) return 0;
|
|
assert(src != NULL && dst != NULL);
|
|
WEBP_UNSAFE_MEMCPY(dst, src, (size_t)size);
|
|
return 1;
|
|
}
|
|
|
|
// Returns true if the current frame is a key-frame.
|
|
static int IsKeyFrame(const WebPIterator* const curr,
|
|
const WebPIterator* const prev,
|
|
int prev_frame_was_key_frame, int canvas_width,
|
|
int canvas_height) {
|
|
if (curr->frame_num == 1) {
|
|
return 1;
|
|
} else if ((!curr->has_alpha || curr->blend_method == WEBP_MUX_NO_BLEND) &&
|
|
IsFullFrame(curr->width, curr->height, canvas_width,
|
|
canvas_height)) {
|
|
return 1;
|
|
} else {
|
|
return (prev->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) &&
|
|
(IsFullFrame(prev->width, prev->height, canvas_width,
|
|
canvas_height) ||
|
|
prev_frame_was_key_frame);
|
|
}
|
|
}
|
|
|
|
// Blend a single channel of 'src' over 'dst', given their alpha channel values.
|
|
// 'src' and 'dst' are assumed to be NOT pre-multiplied by alpha.
|
|
static uint8_t BlendChannelNonPremult(uint32_t src, uint8_t src_a, uint32_t dst,
|
|
uint8_t dst_a, uint32_t scale,
|
|
int shift) {
|
|
const uint8_t src_channel = (src >> shift) & 0xff;
|
|
const uint8_t dst_channel = (dst >> shift) & 0xff;
|
|
const uint32_t blend_unscaled = src_channel * src_a + dst_channel * dst_a;
|
|
assert(blend_unscaled < (1ULL << 32) / scale);
|
|
return (blend_unscaled * scale) >> CHANNEL_SHIFT(3);
|
|
}
|
|
|
|
// Blend 'src' over 'dst' assuming they are NOT pre-multiplied by alpha.
|
|
static uint32_t BlendPixelNonPremult(uint32_t src, uint32_t dst) {
|
|
const uint8_t src_a = (src >> CHANNEL_SHIFT(3)) & 0xff;
|
|
|
|
if (src_a == 0) {
|
|
return dst;
|
|
} else {
|
|
const uint8_t dst_a = (dst >> CHANNEL_SHIFT(3)) & 0xff;
|
|
// This is the approximate integer arithmetic for the actual formula:
|
|
// dst_factor_a = (dst_a * (255 - src_a)) / 255.
|
|
const uint8_t dst_factor_a = (dst_a * (256 - src_a)) >> 8;
|
|
const uint8_t blend_a = src_a + dst_factor_a;
|
|
const uint32_t scale = (1UL << 24) / blend_a;
|
|
|
|
const uint8_t blend_r = BlendChannelNonPremult(
|
|
src, src_a, dst, dst_factor_a, scale, CHANNEL_SHIFT(0));
|
|
const uint8_t blend_g = BlendChannelNonPremult(
|
|
src, src_a, dst, dst_factor_a, scale, CHANNEL_SHIFT(1));
|
|
const uint8_t blend_b = BlendChannelNonPremult(
|
|
src, src_a, dst, dst_factor_a, scale, CHANNEL_SHIFT(2));
|
|
assert(src_a + dst_factor_a < 256);
|
|
|
|
return ((uint32_t)blend_r << CHANNEL_SHIFT(0)) |
|
|
((uint32_t)blend_g << CHANNEL_SHIFT(1)) |
|
|
((uint32_t)blend_b << CHANNEL_SHIFT(2)) |
|
|
((uint32_t)blend_a << CHANNEL_SHIFT(3));
|
|
}
|
|
}
|
|
|
|
// Blend 'num_pixels' in 'src' over 'dst' assuming they are NOT pre-multiplied
|
|
// by alpha.
|
|
static void BlendPixelRowNonPremult(uint32_t* const src,
|
|
const uint32_t* const dst, int num_pixels) {
|
|
int i;
|
|
for (i = 0; i < num_pixels; ++i) {
|
|
const uint8_t src_alpha = (src[i] >> CHANNEL_SHIFT(3)) & 0xff;
|
|
if (src_alpha != 0xff) {
|
|
src[i] = BlendPixelNonPremult(src[i], dst[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Individually multiply each channel in 'pix' by 'scale'.
|
|
static WEBP_INLINE uint32_t ChannelwiseMultiply(uint32_t pix, uint32_t scale) {
|
|
uint32_t mask = 0x00FF00FF;
|
|
uint32_t rb = ((pix & mask) * scale) >> 8;
|
|
uint32_t ag = ((pix >> 8) & mask) * scale;
|
|
return (rb & mask) | (ag & ~mask);
|
|
}
|
|
|
|
// Blend 'src' over 'dst' assuming they are pre-multiplied by alpha.
|
|
static uint32_t BlendPixelPremult(uint32_t src, uint32_t dst) {
|
|
const uint8_t src_a = (src >> CHANNEL_SHIFT(3)) & 0xff;
|
|
return src + ChannelwiseMultiply(dst, 256 - src_a);
|
|
}
|
|
|
|
// Blend 'num_pixels' in 'src' over 'dst' assuming they are pre-multiplied by
|
|
// alpha.
|
|
static void BlendPixelRowPremult(uint32_t* const src, const uint32_t* const dst,
|
|
int num_pixels) {
|
|
int i;
|
|
for (i = 0; i < num_pixels; ++i) {
|
|
const uint8_t src_alpha = (src[i] >> CHANNEL_SHIFT(3)) & 0xff;
|
|
if (src_alpha != 0xff) {
|
|
src[i] = BlendPixelPremult(src[i], dst[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns two ranges (<left, width> pairs) at row 'canvas_y', that belong to
|
|
// 'src' but not 'dst'. A point range is empty if the corresponding width is 0.
|
|
static void FindBlendRangeAtRow(const WebPIterator* const src,
|
|
const WebPIterator* const dst, int canvas_y,
|
|
int* const left1, int* const width1,
|
|
int* const left2, int* const width2) {
|
|
const int src_max_x = src->x_offset + src->width;
|
|
const int dst_max_x = dst->x_offset + dst->width;
|
|
const int dst_max_y = dst->y_offset + dst->height;
|
|
assert(canvas_y >= src->y_offset && canvas_y < (src->y_offset + src->height));
|
|
*left1 = -1;
|
|
*width1 = 0;
|
|
*left2 = -1;
|
|
*width2 = 0;
|
|
|
|
if (canvas_y < dst->y_offset || canvas_y >= dst_max_y ||
|
|
src->x_offset >= dst_max_x || src_max_x <= dst->x_offset) {
|
|
*left1 = src->x_offset;
|
|
*width1 = src->width;
|
|
return;
|
|
}
|
|
|
|
if (src->x_offset < dst->x_offset) {
|
|
*left1 = src->x_offset;
|
|
*width1 = dst->x_offset - src->x_offset;
|
|
}
|
|
|
|
if (src_max_x > dst_max_x) {
|
|
*left2 = dst_max_x;
|
|
*width2 = src_max_x - dst_max_x;
|
|
}
|
|
}
|
|
|
|
int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, uint8_t** buf_ptr,
|
|
int* timestamp_ptr) {
|
|
WebPIterator iter;
|
|
uint32_t width;
|
|
uint32_t height;
|
|
int is_key_frame;
|
|
int timestamp;
|
|
BlendRowFunc blend_row;
|
|
|
|
if (dec == NULL || buf_ptr == NULL || timestamp_ptr == NULL) return 0;
|
|
if (!WebPAnimDecoderHasMoreFrames(dec)) return 0;
|
|
|
|
width = dec->info.canvas_width;
|
|
height = dec->info.canvas_height;
|
|
blend_row = dec->blend_func;
|
|
|
|
// Get compressed frame.
|
|
if (!WebPDemuxGetFrame(dec->demux, dec->next_frame, &iter)) {
|
|
return 0;
|
|
}
|
|
timestamp = dec->prev_frame_timestamp + iter.duration;
|
|
|
|
// Initialize.
|
|
is_key_frame = IsKeyFrame(&iter, &dec->prev_iter,
|
|
dec->prev_frame_was_keyframe, width, height);
|
|
if (is_key_frame) {
|
|
if (!ZeroFillCanvas(dec->curr_frame, width, height)) {
|
|
goto Error;
|
|
}
|
|
} else {
|
|
if (!CopyCanvas(dec->prev_frame_disposed, dec->curr_frame, width, height)) {
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
// Decode.
|
|
{
|
|
const uint8_t* in = iter.fragment.bytes;
|
|
const size_t in_size = iter.fragment.size;
|
|
const uint32_t stride = width * NUM_CHANNELS; // at most 25 + 2 bits
|
|
const uint64_t out_offset = (uint64_t)iter.y_offset * stride +
|
|
(uint64_t)iter.x_offset * NUM_CHANNELS; // 53b
|
|
const uint64_t size = (uint64_t)iter.height * stride; // at most 25 + 27b
|
|
WebPDecoderConfig* const config = &dec->config;
|
|
WebPRGBABuffer* const buf = &config->output.u.RGBA;
|
|
if ((size_t)size != size) goto Error;
|
|
buf->stride = (int)stride;
|
|
buf->size = (size_t)size;
|
|
buf->rgba = dec->curr_frame + out_offset;
|
|
|
|
if (WebPDecode(in, in_size, config) != VP8_STATUS_OK) {
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
// During the decoding of current frame, we may have set some pixels to be
|
|
// transparent (i.e. alpha < 255). However, the value of each of these
|
|
// pixels should have been determined by blending it against the value of
|
|
// that pixel in the previous frame if blending method of is WEBP_MUX_BLEND.
|
|
if (iter.frame_num > 1 && iter.blend_method == WEBP_MUX_BLEND &&
|
|
!is_key_frame) {
|
|
if (dec->prev_iter.dispose_method == WEBP_MUX_DISPOSE_NONE) {
|
|
int y;
|
|
// Blend transparent pixels with pixels in previous canvas.
|
|
for (y = 0; y < iter.height; ++y) {
|
|
const size_t offset = (iter.y_offset + y) * width + iter.x_offset;
|
|
blend_row((uint32_t*)dec->curr_frame + offset,
|
|
(uint32_t*)dec->prev_frame_disposed + offset, iter.width);
|
|
}
|
|
} else {
|
|
int y;
|
|
assert(dec->prev_iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND);
|
|
// We need to blend a transparent pixel with its value just after
|
|
// initialization. That is, blend it with:
|
|
// * Fully transparent pixel if it belongs to prevRect <-- No-op.
|
|
// * The pixel in the previous canvas otherwise <-- Need alpha-blending.
|
|
for (y = 0; y < iter.height; ++y) {
|
|
const int canvas_y = iter.y_offset + y;
|
|
int left1, width1, left2, width2;
|
|
FindBlendRangeAtRow(&iter, &dec->prev_iter, canvas_y, &left1, &width1,
|
|
&left2, &width2);
|
|
if (width1 > 0) {
|
|
const size_t offset1 = canvas_y * width + left1;
|
|
blend_row((uint32_t*)dec->curr_frame + offset1,
|
|
(uint32_t*)dec->prev_frame_disposed + offset1, width1);
|
|
}
|
|
if (width2 > 0) {
|
|
const size_t offset2 = canvas_y * width + left2;
|
|
blend_row((uint32_t*)dec->curr_frame + offset2,
|
|
(uint32_t*)dec->prev_frame_disposed + offset2, width2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update info of the previous frame and dispose it for the next iteration.
|
|
dec->prev_frame_timestamp = timestamp;
|
|
WebPDemuxReleaseIterator(&dec->prev_iter);
|
|
dec->prev_iter = iter;
|
|
dec->prev_frame_was_keyframe = is_key_frame;
|
|
if (!CopyCanvas(dec->curr_frame, dec->prev_frame_disposed, width, height)) {
|
|
goto Error;
|
|
}
|
|
if (dec->prev_iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
|
ZeroFillFrameRect(dec->prev_frame_disposed, width * NUM_CHANNELS,
|
|
dec->prev_iter.x_offset, dec->prev_iter.y_offset,
|
|
dec->prev_iter.width, dec->prev_iter.height);
|
|
}
|
|
++dec->next_frame;
|
|
|
|
// All OK, fill in the values.
|
|
*buf_ptr = dec->curr_frame;
|
|
*timestamp_ptr = timestamp;
|
|
return 1;
|
|
|
|
Error:
|
|
WebPDemuxReleaseIterator(&iter);
|
|
return 0;
|
|
}
|
|
|
|
int WebPAnimDecoderHasMoreFrames(const WebPAnimDecoder* dec) {
|
|
if (dec == NULL) return 0;
|
|
return (dec->next_frame <= (int)dec->info.frame_count);
|
|
}
|
|
|
|
void WebPAnimDecoderReset(WebPAnimDecoder* dec) {
|
|
if (dec != NULL) {
|
|
dec->prev_frame_timestamp = 0;
|
|
WebPDemuxReleaseIterator(&dec->prev_iter);
|
|
WEBP_UNSAFE_MEMSET(&dec->prev_iter, 0, sizeof(dec->prev_iter));
|
|
dec->prev_frame_was_keyframe = 0;
|
|
dec->next_frame = 1;
|
|
}
|
|
}
|
|
|
|
const WebPDemuxer* WebPAnimDecoderGetDemuxer(const WebPAnimDecoder* dec) {
|
|
if (dec == NULL) return NULL;
|
|
return dec->demux;
|
|
}
|
|
|
|
void WebPAnimDecoderDelete(WebPAnimDecoder* dec) {
|
|
if (dec != NULL) {
|
|
WebPDemuxReleaseIterator(&dec->prev_iter);
|
|
WebPDemuxDelete(dec->demux);
|
|
WebPSafeFree(dec->curr_frame);
|
|
WebPSafeFree(dec->prev_frame_disposed);
|
|
WebPSafeFree(dec);
|
|
}
|
|
}
|
|
#endif /* WEBPDEC_IMPLEMENTATION */
|