/src/libjxl/lib/jxl/enc_bit_writer.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/enc_bit_writer.h" |
7 | | |
8 | | #include <cstdint> |
9 | | #include <cstring> // memcpy |
10 | | #include <functional> |
11 | | #include <memory> |
12 | | #include <vector> |
13 | | |
14 | | #include "lib/jxl/base/byte_order.h" |
15 | | #include "lib/jxl/base/common.h" |
16 | | #include "lib/jxl/base/compiler_specific.h" |
17 | | #include "lib/jxl/base/span.h" |
18 | | #include "lib/jxl/base/status.h" |
19 | | #include "lib/jxl/enc_aux_out.h" |
20 | | |
21 | | namespace jxl { |
22 | | |
23 | | Status BitWriter::WithMaxBits(size_t max_bits, LayerType layer, AuxOut* aux_out, |
24 | | const std::function<Status()>& function, |
25 | 32.8k | bool finished_histogram) { |
26 | 32.8k | BitWriter::Allotment allotment(max_bits); |
27 | 32.8k | JXL_RETURN_IF_ERROR(allotment.Init(this)); |
28 | 32.8k | const Status result = function(); |
29 | 32.8k | if (result && finished_histogram) { |
30 | 21.4k | JXL_RETURN_IF_ERROR(allotment.FinishedHistogram(this)); |
31 | 21.4k | } |
32 | 32.8k | JXL_RETURN_IF_ERROR(allotment.ReclaimAndCharge(this, layer, aux_out)); |
33 | 32.8k | return result; |
34 | 32.8k | } |
35 | 32.8k | BitWriter::Allotment::Allotment(size_t max_bits) : max_bits_(max_bits) {} |
36 | | |
37 | 32.8k | Status BitWriter::Allotment::Init(BitWriter* JXL_RESTRICT writer) { |
38 | 32.8k | prev_bits_written_ = writer->BitsWritten(); |
39 | 32.8k | const size_t prev_bytes = writer->storage_.size(); |
40 | 32.8k | const size_t next_bytes = DivCeil(max_bits_, kBitsPerByte); |
41 | 32.8k | if (!writer->storage_.resize(prev_bytes + next_bytes)) { |
42 | 0 | called_ = true; |
43 | 0 | return false; |
44 | 0 | } |
45 | 32.8k | parent_ = writer->current_allotment_; |
46 | 32.8k | writer->current_allotment_ = this; |
47 | 32.8k | return true; |
48 | 32.8k | } |
49 | | |
50 | 32.8k | BitWriter::Allotment::~Allotment() { |
51 | 32.8k | if (!called_) { |
52 | | // Not calling is a bug - unused storage will not be reclaimed. |
53 | 0 | JXL_DEBUG_ABORT("Did not call Allotment::ReclaimUnused"); |
54 | 0 | } |
55 | 32.8k | } |
56 | | |
57 | 21.4k | Status BitWriter::Allotment::FinishedHistogram(BitWriter* JXL_RESTRICT writer) { |
58 | 21.4k | if (writer == nullptr) return true; |
59 | 21.4k | JXL_ENSURE(!called_); // Call before ReclaimUnused |
60 | 21.4k | JXL_ENSURE(histogram_bits_ == 0); // Do not call twice |
61 | 21.4k | JXL_ENSURE(writer->BitsWritten() >= prev_bits_written_); |
62 | 21.4k | if (writer->BitsWritten() >= prev_bits_written_) { |
63 | 21.4k | histogram_bits_ = writer->BitsWritten() - prev_bits_written_; |
64 | 21.4k | } |
65 | 21.4k | return true; |
66 | 21.4k | } |
67 | | |
68 | | Status BitWriter::Allotment::ReclaimAndCharge(BitWriter* JXL_RESTRICT writer, |
69 | | LayerType layer, |
70 | 32.8k | AuxOut* JXL_RESTRICT aux_out) { |
71 | 32.8k | size_t used_bits = 0; |
72 | 32.8k | size_t unused_bits = 0; |
73 | 32.8k | JXL_RETURN_IF_ERROR(PrivateReclaim(writer, &used_bits, &unused_bits)); |
74 | | |
75 | | // This may be a nested call with aux_out == null. Whenever we know that |
76 | | // aux_out is null, we can call ReclaimUnused directly. |
77 | 32.8k | if (aux_out != nullptr) { |
78 | 0 | aux_out->layer(layer).total_bits += used_bits; |
79 | 0 | aux_out->layer(layer).histogram_bits += HistogramBits(); |
80 | 0 | } |
81 | 32.8k | return true; |
82 | 32.8k | } |
83 | | |
84 | | Status BitWriter::Allotment::PrivateReclaim(BitWriter* JXL_RESTRICT writer, |
85 | | size_t* JXL_RESTRICT used_bits, |
86 | 32.8k | size_t* JXL_RESTRICT unused_bits) { |
87 | 32.8k | JXL_DASSERT(!called_); // Do not call twice |
88 | 32.8k | called_ = true; |
89 | 32.8k | if (writer == nullptr) return true; |
90 | | |
91 | 32.8k | JXL_DASSERT(writer->BitsWritten() >= prev_bits_written_); |
92 | 32.8k | *used_bits = writer->BitsWritten() - prev_bits_written_; |
93 | 32.8k | JXL_DASSERT(*used_bits <= max_bits_); |
94 | 32.8k | *unused_bits = max_bits_ - *used_bits; |
95 | | |
96 | | // Reclaim unused bytes whole bytes from writer's allotment. |
97 | 32.8k | const size_t unused_bytes = *unused_bits / kBitsPerByte; // truncate |
98 | 32.8k | JXL_ENSURE(writer->storage_.size() >= unused_bytes); |
99 | 32.8k | JXL_RETURN_IF_ERROR( |
100 | 32.8k | writer->storage_.resize(writer->storage_.size() - unused_bytes)); |
101 | 32.8k | writer->current_allotment_ = parent_; |
102 | | // Ensure we don't also charge the parent for these bits. |
103 | 32.8k | auto* parent = parent_; |
104 | 58.9k | while (parent != nullptr) { |
105 | 26.0k | parent->prev_bits_written_ += *used_bits; |
106 | 26.0k | parent = parent->parent_; |
107 | 26.0k | } |
108 | 32.8k | return true; |
109 | 32.8k | } |
110 | | |
111 | 97 | Status BitWriter::AppendByteAligned(const Span<const uint8_t>& span) { |
112 | 97 | if (span.empty()) return true; |
113 | 97 | JXL_RETURN_IF_ERROR(storage_.resize(storage_.size() + span.size() + |
114 | 97 | 1)); // extra zero padding |
115 | | |
116 | | // Concatenate by copying bytes because both source and destination are bytes. |
117 | 97 | JXL_ENSURE(BitsWritten() % kBitsPerByte == 0); |
118 | 97 | size_t pos = BitsWritten() / kBitsPerByte; |
119 | 97 | memcpy(storage_.data() + pos, span.data(), span.size()); |
120 | 97 | pos += span.size(); |
121 | 97 | JXL_ENSURE(pos < storage_.size()); |
122 | 97 | storage_[pos++] = 0; // for next Write |
123 | 97 | bits_written_ += span.size() * kBitsPerByte; |
124 | 97 | return true; |
125 | 97 | } |
126 | | |
127 | 0 | Status BitWriter::AppendUnaligned(const BitWriter& other) { |
128 | 0 | return WithMaxBits(other.BitsWritten(), LayerType::Header, nullptr, [&] { |
129 | 0 | size_t full_bytes = other.BitsWritten() / kBitsPerByte; |
130 | 0 | size_t remaining_bits = other.BitsWritten() % kBitsPerByte; |
131 | 0 | for (size_t i = 0; i < full_bytes; ++i) { |
132 | 0 | Write(8, other.storage_[i]); |
133 | 0 | } |
134 | 0 | if (remaining_bits > 0) { |
135 | 0 | Write(remaining_bits, |
136 | 0 | other.storage_[full_bytes] & ((1u << remaining_bits) - 1)); |
137 | 0 | } |
138 | 0 | return true; |
139 | 0 | }); |
140 | 0 | } |
141 | | |
142 | | // TODO(lode): avoid code duplication |
143 | | Status BitWriter::AppendByteAligned( |
144 | 566 | const std::vector<std::unique_ptr<BitWriter>>& others) { |
145 | | // Total size to add so we can preallocate |
146 | 566 | size_t other_bytes = 0; |
147 | 1.21k | for (const auto& writer : others) { |
148 | 1.21k | JXL_ENSURE(writer->BitsWritten() % kBitsPerByte == 0); |
149 | 1.21k | other_bytes += DivCeil(writer->BitsWritten(), kBitsPerByte); |
150 | 1.21k | } |
151 | 566 | if (other_bytes == 0) { |
152 | | // No bytes to append: this happens for example when creating per-group |
153 | | // storage for groups, but not writing anything in them for e.g. lossless |
154 | | // images with no alpha. Do nothing. |
155 | 186 | return true; |
156 | 186 | } |
157 | 380 | JXL_RETURN_IF_ERROR(storage_.resize(storage_.size() + other_bytes + |
158 | 380 | 1)); // extra zero padding |
159 | | |
160 | | // Concatenate by copying bytes because both source and destination are bytes. |
161 | 380 | JXL_ENSURE(BitsWritten() % kBitsPerByte == 0); |
162 | 380 | size_t pos = DivCeil(BitsWritten(), kBitsPerByte); |
163 | 1.21k | for (const auto& writer : others) { |
164 | 1.21k | const Span<const uint8_t> span = writer->GetSpan(); |
165 | 1.21k | memcpy(storage_.data() + pos, span.data(), span.size()); |
166 | 1.21k | pos += span.size(); |
167 | 1.21k | } |
168 | 380 | JXL_ENSURE(pos < storage_.size()); |
169 | 380 | storage_[pos++] = 0; // for next Write |
170 | 380 | bits_written_ += other_bytes * kBitsPerByte; |
171 | 380 | return true; |
172 | 380 | } |
173 | | |
174 | | // Example: let's assume that 3 bits (Rs below) have been written already: |
175 | | // BYTE+0 BYTE+1 BYTE+2 |
176 | | // 0000 0RRR ???? ???? ???? ???? |
177 | | // |
178 | | // Now, we could write up to 5 bits by just shifting them left by 3 bits and |
179 | | // OR'ing to BYTE-0. |
180 | | // |
181 | | // For n > 5 bits, we write the lowest 5 bits as above, then write the next |
182 | | // lowest bits into BYTE+1 starting from its lower bits and so on. |
183 | 2.52M | void BitWriter::Write(size_t n_bits, uint64_t bits) { |
184 | 2.52M | JXL_DASSERT((bits >> n_bits) == 0); |
185 | 2.52M | JXL_DASSERT(n_bits <= kMaxBitsPerCall); |
186 | 2.52M | size_t bytes_written = bits_written_ / kBitsPerByte; |
187 | 2.52M | uint8_t* p = &storage_[bytes_written]; |
188 | 2.52M | const size_t bits_in_first_byte = bits_written_ % kBitsPerByte; |
189 | 2.52M | bits <<= bits_in_first_byte; |
190 | 2.52M | #if JXL_BYTE_ORDER_LITTLE |
191 | 2.52M | uint64_t v = *p; |
192 | | // Last (partial) or next byte to write must be zero-initialized! |
193 | | // PaddedBytes initializes the first, and Write/Append maintain this. |
194 | 2.52M | JXL_DASSERT(v >> bits_in_first_byte == 0); |
195 | 2.52M | v |= bits; |
196 | 2.52M | memcpy(p, &v, sizeof(v)); // Write bytes: possibly more than n_bits/8 |
197 | | #else |
198 | | *p++ |= static_cast<uint8_t>(bits & 0xFF); |
199 | | for (size_t bits_left_to_write = n_bits + bits_in_first_byte; |
200 | | bits_left_to_write >= 9; bits_left_to_write -= 8) { |
201 | | bits >>= 8; |
202 | | *p++ = static_cast<uint8_t>(bits & 0xFF); |
203 | | } |
204 | | *p = 0; |
205 | | #endif |
206 | 2.52M | bits_written_ += n_bits; |
207 | 2.52M | } |
208 | | } // namespace jxl |