Merge branch 'lossless_encoder'

* lossless_encoder: (46 commits)
  split StoreHuffmanCode() into smaller functions
  more consolidation: introduce VP8LHistogramSet
  big code clean-up and refactoring and optimization
  Some cosmetics in histogram.c
  Approximate FastLog between value range [256, 8192]
  Forgot to update out_bit_costs to symbol_bit_costs at one instance.
  Evaluate output cluster's bit_costs once in HistogramRefine.
  Simple Huffman code changes.
  Lossless decoder: remove an unneeded param in ReadHuffmanCodeLengths().
  Reducing emerging palette size from 11 to 9 bits.
  Move GetHistImageSymbols to histogram.c
  Improve predict vs no-predict heuristic.
  code-moving and clean-up
  reduce memory usage by allocating only one histo
  Restrict histo_bits to ensure histo_image size is under 32MB
  further simplification for the meta-Huffman coding
  A quick pass of cleanup in backward reference code
  Make transform bits a function of encode method (-m).
  introduce -lossless option, protected by USE_LOSSLESS_ENCODER
  Run TraceBackwards for higher qualities.
  ...

Conflicts:
	src/enc/webpenc.c

Change-Id: I9a5d98cba0889ea91d10699466939cc283da345a
This commit is contained in:
James Zern
2012-05-07 14:27:17 -07:00
20 changed files with 4407 additions and 295 deletions

View File

@ -0,0 +1,748 @@
// Copyright 2012 Google Inc. All Rights Reserved.
//
// This code is licensed under the same terms as WebM:
// Software License Agreement: http://www.webmproject.org/license/software/
// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
// -----------------------------------------------------------------------------
//
// Author: Jyrki Alakuijala (jyrki@google.com)
//
#ifdef USE_LOSSLESS_ENCODER
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include "./backward_references.h"
#include "./histogram.h"
#include "../utils/color_cache.h"
#define VALUES_IN_BYTE 256
static const uint8_t plane_to_code_lut[128] = {
96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255,
101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79,
102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87,
105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91,
110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100,
115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109,
118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114,
119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117,
};
static const int kMinLength = 2;
int VP8LDistanceToPlaneCode(int xsize, int dist) {
const int yoffset = dist / xsize;
const int xoffset = dist - yoffset * xsize;
if (xoffset <= 8 && yoffset < 8) {
return plane_to_code_lut[yoffset * 16 + 8 - xoffset] + 1;
} else if (xoffset > xsize - 8 && yoffset < 7) {
return plane_to_code_lut[(yoffset + 1) * 16 + 8 + (xsize - xoffset)] + 1;
}
return dist + 120;
}
static WEBP_INLINE int FindMatchLength(const uint32_t* const array1,
const uint32_t* const array2,
const int max_limit) {
int match_len = 0;
while (match_len < max_limit && array1[match_len] == array2[match_len]) {
++match_len;
}
return match_len;
}
#define HASH_BITS 18
#define HASH_SIZE (1 << HASH_BITS)
static const uint64_t kHashMultiplier = 0xc6a4a7935bd1e995ULL;
static const int kWindowSize = (1 << 20) - 120; // A window with 1M pixels
// (4 megabytes) - 120
// special codes for short
// distances.
static WEBP_INLINE uint64_t GetHash64(uint64_t num) {
num *= kHashMultiplier;
num >>= 64 - HASH_BITS;
return num;
}
static WEBP_INLINE uint64_t GetPixPair(const uint32_t* const argb) {
return ((uint64_t)(argb[1]) << 32) | argb[0];
}
typedef struct {
// Stores the most recently added position with the given hash value.
int32_t hash_to_first_index_[HASH_SIZE];
// chain_[pos] stores the previous position with the same hash value
// for every pixel in the image.
int32_t* chain_;
} VP8LHashChain;
static int VP8LHashChainInit(VP8LHashChain* const p, int size) {
int i;
p->chain_ = (int*)malloc(size * sizeof(*p->chain_));
if (p->chain_ == NULL) {
return 0;
}
for (i = 0; i < size; ++i) {
p->chain_[i] = -1;
}
for (i = 0; i < HASH_SIZE; ++i) {
p->hash_to_first_index_[i] = -1;
}
return 1;
}
static void VP8LHashChainClear(VP8LHashChain* const p) {
if (p != NULL) {
free(p->chain_);
}
}
static void VP8LHashChainInsert(VP8LHashChain* const p,
const uint32_t* const argb, int32_t pos) {
// Insertion of two pixels at a time.
const uint64_t key = GetPixPair(argb);
const uint64_t hash_code = GetHash64(key);
p->chain_[pos] = p->hash_to_first_index_[hash_code];
p->hash_to_first_index_[hash_code] = pos;
}
static int VP8LHashChainFindCopy(
const VP8LHashChain* const p, int quality, int index, int xsize,
const uint32_t* const argb, int maxlen, int* const distance_ptr,
int* const length_ptr) {
const uint64_t next_two_pixels = GetPixPair(&argb[index]);
const uint64_t hash_code = GetHash64(next_two_pixels);
int prev_length = 0;
int64_t best_val = 0;
int give_up = 10 + (quality >> 1);
const int min_pos = (index > kWindowSize) ? index - kWindowSize : 0;
int32_t pos;
int64_t val;
int best_length = 0;
int best_distance = 0;
for (pos = p->hash_to_first_index_[hash_code];
pos >= min_pos;
pos = p->chain_[pos]) {
int curr_length;
if (give_up < 0) {
if (give_up < -quality * 2 || best_val >= 0xff0000) {
break;
}
}
--give_up;
if (best_length != 0 &&
argb[pos + best_length - 1] != argb[index + best_length - 1]) {
continue;
}
curr_length = FindMatchLength(argb + pos, argb + index, maxlen);
if (curr_length < prev_length) {
continue;
}
val = 65536 * curr_length;
// Favoring 2d locality here gives savings for certain images.
if (index - pos < 9 * xsize) {
const int y = (index - pos) / xsize;
int x = (index - pos) % xsize;
if (x > xsize / 2) {
x = xsize - x;
}
if (x <= 7 && x >= -8) {
val -= y * y + x * x;
} else {
val -= 9 * 9 + 9 * 9;
}
} else {
val -= 9 * 9 + 9 * 9;
}
if (best_val < val) {
prev_length = curr_length;
best_val = val;
best_length = curr_length;
best_distance = index - pos;
if (curr_length >= kMaxLength) {
break;
}
if ((best_distance == 1 || best_distance == xsize) &&
best_length >= 128) {
break;
}
}
}
*distance_ptr = best_distance;
*length_ptr = best_length;
return best_length >= kMinLength;
}
static WEBP_INLINE void PushBackCopy(VP8LBackwardRefs* const refs, int length) {
while (length >= kMaxLength) {
refs->refs[refs->size++] = PixOrCopyCreateCopy(1, kMaxLength);
length -= kMaxLength;
}
if (length > 0) {
refs->refs[refs->size++] = PixOrCopyCreateCopy(1, length);
}
}
void VP8LBackwardReferencesRle(
int xsize, int ysize, const uint32_t* const argb,
VP8LBackwardRefs* const refs) {
const int pix_count = xsize * ysize;
int match_len = 0;
int i;
refs->size = 0;
for (i = 0; i < pix_count; ++i) {
if (i >= 1 && argb[i] == argb[i - 1]) {
++match_len;
} else {
PushBackCopy(refs, match_len);
match_len = 0;
refs->refs[refs->size++] = PixOrCopyCreateLiteral(argb[i]);
}
}
PushBackCopy(refs, match_len);
}
// Returns 1 when successful.
int VP8LBackwardReferencesHashChain(
int xsize, int ysize, int use_color_cache, const uint32_t* const argb,
int cache_bits, int quality, VP8LBackwardRefs* const refs) {
int i;
int ok = 0;
const int pix_count = xsize * ysize;
VP8LHashChain* hash_chain = (VP8LHashChain*)malloc(sizeof(*hash_chain));
VP8LColorCache hashers;
if (hash_chain == NULL ||
!VP8LColorCacheInit(&hashers, cache_bits) ||
!VP8LHashChainInit(hash_chain, pix_count)) {
goto Error;
}
refs->size = 0;
for (i = 0; i < pix_count; ) {
// Alternative#1: Code the pixels starting at 'i' using backward reference.
int offset = 0;
int len = 0;
if (i < pix_count - 1) { // FindCopy(i,..) reads pixels at [i] and [i + 1].
int maxlen = pix_count - i;
if (maxlen > kMaxLength) {
maxlen = kMaxLength;
}
VP8LHashChainFindCopy(hash_chain, quality, i, xsize, argb, maxlen,
&offset, &len);
}
if (len >= kMinLength) {
// Alternative#2: Insert the pixel at 'i' as literal, and code the
// pixels starting at 'i + 1' using backward reference.
int offset2 = 0;
int len2 = 0;
int k;
VP8LHashChainInsert(hash_chain, &argb[i], i);
if (i < pix_count - 2) { // FindCopy(i+1,..) reads [i + 1] and [i + 2].
int maxlen = pix_count - (i + 1);
if (maxlen > kMaxLength) {
maxlen = kMaxLength;
}
VP8LHashChainFindCopy(hash_chain, quality,
i + 1, xsize, argb, maxlen, &offset2, &len2);
if (len2 > len + 1) {
// Alternative#2 is a better match. So push pixel at 'i' as literal.
if (use_color_cache && VP8LColorCacheContains(&hashers, argb[i])) {
const int ix = VP8LColorCacheGetIndex(&hashers, argb[i]);
refs->refs[refs->size] = PixOrCopyCreateCacheIdx(ix);
} else {
refs->refs[refs->size] = PixOrCopyCreateLiteral(argb[i]);
}
++refs->size;
VP8LColorCacheInsert(&hashers, argb[i]);
i++; // Backward reference to be done for next pixel.
len = len2;
offset = offset2;
}
}
if (len >= kMaxLength) {
len = kMaxLength - 1;
}
refs->refs[refs->size++] = PixOrCopyCreateCopy(offset, len);
for (k = 0; k < len; ++k) {
VP8LColorCacheInsert(&hashers, argb[i + k]);
if (k != 0 && i + k + 1 < pix_count) {
// Add to the hash_chain (but cannot add the last pixel).
VP8LHashChainInsert(hash_chain, &argb[i + k], i + k);
}
}
i += len;
} else {
if (use_color_cache && VP8LColorCacheContains(&hashers, argb[i])) {
// push pixel as a PixOrCopyCreateCacheIdx pixel
int ix = VP8LColorCacheGetIndex(&hashers, argb[i]);
refs->refs[refs->size] = PixOrCopyCreateCacheIdx(ix);
} else {
refs->refs[refs->size] = PixOrCopyCreateLiteral(argb[i]);
}
++refs->size;
VP8LColorCacheInsert(&hashers, argb[i]);
if (i + 1 < pix_count) {
VP8LHashChainInsert(hash_chain, &argb[i], i);
}
++i;
}
}
ok = 1;
Error:
VP8LHashChainClear(hash_chain);
free(hash_chain);
VP8LColorCacheClear(&hashers);
return ok;
}
// -----------------------------------------------------------------------------
typedef struct {
double alpha_[VALUES_IN_BYTE];
double red_[VALUES_IN_BYTE];
double literal_[PIX_OR_COPY_CODES_MAX];
double blue_[VALUES_IN_BYTE];
double distance_[DISTANCE_CODES_MAX];
int cache_bits_;
} CostModel;
static int CostModelBuild(CostModel* const p, int xsize, int ysize,
int recursion_level, int use_color_cache,
const uint32_t* const argb, int cache_bits) {
int ok = 0;
VP8LHistogram histo;
VP8LBackwardRefs refs;
if (!VP8LBackwardRefsAlloc(&refs, xsize * ysize)) goto Error;
p->cache_bits_ = cache_bits;
if (recursion_level > 0) {
if (!VP8LBackwardReferencesTraceBackwards(xsize, ysize, recursion_level - 1,
use_color_cache, argb, cache_bits,
&refs)) {
goto Error;
}
} else {
const int quality = 100;
if (!VP8LBackwardReferencesHashChain(xsize, ysize, use_color_cache, argb,
cache_bits, quality, &refs)) {
goto Error;
}
}
VP8LHistogramCreate(&histo, &refs, cache_bits);
VP8LConvertPopulationCountTableToBitEstimates(
VP8LHistogramNumCodes(&histo),
&histo.literal_[0], &p->literal_[0]);
VP8LConvertPopulationCountTableToBitEstimates(
VALUES_IN_BYTE, &histo.red_[0], &p->red_[0]);
VP8LConvertPopulationCountTableToBitEstimates(
VALUES_IN_BYTE, &histo.blue_[0], &p->blue_[0]);
VP8LConvertPopulationCountTableToBitEstimates(
VALUES_IN_BYTE, &histo.alpha_[0], &p->alpha_[0]);
VP8LConvertPopulationCountTableToBitEstimates(
DISTANCE_CODES_MAX, &histo.distance_[0], &p->distance_[0]);
ok = 1;
Error:
VP8LClearBackwardRefs(&refs);
return ok;
}
static WEBP_INLINE double GetLiteralCost(const CostModel* const p, uint32_t v) {
return p->alpha_[v >> 24] +
p->red_[(v >> 16) & 0xff] +
p->literal_[(v >> 8) & 0xff] +
p->blue_[v & 0xff];
}
static WEBP_INLINE double GetCacheCost(const CostModel* const p, uint32_t idx) {
const int literal_idx = VALUES_IN_BYTE + kLengthCodes + idx;
return p->literal_[literal_idx];
}
static WEBP_INLINE double GetLengthCost(const CostModel* const p,
uint32_t length) {
int code, extra_bits_count, extra_bits_value;
PrefixEncode(length, &code, &extra_bits_count, &extra_bits_value);
return p->literal_[VALUES_IN_BYTE + code] + extra_bits_count;
}
static WEBP_INLINE double GetDistanceCost(const CostModel* const p,
uint32_t distance) {
int code, extra_bits_count, extra_bits_value;
PrefixEncode(distance, &code, &extra_bits_count, &extra_bits_value);
return p->distance_[code] + extra_bits_count;
}
static int BackwardReferencesHashChainDistanceOnly(
int xsize, int ysize, int recursive_cost_model, int use_color_cache,
const uint32_t* const argb, int cache_bits, uint32_t* const dist_array) {
const int quality = 100;
const int pix_count = xsize * ysize;
double* cost = (double*)malloc(pix_count * sizeof(*cost));
int i;
CostModel* cost_model = (CostModel*)malloc(sizeof(*cost_model));
VP8LColorCache hashers;
VP8LHashChain* hash_chain = (VP8LHashChain*)malloc(sizeof(*hash_chain));
int ok = 0;
if (cost == NULL ||
cost_model == NULL ||
hash_chain == NULL ||
!VP8LColorCacheInit(&hashers, cache_bits)) {
goto Error;
}
VP8LHashChainInit(hash_chain, pix_count);
CostModelBuild(cost_model, xsize, ysize, recursive_cost_model,
use_color_cache, argb, cache_bits);
for (i = 0; i < pix_count; ++i) {
cost[i] = 1e100;
}
// We loop one pixel at a time, but store all currently best points to
// non-processed locations from this point.
dist_array[0] = 0;
for (i = 0; i < pix_count; ++i) {
double prev_cost = 0.0;
int shortmax;
if (i > 0) {
prev_cost = cost[i - 1];
}
for (shortmax = 0; shortmax < 2; ++shortmax) {
int offset = 0;
int len = 0;
if (i < pix_count - 1) { // FindCopy reads pixels at [i] and [i + 1].
int maxlen = shortmax ? 2 : kMaxLength;
if (maxlen > pix_count - i) {
maxlen = pix_count - i;
}
VP8LHashChainFindCopy(hash_chain, quality, i, xsize, argb, maxlen,
&offset, &len);
}
if (len >= kMinLength) {
const int code = VP8LDistanceToPlaneCode(xsize, offset);
const double distance_cost =
prev_cost + GetDistanceCost(cost_model, code);
int k;
for (k = 1; k < len; ++k) {
const double cost_val =
distance_cost + GetLengthCost(cost_model, k);
if (cost[i + k] > cost_val) {
cost[i + k] = cost_val;
dist_array[i + k] = k + 1;
}
}
// This if is for speedup only. It roughly doubles the speed, and
// makes compression worse by .1 %.
if (len >= 128 && code < 2) {
// Long copy for short distances, let's skip the middle
// lookups for better copies.
// 1) insert the hashes.
for (k = 0; k < len; ++k) {
VP8LColorCacheInsert(&hashers, argb[i + k]);
if (i + k + 1 < pix_count) {
// Add to the hash_chain (but cannot add the last pixel).
VP8LHashChainInsert(hash_chain, &argb[i + k], i + k);
}
}
// 2) jump.
i += len - 1; // for loop does ++i, thus -1 here.
goto next_symbol;
}
}
}
if (i < pix_count - 1) {
VP8LHashChainInsert(hash_chain, &argb[i], i);
}
{
// inserting a literal pixel
double cost_val = prev_cost;
double mul0 = 1.0;
double mul1 = 1.0;
if (recursive_cost_model == 0) {
mul0 = 0.68;
mul1 = 0.82;
}
if (use_color_cache && VP8LColorCacheContains(&hashers, argb[i])) {
int ix = VP8LColorCacheGetIndex(&hashers, argb[i]);
cost_val += GetCacheCost(cost_model, ix) * mul0;
} else {
cost_val += GetLiteralCost(cost_model, argb[i]) * mul1;
}
if (cost[i] > cost_val) {
cost[i] = cost_val;
dist_array[i] = 1; // only one is inserted.
}
VP8LColorCacheInsert(&hashers, argb[i]);
}
next_symbol: ;
}
// Last pixel still to do, it can only be a single step if not reached
// through cheaper means already.
ok = 1;
Error:
if (hash_chain) VP8LHashChainClear(hash_chain);
free(hash_chain);
free(cost_model);
free(cost);
VP8LColorCacheClear(&hashers);
return ok;
}
static void TraceBackwards(
const uint32_t* const dist_array, int dist_array_size,
uint32_t** const chosen_path, int* const chosen_path_size) {
int i;
// Count how many.
int count = 0;
for (i = dist_array_size - 1; i >= 0; ) {
int k = dist_array[i];
assert(k >= 1);
++count;
i -= k;
}
// Allocate.
*chosen_path_size = count;
*chosen_path = (uint32_t*)malloc(count * sizeof(*chosen_path));
// Write in reverse order.
for (i = dist_array_size - 1; i >= 0; ) {
int k = dist_array[i];
assert(k >= 1);
(*chosen_path)[--count] = k;
i -= k;
}
}
static int BackwardReferencesHashChainFollowChosenPath(
int xsize, int ysize, int use_color_cache, const uint32_t* const argb,
int cache_bits, const uint32_t* const chosen_path, int chosen_path_size,
VP8LBackwardRefs* const refs) {
const int quality = 100;
const int pix_count = xsize * ysize;
int size = 0;
int i = 0;
int k;
int ix;
int ok = 0;
VP8LColorCache hashers;
VP8LHashChain* hash_chain = (VP8LHashChain*)malloc(sizeof(*hash_chain));
VP8LHashChainInit(hash_chain, pix_count);
if (hash_chain == NULL ||
!VP8LColorCacheInit(&hashers, cache_bits)) {
goto Error;
}
refs->size = 0;
for (ix = 0; ix < chosen_path_size; ++ix, ++size) {
int offset = 0;
int len = 0;
int maxlen = chosen_path[ix];
if (maxlen != 1) {
VP8LHashChainFindCopy(hash_chain, quality,
i, xsize, argb, maxlen, &offset, &len);
assert(len == maxlen);
refs->refs[size] = PixOrCopyCreateCopy(offset, len);
for (k = 0; k < len; ++k) {
VP8LColorCacheInsert(&hashers, argb[i + k]);
if (i + k + 1 < pix_count) {
// Add to the hash_chain (but cannot add the last pixel).
VP8LHashChainInsert(hash_chain, &argb[i + k], i + k);
}
}
i += len;
} else {
if (use_color_cache && VP8LColorCacheContains(&hashers, argb[i])) {
// push pixel as a color cache index
int ix = VP8LColorCacheGetIndex(&hashers, argb[i]);
refs->refs[size] = PixOrCopyCreateCacheIdx(ix);
} else {
refs->refs[size] = PixOrCopyCreateLiteral(argb[i]);
}
VP8LColorCacheInsert(&hashers, argb[i]);
if (i + 1 < pix_count) {
VP8LHashChainInsert(hash_chain, &argb[i], i);
}
++i;
}
}
assert(size < refs->max_size);
refs->size = size;
ok = 1;
Error:
VP8LHashChainClear(hash_chain);
if (hash_chain) {
free(hash_chain);
}
VP8LColorCacheClear(&hashers);
return ok;
}
// Returns 1 on success.
int VP8LBackwardReferencesTraceBackwards(
int xsize, int ysize, int recursive_cost_model, int use_color_cache,
const uint32_t* const argb, int cache_bits, VP8LBackwardRefs* const refs) {
int ok = 0;
const int dist_array_size = xsize * ysize;
uint32_t* chosen_path = NULL;
int chosen_path_size = 0;
uint32_t* const dist_array =
(uint32_t*)malloc(dist_array_size * sizeof(*dist_array));
if (dist_array == NULL) {
goto Error;
}
if (!BackwardReferencesHashChainDistanceOnly(
xsize, ysize, recursive_cost_model, use_color_cache, argb, cache_bits,
dist_array)) {
free(dist_array);
goto Error;
}
TraceBackwards(dist_array, dist_array_size, &chosen_path, &chosen_path_size);
free(dist_array);
if (!BackwardReferencesHashChainFollowChosenPath(
xsize, ysize, use_color_cache, argb, cache_bits,
chosen_path, chosen_path_size, refs)) {
goto Error;
}
ok = 1;
Error:
free(chosen_path);
return ok;
}
void VP8LBackwardReferences2DLocality(int xsize, VP8LBackwardRefs* const refs) {
int i;
for (i = 0; i < refs->size; ++i) {
if (PixOrCopyIsCopy(&refs->refs[i])) {
const int dist = refs->refs[i].argb_or_distance;
const int transformed_dist = VP8LDistanceToPlaneCode(xsize, dist);
refs->refs[i].argb_or_distance = transformed_dist;
}
}
}
int VP8LVerifyBackwardReferences(
const uint32_t* const argb, int xsize, int ysize, int cache_bits,
const VP8LBackwardRefs* const refs) {
int num_pixels = 0;
int i;
VP8LColorCache hashers;
VP8LColorCacheInit(&hashers, cache_bits);
for (i = 0; i < refs->size; ++i) {
const PixOrCopy token = refs->refs[i];
if (PixOrCopyIsLiteral(&token)) {
if (argb[num_pixels] != PixOrCopyArgb(&token)) {
VP8LColorCacheClear(&hashers);
return 0;
}
VP8LColorCacheInsert(&hashers, argb[num_pixels]);
++num_pixels;
} else if (PixOrCopyIsCacheIdx(&token)) {
const uint32_t cache_entry =
VP8LColorCacheLookup(&hashers, PixOrCopyCacheIdx(&token));
if (argb[num_pixels] != cache_entry) {
VP8LColorCacheClear(&hashers);
return 0;
}
VP8LColorCacheInsert(&hashers, argb[num_pixels]);
++num_pixels;
} else if (PixOrCopyIsCopy(&token)) {
int k;
if (PixOrCopyDistance(&token) == 0) {
VP8LColorCacheClear(&hashers);
return 0;
}
for (k = 0; k < token.len; ++k) {
if (argb[num_pixels] != argb[num_pixels - PixOrCopyDistance(&token)]) {
VP8LColorCacheClear(&hashers);
return 0;
}
VP8LColorCacheInsert(&hashers, argb[num_pixels]);
++num_pixels;
}
}
}
{
const int pix_count = xsize * ysize;
if (num_pixels != pix_count) {
VP8LColorCacheClear(&hashers);
return 0;
}
}
VP8LColorCacheClear(&hashers);
return 1;
}
// Returns 1 on success.
static int ComputeCacheHistogram(
const uint32_t* const argb, int xsize, int ysize,
const VP8LBackwardRefs* const refs, int cache_bits,
VP8LHistogram* const histo) {
int pixel_index = 0;
int i;
uint32_t k;
VP8LColorCache hashers;
if (!VP8LColorCacheInit(&hashers, cache_bits)) {
return 0;
}
for (i = 0; i < refs->size; ++i) {
const PixOrCopy* const v = &refs->refs[i];
if (PixOrCopyIsLiteral(v)) {
if (cache_bits != 0 &&
VP8LColorCacheContains(&hashers, argb[pixel_index])) {
// push pixel as a cache index
const int ix = VP8LColorCacheGetIndex(&hashers, argb[pixel_index]);
const PixOrCopy token = PixOrCopyCreateCacheIdx(ix);
VP8LHistogramAddSinglePixOrCopy(histo, &token);
} else {
VP8LHistogramAddSinglePixOrCopy(histo, v);
}
} else {
VP8LHistogramAddSinglePixOrCopy(histo, v);
}
for (k = 0; k < PixOrCopyLength(v); ++k) {
VP8LColorCacheInsert(&hashers, argb[pixel_index]);
++pixel_index;
}
}
assert(pixel_index == xsize * ysize);
(void)xsize; // xsize is not used in non-debug compilations otherwise.
(void)ysize; // ysize is not used in non-debug compilations otherwise.
VP8LColorCacheClear(&hashers);
return 1;
}
// Returns how many bits are to be used for a color cache.
int VP8LCalculateEstimateForCacheSize(
const uint32_t* const argb, int xsize, int ysize,
int* const best_cache_bits) {
int ok = 0;
int cache_bits;
double lowest_entropy = 1e99;
VP8LBackwardRefs refs;
static const double kSmallPenaltyForLargeCache = 4.0;
static const int quality = 30;
if (!VP8LBackwardRefsAlloc(&refs, xsize * ysize) ||
!VP8LBackwardReferencesHashChain(xsize, ysize, 0, argb, 0, quality,
&refs)) {
goto Error;
}
for (cache_bits = 0; cache_bits <= kColorCacheBitsMax; ++cache_bits) {
double cur_entropy;
VP8LHistogram histo;
VP8LHistogramInit(&histo, cache_bits);
ComputeCacheHistogram(argb, xsize, ysize, &refs, cache_bits, &histo);
cur_entropy = VP8LHistogramEstimateBits(&histo) +
kSmallPenaltyForLargeCache * cache_bits;
if (cache_bits == 0 || cur_entropy < lowest_entropy) {
*best_cache_bits = cache_bits;
lowest_entropy = cur_entropy;
}
}
ok = 1;
Error:
VP8LClearBackwardRefs(&refs);
return ok;
}
#endif

View File

@ -0,0 +1,253 @@
// Copyright 2012 Google Inc. All Rights Reserved.
//
// This code is licensed under the same terms as WebM:
// Software License Agreement: http://www.webmproject.org/license/software/
// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
// -----------------------------------------------------------------------------
//
// Author: Jyrki Alakuijala (jyrki@google.com)
//
#ifndef WEBP_ENC_BACKWARD_REFERENCES_H_
#define WEBP_ENC_BACKWARD_REFERENCES_H_
#ifdef USE_LOSSLESS_ENCODER
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include "../webp/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
// Backward reference distance prefix codes
#define DISTANCE_CODES_MAX 40
// Compression constants
#define CODE_LENGTH_CODES 19
static const int kLengthCodes = 24;
// The spec allows 11, we use 9 bits to reduce memory consumption in encoding.
// Having 9 instead of 11 removes about 0.25 % of compression density.
static const int kColorCacheBitsMax = 9;
#define PIX_OR_COPY_CODES_MAX (256 + 24 + (1 << 9))
static const int kMaxLength = 4096;
// use GNU builtins where available.
#if defined(__GNUC__) && \
((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ >= 4)
static WEBP_INLINE int BitsLog2Floor(uint32_t n) {
return n == 0 ? -1 : 31 ^ __builtin_clz(n);
}
#else
static WEBP_INLINE int BitsLog2Floor(uint32_t n) {
int log;
uint32_t value;
int i;
if (n == 0)
return -1;
log = 0;
value = n;
for (i = 4; i >= 0; --i) {
int shift = (1 << i);
uint32_t x = value >> shift;
if (x != 0) {
value = x;
log += shift;
}
}
return log;
}
#endif
static WEBP_INLINE int VP8LBitsLog2Ceiling(uint32_t n) {
int floor = BitsLog2Floor(n);
if (n == (n & ~(n - 1))) // zero or a power of two.
return floor;
else
return floor + 1;
}
// 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 PrefixEncode(
int distance,
int *code,
int *extra_bits_count,
int *extra_bits_value) {
// Collect the two most significant bits where the highest bit is 1.
const int highest_bit = BitsLog2Floor(--distance);
// & 0x3f is to make behavior well defined when highest_bit
// does not exist or is the least significant bit.
const int second_highest_bit =
(distance >> ((highest_bit - 1) & 0x3f)) & 1;
*extra_bits_count = (highest_bit > 0) ? highest_bit - 1 : 0;
*extra_bits_value = distance & ((1 << *extra_bits_count) - 1);
*code = (highest_bit > 0) ? 2 * highest_bit + second_highest_bit :
(highest_bit == 0) ? 1 : 0;
}
enum Mode {
kLiteral,
kCacheIdx,
kCopy,
kNone,
};
typedef struct {
// mode as uint8_t to make the memory layout to be exactly 8 bytes.
uint8_t mode;
uint16_t len;
uint32_t argb_or_distance;
} PixOrCopy;
static WEBP_INLINE PixOrCopy PixOrCopyCreateCopy(uint32_t distance,
uint16_t len) {
PixOrCopy retval;
retval.mode = kCopy;
retval.argb_or_distance = distance;
retval.len = len;
return retval;
}
static WEBP_INLINE PixOrCopy PixOrCopyCreateCacheIdx(int idx) {
PixOrCopy retval;
assert(idx >= 0);
assert(idx < (1 << kColorCacheBitsMax));
retval.mode = kCacheIdx;
retval.argb_or_distance = idx;
retval.len = 1;
return retval;
}
static WEBP_INLINE PixOrCopy PixOrCopyCreateLiteral(uint32_t argb) {
PixOrCopy retval;
retval.mode = kLiteral;
retval.argb_or_distance = argb;
retval.len = 1;
return retval;
}
static WEBP_INLINE int PixOrCopyIsLiteral(const PixOrCopy* const p) {
return (p->mode == kLiteral);
}
static WEBP_INLINE int PixOrCopyIsCacheIdx(const PixOrCopy* const p) {
return (p->mode == kCacheIdx);
}
static WEBP_INLINE int PixOrCopyIsCopy(const PixOrCopy* const p) {
return (p->mode == kCopy);
}
static WEBP_INLINE uint32_t PixOrCopyLiteral(const PixOrCopy* const p,
int component) {
assert(p->mode == kLiteral);
return (p->argb_or_distance >> (component * 8)) & 0xff;
}
static WEBP_INLINE uint32_t PixOrCopyLength(const PixOrCopy* const p) {
return p->len;
}
static WEBP_INLINE uint32_t PixOrCopyArgb(const PixOrCopy* const p) {
assert(p->mode == kLiteral);
return p->argb_or_distance;
}
static WEBP_INLINE uint32_t PixOrCopyCacheIdx(const PixOrCopy* const p) {
assert(p->mode == kCacheIdx);
assert(p->argb_or_distance < (1 << kColorCacheBitsMax));
return p->argb_or_distance;
}
static WEBP_INLINE uint32_t PixOrCopyDistance(const PixOrCopy* const p) {
assert(p->mode == kCopy);
return p->argb_or_distance;
}
// -----------------------------------------------------------------------------
// VP8LBackwardRefs
typedef struct {
PixOrCopy* refs;
int size; // currently used
int max_size; // maximum capacity
} VP8LBackwardRefs;
static WEBP_INLINE void VP8LInitBackwardRefs(VP8LBackwardRefs* const refs) {
if (refs != NULL) {
refs->refs = NULL;
refs->size = 0;
refs->max_size = 0;
}
}
static WEBP_INLINE void VP8LClearBackwardRefs(VP8LBackwardRefs* const refs) {
if (refs != NULL) {
free(refs->refs);
VP8LInitBackwardRefs(refs);
}
}
// Allocate 'max_size' references. Returns false in case of memory error.
static WEBP_INLINE int VP8LBackwardRefsAlloc(VP8LBackwardRefs* const refs,
int max_size) {
assert(refs != NULL);
refs->size = 0;
refs->max_size = 0;
refs->refs = (PixOrCopy*)malloc(max_size * sizeof(*refs->refs));
if (refs->refs == NULL) return 0;
refs->max_size = max_size;
return 1;
}
// Ridiculously simple backward references for images where it is unlikely
// that there are large backward references (photos).
void VP8LBackwardReferencesRle(
int xsize, int ysize, const uint32_t* const argb,
VP8LBackwardRefs* const refs);
// This is a simple fast function for obtaining backward references
// based on simple heuristics. Returns 1 on success.
int VP8LBackwardReferencesHashChain(
int xsize, int ysize, int use_color_cache, const uint32_t* const argb,
int cache_bits, int quality, VP8LBackwardRefs* const refs);
// This method looks for a shortest path through the backward reference
// network based on a cost model generated by a first round of compression.
// Returns 1 on success.
int VP8LBackwardReferencesTraceBackwards(
int xsize, int ysize, int recursive_cost_model, int use_color_cache,
const uint32_t* const argb, int cache_bits, VP8LBackwardRefs* const refs);
// Convert backward references that are of linear distance along
// the image scan lines to have a 2d locality indexing where
// smaller values are used for backward references that are close by.
void VP8LBackwardReferences2DLocality(int xsize, VP8LBackwardRefs* const refs);
// Internals of locality transform exposed for testing use.
int VP8LDistanceToPlaneCode(int xsize, int distance);
// Returns true if the given backward references actually produce
// the image given in tuple (argb, xsize, ysize).
int VP8LVerifyBackwardReferences(
const uint32_t* const argb, int xsize, int ysize, int cache_bits,
const VP8LBackwardRefs* const refs);
// Produce an estimate for a good color cache size for the image.
int VP8LCalculateEstimateForCacheSize(
const uint32_t* const argb, int xsize, int ysize,
int* const best_cache_bits);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif
#endif // WEBP_ENC_BACKWARD_REFERENCES_H_

View File

@ -44,6 +44,7 @@ int WebPConfigInitInternal(WebPConfig* const config,
config->alpha_compression = 1;
config->alpha_filtering = 1;
config->alpha_quality = 100;
config->lossless = 0;
// TODO(skal): tune.
switch (preset) {
@ -116,6 +117,13 @@ int WebPValidateConfig(const WebPConfig* const config) {
return 0;
if (config->alpha_quality < 0 || config->alpha_quality > 100)
return 0;
#ifdef USE_LOSSLESS_ENCODER
if (config->lossless < 0 || config->lossless > 1)
return 0;
#else
if (config->lossless != 0)
return 0;
#endif
return 1;
}

420
src/enc/histogram.c Normal file
View File

@ -0,0 +1,420 @@
// Copyright 2012 Google Inc. All Rights Reserved.
//
// This code is licensed under the same terms as WebM:
// Software License Agreement: http://www.webmproject.org/license/software/
// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
// -----------------------------------------------------------------------------
//
// Author: Jyrki Alakuijala (jyrki@google.com)
//
#ifdef USE_LOSSLESS_ENCODER
#include <math.h>
#include <stdio.h>
#include "./backward_references.h"
#include "./histogram.h"
#include "../dsp/lossless.h"
static void HistogramClear(VP8LHistogram* const p) {
memset(p->literal_, 0, sizeof(p->literal_));
memset(p->red_, 0, sizeof(p->red_));
memset(p->blue_, 0, sizeof(p->blue_));
memset(p->alpha_, 0, sizeof(p->alpha_));
memset(p->distance_, 0, sizeof(p->distance_));
p->bit_cost_ = 0;
}
void VP8LHistogramCreate(VP8LHistogram* const p,
const VP8LBackwardRefs* const refs,
int palette_code_bits) {
int i;
if (palette_code_bits >= 0) {
p->palette_code_bits_ = palette_code_bits;
}
HistogramClear(p);
for (i = 0; i < refs->size; ++i) {
VP8LHistogramAddSinglePixOrCopy(p, &refs->refs[i]);
}
}
void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits) {
p->palette_code_bits_ = palette_code_bits;
HistogramClear(p);
}
VP8LHistogramSet* VP8LAllocateHistogramSet(int size, int cache_bits) {
int i;
VP8LHistogramSet* set;
VP8LHistogram* bulk;
const size_t total_size = sizeof(*set)
+ size * sizeof(*set->histograms)
+ size * sizeof(**set->histograms);
uint8_t* memory = (uint8_t*)malloc(total_size);
if (memory == NULL) return NULL;
set = (VP8LHistogramSet*)memory;
memory += sizeof(*set);
set->histograms = (VP8LHistogram**)memory;
memory += size * sizeof(*set->histograms);
bulk = (VP8LHistogram*)memory;
set->max_size = size;
set->size = size;
for (i = 0; i < size; ++i) {
set->histograms[i] = bulk + i;
VP8LHistogramInit(set->histograms[i], cache_bits);
}
return set;
}
// -----------------------------------------------------------------------------
void VP8LConvertPopulationCountTableToBitEstimates(
int num_symbols, const int* const population_counts,
double* const output) {
int sum = 0;
int nonzeros = 0;
int i;
for (i = 0; i < num_symbols; ++i) {
sum += population_counts[i];
if (population_counts[i] > 0) {
++nonzeros;
}
}
if (nonzeros <= 1) {
memset(output, 0, num_symbols * sizeof(*output));
return;
}
{
const double log2sum = log2(sum);
for (i = 0; i < num_symbols; ++i) {
if (population_counts[i] == 0) {
output[i] = log2sum;
} else {
output[i] = log2sum - log2(population_counts[i]);
}
}
}
}
void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const p,
const PixOrCopy* const v) {
if (PixOrCopyIsLiteral(v)) {
++p->alpha_[PixOrCopyLiteral(v, 3)];
++p->red_[PixOrCopyLiteral(v, 2)];
++p->literal_[PixOrCopyLiteral(v, 1)];
++p->blue_[PixOrCopyLiteral(v, 0)];
} else if (PixOrCopyIsCacheIdx(v)) {
int literal_ix = 256 + kLengthCodes + PixOrCopyCacheIdx(v);
++p->literal_[literal_ix];
} else {
int code, extra_bits_count, extra_bits_value;
PrefixEncode(PixOrCopyLength(v),
&code, &extra_bits_count, &extra_bits_value);
++p->literal_[256 + code];
PrefixEncode(PixOrCopyDistance(v),
&code, &extra_bits_count, &extra_bits_value);
++p->distance_[code];
}
}
static double BitsEntropy(const int* const array, int n) {
double retval = 0;
int sum = 0;
int nonzeros = 0;
int max_val = 0;
int i;
double mix;
for (i = 0; i < n; ++i) {
if (array[i] != 0) {
sum += array[i];
++nonzeros;
retval += array[i] * VP8LFastLog(array[i]);
if (max_val < array[i]) {
max_val = array[i];
}
}
}
retval -= sum * VP8LFastLog(sum);
retval *= -1.4426950408889634; // 1.0 / -Log(2);
mix = 0.627;
if (nonzeros < 5) {
if (nonzeros <= 1) {
return 0;
}
// Two symbols, they will be 0 and 1 in a Huffman code.
// Let's mix in a bit of entropy to favor good clustering when
// distributions of these are combined.
if (nonzeros == 2) {
return 0.99 * sum + 0.01 * retval;
}
// No matter what the entropy says, we cannot be better than min_limit
// with Huffman coding. I am mixing a bit of entropy into the
// min_limit since it produces much better (~0.5 %) compression results
// perhaps because of better entropy clustering.
if (nonzeros == 3) {
mix = 0.95;
} else {
mix = 0.7; // nonzeros == 4.
}
}
{
double min_limit = 2 * sum - max_val;
min_limit = mix * min_limit + (1.0 - mix) * retval;
if (retval < min_limit) {
return min_limit;
}
}
return retval;
}
double VP8LHistogramEstimateBitsBulk(const VP8LHistogram* const p) {
double retval = BitsEntropy(&p->literal_[0], VP8LHistogramNumCodes(p)) +
BitsEntropy(&p->red_[0], 256) +
BitsEntropy(&p->blue_[0], 256) +
BitsEntropy(&p->alpha_[0], 256) +
BitsEntropy(&p->distance_[0], DISTANCE_CODES_MAX);
// Compute the extra bits cost.
int i;
for (i = 2; i < kLengthCodes - 2; ++i) {
retval +=
(i >> 1) * p->literal_[256 + i + 2];
}
for (i = 2; i < DISTANCE_CODES_MAX - 2; ++i) {
retval += (i >> 1) * p->distance_[i + 2];
}
return retval;
}
double VP8LHistogramEstimateBits(const VP8LHistogram* const p) {
return VP8LHistogramEstimateBitsHeader(p) + VP8LHistogramEstimateBitsBulk(p);
}
// Returns the cost encode the rle-encoded entropy code.
// The constants in this function are experimental.
static double HuffmanCost(const int* const population, int length) {
// Small bias because Huffman code length is typically not stored in
// full length.
static const int kHuffmanCodeOfHuffmanCodeSize = CODE_LENGTH_CODES * 3;
static const double kSmallBias = 9.1;
double retval = kHuffmanCodeOfHuffmanCodeSize - kSmallBias;
int streak = 0;
int i = 0;
for (; i < length - 1; ++i) {
++streak;
if (population[i] == population[i + 1]) {
continue;
}
last_streak_hack:
// population[i] points now to the symbol in the streak of same values.
if (streak > 3) {
if (population[i] == 0) {
retval += 1.5625 + 0.234375 * streak;
} else {
retval += 2.578125 + 0.703125 * streak;
}
} else {
if (population[i] == 0) {
retval += 1.796875 * streak;
} else {
retval += 3.28125 * streak;
}
}
streak = 0;
}
if (i == length - 1) {
++streak;
goto last_streak_hack;
}
return retval;
}
double VP8LHistogramEstimateBitsHeader(const VP8LHistogram* const p) {
return HuffmanCost(&p->alpha_[0], 256) +
HuffmanCost(&p->red_[0], 256) +
HuffmanCost(&p->literal_[0], VP8LHistogramNumCodes(p)) +
HuffmanCost(&p->blue_[0], 256) +
HuffmanCost(&p->distance_[0], DISTANCE_CODES_MAX);
}
static void HistogramBuildImage(int xsize, int histo_bits,
const VP8LBackwardRefs* const backward_refs,
VP8LHistogramSet* const image) {
int i;
int x = 0, y = 0;
const int histo_xsize =
(histo_bits > 0) ? VP8LSubSampleSize(xsize, histo_bits) : 1;
for (i = 0; i < backward_refs->size; ++i) {
const PixOrCopy* const v = &backward_refs->refs[i];
const int ix =
(histo_bits > 0) ? (y >> histo_bits) * histo_xsize + (x >> histo_bits)
: 0;
VP8LHistogramAddSinglePixOrCopy(image->histograms[ix], v);
x += PixOrCopyLength(v);
while (x >= xsize) {
x -= xsize;
++y;
}
}
}
static int HistogramCombine(const VP8LHistogramSet* const in,
VP8LHistogramSet* const out, int num_pairs) {
int ok = 0;
int i, iter;
unsigned int seed = 0;
int tries_with_no_success = 0;
const int min_cluster_size = 2;
int out_size = in->size;
const int outer_iters = in->size * 3;
VP8LHistogram* const histos = (VP8LHistogram*)malloc(2 * sizeof(*histos));
VP8LHistogram* cur_combo = histos + 0; // trial merged histogram
VP8LHistogram* best_combo = histos + 1; // best merged histogram so far
if (histos == NULL) goto End;
// Copy histograms from in[] to out[].
assert(in->size <= out->size);
for (i = 0; i < in->size; ++i) {
in->histograms[i]->bit_cost_ = VP8LHistogramEstimateBits(in->histograms[i]);
*out->histograms[i] = *in->histograms[i];
}
// Collapse similar histograms in 'out'.
for (iter = 0; iter < outer_iters && out_size >= min_cluster_size; ++iter) {
// We pick the best pair to be combined out of 'inner_iters' pairs.
double best_cost_diff = 0.;
int best_idx1 = 0, best_idx2 = 1;
int j;
for (j = 0; j < num_pairs; ++j) {
double curr_cost_diff;
// Choose two histograms at random and try to combine them.
const int idx1 = rand_r(&seed) % out_size;
const int tmp = ((j & 7) + 1) % (out_size - 1);
const int diff = (tmp < 3) ? tmp : rand_r(&seed) % (out_size - 1);
const int idx2 = (idx1 + diff + 1) % out_size;
if (idx1 == idx2) {
continue;
}
*cur_combo = *out->histograms[idx1];
VP8LHistogramAdd(cur_combo, out->histograms[idx2]);
cur_combo->bit_cost_ = VP8LHistogramEstimateBits(cur_combo);
// Calculate cost reduction on combining.
curr_cost_diff = cur_combo->bit_cost_
- out->histograms[idx1]->bit_cost_
- out->histograms[idx2]->bit_cost_;
if (best_cost_diff > curr_cost_diff) { // found a better pair?
{ // swap cur/best combo histograms
VP8LHistogram* const tmp = cur_combo;
cur_combo = best_combo;
best_combo = tmp;
}
best_cost_diff = curr_cost_diff;
best_idx1 = idx1;
best_idx2 = idx2;
}
}
if (best_cost_diff < 0.0) {
*out->histograms[best_idx1] = *best_combo;
// swap best_idx2 slot with last one (which is now unused)
--out_size;
if (best_idx2 != out_size) {
out->histograms[best_idx2] = out->histograms[out_size];
out->histograms[out_size] = NULL; // just for sanity check.
}
tries_with_no_success = 0;
}
if (++tries_with_no_success >= 50) {
break;
}
}
out->size = out_size;
ok = 1;
End:
free(histos);
return ok;
}
// -----------------------------------------------------------------------------
// Histogram refinement
// What is the bit cost of moving square_histogram from
// cur_symbol to candidate_symbol.
// TODO(skal): we don't really need to copy the histogram and Add(). Instead
// we just need VP8LDualHistogramEstimateBits(A, B) estimation function.
static double HistogramDistance(const VP8LHistogram* const square_histogram,
const VP8LHistogram* const candidate) {
const double previous_bit_cost = candidate->bit_cost_;
double new_bit_cost;
VP8LHistogram modified_histo;
modified_histo = *candidate;
VP8LHistogramAdd(&modified_histo, square_histogram);
new_bit_cost = VP8LHistogramEstimateBits(&modified_histo);
return new_bit_cost - previous_bit_cost;
}
// Find the best 'out' histogram for each of the 'in' histograms.
// Note: we assume that out[]->bit_cost_ is already up-to-date.
static void HistogramRemap(const VP8LHistogramSet* const in,
const VP8LHistogramSet* const out,
uint16_t* const symbols) {
int i;
for (i = 0; i < in->size; ++i) {
int best_out = 0;
double best_bits = HistogramDistance(in->histograms[i], out->histograms[0]);
int k;
for (k = 1; k < out->size; ++k) {
const double cur_bits =
HistogramDistance(in->histograms[i], out->histograms[k]);
if (cur_bits < best_bits) {
best_bits = cur_bits;
best_out = k;
}
}
symbols[i] = best_out;
}
// Recompute each out based on raw and symbols.
for (i = 0; i < out->size; ++i) {
HistogramClear(out->histograms[i]);
}
for (i = 0; i < in->size; ++i) {
VP8LHistogramAdd(out->histograms[symbols[i]], in->histograms[i]);
}
}
int VP8LGetHistoImageSymbols(int xsize, int ysize,
const VP8LBackwardRefs* const refs,
int quality, int histo_bits, int cache_bits,
VP8LHistogramSet* const image_in,
uint16_t* const histogram_symbols) {
int ok = 0;
const int histo_xsize = histo_bits ? VP8LSubSampleSize(xsize, histo_bits) : 1;
const int histo_ysize = histo_bits ? VP8LSubSampleSize(ysize, histo_bits) : 1;
const int num_histo_pairs = 10 + quality / 2; // For HistogramCombine().
const int histo_image_raw_size = histo_xsize * histo_ysize;
VP8LHistogramSet* const image_out =
VP8LAllocateHistogramSet(histo_image_raw_size, cache_bits);
if (image_out == NULL) return 0;
// Build histogram image.
HistogramBuildImage(xsize, histo_bits, refs, image_out);
// Collapse similar histograms.
if (!HistogramCombine(image_out, image_in, num_histo_pairs)) {
goto Error;
}
// Find the optimal map from original histograms to the final ones.
HistogramRemap(image_out, image_in, histogram_symbols);
ok = 1;
Error:
free(image_out);
return ok;
}
#endif

140
src/enc/histogram.h Normal file
View File

@ -0,0 +1,140 @@
// Copyright 2012 Google Inc. All Rights Reserved.
//
// This code is licensed under the same terms as WebM:
// Software License Agreement: http://www.webmproject.org/license/software/
// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
// -----------------------------------------------------------------------------
//
// Author: Jyrki Alakuijala (jyrki@google.com)
//
// Models the histograms of literal and distance codes.
#ifndef WEBP_ENC_HISTOGRAM_H_
#define WEBP_ENC_HISTOGRAM_H_
#ifdef USE_LOSSLESS_ENCODER
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "./backward_references.h"
#include "../webp/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
// A simple container for histograms of data.
typedef struct {
// literal_ contains green literal, palette-code and
// copy-length-prefix histogram
int literal_[PIX_OR_COPY_CODES_MAX];
int red_[256];
int blue_[256];
int alpha_[256];
// Backward reference prefix-code histogram.
int distance_[DISTANCE_CODES_MAX];
int palette_code_bits_;
double bit_cost_; // cached value of VP8LHistogramEstimateBits(this)
} VP8LHistogram;
// Collection of histograms with fixed capacity, allocated as one
// big memory chunk. Can be destroyed by simply calling 'free()'.
typedef struct {
int size; // number of slots currently in use
int max_size; // maximum capacity
VP8LHistogram** histograms;
} VP8LHistogramSet;
// Create the histogram.
//
// The input data is the PixOrCopy data, which models the literals, stop
// codes and backward references (both distances and lengths). Also: if
// palette_code_bits is >= 0, initialize the histogram with this value.
void VP8LHistogramCreate(VP8LHistogram* const p,
const VP8LBackwardRefs* const refs,
int palette_code_bits);
// Set the palette_code_bits and reset the stats.
void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits);
// Allocate an array of pointer to histograms, allocated and initialized
// using 'cache_bits'. Return NULL in case of memory error.
VP8LHistogramSet* VP8LAllocateHistogramSet(int size, int cache_bits);
void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const p,
const PixOrCopy* const v);
// Estimate how many bits the combined entropy of literals and distance
// approximately maps to.
double VP8LHistogramEstimateBits(const VP8LHistogram* const p);
// This function estimates the Huffman dictionary + other block overhead
// size for creating a new deflate block.
double VP8LHistogramEstimateBitsHeader(const VP8LHistogram* const p);
// This function estimates the cost in bits excluding the bits needed to
// represent the entropy code itself.
double VP8LHistogramEstimateBitsBulk(const VP8LHistogram* const p);
static WEBP_INLINE void VP8LHistogramAdd(VP8LHistogram* const p,
const VP8LHistogram* const a) {
int i;
for (i = 0; i < PIX_OR_COPY_CODES_MAX; ++i) {
p->literal_[i] += a->literal_[i];
}
for (i = 0; i < DISTANCE_CODES_MAX; ++i) {
p->distance_[i] += a->distance_[i];
}
for (i = 0; i < 256; ++i) {
p->red_[i] += a->red_[i];
p->blue_[i] += a->blue_[i];
p->alpha_[i] += a->alpha_[i];
}
}
static WEBP_INLINE void VP8LHistogramRemove(VP8LHistogram* const p,
const VP8LHistogram* const a) {
int i;
for (i = 0; i < PIX_OR_COPY_CODES_MAX; ++i) {
p->literal_[i] -= a->literal_[i];
assert(p->literal_[i] >= 0);
}
for (i = 0; i < DISTANCE_CODES_MAX; ++i) {
p->distance_[i] -= a->distance_[i];
assert(p->distance_[i] >= 0);
}
for (i = 0; i < 256; ++i) {
p->red_[i] -= a->red_[i];
p->blue_[i] -= a->blue_[i];
p->alpha_[i] -= a->alpha_[i];
assert(p->red_[i] >= 0);
assert(p->blue_[i] >= 0);
assert(p->alpha_[i] >= 0);
}
}
static WEBP_INLINE int VP8LHistogramNumCodes(const VP8LHistogram* const p) {
return 256 + kLengthCodes + (1 << p->palette_code_bits_);
}
void VP8LConvertPopulationCountTableToBitEstimates(
int n, const int* const population_counts, double* const output);
// Builds the histogram image.
int VP8LGetHistoImageSymbols(int xsize, int ysize,
const VP8LBackwardRefs* const refs,
int quality, int histogram_bits, int cache_bits,
VP8LHistogramSet* const image_in,
uint16_t* const histogram_symbols);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif
#endif // WEBP_ENC_HISTOGRAM_H_

View File

@ -32,75 +32,94 @@ int WebPPictureAlloc(WebPPicture* const picture) {
const int has_alpha = picture->colorspace & WEBP_CSP_ALPHA_BIT;
const int width = picture->width;
const int height = picture->height;
const int y_stride = width;
const int uv_width = HALVE(width);
const int uv_height = HALVE(height);
const int uv_stride = uv_width;
int uv0_stride = 0;
int a_width, a_stride;
uint64_t y_size, uv_size, uv0_size, a_size, total_size;
uint8_t* mem;
// U/V
switch (uv_csp) {
case WEBP_YUV420:
break;
if (!picture->use_argb_input) {
const int y_stride = width;
const int uv_width = HALVE(width);
const int uv_height = HALVE(height);
const int uv_stride = uv_width;
int uv0_stride = 0;
int a_width, a_stride;
uint64_t y_size, uv_size, uv0_size, a_size, total_size;
uint8_t* mem;
// U/V
switch (uv_csp) {
case WEBP_YUV420:
break;
#ifdef WEBP_EXPERIMENTAL_FEATURES
case WEBP_YUV400: // for now, we'll just reset the U/V samples
break;
case WEBP_YUV422:
uv0_stride = uv_width;
break;
case WEBP_YUV444:
uv0_stride = width;
break;
case WEBP_YUV400: // for now, we'll just reset the U/V samples
break;
case WEBP_YUV422:
uv0_stride = uv_width;
break;
case WEBP_YUV444:
uv0_stride = width;
break;
#endif
default:
default:
return 0;
}
uv0_size = height * uv0_stride;
// alpha
a_width = has_alpha ? width : 0;
a_stride = a_width;
y_size = (uint64_t)y_stride * height;
uv_size = (uint64_t)uv_stride * uv_height;
a_size = (uint64_t)a_stride * height;
total_size = y_size + a_size + 2 * uv_size + 2 * uv0_size;
// Security and validation checks
if (width <= 0 || height <= 0 || // check for luma/alpha param error
uv_width < 0 || uv_height < 0 || // check for u/v param error
y_size >= (1ULL << 40) || // check for reasonable global size
(size_t)total_size != total_size) { // check for overflow on 32bit
return 0;
}
uv0_size = height * uv0_stride;
}
picture->y_stride = y_stride;
picture->uv_stride = uv_stride;
picture->a_stride = a_stride;
picture->uv0_stride = uv0_stride;
WebPPictureFree(picture); // erase previous buffer
mem = (uint8_t*)malloc((size_t)total_size);
if (mem == NULL) return 0;
// alpha
a_width = has_alpha ? width : 0;
a_stride = a_width;
y_size = (uint64_t)y_stride * height;
uv_size = (uint64_t)uv_stride * uv_height;
a_size = (uint64_t)a_stride * height;
picture->y = mem;
mem += y_size;
total_size = y_size + a_size + 2 * uv_size + 2 * uv0_size;
picture->u = mem;
mem += uv_size;
picture->v = mem;
mem += uv_size;
// Security and validation checks
if (width <= 0 || height <= 0 || // check for luma/alpha param error
uv_width < 0 || uv_height < 0 || // check for u/v param error
y_size >= (1ULL << 40) || // check for reasonable global size
(size_t)total_size != total_size) { // check for overflow on 32bit
if (a_size) {
picture->a = mem;
mem += a_size;
}
if (uv0_size) {
picture->u0 = mem;
mem += uv0_size;
picture->v0 = mem;
mem += uv0_size;
}
} else {
#ifdef USE_LOSSLESS_ENCODER
const uint64_t argb_size = (uint64_t)width * height;
const uint64_t total_size = argb_size * sizeof(*picture->argb);
if (width <= 0 || height <= 0 ||
argb_size >= (1ULL << 40) ||
(size_t)total_size != total_size) {
return 0;
}
WebPPictureFree(picture); // erase previous buffer
picture->argb = (uint32_t*)malloc(total_size);
if (picture->argb == NULL) return 0;
picture->argb_stride = width;
#else
return 0;
}
picture->y_stride = y_stride;
picture->uv_stride = uv_stride;
picture->a_stride = a_stride;
picture->uv0_stride = uv0_stride;
WebPPictureFree(picture); // erase previous buffer
mem = (uint8_t*)malloc((size_t)total_size);
if (mem == NULL) return 0;
picture->y = mem;
mem += y_size;
picture->u = mem;
mem += uv_size;
picture->v = mem;
mem += uv_size;
if (a_size) {
picture->a = mem;
mem += a_size;
}
if (uv0_size) {
picture->u0 = mem;
mem += uv0_size;
picture->v0 = mem;
mem += uv0_size;
#endif
}
}
return 1;
@ -114,12 +133,18 @@ static void WebPPictureGrabSpecs(const WebPPicture* const src,
dst->y = dst->u = dst->v = NULL;
dst->u0 = dst->v0 = NULL;
dst->a = NULL;
#ifdef USE_LOSSLESS_ENCODER
dst->argb = NULL;
#endif
}
// Release memory owned by 'picture'.
void WebPPictureFree(WebPPicture* const picture) {
if (picture != NULL) {
free(picture->y);
#ifdef USE_LOSSLESS_ENCODER
free(picture->argb);
#endif
WebPPictureGrabSpecs(NULL, picture);
}
}
@ -144,28 +169,38 @@ int WebPPictureCopy(const WebPPicture* const src, WebPPicture* const dst) {
WebPPictureGrabSpecs(src, dst);
if (!WebPPictureAlloc(dst)) return 0;
CopyPlane(src->y, src->y_stride,
dst->y, dst->y_stride, dst->width, dst->height);
CopyPlane(src->u, src->uv_stride,
dst->u, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
CopyPlane(src->v, src->uv_stride,
dst->v, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
if (dst->a != NULL) {
CopyPlane(src->a, src->a_stride,
dst->a, dst->a_stride, dst->width, dst->height);
}
#ifdef WEBP_EXPERIMENTAL_FEATURES
if (dst->u0 != NULL) {
int uv0_width = src->width;
if ((dst->colorspace & WEBP_CSP_UV_MASK) == WEBP_YUV422) {
uv0_width = HALVE(uv0_width);
if (!src->use_argb_input) {
CopyPlane(src->y, src->y_stride,
dst->y, dst->y_stride, dst->width, dst->height);
CopyPlane(src->u, src->uv_stride,
dst->u, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
CopyPlane(src->v, src->uv_stride,
dst->v, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
if (dst->a != NULL) {
CopyPlane(src->a, src->a_stride,
dst->a, dst->a_stride, dst->width, dst->height);
}
#ifdef WEBP_EXPERIMENTAL_FEATURES
if (dst->u0 != NULL) {
int uv0_width = src->width;
if ((dst->colorspace & WEBP_CSP_UV_MASK) == WEBP_YUV422) {
uv0_width = HALVE(uv0_width);
}
CopyPlane(src->u0, src->uv0_stride,
dst->u0, dst->uv0_stride, uv0_width, dst->height);
CopyPlane(src->v0, src->uv0_stride,
dst->v0, dst->uv0_stride, uv0_width, dst->height);
}
CopyPlane(src->u0, src->uv0_stride,
dst->u0, dst->uv0_stride, uv0_width, dst->height);
CopyPlane(src->v0, src->uv0_stride,
dst->v0, dst->uv0_stride, uv0_width, dst->height);
}
#endif
} else {
#ifdef USE_LOSSLESS_ENCODER
CopyPlane((uint8_t*)src->argb, 4 * src->argb_stride,
(uint8_t*)dst->argb, 4 * dst->argb_stride,
4 * dst->width, dst->height);
#else
return 0;
#endif
}
return 1;
}
@ -438,66 +473,100 @@ static int Import(WebPPicture* const picture,
const int width = picture->width;
const int height = picture->height;
// Import luma plane
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
const int offset = step * x + y * rgb_stride;
picture->y[x + y * picture->y_stride] =
rgb_to_y(r_ptr[offset], g_ptr[offset], b_ptr[offset]);
}
}
// Downsample U/V plane
if (uv_csp != WEBP_YUV400) {
for (y = 0; y < (height >> 1); ++y) {
for (x = 0; x < (width >> 1); ++x) {
RGB_TO_UV(x, y, SUM4);
}
if (picture->width & 1) {
RGB_TO_UV(x, y, SUM2V);
}
}
if (height & 1) {
for (x = 0; x < (width >> 1); ++x) {
RGB_TO_UV(x, y, SUM2H);
}
if (width & 1) {
RGB_TO_UV(x, y, SUM1);
}
}
#ifdef WEBP_EXPERIMENTAL_FEATURES
// Store original U/V samples too
if (uv_csp == WEBP_YUV422) {
for (y = 0; y < height; ++y) {
for (x = 0; x < (width >> 1); ++x) {
RGB_TO_UV0(2 * x, x, y, SUM2H);
}
if (width & 1) {
RGB_TO_UV0(2 * x, x, y, SUM1);
}
}
} else if (uv_csp == WEBP_YUV444) {
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
RGB_TO_UV0(x, x, y, SUM1);
}
}
}
#endif
} else {
MakeGray(picture);
}
if (import_alpha) {
const uint8_t* const a_ptr = rgb + 3;
assert(step >= 4);
if (!picture->use_argb_input) {
// Import luma plane
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
picture->a[x + y * picture->a_stride] =
a_ptr[step * x + y * rgb_stride];
const int offset = step * x + y * rgb_stride;
picture->y[x + y * picture->y_stride] =
rgb_to_y(r_ptr[offset], g_ptr[offset], b_ptr[offset]);
}
}
// Downsample U/V plane
if (uv_csp != WEBP_YUV400) {
for (y = 0; y < (height >> 1); ++y) {
for (x = 0; x < (width >> 1); ++x) {
RGB_TO_UV(x, y, SUM4);
}
if (picture->width & 1) {
RGB_TO_UV(x, y, SUM2V);
}
}
if (height & 1) {
for (x = 0; x < (width >> 1); ++x) {
RGB_TO_UV(x, y, SUM2H);
}
if (width & 1) {
RGB_TO_UV(x, y, SUM1);
}
}
#ifdef WEBP_EXPERIMENTAL_FEATURES
// Store original U/V samples too
if (uv_csp == WEBP_YUV422) {
for (y = 0; y < height; ++y) {
for (x = 0; x < (width >> 1); ++x) {
RGB_TO_UV0(2 * x, x, y, SUM2H);
}
if (width & 1) {
RGB_TO_UV0(2 * x, x, y, SUM1);
}
}
} else if (uv_csp == WEBP_YUV444) {
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
RGB_TO_UV0(x, x, y, SUM1);
}
}
}
#endif
} else {
MakeGray(picture);
}
if (import_alpha) {
const uint8_t* const a_ptr = rgb + 3;
assert(step >= 4);
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
picture->a[x + y * picture->a_stride] =
a_ptr[step * x + y * rgb_stride];
}
}
}
} else {
#ifdef USE_LOSSLESS_ENCODER
if (!import_alpha) {
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
const int offset = step * x + y * rgb_stride;
const uint32_t argb =
0xff000000 |
(r_ptr[offset] << 16) |
(g_ptr[offset] << 8) |
(b_ptr[offset]);
picture->argb[x + y * picture->argb_stride] = argb;
}
}
} else {
const uint8_t* const a_ptr = rgb + 3;
assert(step >= 4);
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
const int offset = step * x + y * rgb_stride;
const uint32_t argb =
(a_ptr[offset] << 24) |
(r_ptr[offset] << 16) |
(g_ptr[offset] << 8) |
(b_ptr[offset]);
picture->argb[x + y * picture->argb_stride] = argb;
}
}
}
#else
return 0;
#endif
}
return 1;
}

1239
src/enc/vp8l.c Normal file

File diff suppressed because it is too large Load Diff

78
src/enc/vp8li.h Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2012 Google Inc. All Rights Reserved.
//
// This code is licensed under the same terms as WebM:
// Software License Agreement: http://www.webmproject.org/license/software/
// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
// -----------------------------------------------------------------------------
//
// Lossless encoder: internal header.
//
// Author: Vikas Arora (vikaas.arora@gmail.com)
#ifndef WEBP_ENC_VP8LI_H_
#define WEBP_ENC_VP8LI_H_
#ifdef USE_LOSSLESS_ENCODER
#include "./histogram.h"
#include "../webp/encode.h"
#include "../utils/bit_writer.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
// TODO(vikasa): factorize these with ones used in lossless decoder.
#define TAG_SIZE 4
#define CHUNK_HEADER_SIZE 8
#define RIFF_HEADER_SIZE 12
#define HEADER_SIZE (RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE)
#define SIGNATURE_SIZE 1
#define LOSSLESS_MAGIC_BYTE 0x64
#define MAX_PALETTE_SIZE 256
#define PALETTE_KEY_RIGHT_SHIFT 22 // Key for 1K buffer.
typedef struct {
const WebPConfig* config_; // user configuration and parameters
WebPPicture* pic_; // input picture.
uint32_t* argb_; // Transformed argb image data.
uint32_t* argb_scratch_; // Scratch memory for argb rows
// (used for prediction).
uint32_t* transform_data_; // Scratch memory for transform data.
int current_width_; // Corresponds to packed image width.
// Encoding parameters derived from quality parameter.
int histo_bits_;
int transform_bits_;
int cache_bits_; // If equal to 0, don't use color cache.
// Encoding parameters derived from image characteristics.
int use_cross_color_;
int use_predict_;
int use_palette_;
int palette_size_;
uint32_t palette_[MAX_PALETTE_SIZE];
} VP8LEncoder;
//------------------------------------------------------------------------------
// internal functions. Not public.
// in vp8l.c
// Encodes the picture.
// Returns 0 if config or picture is NULL or picture doesn't have valid argb
// input.
int VP8LEncodeImage(const WebPConfig* const config,
WebPPicture* const picture);
//------------------------------------------------------------------------------
#if defined(__cplusplus) || defined(c_plusplus)
} // extern "C"
#endif
#endif
#endif /* WEBP_ENC_VP8LI_H_ */

View File

@ -15,6 +15,7 @@
#include <math.h>
#include "./vp8enci.h"
#include "./vp8li.h"
// #define PRINT_MEMORY_INFO
@ -142,8 +143,8 @@ static void MapConfigToTools(VP8Encoder* const enc) {
// LFStats: 2048
// Picture size (yuv): 589824
static VP8Encoder* InitEncoder(const WebPConfig* const config,
WebPPicture* const picture) {
static VP8Encoder* InitVP8Encoder(const WebPConfig* const config,
WebPPicture* const picture) {
const int use_filter =
(config->filter_strength > 0) || (config->autofilter > 0);
const int mb_w = (picture->width + 15) >> 4;
@ -259,7 +260,7 @@ static VP8Encoder* InitEncoder(const WebPConfig* const config,
return enc;
}
static void DeleteEncoder(VP8Encoder* enc) {
static void DeleteVP8Encoder(VP8Encoder* enc) {
if (enc != NULL) {
VP8EncDeleteAlpha(enc);
#ifdef WEBP_EXPERIMENTAL_FEATURES
@ -327,7 +328,6 @@ int WebPReportProgress(VP8Encoder* const enc, int percent) {
//------------------------------------------------------------------------------
int WebPEncode(const WebPConfig* const config, WebPPicture* const pic) {
VP8Encoder* enc;
int ok;
if (pic == NULL)
@ -339,27 +339,40 @@ int WebPEncode(const WebPConfig* const config, WebPPicture* const pic) {
return WebPEncodingSetError(pic, VP8_ENC_ERROR_INVALID_CONFIGURATION);
if (pic->width <= 0 || pic->height <= 0)
return WebPEncodingSetError(pic, VP8_ENC_ERROR_BAD_DIMENSION);
if (pic->y == NULL || pic->u == NULL || pic->v == NULL)
return WebPEncodingSetError(pic, VP8_ENC_ERROR_NULL_PARAMETER);
if (pic->width > WEBP_MAX_DIMENSION || pic->height > WEBP_MAX_DIMENSION)
return WebPEncodingSetError(pic, VP8_ENC_ERROR_BAD_DIMENSION);
enc = InitEncoder(config, pic);
if (enc == NULL) return 0; // pic->error is already set.
// Note: each of the tasks below account for 20% in the progress report.
ok = VP8EncAnalyze(enc)
&& VP8StatLoop(enc)
&& VP8EncLoop(enc)
&& VP8EncFinishAlpha(enc)
if (!config->lossless) {
VP8Encoder* enc = NULL;
if (pic->y == NULL || pic->u == NULL || pic->v == NULL)
return WebPEncodingSetError(pic, VP8_ENC_ERROR_NULL_PARAMETER);
enc = InitVP8Encoder(config, pic);
if (enc == NULL) return 0; // pic->error is already set.
// Note: each of the tasks below account for 20% in the progress report.
ok = VP8EncAnalyze(enc)
&& VP8StatLoop(enc)
&& VP8EncLoop(enc)
&& VP8EncFinishAlpha(enc)
#ifdef WEBP_EXPERIMENTAL_FEATURES
&& VP8EncFinishLayer(enc)
&& VP8EncFinishLayer(enc)
#endif
&& VP8EncWrite(enc);
StoreStats(enc);
if (!ok) {
VP8EncFreeBitWriters(enc);
}
DeleteVP8Encoder(enc);
} else {
#ifdef USE_LOSSLESS_ENCODER
if (pic->argb == NULL)
return WebPEncodingSetError(pic, VP8_ENC_ERROR_NULL_PARAMETER);
ok = VP8LEncodeImage(config, pic); // Sets pic->error in case of problem.
#else
return WebPEncodingSetError(pic, VP8_ENC_ERROR_INVALID_CONFIGURATION);
#endif
&& VP8EncWrite(enc);
StoreStats(enc);
if (!ok) {
VP8EncFreeBitWriters(enc);
}
DeleteEncoder(enc);
return ok;
}