libwebp: Sync with upstream 1.0.2
This commit is contained in:
parent
2bc981948d
commit
93f6a065f8
|
@ -29,13 +29,11 @@ if env['builtin_libwebp']:
|
||||||
"dsp/cost.c",
|
"dsp/cost.c",
|
||||||
"dsp/cost_mips32.c",
|
"dsp/cost_mips32.c",
|
||||||
"dsp/cost_mips_dsp_r2.c",
|
"dsp/cost_mips_dsp_r2.c",
|
||||||
|
"dsp/cost_neon.c",
|
||||||
"dsp/cost_sse2.c",
|
"dsp/cost_sse2.c",
|
||||||
"dsp/cpu.c",
|
"dsp/cpu.c",
|
||||||
"dsp/dec.c",
|
"dsp/dec.c",
|
||||||
"dsp/dec_clip_tables.c",
|
"dsp/dec_clip_tables.c",
|
||||||
"dsp/ssim.c",
|
|
||||||
"dsp/ssim_sse2.c",
|
|
||||||
"dsp/yuv_neon.c",
|
|
||||||
"dsp/dec_mips32.c",
|
"dsp/dec_mips32.c",
|
||||||
"dsp/dec_mips_dsp_r2.c",
|
"dsp/dec_mips_dsp_r2.c",
|
||||||
"dsp/dec_msa.c",
|
"dsp/dec_msa.c",
|
||||||
|
@ -72,6 +70,8 @@ if env['builtin_libwebp']:
|
||||||
"dsp/rescaler_msa.c",
|
"dsp/rescaler_msa.c",
|
||||||
"dsp/rescaler_neon.c",
|
"dsp/rescaler_neon.c",
|
||||||
"dsp/rescaler_sse2.c",
|
"dsp/rescaler_sse2.c",
|
||||||
|
"dsp/ssim.c",
|
||||||
|
"dsp/ssim_sse2.c",
|
||||||
"dsp/upsampling.c",
|
"dsp/upsampling.c",
|
||||||
"dsp/upsampling_mips_dsp_r2.c",
|
"dsp/upsampling_mips_dsp_r2.c",
|
||||||
"dsp/upsampling_msa.c",
|
"dsp/upsampling_msa.c",
|
||||||
|
@ -81,6 +81,7 @@ if env['builtin_libwebp']:
|
||||||
"dsp/yuv.c",
|
"dsp/yuv.c",
|
||||||
"dsp/yuv_mips32.c",
|
"dsp/yuv_mips32.c",
|
||||||
"dsp/yuv_mips_dsp_r2.c",
|
"dsp/yuv_mips_dsp_r2.c",
|
||||||
|
"dsp/yuv_neon.c",
|
||||||
"dsp/yuv_sse2.c",
|
"dsp/yuv_sse2.c",
|
||||||
"dsp/yuv_sse41.c",
|
"dsp/yuv_sse41.c",
|
||||||
"enc/alpha_enc.c",
|
"enc/alpha_enc.c",
|
||||||
|
|
|
@ -239,7 +239,7 @@ from the Android NDK r18.
|
||||||
## libwebp
|
## libwebp
|
||||||
|
|
||||||
- Upstream: https://chromium.googlesource.com/webm/libwebp/
|
- Upstream: https://chromium.googlesource.com/webm/libwebp/
|
||||||
- Version: 1.0.1
|
- Version: 1.0.2
|
||||||
- License: BSD-3-Clause
|
- License: BSD-3-Clause
|
||||||
|
|
||||||
Files extracted from upstream source:
|
Files extracted from upstream source:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
Contributors:
|
Contributors:
|
||||||
|
- Alan Browning (browning at google dot com)
|
||||||
- Charles Munger (clm at google dot com)
|
- Charles Munger (clm at google dot com)
|
||||||
- Christian Duvivier (cduvivier at google dot com)
|
- Christian Duvivier (cduvivier at google dot com)
|
||||||
- Djordje Pesut (djordje dot pesut at imgtec dot com)
|
- Djordje Pesut (djordje dot pesut at imgtec dot com)
|
||||||
|
@ -6,9 +7,10 @@ Contributors:
|
||||||
- James Zern (jzern at google dot com)
|
- James Zern (jzern at google dot com)
|
||||||
- Jan Engelhardt (jengelh at medozas dot de)
|
- Jan Engelhardt (jengelh at medozas dot de)
|
||||||
- Jehan (jehan at girinstud dot io)
|
- Jehan (jehan at girinstud dot io)
|
||||||
- Johann (johann dot koenig at duck dot com)
|
- Johann Koenig (johann dot koenig at duck dot com)
|
||||||
- Jovan Zelincevic (jovan dot zelincevic at imgtec dot com)
|
- Jovan Zelincevic (jovan dot zelincevic at imgtec dot com)
|
||||||
- Jyrki Alakuijala (jyrki at google dot com)
|
- Jyrki Alakuijala (jyrki at google dot com)
|
||||||
|
- Konstantin Ivlev (tomskside at gmail dot com)
|
||||||
- Lode Vandevenne (lode at google dot com)
|
- Lode Vandevenne (lode at google dot com)
|
||||||
- Lou Quillio (louquillio at google dot com)
|
- Lou Quillio (louquillio at google dot com)
|
||||||
- Mans Rullgard (mans at mansr dot com)
|
- Mans Rullgard (mans at mansr dot com)
|
||||||
|
@ -37,3 +39,4 @@ Contributors:
|
||||||
- Vincent Rabaud (vrabaud at google dot com)
|
- Vincent Rabaud (vrabaud at google dot com)
|
||||||
- Vlad Tsyrklevich (vtsyrklevich at chromium dot org)
|
- Vlad Tsyrklevich (vtsyrklevich at chromium dot org)
|
||||||
- Yang Zhang (yang dot zhang at arm dot com)
|
- Yang Zhang (yang dot zhang at arm dot com)
|
||||||
|
- Yannis Guyon (yguyon at google dot com)
|
||||||
|
|
|
@ -32,7 +32,7 @@ extern "C" {
|
||||||
// version numbers
|
// version numbers
|
||||||
#define DEC_MAJ_VERSION 1
|
#define DEC_MAJ_VERSION 1
|
||||||
#define DEC_MIN_VERSION 0
|
#define DEC_MIN_VERSION 0
|
||||||
#define DEC_REV_VERSION 1
|
#define DEC_REV_VERSION 2
|
||||||
|
|
||||||
// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline).
|
// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline).
|
||||||
// Constraints are: We need to store one 16x16 block of luma samples (y),
|
// Constraints are: We need to store one 16x16 block of luma samples (y),
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
#define DMUX_MAJ_VERSION 1
|
#define DMUX_MAJ_VERSION 1
|
||||||
#define DMUX_MIN_VERSION 0
|
#define DMUX_MIN_VERSION 0
|
||||||
#define DMUX_REV_VERSION 1
|
#define DMUX_REV_VERSION 2
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
size_t start_; // start location of the data
|
size_t start_; // start location of the data
|
||||||
|
|
|
@ -377,6 +377,7 @@ VP8SetResidualCoeffsFunc VP8SetResidualCoeffs;
|
||||||
extern void VP8EncDspCostInitMIPS32(void);
|
extern void VP8EncDspCostInitMIPS32(void);
|
||||||
extern void VP8EncDspCostInitMIPSdspR2(void);
|
extern void VP8EncDspCostInitMIPSdspR2(void);
|
||||||
extern void VP8EncDspCostInitSSE2(void);
|
extern void VP8EncDspCostInitSSE2(void);
|
||||||
|
extern void VP8EncDspCostInitNEON(void);
|
||||||
|
|
||||||
WEBP_DSP_INIT_FUNC(VP8EncDspCostInit) {
|
WEBP_DSP_INIT_FUNC(VP8EncDspCostInit) {
|
||||||
VP8GetResidualCost = GetResidualCost_C;
|
VP8GetResidualCost = GetResidualCost_C;
|
||||||
|
@ -398,6 +399,11 @@ WEBP_DSP_INIT_FUNC(VP8EncDspCostInit) {
|
||||||
if (VP8GetCPUInfo(kSSE2)) {
|
if (VP8GetCPUInfo(kSSE2)) {
|
||||||
VP8EncDspCostInitSSE2();
|
VP8EncDspCostInitSSE2();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(WEBP_USE_NEON)
|
||||||
|
if (VP8GetCPUInfo(kNEON)) {
|
||||||
|
VP8EncDspCostInitNEON();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// ARM NEON version of cost functions
|
||||||
|
|
||||||
|
#include "src/dsp/dsp.h"
|
||||||
|
|
||||||
|
#if defined(WEBP_USE_NEON)
|
||||||
|
|
||||||
|
#include "src/dsp/neon.h"
|
||||||
|
#include "src/enc/cost_enc.h"
|
||||||
|
|
||||||
|
static const uint8_t position[16] = { 1, 2, 3, 4, 5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12, 13, 14, 15, 16 };
|
||||||
|
|
||||||
|
static void SetResidualCoeffs_NEON(const int16_t* const coeffs,
|
||||||
|
VP8Residual* const res) {
|
||||||
|
const int16x8_t minus_one = vdupq_n_s16(-1);
|
||||||
|
const int16x8_t coeffs_0 = vld1q_s16(coeffs);
|
||||||
|
const int16x8_t coeffs_1 = vld1q_s16(coeffs + 8);
|
||||||
|
const uint16x8_t eob_0 = vtstq_s16(coeffs_0, minus_one);
|
||||||
|
const uint16x8_t eob_1 = vtstq_s16(coeffs_1, minus_one);
|
||||||
|
const uint8x16_t eob = vcombine_u8(vqmovn_u16(eob_0), vqmovn_u16(eob_1));
|
||||||
|
const uint8x16_t masked = vandq_u8(eob, vld1q_u8(position));
|
||||||
|
|
||||||
|
#ifdef __aarch64__
|
||||||
|
res->last = vmaxvq_u8(masked) - 1;
|
||||||
|
#else
|
||||||
|
const uint8x8_t eob_8x8 = vmax_u8(vget_low_u8(masked), vget_high_u8(masked));
|
||||||
|
const uint16x8_t eob_16x8 = vmovl_u8(eob_8x8);
|
||||||
|
const uint16x4_t eob_16x4 =
|
||||||
|
vmax_u16(vget_low_u16(eob_16x8), vget_high_u16(eob_16x8));
|
||||||
|
const uint32x4_t eob_32x4 = vmovl_u16(eob_16x4);
|
||||||
|
uint32x2_t eob_32x2 =
|
||||||
|
vmax_u32(vget_low_u32(eob_32x4), vget_high_u32(eob_32x4));
|
||||||
|
eob_32x2 = vpmax_u32(eob_32x2, eob_32x2);
|
||||||
|
|
||||||
|
vst1_lane_s32(&res->last, vreinterpret_s32_u32(eob_32x2), 0);
|
||||||
|
--res->last;
|
||||||
|
#endif // __aarch64__
|
||||||
|
|
||||||
|
res->coeffs = coeffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int GetResidualCost_NEON(int ctx0, const VP8Residual* const res) {
|
||||||
|
uint8_t levels[16], ctxs[16];
|
||||||
|
uint16_t abs_levels[16];
|
||||||
|
int n = res->first;
|
||||||
|
// should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1
|
||||||
|
const int p0 = res->prob[n][ctx0][0];
|
||||||
|
CostArrayPtr const costs = res->costs;
|
||||||
|
const uint16_t* t = costs[n][ctx0];
|
||||||
|
// bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0
|
||||||
|
// (as required by the syntax). For ctx0 == 0, we need to add it here or it'll
|
||||||
|
// be missing during the loop.
|
||||||
|
int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0;
|
||||||
|
|
||||||
|
if (res->last < 0) {
|
||||||
|
return VP8BitCost(0, p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // precompute clamped levels and contexts, packed to 8b.
|
||||||
|
const uint8x16_t kCst2 = vdupq_n_u8(2);
|
||||||
|
const uint8x16_t kCst67 = vdupq_n_u8(MAX_VARIABLE_LEVEL);
|
||||||
|
const int16x8_t c0 = vld1q_s16(res->coeffs);
|
||||||
|
const int16x8_t c1 = vld1q_s16(res->coeffs + 8);
|
||||||
|
const uint16x8_t E0 = vreinterpretq_u16_s16(vabsq_s16(c0));
|
||||||
|
const uint16x8_t E1 = vreinterpretq_u16_s16(vabsq_s16(c1));
|
||||||
|
const uint8x16_t F = vcombine_u8(vqmovn_u16(E0), vqmovn_u16(E1));
|
||||||
|
const uint8x16_t G = vminq_u8(F, kCst2); // context = 0,1,2
|
||||||
|
const uint8x16_t H = vminq_u8(F, kCst67); // clamp_level in [0..67]
|
||||||
|
|
||||||
|
vst1q_u8(ctxs, G);
|
||||||
|
vst1q_u8(levels, H);
|
||||||
|
|
||||||
|
vst1q_u16(abs_levels, E0);
|
||||||
|
vst1q_u16(abs_levels + 8, E1);
|
||||||
|
}
|
||||||
|
for (; n < res->last; ++n) {
|
||||||
|
const int ctx = ctxs[n];
|
||||||
|
const int level = levels[n];
|
||||||
|
const int flevel = abs_levels[n]; // full level
|
||||||
|
cost += VP8LevelFixedCosts[flevel] + t[level]; // simplified VP8LevelCost()
|
||||||
|
t = costs[n + 1][ctx];
|
||||||
|
}
|
||||||
|
// Last coefficient is always non-zero
|
||||||
|
{
|
||||||
|
const int level = levels[n];
|
||||||
|
const int flevel = abs_levels[n];
|
||||||
|
assert(flevel != 0);
|
||||||
|
cost += VP8LevelFixedCosts[flevel] + t[level];
|
||||||
|
if (n < 15) {
|
||||||
|
const int b = VP8EncBands[n + 1];
|
||||||
|
const int ctx = ctxs[n];
|
||||||
|
const int last_p0 = res->prob[b][ctx][0];
|
||||||
|
cost += VP8BitCost(0, last_p0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Entry point
|
||||||
|
|
||||||
|
extern void VP8EncDspCostInitNEON(void);
|
||||||
|
|
||||||
|
WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitNEON(void) {
|
||||||
|
VP8SetResidualCoeffs = SetResidualCoeffs_NEON;
|
||||||
|
VP8GetResidualCost = GetResidualCost_NEON;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // !WEBP_USE_NEON
|
||||||
|
|
||||||
|
WEBP_DSP_INIT_STUB(VP8EncDspCostInitNEON)
|
||||||
|
|
||||||
|
#endif // WEBP_USE_NEON
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef WEBP_DSP_QUANT_H_
|
||||||
|
#define WEBP_DSP_QUANT_H_
|
||||||
|
|
||||||
|
#include "src/dsp/dsp.h"
|
||||||
|
#include "src/webp/types.h"
|
||||||
|
|
||||||
|
#if defined(WEBP_USE_NEON) && !defined(WEBP_ANDROID_NEON) && \
|
||||||
|
!defined(WEBP_HAVE_NEON_RTCD)
|
||||||
|
#include <arm_neon.h>
|
||||||
|
|
||||||
|
#define IsFlat IsFlat_NEON
|
||||||
|
|
||||||
|
static uint32x2_t horizontal_add_uint32x4(const uint32x4_t a) {
|
||||||
|
const uint64x2_t b = vpaddlq_u32(a);
|
||||||
|
return vadd_u32(vreinterpret_u32_u64(vget_low_u64(b)),
|
||||||
|
vreinterpret_u32_u64(vget_high_u64(b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static WEBP_INLINE int IsFlat(const int16_t* levels, int num_blocks,
|
||||||
|
int thresh) {
|
||||||
|
const int16x8_t tst_ones = vdupq_n_s16(-1);
|
||||||
|
uint32x4_t sum = vdupq_n_u32(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_blocks; ++i) {
|
||||||
|
// Set DC to zero.
|
||||||
|
const int16x8_t a_0 = vsetq_lane_s16(0, vld1q_s16(levels), 0);
|
||||||
|
const int16x8_t a_1 = vld1q_s16(levels + 8);
|
||||||
|
|
||||||
|
const uint16x8_t b_0 = vshrq_n_u16(vtstq_s16(a_0, tst_ones), 15);
|
||||||
|
const uint16x8_t b_1 = vshrq_n_u16(vtstq_s16(a_1, tst_ones), 15);
|
||||||
|
|
||||||
|
sum = vpadalq_u16(sum, b_0);
|
||||||
|
sum = vpadalq_u16(sum, b_1);
|
||||||
|
|
||||||
|
levels += 16;
|
||||||
|
}
|
||||||
|
return thresh >= (int32_t)vget_lane_u32(horizontal_add_uint32x4(sum), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define IsFlat IsFlat_C
|
||||||
|
|
||||||
|
static WEBP_INLINE int IsFlat(const int16_t* levels, int num_blocks,
|
||||||
|
int thresh) {
|
||||||
|
int score = 0;
|
||||||
|
while (num_blocks-- > 0) { // TODO(skal): refine positional scoring?
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < 16; ++i) { // omit DC, we're only interested in AC
|
||||||
|
score += (levels[i] != 0);
|
||||||
|
if (score > thresh) return 0;
|
||||||
|
}
|
||||||
|
levels += 16;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // defined(WEBP_USE_NEON) && !defined(WEBP_ANDROID_NEON) &&
|
||||||
|
// !defined(WEBP_HAVE_NEON_RTCD)
|
||||||
|
|
||||||
|
#endif // WEBP_DSP_QUANT_H_
|
|
@ -165,7 +165,7 @@ VP8LHistogramSet* VP8LAllocateHistogramSet(int size, int cache_bits) {
|
||||||
void VP8LHistogramSetClear(VP8LHistogramSet* const set) {
|
void VP8LHistogramSetClear(VP8LHistogramSet* const set) {
|
||||||
int i;
|
int i;
|
||||||
const int cache_bits = set->histograms[0]->palette_code_bits_;
|
const int cache_bits = set->histograms[0]->palette_code_bits_;
|
||||||
const int size = set->size;
|
const int size = set->max_size;
|
||||||
const size_t total_size = HistogramSetTotalSize(size, cache_bits);
|
const size_t total_size = HistogramSetTotalSize(size, cache_bits);
|
||||||
uint8_t* memory = (uint8_t*)set;
|
uint8_t* memory = (uint8_t*)set;
|
||||||
|
|
||||||
|
@ -180,6 +180,20 @@ void VP8LHistogramSetClear(VP8LHistogramSet* const set) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes the histogram 'i' from 'set' by setting it to NULL.
|
||||||
|
static void HistogramSetRemoveHistogram(VP8LHistogramSet* const set, int i,
|
||||||
|
int* const num_used) {
|
||||||
|
assert(set->histograms[i] != NULL);
|
||||||
|
set->histograms[i] = NULL;
|
||||||
|
--*num_used;
|
||||||
|
// If we remove the last valid one, shrink until the next valid one.
|
||||||
|
if (i == set->size - 1) {
|
||||||
|
while (set->size >= 1 && set->histograms[set->size - 1] == NULL) {
|
||||||
|
--set->size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const histo,
|
void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const histo,
|
||||||
|
@ -447,7 +461,9 @@ static double HistogramAddEval(const VP8LHistogram* const a,
|
||||||
static double HistogramAddThresh(const VP8LHistogram* const a,
|
static double HistogramAddThresh(const VP8LHistogram* const a,
|
||||||
const VP8LHistogram* const b,
|
const VP8LHistogram* const b,
|
||||||
double cost_threshold) {
|
double cost_threshold) {
|
||||||
double cost = -a->bit_cost_;
|
double cost;
|
||||||
|
assert(a != NULL && b != NULL);
|
||||||
|
cost = -a->bit_cost_;
|
||||||
GetCombinedHistogramEntropy(a, b, cost_threshold, &cost);
|
GetCombinedHistogramEntropy(a, b, cost_threshold, &cost);
|
||||||
return cost;
|
return cost;
|
||||||
}
|
}
|
||||||
|
@ -561,14 +577,17 @@ static void HistogramBuild(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copies the histograms and computes its bit_cost.
|
// Copies the histograms and computes its bit_cost.
|
||||||
static void HistogramCopyAndAnalyze(
|
static const uint16_t kInvalidHistogramSymbol = (uint16_t)(-1);
|
||||||
VP8LHistogramSet* const orig_histo, VP8LHistogramSet* const image_histo) {
|
static void HistogramCopyAndAnalyze(VP8LHistogramSet* const orig_histo,
|
||||||
int i;
|
VP8LHistogramSet* const image_histo,
|
||||||
const int histo_size = orig_histo->size;
|
int* const num_used,
|
||||||
|
uint16_t* const histogram_symbols) {
|
||||||
|
int i, cluster_id;
|
||||||
|
int num_used_orig = *num_used;
|
||||||
VP8LHistogram** const orig_histograms = orig_histo->histograms;
|
VP8LHistogram** const orig_histograms = orig_histo->histograms;
|
||||||
VP8LHistogram** const histograms = image_histo->histograms;
|
VP8LHistogram** const histograms = image_histo->histograms;
|
||||||
image_histo->size = 0;
|
assert(image_histo->max_size == orig_histo->max_size);
|
||||||
for (i = 0; i < histo_size; ++i) {
|
for (cluster_id = 0, i = 0; i < orig_histo->max_size; ++i) {
|
||||||
VP8LHistogram* const histo = orig_histograms[i];
|
VP8LHistogram* const histo = orig_histograms[i];
|
||||||
UpdateHistogramCost(histo);
|
UpdateHistogramCost(histo);
|
||||||
|
|
||||||
|
@ -576,10 +595,19 @@ static void HistogramCopyAndAnalyze(
|
||||||
// with no information (when they are skipped because of LZ77).
|
// with no information (when they are skipped because of LZ77).
|
||||||
if (!histo->is_used_[0] && !histo->is_used_[1] && !histo->is_used_[2]
|
if (!histo->is_used_[0] && !histo->is_used_[1] && !histo->is_used_[2]
|
||||||
&& !histo->is_used_[3] && !histo->is_used_[4]) {
|
&& !histo->is_used_[3] && !histo->is_used_[4]) {
|
||||||
continue;
|
// The first histogram is always used. If an histogram is empty, we set
|
||||||
|
// its id to be the same as the previous one: this will improve
|
||||||
|
// compressibility for later LZ77.
|
||||||
|
assert(i > 0);
|
||||||
|
HistogramSetRemoveHistogram(image_histo, i, num_used);
|
||||||
|
HistogramSetRemoveHistogram(orig_histo, i, &num_used_orig);
|
||||||
|
histogram_symbols[i] = kInvalidHistogramSymbol;
|
||||||
|
} else {
|
||||||
|
// Copy histograms from orig_histo[] to image_histo[].
|
||||||
|
HistogramCopy(histo, histograms[i]);
|
||||||
|
histogram_symbols[i] = cluster_id++;
|
||||||
|
assert(cluster_id <= image_histo->max_size);
|
||||||
}
|
}
|
||||||
// Copy histograms from orig_histo[] to image_histo[].
|
|
||||||
HistogramCopy(histo, histograms[image_histo->size++]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,29 +624,33 @@ static void HistogramAnalyzeEntropyBin(VP8LHistogramSet* const image_histo,
|
||||||
|
|
||||||
// Analyze the dominant (literal, red and blue) entropy costs.
|
// Analyze the dominant (literal, red and blue) entropy costs.
|
||||||
for (i = 0; i < histo_size; ++i) {
|
for (i = 0; i < histo_size; ++i) {
|
||||||
|
if (histograms[i] == NULL) continue;
|
||||||
UpdateDominantCostRange(histograms[i], &cost_range);
|
UpdateDominantCostRange(histograms[i], &cost_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
// bin-hash histograms on three of the dominant (literal, red and blue)
|
// bin-hash histograms on three of the dominant (literal, red and blue)
|
||||||
// symbol costs and store the resulting bin_id for each histogram.
|
// symbol costs and store the resulting bin_id for each histogram.
|
||||||
for (i = 0; i < histo_size; ++i) {
|
for (i = 0; i < histo_size; ++i) {
|
||||||
|
// bin_map[i] is not set to a special value as its use will later be guarded
|
||||||
|
// by another (histograms[i] == NULL).
|
||||||
|
if (histograms[i] == NULL) continue;
|
||||||
bin_map[i] = GetHistoBinIndex(histograms[i], &cost_range, low_effort);
|
bin_map[i] = GetHistoBinIndex(histograms[i], &cost_range, low_effort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact image_histo[] by merging some histograms with same bin_id together if
|
// Merges some histograms with same bin_id together if it's advantageous.
|
||||||
// it's advantageous.
|
// Sets the remaining histograms to NULL.
|
||||||
static void HistogramCombineEntropyBin(VP8LHistogramSet* const image_histo,
|
static void HistogramCombineEntropyBin(VP8LHistogramSet* const image_histo,
|
||||||
|
int *num_used,
|
||||||
|
const uint16_t* const clusters,
|
||||||
|
uint16_t* const cluster_mappings,
|
||||||
VP8LHistogram* cur_combo,
|
VP8LHistogram* cur_combo,
|
||||||
const uint16_t* const bin_map,
|
const uint16_t* const bin_map,
|
||||||
int bin_map_size, int num_bins,
|
int num_bins,
|
||||||
double combine_cost_factor,
|
double combine_cost_factor,
|
||||||
int low_effort) {
|
int low_effort) {
|
||||||
VP8LHistogram** const histograms = image_histo->histograms;
|
VP8LHistogram** const histograms = image_histo->histograms;
|
||||||
int idx;
|
int idx;
|
||||||
// Work in-place: processed histograms are put at the beginning of
|
|
||||||
// image_histo[]. At the end, we just have to truncate the array.
|
|
||||||
int size = 0;
|
|
||||||
struct {
|
struct {
|
||||||
int16_t first; // position of the histogram that accumulates all
|
int16_t first; // position of the histogram that accumulates all
|
||||||
// histograms with the same bin_id
|
// histograms with the same bin_id
|
||||||
|
@ -631,16 +663,19 @@ static void HistogramCombineEntropyBin(VP8LHistogramSet* const image_histo,
|
||||||
bin_info[idx].num_combine_failures = 0;
|
bin_info[idx].num_combine_failures = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (idx = 0; idx < bin_map_size; ++idx) {
|
// By default, a cluster matches itself.
|
||||||
const int bin_id = bin_map[idx];
|
for (idx = 0; idx < *num_used; ++idx) cluster_mappings[idx] = idx;
|
||||||
const int first = bin_info[bin_id].first;
|
for (idx = 0; idx < image_histo->size; ++idx) {
|
||||||
assert(size <= idx);
|
int bin_id, first;
|
||||||
|
if (histograms[idx] == NULL) continue;
|
||||||
|
bin_id = bin_map[idx];
|
||||||
|
first = bin_info[bin_id].first;
|
||||||
if (first == -1) {
|
if (first == -1) {
|
||||||
// just move histogram #idx to its final position
|
bin_info[bin_id].first = idx;
|
||||||
histograms[size] = histograms[idx];
|
|
||||||
bin_info[bin_id].first = size++;
|
|
||||||
} else if (low_effort) {
|
} else if (low_effort) {
|
||||||
HistogramAdd(histograms[idx], histograms[first], histograms[first]);
|
HistogramAdd(histograms[idx], histograms[first], histograms[first]);
|
||||||
|
HistogramSetRemoveHistogram(image_histo, idx, num_used);
|
||||||
|
cluster_mappings[clusters[idx]] = clusters[first];
|
||||||
} else {
|
} else {
|
||||||
// try to merge #idx into #first (both share the same bin_id)
|
// try to merge #idx into #first (both share the same bin_id)
|
||||||
const double bit_cost = histograms[idx]->bit_cost_;
|
const double bit_cost = histograms[idx]->bit_cost_;
|
||||||
|
@ -663,19 +698,18 @@ static void HistogramCombineEntropyBin(VP8LHistogramSet* const image_histo,
|
||||||
bin_info[bin_id].num_combine_failures >= max_combine_failures) {
|
bin_info[bin_id].num_combine_failures >= max_combine_failures) {
|
||||||
// move the (better) merged histogram to its final slot
|
// move the (better) merged histogram to its final slot
|
||||||
HistogramSwap(&cur_combo, &histograms[first]);
|
HistogramSwap(&cur_combo, &histograms[first]);
|
||||||
|
HistogramSetRemoveHistogram(image_histo, idx, num_used);
|
||||||
|
cluster_mappings[clusters[idx]] = clusters[first];
|
||||||
} else {
|
} else {
|
||||||
histograms[size++] = histograms[idx];
|
|
||||||
++bin_info[bin_id].num_combine_failures;
|
++bin_info[bin_id].num_combine_failures;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
histograms[size++] = histograms[idx];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
image_histo->size = size;
|
|
||||||
if (low_effort) {
|
if (low_effort) {
|
||||||
// for low_effort case, update the final cost when everything is merged
|
// for low_effort case, update the final cost when everything is merged
|
||||||
for (idx = 0; idx < size; ++idx) {
|
for (idx = 0; idx < image_histo->size; ++idx) {
|
||||||
|
if (histograms[idx] == NULL) continue;
|
||||||
UpdateHistogramCost(histograms[idx]);
|
UpdateHistogramCost(histograms[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -706,16 +740,9 @@ typedef struct {
|
||||||
int max_size;
|
int max_size;
|
||||||
} HistoQueue;
|
} HistoQueue;
|
||||||
|
|
||||||
static int HistoQueueInit(HistoQueue* const histo_queue, const int max_index) {
|
static int HistoQueueInit(HistoQueue* const histo_queue, const int max_size) {
|
||||||
histo_queue->size = 0;
|
histo_queue->size = 0;
|
||||||
// max_index^2 for the queue size is safe. If you look at
|
histo_queue->max_size = max_size;
|
||||||
// HistogramCombineGreedy, and imagine that UpdateQueueFront always pushes
|
|
||||||
// data to the queue, you insert at most:
|
|
||||||
// - max_index*(max_index-1)/2 (the first two for loops)
|
|
||||||
// - max_index - 1 in the last for loop at the first iteration of the while
|
|
||||||
// loop, max_index - 2 at the second iteration ... therefore
|
|
||||||
// max_index*(max_index-1)/2 overall too
|
|
||||||
histo_queue->max_size = max_index * max_index;
|
|
||||||
// We allocate max_size + 1 because the last element at index "size" is
|
// We allocate max_size + 1 because the last element at index "size" is
|
||||||
// used as temporary data (and it could be up to max_size).
|
// used as temporary data (and it could be up to max_size).
|
||||||
histo_queue->queue = (HistogramPair*)WebPSafeMalloc(
|
histo_queue->queue = (HistogramPair*)WebPSafeMalloc(
|
||||||
|
@ -778,6 +805,8 @@ static double HistoQueuePush(HistoQueue* const histo_queue,
|
||||||
const VP8LHistogram* h2;
|
const VP8LHistogram* h2;
|
||||||
HistogramPair pair;
|
HistogramPair pair;
|
||||||
|
|
||||||
|
// Stop here if the queue is full.
|
||||||
|
if (histo_queue->size == histo_queue->max_size) return 0.;
|
||||||
assert(threshold <= 0.);
|
assert(threshold <= 0.);
|
||||||
if (idx1 > idx2) {
|
if (idx1 > idx2) {
|
||||||
const int tmp = idx2;
|
const int tmp = idx2;
|
||||||
|
@ -794,8 +823,6 @@ static double HistoQueuePush(HistoQueue* const histo_queue,
|
||||||
// Do not even consider the pair if it does not improve the entropy.
|
// Do not even consider the pair if it does not improve the entropy.
|
||||||
if (pair.cost_diff >= threshold) return 0.;
|
if (pair.cost_diff >= threshold) return 0.;
|
||||||
|
|
||||||
// We cannot add more elements than the capacity.
|
|
||||||
assert(histo_queue->size < histo_queue->max_size);
|
|
||||||
histo_queue->queue[histo_queue->size++] = pair;
|
histo_queue->queue[histo_queue->size++] = pair;
|
||||||
HistoQueueUpdateHead(histo_queue, &histo_queue->queue[histo_queue->size - 1]);
|
HistoQueueUpdateHead(histo_queue, &histo_queue->queue[histo_queue->size - 1]);
|
||||||
|
|
||||||
|
@ -806,42 +833,43 @@ static double HistoQueuePush(HistoQueue* const histo_queue,
|
||||||
|
|
||||||
// Combines histograms by continuously choosing the one with the highest cost
|
// Combines histograms by continuously choosing the one with the highest cost
|
||||||
// reduction.
|
// reduction.
|
||||||
static int HistogramCombineGreedy(VP8LHistogramSet* const image_histo) {
|
static int HistogramCombineGreedy(VP8LHistogramSet* const image_histo,
|
||||||
|
int* const num_used) {
|
||||||
int ok = 0;
|
int ok = 0;
|
||||||
int image_histo_size = image_histo->size;
|
const int image_histo_size = image_histo->size;
|
||||||
int i, j;
|
int i, j;
|
||||||
VP8LHistogram** const histograms = image_histo->histograms;
|
VP8LHistogram** const histograms = image_histo->histograms;
|
||||||
// Indexes of remaining histograms.
|
|
||||||
int* const clusters =
|
|
||||||
(int*)WebPSafeMalloc(image_histo_size, sizeof(*clusters));
|
|
||||||
// Priority queue of histogram pairs.
|
// Priority queue of histogram pairs.
|
||||||
HistoQueue histo_queue;
|
HistoQueue histo_queue;
|
||||||
|
|
||||||
if (!HistoQueueInit(&histo_queue, image_histo_size) || clusters == NULL) {
|
// image_histo_size^2 for the queue size is safe. If you look at
|
||||||
|
// HistogramCombineGreedy, and imagine that UpdateQueueFront always pushes
|
||||||
|
// data to the queue, you insert at most:
|
||||||
|
// - image_histo_size*(image_histo_size-1)/2 (the first two for loops)
|
||||||
|
// - image_histo_size - 1 in the last for loop at the first iteration of
|
||||||
|
// the while loop, image_histo_size - 2 at the second iteration ...
|
||||||
|
// therefore image_histo_size*(image_histo_size-1)/2 overall too
|
||||||
|
if (!HistoQueueInit(&histo_queue, image_histo_size * image_histo_size)) {
|
||||||
goto End;
|
goto End;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < image_histo_size; ++i) {
|
for (i = 0; i < image_histo_size; ++i) {
|
||||||
// Initialize clusters indexes.
|
if (image_histo->histograms[i] == NULL) continue;
|
||||||
clusters[i] = i;
|
|
||||||
for (j = i + 1; j < image_histo_size; ++j) {
|
for (j = i + 1; j < image_histo_size; ++j) {
|
||||||
// Initialize positions array.
|
// Initialize queue.
|
||||||
|
if (image_histo->histograms[j] == NULL) continue;
|
||||||
HistoQueuePush(&histo_queue, histograms, i, j, 0.);
|
HistoQueuePush(&histo_queue, histograms, i, j, 0.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (image_histo_size > 1 && histo_queue.size > 0) {
|
while (histo_queue.size > 0) {
|
||||||
const int idx1 = histo_queue.queue[0].idx1;
|
const int idx1 = histo_queue.queue[0].idx1;
|
||||||
const int idx2 = histo_queue.queue[0].idx2;
|
const int idx2 = histo_queue.queue[0].idx2;
|
||||||
HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]);
|
HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]);
|
||||||
histograms[idx1]->bit_cost_ = histo_queue.queue[0].cost_combo;
|
histograms[idx1]->bit_cost_ = histo_queue.queue[0].cost_combo;
|
||||||
|
|
||||||
// Remove merged histogram.
|
// Remove merged histogram.
|
||||||
for (i = 0; i + 1 < image_histo_size; ++i) {
|
HistogramSetRemoveHistogram(image_histo, idx2, num_used);
|
||||||
if (clusters[i] >= idx2) {
|
|
||||||
clusters[i] = clusters[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
--image_histo_size;
|
|
||||||
|
|
||||||
// Remove pairs intersecting the just combined best pair.
|
// Remove pairs intersecting the just combined best pair.
|
||||||
for (i = 0; i < histo_queue.size;) {
|
for (i = 0; i < histo_queue.size;) {
|
||||||
|
@ -856,24 +884,15 @@ static int HistogramCombineGreedy(VP8LHistogramSet* const image_histo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push new pairs formed with combined histogram to the queue.
|
// Push new pairs formed with combined histogram to the queue.
|
||||||
for (i = 0; i < image_histo_size; ++i) {
|
for (i = 0; i < image_histo->size; ++i) {
|
||||||
if (clusters[i] != idx1) {
|
if (i == idx1 || image_histo->histograms[i] == NULL) continue;
|
||||||
HistoQueuePush(&histo_queue, histograms, idx1, clusters[i], 0.);
|
HistoQueuePush(&histo_queue, image_histo->histograms, idx1, i, 0.);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move remaining histograms to the beginning of the array.
|
|
||||||
for (i = 0; i < image_histo_size; ++i) {
|
|
||||||
if (i != clusters[i]) { // swap the two histograms
|
|
||||||
HistogramSwap(&histograms[i], &histograms[clusters[i]]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image_histo->size = image_histo_size;
|
|
||||||
ok = 1;
|
ok = 1;
|
||||||
|
|
||||||
End:
|
End:
|
||||||
WebPSafeFree(clusters);
|
|
||||||
HistoQueueClear(&histo_queue);
|
HistoQueueClear(&histo_queue);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
@ -881,47 +900,69 @@ static int HistogramCombineGreedy(VP8LHistogramSet* const image_histo) {
|
||||||
// Perform histogram aggregation using a stochastic approach.
|
// Perform histogram aggregation using a stochastic approach.
|
||||||
// 'do_greedy' is set to 1 if a greedy approach needs to be performed
|
// 'do_greedy' is set to 1 if a greedy approach needs to be performed
|
||||||
// afterwards, 0 otherwise.
|
// afterwards, 0 otherwise.
|
||||||
|
static int PairComparison(const void* idx1, const void* idx2) {
|
||||||
|
// To be used with bsearch: <0 when *idx1<*idx2, >0 if >, 0 when ==.
|
||||||
|
return (*(int*) idx1 - *(int*) idx2);
|
||||||
|
}
|
||||||
static int HistogramCombineStochastic(VP8LHistogramSet* const image_histo,
|
static int HistogramCombineStochastic(VP8LHistogramSet* const image_histo,
|
||||||
int min_cluster_size,
|
int* const num_used, int min_cluster_size,
|
||||||
int* const do_greedy) {
|
int* const do_greedy) {
|
||||||
int iter;
|
int j, iter;
|
||||||
uint32_t seed = 1;
|
uint32_t seed = 1;
|
||||||
int tries_with_no_success = 0;
|
int tries_with_no_success = 0;
|
||||||
int image_histo_size = image_histo->size;
|
const int outer_iters = *num_used;
|
||||||
const int outer_iters = image_histo_size;
|
|
||||||
const int num_tries_no_success = outer_iters / 2;
|
const int num_tries_no_success = outer_iters / 2;
|
||||||
VP8LHistogram** const histograms = image_histo->histograms;
|
VP8LHistogram** const histograms = image_histo->histograms;
|
||||||
// Priority queue of histogram pairs. Its size of "kCostHeapSizeSqrt"^2
|
// Priority queue of histogram pairs. Its size of 'kHistoQueueSize'
|
||||||
// impacts the quality of the compression and the speed: the smaller the
|
// impacts the quality of the compression and the speed: the smaller the
|
||||||
// faster but the worse for the compression.
|
// faster but the worse for the compression.
|
||||||
HistoQueue histo_queue;
|
HistoQueue histo_queue;
|
||||||
const int kHistoQueueSizeSqrt = 3;
|
const int kHistoQueueSize = 9;
|
||||||
int ok = 0;
|
int ok = 0;
|
||||||
|
// mapping from an index in image_histo with no NULL histogram to the full
|
||||||
|
// blown image_histo.
|
||||||
|
int* mappings;
|
||||||
|
|
||||||
if (!HistoQueueInit(&histo_queue, kHistoQueueSizeSqrt)) {
|
if (*num_used < min_cluster_size) {
|
||||||
|
*do_greedy = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings = (int*) WebPSafeMalloc(*num_used, sizeof(*mappings));
|
||||||
|
if (mappings == NULL || !HistoQueueInit(&histo_queue, kHistoQueueSize)) {
|
||||||
goto End;
|
goto End;
|
||||||
}
|
}
|
||||||
|
// Fill the initial mapping.
|
||||||
|
for (j = 0, iter = 0; iter < image_histo->size; ++iter) {
|
||||||
|
if (histograms[iter] == NULL) continue;
|
||||||
|
mappings[j++] = iter;
|
||||||
|
}
|
||||||
|
assert(j == *num_used);
|
||||||
|
|
||||||
// Collapse similar histograms in 'image_histo'.
|
// Collapse similar histograms in 'image_histo'.
|
||||||
++min_cluster_size;
|
for (iter = 0;
|
||||||
for (iter = 0; iter < outer_iters && image_histo_size >= min_cluster_size &&
|
iter < outer_iters && *num_used >= min_cluster_size &&
|
||||||
++tries_with_no_success < num_tries_no_success;
|
++tries_with_no_success < num_tries_no_success;
|
||||||
++iter) {
|
++iter) {
|
||||||
|
int* mapping_index;
|
||||||
double best_cost =
|
double best_cost =
|
||||||
(histo_queue.size == 0) ? 0. : histo_queue.queue[0].cost_diff;
|
(histo_queue.size == 0) ? 0. : histo_queue.queue[0].cost_diff;
|
||||||
int best_idx1 = -1, best_idx2 = 1;
|
int best_idx1 = -1, best_idx2 = 1;
|
||||||
int j;
|
const uint32_t rand_range = (*num_used - 1) * (*num_used);
|
||||||
const uint32_t rand_range = (image_histo_size - 1) * image_histo_size;
|
// (*num_used) / 2 was chosen empirically. Less means faster but worse
|
||||||
// image_histo_size / 2 was chosen empirically. Less means faster but worse
|
|
||||||
// compression.
|
// compression.
|
||||||
const int num_tries = image_histo_size / 2;
|
const int num_tries = (*num_used) / 2;
|
||||||
|
|
||||||
for (j = 0; j < num_tries; ++j) {
|
// Pick random samples.
|
||||||
|
for (j = 0; *num_used >= 2 && j < num_tries; ++j) {
|
||||||
double curr_cost;
|
double curr_cost;
|
||||||
// Choose two different histograms at random and try to combine them.
|
// Choose two different histograms at random and try to combine them.
|
||||||
const uint32_t tmp = MyRand(&seed) % rand_range;
|
const uint32_t tmp = MyRand(&seed) % rand_range;
|
||||||
const uint32_t idx1 = tmp / (image_histo_size - 1);
|
uint32_t idx1 = tmp / (*num_used - 1);
|
||||||
uint32_t idx2 = tmp % (image_histo_size - 1);
|
uint32_t idx2 = tmp % (*num_used - 1);
|
||||||
if (idx2 >= idx1) ++idx2;
|
if (idx2 >= idx1) ++idx2;
|
||||||
|
idx1 = mappings[idx1];
|
||||||
|
idx2 = mappings[idx2];
|
||||||
|
|
||||||
// Calculate cost reduction on combination.
|
// Calculate cost reduction on combination.
|
||||||
curr_cost =
|
curr_cost =
|
||||||
|
@ -934,18 +975,21 @@ static int HistogramCombineStochastic(VP8LHistogramSet* const image_histo,
|
||||||
}
|
}
|
||||||
if (histo_queue.size == 0) continue;
|
if (histo_queue.size == 0) continue;
|
||||||
|
|
||||||
// Merge the two best histograms.
|
// Get the best histograms.
|
||||||
best_idx1 = histo_queue.queue[0].idx1;
|
best_idx1 = histo_queue.queue[0].idx1;
|
||||||
best_idx2 = histo_queue.queue[0].idx2;
|
best_idx2 = histo_queue.queue[0].idx2;
|
||||||
assert(best_idx1 < best_idx2);
|
assert(best_idx1 < best_idx2);
|
||||||
HistogramAddEval(histograms[best_idx1], histograms[best_idx2],
|
// Pop best_idx2 from mappings.
|
||||||
histograms[best_idx1], 0);
|
mapping_index = (int*) bsearch(&best_idx2, mappings, *num_used,
|
||||||
// Swap the best_idx2 histogram with the last one (which is now unused).
|
sizeof(best_idx2), &PairComparison);
|
||||||
--image_histo_size;
|
assert(mapping_index != NULL);
|
||||||
if (best_idx2 != image_histo_size) {
|
memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *
|
||||||
HistogramSwap(&histograms[image_histo_size], &histograms[best_idx2]);
|
((*num_used) - (mapping_index - mappings) - 1));
|
||||||
}
|
// Merge the histograms and remove best_idx2 from the queue.
|
||||||
histograms[image_histo_size] = NULL;
|
HistogramAdd(histograms[best_idx2], histograms[best_idx1],
|
||||||
|
histograms[best_idx1]);
|
||||||
|
histograms[best_idx1]->bit_cost_ = histo_queue.queue[0].cost_combo;
|
||||||
|
HistogramSetRemoveHistogram(image_histo, best_idx2, num_used);
|
||||||
// Parse the queue and update each pair that deals with best_idx1,
|
// Parse the queue and update each pair that deals with best_idx1,
|
||||||
// best_idx2 or image_histo_size.
|
// best_idx2 or image_histo_size.
|
||||||
for (j = 0; j < histo_queue.size;) {
|
for (j = 0; j < histo_queue.size;) {
|
||||||
|
@ -968,12 +1012,6 @@ static int HistogramCombineStochastic(VP8LHistogramSet* const image_histo,
|
||||||
p->idx2 = best_idx1;
|
p->idx2 = best_idx1;
|
||||||
do_eval = 1;
|
do_eval = 1;
|
||||||
}
|
}
|
||||||
if (p->idx2 == image_histo_size) {
|
|
||||||
// No need to re-evaluate here as it does not involve a pair
|
|
||||||
// containing best_idx1 or best_idx2.
|
|
||||||
p->idx2 = best_idx2;
|
|
||||||
}
|
|
||||||
assert(p->idx2 < image_histo_size);
|
|
||||||
// Make sure the index order is respected.
|
// Make sure the index order is respected.
|
||||||
if (p->idx1 > p->idx2) {
|
if (p->idx1 > p->idx2) {
|
||||||
const int tmp = p->idx2;
|
const int tmp = p->idx2;
|
||||||
|
@ -991,15 +1029,14 @@ static int HistogramCombineStochastic(VP8LHistogramSet* const image_histo,
|
||||||
HistoQueueUpdateHead(&histo_queue, p);
|
HistoQueueUpdateHead(&histo_queue, p);
|
||||||
++j;
|
++j;
|
||||||
}
|
}
|
||||||
|
|
||||||
tries_with_no_success = 0;
|
tries_with_no_success = 0;
|
||||||
}
|
}
|
||||||
image_histo->size = image_histo_size;
|
*do_greedy = (*num_used <= min_cluster_size);
|
||||||
*do_greedy = (image_histo->size <= min_cluster_size);
|
|
||||||
ok = 1;
|
ok = 1;
|
||||||
|
|
||||||
End:
|
End:
|
||||||
HistoQueueClear(&histo_queue);
|
HistoQueueClear(&histo_queue);
|
||||||
|
WebPSafeFree(mappings);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1007,23 +1044,29 @@ End:
|
||||||
// Histogram refinement
|
// Histogram refinement
|
||||||
|
|
||||||
// Find the best 'out' histogram for each of the 'in' histograms.
|
// Find the best 'out' histogram for each of the 'in' histograms.
|
||||||
|
// At call-time, 'out' contains the histograms of the clusters.
|
||||||
// Note: we assume that out[]->bit_cost_ is already up-to-date.
|
// Note: we assume that out[]->bit_cost_ is already up-to-date.
|
||||||
static void HistogramRemap(const VP8LHistogramSet* const in,
|
static void HistogramRemap(const VP8LHistogramSet* const in,
|
||||||
const VP8LHistogramSet* const out,
|
VP8LHistogramSet* const out,
|
||||||
uint16_t* const symbols) {
|
uint16_t* const symbols) {
|
||||||
int i;
|
int i;
|
||||||
VP8LHistogram** const in_histo = in->histograms;
|
VP8LHistogram** const in_histo = in->histograms;
|
||||||
VP8LHistogram** const out_histo = out->histograms;
|
VP8LHistogram** const out_histo = out->histograms;
|
||||||
const int in_size = in->size;
|
const int in_size = out->max_size;
|
||||||
const int out_size = out->size;
|
const int out_size = out->size;
|
||||||
if (out_size > 1) {
|
if (out_size > 1) {
|
||||||
for (i = 0; i < in_size; ++i) {
|
for (i = 0; i < in_size; ++i) {
|
||||||
int best_out = 0;
|
int best_out = 0;
|
||||||
double best_bits = MAX_COST;
|
double best_bits = MAX_COST;
|
||||||
int k;
|
int k;
|
||||||
|
if (in_histo[i] == NULL) {
|
||||||
|
// Arbitrarily set to the previous value if unused to help future LZ77.
|
||||||
|
symbols[i] = symbols[i - 1];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (k = 0; k < out_size; ++k) {
|
for (k = 0; k < out_size; ++k) {
|
||||||
const double cur_bits =
|
double cur_bits;
|
||||||
HistogramAddThresh(out_histo[k], in_histo[i], best_bits);
|
cur_bits = HistogramAddThresh(out_histo[k], in_histo[i], best_bits);
|
||||||
if (k == 0 || cur_bits < best_bits) {
|
if (k == 0 || cur_bits < best_bits) {
|
||||||
best_bits = cur_bits;
|
best_bits = cur_bits;
|
||||||
best_out = k;
|
best_out = k;
|
||||||
|
@ -1039,12 +1082,13 @@ static void HistogramRemap(const VP8LHistogramSet* const in,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recompute each out based on raw and symbols.
|
// Recompute each out based on raw and symbols.
|
||||||
for (i = 0; i < out_size; ++i) {
|
VP8LHistogramSetClear(out);
|
||||||
HistogramClear(out_histo[i]);
|
out->size = out_size;
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < in_size; ++i) {
|
for (i = 0; i < in_size; ++i) {
|
||||||
const int idx = symbols[i];
|
int idx;
|
||||||
|
if (in_histo[i] == NULL) continue;
|
||||||
|
idx = symbols[i];
|
||||||
HistogramAdd(in_histo[i], out_histo[idx], out_histo[idx]);
|
HistogramAdd(in_histo[i], out_histo[idx], out_histo[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1060,6 +1104,70 @@ static double GetCombineCostFactor(int histo_size, int quality) {
|
||||||
return combine_cost_factor;
|
return combine_cost_factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given a HistogramSet 'set', the mapping of clusters 'cluster_mapping' and the
|
||||||
|
// current assignment of the cells in 'symbols', merge the clusters and
|
||||||
|
// assign the smallest possible clusters values.
|
||||||
|
static void OptimizeHistogramSymbols(const VP8LHistogramSet* const set,
|
||||||
|
uint16_t* const cluster_mappings,
|
||||||
|
int num_clusters,
|
||||||
|
uint16_t* const cluster_mappings_tmp,
|
||||||
|
uint16_t* const symbols) {
|
||||||
|
int i, cluster_max;
|
||||||
|
int do_continue = 1;
|
||||||
|
// First, assign the lowest cluster to each pixel.
|
||||||
|
while (do_continue) {
|
||||||
|
do_continue = 0;
|
||||||
|
for (i = 0; i < num_clusters; ++i) {
|
||||||
|
int k;
|
||||||
|
k = cluster_mappings[i];
|
||||||
|
while (k != cluster_mappings[k]) {
|
||||||
|
cluster_mappings[k] = cluster_mappings[cluster_mappings[k]];
|
||||||
|
k = cluster_mappings[k];
|
||||||
|
}
|
||||||
|
if (k != cluster_mappings[i]) {
|
||||||
|
do_continue = 1;
|
||||||
|
cluster_mappings[i] = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create a mapping from a cluster id to its minimal version.
|
||||||
|
cluster_max = 0;
|
||||||
|
memset(cluster_mappings_tmp, 0,
|
||||||
|
set->max_size * sizeof(*cluster_mappings_tmp));
|
||||||
|
assert(cluster_mappings[0] == 0);
|
||||||
|
// Re-map the ids.
|
||||||
|
for (i = 0; i < set->max_size; ++i) {
|
||||||
|
int cluster;
|
||||||
|
if (symbols[i] == kInvalidHistogramSymbol) continue;
|
||||||
|
cluster = cluster_mappings[symbols[i]];
|
||||||
|
assert(symbols[i] < num_clusters);
|
||||||
|
if (cluster > 0 && cluster_mappings_tmp[cluster] == 0) {
|
||||||
|
++cluster_max;
|
||||||
|
cluster_mappings_tmp[cluster] = cluster_max;
|
||||||
|
}
|
||||||
|
symbols[i] = cluster_mappings_tmp[cluster];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure all cluster values are used.
|
||||||
|
cluster_max = 0;
|
||||||
|
for (i = 0; i < set->max_size; ++i) {
|
||||||
|
if (symbols[i] == kInvalidHistogramSymbol) continue;
|
||||||
|
if (symbols[i] <= cluster_max) continue;
|
||||||
|
++cluster_max;
|
||||||
|
assert(symbols[i] == cluster_max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RemoveEmptyHistograms(VP8LHistogramSet* const image_histo) {
|
||||||
|
uint32_t size;
|
||||||
|
int i;
|
||||||
|
for (i = 0, size = 0; i < image_histo->size; ++i) {
|
||||||
|
if (image_histo->histograms[i] == NULL) continue;
|
||||||
|
image_histo->histograms[size++] = image_histo->histograms[i];
|
||||||
|
}
|
||||||
|
image_histo->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
int VP8LGetHistoImageSymbols(int xsize, int ysize,
|
int VP8LGetHistoImageSymbols(int xsize, int ysize,
|
||||||
const VP8LBackwardRefs* const refs,
|
const VP8LBackwardRefs* const refs,
|
||||||
int quality, int low_effort,
|
int quality, int low_effort,
|
||||||
|
@ -1078,27 +1186,36 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize,
|
||||||
// maximum quality q==100 (to preserve the compression gains at that level).
|
// maximum quality q==100 (to preserve the compression gains at that level).
|
||||||
const int entropy_combine_num_bins = low_effort ? NUM_PARTITIONS : BIN_SIZE;
|
const int entropy_combine_num_bins = low_effort ? NUM_PARTITIONS : BIN_SIZE;
|
||||||
int entropy_combine;
|
int entropy_combine;
|
||||||
|
uint16_t* const map_tmp =
|
||||||
if (orig_histo == NULL) goto Error;
|
WebPSafeMalloc(2 * image_histo_raw_size, sizeof(map_tmp));
|
||||||
|
uint16_t* const cluster_mappings = map_tmp + image_histo_raw_size;
|
||||||
|
int num_used = image_histo_raw_size;
|
||||||
|
if (orig_histo == NULL || map_tmp == NULL) goto Error;
|
||||||
|
|
||||||
// Construct the histograms from backward references.
|
// Construct the histograms from backward references.
|
||||||
HistogramBuild(xsize, histo_bits, refs, orig_histo);
|
HistogramBuild(xsize, histo_bits, refs, orig_histo);
|
||||||
// Copies the histograms and computes its bit_cost.
|
// Copies the histograms and computes its bit_cost.
|
||||||
HistogramCopyAndAnalyze(orig_histo, image_histo);
|
// histogram_symbols is optimized
|
||||||
|
HistogramCopyAndAnalyze(orig_histo, image_histo, &num_used,
|
||||||
|
histogram_symbols);
|
||||||
|
|
||||||
entropy_combine =
|
entropy_combine =
|
||||||
(image_histo->size > entropy_combine_num_bins * 2) && (quality < 100);
|
(num_used > entropy_combine_num_bins * 2) && (quality < 100);
|
||||||
|
|
||||||
if (entropy_combine) {
|
if (entropy_combine) {
|
||||||
const int bin_map_size = image_histo->size;
|
uint16_t* const bin_map = map_tmp;
|
||||||
// Reuse histogram_symbols storage. By definition, it's guaranteed to be ok.
|
|
||||||
uint16_t* const bin_map = histogram_symbols;
|
|
||||||
const double combine_cost_factor =
|
const double combine_cost_factor =
|
||||||
GetCombineCostFactor(image_histo_raw_size, quality);
|
GetCombineCostFactor(image_histo_raw_size, quality);
|
||||||
|
const uint32_t num_clusters = num_used;
|
||||||
|
|
||||||
HistogramAnalyzeEntropyBin(image_histo, bin_map, low_effort);
|
HistogramAnalyzeEntropyBin(image_histo, bin_map, low_effort);
|
||||||
// Collapse histograms with similar entropy.
|
// Collapse histograms with similar entropy.
|
||||||
HistogramCombineEntropyBin(image_histo, tmp_histo, bin_map, bin_map_size,
|
HistogramCombineEntropyBin(image_histo, &num_used, histogram_symbols,
|
||||||
|
cluster_mappings, tmp_histo, bin_map,
|
||||||
entropy_combine_num_bins, combine_cost_factor,
|
entropy_combine_num_bins, combine_cost_factor,
|
||||||
low_effort);
|
low_effort);
|
||||||
|
OptimizeHistogramSymbols(image_histo, cluster_mappings, num_clusters,
|
||||||
|
map_tmp, histogram_symbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't combine the histograms using stochastic and greedy heuristics for
|
// Don't combine the histograms using stochastic and greedy heuristics for
|
||||||
|
@ -1108,21 +1225,26 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize,
|
||||||
// cubic ramp between 1 and MAX_HISTO_GREEDY:
|
// cubic ramp between 1 and MAX_HISTO_GREEDY:
|
||||||
const int threshold_size = (int)(1 + (x * x * x) * (MAX_HISTO_GREEDY - 1));
|
const int threshold_size = (int)(1 + (x * x * x) * (MAX_HISTO_GREEDY - 1));
|
||||||
int do_greedy;
|
int do_greedy;
|
||||||
if (!HistogramCombineStochastic(image_histo, threshold_size, &do_greedy)) {
|
if (!HistogramCombineStochastic(image_histo, &num_used, threshold_size,
|
||||||
|
&do_greedy)) {
|
||||||
goto Error;
|
goto Error;
|
||||||
}
|
}
|
||||||
if (do_greedy && !HistogramCombineGreedy(image_histo)) {
|
if (do_greedy) {
|
||||||
goto Error;
|
RemoveEmptyHistograms(image_histo);
|
||||||
|
if (!HistogramCombineGreedy(image_histo, &num_used)) {
|
||||||
|
goto Error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(vrabaud): Optimize HistogramRemap for low-effort compression mode.
|
|
||||||
// Find the optimal map from original histograms to the final ones.
|
// Find the optimal map from original histograms to the final ones.
|
||||||
|
RemoveEmptyHistograms(image_histo);
|
||||||
HistogramRemap(orig_histo, image_histo, histogram_symbols);
|
HistogramRemap(orig_histo, image_histo, histogram_symbols);
|
||||||
|
|
||||||
ok = 1;
|
ok = 1;
|
||||||
|
|
||||||
Error:
|
Error:
|
||||||
VP8LFreeHistogramSet(orig_histo);
|
VP8LFreeHistogramSet(orig_histo);
|
||||||
|
WebPSafeFree(map_tmp);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,12 +177,15 @@ static uint8_t NearLosslessComponent(uint8_t value, uint8_t predict,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static WEBP_INLINE uint8_t NearLosslessDiff(uint8_t a, uint8_t b) {
|
||||||
|
return (uint8_t)((((int)(a) - (int)(b))) & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
// Quantize every component of the difference between the actual pixel value and
|
// Quantize every component of the difference between the actual pixel value and
|
||||||
// its prediction to a multiple of a quantization (a power of 2, not larger than
|
// its prediction to a multiple of a quantization (a power of 2, not larger than
|
||||||
// max_quantization which is a power of 2, smaller than max_diff). Take care if
|
// max_quantization which is a power of 2, smaller than max_diff). Take care if
|
||||||
// value and predict have undergone subtract green, which means that red and
|
// value and predict have undergone subtract green, which means that red and
|
||||||
// blue are represented as offsets from green.
|
// blue are represented as offsets from green.
|
||||||
#define NEAR_LOSSLESS_DIFF(a, b) (uint8_t)((((int)(a) - (int)(b))) & 0xff)
|
|
||||||
static uint32_t NearLossless(uint32_t value, uint32_t predict,
|
static uint32_t NearLossless(uint32_t value, uint32_t predict,
|
||||||
int max_quantization, int max_diff,
|
int max_quantization, int max_diff,
|
||||||
int used_subtract_green) {
|
int used_subtract_green) {
|
||||||
|
@ -199,7 +202,7 @@ static uint32_t NearLossless(uint32_t value, uint32_t predict,
|
||||||
}
|
}
|
||||||
if ((value >> 24) == 0 || (value >> 24) == 0xff) {
|
if ((value >> 24) == 0 || (value >> 24) == 0xff) {
|
||||||
// Preserve transparency of fully transparent or fully opaque pixels.
|
// Preserve transparency of fully transparent or fully opaque pixels.
|
||||||
a = NEAR_LOSSLESS_DIFF(value >> 24, predict >> 24);
|
a = NearLosslessDiff(value >> 24, predict >> 24);
|
||||||
} else {
|
} else {
|
||||||
a = NearLosslessComponent(value >> 24, predict >> 24, 0xff, quantization);
|
a = NearLosslessComponent(value >> 24, predict >> 24, 0xff, quantization);
|
||||||
}
|
}
|
||||||
|
@ -212,16 +215,15 @@ static uint32_t NearLossless(uint32_t value, uint32_t predict,
|
||||||
// The amount by which green has been adjusted during quantization. It is
|
// The amount by which green has been adjusted during quantization. It is
|
||||||
// subtracted from red and blue for compensation, to avoid accumulating two
|
// subtracted from red and blue for compensation, to avoid accumulating two
|
||||||
// quantization errors in them.
|
// quantization errors in them.
|
||||||
green_diff = NEAR_LOSSLESS_DIFF(new_green, value >> 8);
|
green_diff = NearLosslessDiff(new_green, value >> 8);
|
||||||
}
|
}
|
||||||
r = NearLosslessComponent(NEAR_LOSSLESS_DIFF(value >> 16, green_diff),
|
r = NearLosslessComponent(NearLosslessDiff(value >> 16, green_diff),
|
||||||
(predict >> 16) & 0xff, 0xff - new_green,
|
(predict >> 16) & 0xff, 0xff - new_green,
|
||||||
quantization);
|
quantization);
|
||||||
b = NearLosslessComponent(NEAR_LOSSLESS_DIFF(value, green_diff),
|
b = NearLosslessComponent(NearLosslessDiff(value, green_diff),
|
||||||
predict & 0xff, 0xff - new_green, quantization);
|
predict & 0xff, 0xff - new_green, quantization);
|
||||||
return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||||
}
|
}
|
||||||
#undef NEAR_LOSSLESS_DIFF
|
|
||||||
#endif // (WEBP_NEAR_LOSSLESS == 1)
|
#endif // (WEBP_NEAR_LOSSLESS == 1)
|
||||||
|
|
||||||
// Stores the difference between the pixel and its prediction in "out".
|
// Stores the difference between the pixel and its prediction in "out".
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdlib.h> // for abs()
|
#include <stdlib.h> // for abs()
|
||||||
|
|
||||||
|
#include "src/dsp/quant.h"
|
||||||
#include "src/enc/vp8i_enc.h"
|
#include "src/enc/vp8i_enc.h"
|
||||||
#include "src/enc/cost_enc.h"
|
#include "src/enc/cost_enc.h"
|
||||||
|
|
||||||
|
@ -977,19 +978,6 @@ static void SwapOut(VP8EncIterator* const it) {
|
||||||
SwapPtr(&it->yuv_out_, &it->yuv_out2_);
|
SwapPtr(&it->yuv_out_, &it->yuv_out2_);
|
||||||
}
|
}
|
||||||
|
|
||||||
static score_t IsFlat(const int16_t* levels, int num_blocks, score_t thresh) {
|
|
||||||
score_t score = 0;
|
|
||||||
while (num_blocks-- > 0) { // TODO(skal): refine positional scoring?
|
|
||||||
int i;
|
|
||||||
for (i = 1; i < 16; ++i) { // omit DC, we're only interested in AC
|
|
||||||
score += (levels[i] != 0);
|
|
||||||
if (score > thresh) return 0;
|
|
||||||
}
|
|
||||||
levels += 16;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PickBestIntra16(VP8EncIterator* const it, VP8ModeScore* rd) {
|
static void PickBestIntra16(VP8EncIterator* const it, VP8ModeScore* rd) {
|
||||||
const int kNumBlocks = 16;
|
const int kNumBlocks = 16;
|
||||||
VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_];
|
VP8SegmentInfo* const dqm = &it->enc_->dqm_[it->mb_->segment_];
|
||||||
|
|
|
@ -32,7 +32,7 @@ extern "C" {
|
||||||
// version numbers
|
// version numbers
|
||||||
#define ENC_MAJ_VERSION 1
|
#define ENC_MAJ_VERSION 1
|
||||||
#define ENC_MIN_VERSION 0
|
#define ENC_MIN_VERSION 0
|
||||||
#define ENC_REV_VERSION 1
|
#define ENC_REV_VERSION 2
|
||||||
|
|
||||||
enum { MAX_LF_LEVELS = 64, // Maximum loop filter level
|
enum { MAX_LF_LEVELS = 64, // Maximum loop filter level
|
||||||
MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost
|
MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost
|
||||||
|
|
|
@ -462,6 +462,7 @@ static int GetHuffBitLengthsAndCodes(
|
||||||
for (i = 0; i < histogram_image_size; ++i) {
|
for (i = 0; i < histogram_image_size; ++i) {
|
||||||
const VP8LHistogram* const histo = histogram_image->histograms[i];
|
const VP8LHistogram* const histo = histogram_image->histograms[i];
|
||||||
HuffmanTreeCode* const codes = &huffman_codes[5 * i];
|
HuffmanTreeCode* const codes = &huffman_codes[5 * i];
|
||||||
|
assert(histo != NULL);
|
||||||
for (k = 0; k < 5; ++k) {
|
for (k = 0; k < 5; ++k) {
|
||||||
const int num_symbols =
|
const int num_symbols =
|
||||||
(k == 0) ? VP8LHistogramNumCodes(histo->palette_code_bits_) :
|
(k == 0) ? VP8LHistogramNumCodes(histo->palette_code_bits_) :
|
||||||
|
|
|
@ -29,7 +29,7 @@ extern "C" {
|
||||||
|
|
||||||
#define MUX_MAJ_VERSION 1
|
#define MUX_MAJ_VERSION 1
|
||||||
#define MUX_MIN_VERSION 0
|
#define MUX_MIN_VERSION 0
|
||||||
#define MUX_REV_VERSION 1
|
#define MUX_REV_VERSION 2
|
||||||
|
|
||||||
// Chunk object.
|
// Chunk object.
|
||||||
typedef struct WebPChunk WebPChunk;
|
typedef struct WebPChunk WebPChunk;
|
||||||
|
|
|
@ -248,6 +248,7 @@ int VP8LBitWriterClone(const VP8LBitWriter* const src,
|
||||||
dst->bits_ = src->bits_;
|
dst->bits_ = src->bits_;
|
||||||
dst->used_ = src->used_;
|
dst->used_ = src->used_;
|
||||||
dst->error_ = src->error_;
|
dst->error_ = src->error_;
|
||||||
|
dst->cur_ = dst->buf_ + current_size;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,19 +107,6 @@ static WEBP_INLINE void PutLE32(uint8_t* const data, uint32_t val) {
|
||||||
PutLE16(data + 2, (int)(val >> 16));
|
PutLE16(data + 2, (int)(val >> 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns (int)floor(log2(n)). n must be > 0.
|
// Returns (int)floor(log2(n)). n must be > 0.
|
||||||
// use GNU builtins where available.
|
// use GNU builtins where available.
|
||||||
#if defined(__GNUC__) && \
|
#if defined(__GNUC__) && \
|
||||||
|
@ -138,6 +125,19 @@ static WEBP_INLINE int BitsLog2Floor(uint32_t n) {
|
||||||
return first_set_bit;
|
return first_set_bit;
|
||||||
}
|
}
|
||||||
#else // default: use the C-version.
|
#else // default: use the C-version.
|
||||||
|
// 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 BitsLog2Floor(uint32_t n) { return WebPLog2FloorC(n); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,12 @@ WEBP_EXTERN int WebPGetDecoderVersion(void);
|
||||||
// This function will also validate the header, returning true on success,
|
// This function will also validate the header, returning true on success,
|
||||||
// false otherwise. '*width' and '*height' are only valid on successful return.
|
// false otherwise. '*width' and '*height' are only valid on successful return.
|
||||||
// Pointers 'width' and 'height' can be passed NULL if deemed irrelevant.
|
// 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_EXTERN int WebPGetInfo(const uint8_t* data, size_t data_size,
|
WEBP_EXTERN int WebPGetInfo(const uint8_t* data, size_t data_size,
|
||||||
int* width, int* height);
|
int* width, int* height);
|
||||||
|
|
||||||
|
@ -425,6 +431,12 @@ WEBP_EXTERN VP8StatusCode WebPGetFeaturesInternal(
|
||||||
// Returns VP8_STATUS_OK when the features are successfully retrieved. Returns
|
// Returns VP8_STATUS_OK when the features are successfully retrieved. Returns
|
||||||
// VP8_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the
|
// VP8_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the
|
||||||
// features from headers. Returns error in other cases.
|
// 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(
|
static WEBP_INLINE VP8StatusCode WebPGetFeatures(
|
||||||
const uint8_t* data, size_t data_size,
|
const uint8_t* data, size_t data_size,
|
||||||
WebPBitstreamFeatures* features) {
|
WebPBitstreamFeatures* features) {
|
||||||
|
|
Loading…
Reference in New Issue