/src/libjxl/lib/jxl/ans_common.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/jxl/ans_common.h" |
7 | | |
8 | | #include <cstddef> |
9 | | #include <cstdint> |
10 | | #include <numeric> |
11 | | #include <vector> |
12 | | |
13 | | #include "lib/jxl/ans_params.h" |
14 | | #include "lib/jxl/base/status.h" |
15 | | |
16 | | namespace jxl { |
17 | | |
18 | 12.1k | std::vector<int32_t> CreateFlatHistogram(int length, int total_count) { |
19 | 12.1k | JXL_DASSERT(length > 0); |
20 | 12.1k | JXL_DASSERT(length <= total_count); |
21 | 12.1k | const int count = total_count / length; |
22 | 12.1k | std::vector<int32_t> result(length, count); |
23 | 12.1k | const int rem_counts = total_count % length; |
24 | 101k | for (int i = 0; i < rem_counts; ++i) { |
25 | 89.3k | ++result[i]; |
26 | 89.3k | } |
27 | 12.1k | return result; |
28 | 12.1k | } |
29 | | |
30 | | // First, all trailing non-occurring symbols are removed from the distribution; |
31 | | // if this leaves the distribution empty, a placeholder symbol with max weight |
32 | | // is added. This ensures that the resulting distribution sums to total table |
33 | | // size. Then, `entry_size` is chosen to be the largest power of two so that |
34 | | // `table_size` = ANS_TAB_SIZE/`entry_size` is at least as big as the |
35 | | // distribution size. |
36 | | // Note that each entry will only ever contain two different symbols, and |
37 | | // consecutive ranges of offsets, which allows us to use a compact |
38 | | // representation. |
39 | | // Each entry is initialized with only the (symbol=i, offset) pairs; then |
40 | | // positions for which the entry overflows (i.e. distribution[i] > entry_size) |
41 | | // or is not full are computed, and put into a stack in increasing order. |
42 | | // Missing symbols in the distribution are padded with 0 (because `table_size` |
43 | | // >= number of symbols). The `cutoff` value for each entry is initialized to |
44 | | // the number of occupied slots in that entry (i.e. `distributions[i]`). While |
45 | | // the overflowing-symbol stack is not empty (which implies that the |
46 | | // underflowing-symbol stack also is not), the top overfull and underfull |
47 | | // positions are popped from the stack; the empty slots in the underfull entry |
48 | | // are then filled with as many slots as needed from the overfull entry; such |
49 | | // slots are placed after the slots in the overfull entry, and `offsets[1]` is |
50 | | // computed accordingly. The formerly underfull entry is thus now neither |
51 | | // underfull nor overfull, and represents exactly two symbols. The overfull |
52 | | // entry might be either overfull or underfull, and is pushed into the |
53 | | // corresponding stack. |
54 | | Status InitAliasTable(std::vector<int32_t> distribution, uint32_t log_range, |
55 | | size_t log_alpha_size, |
56 | 57.5k | AliasTable::Entry* JXL_RESTRICT a) { |
57 | 57.5k | const uint32_t range = 1 << log_range; |
58 | 57.5k | const size_t table_size = 1 << log_alpha_size; |
59 | 57.5k | JXL_ENSURE(table_size <= range); |
60 | 57.5k | while (!distribution.empty() && distribution.back() == 0) { |
61 | 0 | distribution.pop_back(); |
62 | 0 | } |
63 | | // Ensure that a valid table is always returned, even for an empty |
64 | | // alphabet. Otherwise, a specially-crafted stream might crash the |
65 | | // decoder. |
66 | 57.5k | if (distribution.empty()) { |
67 | 0 | distribution.emplace_back(range); |
68 | 0 | } |
69 | 57.5k | JXL_ENSURE(distribution.size() <= table_size); |
70 | 57.5k | const uint32_t entry_size = range >> log_alpha_size; // this is exact |
71 | 57.5k | int single_symbol = -1; |
72 | 57.5k | int sum = 0; |
73 | | // Special case for single-symbol distributions, that ensures that the state |
74 | | // does not change when decoding from such a distribution. Note that, since we |
75 | | // hardcode offset0 == 0, it is not straightforward (if at all possible) to |
76 | | // fix the general case to produce this result. |
77 | 774k | for (size_t sym = 0; sym < distribution.size(); sym++) { |
78 | 717k | int32_t v = distribution[sym]; |
79 | 717k | sum += v; |
80 | 717k | if (v == ANS_TAB_SIZE) { |
81 | 21.8k | JXL_ENSURE(single_symbol == -1); |
82 | 21.8k | single_symbol = sym; |
83 | 21.8k | } |
84 | 717k | } |
85 | 57.5k | JXL_ENSURE(static_cast<uint32_t>(sum) == range); |
86 | 57.5k | if (single_symbol != -1) { |
87 | 21.8k | uint8_t sym = single_symbol; |
88 | 21.8k | JXL_ENSURE(single_symbol == sym); |
89 | 1.34M | for (size_t i = 0; i < table_size; i++) { |
90 | 1.31M | a[i].right_value = sym; |
91 | 1.31M | a[i].cutoff = 0; |
92 | 1.31M | a[i].offsets1 = entry_size * i; |
93 | 1.31M | a[i].freq0 = 0; |
94 | 1.31M | a[i].freq1_xor_freq0 = ANS_TAB_SIZE; |
95 | 1.31M | } |
96 | 21.8k | return true; |
97 | 21.8k | } |
98 | | |
99 | 35.6k | std::vector<uint32_t> underfull_posn; |
100 | 35.6k | std::vector<uint32_t> overfull_posn; |
101 | 35.6k | std::vector<uint32_t> cutoffs(1 << log_alpha_size); |
102 | | // Initialize entries. |
103 | 614k | for (size_t i = 0; i < distribution.size(); i++) { |
104 | 578k | cutoffs[i] = distribution[i]; |
105 | 578k | if (cutoffs[i] > entry_size) { |
106 | 332k | overfull_posn.push_back(i); |
107 | 332k | } else if (cutoffs[i] < entry_size) { |
108 | 192k | underfull_posn.push_back(i); |
109 | 192k | } |
110 | 578k | } |
111 | 1.87M | for (uint32_t i = distribution.size(); i < table_size; i++) { |
112 | 1.84M | cutoffs[i] = 0; |
113 | 1.84M | underfull_posn.push_back(i); |
114 | 1.84M | } |
115 | | // Reassign overflow/underflow values. |
116 | 2.25M | while (!overfull_posn.empty()) { |
117 | 2.21M | uint32_t overfull_i = overfull_posn.back(); |
118 | 2.21M | overfull_posn.pop_back(); |
119 | 2.21M | JXL_ENSURE(!underfull_posn.empty()); |
120 | 2.21M | uint32_t underfull_i = underfull_posn.back(); |
121 | 2.21M | underfull_posn.pop_back(); |
122 | 2.21M | uint32_t underfull_by = entry_size - cutoffs[underfull_i]; |
123 | 2.21M | cutoffs[overfull_i] -= underfull_by; |
124 | | // overfull positions have their original symbols |
125 | 2.21M | a[underfull_i].right_value = overfull_i; |
126 | 2.21M | a[underfull_i].offsets1 = cutoffs[overfull_i]; |
127 | | // Slots in the right part of entry underfull_i were taken from the end |
128 | | // of the symbols in entry overfull_i. |
129 | 2.21M | if (cutoffs[overfull_i] < entry_size) { |
130 | 182k | underfull_posn.push_back(overfull_i); |
131 | 2.03M | } else if (cutoffs[overfull_i] > entry_size) { |
132 | 1.88M | overfull_posn.push_back(overfull_i); |
133 | 1.88M | } |
134 | 2.21M | } |
135 | 2.45M | for (uint32_t i = 0; i < table_size; i++) { |
136 | | // cutoffs[i] is properly initialized but the clang-analyzer doesn't infer |
137 | | // it since it is partially initialized across two for-loops. |
138 | | // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) |
139 | 2.41M | if (cutoffs[i] == entry_size) { |
140 | 204k | a[i].right_value = i; |
141 | 204k | a[i].offsets1 = 0; |
142 | 204k | a[i].cutoff = 0; |
143 | 2.21M | } else { |
144 | | // Note that, if cutoff is not equal to entry_size, |
145 | | // a[i].offsets1 was initialized with (overfull cutoff) - |
146 | | // (entry_size - a[i].cutoff). Thus, subtracting |
147 | | // a[i].cutoff cannot make it negative. |
148 | 2.21M | a[i].offsets1 -= cutoffs[i]; |
149 | 2.21M | a[i].cutoff = cutoffs[i]; |
150 | 2.21M | } |
151 | 2.41M | const size_t freq0 = i < distribution.size() ? distribution[i] : 0; |
152 | 2.41M | const size_t i1 = a[i].right_value; |
153 | 18.4E | const size_t freq1 = i1 < distribution.size() ? distribution[i1] : 0; |
154 | 2.41M | a[i].freq0 = static_cast<uint16_t>(freq0); |
155 | 2.41M | a[i].freq1_xor_freq0 = static_cast<uint16_t>(freq1 ^ freq0); |
156 | 2.41M | } |
157 | 35.6k | return true; |
158 | 35.6k | } |
159 | | |
160 | | } // namespace jxl |