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