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
commit e38602d2ad
20 changed files with 4407 additions and 295 deletions

View File

@ -838,6 +838,11 @@ int main(int argc, const char *argv[]) {
}
} else if (!strcmp(argv[c], "-noalpha")) {
keep_alpha = 0;
#ifdef USE_LOSSLESS_ENCODER
} else if (!strcmp(argv[c], "-lossless")) {
config.lossless = 1;
picture.use_argb_input = 1;
#endif
} else if (!strcmp(argv[c], "-size") && c < argc - 1) {
config.target_size = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-psnr") && c < argc - 1) {

View File

@ -31,6 +31,7 @@ static const int kCodeLengthRepeatOffsets[3] = { 3, 3, 11 };
#define NUM_LENGTH_CODES 24
#define NUM_DISTANCE_CODES 40
#define DEFAULT_CODE_LENGTH 8
#define MAX_CACHE_BITS 11
// -----------------------------------------------------------------------------
// Five Huffman codes are used at each meta code:
@ -171,7 +172,7 @@ static WEBP_INLINE int ReadSymbol(const HuffmanTree* tree,
static int ReadHuffmanCodeLengths(
VP8LDecoder* const dec, const int* const code_length_code_lengths,
int num_codes, int num_symbols, int* const code_lengths) {
int num_symbols, int* const code_lengths) {
int ok = 0;
VP8LBitReader* const br = &dec->br_;
int symbol;
@ -179,7 +180,8 @@ static int ReadHuffmanCodeLengths(
int prev_code_len = DEFAULT_CODE_LENGTH;
HuffmanTree tree;
if (!HuffmanTreeBuildImplicit(&tree, code_length_code_lengths, num_codes)) {
if (!HuffmanTreeBuildImplicit(&tree, code_length_code_lengths,
NUM_CODE_LENGTH_CODES)) {
dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
return 0;
}
@ -236,25 +238,17 @@ static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec,
int symbols[2];
int codes[2];
int code_lengths[2];
const int nbits = VP8LReadBits(br, 3);
const int num_symbols = 1 + ((nbits == 0) ? 0 : VP8LReadBits(br, 1));
if (nbits == 0) {
symbols[0] = 0;
codes[0] = 0;
code_lengths[0] = 0;
} else {
const int num_bits = (nbits - 1) * 2 + 4;
int i;
for (i = 0; i < num_symbols; ++i) {
symbols[i] = VP8LReadBits(br, num_bits);
if (symbols[i] >= alphabet_size) {
dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
return 0;
}
codes[i] = i;
code_lengths[i] = num_symbols - 1;
}
const int num_symbols = VP8LReadBits(br, 1) + 1;
const int first_symbol_len_code = VP8LReadBits(br, 1);
// The first code is either 1 bit or 8 bit code.
symbols[0] = VP8LReadBits(br, (first_symbol_len_code == 0) ? 1 : 8);
codes[0] = 0;
code_lengths[0] = num_symbols - 1;
// The second code (if present), is always 8 bit long.
if (num_symbols == 2) {
symbols[1] = VP8LReadBits(br, 8);
codes[1] = 1;
code_lengths[1] = num_symbols - 1;
}
ok = HuffmanTreeBuildExplicit(tree, code_lengths, codes,
symbols, num_symbols);
@ -277,9 +271,8 @@ static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec,
for (i = 0; i < num_codes; ++i) {
code_length_code_lengths[kCodeLengthCodeOrder[i]] = VP8LReadBits(br, 3);
}
ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths,
NUM_CODE_LENGTH_CODES,
alphabet_size, code_lengths);
ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths, alphabet_size,
code_lengths);
if (ok) {
ok = HuffmanTreeBuildImplicit(tree, code_lengths, alphabet_size);
}
@ -293,11 +286,23 @@ static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec,
return 1;
}
static void DeleteHtreeGroups(HTreeGroup* htree_groups, int num_htree_groups) {
if (htree_groups != NULL) {
int i, j;
for (i = 0; i < num_htree_groups; ++i) {
HuffmanTree* const htrees = htree_groups[i].htrees_;
for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
HuffmanTreeRelease(&htrees[j]);
}
}
free(htree_groups);
}
}
static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,
int* const color_cache_bits_ptr) {
int color_cache_bits) {
int ok = 0;
int i, j;
int color_cache_size;
VP8LBitReader* const br = &dec->br_;
VP8LMetadata* const hdr = &dec->hdr_;
uint32_t* huffman_image = NULL;
@ -305,11 +310,11 @@ static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,
int num_htree_groups = 1;
if (VP8LReadBits(br, 1)) { // use meta Huffman codes
int meta_codes_nbits;
const int huffman_precision = VP8LReadBits(br, 4);
const int huffman_xsize = VP8LSubSampleSize(xsize, huffman_precision);
const int huffman_ysize = VP8LSubSampleSize(ysize, huffman_precision);
const int huffman_pixs = huffman_xsize * huffman_ysize;
if (!DecodeImageStream(huffman_xsize, huffman_ysize, 0, dec,
&huffman_image)) {
dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
@ -318,19 +323,12 @@ static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,
hdr->huffman_subsample_bits_ = huffman_precision;
for (i = 0; i < huffman_pixs; ++i) {
// The huffman data is stored in red and green bytes.
huffman_image[i] = (huffman_image[i] >> 8) & 0xffff;
const int index = (huffman_image[i] >> 8) & 0xffff;
huffman_image[i] = index;
if (index >= num_htree_groups) {
num_htree_groups = index + 1;
}
}
meta_codes_nbits = VP8LReadBits(br, 4);
num_htree_groups = 2 + VP8LReadBits(br, meta_codes_nbits);
}
if (VP8LReadBits(br, 1)) { // use color cache
*color_cache_bits_ptr = VP8LReadBits(br, 4);
color_cache_size = 1 << *color_cache_bits_ptr;
} else {
*color_cache_bits_ptr = 0;
color_cache_size = 0;
}
htree_groups = (HTreeGroup*)calloc(num_htree_groups, sizeof(*htree_groups));
@ -341,12 +339,13 @@ static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,
ok = !br->error_;
for (i = 0; ok && i < num_htree_groups; ++i) {
HuffmanTree* const htrees = htree_groups[i].htrees_;
for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
int alphabet_size = kAlphabetSize[j];
if (j == 0) {
alphabet_size += color_cache_size;
if (j == 0 && color_cache_bits > 0) {
alphabet_size += 1 << color_cache_bits;
}
ok = ReadHuffmanCode(alphabet_size, dec, &htree_groups[i].htrees_[j]);
ok = ReadHuffmanCode(alphabet_size, dec, htrees + j);
ok = ok && !br->error_;
}
}
@ -360,14 +359,7 @@ static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,
Error:
free(huffman_image);
if (htree_groups != NULL) {
for (i = 0; i < num_htree_groups; ++i) {
for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
HuffmanTreeRelease(&htree_groups[i].htrees_[j]);
}
}
free(htree_groups);
}
DeleteHtreeGroups(htree_groups, num_htree_groups);
return 0;
}
@ -498,6 +490,7 @@ static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr,
int x, int y) {
const int meta_index = GetMetaIndex(hdr->huffman_image_, hdr->huffman_xsize_,
hdr->huffman_subsample_bits_, x, y);
assert(meta_index < hdr->num_htree_groups_);
return hdr->htree_groups_ + meta_index;
}
@ -770,16 +763,7 @@ static void ClearMetadata(VP8LMetadata* const hdr) {
assert(hdr);
free(hdr->huffman_image_);
if (hdr->htree_groups_ != NULL) {
int i, j;
for (i = 0; i < hdr->num_htree_groups_; ++i) {
for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
HuffmanTreeRelease(&hdr->htree_groups_[i].htrees_[j]);
}
}
free(hdr->htree_groups_);
}
DeleteHtreeGroups(hdr->htree_groups_, hdr->num_htree_groups_);
VP8LColorCacheDelete(hdr->color_cache_);
InitMetadata(hdr);
}
@ -836,29 +820,38 @@ static int DecodeImageStream(int xsize, int ysize,
int ok = 1;
int transform_xsize = xsize;
int transform_ysize = ysize;
VP8LBitReader* const br = &dec->br_;
VP8LMetadata* const hdr = &dec->hdr_;
uint32_t* data = NULL;
const int transform_start_idx = dec->next_transform_;
int color_cache_bits = 0;
VP8LBitReader* const br = &dec->br_;
int transform_start_idx = dec->next_transform_;
// Step#1: Read the transforms (may recurse).
// Read the transforms (may recurse).
if (is_level0) {
while (ok && VP8LReadBits(br, 1)) {
ok = ReadTransform(&transform_xsize, &transform_ysize, dec);
}
}
// Step#2: Read the Huffman codes (may recurse).
ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize,
&color_cache_bits);
// Color cache
if (ok && VP8LReadBits(br, 1)) {
color_cache_bits = VP8LReadBits(br, 4);
ok = (color_cache_bits >= 1 && color_cache_bits <= MAX_CACHE_BITS);
if (!ok) {
dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
goto End;
}
}
// Read the Huffman codes (may recurse).
ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize,
color_cache_bits);
if (!ok) {
dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
goto End;
}
// Finish setting up the color-cache
if (color_cache_bits > 0) {
hdr->color_cache_size_ = 1 << color_cache_bits;
hdr->color_cache_ = (VP8LColorCache*)malloc(sizeof(*hdr->color_cache_));
@ -869,7 +862,6 @@ static int DecodeImageStream(int xsize, int ysize,
goto End;
}
}
UpdateDecoder(dec, transform_xsize, transform_ysize);
if (is_level0) { // level 0 complete
@ -884,11 +876,11 @@ static int DecodeImageStream(int xsize, int ysize,
goto End;
}
// Step#3: Use the Huffman trees to decode the LZ77 encoded data.
// Use the Huffman trees to decode the LZ77 encoded data.
ok = DecodeImageData(dec, data, transform_xsize, transform_ysize, 0);
ok = ok && !br->error_;
// Step#4: Apply transforms on the decoded data.
// Apply transforms on the decoded data.
if (ok) ApplyInverseTransforms(dec, transform_start_idx, data);
End:

View File

@ -15,12 +15,128 @@
extern "C" {
#endif
#include <math.h>
#include <stdlib.h>
#include "./lossless.h"
#include "../dec/vp8li.h"
#ifdef USE_LOSSLESS_ENCODER
#include "../enc/histogram.h"
// A lookup table for small values of log(int) to be used in entropy
// computation.
//
// ", ".join(["%.16ff" % x for x in [0.0]+[log(x) for x in range(1, 256)]])
#define LOG_LOOKUP_IDX_MAX 256
static const float kLogTable[LOG_LOOKUP_IDX_MAX] = {
0.0000000000000000f, 0.0000000000000000f, 0.6931471805599453f,
1.0986122886681098f, 1.3862943611198906f, 1.6094379124341003f,
1.7917594692280550f, 1.9459101490553132f, 2.0794415416798357f,
2.1972245773362196f, 2.3025850929940459f, 2.3978952727983707f,
2.4849066497880004f, 2.5649493574615367f, 2.6390573296152584f,
2.7080502011022101f, 2.7725887222397811f, 2.8332133440562162f,
2.8903717578961645f, 2.9444389791664403f, 2.9957322735539909f,
3.0445224377234230f, 3.0910424533583161f, 3.1354942159291497f,
3.1780538303479458f, 3.2188758248682006f, 3.2580965380214821f,
3.2958368660043291f, 3.3322045101752038f, 3.3672958299864741f,
3.4011973816621555f, 3.4339872044851463f, 3.4657359027997265f,
3.4965075614664802f, 3.5263605246161616f, 3.5553480614894135f,
3.5835189384561099f, 3.6109179126442243f, 3.6375861597263857f,
3.6635616461296463f, 3.6888794541139363f, 3.7135720667043080f,
3.7376696182833684f, 3.7612001156935624f, 3.7841896339182610f,
3.8066624897703196f, 3.8286413964890951f, 3.8501476017100584f,
3.8712010109078911f, 3.8918202981106265f, 3.9120230054281460f,
3.9318256327243257f, 3.9512437185814275f, 3.9702919135521220f,
3.9889840465642745f, 4.0073331852324712f, 4.0253516907351496f,
4.0430512678345503f, 4.0604430105464191f, 4.0775374439057197f,
4.0943445622221004f, 4.1108738641733114f, 4.1271343850450917f,
4.1431347263915326f, 4.1588830833596715f, 4.1743872698956368f,
4.1896547420264252f, 4.2046926193909657f, 4.2195077051761070f,
4.2341065045972597f, 4.2484952420493594f, 4.2626798770413155f,
4.2766661190160553f, 4.2904594411483910f, 4.3040650932041702f,
4.3174881135363101f, 4.3307333402863311f, 4.3438054218536841f,
4.3567088266895917f, 4.3694478524670215f, 4.3820266346738812f,
4.3944491546724391f, 4.4067192472642533f, 4.4188406077965983f,
4.4308167988433134f, 4.4426512564903167f, 4.4543472962535073f,
4.4659081186545837f, 4.4773368144782069f, 4.4886363697321396f,
4.4998096703302650f, 4.5108595065168497f, 4.5217885770490405f,
4.5325994931532563f, 4.5432947822700038f, 4.5538768916005408f,
4.5643481914678361f, 4.5747109785033828f, 4.5849674786705723f,
4.5951198501345898f, 4.6051701859880918f, 4.6151205168412597f,
4.6249728132842707f, 4.6347289882296359f, 4.6443908991413725f,
4.6539603501575231f, 4.6634390941120669f, 4.6728288344619058f,
4.6821312271242199f, 4.6913478822291435f, 4.7004803657924166f,
4.7095302013123339f, 4.7184988712950942f, 4.7273878187123408f,
4.7361984483944957f, 4.7449321283632502f, 4.7535901911063645f,
4.7621739347977563f, 4.7706846244656651f, 4.7791234931115296f,
4.7874917427820458f, 4.7957905455967413f, 4.8040210447332568f,
4.8121843553724171f, 4.8202815656050371f, 4.8283137373023015f,
4.8362819069514780f, 4.8441870864585912f, 4.8520302639196169f,
4.8598124043616719f, 4.8675344504555822f, 4.8751973232011512f,
4.8828019225863706f, 4.8903491282217537f, 4.8978397999509111f,
4.9052747784384296f, 4.9126548857360524f, 4.9199809258281251f,
4.9272536851572051f, 4.9344739331306915f, 4.9416424226093039f,
4.9487598903781684f, 4.9558270576012609f, 4.9628446302599070f,
4.9698132995760007f, 4.9767337424205742f, 4.9836066217083363f,
4.9904325867787360f, 4.9972122737641147f, 5.0039463059454592f,
5.0106352940962555f, 5.0172798368149243f, 5.0238805208462765f,
5.0304379213924353f, 5.0369526024136295f, 5.0434251169192468f,
5.0498560072495371f, 5.0562458053483077f, 5.0625950330269669f,
5.0689042022202315f, 5.0751738152338266f, 5.0814043649844631f,
5.0875963352323836f, 5.0937502008067623f, 5.0998664278241987f,
5.1059454739005803f, 5.1119877883565437f, 5.1179938124167554f,
5.1239639794032588f, 5.1298987149230735f, 5.1357984370502621f,
5.1416635565026603f, 5.1474944768134527f, 5.1532915944977793f,
5.1590552992145291f, 5.1647859739235145f, 5.1704839950381514f,
5.1761497325738288f, 5.1817835502920850f, 5.1873858058407549f,
5.1929568508902104f, 5.1984970312658261f, 5.2040066870767951f,
5.2094861528414214f, 5.2149357576089859f, 5.2203558250783244f,
5.2257466737132017f, 5.2311086168545868f, 5.2364419628299492f,
5.2417470150596426f, 5.2470240721604862f, 5.2522734280466299f,
5.2574953720277815f, 5.2626901889048856f, 5.2678581590633282f,
5.2729995585637468f, 5.2781146592305168f, 5.2832037287379885f,
5.2882670306945352f, 5.2933048247244923f, 5.2983173665480363f,
5.3033049080590757f, 5.3082676974012051f, 5.3132059790417872f,
5.3181199938442161f, 5.3230099791384085f, 5.3278761687895813f,
5.3327187932653688f, 5.3375380797013179f, 5.3423342519648109f,
5.3471075307174685f, 5.3518581334760666f, 5.3565862746720123f,
5.3612921657094255f, 5.3659760150218512f, 5.3706380281276624f,
5.3752784076841653f, 5.3798973535404597f, 5.3844950627890888f,
5.3890717298165010f, 5.3936275463523620f, 5.3981627015177525f,
5.4026773818722793f, 5.4071717714601188f, 5.4116460518550396f,
5.4161004022044201f, 5.4205349992722862f, 5.4249500174814029f,
5.4293456289544411f, 5.4337220035542400f, 5.4380793089231956f,
5.4424177105217932f, 5.4467373716663099f, 5.4510384535657002f,
5.4553211153577017f, 5.4595855141441589f, 5.4638318050256105f,
5.4680601411351315f, 5.4722706736714750f, 5.4764635519315110f,
5.4806389233419912f, 5.4847969334906548f, 5.4889377261566867f,
5.4930614433405482f, 5.4971682252932021f, 5.5012582105447274f,
5.5053315359323625f, 5.5093883366279774f, 5.5134287461649825f,
5.5174528964647074f, 5.5214609178622460f, 5.5254529391317835f,
5.5294290875114234f, 5.5333894887275203f, 5.5373342670185366f,
5.5412635451584258f
};
#define APPROX_LOG_MAX 4096
#define LOG_2_BASE_E 0.6931471805599453f
float VP8LFastLog(int v) {
if (v < APPROX_LOG_MAX) {
int log_cnt = 0;
while (v >= LOG_LOOKUP_IDX_MAX) {
++log_cnt;
v = v >> 1;
}
return kLogTable[v] + (log_cnt * LOG_2_BASE_E);
}
return log(v);
}
#endif
//------------------------------------------------------------------------------
// Inverse image transforms.
// Image transforms.
// In-place sum of each component with mod 256.
static WEBP_INLINE void AddPixelsEq(uint32_t* a, uint32_t b) {
@ -101,61 +217,67 @@ static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) {
//------------------------------------------------------------------------------
// Predictors
static void Predictor0(uint32_t* src, const uint32_t* top) {
static uint32_t Predictor0(uint32_t left, const uint32_t* const top) {
(void)top;
AddPixelsEq(src, ARGB_BLACK);
(void)left;
return ARGB_BLACK;
}
static void Predictor1(uint32_t* src, const uint32_t* top) {
static uint32_t Predictor1(uint32_t left, const uint32_t* const top) {
(void)top;
AddPixelsEq(src, src[-1]); // left
return left;
}
static void Predictor2(uint32_t* src, const uint32_t* top) {
AddPixelsEq(src, top[0]);
static uint32_t Predictor2(uint32_t left, const uint32_t* const top) {
(void)left;
return top[0];
}
static void Predictor3(uint32_t* src, const uint32_t* top) {
AddPixelsEq(src, top[1]);
static uint32_t Predictor3(uint32_t left, const uint32_t* const top) {
(void)left;
return top[1];
}
static void Predictor4(uint32_t* src, const uint32_t* top) {
AddPixelsEq(src, top[-1]);
static uint32_t Predictor4(uint32_t left, const uint32_t* const top) {
(void)left;
return top[-1];
}
static void Predictor5(uint32_t* src, const uint32_t* top) {
const uint32_t pred = Average3(src[-1], top[0], top[1]);
AddPixelsEq(src, pred);
static uint32_t Predictor5(uint32_t left, const uint32_t* const top) {
const uint32_t pred = Average3(left, top[0], top[1]);
return pred;
}
static void Predictor6(uint32_t* src, const uint32_t* top) {
const uint32_t pred = Average2(src[-1], top[-1]);
AddPixelsEq(src, pred);
static uint32_t Predictor6(uint32_t left, const uint32_t* const top) {
const uint32_t pred = Average2(left, top[-1]);
return pred;
}
static void Predictor7(uint32_t* src, const uint32_t* top) {
const uint32_t pred = Average2(src[-1], top[0]);
AddPixelsEq(src, pred);
static uint32_t Predictor7(uint32_t left, const uint32_t* const top) {
const uint32_t pred = Average2(left, top[0]);
return pred;
}
static void Predictor8(uint32_t* src, const uint32_t* top) {
static uint32_t Predictor8(uint32_t left, const uint32_t* const top) {
const uint32_t pred = Average2(top[-1], top[0]);
AddPixelsEq(src, pred);
(void)left;
return pred;
}
static void Predictor9(uint32_t* src, const uint32_t* top) {
static uint32_t Predictor9(uint32_t left, const uint32_t* const top) {
const uint32_t pred = Average2(top[0], top[1]);
AddPixelsEq(src, pred);
(void)left;
return pred;
}
static void Predictor10(uint32_t* src, const uint32_t* top) {
const uint32_t pred = Average4(src[-1], top[-1], top[0], top[1]);
AddPixelsEq(src, pred);
static uint32_t Predictor10(uint32_t left, const uint32_t* const top) {
const uint32_t pred = Average4(left, top[-1], top[0], top[1]);
return pred;
}
static void Predictor11(uint32_t* src, const uint32_t* top) {
const uint32_t pred = Select(top[0], src[-1], top[-1]);
AddPixelsEq(src, pred);
static uint32_t Predictor11(uint32_t left, const uint32_t* const top) {
const uint32_t pred = Select(top[0], left, top[-1]);
return pred;
}
static void Predictor12(uint32_t* src, const uint32_t* top) {
const uint32_t pred = ClampedAddSubtractFull(src[-1], top[0], top[-1]);
AddPixelsEq(src, pred);
static uint32_t Predictor12(uint32_t left, const uint32_t* const top) {
const uint32_t pred = ClampedAddSubtractFull(left, top[0], top[-1]);
return pred;
}
static void Predictor13(uint32_t* src, const uint32_t* top) {
const uint32_t pred = ClampedAddSubtractHalf(src[-1], top[0], top[-1]);
AddPixelsEq(src, pred);
static uint32_t Predictor13(uint32_t left, const uint32_t* const top) {
const uint32_t pred = ClampedAddSubtractHalf(left, top[0], top[-1]);
return pred;
}
typedef void (*PredictorFunc)(uint32_t* src, const uint32_t* top);
typedef uint32_t (*PredictorFunc)(uint32_t left, const uint32_t* const top);
static const PredictorFunc kPredictors[16] = {
Predictor0, Predictor1, Predictor2, Predictor3,
Predictor4, Predictor5, Predictor6, Predictor7,
@ -164,15 +286,215 @@ static const PredictorFunc kPredictors[16] = {
Predictor0, Predictor0 // <- padding security sentinels
};
#ifdef USE_LOSSLESS_ENCODER
// TODO(vikasa): Replace 256 etc with defines.
static double PredictionCostSpatial(const int* counts,
int weight_0, double exp_val) {
const int significant_symbols = 16;
const double exp_decay_factor = 0.6;
double bits = weight_0 * counts[0];
int i;
for (i = 1; i < significant_symbols; ++i) {
bits += exp_val * (counts[i] + counts[256 - i]);
exp_val *= exp_decay_factor;
}
return -0.1 * bits;
}
// Compute the Shanon's entropy: Sum(p*log2(p))
static double ShannonEntropy(const int* const array, int n) {
int i;
double retval = 0;
int sum = 0;
for (i = 0; i < n; ++i) {
if (array[i] != 0) {
sum += array[i];
retval += array[i] * VP8LFastLog(array[i]);
}
}
retval -= sum * VP8LFastLog(sum);
retval *= -1.4426950408889634; // 1.0 / -FastLog(2);
return retval;
}
static double PredictionCostSpatialHistogram(int accumulated[4][256],
int tile[4][256]) {
int i;
int k;
int combo[256];
double retval = 0;
for (i = 0; i < 4; ++i) {
const double exp_val = 0.94;
retval += PredictionCostSpatial(&tile[i][0], 1, exp_val);
retval += ShannonEntropy(&tile[i][0], 256);
for (k = 0; k < 256; ++k) {
combo[k] = accumulated[i][k] + tile[i][k];
}
retval += ShannonEntropy(&combo[0], 256);
}
return retval;
}
static int GetBestPredictorForTile(int width, int height,
int tile_x, int tile_y, int bits,
int accumulated[4][256],
const uint32_t* const argb_scratch) {
const int kNumPredModes = 14;
const int col_start = tile_x << bits;
const int row_start = tile_y << bits;
const int tile_size = 1 << bits;
const int ymax = (tile_size <= height - row_start) ?
tile_size : height - row_start;
const int xmax = (tile_size <= width - col_start) ?
tile_size : width - col_start;
int histo[4][256];
double best_diff = 1e99;
int best_mode = 0;
int mode;
for (mode = 0; mode < kNumPredModes; ++mode) {
const uint32_t* current_row = argb_scratch;
const PredictorFunc pred_func = kPredictors[mode];
double cur_diff;
int y;
memset(&histo[0][0], 0, sizeof(histo));
for (y = 0; y < ymax; ++y) {
int x;
const int row = row_start + y;
const uint32_t* const upper_row = current_row;
current_row = upper_row + width;
for (x = 0; x < xmax; ++x) {
const int col = col_start + x;
uint32_t predict;
uint32_t predict_diff;
if (row == 0) {
predict = (col == 0) ? ARGB_BLACK : current_row[col - 1]; // Left.
} else if (col == 0) {
predict = upper_row[col]; // Top.
} else {
predict = pred_func(current_row[col - 1], upper_row + col);
}
predict_diff = VP8LSubPixels(current_row[col], predict);
++histo[0][predict_diff >> 24];
++histo[1][((predict_diff >> 16) & 0xff)];
++histo[2][((predict_diff >> 8) & 0xff)];
++histo[3][(predict_diff & 0xff)];
}
}
cur_diff = PredictionCostSpatialHistogram(accumulated, histo);
if (cur_diff < best_diff) {
best_diff = cur_diff;
best_mode = mode;
}
}
return best_mode;
}
static void CopyTileWithPrediction(int width, int height,
int tile_x, int tile_y, int bits, int mode,
const uint32_t* const argb_scratch,
uint32_t* const argb) {
const int col_start = tile_x << bits;
const int row_start = tile_y << bits;
const int tile_size = 1 << bits;
const int ymax = (tile_size <= height - row_start) ?
tile_size : height - row_start;
const int xmax = (tile_size <= width - col_start) ?
tile_size : width - col_start;
const PredictorFunc pred_func = kPredictors[mode];
const uint32_t* current_row = argb_scratch;
int y;
for (y = 0; y < ymax; ++y) {
int x;
const int row = row_start + y;
const uint32_t* const upper_row = current_row;
current_row = upper_row + width;
for (x = 0; x < xmax; ++x) {
const int col = col_start + x;
const int pix = row * width + col;
uint32_t predict;
if (row == 0) {
predict = (col == 0) ? ARGB_BLACK : current_row[col - 1]; // Left.
} else if (col == 0) {
predict = upper_row[col]; // Top.
} else {
predict = pred_func(current_row[col - 1], upper_row + col);
}
argb[pix] = VP8LSubPixels(current_row[col], predict);
}
}
}
void VP8LResidualImage(int width, int height, int bits,
uint32_t* const argb, uint32_t* const argb_scratch,
uint32_t* const image) {
const int max_tile_size = 1 << bits;
const int tiles_per_row = VP8LSubSampleSize(width, bits);
const int tiles_per_col = VP8LSubSampleSize(height, bits);
uint32_t* const upper_row = argb_scratch;
uint32_t* const current_tile_rows = argb_scratch + width;
int tile_y;
int histo[4][256];
memset(histo, 0, sizeof(histo));
for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) {
const int tile_y_offset = tile_y * max_tile_size;
const int this_tile_height =
(tile_y < tiles_per_col - 1) ? max_tile_size : height - tile_y_offset;
int tile_x;
if (tile_y > 0) {
memcpy(upper_row, current_tile_rows + (max_tile_size - 1) * width,
width * sizeof(*upper_row));
}
memcpy(current_tile_rows, &argb[tile_y_offset * width],
this_tile_height * width * sizeof(*current_tile_rows));
for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) {
int pred;
int y;
const int tile_x_offset = tile_x * max_tile_size;
int all_x_max = tile_x_offset + max_tile_size;
if (all_x_max > width) {
all_x_max = width;
}
pred = GetBestPredictorForTile(width, height, tile_x, tile_y, bits, histo,
argb_scratch);
image[tile_y * tiles_per_row + tile_x] = 0xff000000u | (pred << 8);
CopyTileWithPrediction(width, height, tile_x, tile_y, bits, pred,
argb_scratch, argb);
for (y = 0; y < max_tile_size; ++y) {
int ix;
int all_x;
int all_y = tile_y_offset + y;
if (all_y >= height) {
break;
}
ix = all_y * width + tile_x_offset;
for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) {
const uint32_t a = argb[ix];
++histo[0][a >> 24];
++histo[1][((a >> 16) & 0xff)];
++histo[2][((a >> 8) & 0xff)];
++histo[3][(a & 0xff)];
}
}
}
}
}
#endif
// Inverse prediction.
static void PredictorInverseTransform(const VP8LTransform* const transform,
int y_start, int y_end, uint32_t* data) {
const int width = transform->xsize_;
if (y_start == 0) { // First Row follows the L (mode=1) mode.
int x;
Predictor0(data, NULL);
const uint32_t pred = Predictor0(data[-1], NULL);
AddPixelsEq(data, pred);
for (x = 1; x < width; ++x) {
Predictor1(data + x, NULL);
const uint32_t pred = Predictor1(data[x - 1], NULL);
AddPixelsEq(data + x, pred);
}
data += width;
++y_start;
@ -186,20 +508,24 @@ static void PredictorInverseTransform(const VP8LTransform* const transform,
transform->data_ + (y >> transform->bits_) * tiles_per_row;
while (y < y_end) {
int x;
uint32_t pred;
const uint32_t* pred_mode_src = pred_mode_base;
PredictorFunc pred_func;
int x;
// First pixel follows the T (mode=2) mode.
Predictor2(data, data - width);
pred = Predictor2(data[-1], data - width);
AddPixelsEq(data, pred);
// .. the rest:
pred_func = kPredictors[((*pred_mode_src++) >> 8) & 0xf];
for (x = 1; x < width; ++x) {
uint32_t pred;
if ((x & mask) == 0) { // start of tile. Read predictor function.
pred_func = kPredictors[((*pred_mode_src++) >> 8) & 0xf];
}
pred_func(data + x, data + x - width);
pred = pred_func(data[x - 1], data + x - width);
AddPixelsEq(data + x, pred);
}
data += width;
++y;
@ -210,8 +536,21 @@ static void PredictorInverseTransform(const VP8LTransform* const transform,
}
}
// Add Green to Blue and Red channels (i.e. perform the inverse transform of
// 'Subtract Green').
#ifdef USE_LOSSLESS_ENCODER
void VP8LSubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixs) {
int i;
for (i = 0; i < num_pixs; ++i) {
const uint32_t argb = argb_data[i];
const uint32_t green = (argb >> 8) & 0xff;
const uint32_t new_r = (((argb >> 16) & 0xff) - green) & 0xff;
const uint32_t new_b = ((argb & 0xff) - green) & 0xff;
argb_data[i] = (argb & 0xff00ff00) | (new_r << 16) | new_b;
}
}
#endif
// Add green to blue and red channels (i.e. perform the inverse transform of
// 'subtract green').
static void AddGreenToBlueAndRed(const VP8LTransform* const transform,
int y_start, int y_end, uint32_t* data) {
const int width = transform->xsize_;
@ -228,13 +567,21 @@ static void AddGreenToBlueAndRed(const VP8LTransform* const transform,
}
typedef struct {
int green_to_red_;
int green_to_blue_;
int red_to_blue_;
// Note: the members are uint8_t, so that any negative values are
// automatically converted to "mod 256" values.
uint8_t green_to_red_;
uint8_t green_to_blue_;
uint8_t red_to_blue_;
} Multipliers;
static WEBP_INLINE void MultipliersClear(Multipliers* m) {
m->green_to_red_ = 0;
m->green_to_blue_ = 0;
m->red_to_blue_ = 0;
}
static WEBP_INLINE uint32_t ColorTransformDelta(int8_t color_pred,
int8_t color) {
int8_t color) {
return (uint32_t)((int)(color_pred) * color) >> 5;
}
@ -245,21 +592,277 @@ static WEBP_INLINE void ColorCodeToMultipliers(uint32_t color_code,
m->red_to_blue_ = (color_code >> 16) & 0xff;
}
static WEBP_INLINE void TransformColor(const Multipliers* const m,
uint32_t* const argb) {
const uint32_t green = *argb >> 8;
const uint32_t red = *argb >> 16;
uint32_t new_red = red;
uint32_t new_blue = *argb;
new_red += ColorTransformDelta(m->green_to_red_, green);
new_red &= 0xff;
new_blue += ColorTransformDelta(m->green_to_blue_, green);
new_blue += ColorTransformDelta(m->red_to_blue_, new_red);
new_blue &= 0xff;
*argb = (*argb & 0xff00ff00u) | (new_red << 16) | (new_blue);
static WEBP_INLINE uint32_t MultipliersToColorCode(Multipliers* const m) {
return 0xff000000u |
((uint32_t)(m->red_to_blue_) << 16) |
((uint32_t)(m->green_to_blue_) << 8) |
m->green_to_red_;
}
static WEBP_INLINE uint32_t TransformColor(const Multipliers* const m,
uint32_t argb, int inverse) {
const uint32_t green = argb >> 8;
const uint32_t red = argb >> 16;
uint32_t new_red = red;
uint32_t new_blue = argb;
if (inverse) {
new_red += ColorTransformDelta(m->green_to_red_, green);
new_red &= 0xff;
new_blue += ColorTransformDelta(m->green_to_blue_, green);
new_blue += ColorTransformDelta(m->red_to_blue_, new_red);
new_blue &= 0xff;
} else {
new_red -= ColorTransformDelta(m->green_to_red_, green);
new_red &= 0xff;
new_blue -= ColorTransformDelta(m->green_to_blue_, green);
new_blue -= ColorTransformDelta(m->red_to_blue_, red);
new_blue &= 0xff;
}
return (argb & 0xff00ff00u) | (new_red << 16) | (new_blue);
}
#ifdef USE_LOSSLESS_ENCODER
static WEBP_INLINE int SkipRepeatedPixels(const uint32_t* const argb,
int ix, int xsize) {
const uint32_t v = argb[ix];
if (ix >= xsize + 3) {
if (v == argb[ix - xsize] &&
argb[ix - 1] == argb[ix - xsize - 1] &&
argb[ix - 2] == argb[ix - xsize - 2] &&
argb[ix - 3] == argb[ix - xsize - 3]) {
return 1;
}
return v == argb[ix - 3] && v == argb[ix - 2] && v == argb[ix - 1];
} else if (ix >= 3) {
return v == argb[ix - 3] && v == argb[ix - 2] && v == argb[ix - 1];
}
return 0;
}
static double PredictionCostCrossColor(const int accumulated[256],
const int counts[256]) {
// Favor low entropy, locally and globally.
int i;
int combo[256];
for (i = 0; i < 256; ++i) {
combo[i] = accumulated[i] + counts[i];
}
return ShannonEntropy(combo, 256) +
ShannonEntropy(counts, 256) +
PredictionCostSpatial(counts, 3, 2.4); // Favor small absolute values.
}
static Multipliers GetBestColorTransformForTile(
int tile_x, int tile_y, int bits,
Multipliers prevX,
Multipliers prevY,
int step, int xsize, int ysize,
int* accumulated_red_histo,
int* accumulated_blue_histo,
const uint32_t* const argb) {
double best_diff = 1e99;
double cur_diff;
const int halfstep = step / 2;
const int max_tile_size = 1 << bits;
const int tile_y_offset = tile_y * max_tile_size;
const int tile_x_offset = tile_x * max_tile_size;
int green_to_red;
int green_to_blue;
int red_to_blue;
int all_x_max = tile_x_offset + max_tile_size;
int all_y_max = tile_y_offset + max_tile_size;
Multipliers best_tx;
MultipliersClear(&best_tx);
if (all_x_max > xsize) {
all_x_max = xsize;
}
if (all_y_max > ysize) {
all_y_max = ysize;
}
for (green_to_red = -64; green_to_red <= 64; green_to_red += halfstep) {
int histo[256] = { 0 };
int all_y;
Multipliers tx;
MultipliersClear(&tx);
tx.green_to_red_ = green_to_red & 0xff;
for (all_y = tile_y_offset; all_y < all_y_max; ++all_y) {
uint32_t predict;
int ix = all_y * xsize + tile_x_offset;
int all_x;
for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) {
if (SkipRepeatedPixels(argb, ix, xsize)) {
continue;
}
predict = TransformColor(&tx, argb[ix], 0);
++histo[(predict >> 16) & 0xff]; // red.
}
}
cur_diff = PredictionCostCrossColor(&accumulated_red_histo[0], &histo[0]);
if (tx.green_to_red_ == prevX.green_to_red_) {
cur_diff -= 3; // favor keeping the areas locally similar
}
if (tx.green_to_red_ == prevY.green_to_red_) {
cur_diff -= 3; // favor keeping the areas locally similar
}
if (tx.green_to_red_ == 0) {
cur_diff -= 3;
}
if (cur_diff < best_diff) {
best_diff = cur_diff;
best_tx = tx;
}
}
best_diff = 1e99;
green_to_red = best_tx.green_to_red_;
for (green_to_blue = -32; green_to_blue <= 32; green_to_blue += step) {
for (red_to_blue = -32; red_to_blue <= 32; red_to_blue += step) {
int all_y;
int histo[256] = { 0 };
Multipliers tx;
tx.green_to_red_ = green_to_red;
tx.green_to_blue_ = green_to_blue;
tx.red_to_blue_ = red_to_blue;
for (all_y = tile_y_offset; all_y < all_y_max; ++all_y) {
uint32_t predict;
int all_x;
int ix = all_y * xsize + tile_x_offset;
for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) {
if (SkipRepeatedPixels(argb, ix, xsize)) {
continue;
}
predict = TransformColor(&tx, argb[ix], 0);
++histo[predict & 0xff]; // blue.
}
}
cur_diff =
PredictionCostCrossColor(&accumulated_blue_histo[0], &histo[0]);
if (tx.green_to_blue_ == prevX.green_to_blue_) {
cur_diff -= 3; // favor keeping the areas locally similar
}
if (tx.green_to_blue_ == prevY.green_to_blue_) {
cur_diff -= 3; // favor keeping the areas locally similar
}
if (tx.red_to_blue_ == prevX.red_to_blue_) {
cur_diff -= 3; // favor keeping the areas locally similar
}
if (tx.red_to_blue_ == prevY.red_to_blue_) {
cur_diff -= 3; // favor keeping the areas locally similar
}
if (tx.green_to_blue_ == 0) {
cur_diff -= 3;
}
if (tx.red_to_blue_ == 0) {
cur_diff -= 3;
}
if (cur_diff < best_diff) {
best_diff = cur_diff;
best_tx = tx;
}
}
}
return best_tx;
}
static void CopyTileWithColorTransform(int xsize, int ysize,
int tile_x, int tile_y, int bits,
Multipliers color_transform,
uint32_t* const argb) {
int y;
int xscan = 1 << bits;
int yscan = 1 << bits;
tile_x <<= bits;
tile_y <<= bits;
if (xscan > xsize - tile_x) {
xscan = xsize - tile_x;
}
if (yscan > ysize - tile_y) {
yscan = ysize - tile_y;
}
yscan += tile_y;
for (y = tile_y; y < yscan; ++y) {
int ix = y * xsize + tile_x;
const int end_ix = ix + xscan;
for (; ix < end_ix; ++ix) {
argb[ix] = TransformColor(&color_transform, argb[ix], 0);
}
}
}
void VP8LColorSpaceTransform(int width, int height, int bits, int step,
uint32_t* const argb, uint32_t* image) {
const int max_tile_size = 1 << bits;
int tile_xsize = VP8LSubSampleSize(width, bits);
int tile_ysize = VP8LSubSampleSize(height, bits);
int accumulated_red_histo[256] = { 0 };
int accumulated_blue_histo[256] = { 0 };
int tile_y;
int tile_x;
Multipliers prevX;
Multipliers prevY;
MultipliersClear(&prevY);
MultipliersClear(&prevX);
for (tile_y = 0; tile_y < tile_ysize; ++tile_y) {
for (tile_x = 0; tile_x < tile_xsize; ++tile_x) {
Multipliers color_transform;
int all_x_max;
int y;
const int tile_y_offset = tile_y * max_tile_size;
const int tile_x_offset = tile_x * max_tile_size;
if (tile_y != 0) {
ColorCodeToMultipliers(image[tile_y * tile_xsize + tile_x - 1], &prevX);
ColorCodeToMultipliers(image[(tile_y - 1) * tile_xsize + tile_x],
&prevY);
} else if (tile_x != 0) {
ColorCodeToMultipliers(image[tile_y * tile_xsize + tile_x - 1], &prevX);
}
color_transform =
GetBestColorTransformForTile(tile_x, tile_y, bits,
prevX, prevY,
step, width, height,
&accumulated_red_histo[0],
&accumulated_blue_histo[0],
argb);
image[tile_y * tile_xsize + tile_x] =
MultipliersToColorCode(&color_transform);
CopyTileWithColorTransform(width, height, tile_x, tile_y, bits,
color_transform, argb);
// Gather accumulated histogram data.
all_x_max = tile_x_offset + max_tile_size;
if (all_x_max > width) {
all_x_max = width;
}
for (y = 0; y < max_tile_size; ++y) {
int ix;
int all_x;
int all_y = tile_y_offset + y;
if (all_y >= height) {
break;
}
ix = all_y * width + tile_x_offset;
for (all_x = tile_x_offset; all_x < all_x_max; ++all_x, ++ix) {
if (ix >= 2 &&
argb[ix] == argb[ix - 2] &&
argb[ix] == argb[ix - 1]) {
continue; // repeated pixels are handled by backward references
}
if (ix >= width + 2 &&
argb[ix - 2] == argb[ix - width - 2] &&
argb[ix - 1] == argb[ix - width - 1] &&
argb[ix] == argb[ix - width]) {
continue; // repeated pixels are handled by backward references
}
++accumulated_red_histo[(argb[ix] >> 16) & 0xff];
++accumulated_blue_histo[argb[ix] & 0xff];
}
}
}
}
}
#endif
// Color space inverse transform.
static void ColorSpaceInverseTransform(const VP8LTransform* const transform,
int y_start, int y_end, uint32_t* data) {
@ -277,7 +880,7 @@ static void ColorSpaceInverseTransform(const VP8LTransform* const transform,
for (x = 0; x < width; ++x) {
if ((x & mask) == 0) ColorCodeToMultipliers(*pred++, &m);
TransformColor(&m, data + x);
data[x] = TransformColor(&m, data[x], 1);
}
data += width;
++y;

View File

@ -21,7 +21,7 @@ extern "C" {
#endif
//------------------------------------------------------------------------------
// Inverse image transforms.
// Image transforms.
struct VP8LTransform; // Defined in dec/vp8li.h.
@ -33,13 +33,25 @@ void VP8LInverseTransform(const struct VP8LTransform* const transform,
int row_start, int row_end,
uint32_t* const data_in, uint32_t* const data_out);
#ifdef USE_LOSSLESS_ENCODER
// Subtracts green from blue and red channels.
void VP8LSubtractGreenFromBlueAndRed(uint32_t* argb_data, int num_pixs);
void VP8LResidualImage(int width, int height, int bits,
uint32_t* const argb, uint32_t* const argb_scratch,
uint32_t* const image);
void VP8LColorSpaceTransform(int width, int height, int bits, int step,
uint32_t* const argb, uint32_t* image);
#endif
//------------------------------------------------------------------------------
// Color space conversion.
// Converts from BGRA to other color spaces.
void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels,
WEBP_CSP_MODE out_colorspace,
uint8_t* const rgba);
WEBP_CSP_MODE out_colorspace,
uint8_t* const rgba);
//------------------------------------------------------------------------------
// Misc methods.
@ -50,6 +62,20 @@ static WEBP_INLINE uint32_t VP8LSubSampleSize(uint32_t size,
return (size + (1 << sampling_bits) - 1) >> sampling_bits;
}
#ifdef USE_LOSSLESS_ENCODER
// Faster logarithm for small integers, with the property of log(0) == 0.
float VP8LFastLog(int v);
// In-place difference of each component with mod 256.
static WEBP_INLINE uint32_t VP8LSubPixels(uint32_t a, uint32_t b) {
const uint32_t alpha_and_green =
0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u);
const uint32_t red_and_blue =
0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu);
return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu);
}
#endif
//------------------------------------------------------------------------------
#if defined(__cplusplus) || defined(c_plusplus)

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;
}

View File

@ -8,6 +8,7 @@
// Bit writing and boolean coder
//
// Author: Skal (pascal.massimino@gmail.com)
// Vikas Arora (vikaas.arora@gmail.com)
#include <assert.h>
#include <string.h> // for memcpy()
@ -186,6 +187,86 @@ void VP8BitWriterWipeOut(VP8BitWriter* const bw) {
}
}
#ifdef USE_LOSSLESS_ENCODER
//------------------------------------------------------------------------------
// VP8LBitWriter
// Returns 1 on success.
static int VP8LBitWriterResize(VP8LBitWriter* const bw, size_t extra_size) {
uint8_t* allocated_buf;
size_t allocated_size;
const size_t size_required = VP8LBitWriterNumBytes(bw) + extra_size;
if ((bw->max_bytes_ > 0) && (size_required <= bw->max_bytes_)) return 1;
allocated_size = (3 * bw->max_bytes_) >> 1;
if (allocated_size < size_required) {
allocated_size = size_required;
}
// Make Allocated size multiple of KBs
allocated_size = (((allocated_size >> 10) + 1) << 10);
allocated_buf = (uint8_t*)malloc(allocated_size);
if (allocated_buf == NULL) return 0;
memset(allocated_buf, 0, allocated_size);
if (bw->bit_pos_ > 0) {
memcpy(allocated_buf, bw->buf_, VP8LBitWriterNumBytes(bw));
}
free(bw->buf_);
bw->buf_ = allocated_buf;
bw->max_bytes_ = allocated_size;
return 1;
}
int VP8LBitWriterInit(VP8LBitWriter* const bw, size_t expected_size) {
memset(bw, 0, sizeof(*bw));
return VP8LBitWriterResize(bw, expected_size);
}
void VP8LBitWriterDestroy(VP8LBitWriter* const bw) {
if (bw != NULL) {
free(bw->buf_);
memset(bw, 0, sizeof(*bw));
}
}
void VP8LWriteBits(VP8LBitWriter* const bw, int n_bits, uint32_t bits) {
if (n_bits < 1) return;
#if !defined(__BIG_ENDIAN__)
// Technically, this branch of the code can write up to 25 bits at a time,
// but in deflate, the maximum number of bits written is 16 at a time.
{
uint8_t* p = &bw->buf_[bw->bit_pos_ >> 3];
uint32_t v = *(const uint32_t*)(p);
v |= bits << (bw->bit_pos_ & 7);
*(uint32_t*)(p) = v;
bw->bit_pos_ += n_bits;
}
#else // LITTLE_ENDIAN
// implicit & 0xff is assumed for uint8_t arithmetics
{
uint8_t* p = &bw->buf_[bw->bit_pos_ >> 3];
const int bits_reserved_in_first_byte = (bw->bit_pos_ & 7);
*p++ |= (bits << bits_reserved_in_first_byte);
const int bits_left_to_write = n_bits - 8 + bits_reserved_in_first_byte;
if (bits_left_to_write >= 1) {
*p++ = bits >> (8 - bits_reserved_in_first_byte);
if (bits_left_to_write >= 9) {
*p++ = bits >> (16 - bits_reserved_in_first_byte);
}
}
*p = 0;
bw->bit_pos_ += n_bits;
}
#endif // BIG_ENDIAN
if ((bw->bit_pos_ >> 3) > (bw->max_bytes_ - 8)) {
const size_t kAdditionalBuffer = 32768 + bw->max_bytes_;
if (!VP8LBitWriterResize(bw, kAdditionalBuffer)) {
bw->bit_pos_ = 0;
bw->error_ = 1;
}
}
}
#endif
//------------------------------------------------------------------------------
#if defined(__cplusplus) || defined(c_plusplus)

View File

@ -64,7 +64,59 @@ static WEBP_INLINE size_t VP8BitWriterSize(const VP8BitWriter* const bw) {
return bw->pos_;
}
#ifdef USE_LOSSLESS_ENCODER
//------------------------------------------------------------------------------
// VP8LBitWriter
// TODO(vikasa): VP8LBitWriter is copied as-is from lossless code. There's scope
// of re-using VP8BitWriter. Will evaluate once basic lossless encoder is
// implemented.
typedef struct {
uint8_t* buf_;
size_t bit_pos_;
size_t max_bytes_;
// After all bits are written, the caller must observe the state of
// error_. A value of 1 indicates that a memory allocation failure
// has happened during bit writing. A value of 0 indicates successful
// writing of bits.
int error_;
} VP8LBitWriter;
static WEBP_INLINE size_t VP8LBitWriterNumBytes(VP8LBitWriter* const bw) {
return (bw->bit_pos_ + 7) >> 3;
}
static WEBP_INLINE uint8_t* VP8LBitWriterFinish(VP8LBitWriter* const bw) {
return bw->buf_;
}
// Returns 0 in case of memory allocation error.
int VP8LBitWriterInit(VP8LBitWriter* const bw, size_t expected_size);
void VP8LBitWriterDestroy(VP8LBitWriter* const bw);
// This function writes bits into bytes in increasing addresses, and within
// a byte least-significant-bit first.
//
// The function can write up to 16 bits in one go with WriteBits
// Example: let's assume that 3 bits (Rs below) have been written already:
//
// BYTE-0 BYTE+1 BYTE+2
//
// 0000 0RRR 0000 0000 0000 0000
//
// Now, we could write 5 or less bits in MSB by just sifting by 3
// and OR'ing to BYTE-0.
//
// For n bits, we take the last 5 bytes, OR that with high bits in BYTE-0,
// and locate the rest in BYTE+1 and BYTE+2.
//
// VP8LBitWriter's error_ flag is set in case of memory allocation error.
void VP8LWriteBits(VP8LBitWriter* const bw, int n_bits, uint32_t bits);
//------------------------------------------------------------------------------
#endif
#if defined(__cplusplus) || defined(c_plusplus)
} // extern "C"

View File

@ -32,14 +32,17 @@ int VP8LColorCacheInit(VP8LColorCache* const cc, int hash_bits) {
return 1;
}
void VP8LColorCacheDelete(VP8LColorCache* const cc) {
void VP8LColorCacheClear(VP8LColorCache* const cc) {
if (cc != NULL) {
free(cc->colors_);
free(cc);
}
}
void VP8LColorCacheDelete(VP8LColorCache* const cc) {
VP8LColorCacheClear(cc);
free(cc);
}
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif

View File

@ -39,6 +39,7 @@ static WEBP_INLINE void VP8LColorCacheInsert(const VP8LColorCache* const cc,
cc->colors_[key] = argb;
}
#ifdef USE_LOSSLESS_ENCODER
static WEBP_INLINE int VP8LColorCacheGetIndex(const VP8LColorCache* const cc,
uint32_t argb) {
return (kHashMul * argb) >> cc->hash_shift_;
@ -49,6 +50,7 @@ static WEBP_INLINE int VP8LColorCacheContains(const VP8LColorCache* const cc,
const uint32_t key = (kHashMul * argb) >> cc->hash_shift_;
return cc->colors_[key] == argb;
}
#endif
//------------------------------------------------------------------------------
@ -57,6 +59,9 @@ static WEBP_INLINE int VP8LColorCacheContains(const VP8LColorCache* const cc,
int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits);
// Delete the color cache.
void VP8LColorCacheClear(VP8LColorCache* const color_cache);
// Delete the color_cache object.
void VP8LColorCacheDelete(VP8LColorCache* const color_cache);
//------------------------------------------------------------------------------

318
src/utils/huffman_encode.c Normal file
View File

@ -0,0 +1,318 @@
// Copyright 2011 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@google.com (Jyrki Alakuijala)
//
// Flate like entropy encoding (Huffman) for webp lossless.
#ifdef USE_LOSSLESS_ENCODER
#include "./huffman_encode.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int total_count_;
int value_;
int pool_index_left_;
int pool_index_right_;
} HuffmanTree;
// Sort the root nodes, most popular first.
static int CompHuffmanTree(const void* vp0, const void* vp1) {
const HuffmanTree* v0 = (const HuffmanTree*)vp0;
const HuffmanTree* v1 = (const HuffmanTree*)vp1;
if (v0->total_count_ > v1->total_count_) {
return -1;
} else if (v0->total_count_ < v1->total_count_) {
return 1;
} else {
if (v0->value_ < v1->value_) {
return -1;
}
if (v0->value_ > v1->value_) {
return 1;
}
return 0;
}
}
static void SetDepth(const HuffmanTree* p,
HuffmanTree* pool,
uint8_t* depth,
const int level) {
if (p->pool_index_left_ >= 0) {
SetDepth(&pool[p->pool_index_left_], pool, depth, level + 1);
SetDepth(&pool[p->pool_index_right_], pool, depth, level + 1);
} else {
depth[p->value_] = level;
}
}
// This function will create a Huffman tree.
//
// The catch here is that the tree cannot be arbitrarily deep.
// Deflate specifies a maximum depth of 15 bits for "code trees"
// and 7 bits for "code length code trees."
//
// count_limit is the value that is to be faked as the minimum value
// and this minimum value is raised until the tree matches the
// maximum length requirement.
//
// This algorithm is not of excellent performance for very long data blocks,
// especially when population counts are longer than 2**tree_limit, but
// we are not planning to use this with extremely long blocks.
//
// See http://en.wikipedia.org/wiki/Huffman_coding
int VP8LCreateHuffmanTree(const int* const histogram, int histogram_size,
int tree_depth_limit,
uint8_t* const bit_depths) {
HuffmanTree* tree;
HuffmanTree* tree_pool;
int tree_pool_size;
// For block sizes with less than 64k symbols we never need to do a
// second iteration of this loop.
// If we actually start running inside this loop a lot, we would perhaps
// be better off with the Katajainen algorithm.
int count_limit;
for (count_limit = 1; ; count_limit *= 2) {
int tree_size = 0;
int i;
for (i = 0; i < histogram_size; ++i) {
if (histogram[i]) {
++tree_size;
}
}
// 3 * tree_size is enough to cover all the nodes representing a
// population and all the inserted nodes combining two existing nodes.
// The tree pool needs 2 * (tree_size - 1) entities, and the
// tree needs exactly tree_size entities.
tree = (HuffmanTree*)malloc(3 * tree_size * sizeof(*tree));
if (tree == NULL) {
return 0;
}
{
int j = 0;
int i;
for (i = 0; i < histogram_size; ++i) {
if (histogram[i]) {
const int count =
(histogram[i] < count_limit) ? count_limit : histogram[i];
tree[j].total_count_ = count;
tree[j].value_ = i;
tree[j].pool_index_left_ = -1;
tree[j].pool_index_right_ = -1;
++j;
}
}
}
qsort((void*)tree, tree_size, sizeof(*tree), CompHuffmanTree);
tree_pool = tree + tree_size;
tree_pool_size = 0;
if (tree_size >= 2) {
while (tree_size >= 2) { // Finish when we have only one root.
int count;
tree_pool[tree_pool_size] = tree[tree_size - 1];
++tree_pool_size;
tree_pool[tree_pool_size] = tree[tree_size - 2];
++tree_pool_size;
count =
tree_pool[tree_pool_size - 1].total_count_ +
tree_pool[tree_pool_size - 2].total_count_;
tree_size -= 2;
{
int k = 0;
// Search for the insertion point.
for (k = 0; k < tree_size; ++k) {
if (tree[k].total_count_ <= count) {
break;
}
}
memmove(tree + (k + 1), tree + k, (tree_size - k) * sizeof(*tree));
tree[k].total_count_ = count;
tree[k].value_ = -1;
tree[k].pool_index_left_ = tree_pool_size - 1;
tree[k].pool_index_right_ = tree_pool_size - 2;
tree_size = tree_size + 1;
}
}
SetDepth(&tree[0], tree_pool, bit_depths, 0);
} else {
if (tree_size == 1) {
// Only one element.
bit_depths[tree[0].value_] = 1;
}
}
free(tree);
// We need to pack the Huffman tree in tree_depth_limit bits.
// If this was not successful, add fake entities to the lowest values
// and retry.
{
int max_depth = bit_depths[0];
int j;
for (j = 1; j < histogram_size; ++j) {
if (max_depth < bit_depths[j]) {
max_depth = bit_depths[j];
}
}
if (max_depth <= tree_depth_limit) {
break;
}
}
}
return 1;
}
static void WriteHuffmanTreeRepetitions(
const int value,
const int prev_value,
int repetitions,
int* num_symbols,
uint8_t* tree,
uint8_t* extra_bits_data) {
if (value != prev_value) {
tree[*num_symbols] = value;
extra_bits_data[*num_symbols] = 0;
++(*num_symbols);
--repetitions;
}
while (repetitions >= 1) {
if (repetitions < 3) {
int i;
for (i = 0; i < repetitions; ++i) {
tree[*num_symbols] = value;
extra_bits_data[*num_symbols] = 0;
++(*num_symbols);
}
return;
} else if (repetitions < 7) {
// 3 to 6 left
tree[*num_symbols] = 16;
extra_bits_data[*num_symbols] = repetitions - 3;
++(*num_symbols);
return;
} else {
tree[*num_symbols] = 16;
extra_bits_data[*num_symbols] = 3;
++(*num_symbols);
repetitions -= 6;
}
}
}
static void WriteHuffmanTreeRepetitionsZeros(
const int value,
int repetitions,
int* num_symbols,
uint8_t* tree,
uint8_t* extra_bits_data) {
while (repetitions >= 1) {
if (repetitions < 3) {
int i;
for (i = 0; i < repetitions; ++i) {
tree[*num_symbols] = value;
extra_bits_data[*num_symbols] = 0;
++(*num_symbols);
}
return;
} else if (repetitions < 11) {
tree[*num_symbols] = 17;
extra_bits_data[*num_symbols] = repetitions - 3;
++(*num_symbols);
return;
} else if (repetitions < 139) {
tree[*num_symbols] = 18;
extra_bits_data[*num_symbols] = repetitions - 11;
++(*num_symbols);
return;
} else {
tree[*num_symbols] = 18;
extra_bits_data[*num_symbols] = 0x7f; // 138 repeated 0s
++(*num_symbols);
repetitions -= 138;
}
}
}
void VP8LCreateCompressedHuffmanTree(const uint8_t* const depth,
int depth_size,
int* num_symbols,
uint8_t* tree,
uint8_t* extra_bits_data) {
int prev_value = 8; // 8 is the initial value for rle.
int i;
for (i = 0; i < depth_size;) {
const int value = depth[i];
int reps = 1;
int k;
for (k = i + 1; k < depth_size && depth[k] == value; ++k) {
++reps;
}
if (value == 0) {
WriteHuffmanTreeRepetitionsZeros(value, reps,
num_symbols,
tree, extra_bits_data);
} else {
WriteHuffmanTreeRepetitions(value, prev_value, reps,
num_symbols,
tree, extra_bits_data);
prev_value = value;
}
i += reps;
}
}
static uint32_t ReverseBits(int num_bits, uint32_t bits) {
uint32_t retval = 0;
int i;
for (i = 0; i < num_bits; ++i) {
retval <<= 1;
retval |= bits & 1;
bits >>= 1;
}
return retval;
}
void VP8LConvertBitDepthsToSymbols(const uint8_t* depth, int len,
uint16_t* bits) {
// This function is based on RFC 1951.
//
// In deflate, all bit depths are [1..15]
// 0 bit depth means that the symbol does not exist.
// 0..15 are values for bits
#define MAX_BITS 16
uint32_t next_code[MAX_BITS];
uint32_t bl_count[MAX_BITS] = { 0 };
int i;
{
for (i = 0; i < len; ++i) {
++bl_count[depth[i]];
}
bl_count[0] = 0;
}
next_code[0] = 0;
{
int code = 0;
int bits;
for (bits = 1; bits < MAX_BITS; ++bits) {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
}
for (i = 0; i < len; ++i) {
if (depth[i]) {
bits[i] = ReverseBits(depth[i], next_code[depth[i]]++);
}
}
}
#undef MAX_BITS
#endif

View File

@ -0,0 +1,54 @@
// Copyright 2011 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@google.com (Jyrki Alakuijala)
//
// Flate like entropy encoding (Huffman) for webp lossless
#ifndef WEBP_UTILS_HUFFMAN_ENCODE_H_
#define WEBP_UTILS_HUFFMAN_ENCODE_H_
#ifdef USE_LOSSLESS_ENCODER
#include <stdint.h>
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
// This function will create a Huffman tree.
//
// The (data,length) contains the population counts.
// The tree_limit is the maximum bit depth of the Huffman codes.
//
// The depth contains the tree, i.e., how many bits are used for
// the symbol.
//
// See http://en.wikipedia.org/wiki/Huffman_coding
//
// Returns 0 when an error has occured.
int VP8LCreateHuffmanTree(const int* data, const int length,
const int tree_limit, uint8_t* depth);
// Write a huffman tree from bit depths. The generated Huffman tree is
// compressed once more using a Huffman tree.
void VP8LCreateCompressedHuffmanTree(const uint8_t* const depth, int len,
int* num_symbols,
uint8_t* tree,
uint8_t* extra_bits_data);
// Get the actual bit values for a tree of bit depths.
void VP8LConvertBitDepthsToSymbols(const uint8_t* depth, int len,
uint16_t* bits);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif
#endif // WEBP_UTILS_HUFFMAN_ENCODE_H_

View File

@ -76,6 +76,7 @@ typedef struct {
// 0: none, 1: fast, 2: best. Default if 1.
int alpha_quality; // Between 0 (smallest size) and 100 (lossless).
// Default is 100.
int lossless; // Lossless encoding (0=lossy(default), 1=lossless).
} WebPConfig;
// Enumerate some predefined settings for WebPConfig, depending on the type
@ -189,7 +190,7 @@ struct WebPPicture {
int width, height; // dimensions (less or equal to WEBP_MAX_DIMENSION)
uint8_t *y, *u, *v; // pointers to luma/chroma planes.
int y_stride, uv_stride; // luma/chroma strides.
uint8_t *a; // pointer to the alpha plane
uint8_t* a; // pointer to the alpha plane
int a_stride; // stride of the alpha plane
// output
@ -216,6 +217,10 @@ struct WebPPicture {
WebPEncodingError error_code; // error code in case of problem.
WebPProgressHook progress_hook; // if not NULL, called while encoding.
int use_argb_input; // Flag for encoder to use argb pixels as input.
uint32_t* argb; // Pointer to argb (32 bit) plane.
int argb_stride; // This is stride in pixels units, not bytes.
};
// Internal, version-checked, entry point