mirror of
https://github.com/google/brotli.git
synced 2024-11-22 03:30:07 +00:00
Push updates to the Brotli encoder and decoder upstream.
This commit contains a batch of changes that were made to the Brotli library since March 2014. Most important changes: * Fix BrotliDecompressedSize() to work for an uncompressed plus an empty meta-block. * Move serialization functions into their own file. * Fix storing of the meta-block header for last empty meta-block. * Add a fast version of the hasher.
This commit is contained in:
parent
e3980e3bd7
commit
a597255213
71
dec/decode.c
71
dec/decode.c
@ -50,8 +50,9 @@ static const int kDistanceContextBits = 2;
|
||||
|
||||
#define HUFFMAN_TABLE_BITS 8
|
||||
#define HUFFMAN_TABLE_MASK 0xff
|
||||
/* This is a rough estimate, not an exact bound. */
|
||||
#define HUFFMAN_MAX_TABLE_SIZE 2048
|
||||
/* Maximum possible Huffman table size for an alphabet size of 704, max code
|
||||
* length 15 and root table bits 8. */
|
||||
#define HUFFMAN_MAX_TABLE_SIZE 1080
|
||||
|
||||
#define CODE_LENGTH_CODES 18
|
||||
static const uint8_t kCodeLengthCodeOrder[CODE_LENGTH_CODES] = {
|
||||
@ -633,22 +634,62 @@ int CopyUncompressedBlockToOutput(BrotliOutput output, int len, int pos,
|
||||
int BrotliDecompressedSize(size_t encoded_size,
|
||||
const uint8_t* encoded_buffer,
|
||||
size_t* decoded_size) {
|
||||
BrotliMemInput memin;
|
||||
BrotliInput input = BrotliInitMemInput(encoded_buffer, encoded_size, &memin);
|
||||
BrotliBitReader br;
|
||||
int meta_block_len;
|
||||
int input_end;
|
||||
int is_uncompressed;
|
||||
if (!BrotliInitBitReader(&br, input)) {
|
||||
int i;
|
||||
uint64_t val = 0;
|
||||
int bit_pos = 0;
|
||||
int is_last;
|
||||
int is_uncompressed = 0;
|
||||
int size_nibbles;
|
||||
int meta_block_len = 0;
|
||||
if (encoded_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
DecodeWindowBits(&br);
|
||||
DecodeMetaBlockLength(&br, &meta_block_len, &input_end, &is_uncompressed);
|
||||
if (!input_end) {
|
||||
return 0;
|
||||
/* Look at the first 8 bytes, it is enough to decode the length of the first
|
||||
meta-block. */
|
||||
for (i = 0; i < encoded_size && i < 8; ++i) {
|
||||
val |= (uint64_t)encoded_buffer[i] << (8 * i);
|
||||
}
|
||||
*decoded_size = (size_t)meta_block_len;
|
||||
return 1;
|
||||
/* Skip the window bits. */
|
||||
bit_pos += (val & 1) ? 4 : 1;
|
||||
/* Decode the ISLAST bit. */
|
||||
is_last = (val >> bit_pos) & 1;
|
||||
++bit_pos;
|
||||
if (is_last) {
|
||||
/* Decode the ISEMPTY bit, if it is set to 1, we are done. */
|
||||
if ((val >> bit_pos) & 1) {
|
||||
*decoded_size = 0;
|
||||
return 1;
|
||||
}
|
||||
++bit_pos;
|
||||
}
|
||||
/* Decode the length of the first meta-block. */
|
||||
size_nibbles = (int)((val >> bit_pos) & 3) + 4;
|
||||
bit_pos += 2;
|
||||
for (i = 0; i < size_nibbles; ++i) {
|
||||
meta_block_len |= (int)((val >> bit_pos) & 0xf) << (4 * i);
|
||||
bit_pos += 4;
|
||||
}
|
||||
++meta_block_len;
|
||||
if (is_last) {
|
||||
/* If this meta-block is the only one, we are done. */
|
||||
*decoded_size = (size_t)meta_block_len;
|
||||
return 1;
|
||||
}
|
||||
is_uncompressed = (val >> bit_pos) & 1;
|
||||
++bit_pos;
|
||||
if (is_uncompressed) {
|
||||
/* If the first meta-block is uncompressed, we skip it and look at the
|
||||
first two bits (ISLAST and ISEMPTY) of the next meta-block, and if
|
||||
both are set to 1, we have a stream with an uncompressed meta-block
|
||||
followed by an empty one, so the decompressed size is the size of the
|
||||
first meta-block. */
|
||||
int offset = ((bit_pos + 7) >> 3) + meta_block_len;
|
||||
if (offset < encoded_size && ((encoded_buffer[offset] & 3) == 3)) {
|
||||
*decoded_size = (size_t)meta_block_len;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int BrotliDecompressBuffer(size_t encoded_size,
|
||||
|
@ -27,7 +27,8 @@ extern "C" {
|
||||
|
||||
/* Sets *decoded_size to the decompressed size of the given encoded stream. */
|
||||
/* This function only works if the encoded buffer has a single meta block, */
|
||||
/* and this meta block must have the "is last" bit set. */
|
||||
/* or if it has two meta-blocks, where the first is uncompressed and the */
|
||||
/* second is empty. */
|
||||
/* Returns 1 on success, 0 on failure. */
|
||||
int BrotliDecompressedSize(size_t encoded_size,
|
||||
const uint8_t* encoded_buffer,
|
||||
|
@ -15,8 +15,8 @@
|
||||
Size-checked memory allocation.
|
||||
*/
|
||||
|
||||
#ifndef BROTLI_UTILS_UTILS_H_
|
||||
#define BROTLI_UTILS_UTILS_H_
|
||||
#ifndef BROTLI_DEC_SAFE_MALLOC_H_
|
||||
#define BROTLI_DEC_SAFE_MALLOC_H_
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@ -42,4 +42,4 @@ void* BrotliSafeMalloc(uint64_t nmemb, size_t size);
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* BROTLI_UTILS_UTILS_H_ */
|
||||
#endif /* BROTLI_DEC_SAFE_MALLOC_H_ */
|
||||
|
@ -23,173 +23,230 @@
|
||||
|
||||
namespace brotli {
|
||||
|
||||
template<typename Hasher>
|
||||
template<typename Hasher, bool kUseCostModel, bool kUseDictionary>
|
||||
void CreateBackwardReferences(size_t num_bytes,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
const float* literal_cost,
|
||||
size_t ringbuffer_mask,
|
||||
const float* literal_cost,
|
||||
size_t literal_cost_mask,
|
||||
const size_t max_backward_limit,
|
||||
const double base_min_score,
|
||||
const int quality,
|
||||
Hasher* hasher,
|
||||
std::vector<Command>* commands) {
|
||||
// Length heuristic that seems to help probably by better selection
|
||||
// of lazy matches of similar lengths.
|
||||
int insert_length = 0;
|
||||
int* dist_cache,
|
||||
int* last_insert_len,
|
||||
Command* commands,
|
||||
int* num_commands) {
|
||||
if (num_bytes >= 3 && position >= 3) {
|
||||
// Prepare the hashes for three last bytes of the last write.
|
||||
// These could not be calculated before, since they require knowledge
|
||||
// of both the previous and the current block.
|
||||
hasher->Store(&ringbuffer[(position - 3) & ringbuffer_mask],
|
||||
position - 3);
|
||||
hasher->Store(&ringbuffer[(position - 2) & ringbuffer_mask],
|
||||
position - 2);
|
||||
hasher->Store(&ringbuffer[(position - 1) & ringbuffer_mask],
|
||||
position - 1);
|
||||
}
|
||||
const Command * const orig_commands = commands;
|
||||
int insert_length = *last_insert_len;
|
||||
size_t i = position & ringbuffer_mask;
|
||||
const int i_diff = position - i;
|
||||
const size_t i_end = i + num_bytes;
|
||||
|
||||
const int random_heuristics_window_size = 512;
|
||||
// For speed up heuristics for random data.
|
||||
const int random_heuristics_window_size = quality < 9 ? 64 : 512;
|
||||
int apply_random_heuristics = i + random_heuristics_window_size;
|
||||
|
||||
double average_cost = 0.0;
|
||||
for (int k = position; k < position + num_bytes; ++k) {
|
||||
average_cost += literal_cost[k & ringbuffer_mask];
|
||||
double average_cost = 5.4;
|
||||
if (kUseCostModel) {
|
||||
average_cost = 0.0;
|
||||
for (int k = position; k < position + num_bytes; ++k) {
|
||||
average_cost += literal_cost[k & literal_cost_mask];
|
||||
}
|
||||
average_cost /= num_bytes;
|
||||
}
|
||||
average_cost /= num_bytes;
|
||||
hasher->set_average_cost(average_cost);
|
||||
|
||||
// M1 match is for considering for two repeated copies, if moving
|
||||
// one literal form the previous copy to the current one allows the
|
||||
// current copy to be more efficient (because the way static dictionary
|
||||
// codes words). M1 matching improves text compression density by ~0.15 %.
|
||||
bool match_found_M1 = false;
|
||||
size_t best_len_M1 = 0;
|
||||
size_t best_len_code_M1 = 0;
|
||||
size_t best_dist_M1 = 0;
|
||||
int best_len_M1 = 0;
|
||||
int best_len_code_M1 = 0;
|
||||
int best_dist_M1 = 0;
|
||||
double best_score_M1 = 0;
|
||||
while (i + 2 < i_end) {
|
||||
size_t best_len = 0;
|
||||
size_t best_len_code = 0;
|
||||
size_t best_dist = 0;
|
||||
double best_score = 0;
|
||||
while (i + 3 < i_end) {
|
||||
int max_length = i_end - i;
|
||||
size_t max_distance = std::min(i + i_diff, max_backward_limit);
|
||||
bool in_dictionary;
|
||||
hasher->set_insert_length(insert_length);
|
||||
double min_score = base_min_score;
|
||||
if (kUseCostModel && insert_length < 8) {
|
||||
double cost_diff[8] =
|
||||
{ 0.1, 0.038, 0.019, 0.013, 0.001, 0.001, 0.001, 0.001 };
|
||||
min_score += cost_diff[insert_length];
|
||||
}
|
||||
int best_len = 0;
|
||||
int best_len_code = 0;
|
||||
int best_dist = 0;
|
||||
double best_score = min_score;
|
||||
bool match_found = hasher->FindLongestMatch(
|
||||
ringbuffer, literal_cost, ringbuffer_mask,
|
||||
i + i_diff, i_end - i, max_distance,
|
||||
&best_len, &best_len_code, &best_dist, &best_score,
|
||||
&in_dictionary);
|
||||
bool best_in_dictionary = in_dictionary;
|
||||
ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, average_cost,
|
||||
dist_cache, i + i_diff, max_length, max_distance,
|
||||
&best_len, &best_len_code, &best_dist, &best_score);
|
||||
if (match_found) {
|
||||
if (match_found_M1 && best_score_M1 > best_score) {
|
||||
if (kUseDictionary && match_found_M1 && best_score_M1 > best_score) {
|
||||
// Two copies after each other. Take the last literal from the
|
||||
// last copy, and use it as the first of this one.
|
||||
(commands->rbegin())->copy_length_ -= 1;
|
||||
(commands->rbegin())->copy_length_code_ -= 1;
|
||||
Command prev_cmd = commands[-1];
|
||||
commands[-1] = Command(prev_cmd.insert_len_,
|
||||
prev_cmd.copy_len_ - 1,
|
||||
prev_cmd.copy_len_ - 1,
|
||||
prev_cmd.DistanceCode());
|
||||
hasher->Store(ringbuffer + i, i + i_diff);
|
||||
--i;
|
||||
best_len = best_len_M1;
|
||||
best_len_code = best_len_code_M1;
|
||||
best_dist = best_dist_M1;
|
||||
best_score = best_score_M1;
|
||||
// in_dictionary doesn't need to be correct, but it is the only
|
||||
// reason why M1 matching should be beneficial here. Setting it here
|
||||
// will only disable further M1 matching against this copy.
|
||||
best_in_dictionary = true;
|
||||
in_dictionary = true;
|
||||
} else {
|
||||
// Found a match. Let's look for something even better ahead.
|
||||
int delayed_backward_references_in_row = 0;
|
||||
while (i + 4 < i_end &&
|
||||
delayed_backward_references_in_row < 4) {
|
||||
size_t best_len_2 = 0;
|
||||
size_t best_len_code_2 = 0;
|
||||
size_t best_dist_2 = 0;
|
||||
double best_score_2 = 0;
|
||||
for (;;) {
|
||||
--max_length;
|
||||
int best_len_2 = quality < 4 ? std::min(best_len - 1, max_length) : 0;
|
||||
int best_len_code_2 = 0;
|
||||
int best_dist_2 = 0;
|
||||
double best_score_2 = min_score;
|
||||
max_distance = std::min(i + i_diff + 1, max_backward_limit);
|
||||
hasher->Store(ringbuffer + i, i + i_diff);
|
||||
match_found = hasher->FindLongestMatch(
|
||||
ringbuffer, literal_cost, ringbuffer_mask,
|
||||
i + i_diff + 1, i_end - i - 1, max_distance,
|
||||
&best_len_2, &best_len_code_2, &best_dist_2, &best_score_2,
|
||||
&in_dictionary);
|
||||
double cost_diff_lazy = 0;
|
||||
if (best_len >= 4) {
|
||||
cost_diff_lazy +=
|
||||
literal_cost[(i + 4) & ringbuffer_mask] - average_cost;
|
||||
}
|
||||
{
|
||||
const int tail_length = best_len_2 - best_len + 1;
|
||||
for (int k = 0; k < tail_length; ++k) {
|
||||
cost_diff_lazy -=
|
||||
literal_cost[(i + best_len + k) & ringbuffer_mask] -
|
||||
average_cost;
|
||||
ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, average_cost,
|
||||
dist_cache, i + i_diff + 1, max_length, max_distance,
|
||||
&best_len_2, &best_len_code_2, &best_dist_2, &best_score_2);
|
||||
double cost_diff_lazy = 7.0;
|
||||
if (kUseCostModel) {
|
||||
cost_diff_lazy = 0.0;
|
||||
if (best_len >= 4) {
|
||||
cost_diff_lazy +=
|
||||
literal_cost[(i + 4) & literal_cost_mask] - average_cost;
|
||||
}
|
||||
{
|
||||
const int tail_length = best_len_2 - best_len + 1;
|
||||
for (int k = 0; k < tail_length; ++k) {
|
||||
cost_diff_lazy -=
|
||||
literal_cost[(i + best_len + k) & literal_cost_mask] -
|
||||
average_cost;
|
||||
}
|
||||
}
|
||||
// If we are not inserting any symbols, inserting one is more
|
||||
// expensive than if we were inserting symbols anyways.
|
||||
if (insert_length < 1) {
|
||||
cost_diff_lazy += 0.97;
|
||||
}
|
||||
// Add bias to slightly avoid lazy matching.
|
||||
cost_diff_lazy += 2.0 + delayed_backward_references_in_row * 0.2;
|
||||
cost_diff_lazy += 0.04 * literal_cost[i & literal_cost_mask];
|
||||
}
|
||||
// If we are not inserting any symbols, inserting one is more
|
||||
// expensive than if we were inserting symbols anyways.
|
||||
if (insert_length < 1) {
|
||||
cost_diff_lazy += 0.97;
|
||||
}
|
||||
// Add bias to slightly avoid lazy matching.
|
||||
cost_diff_lazy += 2.0 + delayed_backward_references_in_row * 0.2;
|
||||
cost_diff_lazy += 0.04 * literal_cost[i & ringbuffer_mask];
|
||||
|
||||
if (match_found && best_score_2 >= best_score + cost_diff_lazy) {
|
||||
// Ok, let's just write one byte for now and start a match from the
|
||||
// next byte.
|
||||
++i;
|
||||
++insert_length;
|
||||
++delayed_backward_references_in_row;
|
||||
best_len = best_len_2;
|
||||
best_len_code = best_len_code_2;
|
||||
best_dist = best_dist_2;
|
||||
best_score = best_score_2;
|
||||
best_in_dictionary = in_dictionary;
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
if (++delayed_backward_references_in_row < 4) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
apply_random_heuristics =
|
||||
i + 2 * best_len + random_heuristics_window_size;
|
||||
Command cmd;
|
||||
cmd.insert_length_ = insert_length;
|
||||
cmd.copy_length_ = best_len;
|
||||
cmd.copy_length_code_ = best_len_code;
|
||||
cmd.copy_distance_ = best_dist;
|
||||
commands->push_back(cmd);
|
||||
insert_length = 0;
|
||||
++i;
|
||||
if (best_dist <= std::min(i + i_diff, max_backward_limit)) {
|
||||
hasher->set_last_distance(best_dist);
|
||||
max_distance = std::min(i + i_diff, max_backward_limit);
|
||||
int distance_code = best_dist + 16;
|
||||
if (best_dist <= max_distance) {
|
||||
if (best_dist == dist_cache[0]) {
|
||||
distance_code = 1;
|
||||
} else if (best_dist == dist_cache[1]) {
|
||||
distance_code = 2;
|
||||
} else if (best_dist == dist_cache[2]) {
|
||||
distance_code = 3;
|
||||
} else if (best_dist == dist_cache[3]) {
|
||||
distance_code = 4;
|
||||
} else if (quality > 1 && best_dist >= 6) {
|
||||
for (int k = 4; k < kNumDistanceShortCodes; ++k) {
|
||||
int idx = kDistanceCacheIndex[k];
|
||||
int candidate = dist_cache[idx] + kDistanceCacheOffset[k];
|
||||
static const int kLimits[16] = { 0, 0, 0, 0,
|
||||
6, 6, 11, 11,
|
||||
11, 11, 11, 11,
|
||||
12, 12, 12, 12 };
|
||||
if (best_dist == candidate && best_dist >= kLimits[k]) {
|
||||
distance_code = k + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (distance_code > 1) {
|
||||
dist_cache[3] = dist_cache[2];
|
||||
dist_cache[2] = dist_cache[1];
|
||||
dist_cache[1] = dist_cache[0];
|
||||
dist_cache[0] = best_dist;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all copied literals to the hasher, except the last one.
|
||||
// We cannot store the last one yet, otherwise we couldn't find
|
||||
// the possible M1 match.
|
||||
for (int j = 1; j < best_len - 1; ++j) {
|
||||
if (i + 2 < i_end) {
|
||||
Command cmd(insert_length, best_len, best_len_code, distance_code);
|
||||
*commands++ = cmd;
|
||||
insert_length = 0;
|
||||
if (kUseDictionary) {
|
||||
++i;
|
||||
// Copy all copied literals to the hasher, except the last one.
|
||||
// We cannot store the last one yet, otherwise we couldn't find
|
||||
// the possible M1 match.
|
||||
for (int j = 1; j < best_len - 1; ++j) {
|
||||
if (i + 3 < i_end) {
|
||||
hasher->Store(ringbuffer + i, i + i_diff);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// Prepare M1 match.
|
||||
if (hasher->HasStaticDictionary() &&
|
||||
best_len >= 4 && i + 20 < i_end && best_dist <= max_distance) {
|
||||
max_distance = std::min(i + i_diff, max_backward_limit);
|
||||
best_score_M1 = min_score;
|
||||
match_found_M1 = hasher->FindLongestMatch(
|
||||
ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, average_cost,
|
||||
dist_cache, i + i_diff, i_end - i, max_distance,
|
||||
&best_len_M1, &best_len_code_M1, &best_dist_M1, &best_score_M1);
|
||||
} else {
|
||||
match_found_M1 = false;
|
||||
}
|
||||
if (kUseCostModel) {
|
||||
// This byte is just moved from the previous copy to the current,
|
||||
// that is no gain.
|
||||
best_score_M1 -= literal_cost[i & literal_cost_mask];
|
||||
// Adjust for losing the opportunity for lazy matching.
|
||||
best_score_M1 -= 3.75;
|
||||
}
|
||||
// Store the last one of the match.
|
||||
if (i + 3 < i_end) {
|
||||
hasher->Store(ringbuffer + i, i + i_diff);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// Prepare M1 match.
|
||||
if (hasher->HasStaticDictionary() &&
|
||||
best_len >= 4 && i + 20 < i_end && !best_in_dictionary) {
|
||||
max_distance = std::min(i + i_diff, max_backward_limit);
|
||||
match_found_M1 = hasher->FindLongestMatch(
|
||||
ringbuffer, literal_cost, ringbuffer_mask,
|
||||
i + i_diff, i_end - i, max_distance,
|
||||
&best_len_M1, &best_len_code_M1, &best_dist_M1, &best_score_M1,
|
||||
&in_dictionary);
|
||||
} else {
|
||||
match_found_M1 = false;
|
||||
in_dictionary = false;
|
||||
// Put the hash keys into the table, if there are enough
|
||||
// bytes left.
|
||||
for (int j = 1; j < best_len; ++j) {
|
||||
hasher->Store(&ringbuffer[i + j], i + i_diff + j);
|
||||
}
|
||||
i += best_len;
|
||||
}
|
||||
// This byte is just moved from the previous copy to the current,
|
||||
// that is no gain.
|
||||
best_score_M1 -= literal_cost[i & ringbuffer_mask];
|
||||
// Adjust for losing the opportunity for lazy matching.
|
||||
best_score_M1 -= 3.75;
|
||||
|
||||
// Store the last one of the match.
|
||||
if (i + 2 < i_end) {
|
||||
hasher->Store(ringbuffer + i, i + i_diff);
|
||||
}
|
||||
++i;
|
||||
} else {
|
||||
match_found_M1 = false;
|
||||
++insert_length;
|
||||
@ -214,7 +271,7 @@ void CreateBackwardReferences(size_t num_bytes,
|
||||
insert_length += 4;
|
||||
}
|
||||
} else {
|
||||
int i_jump = std::min(i + 8, i_end - 2);
|
||||
int i_jump = std::min(i + 8, i_end - 3);
|
||||
for (; i < i_jump; i += 2) {
|
||||
hasher->Store(ringbuffer + i, i + i_diff);
|
||||
insert_length += 2;
|
||||
@ -224,44 +281,92 @@ void CreateBackwardReferences(size_t num_bytes,
|
||||
}
|
||||
}
|
||||
insert_length += (i_end - i);
|
||||
|
||||
if (insert_length > 0) {
|
||||
Command cmd;
|
||||
cmd.insert_length_ = insert_length;
|
||||
cmd.copy_length_ = 0;
|
||||
cmd.copy_distance_ = 0;
|
||||
commands->push_back(cmd);
|
||||
}
|
||||
*last_insert_len = insert_length;
|
||||
*num_commands += (commands - orig_commands);
|
||||
}
|
||||
|
||||
void CreateBackwardReferences(size_t num_bytes,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
const float* literal_cost,
|
||||
size_t ringbuffer_mask,
|
||||
const float* literal_cost,
|
||||
size_t literal_cost_mask,
|
||||
const size_t max_backward_limit,
|
||||
const double base_min_score,
|
||||
const int quality,
|
||||
Hashers* hashers,
|
||||
Hashers::Type hash_type,
|
||||
std::vector<Command>* commands) {
|
||||
int hash_type,
|
||||
int* dist_cache,
|
||||
int* last_insert_len,
|
||||
Command* commands,
|
||||
int* num_commands) {
|
||||
switch (hash_type) {
|
||||
case Hashers::HASH_15_8_4:
|
||||
CreateBackwardReferences(
|
||||
num_bytes, position, ringbuffer, literal_cost,
|
||||
ringbuffer_mask, max_backward_limit,
|
||||
hashers->hash_15_8_4.get(),
|
||||
commands);
|
||||
case 1:
|
||||
CreateBackwardReferences<Hashers::H1, false, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h1.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case Hashers::HASH_15_8_2:
|
||||
CreateBackwardReferences(
|
||||
num_bytes, position, ringbuffer, literal_cost,
|
||||
ringbuffer_mask, max_backward_limit,
|
||||
hashers->hash_15_8_2.get(),
|
||||
commands);
|
||||
case 2:
|
||||
CreateBackwardReferences<Hashers::H2, false, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h2.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case 3:
|
||||
CreateBackwardReferences<Hashers::H3, false, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h3.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case 4:
|
||||
CreateBackwardReferences<Hashers::H4, false, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h4.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case 5:
|
||||
CreateBackwardReferences<Hashers::H5, false, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h5.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case 6:
|
||||
CreateBackwardReferences<Hashers::H6, false, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h6.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case 7:
|
||||
CreateBackwardReferences<Hashers::H7, true, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h7.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case 8:
|
||||
CreateBackwardReferences<Hashers::H8, true, true>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h8.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
case 9:
|
||||
CreateBackwardReferences<Hashers::H9, true, false>(
|
||||
num_bytes, position, ringbuffer, ringbuffer_mask,
|
||||
literal_cost, literal_cost_mask, max_backward_limit, base_min_score,
|
||||
quality, hashers->hash_h9.get(), dist_cache, last_insert_len,
|
||||
commands, num_commands);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace brotli
|
||||
|
@ -28,12 +28,18 @@ namespace brotli {
|
||||
void CreateBackwardReferences(size_t num_bytes,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
const float* literal_cost,
|
||||
size_t ringbuffer_mask,
|
||||
const float* literal_cost,
|
||||
size_t literal_cost_mask,
|
||||
const size_t max_backward_limit,
|
||||
const double base_min_score,
|
||||
const int quality,
|
||||
Hashers* hashers,
|
||||
Hashers::Type hash_type,
|
||||
std::vector<Command>* commands);
|
||||
int hash_type,
|
||||
int* dist_cache,
|
||||
int* last_insert_len,
|
||||
Command* commands,
|
||||
int* num_commands);
|
||||
|
||||
} // namespace brotli
|
||||
|
||||
|
@ -91,7 +91,7 @@ static inline int HuffmanBitCost(const uint8_t* depth, int length) {
|
||||
|
||||
// create huffman tree of huffman tree
|
||||
uint8_t cost[kCodeLengthCodes] = { 0 };
|
||||
CreateHuffmanTree(histogram, kCodeLengthCodes, 7, cost);
|
||||
CreateHuffmanTree(histogram, kCodeLengthCodes, 7, 9, cost);
|
||||
// account for rle extra bits
|
||||
cost[16] += 2;
|
||||
cost[17] += 3;
|
||||
@ -123,7 +123,7 @@ double PopulationCost(const Histogram<kSize>& histogram) {
|
||||
return 20 + histogram.total_count_;
|
||||
}
|
||||
uint8_t depth[kSize] = { 0 };
|
||||
CreateHuffmanTree(&histogram.data_[0], kSize, 15, depth);
|
||||
CreateHuffmanTree(&histogram.data_[0], kSize, 15, 9, depth);
|
||||
int bits = 0;
|
||||
for (int i = 0; i < kSize; ++i) {
|
||||
bits += histogram.data_[i] * depth[i];
|
||||
|
@ -33,7 +33,7 @@ namespace brotli {
|
||||
|
||||
static const int kMaxLiteralHistograms = 100;
|
||||
static const int kMaxCommandHistograms = 50;
|
||||
static const double kLiteralBlockSwitchCost = 26;
|
||||
static const double kLiteralBlockSwitchCost = 28.1;
|
||||
static const double kCommandBlockSwitchCost = 13.5;
|
||||
static const double kDistanceBlockSwitchCost = 14.6;
|
||||
static const int kLiteralStrideLength = 70;
|
||||
@ -51,7 +51,7 @@ void CopyLiteralsToByteArray(const std::vector<Command>& cmds,
|
||||
// Count how many we have.
|
||||
size_t total_length = 0;
|
||||
for (int i = 0; i < cmds.size(); ++i) {
|
||||
total_length += cmds[i].insert_length_;
|
||||
total_length += cmds[i].insert_len_;
|
||||
}
|
||||
if (total_length == 0) {
|
||||
return;
|
||||
@ -64,9 +64,9 @@ void CopyLiteralsToByteArray(const std::vector<Command>& cmds,
|
||||
size_t pos = 0;
|
||||
size_t from_pos = 0;
|
||||
for (int i = 0; i < cmds.size() && pos < total_length; ++i) {
|
||||
memcpy(&(*literals)[pos], data + from_pos, cmds[i].insert_length_);
|
||||
pos += cmds[i].insert_length_;
|
||||
from_pos += cmds[i].insert_length_ + cmds[i].copy_length_;
|
||||
memcpy(&(*literals)[pos], data + from_pos, cmds[i].insert_len_);
|
||||
pos += cmds[i].insert_len_;
|
||||
from_pos += cmds[i].insert_len_ + cmds[i].copy_len_;
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,9 +75,9 @@ void CopyCommandsToByteArray(const std::vector<Command>& cmds,
|
||||
std::vector<uint8_t>* distance_prefixes) {
|
||||
for (int i = 0; i < cmds.size(); ++i) {
|
||||
const Command& cmd = cmds[i];
|
||||
insert_and_copy_codes->push_back(cmd.command_prefix_);
|
||||
if (cmd.copy_length_ > 0 && cmd.distance_prefix_ != 0xffff) {
|
||||
distance_prefixes->push_back(cmd.distance_prefix_);
|
||||
insert_and_copy_codes->push_back(cmd.cmd_prefix_);
|
||||
if (cmd.copy_len_ > 0 && cmd.cmd_prefix_ >= 128) {
|
||||
distance_prefixes->push_back(cmd.dist_prefix_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -301,7 +301,7 @@ void SplitByteVector(const std::vector<DataType>& data,
|
||||
const double block_switch_cost,
|
||||
BlockSplit* split) {
|
||||
if (data.empty()) {
|
||||
split->num_types_ = 0;
|
||||
split->num_types_ = 1;
|
||||
return;
|
||||
} else if (data.size() < kMinLengthForBlockSplitting) {
|
||||
split->num_types_ = 1;
|
||||
@ -376,7 +376,7 @@ void SplitBlockByTotalLength(const std::vector<Command>& all_commands,
|
||||
std::vector<Command> cur_block;
|
||||
for (int i = 0; i < all_commands.size(); ++i) {
|
||||
const Command& cmd = all_commands[i];
|
||||
int cmd_length = cmd.insert_length_ + cmd.copy_length_;
|
||||
int cmd_length = cmd.insert_len_ + cmd.copy_len_;
|
||||
if (total_length > length_limit) {
|
||||
blocks->push_back(cur_block);
|
||||
cur_block.clear();
|
||||
|
@ -29,8 +29,7 @@ namespace brotli {
|
||||
|
||||
struct BlockSplit {
|
||||
int num_types_;
|
||||
std::vector<uint8_t> types_;
|
||||
std::vector<int> type_codes_;
|
||||
std::vector<int> types_;
|
||||
std::vector<int> lengths_;
|
||||
};
|
||||
|
||||
|
575
enc/brotli_bit_stream.cc
Normal file
575
enc/brotli_bit_stream.cc
Normal file
@ -0,0 +1,575 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Brotli bit stream functions to support the low level format. There are no
|
||||
// compression algorithms here, just the right ordering of bits to match the
|
||||
// specs.
|
||||
|
||||
#include "./brotli_bit_stream.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "./bit_cost.h"
|
||||
#include "./entropy_encode.h"
|
||||
#include "./fast_log.h"
|
||||
#include "./prefix.h"
|
||||
#include "./write_bits.h"
|
||||
|
||||
namespace brotli {
|
||||
|
||||
// returns false if fail
|
||||
// nibblesbits represents the 2 bits to encode MNIBBLES (0-3)
|
||||
bool EncodeMlen(size_t length, int* bits, int* numbits, int* nibblesbits) {
|
||||
length--; // MLEN - 1 is encoded
|
||||
int lg = length == 0 ? 1 : Log2Floor(length) + 1;
|
||||
if (lg > 28) return false;
|
||||
int mnibbles = (lg < 16 ? 16 : (lg + 3)) / 4;
|
||||
*nibblesbits = mnibbles - 4;
|
||||
*numbits = mnibbles * 4;
|
||||
*bits = length;
|
||||
return true;
|
||||
}
|
||||
|
||||
void StoreVarLenUint8(int n, int* storage_ix, uint8_t* storage) {
|
||||
if (n == 0) {
|
||||
WriteBits(1, 0, storage_ix, storage);
|
||||
} else {
|
||||
WriteBits(1, 1, storage_ix, storage);
|
||||
int nbits = Log2Floor(n);
|
||||
WriteBits(3, nbits, storage_ix, storage);
|
||||
WriteBits(nbits, n - (1 << nbits), storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
bool StoreCompressedMetaBlockHeader(bool final_block,
|
||||
int length,
|
||||
int* storage_ix,
|
||||
uint8_t* storage) {
|
||||
// Write ISLAST bit.
|
||||
WriteBits(1, final_block, storage_ix, storage);
|
||||
// Write ISEMPTY bit.
|
||||
if (final_block) {
|
||||
WriteBits(1, length == 0, storage_ix, storage);
|
||||
if (length == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
// Only the last meta-block can be empty.
|
||||
return false;
|
||||
}
|
||||
|
||||
int lenbits;
|
||||
int nlenbits;
|
||||
int nibblesbits;
|
||||
if (!EncodeMlen(length, &lenbits, &nlenbits, &nibblesbits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteBits(2, nibblesbits, storage_ix, storage);
|
||||
WriteBits(nlenbits, lenbits, storage_ix, storage);
|
||||
|
||||
if (!final_block) {
|
||||
// Write ISUNCOMPRESSED bit.
|
||||
WriteBits(1, 0, storage_ix, storage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StoreUncompressedMetaBlockHeader(int length,
|
||||
int* storage_ix,
|
||||
uint8_t* storage) {
|
||||
// Write ISLAST bit. Uncompressed block cannot be the last one, so set to 0.
|
||||
WriteBits(1, 0, storage_ix, storage);
|
||||
int lenbits;
|
||||
int nlenbits;
|
||||
int nibblesbits;
|
||||
if (!EncodeMlen(length, &lenbits, &nlenbits, &nibblesbits)) {
|
||||
return false;
|
||||
}
|
||||
WriteBits(2, nibblesbits, storage_ix, storage);
|
||||
WriteBits(nlenbits, lenbits, storage_ix, storage);
|
||||
// Write ISUNCOMPRESSED bit.
|
||||
WriteBits(1, 1, storage_ix, storage);
|
||||
return true;
|
||||
}
|
||||
|
||||
void StoreHuffmanTreeOfHuffmanTreeToBitMask(
|
||||
const int num_codes,
|
||||
const uint8_t *code_length_bitdepth,
|
||||
int *storage_ix,
|
||||
uint8_t *storage) {
|
||||
static const uint8_t kStorageOrder[kCodeLengthCodes] = {
|
||||
1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
};
|
||||
// The bit lengths of the Huffman code over the code length alphabet
|
||||
// are compressed with the following static Huffman code:
|
||||
// Symbol Code
|
||||
// ------ ----
|
||||
// 0 00
|
||||
// 1 1110
|
||||
// 2 110
|
||||
// 3 01
|
||||
// 4 10
|
||||
// 5 1111
|
||||
static const uint8_t kHuffmanBitLengthHuffmanCodeSymbols[6] = {
|
||||
0, 7, 3, 2, 1, 15
|
||||
};
|
||||
static const uint8_t kHuffmanBitLengthHuffmanCodeBitLengths[6] = {
|
||||
2, 4, 3, 2, 2, 4
|
||||
};
|
||||
|
||||
// Throw away trailing zeros:
|
||||
int codes_to_store = kCodeLengthCodes;
|
||||
if (num_codes > 1) {
|
||||
for (; codes_to_store > 0; --codes_to_store) {
|
||||
if (code_length_bitdepth[kStorageOrder[codes_to_store - 1]] != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int skip_some = 0; // skips none.
|
||||
if (code_length_bitdepth[kStorageOrder[0]] == 0 &&
|
||||
code_length_bitdepth[kStorageOrder[1]] == 0) {
|
||||
skip_some = 2; // skips two.
|
||||
if (code_length_bitdepth[kStorageOrder[2]] == 0) {
|
||||
skip_some = 3; // skips three.
|
||||
}
|
||||
}
|
||||
WriteBits(2, skip_some, storage_ix, storage);
|
||||
for (int i = skip_some; i < codes_to_store; ++i) {
|
||||
uint8_t l = code_length_bitdepth[kStorageOrder[i]];
|
||||
WriteBits(kHuffmanBitLengthHuffmanCodeBitLengths[l],
|
||||
kHuffmanBitLengthHuffmanCodeSymbols[l], storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
void StoreHuffmanTreeToBitMask(
|
||||
const std::vector<uint8_t> &huffman_tree,
|
||||
const std::vector<uint8_t> &huffman_tree_extra_bits,
|
||||
const uint8_t *code_length_bitdepth,
|
||||
const std::vector<uint16_t> &code_length_bitdepth_symbols,
|
||||
int * __restrict storage_ix,
|
||||
uint8_t * __restrict storage) {
|
||||
for (int i = 0; i < huffman_tree.size(); ++i) {
|
||||
int ix = huffman_tree[i];
|
||||
WriteBits(code_length_bitdepth[ix], code_length_bitdepth_symbols[ix],
|
||||
storage_ix, storage);
|
||||
// Extra bits
|
||||
switch (ix) {
|
||||
case 16:
|
||||
WriteBits(2, huffman_tree_extra_bits[i], storage_ix, storage);
|
||||
break;
|
||||
case 17:
|
||||
WriteBits(3, huffman_tree_extra_bits[i], storage_ix, storage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StoreSimpleHuffmanTree(const uint8_t* depths,
|
||||
int symbols[4],
|
||||
int num_symbols,
|
||||
int max_bits,
|
||||
int *storage_ix, uint8_t *storage) {
|
||||
// value of 1 indicates a simple Huffman code
|
||||
WriteBits(2, 1, storage_ix, storage);
|
||||
WriteBits(2, num_symbols - 1, storage_ix, storage); // NSYM - 1
|
||||
|
||||
// Sort
|
||||
for (int i = 0; i < num_symbols; i++) {
|
||||
for (int j = i + 1; j < num_symbols; j++) {
|
||||
if (depths[symbols[j]] < depths[symbols[i]]) {
|
||||
std::swap(symbols[j], symbols[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (num_symbols == 2) {
|
||||
WriteBits(max_bits, symbols[0], storage_ix, storage);
|
||||
WriteBits(max_bits, symbols[1], storage_ix, storage);
|
||||
} else if (num_symbols == 3) {
|
||||
WriteBits(max_bits, symbols[0], storage_ix, storage);
|
||||
WriteBits(max_bits, symbols[1], storage_ix, storage);
|
||||
WriteBits(max_bits, symbols[2], storage_ix, storage);
|
||||
} else {
|
||||
WriteBits(max_bits, symbols[0], storage_ix, storage);
|
||||
WriteBits(max_bits, symbols[1], storage_ix, storage);
|
||||
WriteBits(max_bits, symbols[2], storage_ix, storage);
|
||||
WriteBits(max_bits, symbols[3], storage_ix, storage);
|
||||
// tree-select
|
||||
WriteBits(1, depths[symbols[0]] == 1 ? 1 : 0, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
// num = alphabet size
|
||||
// depths = symbol depths
|
||||
void StoreHuffmanTree(const uint8_t* depths, size_t num,
|
||||
int quality,
|
||||
int *storage_ix, uint8_t *storage) {
|
||||
// Write the Huffman tree into the brotli-representation.
|
||||
std::vector<uint8_t> huffman_tree;
|
||||
std::vector<uint8_t> huffman_tree_extra_bits;
|
||||
// TODO(user): Consider allocating these from stack.
|
||||
huffman_tree.reserve(256);
|
||||
huffman_tree_extra_bits.reserve(256);
|
||||
WriteHuffmanTree(depths, num, &huffman_tree, &huffman_tree_extra_bits);
|
||||
|
||||
// Calculate the statistics of the Huffman tree in brotli-representation.
|
||||
int huffman_tree_histogram[kCodeLengthCodes] = { 0 };
|
||||
for (int i = 0; i < huffman_tree.size(); ++i) {
|
||||
++huffman_tree_histogram[huffman_tree[i]];
|
||||
}
|
||||
|
||||
int num_codes = 0;
|
||||
int code = 0;
|
||||
for (int i = 0; i < kCodeLengthCodes; ++i) {
|
||||
if (huffman_tree_histogram[i]) {
|
||||
if (num_codes == 0) {
|
||||
code = i;
|
||||
num_codes = 1;
|
||||
} else if (num_codes == 1) {
|
||||
num_codes = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate another Huffman tree to use for compressing both the
|
||||
// earlier Huffman tree with.
|
||||
// TODO(user): Consider allocating these from stack.
|
||||
uint8_t code_length_bitdepth[kCodeLengthCodes] = { 0 };
|
||||
std::vector<uint16_t> code_length_bitdepth_symbols(kCodeLengthCodes);
|
||||
CreateHuffmanTree(&huffman_tree_histogram[0], kCodeLengthCodes,
|
||||
5, quality, &code_length_bitdepth[0]);
|
||||
ConvertBitDepthsToSymbols(code_length_bitdepth, kCodeLengthCodes,
|
||||
code_length_bitdepth_symbols.data());
|
||||
|
||||
// Now, we have all the data, let's start storing it
|
||||
StoreHuffmanTreeOfHuffmanTreeToBitMask(num_codes, code_length_bitdepth,
|
||||
storage_ix, storage);
|
||||
|
||||
if (num_codes == 1) {
|
||||
code_length_bitdepth[code] = 0;
|
||||
}
|
||||
|
||||
// Store the real huffman tree now.
|
||||
StoreHuffmanTreeToBitMask(huffman_tree,
|
||||
huffman_tree_extra_bits,
|
||||
&code_length_bitdepth[0],
|
||||
code_length_bitdepth_symbols,
|
||||
storage_ix, storage);
|
||||
}
|
||||
|
||||
void BuildAndStoreHuffmanTree(const int *histogram,
|
||||
const int length,
|
||||
const int quality,
|
||||
uint8_t* depth,
|
||||
uint16_t* bits,
|
||||
int* storage_ix,
|
||||
uint8_t* storage) {
|
||||
int count = 0;
|
||||
int s4[4] = { 0 };
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (histogram[i]) {
|
||||
if (count < 4) {
|
||||
s4[count] = i;
|
||||
} else if (quality < 3 && count > 4) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
int max_bits_counter = length - 1;
|
||||
int max_bits = 0;
|
||||
while (max_bits_counter) {
|
||||
max_bits_counter >>= 1;
|
||||
++max_bits;
|
||||
}
|
||||
|
||||
if (count <= 1) {
|
||||
WriteBits(4, 1, storage_ix, storage);
|
||||
WriteBits(max_bits, s4[0], storage_ix, storage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (length >= 50 && count >= 16 && quality >= 3) {
|
||||
std::vector<int> counts(length);
|
||||
memcpy(&counts[0], histogram, sizeof(counts[0]) * length);
|
||||
OptimizeHuffmanCountsForRle(length, &counts[0]);
|
||||
CreateHuffmanTree(&counts[0], length, 15, quality, depth);
|
||||
} else {
|
||||
CreateHuffmanTree(histogram, length, 15, quality, depth);
|
||||
}
|
||||
ConvertBitDepthsToSymbols(depth, length, bits);
|
||||
|
||||
if (count <= 4) {
|
||||
StoreSimpleHuffmanTree(depth, s4, count, max_bits, storage_ix, storage);
|
||||
} else {
|
||||
StoreHuffmanTree(depth, length, quality, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
int IndexOf(const std::vector<int>& v, int value) {
|
||||
for (int i = 0; i < v.size(); ++i) {
|
||||
if (v[i] == value) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MoveToFront(std::vector<int>* v, int index) {
|
||||
int value = (*v)[index];
|
||||
for (int i = index; i > 0; --i) {
|
||||
(*v)[i] = (*v)[i - 1];
|
||||
}
|
||||
(*v)[0] = value;
|
||||
}
|
||||
|
||||
std::vector<int> MoveToFrontTransform(const std::vector<int>& v) {
|
||||
if (v.empty()) return v;
|
||||
std::vector<int> mtf(*max_element(v.begin(), v.end()) + 1);
|
||||
for (int i = 0; i < mtf.size(); ++i) mtf[i] = i;
|
||||
std::vector<int> result(v.size());
|
||||
for (int i = 0; i < v.size(); ++i) {
|
||||
int index = IndexOf(mtf, v[i]);
|
||||
result[i] = index;
|
||||
MoveToFront(&mtf, index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Finds runs of zeros in v_in and replaces them with a prefix code of the run
|
||||
// length plus extra bits in *v_out and *extra_bits. Non-zero values in v_in are
|
||||
// shifted by *max_length_prefix. Will not create prefix codes bigger than the
|
||||
// initial value of *max_run_length_prefix. The prefix code of run length L is
|
||||
// simply Log2Floor(L) and the number of extra bits is the same as the prefix
|
||||
// code.
|
||||
void RunLengthCodeZeros(const std::vector<int>& v_in,
|
||||
int* max_run_length_prefix,
|
||||
std::vector<int>* v_out,
|
||||
std::vector<int>* extra_bits) {
|
||||
int max_reps = 0;
|
||||
for (int i = 0; i < v_in.size();) {
|
||||
for (; i < v_in.size() && v_in[i] != 0; ++i) ;
|
||||
int reps = 0;
|
||||
for (; i < v_in.size() && v_in[i] == 0; ++i) {
|
||||
++reps;
|
||||
}
|
||||
max_reps = std::max(reps, max_reps);
|
||||
}
|
||||
int max_prefix = max_reps > 0 ? Log2Floor(max_reps) : 0;
|
||||
*max_run_length_prefix = std::min(max_prefix, *max_run_length_prefix);
|
||||
for (int i = 0; i < v_in.size();) {
|
||||
if (v_in[i] != 0) {
|
||||
v_out->push_back(v_in[i] + *max_run_length_prefix);
|
||||
extra_bits->push_back(0);
|
||||
++i;
|
||||
} else {
|
||||
int reps = 1;
|
||||
for (uint32_t k = i + 1; k < v_in.size() && v_in[k] == 0; ++k) {
|
||||
++reps;
|
||||
}
|
||||
i += reps;
|
||||
while (reps) {
|
||||
if (reps < (2 << *max_run_length_prefix)) {
|
||||
int run_length_prefix = Log2Floor(reps);
|
||||
v_out->push_back(run_length_prefix);
|
||||
extra_bits->push_back(reps - (1 << run_length_prefix));
|
||||
break;
|
||||
} else {
|
||||
v_out->push_back(*max_run_length_prefix);
|
||||
extra_bits->push_back((1 << *max_run_length_prefix) - 1);
|
||||
reps -= (2 << *max_run_length_prefix) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a maximum zero-run-length-prefix value such that run-length coding
|
||||
// zeros in v with this maximum prefix value and then encoding the resulting
|
||||
// histogram and entropy-coding v produces the least amount of bits.
|
||||
int BestMaxZeroRunLengthPrefix(const std::vector<int>& v) {
|
||||
int min_cost = std::numeric_limits<int>::max();
|
||||
int best_max_prefix = 0;
|
||||
for (int max_prefix = 0; max_prefix <= 16; ++max_prefix) {
|
||||
std::vector<int> rle_symbols;
|
||||
std::vector<int> extra_bits;
|
||||
int max_run_length_prefix = max_prefix;
|
||||
RunLengthCodeZeros(v, &max_run_length_prefix, &rle_symbols, &extra_bits);
|
||||
if (max_run_length_prefix < max_prefix) break;
|
||||
HistogramContextMap histogram;
|
||||
for (int i = 0; i < rle_symbols.size(); ++i) {
|
||||
histogram.Add(rle_symbols[i]);
|
||||
}
|
||||
int bit_cost = PopulationCost(histogram);
|
||||
if (max_prefix > 0) {
|
||||
bit_cost += 4;
|
||||
}
|
||||
for (int i = 1; i <= max_prefix; ++i) {
|
||||
bit_cost += histogram.data_[i] * i; // extra bits
|
||||
}
|
||||
if (bit_cost < min_cost) {
|
||||
min_cost = bit_cost;
|
||||
best_max_prefix = max_prefix;
|
||||
}
|
||||
}
|
||||
return best_max_prefix;
|
||||
}
|
||||
|
||||
void EncodeContextMap(const std::vector<int>& context_map,
|
||||
int num_clusters,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
StoreVarLenUint8(num_clusters - 1, storage_ix, storage);
|
||||
|
||||
if (num_clusters == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> transformed_symbols = MoveToFrontTransform(context_map);
|
||||
std::vector<int> rle_symbols;
|
||||
std::vector<int> extra_bits;
|
||||
int max_run_length_prefix = BestMaxZeroRunLengthPrefix(transformed_symbols);
|
||||
RunLengthCodeZeros(transformed_symbols, &max_run_length_prefix,
|
||||
&rle_symbols, &extra_bits);
|
||||
HistogramContextMap symbol_histogram;
|
||||
for (int i = 0; i < rle_symbols.size(); ++i) {
|
||||
symbol_histogram.Add(rle_symbols[i]);
|
||||
}
|
||||
bool use_rle = max_run_length_prefix > 0;
|
||||
WriteBits(1, use_rle, storage_ix, storage);
|
||||
if (use_rle) {
|
||||
WriteBits(4, max_run_length_prefix - 1, storage_ix, storage);
|
||||
}
|
||||
EntropyCodeContextMap symbol_code;
|
||||
memset(symbol_code.depth_, 0, sizeof(symbol_code.depth_));
|
||||
memset(symbol_code.bits_, 0, sizeof(symbol_code.bits_));
|
||||
BuildAndStoreHuffmanTree(symbol_histogram.data_,
|
||||
num_clusters + max_run_length_prefix,
|
||||
9, // quality
|
||||
symbol_code.depth_, symbol_code.bits_,
|
||||
storage_ix, storage);
|
||||
for (int i = 0; i < rle_symbols.size(); ++i) {
|
||||
WriteBits(symbol_code.depth_[rle_symbols[i]],
|
||||
symbol_code.bits_[rle_symbols[i]],
|
||||
storage_ix, storage);
|
||||
if (rle_symbols[i] > 0 && rle_symbols[i] <= max_run_length_prefix) {
|
||||
WriteBits(rle_symbols[i], extra_bits[i], storage_ix, storage);
|
||||
}
|
||||
}
|
||||
WriteBits(1, 1, storage_ix, storage); // use move-to-front
|
||||
}
|
||||
|
||||
void StoreBlockSwitch(const BlockSplitCode& code,
|
||||
const int block_ix,
|
||||
int* storage_ix,
|
||||
uint8_t* storage) {
|
||||
if (block_ix > 0) {
|
||||
int typecode = code.type_code[block_ix];
|
||||
WriteBits(code.type_depths[typecode], code.type_bits[typecode],
|
||||
storage_ix, storage);
|
||||
}
|
||||
int lencode = code.length_prefix[block_ix];
|
||||
WriteBits(code.length_depths[lencode], code.length_bits[lencode],
|
||||
storage_ix, storage);
|
||||
WriteBits(code.length_nextra[block_ix], code.length_extra[block_ix],
|
||||
storage_ix, storage);
|
||||
}
|
||||
|
||||
void BuildAndStoreBlockSplitCode(const std::vector<int>& types,
|
||||
const std::vector<int>& lengths,
|
||||
const int num_types,
|
||||
const int quality,
|
||||
BlockSplitCode* code,
|
||||
int* storage_ix,
|
||||
uint8_t* storage) {
|
||||
const int num_blocks = types.size();
|
||||
std::vector<int> type_histo(num_types + 2);
|
||||
std::vector<int> length_histo(26);
|
||||
int last_type = 1;
|
||||
int second_last_type = 0;
|
||||
code->type_code.resize(num_blocks);
|
||||
code->length_prefix.resize(num_blocks);
|
||||
code->length_nextra.resize(num_blocks);
|
||||
code->length_extra.resize(num_blocks);
|
||||
code->type_depths.resize(num_types + 2);
|
||||
code->type_bits.resize(num_types + 2);
|
||||
code->length_depths.resize(26);
|
||||
code->length_bits.resize(26);
|
||||
for (int i = 0; i < num_blocks; ++i) {
|
||||
int type = types[i];
|
||||
int type_code = (type == last_type + 1 ? 1 :
|
||||
type == second_last_type ? 0 :
|
||||
type + 2);
|
||||
second_last_type = last_type;
|
||||
last_type = type;
|
||||
code->type_code[i] = type_code;
|
||||
if (i > 0) ++type_histo[type_code];
|
||||
GetBlockLengthPrefixCode(lengths[i],
|
||||
&code->length_prefix[i],
|
||||
&code->length_nextra[i],
|
||||
&code->length_extra[i]);
|
||||
++length_histo[code->length_prefix[i]];
|
||||
}
|
||||
StoreVarLenUint8(num_types - 1, storage_ix, storage);
|
||||
if (num_types > 1) {
|
||||
BuildAndStoreHuffmanTree(&type_histo[0], num_types + 2, quality,
|
||||
&code->type_depths[0], &code->type_bits[0],
|
||||
storage_ix, storage);
|
||||
BuildAndStoreHuffmanTree(&length_histo[0], 26, quality,
|
||||
&code->length_depths[0], &code->length_bits[0],
|
||||
storage_ix, storage);
|
||||
StoreBlockSwitch(*code, 0, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
void StoreTrivialContextMap(int num_types,
|
||||
int context_bits,
|
||||
int* storage_ix,
|
||||
uint8_t* storage) {
|
||||
StoreVarLenUint8(num_types - 1, storage_ix, storage);
|
||||
if (num_types > 1) {
|
||||
int repeat_code = context_bits - 1;
|
||||
int repeat_bits = (1 << repeat_code) - 1;
|
||||
int alphabet_size = num_types + repeat_code;
|
||||
std::vector<int> histogram(alphabet_size);
|
||||
std::vector<uint8_t> depths(alphabet_size);
|
||||
std::vector<uint16_t> bits(alphabet_size);
|
||||
// Write RLEMAX.
|
||||
WriteBits(1, 1, storage_ix, storage);
|
||||
WriteBits(4, repeat_code - 1, storage_ix, storage);
|
||||
histogram[repeat_code] = num_types;
|
||||
histogram[0] = 1;
|
||||
for (int i = context_bits; i < alphabet_size; ++i) {
|
||||
histogram[i] = 1;
|
||||
}
|
||||
BuildAndStoreHuffmanTree(&histogram[0], alphabet_size, 1,
|
||||
&depths[0], &bits[0],
|
||||
storage_ix, storage);
|
||||
for (int i = 0; i < num_types; ++i) {
|
||||
int code = (i == 0 ? 0 : i + context_bits - 1);
|
||||
WriteBits(depths[code], bits[code], storage_ix, storage);
|
||||
WriteBits(depths[repeat_code], bits[repeat_code], storage_ix, storage);
|
||||
WriteBits(repeat_code, repeat_bits, storage_ix, storage);
|
||||
}
|
||||
// Write IMTF (inverse-move-to-front) bit.
|
||||
WriteBits(1, 1, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace brotli
|
109
enc/brotli_bit_stream.h
Normal file
109
enc/brotli_bit_stream.h
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Functions to convert brotli-related data structures into the
|
||||
// brotli bit stream. The functions here operate under
|
||||
// assumption that there is enough space in the storage, i.e., there are
|
||||
// no out-of-range checks anywhere.
|
||||
//
|
||||
// These functions do bit addressing into a byte array. The byte array
|
||||
// is called "storage" and the index to the bit is called storage_ix
|
||||
// in function arguments.
|
||||
|
||||
#ifndef BROTLI_ENC_BROTLI_BIT_STREAM_H_
|
||||
#define BROTLI_ENC_BROTLI_BIT_STREAM_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
namespace brotli {
|
||||
|
||||
// All Store functions here will use a storage_ix, which is always the bit
|
||||
// position for the current storage.
|
||||
|
||||
// Stores a number between 0 and 255.
|
||||
void StoreVarLenUint8(int n, int* storage_ix, uint8_t* storage);
|
||||
|
||||
// Stores the compressed meta-block header.
|
||||
bool StoreCompressedMetaBlockHeader(bool final_block,
|
||||
int length,
|
||||
int* storage_ix,
|
||||
uint8_t* storage);
|
||||
|
||||
// Stores the uncompressed meta-block header.
|
||||
bool StoreUncompressedMetaBlockHeader(int length,
|
||||
int* storage_ix,
|
||||
uint8_t* storage);
|
||||
|
||||
// Stores a context map where the histogram type is always the block type.
|
||||
void StoreTrivialContextMap(int num_types,
|
||||
int context_bits,
|
||||
int* storage_ix,
|
||||
uint8_t* storage);
|
||||
|
||||
void StoreHuffmanTreeOfHuffmanTreeToBitMask(
|
||||
const int num_codes,
|
||||
const uint8_t *code_length_bitdepth,
|
||||
int *storage_ix,
|
||||
uint8_t *storage);
|
||||
|
||||
// Builds a Huffman tree from histogram[0:length] into depth[0:length] and
|
||||
// bits[0:length] and stores the encoded tree to the bit stream.
|
||||
void BuildAndStoreHuffmanTree(const int *histogram,
|
||||
const int length,
|
||||
const int quality,
|
||||
uint8_t* depth,
|
||||
uint16_t* bits,
|
||||
int* storage_ix,
|
||||
uint8_t* storage);
|
||||
|
||||
// Encodes the given context map to the bit stream. The number of different
|
||||
// histogram ids is given by num_clusters.
|
||||
void EncodeContextMap(const std::vector<int>& context_map,
|
||||
int num_clusters,
|
||||
int* storage_ix, uint8_t* storage);
|
||||
|
||||
// Data structure that stores everything that is needed to encode each block
|
||||
// block switch command.
|
||||
struct BlockSplitCode {
|
||||
std::vector<int> type_code;
|
||||
std::vector<int> length_prefix;
|
||||
std::vector<int> length_nextra;
|
||||
std::vector<int> length_extra;
|
||||
std::vector<uint8_t> type_depths;
|
||||
std::vector<uint16_t> type_bits;
|
||||
std::vector<uint8_t> length_depths;
|
||||
std::vector<uint16_t> length_bits;
|
||||
};
|
||||
|
||||
// Builds a BlockSplitCode data structure from the block split given by the
|
||||
// vector of block types and block lengths and stores it to the bit stream.
|
||||
void BuildAndStoreBlockSplitCode(const std::vector<int>& types,
|
||||
const std::vector<int>& lengths,
|
||||
const int num_types,
|
||||
const int quality,
|
||||
BlockSplitCode* code,
|
||||
int* storage_ix,
|
||||
uint8_t* storage);
|
||||
|
||||
// Stores the block switch command with index block_ix to the bit stream.
|
||||
void StoreBlockSwitch(const BlockSplitCode& code,
|
||||
const int block_ix,
|
||||
int* storage_ix,
|
||||
uint8_t* storage);
|
||||
|
||||
} // namespace brotli
|
||||
|
||||
#endif // BROTLI_ENC_BROTLI_BIT_STREAM_H_
|
@ -20,6 +20,7 @@
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <algorithm>
|
||||
#include <complex>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@ -42,7 +43,7 @@ struct HistogramPair {
|
||||
};
|
||||
|
||||
struct HistogramPairComparator {
|
||||
bool operator()(const HistogramPair& p1, const HistogramPair& p2) {
|
||||
bool operator()(const HistogramPair& p1, const HistogramPair& p2) const {
|
||||
if (p1.cost_diff != p2.cost_diff) {
|
||||
return p1.cost_diff > p2.cost_diff;
|
||||
}
|
||||
@ -59,8 +60,8 @@ inline double ClusterCostDiff(int size_a, int size_b) {
|
||||
|
||||
// Computes the bit cost reduction by combining out[idx1] and out[idx2] and if
|
||||
// it is below a threshold, stores the pair (idx1, idx2) in the *pairs heap.
|
||||
template<int kSize>
|
||||
void CompareAndPushToHeap(const Histogram<kSize>* out,
|
||||
template<typename HistogramType>
|
||||
void CompareAndPushToHeap(const HistogramType* out,
|
||||
const int* cluster_size,
|
||||
int idx1, int idx2,
|
||||
std::vector<HistogramPair>* pairs) {
|
||||
@ -90,7 +91,7 @@ void CompareAndPushToHeap(const Histogram<kSize>* out,
|
||||
} else {
|
||||
double threshold = pairs->empty() ? 1e99 :
|
||||
std::max(0.0, (*pairs)[0].cost_diff);
|
||||
Histogram<kSize> combo = out[idx1];
|
||||
HistogramType combo = out[idx1];
|
||||
combo.AddHistogram(out[idx2]);
|
||||
double cost_combo = PopulationCost(combo);
|
||||
if (cost_combo < threshold - p.cost_diff) {
|
||||
@ -105,8 +106,8 @@ void CompareAndPushToHeap(const Histogram<kSize>* out,
|
||||
}
|
||||
}
|
||||
|
||||
template<int kSize>
|
||||
void HistogramCombine(Histogram<kSize>* out,
|
||||
template<typename HistogramType>
|
||||
void HistogramCombine(HistogramType* out,
|
||||
int* cluster_size,
|
||||
int* symbols,
|
||||
int symbols_size,
|
||||
@ -178,22 +179,22 @@ void HistogramCombine(Histogram<kSize>* out,
|
||||
// Histogram refinement
|
||||
|
||||
// What is the bit cost of moving histogram from cur_symbol to candidate.
|
||||
template<int kSize>
|
||||
double HistogramBitCostDistance(const Histogram<kSize>& histogram,
|
||||
const Histogram<kSize>& candidate) {
|
||||
template<typename HistogramType>
|
||||
double HistogramBitCostDistance(const HistogramType& histogram,
|
||||
const HistogramType& candidate) {
|
||||
if (histogram.total_count_ == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
Histogram<kSize> tmp = histogram;
|
||||
HistogramType tmp = histogram;
|
||||
tmp.AddHistogram(candidate);
|
||||
return PopulationCost(tmp) - candidate.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.
|
||||
template<int kSize>
|
||||
void HistogramRemap(const Histogram<kSize>* in, int in_size,
|
||||
Histogram<kSize>* out, int* symbols) {
|
||||
template<typename HistogramType>
|
||||
void HistogramRemap(const HistogramType* in, int in_size,
|
||||
HistogramType* out, int* symbols) {
|
||||
std::set<int> all_symbols;
|
||||
for (int i = 0; i < in_size; ++i) {
|
||||
all_symbols.insert(symbols[i]);
|
||||
@ -224,10 +225,10 @@ void HistogramRemap(const Histogram<kSize>* in, int in_size,
|
||||
|
||||
// Reorder histograms in *out so that the new symbols in *symbols come in
|
||||
// increasing order.
|
||||
template<int kSize>
|
||||
void HistogramReindex(std::vector<Histogram<kSize> >* out,
|
||||
template<typename HistogramType>
|
||||
void HistogramReindex(std::vector<HistogramType>* out,
|
||||
std::vector<int>* symbols) {
|
||||
std::vector<Histogram<kSize> > tmp(*out);
|
||||
std::vector<HistogramType> tmp(*out);
|
||||
std::map<int, int> new_index;
|
||||
int next_index = 0;
|
||||
for (int i = 0; i < symbols->size(); ++i) {
|
||||
@ -246,11 +247,11 @@ void HistogramReindex(std::vector<Histogram<kSize> >* out,
|
||||
// Clusters similar histograms in 'in' together, the selected histograms are
|
||||
// placed in 'out', and for each index in 'in', *histogram_symbols will
|
||||
// indicate which of the 'out' histograms is the best approximation.
|
||||
template<int kSize>
|
||||
void ClusterHistograms(const std::vector<Histogram<kSize> >& in,
|
||||
template<typename HistogramType>
|
||||
void ClusterHistograms(const std::vector<HistogramType>& in,
|
||||
int num_contexts, int num_blocks,
|
||||
int max_histograms,
|
||||
std::vector<Histogram<kSize> >* out,
|
||||
std::vector<HistogramType>* out,
|
||||
std::vector<int>* histogram_symbols) {
|
||||
const int in_size = num_contexts * num_blocks;
|
||||
std::vector<int> cluster_size(in_size, 1);
|
||||
|
142
enc/command.h
142
enc/command.h
@ -18,31 +18,131 @@
|
||||
#define BROTLI_ENC_COMMAND_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "./fast_log.h"
|
||||
|
||||
namespace brotli {
|
||||
|
||||
// Command holds a sequence of literals and a backward reference copy.
|
||||
class Command {
|
||||
public:
|
||||
// distance_code_ is initialized to 17 because it refers to the distance
|
||||
// code of a backward distance of 1, this way the last insert-only command
|
||||
// won't use the last-distance short code, and accordingly distance_prefix_ is
|
||||
// set to 16
|
||||
Command() : insert_length_(0), copy_length_(0), copy_length_code_(0),
|
||||
copy_distance_(0), distance_code_(17),
|
||||
distance_prefix_(16), command_prefix_(0),
|
||||
distance_extra_bits_(0), distance_extra_bits_value_(0) {}
|
||||
static inline void GetDistCode(int distance_code,
|
||||
uint16_t* code, uint32_t* extra) {
|
||||
distance_code -= 1;
|
||||
if (distance_code < 16) {
|
||||
*code = distance_code;
|
||||
*extra = 0;
|
||||
} else {
|
||||
distance_code -= 12;
|
||||
int numextra = Log2FloorNonZero(distance_code) - 1;
|
||||
int prefix = distance_code >> numextra;
|
||||
*code = 12 + 2 * numextra + prefix;
|
||||
*extra = (numextra << 24) | (distance_code - (prefix << numextra));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t insert_length_;
|
||||
uint32_t copy_length_;
|
||||
uint32_t copy_length_code_;
|
||||
uint32_t copy_distance_;
|
||||
// Values <= 16 are short codes, values > 16 are distances shifted by 16.
|
||||
uint32_t distance_code_;
|
||||
uint16_t distance_prefix_;
|
||||
uint16_t command_prefix_;
|
||||
int distance_extra_bits_;
|
||||
uint32_t distance_extra_bits_value_;
|
||||
static int insbase[] = { 0, 1, 2, 3, 4, 5, 6, 8, 10, 14, 18, 26, 34, 50, 66,
|
||||
98, 130, 194, 322, 578, 1090, 2114, 6210, 22594 };
|
||||
static int insextra[] = { 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5,
|
||||
5, 6, 7, 8, 9, 10, 12, 14, 24 };
|
||||
static int copybase[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 18, 22, 30, 38,
|
||||
54, 70, 102, 134, 198, 326, 582, 1094, 2118 };
|
||||
static int copyextra[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4,
|
||||
4, 5, 5, 6, 7, 8, 9, 10, 24 };
|
||||
|
||||
static inline int GetInsertLengthCode(int insertlen) {
|
||||
if (insertlen < 6) {
|
||||
return insertlen;
|
||||
} else if (insertlen < 130) {
|
||||
insertlen -= 2;
|
||||
int nbits = Log2FloorNonZero(insertlen) - 1;
|
||||
return (nbits << 1) + (insertlen >> nbits) + 2;
|
||||
} else if (insertlen < 2114) {
|
||||
return Log2FloorNonZero(insertlen - 66) + 10;
|
||||
} else if (insertlen < 6210) {
|
||||
return 21;
|
||||
} else if (insertlen < 22594) {
|
||||
return 22;
|
||||
} else {
|
||||
return 23;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int GetCopyLengthCode(int copylen) {
|
||||
if (copylen < 10) {
|
||||
return copylen - 2;
|
||||
} else if (copylen < 134) {
|
||||
copylen -= 6;
|
||||
int nbits = Log2FloorNonZero(copylen) - 1;
|
||||
return (nbits << 1) + (copylen >> nbits) + 4;
|
||||
} else if (copylen < 2118) {
|
||||
return Log2FloorNonZero(copylen - 70) + 12;
|
||||
} else {
|
||||
return 23;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int CombineLengthCodes(
|
||||
int inscode, int copycode, int distancecode) {
|
||||
int bits64 = (copycode & 0x7u) | ((inscode & 0x7u) << 3);
|
||||
if (distancecode == 0 && inscode < 8 && copycode < 16) {
|
||||
return (copycode < 8) ? bits64 : (bits64 | 64);
|
||||
} else {
|
||||
// "To convert an insert-and-copy length code to an insert length code and
|
||||
// a copy length code, the following table can be used"
|
||||
static const int cells[9] = { 2, 3, 6, 4, 5, 8, 7, 9, 10 };
|
||||
return (cells[(copycode >> 3) + 3 * (inscode >> 3)] << 6) | bits64;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void GetLengthCode(int insertlen, int copylen, int distancecode,
|
||||
uint16_t* code, uint64_t* extra) {
|
||||
int inscode = GetInsertLengthCode(insertlen);
|
||||
int copycode = GetCopyLengthCode(copylen);
|
||||
uint64_t insnumextra = insextra[inscode];
|
||||
uint64_t numextra = insnumextra + copyextra[copycode];
|
||||
uint64_t insextraval = insertlen - insbase[inscode];
|
||||
uint64_t copyextraval = copylen - copybase[copycode];
|
||||
*code = CombineLengthCodes(inscode, copycode, distancecode);
|
||||
*extra = (numextra << 48) | (copyextraval << insnumextra) | insextraval;
|
||||
}
|
||||
|
||||
struct Command {
|
||||
Command() {}
|
||||
|
||||
Command(int insertlen, int copylen, int copylen_code, int distance_code)
|
||||
: insert_len_(insertlen), copy_len_(copylen) {
|
||||
GetDistCode(distance_code, &dist_prefix_, &dist_extra_);
|
||||
GetLengthCode(insertlen, copylen_code, dist_prefix_,
|
||||
&cmd_prefix_, &cmd_extra_);
|
||||
}
|
||||
|
||||
Command(int insertlen)
|
||||
: insert_len_(insertlen), copy_len_(0), dist_prefix_(16), dist_extra_(0) {
|
||||
GetLengthCode(insertlen, 4, dist_prefix_, &cmd_prefix_, &cmd_extra_);
|
||||
}
|
||||
|
||||
int DistanceCode() const {
|
||||
if (dist_prefix_ < 16) {
|
||||
return dist_prefix_ + 1;
|
||||
}
|
||||
int nbits = dist_extra_ >> 24;
|
||||
int extra = dist_extra_ & 0xffffff;
|
||||
int prefix = dist_prefix_ - 12 - 2 * nbits;
|
||||
return (prefix << nbits) + extra + 13;
|
||||
}
|
||||
|
||||
int DistanceContext() const {
|
||||
int r = cmd_prefix_ >> 6;
|
||||
int c = cmd_prefix_ & 7;
|
||||
if ((r == 0 || r == 2 || r == 4 || r == 7) && (c <= 2)) {
|
||||
return c;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
int insert_len_;
|
||||
int copy_len_;
|
||||
uint16_t cmd_prefix_;
|
||||
uint16_t dist_prefix_;
|
||||
uint64_t cmd_extra_;
|
||||
uint32_t dist_extra_;
|
||||
};
|
||||
|
||||
} // namespace brotli
|
||||
|
@ -17,6 +17,8 @@
|
||||
#ifndef BROTLI_ENC_DICTIONARY_H_
|
||||
#define BROTLI_ENC_DICTIONARY_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t kBrotliDictionary[] = {
|
||||
0x74, 0x69, 0x6d, 0x65, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x66, 0x65, 0x6c,
|
||||
0x65, 0x66, 0x74, 0x62, 0x61, 0x63, 0x6b, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x61,
|
||||
|
589
enc/encode.cc
589
enc/encode.cc
@ -22,6 +22,7 @@
|
||||
#include "./backward_references.h"
|
||||
#include "./bit_cost.h"
|
||||
#include "./block_splitter.h"
|
||||
#include "./brotli_bit_stream.h"
|
||||
#include "./cluster.h"
|
||||
#include "./context.h"
|
||||
#include "./transform.h"
|
||||
@ -65,19 +66,6 @@ double TotalBitCost(const std::vector<Histogram<kSize> >& histograms) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
void EncodeVarLenUint8(int n, int* storage_ix, uint8_t* storage) {
|
||||
if (n == 0) {
|
||||
WriteBits(1, 0, storage_ix, storage);
|
||||
} else {
|
||||
WriteBits(1, 1, storage_ix, storage);
|
||||
int nbits = Log2Floor(n);
|
||||
WriteBits(3, nbits, storage_ix, storage);
|
||||
if (nbits > 0) {
|
||||
WriteBits(nbits, n - (1 << nbits), storage_ix, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ParseAsUTF8(int* symbol, const uint8_t* input, int size) {
|
||||
// ASCII
|
||||
if ((input[0] & 0x80) == 0) {
|
||||
@ -168,134 +156,6 @@ void EncodeMetaBlockLength(size_t meta_block_size,
|
||||
}
|
||||
}
|
||||
|
||||
void StoreHuffmanTreeOfHuffmanTreeToBitMask(
|
||||
const uint8_t* code_length_bitdepth,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
static const uint8_t kStorageOrder[kCodeLengthCodes] = {
|
||||
1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
};
|
||||
// Throw away trailing zeros:
|
||||
int codes_to_store = kCodeLengthCodes;
|
||||
for (; codes_to_store > 0; --codes_to_store) {
|
||||
if (code_length_bitdepth[kStorageOrder[codes_to_store - 1]] != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
int num_codes = 0;
|
||||
for (int i = 0; i < codes_to_store; ++i) {
|
||||
if (code_length_bitdepth[kStorageOrder[i]] != 0) {
|
||||
++num_codes;
|
||||
}
|
||||
}
|
||||
if (num_codes == 1) {
|
||||
codes_to_store = kCodeLengthCodes;
|
||||
}
|
||||
int skip_some = 0; // skips none.
|
||||
if (code_length_bitdepth[kStorageOrder[0]] == 0 &&
|
||||
code_length_bitdepth[kStorageOrder[1]] == 0) {
|
||||
skip_some = 2; // skips two.
|
||||
if (code_length_bitdepth[kStorageOrder[2]] == 0) {
|
||||
skip_some = 3; // skips three.
|
||||
}
|
||||
}
|
||||
WriteBits(2, skip_some, storage_ix, storage);
|
||||
for (int i = skip_some; i < codes_to_store; ++i) {
|
||||
uint8_t len[] = { 2, 4, 3, 2, 2, 4 };
|
||||
uint8_t bits[] = { 0, 7, 3, 2, 1, 15 };
|
||||
int v = code_length_bitdepth[kStorageOrder[i]];
|
||||
WriteBits(len[v], bits[v], storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
void StoreHuffmanTreeToBitMask(
|
||||
const uint8_t* huffman_tree,
|
||||
const uint8_t* huffman_tree_extra_bits,
|
||||
const int huffman_tree_size,
|
||||
const EntropyCode<kCodeLengthCodes>& entropy,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
for (int i = 0; i < huffman_tree_size; ++i) {
|
||||
const int ix = huffman_tree[i];
|
||||
const int extra_bits = huffman_tree_extra_bits[i];
|
||||
if (entropy.count_ > 1) {
|
||||
WriteBits(entropy.depth_[ix], entropy.bits_[ix], storage_ix, storage);
|
||||
}
|
||||
switch (ix) {
|
||||
case 16:
|
||||
WriteBits(2, extra_bits, storage_ix, storage);
|
||||
break;
|
||||
case 17:
|
||||
WriteBits(3, extra_bits, storage_ix, storage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int kSize>
|
||||
void StoreHuffmanCodeSimple(
|
||||
const EntropyCode<kSize>& code, int alphabet_size,
|
||||
int max_bits, int* storage_ix, uint8_t* storage) {
|
||||
const uint8_t *depth = &code.depth_[0];
|
||||
int symbols[4];
|
||||
// Quadratic sort.
|
||||
int k, j;
|
||||
for (k = 0; k < code.count_; ++k) {
|
||||
symbols[k] = code.symbols_[k];
|
||||
}
|
||||
for (k = 0; k < code.count_; ++k) {
|
||||
for (j = k + 1; j < code.count_; ++j) {
|
||||
if (depth[symbols[j]] < depth[symbols[k]]) {
|
||||
int t = symbols[k];
|
||||
symbols[k] = symbols[j];
|
||||
symbols[j] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Small tree marker to encode 1-4 symbols.
|
||||
WriteBits(2, 1, storage_ix, storage);
|
||||
WriteBits(2, code.count_ - 1, storage_ix, storage);
|
||||
for (int i = 0; i < code.count_; ++i) {
|
||||
WriteBits(max_bits, symbols[i], storage_ix, storage);
|
||||
}
|
||||
if (code.count_ == 4) {
|
||||
if (depth[symbols[0]] == 2 &&
|
||||
depth[symbols[1]] == 2 &&
|
||||
depth[symbols[2]] == 2 &&
|
||||
depth[symbols[3]] == 2) {
|
||||
WriteBits(1, 0, storage_ix, storage);
|
||||
} else {
|
||||
WriteBits(1, 1, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int kSize>
|
||||
void StoreHuffmanCodeComplex(
|
||||
const EntropyCode<kSize>& code, int alphabet_size,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
const uint8_t *depth = &code.depth_[0];
|
||||
uint8_t huffman_tree[kSize];
|
||||
uint8_t huffman_tree_extra_bits[kSize];
|
||||
int huffman_tree_size = 0;
|
||||
WriteHuffmanTree(depth,
|
||||
alphabet_size,
|
||||
&huffman_tree[0],
|
||||
&huffman_tree_extra_bits[0],
|
||||
&huffman_tree_size);
|
||||
Histogram<kCodeLengthCodes> huffman_tree_histogram;
|
||||
memset(huffman_tree_histogram.data_, 0, sizeof(huffman_tree_histogram.data_));
|
||||
for (int i = 0; i < huffman_tree_size; ++i) {
|
||||
huffman_tree_histogram.Add(huffman_tree[i]);
|
||||
}
|
||||
EntropyCode<kCodeLengthCodes> huffman_tree_entropy;
|
||||
BuildEntropyCode(huffman_tree_histogram, 5, kCodeLengthCodes,
|
||||
&huffman_tree_entropy);
|
||||
StoreHuffmanTreeOfHuffmanTreeToBitMask(
|
||||
&huffman_tree_entropy.depth_[0], storage_ix, storage);
|
||||
StoreHuffmanTreeToBitMask(&huffman_tree[0], &huffman_tree_extra_bits[0],
|
||||
huffman_tree_size, huffman_tree_entropy,
|
||||
storage_ix, storage);
|
||||
}
|
||||
|
||||
template<int kSize>
|
||||
void BuildAndStoreEntropyCode(const Histogram<kSize>& histogram,
|
||||
const int tree_limit,
|
||||
@ -304,45 +164,8 @@ void BuildAndStoreEntropyCode(const Histogram<kSize>& histogram,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
memset(code->depth_, 0, sizeof(code->depth_));
|
||||
memset(code->bits_, 0, sizeof(code->bits_));
|
||||
memset(code->symbols_, 0, sizeof(code->symbols_));
|
||||
code->count_ = 0;
|
||||
|
||||
int max_bits_counter = alphabet_size - 1;
|
||||
int max_bits = 0;
|
||||
while (max_bits_counter) {
|
||||
max_bits_counter >>= 1;
|
||||
++max_bits;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < alphabet_size; i++) {
|
||||
if (histogram.data_[i] > 0) {
|
||||
if (code->count_ < 4) code->symbols_[code->count_] = i;
|
||||
++code->count_;
|
||||
}
|
||||
}
|
||||
|
||||
if (code->count_ <= 1) {
|
||||
WriteBits(2, 1, storage_ix, storage);
|
||||
WriteBits(2, 0, storage_ix, storage);
|
||||
WriteBits(max_bits, code->symbols_[0], storage_ix, storage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (alphabet_size >= 50 && code->count_ >= 16) {
|
||||
std::vector<int> counts(alphabet_size);
|
||||
memcpy(&counts[0], histogram.data_, sizeof(counts[0]) * alphabet_size);
|
||||
OptimizeHuffmanCountsForRle(alphabet_size, &counts[0]);
|
||||
CreateHuffmanTree(&counts[0], alphabet_size, tree_limit, code->depth_);
|
||||
} else {
|
||||
CreateHuffmanTree(histogram.data_, alphabet_size, tree_limit, code->depth_);
|
||||
}
|
||||
ConvertBitDepthsToSymbols(code->depth_, alphabet_size, code->bits_);
|
||||
|
||||
if (code->count_ <= 4) {
|
||||
StoreHuffmanCodeSimple(*code, alphabet_size, max_bits, storage_ix, storage);
|
||||
} else {
|
||||
StoreHuffmanCodeComplex(*code, alphabet_size, storage_ix, storage);
|
||||
}
|
||||
BuildAndStoreHuffmanTree(histogram.data_, alphabet_size, 9,
|
||||
code->depth_, code->bits_, storage_ix, storage);
|
||||
}
|
||||
|
||||
template<int kSize>
|
||||
@ -362,324 +185,45 @@ void BuildAndStoreEntropyCodes(
|
||||
void EncodeCommand(const Command& cmd,
|
||||
const EntropyCodeCommand& entropy,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
int code = cmd.command_prefix_;
|
||||
int code = cmd.cmd_prefix_;
|
||||
WriteBits(entropy.depth_[code], entropy.bits_[code], storage_ix, storage);
|
||||
if (code >= 128) {
|
||||
code -= 128;
|
||||
}
|
||||
int insert_extra_bits = InsertLengthExtraBits(code);
|
||||
uint64_t insert_extra_bits_val =
|
||||
cmd.insert_length_ - InsertLengthOffset(code);
|
||||
int copy_extra_bits = CopyLengthExtraBits(code);
|
||||
uint64_t copy_extra_bits_val = cmd.copy_length_code_ - CopyLengthOffset(code);
|
||||
if (insert_extra_bits > 0) {
|
||||
WriteBits(insert_extra_bits, insert_extra_bits_val, storage_ix, storage);
|
||||
}
|
||||
if (copy_extra_bits > 0) {
|
||||
WriteBits(copy_extra_bits, copy_extra_bits_val, storage_ix, storage);
|
||||
int nextra = cmd.cmd_extra_ >> 48;
|
||||
uint64_t extra = cmd.cmd_extra_ & 0xffffffffffffULL;
|
||||
if (nextra > 0) {
|
||||
WriteBits(nextra, extra, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeCopyDistance(const Command& cmd, const EntropyCodeDistance& entropy,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
int code = cmd.distance_prefix_;
|
||||
int extra_bits = cmd.distance_extra_bits_;
|
||||
uint64_t extra_bits_val = cmd.distance_extra_bits_value_;
|
||||
int code = cmd.dist_prefix_;
|
||||
int extra_bits = cmd.dist_extra_ >> 24;
|
||||
uint64_t extra_bits_val = cmd.dist_extra_ & 0xffffff;
|
||||
WriteBits(entropy.depth_[code], entropy.bits_[code], storage_ix, storage);
|
||||
if (extra_bits > 0) {
|
||||
WriteBits(extra_bits, extra_bits_val, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeDistanceShortCodes(std::vector<Command>* cmds,
|
||||
size_t pos,
|
||||
const size_t max_backward,
|
||||
int* dist_ringbuffer,
|
||||
size_t* ringbuffer_idx) {
|
||||
static const int kIndexOffset[16] = {
|
||||
3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2
|
||||
};
|
||||
static const int kValueOffset[16] = {
|
||||
0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3
|
||||
};
|
||||
for (int i = 0; i < cmds->size(); ++i) {
|
||||
pos += (*cmds)[i].insert_length_;
|
||||
size_t max_distance = std::min(pos, max_backward);
|
||||
int cur_dist = (*cmds)[i].copy_distance_;
|
||||
int dist_code = cur_dist + 16;
|
||||
if (cur_dist <= max_distance) {
|
||||
if (cur_dist == 0) break;
|
||||
int limits[16] = { 0, 0, 0, 0,
|
||||
6, 6, 11, 11,
|
||||
11, 11, 11, 11,
|
||||
12, 12, 12, 12 };
|
||||
for (int k = 0; k < 16; ++k) {
|
||||
// Only accept more popular choices.
|
||||
if (cur_dist < limits[k]) {
|
||||
// Typically unpopular ranges, don't replace a short distance
|
||||
// with them.
|
||||
continue;
|
||||
}
|
||||
int comp = (dist_ringbuffer[(*ringbuffer_idx + kIndexOffset[k]) & 3] +
|
||||
kValueOffset[k]);
|
||||
if (cur_dist == comp) {
|
||||
dist_code = k + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dist_code > 1) {
|
||||
dist_ringbuffer[*ringbuffer_idx & 3] = cur_dist;
|
||||
++(*ringbuffer_idx);
|
||||
}
|
||||
pos += (*cmds)[i].copy_length_;
|
||||
} else {
|
||||
int word_idx = cur_dist - max_distance - 1;
|
||||
const std::string word =
|
||||
GetTransformedDictionaryWord((*cmds)[i].copy_length_code_, word_idx);
|
||||
pos += word.size();
|
||||
}
|
||||
(*cmds)[i].distance_code_ = dist_code;
|
||||
void RecomputeDistancePrefixes(std::vector<Command>* cmds,
|
||||
int num_direct_distance_codes,
|
||||
int distance_postfix_bits) {
|
||||
if (num_direct_distance_codes == 0 &&
|
||||
distance_postfix_bits == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeCommandPrefixes(std::vector<Command>* cmds,
|
||||
int num_direct_distance_codes,
|
||||
int distance_postfix_bits) {
|
||||
for (int i = 0; i < cmds->size(); ++i) {
|
||||
Command* cmd = &(*cmds)[i];
|
||||
cmd->command_prefix_ = CommandPrefix(cmd->insert_length_,
|
||||
cmd->copy_length_code_);
|
||||
if (cmd->copy_length_code_ > 0) {
|
||||
PrefixEncodeCopyDistance(cmd->distance_code_,
|
||||
if (cmd->copy_len_ > 0 && cmd->cmd_prefix_ >= 128) {
|
||||
PrefixEncodeCopyDistance(cmd->DistanceCode(),
|
||||
num_direct_distance_codes,
|
||||
distance_postfix_bits,
|
||||
&cmd->distance_prefix_,
|
||||
&cmd->distance_extra_bits_,
|
||||
&cmd->distance_extra_bits_value_);
|
||||
}
|
||||
if (cmd->command_prefix_ < 128 && cmd->distance_prefix_ == 0) {
|
||||
cmd->distance_prefix_ = 0xffff;
|
||||
} else {
|
||||
cmd->command_prefix_ += 128;
|
||||
&cmd->dist_prefix_,
|
||||
&cmd->dist_extra_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int IndexOf(const std::vector<int>& v, int value) {
|
||||
for (int i = 0; i < v.size(); ++i) {
|
||||
if (v[i] == value) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MoveToFront(std::vector<int>* v, int index) {
|
||||
int value = (*v)[index];
|
||||
for (int i = index; i > 0; --i) {
|
||||
(*v)[i] = (*v)[i - 1];
|
||||
}
|
||||
(*v)[0] = value;
|
||||
}
|
||||
|
||||
std::vector<int> MoveToFrontTransform(const std::vector<int>& v) {
|
||||
if (v.empty()) return v;
|
||||
std::vector<int> mtf(*max_element(v.begin(), v.end()) + 1);
|
||||
for (int i = 0; i < mtf.size(); ++i) mtf[i] = i;
|
||||
std::vector<int> result(v.size());
|
||||
for (int i = 0; i < v.size(); ++i) {
|
||||
int index = IndexOf(mtf, v[i]);
|
||||
result[i] = index;
|
||||
MoveToFront(&mtf, index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Finds runs of zeros in v_in and replaces them with a prefix code of the run
|
||||
// length plus extra bits in *v_out and *extra_bits. Non-zero values in v_in are
|
||||
// shifted by *max_length_prefix. Will not create prefix codes bigger than the
|
||||
// initial value of *max_run_length_prefix. The prefix code of run length L is
|
||||
// simply Log2Floor(L) and the number of extra bits is the same as the prefix
|
||||
// code.
|
||||
void RunLengthCodeZeros(const std::vector<int>& v_in,
|
||||
int* max_run_length_prefix,
|
||||
std::vector<int>* v_out,
|
||||
std::vector<int>* extra_bits) {
|
||||
int max_reps = 0;
|
||||
for (int i = 0; i < v_in.size();) {
|
||||
for (; i < v_in.size() && v_in[i] != 0; ++i) ;
|
||||
int reps = 0;
|
||||
for (; i < v_in.size() && v_in[i] == 0; ++i) {
|
||||
++reps;
|
||||
}
|
||||
max_reps = std::max(reps, max_reps);
|
||||
}
|
||||
int max_prefix = max_reps > 0 ? Log2Floor(max_reps) : 0;
|
||||
*max_run_length_prefix = std::min(max_prefix, *max_run_length_prefix);
|
||||
for (int i = 0; i < v_in.size();) {
|
||||
if (v_in[i] != 0) {
|
||||
v_out->push_back(v_in[i] + *max_run_length_prefix);
|
||||
extra_bits->push_back(0);
|
||||
++i;
|
||||
} else {
|
||||
int reps = 1;
|
||||
for (uint32_t k = i + 1; k < v_in.size() && v_in[k] == 0; ++k) {
|
||||
++reps;
|
||||
}
|
||||
i += reps;
|
||||
while (reps) {
|
||||
if (reps < (2 << *max_run_length_prefix)) {
|
||||
int run_length_prefix = Log2Floor(reps);
|
||||
v_out->push_back(run_length_prefix);
|
||||
extra_bits->push_back(reps - (1 << run_length_prefix));
|
||||
break;
|
||||
} else {
|
||||
v_out->push_back(*max_run_length_prefix);
|
||||
extra_bits->push_back((1 << *max_run_length_prefix) - 1);
|
||||
reps -= (2 << *max_run_length_prefix) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a maximum zero-run-length-prefix value such that run-length coding
|
||||
// zeros in v with this maximum prefix value and then encoding the resulting
|
||||
// histogram and entropy-coding v produces the least amount of bits.
|
||||
int BestMaxZeroRunLengthPrefix(const std::vector<int>& v) {
|
||||
int min_cost = std::numeric_limits<int>::max();
|
||||
int best_max_prefix = 0;
|
||||
for (int max_prefix = 0; max_prefix <= 16; ++max_prefix) {
|
||||
std::vector<int> rle_symbols;
|
||||
std::vector<int> extra_bits;
|
||||
int max_run_length_prefix = max_prefix;
|
||||
RunLengthCodeZeros(v, &max_run_length_prefix, &rle_symbols, &extra_bits);
|
||||
if (max_run_length_prefix < max_prefix) break;
|
||||
HistogramContextMap histogram;
|
||||
for (int i = 0; i < rle_symbols.size(); ++i) {
|
||||
histogram.Add(rle_symbols[i]);
|
||||
}
|
||||
int bit_cost = PopulationCost(histogram);
|
||||
if (max_prefix > 0) {
|
||||
bit_cost += 4;
|
||||
}
|
||||
for (int i = 1; i <= max_prefix; ++i) {
|
||||
bit_cost += histogram.data_[i] * i; // extra bits
|
||||
}
|
||||
if (bit_cost < min_cost) {
|
||||
min_cost = bit_cost;
|
||||
best_max_prefix = max_prefix;
|
||||
}
|
||||
}
|
||||
return best_max_prefix;
|
||||
}
|
||||
|
||||
void EncodeContextMap(const std::vector<int>& context_map,
|
||||
int num_clusters,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
EncodeVarLenUint8(num_clusters - 1, storage_ix, storage);
|
||||
|
||||
if (num_clusters == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> transformed_symbols = MoveToFrontTransform(context_map);
|
||||
std::vector<int> rle_symbols;
|
||||
std::vector<int> extra_bits;
|
||||
int max_run_length_prefix = BestMaxZeroRunLengthPrefix(transformed_symbols);
|
||||
RunLengthCodeZeros(transformed_symbols, &max_run_length_prefix,
|
||||
&rle_symbols, &extra_bits);
|
||||
HistogramContextMap symbol_histogram;
|
||||
for (int i = 0; i < rle_symbols.size(); ++i) {
|
||||
symbol_histogram.Add(rle_symbols[i]);
|
||||
}
|
||||
bool use_rle = max_run_length_prefix > 0;
|
||||
WriteBits(1, use_rle, storage_ix, storage);
|
||||
if (use_rle) {
|
||||
WriteBits(4, max_run_length_prefix - 1, storage_ix, storage);
|
||||
}
|
||||
EntropyCodeContextMap symbol_code;
|
||||
BuildAndStoreEntropyCode(symbol_histogram, 15,
|
||||
num_clusters + max_run_length_prefix,
|
||||
&symbol_code,
|
||||
storage_ix, storage);
|
||||
for (int i = 0; i < rle_symbols.size(); ++i) {
|
||||
WriteBits(symbol_code.depth_[rle_symbols[i]],
|
||||
symbol_code.bits_[rle_symbols[i]],
|
||||
storage_ix, storage);
|
||||
if (rle_symbols[i] > 0 && rle_symbols[i] <= max_run_length_prefix) {
|
||||
WriteBits(rle_symbols[i], extra_bits[i], storage_ix, storage);
|
||||
}
|
||||
}
|
||||
WriteBits(1, 1, storage_ix, storage); // use move-to-front
|
||||
}
|
||||
|
||||
struct BlockSplitCode {
|
||||
EntropyCodeBlockType block_type_code;
|
||||
EntropyCodeBlockLength block_len_code;
|
||||
};
|
||||
|
||||
void EncodeBlockLength(const EntropyCodeBlockLength& entropy,
|
||||
int length,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
int len_code = BlockLengthPrefix(length);
|
||||
int extra_bits = BlockLengthExtraBits(len_code);
|
||||
int extra_bits_value = length - BlockLengthOffset(len_code);
|
||||
WriteBits(entropy.depth_[len_code], entropy.bits_[len_code],
|
||||
storage_ix, storage);
|
||||
if (extra_bits > 0) {
|
||||
WriteBits(extra_bits, extra_bits_value, storage_ix, storage);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeBlockTypeShortCodes(BlockSplit* split) {
|
||||
if (split->num_types_ <= 1) {
|
||||
split->num_types_ = 1;
|
||||
return;
|
||||
}
|
||||
int ringbuffer[2] = { 0, 1 };
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < split->types_.size(); ++i) {
|
||||
int type = split->types_[i];
|
||||
int type_code;
|
||||
if (type == ringbuffer[index & 1]) {
|
||||
type_code = 0;
|
||||
} else if (type == ringbuffer[(index - 1) & 1] + 1) {
|
||||
type_code = 1;
|
||||
} else {
|
||||
type_code = type + 2;
|
||||
}
|
||||
ringbuffer[index & 1] = type;
|
||||
++index;
|
||||
split->type_codes_.push_back(type_code);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildAndEncodeBlockSplitCode(const BlockSplit& split,
|
||||
BlockSplitCode* code,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
EncodeVarLenUint8(split.num_types_ - 1, storage_ix, storage);
|
||||
|
||||
if (split.num_types_ == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
HistogramBlockType type_histo;
|
||||
for (int i = 1; i < split.type_codes_.size(); ++i) {
|
||||
type_histo.Add(split.type_codes_[i]);
|
||||
}
|
||||
HistogramBlockLength length_histo;
|
||||
for (int i = 0; i < split.lengths_.size(); ++i) {
|
||||
length_histo.Add(BlockLengthPrefix(split.lengths_[i]));
|
||||
}
|
||||
BuildAndStoreEntropyCode(type_histo, 15, split.num_types_ + 2,
|
||||
&code->block_type_code,
|
||||
storage_ix, storage);
|
||||
BuildAndStoreEntropyCode(length_histo, 15, kNumBlockLenPrefixes,
|
||||
&code->block_len_code,
|
||||
storage_ix, storage);
|
||||
EncodeBlockLength(code->block_len_code, split.lengths_[0],
|
||||
storage_ix, storage);
|
||||
}
|
||||
|
||||
void MoveAndEncode(const BlockSplitCode& code,
|
||||
BlockSplitIterator* it,
|
||||
int* storage_ix, uint8_t* storage) {
|
||||
@ -687,11 +231,7 @@ void MoveAndEncode(const BlockSplitCode& code,
|
||||
++it->idx_;
|
||||
it->type_ = it->split_.types_[it->idx_];
|
||||
it->length_ = it->split_.lengths_[it->idx_];
|
||||
int type_code = it->split_.type_codes_[it->idx_];
|
||||
WriteBits(code.block_type_code.depth_[type_code],
|
||||
code.block_type_code.bits_[type_code],
|
||||
storage_ix, storage);
|
||||
EncodeBlockLength(code.block_len_code, it->length_, storage_ix, storage);
|
||||
StoreBlockSwitch(code, it->idx_, storage_ix, storage);
|
||||
}
|
||||
--it->length_;
|
||||
}
|
||||
@ -727,17 +267,14 @@ void BuildMetaBlock(const EncodingParams& params,
|
||||
if (cmds.empty()) {
|
||||
return;
|
||||
}
|
||||
ComputeCommandPrefixes(&mb->cmds,
|
||||
mb->params.num_direct_distance_codes,
|
||||
mb->params.distance_postfix_bits);
|
||||
RecomputeDistancePrefixes(&mb->cmds,
|
||||
mb->params.num_direct_distance_codes,
|
||||
mb->params.distance_postfix_bits);
|
||||
SplitBlock(mb->cmds,
|
||||
&ringbuffer[pos & mask],
|
||||
&mb->literal_split,
|
||||
&mb->command_split,
|
||||
&mb->distance_split);
|
||||
ComputeBlockTypeShortCodes(&mb->literal_split);
|
||||
ComputeBlockTypeShortCodes(&mb->command_split);
|
||||
ComputeBlockTypeShortCodes(&mb->distance_split);
|
||||
|
||||
mb->literal_context_modes.resize(mb->literal_split.num_types_,
|
||||
mb->params.literal_context_mode);
|
||||
@ -786,7 +323,7 @@ size_t MetaBlockLength(const std::vector<Command>& cmds) {
|
||||
size_t length = 0;
|
||||
for (int i = 0; i < cmds.size(); ++i) {
|
||||
const Command& cmd = cmds[i];
|
||||
length += cmd.insert_length_ + cmd.copy_length_;
|
||||
length += cmd.insert_len_ + cmd.copy_len_;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
@ -807,12 +344,24 @@ void StoreMetaBlock(const MetaBlock& mb,
|
||||
BlockSplitCode literal_split_code;
|
||||
BlockSplitCode command_split_code;
|
||||
BlockSplitCode distance_split_code;
|
||||
BuildAndEncodeBlockSplitCode(mb.literal_split, &literal_split_code,
|
||||
storage_ix, storage);
|
||||
BuildAndEncodeBlockSplitCode(mb.command_split, &command_split_code,
|
||||
storage_ix, storage);
|
||||
BuildAndEncodeBlockSplitCode(mb.distance_split, &distance_split_code,
|
||||
storage_ix, storage);
|
||||
BuildAndStoreBlockSplitCode(mb.literal_split.types_,
|
||||
mb.literal_split.lengths_,
|
||||
mb.literal_split.num_types_,
|
||||
9, // quality
|
||||
&literal_split_code,
|
||||
storage_ix, storage);
|
||||
BuildAndStoreBlockSplitCode(mb.command_split.types_,
|
||||
mb.command_split.lengths_,
|
||||
mb.command_split.num_types_,
|
||||
9, // quality
|
||||
&command_split_code,
|
||||
storage_ix, storage);
|
||||
BuildAndStoreBlockSplitCode(mb.distance_split.types_,
|
||||
mb.distance_split.lengths_,
|
||||
mb.distance_split.num_types_,
|
||||
9, // quality
|
||||
&distance_split_code,
|
||||
storage_ix, storage);
|
||||
WriteBits(2, mb.params.distance_postfix_bits, storage_ix, storage);
|
||||
WriteBits(4,
|
||||
mb.params.num_direct_distance_codes >>
|
||||
@ -844,7 +393,7 @@ void StoreMetaBlock(const MetaBlock& mb,
|
||||
const Command& cmd = mb.cmds[i];
|
||||
MoveAndEncode(command_split_code, &command_it, storage_ix, storage);
|
||||
EncodeCommand(cmd, command_codes[command_it.type_], storage_ix, storage);
|
||||
for (int j = 0; j < cmd.insert_length_; ++j) {
|
||||
for (int j = 0; j < cmd.insert_len_; ++j) {
|
||||
MoveAndEncode(literal_split_code, &literal_it, storage_ix, storage);
|
||||
int histogram_idx = literal_it.type_;
|
||||
uint8_t prev_byte = *pos > 0 ? ringbuffer[(*pos - 1) & mask] : 0;
|
||||
@ -859,16 +408,14 @@ void StoreMetaBlock(const MetaBlock& mb,
|
||||
storage_ix, storage);
|
||||
++(*pos);
|
||||
}
|
||||
if (*pos < end_pos && cmd.distance_prefix_ != 0xffff) {
|
||||
if (*pos < end_pos && cmd.cmd_prefix_ >= 128) {
|
||||
MoveAndEncode(distance_split_code, &distance_it, storage_ix, storage);
|
||||
int context = (distance_it.type_ << 2) +
|
||||
((cmd.copy_length_code_ > 4) ? 3 : cmd.copy_length_code_ - 2);
|
||||
int context = (distance_it.type_ << 2) + cmd.DistanceContext();
|
||||
int histogram_index = mb.distance_context_map[context];
|
||||
size_t max_distance = std::min(*pos, (size_t)kMaxBackwardDistance);
|
||||
EncodeCopyDistance(cmd, distance_codes[histogram_index],
|
||||
storage_ix, storage);
|
||||
}
|
||||
*pos += cmd.copy_length_;
|
||||
*pos += cmd.copy_len_;
|
||||
}
|
||||
}
|
||||
|
||||
@ -876,20 +423,19 @@ BrotliCompressor::BrotliCompressor(BrotliParams params)
|
||||
: params_(params),
|
||||
window_bits_(kWindowBits),
|
||||
hashers_(new Hashers()),
|
||||
dist_ringbuffer_idx_(0),
|
||||
input_pos_(0),
|
||||
ringbuffer_(kRingBufferBits, kMetaBlockSizeBits),
|
||||
literal_cost_(1 << kRingBufferBits),
|
||||
storage_ix_(0),
|
||||
storage_(new uint8_t[2 << kMetaBlockSizeBits]) {
|
||||
dist_ringbuffer_[0] = 16;
|
||||
dist_ringbuffer_[1] = 15;
|
||||
dist_ringbuffer_[2] = 11;
|
||||
dist_ringbuffer_[3] = 4;
|
||||
dist_cache_[0] = 4;
|
||||
dist_cache_[1] = 11;
|
||||
dist_cache_[2] = 15;
|
||||
dist_cache_[3] = 16;
|
||||
storage_[0] = 0;
|
||||
switch (params.mode) {
|
||||
case BrotliParams::MODE_TEXT: hash_type_ = Hashers::HASH_15_8_4; break;
|
||||
case BrotliParams::MODE_FONT: hash_type_ = Hashers::HASH_15_8_2; break;
|
||||
case BrotliParams::MODE_TEXT: hash_type_ = 8; break;
|
||||
case BrotliParams::MODE_FONT: hash_type_ = 9; break;
|
||||
default: break;
|
||||
}
|
||||
hashers_->Init(hash_type_);
|
||||
@ -942,7 +488,7 @@ void BrotliCompressor::WriteMetaBlock(const size_t input_size,
|
||||
uint8_t* encoded_buffer) {
|
||||
static const double kMinUTF8Ratio = 0.75;
|
||||
bool utf8_mode = false;
|
||||
std::vector<Command> commands;
|
||||
std::vector<Command> commands((input_size + 1) >> 1);
|
||||
if (input_size > 0) {
|
||||
ringbuffer_.Write(input_buffer, input_size);
|
||||
utf8_mode = IsMostlyUTF8(
|
||||
@ -957,17 +503,26 @@ void BrotliCompressor::WriteMetaBlock(const size_t input_size,
|
||||
kRingBufferMask, kRingBufferMask,
|
||||
ringbuffer_.start(), &literal_cost_[0]);
|
||||
}
|
||||
int last_insert_len = 0;
|
||||
int num_commands = 0;
|
||||
double base_min_score = 8.115;
|
||||
CreateBackwardReferences(
|
||||
input_size, input_pos_,
|
||||
ringbuffer_.start(),
|
||||
&literal_cost_[0],
|
||||
kRingBufferMask, kMaxBackwardDistance,
|
||||
ringbuffer_.start(), kRingBufferMask,
|
||||
&literal_cost_[0], kRingBufferMask,
|
||||
kMaxBackwardDistance,
|
||||
base_min_score,
|
||||
9, // quality
|
||||
hashers_.get(),
|
||||
hash_type_,
|
||||
&commands);
|
||||
ComputeDistanceShortCodes(&commands, input_pos_, kMaxBackwardDistance,
|
||||
dist_ringbuffer_,
|
||||
&dist_ringbuffer_idx_);
|
||||
dist_cache_,
|
||||
&last_insert_len,
|
||||
&commands[0],
|
||||
&num_commands);
|
||||
commands.resize(num_commands);
|
||||
if (last_insert_len > 0) {
|
||||
commands.push_back(Command(last_insert_len));
|
||||
}
|
||||
}
|
||||
EncodingParams params;
|
||||
params.num_direct_distance_codes =
|
||||
@ -1015,7 +570,6 @@ void BrotliCompressor::FinishStream(
|
||||
WriteMetaBlock(0, NULL, true, encoded_size, encoded_buffer);
|
||||
}
|
||||
|
||||
|
||||
int BrotliCompressBuffer(BrotliParams params,
|
||||
size_t input_size,
|
||||
const uint8_t* input_buffer,
|
||||
@ -1049,7 +603,6 @@ int BrotliCompressBuffer(BrotliParams params,
|
||||
*encoded_size += output_size;
|
||||
max_output_size -= output_size;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,6 @@ class BrotliCompressor {
|
||||
// sets *encoded_size to the number of bytes written.
|
||||
void FinishStream(size_t* encoded_size, uint8_t* encoded_buffer);
|
||||
|
||||
|
||||
private:
|
||||
// Initializes the hasher with the hashes of dictionary words.
|
||||
void StoreDictionaryWordHashes();
|
||||
@ -67,12 +66,11 @@ class BrotliCompressor {
|
||||
BrotliParams params_;
|
||||
int window_bits_;
|
||||
std::unique_ptr<Hashers> hashers_;
|
||||
Hashers::Type hash_type_;
|
||||
int dist_ringbuffer_[4];
|
||||
size_t dist_ringbuffer_idx_;
|
||||
int hash_type_;
|
||||
size_t input_pos_;
|
||||
RingBuffer ringbuffer_;
|
||||
std::vector<float> literal_cost_;
|
||||
int dist_cache_[4];
|
||||
int storage_ix_;
|
||||
uint8_t* storage_;
|
||||
static StaticDictionary *static_dictionary_;
|
||||
@ -87,7 +85,6 @@ int BrotliCompressBuffer(BrotliParams params,
|
||||
size_t* encoded_size,
|
||||
uint8_t* encoded_buffer);
|
||||
|
||||
|
||||
} // namespace brotli
|
||||
|
||||
#endif // BROTLI_ENC_ENCODE_H_
|
||||
|
@ -42,7 +42,7 @@ struct HuffmanTree {
|
||||
|
||||
HuffmanTree::HuffmanTree() {}
|
||||
|
||||
// Sort the root nodes, least popular first.
|
||||
// Sort the root nodes, least popular first, break ties by value.
|
||||
bool SortHuffmanTree(const HuffmanTree &v0, const HuffmanTree &v1) {
|
||||
if (v0.total_count_ == v1.total_count_) {
|
||||
return v0.index_right_or_value_ > v1.index_right_or_value_;
|
||||
@ -50,6 +50,11 @@ bool SortHuffmanTree(const HuffmanTree &v0, const HuffmanTree &v1) {
|
||||
return v0.total_count_ < v1.total_count_;
|
||||
}
|
||||
|
||||
// Sort the root nodes, least popular first.
|
||||
bool SortHuffmanTreeFast(const HuffmanTree &v0, const HuffmanTree &v1) {
|
||||
return v0.total_count_ < v1.total_count_;
|
||||
}
|
||||
|
||||
void SetDepth(const HuffmanTree &p,
|
||||
HuffmanTree *pool,
|
||||
uint8_t *depth,
|
||||
@ -83,6 +88,7 @@ void SetDepth(const HuffmanTree &p,
|
||||
void CreateHuffmanTree(const int *data,
|
||||
const int length,
|
||||
const int tree_limit,
|
||||
const int quality,
|
||||
uint8_t *depth) {
|
||||
// For block sizes below 64 kB, we never need to do a second iteration
|
||||
// of this loop. Probably all of our block sizes will be smaller than
|
||||
@ -105,8 +111,11 @@ void CreateHuffmanTree(const int *data,
|
||||
break;
|
||||
}
|
||||
|
||||
std::sort(tree.begin(), tree.end(), SortHuffmanTree);
|
||||
|
||||
if (quality > 1) {
|
||||
std::sort(tree.begin(), tree.end(), SortHuffmanTree);
|
||||
} else {
|
||||
std::sort(tree.begin(), tree.end(), SortHuffmanTreeFast);
|
||||
}
|
||||
// The nodes are:
|
||||
// [0, n): the sorted leaf nodes that we start with.
|
||||
// [n]: we add a sentinel here.
|
||||
@ -158,12 +167,12 @@ void CreateHuffmanTree(const int *data,
|
||||
}
|
||||
}
|
||||
|
||||
void Reverse(uint8_t* v, int start, int end) {
|
||||
void Reverse(std::vector<uint8_t>* v, int start, int end) {
|
||||
--end;
|
||||
while (start < end) {
|
||||
int tmp = v[start];
|
||||
v[start] = v[end];
|
||||
v[end] = tmp;
|
||||
int tmp = (*v)[start];
|
||||
(*v)[start] = (*v)[end];
|
||||
(*v)[end] = tmp;
|
||||
++start;
|
||||
--end;
|
||||
}
|
||||
@ -173,75 +182,65 @@ void WriteHuffmanTreeRepetitions(
|
||||
const int previous_value,
|
||||
const int value,
|
||||
int repetitions,
|
||||
uint8_t* tree,
|
||||
uint8_t* extra_bits,
|
||||
int* tree_size) {
|
||||
std::vector<uint8_t> *tree,
|
||||
std::vector<uint8_t> *extra_bits_data) {
|
||||
if (previous_value != value) {
|
||||
tree[*tree_size] = value;
|
||||
extra_bits[*tree_size] = 0;
|
||||
++(*tree_size);
|
||||
tree->push_back(value);
|
||||
extra_bits_data->push_back(0);
|
||||
--repetitions;
|
||||
}
|
||||
if (repetitions == 7) {
|
||||
tree[*tree_size] = value;
|
||||
extra_bits[*tree_size] = 0;
|
||||
++(*tree_size);
|
||||
tree->push_back(value);
|
||||
extra_bits_data->push_back(0);
|
||||
--repetitions;
|
||||
}
|
||||
if (repetitions < 3) {
|
||||
for (int i = 0; i < repetitions; ++i) {
|
||||
tree[*tree_size] = value;
|
||||
extra_bits[*tree_size] = 0;
|
||||
++(*tree_size);
|
||||
tree->push_back(value);
|
||||
extra_bits_data->push_back(0);
|
||||
}
|
||||
} else {
|
||||
repetitions -= 3;
|
||||
int start = *tree_size;
|
||||
int start = tree->size();
|
||||
while (repetitions >= 0) {
|
||||
tree[*tree_size] = 16;
|
||||
extra_bits[*tree_size] = repetitions & 0x3;
|
||||
++(*tree_size);
|
||||
tree->push_back(16);
|
||||
extra_bits_data->push_back(repetitions & 0x3);
|
||||
repetitions >>= 2;
|
||||
--repetitions;
|
||||
}
|
||||
Reverse(tree, start, *tree_size);
|
||||
Reverse(extra_bits, start, *tree_size);
|
||||
Reverse(tree, start, tree->size());
|
||||
Reverse(extra_bits_data, start, tree->size());
|
||||
}
|
||||
}
|
||||
|
||||
void WriteHuffmanTreeRepetitionsZeros(
|
||||
int repetitions,
|
||||
uint8_t* tree,
|
||||
uint8_t* extra_bits,
|
||||
int* tree_size) {
|
||||
std::vector<uint8_t> *tree,
|
||||
std::vector<uint8_t> *extra_bits_data) {
|
||||
if (repetitions == 11) {
|
||||
tree[*tree_size] = 0;
|
||||
extra_bits[*tree_size] = 0;
|
||||
++(*tree_size);
|
||||
tree->push_back(0);
|
||||
extra_bits_data->push_back(0);
|
||||
--repetitions;
|
||||
}
|
||||
if (repetitions < 3) {
|
||||
for (int i = 0; i < repetitions; ++i) {
|
||||
tree[*tree_size] = 0;
|
||||
extra_bits[*tree_size] = 0;
|
||||
++(*tree_size);
|
||||
tree->push_back(0);
|
||||
extra_bits_data->push_back(0);
|
||||
}
|
||||
} else {
|
||||
repetitions -= 3;
|
||||
int start = *tree_size;
|
||||
int start = tree->size();
|
||||
while (repetitions >= 0) {
|
||||
tree[*tree_size] = 17;
|
||||
extra_bits[*tree_size] = repetitions & 0x7;
|
||||
++(*tree_size);
|
||||
tree->push_back(17);
|
||||
extra_bits_data->push_back(repetitions & 0x7);
|
||||
repetitions >>= 3;
|
||||
--repetitions;
|
||||
}
|
||||
Reverse(tree, start, *tree_size);
|
||||
Reverse(extra_bits, start, *tree_size);
|
||||
Reverse(tree, start, tree->size());
|
||||
Reverse(extra_bits_data, start, tree->size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int OptimizeHuffmanCountsForRle(int length, int* counts) {
|
||||
int stride;
|
||||
int limit;
|
||||
@ -371,7 +370,6 @@ int OptimizeHuffmanCountsForRle(int length, int* counts) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void DecideOverRleUse(const uint8_t* depth, const int length,
|
||||
bool *use_rle_for_non_zero,
|
||||
bool *use_rle_for_zero) {
|
||||
@ -379,20 +377,10 @@ static void DecideOverRleUse(const uint8_t* depth, const int length,
|
||||
int total_reps_non_zero = 0;
|
||||
int count_reps_zero = 0;
|
||||
int count_reps_non_zero = 0;
|
||||
int new_length = length;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (depth[length - i - 1] == 0) {
|
||||
--new_length;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < new_length;) {
|
||||
for (uint32_t i = 0; i < length;) {
|
||||
const int value = depth[i];
|
||||
int reps = 1;
|
||||
// Find rle coding for longer codes.
|
||||
// Shorter codes seem not to benefit from rle.
|
||||
for (uint32_t k = i + 1; k < new_length && depth[k] == value; ++k) {
|
||||
for (uint32_t k = i + 1; k < length && depth[k] == value; ++k) {
|
||||
++reps;
|
||||
}
|
||||
if (reps >= 3 && value == 0) {
|
||||
@ -411,48 +399,51 @@ static void DecideOverRleUse(const uint8_t* depth, const int length,
|
||||
*use_rle_for_zero = total_reps_zero > 2;
|
||||
}
|
||||
|
||||
|
||||
void WriteHuffmanTree(const uint8_t* depth, const int length,
|
||||
uint8_t* tree,
|
||||
uint8_t* extra_bits_data,
|
||||
int* huffman_tree_size) {
|
||||
void WriteHuffmanTree(const uint8_t* depth,
|
||||
uint32_t length,
|
||||
std::vector<uint8_t> *tree,
|
||||
std::vector<uint8_t> *extra_bits_data) {
|
||||
int previous_value = 8;
|
||||
|
||||
// Throw away trailing zeros.
|
||||
int new_length = length;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (depth[length - i - 1] == 0) {
|
||||
--new_length;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// First gather statistics on if it is a good idea to do rle.
|
||||
bool use_rle_for_non_zero;
|
||||
bool use_rle_for_zero;
|
||||
DecideOverRleUse(depth, length, &use_rle_for_non_zero, &use_rle_for_zero);
|
||||
bool use_rle_for_non_zero = false;
|
||||
bool use_rle_for_zero = false;
|
||||
if (length > 50) {
|
||||
// Find rle coding for longer codes.
|
||||
// Shorter codes seem not to benefit from rle.
|
||||
DecideOverRleUse(depth, new_length,
|
||||
&use_rle_for_non_zero, &use_rle_for_zero);
|
||||
}
|
||||
|
||||
// Actual rle coding.
|
||||
for (uint32_t i = 0; i < length;) {
|
||||
for (uint32_t i = 0; i < new_length;) {
|
||||
const int value = depth[i];
|
||||
int reps = 1;
|
||||
if (length > 50) {
|
||||
// Find rle coding for longer codes.
|
||||
// Shorter codes seem not to benefit from rle.
|
||||
if ((value != 0 && use_rle_for_non_zero) ||
|
||||
(value == 0 && use_rle_for_zero)) {
|
||||
for (uint32_t k = i + 1; k < length && depth[k] == value; ++k) {
|
||||
++reps;
|
||||
}
|
||||
if ((value != 0 && use_rle_for_non_zero) ||
|
||||
(value == 0 && use_rle_for_zero)) {
|
||||
for (uint32_t k = i + 1; k < new_length && depth[k] == value; ++k) {
|
||||
++reps;
|
||||
}
|
||||
}
|
||||
if (value == 0) {
|
||||
WriteHuffmanTreeRepetitionsZeros(reps, tree, extra_bits_data,
|
||||
huffman_tree_size);
|
||||
WriteHuffmanTreeRepetitionsZeros(reps, tree, extra_bits_data);
|
||||
} else {
|
||||
WriteHuffmanTreeRepetitions(previous_value, value, reps, tree,
|
||||
extra_bits_data, huffman_tree_size);
|
||||
WriteHuffmanTreeRepetitions(previous_value,
|
||||
value, reps, tree, extra_bits_data);
|
||||
previous_value = value;
|
||||
}
|
||||
i += reps;
|
||||
}
|
||||
// Throw away trailing zeros.
|
||||
for (; *huffman_tree_size > 0; --(*huffman_tree_size)) {
|
||||
if (tree[*huffman_tree_size - 1] > 0 && tree[*huffman_tree_size - 1] < 17) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include "./histogram.h"
|
||||
#include "./prefix.h"
|
||||
|
||||
@ -36,6 +37,7 @@ namespace brotli {
|
||||
void CreateHuffmanTree(const int *data,
|
||||
const int length,
|
||||
const int tree_limit,
|
||||
const int quality,
|
||||
uint8_t *depth);
|
||||
|
||||
// Change the population counts in a way that the consequent
|
||||
@ -46,14 +48,13 @@ void CreateHuffmanTree(const int *data,
|
||||
// counts contains the population counts.
|
||||
int OptimizeHuffmanCountsForRle(int length, int* counts);
|
||||
|
||||
|
||||
// Write a huffman tree from bit depths into the bitstream representation
|
||||
// of a Huffman tree. The generated Huffman tree is to be compressed once
|
||||
// more using a Huffman tree
|
||||
void WriteHuffmanTree(const uint8_t* depth, const int length,
|
||||
uint8_t* tree,
|
||||
uint8_t* extra_bits_data,
|
||||
int* huffman_tree_size);
|
||||
void WriteHuffmanTree(const uint8_t* depth,
|
||||
uint32_t num,
|
||||
std::vector<uint8_t> *tree,
|
||||
std::vector<uint8_t> *extra_bits_data);
|
||||
|
||||
// Get the actual bit values for a tree of bit depths.
|
||||
void ConvertBitDepthsToSymbols(const uint8_t *depth, int len, uint16_t *bits);
|
||||
@ -70,34 +71,6 @@ struct EntropyCode {
|
||||
int symbols_[4];
|
||||
};
|
||||
|
||||
template<int kSize>
|
||||
void BuildEntropyCode(const Histogram<kSize>& histogram,
|
||||
const int tree_limit,
|
||||
const int alphabet_size,
|
||||
EntropyCode<kSize>* code) {
|
||||
memset(code->depth_, 0, sizeof(code->depth_));
|
||||
memset(code->bits_, 0, sizeof(code->bits_));
|
||||
memset(code->symbols_, 0, sizeof(code->symbols_));
|
||||
code->count_ = 0;
|
||||
if (histogram.total_count_ == 0) return;
|
||||
for (int i = 0; i < kSize; ++i) {
|
||||
if (histogram.data_[i] > 0) {
|
||||
if (code->count_ < 4) code->symbols_[code->count_] = i;
|
||||
++code->count_;
|
||||
}
|
||||
}
|
||||
if (alphabet_size >= 50 && code->count_ >= 16) {
|
||||
int counts[kSize];
|
||||
memcpy(counts, &histogram.data_[0], sizeof(counts[0]) * kSize);
|
||||
OptimizeHuffmanCountsForRle(alphabet_size, counts);
|
||||
CreateHuffmanTree(counts, alphabet_size, tree_limit, &code->depth_[0]);
|
||||
} else {
|
||||
CreateHuffmanTree(&histogram.data_[0], alphabet_size, tree_limit,
|
||||
&code->depth_[0]);
|
||||
}
|
||||
ConvertBitDepthsToSymbols(&code->depth_[0], alphabet_size, &code->bits_[0]);
|
||||
}
|
||||
|
||||
static const int kCodeLengthCodes = 18;
|
||||
|
||||
// Literal entropy code.
|
||||
|
@ -46,6 +46,16 @@ inline int Log2Floor(uint32_t n) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int Log2FloorNonZero(uint32_t n) {
|
||||
#ifdef __GNUC__
|
||||
return 31 ^ __builtin_clz(n);
|
||||
#else
|
||||
unsigned int result = 0;
|
||||
while (n >>= 1) result++;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return ceiling(log2(n)) for positive integer n. Returns -1 iff n == 0.
|
||||
inline int Log2Ceiling(uint32_t n) {
|
||||
int floor = Log2Floor(n);
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "./port.h"
|
||||
|
||||
namespace brotli {
|
||||
|
417
enc/hash.h
417
enc/hash.h
@ -31,10 +31,18 @@
|
||||
#include "./fast_log.h"
|
||||
#include "./find_match_length.h"
|
||||
#include "./port.h"
|
||||
#include "./prefix.h"
|
||||
#include "./static_dict.h"
|
||||
|
||||
namespace brotli {
|
||||
|
||||
static const int kDistanceCacheIndex[] = {
|
||||
0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
|
||||
};
|
||||
static const int kDistanceCacheOffset[] = {
|
||||
0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3
|
||||
};
|
||||
|
||||
// kHashMul32 multiplier has these properties:
|
||||
// * The multiplier must be odd. Otherwise we may lose the highest bit.
|
||||
// * No long streaks of 1s or 0s.
|
||||
@ -75,59 +83,194 @@ inline uint32_t Hash(const uint8_t *data) {
|
||||
// when it is not much longer and the bit cost for encoding it is more
|
||||
// than the saved literals.
|
||||
inline double BackwardReferenceScore(double average_cost,
|
||||
double start_cost4,
|
||||
double start_cost3,
|
||||
double start_cost2,
|
||||
int copy_length,
|
||||
int backward_reference_offset) {
|
||||
double retval = 0;
|
||||
switch (copy_length) {
|
||||
case 2: retval = start_cost2; break;
|
||||
case 3: retval = start_cost3; break;
|
||||
default: retval = start_cost4 + (copy_length - 4) * average_cost; break;
|
||||
}
|
||||
retval -= 1.20 * Log2Floor(backward_reference_offset);
|
||||
return retval;
|
||||
return (copy_length * average_cost -
|
||||
1.20 * Log2Floor(backward_reference_offset));
|
||||
}
|
||||
|
||||
inline double BackwardReferenceScoreUsingLastDistance(double average_cost,
|
||||
double start_cost4,
|
||||
double start_cost3,
|
||||
double start_cost2,
|
||||
int copy_length,
|
||||
int distance_short_code) {
|
||||
double retval = 0;
|
||||
switch (copy_length) {
|
||||
case 2: retval = start_cost2; break;
|
||||
case 3: retval = start_cost3; break;
|
||||
default: retval = start_cost4 + (copy_length - 4) * average_cost; break;
|
||||
}
|
||||
static const double kDistanceShortCodeBitCost[16] = {
|
||||
-0.6, 0.95, 1.17, 1.27,
|
||||
0.93, 0.93, 0.96, 0.96, 0.99, 0.99,
|
||||
1.05, 1.05, 1.15, 1.15, 1.25, 1.25
|
||||
};
|
||||
retval -= kDistanceShortCodeBitCost[distance_short_code];
|
||||
return retval;
|
||||
return (average_cost * copy_length
|
||||
- kDistanceShortCodeBitCost[distance_short_code]);
|
||||
}
|
||||
|
||||
// A (forgetful) hash table to the data seen by the compressor, to
|
||||
// help create backward references to previous data.
|
||||
//
|
||||
// This is a hash map of fixed size (kBucketSize). Starting from the
|
||||
// given index, kBucketSweep buckets are used to store values of a key.
|
||||
template <int kBucketBits, int kBucketSweep>
|
||||
class HashLongestMatchQuickly {
|
||||
public:
|
||||
HashLongestMatchQuickly() {
|
||||
Reset();
|
||||
}
|
||||
void Reset() {
|
||||
// It is not strictly necessary to fill this buffer here, but
|
||||
// not filling will make the results of the compression stochastic
|
||||
// (but correct). This is because random data would cause the
|
||||
// system to find accidentally good backward references here and there.
|
||||
std::fill(&buckets_[0],
|
||||
&buckets_[sizeof(buckets_) / sizeof(buckets_[0])],
|
||||
0);
|
||||
}
|
||||
// Look at 4 bytes at data.
|
||||
// Compute a hash from these, and store the value somewhere within
|
||||
// [ix .. ix+3].
|
||||
inline void Store(const uint8_t *data, const int ix) {
|
||||
const uint32_t key = Hash<kBucketBits, 4>(data);
|
||||
// Wiggle the value with the bucket sweep range.
|
||||
const uint32_t off = (static_cast<uint32_t>(ix) >> 3) % kBucketSweep;
|
||||
buckets_[key + off] = ix;
|
||||
}
|
||||
|
||||
// Store hashes for a range of data.
|
||||
void StoreHashes(const uint8_t *data, size_t len, int startix, int mask) {
|
||||
for (int p = 0; p < len; ++p) {
|
||||
Store(&data[p & mask], startix + p);
|
||||
}
|
||||
}
|
||||
|
||||
bool HasStaticDictionary() const { return false; }
|
||||
|
||||
// Find a longest backward match of &ring_buffer[cur_ix & ring_buffer_mask]
|
||||
// up to the length of max_length.
|
||||
//
|
||||
// Does not look for matches longer than max_length.
|
||||
// Does not look for matches further away than max_backward.
|
||||
// Writes the best found match length into best_len_out.
|
||||
// Writes the index (&data[index]) of the start of the best match into
|
||||
// best_distance_out.
|
||||
inline bool FindLongestMatch(const uint8_t * __restrict ring_buffer,
|
||||
const size_t ring_buffer_mask,
|
||||
const float* __restrict literal_cost,
|
||||
const size_t literal_cost_mask,
|
||||
const double average_cost,
|
||||
const int* __restrict distance_cache,
|
||||
const uint32_t cur_ix,
|
||||
const uint32_t max_length,
|
||||
const uint32_t max_backward,
|
||||
int * __restrict best_len_out,
|
||||
int * __restrict best_len_code_out,
|
||||
int * __restrict best_distance_out,
|
||||
double* __restrict best_score_out) {
|
||||
const int best_len_in = *best_len_out;
|
||||
const int cur_ix_masked = cur_ix & ring_buffer_mask;
|
||||
int compare_char = ring_buffer[cur_ix_masked + best_len_in];
|
||||
double best_score = *best_score_out;
|
||||
int best_len = best_len_in;
|
||||
int backward = distance_cache[0];
|
||||
size_t prev_ix = cur_ix - backward;
|
||||
bool match_found = false;
|
||||
if (prev_ix < cur_ix) {
|
||||
prev_ix &= ring_buffer_mask;
|
||||
if (compare_char == ring_buffer[prev_ix + best_len]) {
|
||||
int len = FindMatchLengthWithLimit(&ring_buffer[prev_ix],
|
||||
&ring_buffer[cur_ix_masked],
|
||||
max_length);
|
||||
if (len >= 4) {
|
||||
best_score = BackwardReferenceScoreUsingLastDistance(average_cost,
|
||||
len, 0);
|
||||
best_len = len;
|
||||
*best_len_out = len;
|
||||
*best_len_code_out = len;
|
||||
*best_distance_out = backward;
|
||||
*best_score_out = best_score;
|
||||
compare_char = ring_buffer[cur_ix_masked + best_len];
|
||||
if (kBucketSweep == 1) {
|
||||
return true;
|
||||
} else {
|
||||
match_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const uint32_t key = Hash<kBucketBits, 4>(&ring_buffer[cur_ix_masked]);
|
||||
if (kBucketSweep == 1) {
|
||||
// Only one to look for, don't bother to prepare for a loop.
|
||||
prev_ix = buckets_[key];
|
||||
backward = cur_ix - prev_ix;
|
||||
prev_ix &= ring_buffer_mask;
|
||||
if (compare_char != ring_buffer[prev_ix + best_len_in]) {
|
||||
return false;
|
||||
}
|
||||
if (PREDICT_FALSE(backward == 0 || backward > max_backward)) {
|
||||
return false;
|
||||
}
|
||||
const int len = FindMatchLengthWithLimit(&ring_buffer[prev_ix],
|
||||
&ring_buffer[cur_ix_masked],
|
||||
max_length);
|
||||
if (len >= 4) {
|
||||
*best_len_out = len;
|
||||
*best_len_code_out = len;
|
||||
*best_distance_out = backward;
|
||||
*best_score_out = BackwardReferenceScore(average_cost, len, backward);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
uint32_t *bucket = buckets_ + key;
|
||||
prev_ix = *bucket++;
|
||||
for (int i = 0; i < kBucketSweep; ++i, prev_ix = *bucket++) {
|
||||
const int backward = cur_ix - prev_ix;
|
||||
prev_ix &= ring_buffer_mask;
|
||||
if (compare_char != ring_buffer[prev_ix + best_len]) {
|
||||
continue;
|
||||
}
|
||||
if (PREDICT_FALSE(backward == 0 || backward > max_backward)) {
|
||||
continue;
|
||||
}
|
||||
const int len =
|
||||
FindMatchLengthWithLimit(&ring_buffer[prev_ix],
|
||||
&ring_buffer[cur_ix_masked],
|
||||
max_length);
|
||||
if (len >= 4) {
|
||||
const double score = BackwardReferenceScore(average_cost,
|
||||
len, backward);
|
||||
if (best_score < score) {
|
||||
best_score = score;
|
||||
best_len = len;
|
||||
*best_len_out = best_len;
|
||||
*best_len_code_out = best_len;
|
||||
*best_distance_out = backward;
|
||||
*best_score_out = score;
|
||||
compare_char = ring_buffer[cur_ix_masked + best_len];
|
||||
match_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return match_found;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static const uint32_t kBucketSize = 1 << kBucketBits;
|
||||
uint32_t buckets_[kBucketSize + kBucketSweep];
|
||||
};
|
||||
|
||||
// A (forgetful) hash table to the data seen by the compressor, to
|
||||
// help create backward references to previous data.
|
||||
//
|
||||
// This is a hash map of fixed size (kBucketSize) to a ring buffer of
|
||||
// fixed size (kBlockSize). The ring buffer contains the last kBlockSize
|
||||
// index positions of the given hash key in the compressed data.
|
||||
template <int kBucketBits, int kBlockBits, int kMinLength>
|
||||
template <int kBucketBits,
|
||||
int kBlockBits,
|
||||
int kMinLength,
|
||||
int kNumLastDistancesToCheck,
|
||||
bool kUseCostModel,
|
||||
bool kUseDictionary>
|
||||
class HashLongestMatch {
|
||||
public:
|
||||
HashLongestMatch()
|
||||
: last_distance1_(4),
|
||||
last_distance2_(11),
|
||||
last_distance3_(15),
|
||||
last_distance4_(16),
|
||||
insert_length_(0),
|
||||
average_cost_(5.4),
|
||||
static_dict_(NULL) {
|
||||
HashLongestMatch() : static_dict_(NULL) {
|
||||
Reset();
|
||||
}
|
||||
void Reset() {
|
||||
@ -166,72 +309,58 @@ class HashLongestMatch {
|
||||
// into best_distance_out.
|
||||
// Write the score of the best match into best_score_out.
|
||||
bool FindLongestMatch(const uint8_t * __restrict data,
|
||||
const float * __restrict literal_cost,
|
||||
const size_t ring_buffer_mask,
|
||||
const float * __restrict literal_cost,
|
||||
const size_t literal_cost_mask,
|
||||
const double average_cost,
|
||||
const int* __restrict distance_cache,
|
||||
const uint32_t cur_ix,
|
||||
uint32_t max_length,
|
||||
const uint32_t max_backward,
|
||||
size_t * __restrict best_len_out,
|
||||
size_t * __restrict best_len_code_out,
|
||||
size_t * __restrict best_distance_out,
|
||||
double * __restrict best_score_out,
|
||||
bool * __restrict in_dictionary) {
|
||||
*in_dictionary = true;
|
||||
int * __restrict best_len_out,
|
||||
int * __restrict best_len_code_out,
|
||||
int * __restrict best_distance_out,
|
||||
double * __restrict best_score_out) {
|
||||
*best_len_code_out = 0;
|
||||
const size_t cur_ix_masked = cur_ix & ring_buffer_mask;
|
||||
const double start_cost4 = literal_cost == NULL ? 20 :
|
||||
literal_cost[cur_ix_masked] +
|
||||
literal_cost[(cur_ix + 1) & ring_buffer_mask] +
|
||||
literal_cost[(cur_ix + 2) & ring_buffer_mask] +
|
||||
literal_cost[(cur_ix + 3) & ring_buffer_mask];
|
||||
const double start_cost3 = literal_cost == NULL ? 15 :
|
||||
literal_cost[cur_ix_masked] +
|
||||
literal_cost[(cur_ix + 1) & ring_buffer_mask] +
|
||||
literal_cost[(cur_ix + 2) & ring_buffer_mask] + 0.3;
|
||||
double start_cost2 = literal_cost == NULL ? 10 :
|
||||
literal_cost[cur_ix_masked] +
|
||||
literal_cost[(cur_ix + 1) & ring_buffer_mask] + 1.2;
|
||||
double start_cost_diff4 = 0.0;
|
||||
double start_cost_diff3 = 0.0;
|
||||
double start_cost_diff2 = 0.0;
|
||||
if (kUseCostModel) {
|
||||
start_cost_diff4 = literal_cost == NULL ? 0 :
|
||||
literal_cost[cur_ix & literal_cost_mask] +
|
||||
literal_cost[(cur_ix + 1) & literal_cost_mask] +
|
||||
literal_cost[(cur_ix + 2) & literal_cost_mask] +
|
||||
literal_cost[(cur_ix + 3) & literal_cost_mask] -
|
||||
4 * average_cost;
|
||||
start_cost_diff3 = literal_cost == NULL ? 0 :
|
||||
literal_cost[cur_ix & literal_cost_mask] +
|
||||
literal_cost[(cur_ix + 1) & literal_cost_mask] +
|
||||
literal_cost[(cur_ix + 2) & literal_cost_mask] -
|
||||
3 * average_cost + 0.3;
|
||||
start_cost_diff2 = literal_cost == NULL ? 0 :
|
||||
literal_cost[cur_ix & literal_cost_mask] +
|
||||
literal_cost[(cur_ix + 1) & literal_cost_mask] -
|
||||
2 * average_cost + 1.2;
|
||||
}
|
||||
bool match_found = false;
|
||||
// Don't accept a short copy from far away.
|
||||
double best_score = 8.115;
|
||||
if (insert_length_ < 4) {
|
||||
double cost_diff[4] = { 0.10, 0.04, 0.02, 0.01 };
|
||||
best_score += cost_diff[insert_length_];
|
||||
}
|
||||
size_t best_len = *best_len_out;
|
||||
double best_score = *best_score_out;
|
||||
int best_len = *best_len_out;
|
||||
*best_len_out = 0;
|
||||
size_t best_ix = 1;
|
||||
// Try last distance first.
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
size_t prev_ix = cur_ix;
|
||||
switch(i) {
|
||||
case 0: prev_ix -= last_distance1_; break;
|
||||
case 1: prev_ix -= last_distance2_; break;
|
||||
case 2: prev_ix -= last_distance3_; break;
|
||||
case 3: prev_ix -= last_distance4_; break;
|
||||
|
||||
case 4: prev_ix -= last_distance1_ - 1; break;
|
||||
case 5: prev_ix -= last_distance1_ + 1; break;
|
||||
case 6: prev_ix -= last_distance1_ - 2; break;
|
||||
case 7: prev_ix -= last_distance1_ + 2; break;
|
||||
case 8: prev_ix -= last_distance1_ - 3; break;
|
||||
case 9: prev_ix -= last_distance1_ + 3; break;
|
||||
|
||||
case 10: prev_ix -= last_distance2_ - 1; break;
|
||||
case 11: prev_ix -= last_distance2_ + 1; break;
|
||||
case 12: prev_ix -= last_distance2_ - 2; break;
|
||||
case 13: prev_ix -= last_distance2_ + 2; break;
|
||||
case 14: prev_ix -= last_distance2_ - 3; break;
|
||||
case 15: prev_ix -= last_distance2_ + 3; break;
|
||||
}
|
||||
for (int i = 0; i < kNumLastDistancesToCheck; ++i) {
|
||||
const int idx = kDistanceCacheIndex[i];
|
||||
const int backward = distance_cache[idx] + kDistanceCacheOffset[i];
|
||||
size_t prev_ix = cur_ix - backward;
|
||||
if (prev_ix >= cur_ix) {
|
||||
continue;
|
||||
}
|
||||
const size_t backward = cur_ix - prev_ix;
|
||||
if (PREDICT_FALSE(backward > max_backward)) {
|
||||
continue;
|
||||
}
|
||||
prev_ix &= ring_buffer_mask;
|
||||
|
||||
if (cur_ix_masked + best_len > ring_buffer_mask ||
|
||||
prev_ix + best_len > ring_buffer_mask ||
|
||||
data[cur_ix_masked + best_len] != data[prev_ix + best_len]) {
|
||||
@ -245,29 +374,30 @@ class HashLongestMatch {
|
||||
// Comparing for >= 2 does not change the semantics, but just saves for
|
||||
// a few unnecessary binary logarithms in backward reference score,
|
||||
// since we are not interested in such short matches.
|
||||
const double score = BackwardReferenceScoreUsingLastDistance(
|
||||
average_cost_,
|
||||
start_cost4,
|
||||
start_cost3,
|
||||
start_cost2,
|
||||
len, i);
|
||||
double score = BackwardReferenceScoreUsingLastDistance(
|
||||
average_cost, len, i);
|
||||
if (kUseCostModel) {
|
||||
switch (len) {
|
||||
case 2: score += start_cost_diff2; break;
|
||||
case 3: score += start_cost_diff3; break;
|
||||
default: score += start_cost_diff4;
|
||||
}
|
||||
}
|
||||
if (best_score < score) {
|
||||
best_score = score;
|
||||
best_len = len;
|
||||
best_ix = backward;
|
||||
*best_len_out = best_len;
|
||||
*best_len_code_out = best_len;
|
||||
*best_distance_out = best_ix;
|
||||
*best_distance_out = backward;
|
||||
*best_score_out = best_score;
|
||||
match_found = true;
|
||||
*in_dictionary = backward > max_backward;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kMinLength == 2) {
|
||||
int stop = int(cur_ix) - 64;
|
||||
if (stop < 0) { stop = 0; }
|
||||
start_cost2 -= 1.0;
|
||||
start_cost_diff2 -= 1.0;
|
||||
for (int i = cur_ix - 1; i > stop; --i) {
|
||||
size_t prev_ix = i;
|
||||
const size_t backward = cur_ix - prev_ix;
|
||||
@ -280,15 +410,15 @@ class HashLongestMatch {
|
||||
continue;
|
||||
}
|
||||
int len = 2;
|
||||
const double score = start_cost2 - 2.3 * Log2Floor(backward);
|
||||
const double score =
|
||||
average_cost * 2 - 2.3 * Log2Floor(backward) + start_cost_diff2;
|
||||
|
||||
if (best_score < score) {
|
||||
best_score = score;
|
||||
best_len = len;
|
||||
best_ix = backward;
|
||||
*best_len_out = best_len;
|
||||
*best_len_code_out = best_len;
|
||||
*best_distance_out = best_ix;
|
||||
*best_distance_out = backward;
|
||||
match_found = true;
|
||||
}
|
||||
}
|
||||
@ -316,26 +446,24 @@ class HashLongestMatch {
|
||||
// Comparing for >= 3 does not change the semantics, but just saves
|
||||
// for a few unnecessary binary logarithms in backward reference
|
||||
// score, since we are not interested in such short matches.
|
||||
const double score = BackwardReferenceScore(average_cost_,
|
||||
start_cost4,
|
||||
start_cost3,
|
||||
start_cost2,
|
||||
len, backward);
|
||||
double score = BackwardReferenceScore(average_cost,
|
||||
len, backward);
|
||||
if (kUseCostModel) {
|
||||
score += (len >= 4) ? start_cost_diff4 : start_cost_diff3;
|
||||
}
|
||||
if (best_score < score) {
|
||||
best_score = score;
|
||||
best_len = len;
|
||||
best_ix = backward;
|
||||
*best_len_out = best_len;
|
||||
*best_len_code_out = best_len;
|
||||
*best_distance_out = best_ix;
|
||||
*best_distance_out = backward;
|
||||
*best_score_out = best_score;
|
||||
match_found = true;
|
||||
*in_dictionary = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (static_dict_ != NULL) {
|
||||
if (kUseDictionary && static_dict_ != NULL) {
|
||||
// We decide based on first 4 bytes how many bytes to test for.
|
||||
int prefix = BROTLI_UNALIGNED_LOAD32(&data[cur_ix_masked]);
|
||||
int maxlen = static_dict_->GetLength(prefix);
|
||||
@ -346,21 +474,17 @@ class HashLongestMatch {
|
||||
int word_id;
|
||||
if (static_dict_->Get(snippet, ©_len_code, &word_id)) {
|
||||
const size_t backward = max_backward + word_id + 1;
|
||||
const double score = BackwardReferenceScore(average_cost_,
|
||||
start_cost4,
|
||||
start_cost3,
|
||||
start_cost2,
|
||||
len, backward);
|
||||
const double score = (BackwardReferenceScore(average_cost,
|
||||
len, backward) +
|
||||
start_cost_diff4);
|
||||
if (best_score < score) {
|
||||
best_score = score;
|
||||
best_len = len;
|
||||
best_ix = backward;
|
||||
*best_len_out = best_len;
|
||||
*best_len_code_out = copy_len_code;
|
||||
*best_distance_out = best_ix;
|
||||
*best_distance_out = backward;
|
||||
*best_score_out = best_score;
|
||||
match_found = true;
|
||||
*in_dictionary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,21 +492,6 @@ class HashLongestMatch {
|
||||
return match_found;
|
||||
}
|
||||
|
||||
void set_last_distance(int v) {
|
||||
if (last_distance1_ != v) {
|
||||
last_distance4_ = last_distance3_;
|
||||
last_distance3_ = last_distance2_;
|
||||
last_distance2_ = last_distance1_;
|
||||
last_distance1_ = v;
|
||||
}
|
||||
}
|
||||
|
||||
int last_distance() const { return last_distance1_; }
|
||||
|
||||
void set_insert_length(int v) { insert_length_ = v; }
|
||||
|
||||
void set_average_cost(double v) { average_cost_ = v; }
|
||||
|
||||
private:
|
||||
// Number of hash buckets.
|
||||
static const uint32_t kBucketSize = 1 << kBucketBits;
|
||||
@ -400,46 +509,48 @@ class HashLongestMatch {
|
||||
// Buckets containing kBlockSize of backward references.
|
||||
int buckets_[kBucketSize][kBlockSize];
|
||||
|
||||
int last_distance1_;
|
||||
int last_distance2_;
|
||||
int last_distance3_;
|
||||
int last_distance4_;
|
||||
|
||||
// Cost adjustment for how many literals we are planning to insert
|
||||
// anyway.
|
||||
int insert_length_;
|
||||
|
||||
double average_cost_;
|
||||
|
||||
const StaticDictionary *static_dict_;
|
||||
};
|
||||
|
||||
struct Hashers {
|
||||
enum Type {
|
||||
HASH_15_8_4 = 0,
|
||||
HASH_15_8_2 = 1,
|
||||
};
|
||||
typedef HashLongestMatchQuickly<16, 1> H1;
|
||||
typedef HashLongestMatchQuickly<17, 4> H2;
|
||||
typedef HashLongestMatch<14, 4, 4, 4, false, false> H3;
|
||||
typedef HashLongestMatch<14, 5, 4, 4, false, false> H4;
|
||||
typedef HashLongestMatch<15, 6, 4, 10, false, false> H5;
|
||||
typedef HashLongestMatch<15, 7, 4, 10, false, false> H6;
|
||||
typedef HashLongestMatch<15, 8, 4, 16, true, false> H7;
|
||||
typedef HashLongestMatch<15, 8, 4, 16, true, true> H8;
|
||||
typedef HashLongestMatch<15, 8, 2, 16, true, false> H9;
|
||||
|
||||
void Init(Type type) {
|
||||
void Init(int type) {
|
||||
switch (type) {
|
||||
case HASH_15_8_4:
|
||||
hash_15_8_4.reset(new HashLongestMatch<15, 8, 4>());
|
||||
break;
|
||||
case HASH_15_8_2:
|
||||
hash_15_8_2.reset(new HashLongestMatch<15, 8, 2>());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 1: hash_h1.reset(new H1); break;
|
||||
case 2: hash_h2.reset(new H2); break;
|
||||
case 3: hash_h3.reset(new H3); break;
|
||||
case 4: hash_h4.reset(new H4); break;
|
||||
case 5: hash_h5.reset(new H5); break;
|
||||
case 6: hash_h6.reset(new H6); break;
|
||||
case 7: hash_h7.reset(new H7); break;
|
||||
case 8: hash_h8.reset(new H8); break;
|
||||
case 9: hash_h9.reset(new H9); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void SetStaticDictionary(const StaticDictionary *dict) {
|
||||
if (hash_15_8_4.get() != NULL) hash_15_8_4->SetStaticDictionary(dict);
|
||||
if (hash_15_8_2.get() != NULL) hash_15_8_2->SetStaticDictionary(dict);
|
||||
if (hash_h8.get() != NULL) hash_h8->SetStaticDictionary(dict);
|
||||
}
|
||||
|
||||
std::unique_ptr<HashLongestMatch<15, 8, 4> > hash_15_8_4;
|
||||
std::unique_ptr<HashLongestMatch<15, 8, 2> > hash_15_8_2;
|
||||
std::unique_ptr<H1> hash_h1;
|
||||
std::unique_ptr<H2> hash_h2;
|
||||
std::unique_ptr<H3> hash_h3;
|
||||
std::unique_ptr<H4> hash_h4;
|
||||
std::unique_ptr<H5> hash_h5;
|
||||
std::unique_ptr<H6> hash_h6;
|
||||
std::unique_ptr<H7> hash_h7;
|
||||
std::unique_ptr<H8> hash_h8;
|
||||
std::unique_ptr<H9> hash_h9;
|
||||
};
|
||||
|
||||
} // namespace brotli
|
||||
|
@ -45,8 +45,8 @@ void BuildHistograms(
|
||||
const Command &cmd = cmds[i];
|
||||
insert_and_copy_it.Next();
|
||||
(*insert_and_copy_histograms)[insert_and_copy_it.type_].Add(
|
||||
cmd.command_prefix_);
|
||||
for (int j = 0; j < cmd.insert_length_; ++j) {
|
||||
cmd.cmd_prefix_);
|
||||
for (int j = 0; j < cmd.insert_len_; ++j) {
|
||||
literal_it.Next();
|
||||
uint8_t prev_byte = pos > 0 ? ringbuffer[(pos - 1) & mask] : 0;
|
||||
uint8_t prev_byte2 = pos > 1 ? ringbuffer[(pos - 2) & mask] : 0;
|
||||
@ -55,12 +55,12 @@ void BuildHistograms(
|
||||
(*literal_histograms)[context].Add(ringbuffer[pos & mask]);
|
||||
++pos;
|
||||
}
|
||||
pos += cmd.copy_length_;
|
||||
if (cmd.copy_length_ > 0 && cmd.distance_prefix_ != 0xffff) {
|
||||
pos += cmd.copy_len_;
|
||||
if (cmd.copy_len_ > 0 && cmd.cmd_prefix_ >= 128) {
|
||||
dist_it.Next();
|
||||
int context = (dist_it.type_ << kDistanceContextBits) +
|
||||
((cmd.copy_length_code_ > 4) ? 3 : cmd.copy_length_code_ - 2);
|
||||
(*copy_dist_histograms)[context].Add(cmd.distance_prefix_);
|
||||
cmd.DistanceContext();
|
||||
(*copy_dist_histograms)[context].Add(cmd.dist_prefix_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,7 @@ void BuildLiteralHistogramsForBlockType(
|
||||
BlockSplitIterator literal_it(literal_split);
|
||||
for (int i = 0; i < cmds.size(); ++i) {
|
||||
const Command &cmd = cmds[i];
|
||||
for (int j = 0; j < cmd.insert_length_; ++j) {
|
||||
for (int j = 0; j < cmd.insert_len_; ++j) {
|
||||
literal_it.Next();
|
||||
if (literal_it.type_ == block_type) {
|
||||
uint8_t prev_byte = pos > 0 ? ringbuffer[(pos - 1) & mask] : 0;
|
||||
@ -87,7 +87,7 @@ void BuildLiteralHistogramsForBlockType(
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
pos += cmd.copy_length_;
|
||||
pos += cmd.copy_len_;
|
||||
}
|
||||
}
|
||||
|
||||
|
63
enc/prefix.h
63
enc/prefix.h
@ -19,6 +19,7 @@
|
||||
#define BROTLI_ENC_PREFIX_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "./fast_log.h"
|
||||
|
||||
namespace brotli {
|
||||
|
||||
@ -29,22 +30,56 @@ static const int kNumBlockLenPrefixes = 26;
|
||||
static const int kNumDistanceShortCodes = 16;
|
||||
static const int kNumDistancePrefixes = 520;
|
||||
|
||||
int CommandPrefix(int insert_length, int copy_length);
|
||||
int InsertLengthExtraBits(int prefix);
|
||||
int InsertLengthOffset(int prefix);
|
||||
int CopyLengthExtraBits(int prefix);
|
||||
int CopyLengthOffset(int prefix);
|
||||
// Represents the range of values belonging to a prefix code:
|
||||
// [offset, offset + 2^nbits)
|
||||
struct PrefixCodeRange {
|
||||
int offset;
|
||||
int nbits;
|
||||
};
|
||||
|
||||
void PrefixEncodeCopyDistance(int distance_code,
|
||||
int num_direct_codes,
|
||||
int shift_bits,
|
||||
uint16_t* prefix,
|
||||
int* nbits,
|
||||
uint32_t* extra_bits);
|
||||
static const PrefixCodeRange kBlockLengthPrefixCode[kNumBlockLenPrefixes] = {
|
||||
{ 1, 2}, { 5, 2}, { 9, 2}, { 13, 2},
|
||||
{ 17, 3}, { 25, 3}, { 33, 3}, { 41, 3},
|
||||
{ 49, 4}, { 65, 4}, { 81, 4}, { 97, 4},
|
||||
{ 113, 5}, { 145, 5}, { 177, 5}, { 209, 5},
|
||||
{ 241, 6}, { 305, 6}, { 369, 7}, { 497, 8},
|
||||
{ 753, 9}, { 1265, 10}, {2289, 11}, {4337, 12},
|
||||
{8433, 13}, {16625, 24}
|
||||
};
|
||||
|
||||
int BlockLengthPrefix(int length);
|
||||
int BlockLengthExtraBits(int prefix);
|
||||
int BlockLengthOffset(int prefix);
|
||||
inline void GetBlockLengthPrefixCode(int len,
|
||||
int* code, int* n_extra, int* extra) {
|
||||
*code = 0;
|
||||
while (*code < 25 && len >= kBlockLengthPrefixCode[*code + 1].offset) {
|
||||
++(*code);
|
||||
}
|
||||
*n_extra = kBlockLengthPrefixCode[*code].nbits;
|
||||
*extra = len - kBlockLengthPrefixCode[*code].offset;
|
||||
}
|
||||
|
||||
inline void PrefixEncodeCopyDistance(int distance_code,
|
||||
int num_direct_codes,
|
||||
int postfix_bits,
|
||||
uint16_t* code,
|
||||
uint32_t* extra_bits) {
|
||||
distance_code -= 1;
|
||||
if (distance_code < kNumDistanceShortCodes + num_direct_codes) {
|
||||
*code = distance_code;
|
||||
*extra_bits = 0;
|
||||
return;
|
||||
}
|
||||
distance_code -= kNumDistanceShortCodes + num_direct_codes;
|
||||
distance_code += (1 << (postfix_bits + 2));
|
||||
int bucket = Log2Floor(distance_code) - 1;
|
||||
int postfix_mask = (1 << postfix_bits) - 1;
|
||||
int postfix = distance_code & postfix_mask;
|
||||
int prefix = (distance_code >> bucket) & 1;
|
||||
int offset = (2 + prefix) << bucket;
|
||||
int nbits = bucket - postfix_bits;
|
||||
*code = kNumDistanceShortCodes + num_direct_codes +
|
||||
((2 * (nbits - 1) + prefix) << postfix_bits) + postfix;
|
||||
*extra_bits = (nbits << 24) | ((distance_code - offset) >> postfix_bits);
|
||||
}
|
||||
|
||||
} // namespace brotli
|
||||
|
||||
|
@ -17,6 +17,9 @@
|
||||
#ifndef BROTLI_ENC_RINGBUFFER_H_
|
||||
#define BROTLI_ENC_RINGBUFFER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// A RingBuffer(window_bits, tail_bits) contains `1 << window_bits' bytes of
|
||||
// data in a circular manner: writing a byte writes it to
|
||||
// `position() % (1 << window_bits)'. For convenience, the RingBuffer array
|
||||
@ -26,10 +29,10 @@ class RingBuffer {
|
||||
public:
|
||||
RingBuffer(int window_bits, int tail_bits)
|
||||
: window_bits_(window_bits), tail_bits_(tail_bits), pos_(0) {
|
||||
static const int kSlackForThreeByteHashingEverywhere = 2;
|
||||
static const int kSlackForFourByteHashingEverywhere = 3;
|
||||
const int buflen = (1 << window_bits_) + (1 << tail_bits_);
|
||||
buffer_ = new uint8_t[buflen + kSlackForThreeByteHashingEverywhere];
|
||||
for (int i = 0; i < kSlackForThreeByteHashingEverywhere; ++i) {
|
||||
buffer_ = new uint8_t[buflen + kSlackForFourByteHashingEverywhere];
|
||||
for (int i = 0; i < kSlackForFourByteHashingEverywhere; ++i) {
|
||||
buffer_[buflen + i] = 0;
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ inline void WriteBits(int n_bits,
|
||||
#ifdef BIT_WRITER_DEBUG
|
||||
printf("WriteBits %2d 0x%016llx %10d\n", n_bits, bits, *pos);
|
||||
#endif
|
||||
assert(bits < 1ULL << n_bits);
|
||||
#ifdef IS_LITTLE_ENDIAN
|
||||
// This branch of the code can write up to 56 bits at a time,
|
||||
// 7 bits are lost by being perhaps already in *p and at least
|
||||
|
Loading…
Reference in New Issue
Block a user