Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/enc_bit_writer.cc
Line
Count
Source
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
389k
                              bool finished_histogram) {
26
389k
  BitWriter::Allotment allotment(max_bits);
27
389k
  JXL_RETURN_IF_ERROR(allotment.Init(this));
28
389k
  const Status result = function();
29
389k
  if (result && finished_histogram) {
30
203k
    JXL_RETURN_IF_ERROR(allotment.FinishedHistogram(this));
31
203k
  }
32
389k
  JXL_RETURN_IF_ERROR(allotment.ReclaimAndCharge(this, layer, aux_out));
33
389k
  return result;
34
389k
}
35
389k
BitWriter::Allotment::Allotment(size_t max_bits) : max_bits_(max_bits) {}
36
37
389k
Status BitWriter::Allotment::Init(BitWriter* JXL_RESTRICT writer) {
38
389k
  prev_bits_written_ = writer->BitsWritten();
39
389k
  const size_t prev_bytes = writer->storage_.size();
40
389k
  const size_t next_bytes = DivCeil(max_bits_, kBitsPerByte);
41
389k
  if (!writer->storage_.resize(prev_bytes + next_bytes)) {
42
0
    called_ = true;
43
0
    return false;
44
0
  }
45
389k
  parent_ = writer->current_allotment_;
46
389k
  writer->current_allotment_ = this;
47
389k
  return true;
48
389k
}
49
50
389k
BitWriter::Allotment::~Allotment() {
51
389k
  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
389k
}
56
57
203k
Status BitWriter::Allotment::FinishedHistogram(BitWriter* JXL_RESTRICT writer) {
58
203k
  if (writer == nullptr) return true;
59
203k
  JXL_ENSURE(!called_);              // Call before ReclaimUnused
60
203k
  JXL_ENSURE(histogram_bits_ == 0);  // Do not call twice
61
203k
  JXL_ENSURE(writer->BitsWritten() >= prev_bits_written_);
62
203k
  if (writer->BitsWritten() >= prev_bits_written_) {
63
203k
    histogram_bits_ = writer->BitsWritten() - prev_bits_written_;
64
203k
  }
65
203k
  return true;
66
203k
}
67
68
Status BitWriter::Allotment::ReclaimAndCharge(BitWriter* JXL_RESTRICT writer,
69
                                              LayerType layer,
70
389k
                                              AuxOut* JXL_RESTRICT aux_out) {
71
389k
  size_t used_bits = 0;
72
389k
  size_t unused_bits = 0;
73
389k
  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
389k
  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
389k
  return true;
82
389k
}
83
84
Status BitWriter::Allotment::PrivateReclaim(BitWriter* JXL_RESTRICT writer,
85
                                            size_t* JXL_RESTRICT used_bits,
86
389k
                                            size_t* JXL_RESTRICT unused_bits) {
87
389k
  JXL_DASSERT(!called_);  // Do not call twice
88
389k
  called_ = true;
89
389k
  if (writer == nullptr) return true;
90
91
389k
  JXL_DASSERT(writer->BitsWritten() >= prev_bits_written_);
92
389k
  *used_bits = writer->BitsWritten() - prev_bits_written_;
93
389k
  JXL_DASSERT(*used_bits <= max_bits_);
94
389k
  *unused_bits = max_bits_ - *used_bits;
95
96
  // Reclaim unused bytes whole bytes from writer's allotment.
97
389k
  const size_t unused_bytes = *unused_bits / kBitsPerByte;  // truncate
98
389k
  JXL_ENSURE(writer->storage_.size() >= unused_bytes);
99
389k
  JXL_RETURN_IF_ERROR(
100
389k
      writer->storage_.resize(writer->storage_.size() - unused_bytes));
101
389k
  writer->current_allotment_ = parent_;
102
  // Ensure we don't also charge the parent for these bits.
103
389k
  auto* parent = parent_;
104
638k
  while (parent != nullptr) {
105
248k
    parent->prev_bits_written_ += *used_bits;
106
248k
    parent = parent->parent_;
107
248k
  }
108
389k
  return true;
109
389k
}
110
111
793
Status BitWriter::AppendByteAligned(const Span<const uint8_t>& span) {
112
793
  if (span.empty()) return true;
113
793
  JXL_RETURN_IF_ERROR(storage_.resize(storage_.size() + span.size() +
114
793
                                      1));  // extra zero padding
115
116
  // Concatenate by copying bytes because both source and destination are bytes.
117
793
  JXL_ENSURE(BitsWritten() % kBitsPerByte == 0);
118
793
  size_t pos = BitsWritten() / kBitsPerByte;
119
793
  memcpy(storage_.data() + pos, span.data(), span.size());
120
793
  pos += span.size();
121
793
  JXL_ENSURE(pos < storage_.size());
122
793
  storage_[pos++] = 0;  // for next Write
123
793
  bits_written_ += span.size() * kBitsPerByte;
124
793
  return true;
125
793
}
126
127
32.6k
Status BitWriter::AppendUnaligned(const BitWriter& other) {
128
32.6k
  return WithMaxBits(other.BitsWritten(), LayerType::Header, nullptr, [&] {
129
32.6k
    size_t full_bytes = other.BitsWritten() / kBitsPerByte;
130
32.6k
    size_t remaining_bits = other.BitsWritten() % kBitsPerByte;
131
484k
    for (size_t i = 0; i < full_bytes; ++i) {
132
452k
      Write(8, other.storage_[i]);
133
452k
    }
134
32.6k
    if (remaining_bits > 0) {
135
27.9k
      Write(remaining_bits,
136
27.9k
            other.storage_[full_bytes] & ((1u << remaining_bits) - 1));
137
27.9k
    }
138
32.6k
    return true;
139
32.6k
  });
140
32.6k
}
141
142
// TODO(lode): avoid code duplication
143
Status BitWriter::AppendByteAligned(
144
6.84k
    const std::vector<std::unique_ptr<BitWriter>>& others) {
145
  // Total size to add so we can preallocate
146
6.84k
  size_t other_bytes = 0;
147
9.37k
  for (const auto& writer : others) {
148
9.37k
    JXL_ENSURE(writer->BitsWritten() % kBitsPerByte == 0);
149
9.37k
    other_bytes += DivCeil(writer->BitsWritten(), kBitsPerByte);
150
9.37k
  }
151
6.84k
  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
2.66k
    return true;
156
2.66k
  }
157
4.18k
  JXL_RETURN_IF_ERROR(storage_.resize(storage_.size() + other_bytes +
158
4.18k
                                      1));  // extra zero padding
159
160
  // Concatenate by copying bytes because both source and destination are bytes.
161
4.18k
  JXL_ENSURE(BitsWritten() % kBitsPerByte == 0);
162
4.18k
  size_t pos = DivCeil(BitsWritten(), kBitsPerByte);
163
9.37k
  for (const auto& writer : others) {
164
9.37k
    const Span<const uint8_t> span = writer->GetSpan();
165
9.37k
    memcpy(storage_.data() + pos, span.data(), span.size());
166
9.37k
    pos += span.size();
167
9.37k
  }
168
4.18k
  JXL_ENSURE(pos < storage_.size());
169
4.18k
  storage_[pos++] = 0;  // for next Write
170
4.18k
  bits_written_ += other_bytes * kBitsPerByte;
171
4.18k
  return true;
172
4.18k
}
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
41.8M
void BitWriter::Write(size_t n_bits, uint64_t bits) {
184
41.8M
  JXL_DASSERT((bits >> n_bits) == 0);
185
41.8M
  JXL_DASSERT(n_bits <= kMaxBitsPerCall);
186
41.8M
  size_t bytes_written = bits_written_ / kBitsPerByte;
187
41.8M
  uint8_t* p = &storage_[bytes_written];
188
41.8M
  const size_t bits_in_first_byte = bits_written_ % kBitsPerByte;
189
41.8M
  bits <<= bits_in_first_byte;
190
41.8M
#if JXL_BYTE_ORDER_LITTLE
191
41.8M
  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
41.8M
  JXL_DASSERT(v >> bits_in_first_byte == 0);
195
41.8M
  v |= bits;
196
41.8M
  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
41.8M
  bits_written_ += n_bits;
207
41.8M
}
208
}  // namespace jxl