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