/src/libjxl/lib/jxl/encode_internal.h
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 | | |
7 | | #ifndef LIB_JXL_ENCODE_INTERNAL_H_ |
8 | | #define LIB_JXL_ENCODE_INTERNAL_H_ |
9 | | |
10 | | #include <jxl/cms_interface.h> |
11 | | #include <jxl/codestream_header.h> |
12 | | #include <jxl/encode.h> |
13 | | #include <jxl/memory_manager.h> |
14 | | #include <jxl/types.h> |
15 | | |
16 | | #include <algorithm> |
17 | | #include <array> |
18 | | #include <cstddef> |
19 | | #include <cstdint> |
20 | | #include <cstring> |
21 | | #include <functional> |
22 | | #include <map> |
23 | | #include <memory> |
24 | | #include <string> |
25 | | #include <utility> |
26 | | #include <vector> |
27 | | |
28 | | #include "lib/jxl/base/c_callback_support.h" |
29 | | #include "lib/jxl/base/common.h" |
30 | | #include "lib/jxl/base/compiler_specific.h" |
31 | | #include "lib/jxl/base/data_parallel.h" |
32 | | #include "lib/jxl/base/status.h" |
33 | | #include "lib/jxl/enc_fast_lossless.h" |
34 | | #include "lib/jxl/enc_params.h" |
35 | | #include "lib/jxl/image_metadata.h" |
36 | | #include "lib/jxl/jpeg/jpeg_data.h" |
37 | | #include "lib/jxl/memory_manager_internal.h" |
38 | | #include "lib/jxl/padded_bytes.h" |
39 | | |
40 | | namespace jxl { |
41 | | |
42 | | /* Frame index box 'jxli' will start with Varint() for |
43 | | NF: has type Varint(): number of frames listed in the index. |
44 | | TNUM: has type u32: numerator of tick unit. |
45 | | TDEN: has type u32: denominator of tick unit. Value 0 means the file is |
46 | | ill-formed. per frame i listed: OFFi: has type Varint(): offset of start byte of |
47 | | this frame compared to start byte of previous frame from this index in the JPEG |
48 | | XL codestream. For the first frame, this is the offset from the first byte of |
49 | | the JPEG XL codestream. Ti: has type Varint(): duration in ticks between the |
50 | | start of this frame and the start of the next frame in the index. If this is the |
51 | | last frame in the index, this is the duration in ticks between the start of this |
52 | | frame and the end of the stream. A tick lasts TNUM / TDEN seconds. Fi: has type |
53 | | Varint(): amount of frames the next frame in the index occurs after this frame. |
54 | | If this is the last frame in the index, this is the amount of frames after this |
55 | | frame in the remainder of the stream. Only frames that are presented by the |
56 | | decoder are counted for this purpose, this excludes frames that are not intended |
57 | | for display but for compositing with other frames, such as frames that aren't |
58 | | the last frame with a duration of 0 ticks. |
59 | | |
60 | | All the frames listed in jxli are keyframes and the first frame is |
61 | | present in the list. |
62 | | There shall be either zero or one Frame Index boxes in a JPEG XL file. |
63 | | The offsets OFFi per frame are given as bytes in the codestream, not as |
64 | | bytes in the file format using the box structure. This means if JPEG XL Partial |
65 | | Codestream boxes are used, the offset is counted within the concatenated |
66 | | codestream, bytes from box headers or non-codestream boxes are not counted. |
67 | | */ |
68 | | |
69 | | struct JxlEncoderFrameIndexBoxEntry { |
70 | | bool to_be_indexed; |
71 | | uint32_t duration; |
72 | | uint64_t OFFi; |
73 | | }; |
74 | | |
75 | | struct JxlEncoderFrameIndexBox { |
76 | | // We always need to record the first frame entry, so presence of the |
77 | | // first entry alone is not an indication if it was requested to be |
78 | | // stored. |
79 | | bool index_box_requested_through_api = false; |
80 | | |
81 | 0 | int64_t NF() const { return entries.size(); } |
82 | 186 | bool StoreFrameIndexBox() { |
83 | 186 | for (auto e : entries) { |
84 | 186 | if (e.to_be_indexed) { |
85 | 0 | return true; |
86 | 0 | } |
87 | 186 | } |
88 | 186 | return false; |
89 | 186 | } |
90 | | int32_t TNUM = 1; |
91 | | int32_t TDEN = 1000; |
92 | | |
93 | | std::vector<JxlEncoderFrameIndexBoxEntry> entries; |
94 | | |
95 | | // That way we can ensure that every index box will have the first frame. |
96 | | // If the API user decides to mark it as an indexed frame, we call |
97 | | // the AddFrame again, this time with requested. |
98 | 186 | void AddFrame(uint64_t OFFi, uint32_t duration, bool to_be_indexed) { |
99 | | // We call AddFrame to every frame. |
100 | | // Recording the first frame is required by the standard. |
101 | | // Knowing the last frame is required, since the last indexed frame |
102 | | // needs to know how many frames until the end. |
103 | | // To be able to tell how many frames there are between each index |
104 | | // entry we just record every frame here. |
105 | 186 | if (entries.size() == 1) { |
106 | 0 | if (OFFi == entries[0].OFFi) { |
107 | | // API use for the first frame, let's clear the already recorded first |
108 | | // frame. |
109 | 0 | entries.clear(); |
110 | 0 | } |
111 | 0 | } |
112 | 186 | JxlEncoderFrameIndexBoxEntry e; |
113 | 186 | e.to_be_indexed = to_be_indexed; |
114 | 186 | e.OFFi = OFFi; |
115 | 186 | e.duration = duration; |
116 | 186 | entries.push_back(e); |
117 | 186 | } |
118 | | }; |
119 | | |
120 | | // The encoder options (such as quality, compression speed, ...) for a single |
121 | | // frame, but not encoder-wide options such as box-related options. |
122 | | struct JxlEncoderFrameSettingsValues { |
123 | | // lossless is a separate setting from cparams because it is a combination |
124 | | // setting that overrides multiple settings inside of cparams. |
125 | | bool lossless; |
126 | | CompressParams cparams; |
127 | | JxlFrameHeader header; |
128 | | std::vector<JxlBlendInfo> extra_channel_blend_info; |
129 | | std::string frame_name; |
130 | | JxlBitDepth image_bit_depth; |
131 | | bool frame_index_box = false; |
132 | | jxl::AuxOut* aux_out = nullptr; |
133 | | }; |
134 | | |
135 | | using BoxType = std::array<uint8_t, 4>; |
136 | | |
137 | | // Utility function that makes a BoxType from a string literal. The string must |
138 | | // have 4 characters, a 5th null termination character is optional. |
139 | 104 | constexpr BoxType MakeBoxType(const char* type) { |
140 | 104 | return BoxType( |
141 | 104 | {{static_cast<uint8_t>(type[0]), static_cast<uint8_t>(type[1]), |
142 | 104 | static_cast<uint8_t>(type[2]), static_cast<uint8_t>(type[3])}}); |
143 | 104 | } |
144 | | |
145 | | constexpr std::array<unsigned char, 32> kContainerHeader = { |
146 | | 0, 0, 0, 0xc, 'J', 'X', 'L', ' ', 0xd, 0xa, 0x87, |
147 | | 0xa, 0, 0, 0, 0x14, 'f', 't', 'y', 'p', 'j', 'x', |
148 | | 'l', ' ', 0, 0, 0, 0, 'j', 'x', 'l', ' '}; |
149 | | |
150 | | constexpr std::array<unsigned char, 8> kLevelBoxHeader = {0, 0, 0, 0x9, |
151 | | 'j', 'x', 'l', 'l'}; |
152 | | |
153 | 566 | static JXL_INLINE size_t BitsPerChannel(JxlDataType data_type) { |
154 | 566 | switch (data_type) { |
155 | 310 | case JXL_TYPE_UINT8: |
156 | 310 | return 8; |
157 | 50 | case JXL_TYPE_UINT16: |
158 | 50 | return 16; |
159 | 206 | case JXL_TYPE_FLOAT: |
160 | 206 | return 32; |
161 | 0 | case JXL_TYPE_FLOAT16: |
162 | 0 | return 16; |
163 | 0 | default: |
164 | 0 | return 0; // signals unhandled JxlDataType |
165 | 566 | } |
166 | 566 | } encode.cc:jxl::BitsPerChannel(JxlDataType) Line | Count | Source | 153 | 283 | static JXL_INLINE size_t BitsPerChannel(JxlDataType data_type) { | 154 | 283 | switch (data_type) { | 155 | 155 | case JXL_TYPE_UINT8: | 156 | 155 | return 8; | 157 | 25 | case JXL_TYPE_UINT16: | 158 | 25 | return 16; | 159 | 103 | case JXL_TYPE_FLOAT: | 160 | 103 | return 32; | 161 | 0 | case JXL_TYPE_FLOAT16: | 162 | 0 | return 16; | 163 | 0 | default: | 164 | 0 | return 0; // signals unhandled JxlDataType | 165 | 283 | } | 166 | 283 | } |
Unexecuted instantiation: enc_fast_lossless.cc:jxl::BitsPerChannel(JxlDataType) enc_frame.cc:jxl::BitsPerChannel(JxlDataType) Line | Count | Source | 153 | 283 | static JXL_INLINE size_t BitsPerChannel(JxlDataType data_type) { | 154 | 283 | switch (data_type) { | 155 | 155 | case JXL_TYPE_UINT8: | 156 | 155 | return 8; | 157 | 25 | case JXL_TYPE_UINT16: | 158 | 25 | return 16; | 159 | 103 | case JXL_TYPE_FLOAT: | 160 | 103 | return 32; | 161 | 0 | case JXL_TYPE_FLOAT16: | 162 | 0 | return 16; | 163 | 0 | default: | 164 | 0 | return 0; // signals unhandled JxlDataType | 165 | 283 | } | 166 | 283 | } |
Unexecuted instantiation: enc_patch_dictionary.cc:jxl::BitsPerChannel(JxlDataType) Unexecuted instantiation: enc_cache.cc:jxl::BitsPerChannel(JxlDataType) |
167 | | |
168 | 283 | static JXL_INLINE size_t BytesPerPixel(JxlPixelFormat format) { |
169 | 283 | return format.num_channels * BitsPerChannel(format.data_type) / 8; |
170 | 283 | } encode.cc:jxl::BytesPerPixel(JxlPixelFormat) Line | Count | Source | 168 | 283 | static JXL_INLINE size_t BytesPerPixel(JxlPixelFormat format) { | 169 | 283 | return format.num_channels * BitsPerChannel(format.data_type) / 8; | 170 | 283 | } |
Unexecuted instantiation: enc_fast_lossless.cc:jxl::BytesPerPixel(JxlPixelFormat) Unexecuted instantiation: enc_frame.cc:jxl::BytesPerPixel(JxlPixelFormat) Unexecuted instantiation: enc_patch_dictionary.cc:jxl::BytesPerPixel(JxlPixelFormat) Unexecuted instantiation: enc_cache.cc:jxl::BytesPerPixel(JxlPixelFormat) |
171 | | |
172 | | using ScopedInputBuffer = |
173 | | std::unique_ptr<const void, std::function<void(const void*)>>; |
174 | | |
175 | | static JXL_INLINE ScopedInputBuffer |
176 | | GetColorBuffer(JxlChunkedFrameInputSource& input, size_t xpos, size_t ypos, |
177 | 283 | size_t xsize, size_t ysize, size_t* row_offset) { |
178 | 283 | return ScopedInputBuffer( |
179 | 283 | input.get_color_channel_data_at(input.opaque, xpos, ypos, xsize, ysize, |
180 | 283 | row_offset), |
181 | 283 | [&input](const void* p) { input.release_buffer(input.opaque, p); }); Unexecuted instantiation: encode.cc:jxl::GetColorBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*)::{lambda(void const*)#1}::operator()(void const*) const enc_frame.cc:jxl::GetColorBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*)::{lambda(void const*)#1}::operator()(void const*) const Line | Count | Source | 181 | 283 | [&input](const void* p) { input.release_buffer(input.opaque, p); }); |
|
182 | 283 | } Unexecuted instantiation: encode.cc:jxl::GetColorBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Unexecuted instantiation: enc_fast_lossless.cc:jxl::GetColorBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) enc_frame.cc:jxl::GetColorBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Line | Count | Source | 177 | 283 | size_t xsize, size_t ysize, size_t* row_offset) { | 178 | 283 | return ScopedInputBuffer( | 179 | 283 | input.get_color_channel_data_at(input.opaque, xpos, ypos, xsize, ysize, | 180 | 283 | row_offset), | 181 | 283 | [&input](const void* p) { input.release_buffer(input.opaque, p); }); | 182 | 283 | } |
Unexecuted instantiation: enc_patch_dictionary.cc:jxl::GetColorBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Unexecuted instantiation: enc_cache.cc:jxl::GetColorBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) |
183 | | |
184 | | static JXL_INLINE ScopedInputBuffer GetExtraChannelBuffer( |
185 | | JxlChunkedFrameInputSource& input, size_t ec_index, size_t xpos, |
186 | 0 | size_t ypos, size_t xsize, size_t ysize, size_t* row_offset) { |
187 | 0 | return ScopedInputBuffer( |
188 | 0 | input.get_extra_channel_data_at(input.opaque, ec_index, xpos, ypos, xsize, |
189 | 0 | ysize, row_offset), |
190 | 0 | [&input](const void* p) { input.release_buffer(input.opaque, p); }); Unexecuted instantiation: encode.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*)::{lambda(void const*)#1}::operator()(void const*) const Unexecuted instantiation: enc_frame.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*)::{lambda(void const*)#1}::operator()(void const*) const |
191 | 0 | } Unexecuted instantiation: encode.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Unexecuted instantiation: enc_fast_lossless.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Unexecuted instantiation: enc_frame.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Unexecuted instantiation: enc_patch_dictionary.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Unexecuted instantiation: enc_cache.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) |
192 | | |
193 | | // Common adapter for an existing frame input source or a whole-image input |
194 | | // buffer or a parsed JPEG file. |
195 | | class JxlEncoderChunkedFrameAdapter { |
196 | | public: |
197 | | JxlEncoderChunkedFrameAdapter(size_t xs, size_t ys, size_t num_extra_channels) |
198 | 283 | : xsize(xs), ysize(ys), channels_(1 + num_extra_channels) {} |
199 | | |
200 | 0 | void SetInputSource(JxlChunkedFrameInputSource input_source) { |
201 | 0 | input_source_ = input_source; |
202 | 0 | has_input_source_ = true; |
203 | 0 | } |
204 | | |
205 | | bool SetFromBuffer(size_t channel, const uint8_t* buffer, size_t size, |
206 | 283 | JxlPixelFormat format) { |
207 | 283 | if (channel >= channels_.size()) return false; |
208 | 283 | if (!channels_[channel].SetFromBuffer(buffer, size, format, xsize, ysize)) { |
209 | 0 | return false; |
210 | 0 | } |
211 | 283 | if (channel > 0) channels_[channel].CopyBuffer(); |
212 | 283 | return true; |
213 | 283 | } |
214 | | |
215 | 0 | void SetJPEGData(std::unique_ptr<jpeg::JPEGData> jpeg_data) { |
216 | 0 | jpeg_data_ = std::move(jpeg_data); |
217 | 0 | } |
218 | | |
219 | | // NB: after TakeJPEGData it will return false! |
220 | 849 | bool IsJPEG() const { return jpeg_data_ != nullptr; } |
221 | | |
222 | 0 | std::unique_ptr<jpeg::JPEGData> TakeJPEGData() { |
223 | 0 | return std::move(jpeg_data_); |
224 | 0 | } |
225 | | |
226 | 469 | JxlChunkedFrameInputSource GetInputSource() { |
227 | 469 | if (has_input_source_) { |
228 | 0 | return input_source_; |
229 | 0 | } |
230 | 469 | return JxlChunkedFrameInputSource{ |
231 | 469 | this, |
232 | 469 | METHOD_TO_C_CALLBACK( |
233 | 469 | &JxlEncoderChunkedFrameAdapter::GetColorChannelsPixelFormat), |
234 | 469 | METHOD_TO_C_CALLBACK( |
235 | 469 | &JxlEncoderChunkedFrameAdapter::GetColorChannelDataAt), |
236 | 469 | METHOD_TO_C_CALLBACK( |
237 | 469 | &JxlEncoderChunkedFrameAdapter::GetExtraChannelPixelFormat), |
238 | 469 | METHOD_TO_C_CALLBACK( |
239 | 469 | &JxlEncoderChunkedFrameAdapter::GetExtraChannelDataAt), |
240 | 469 | METHOD_TO_C_CALLBACK( |
241 | 469 | &JxlEncoderChunkedFrameAdapter::ReleaseCurrentData)}; |
242 | 469 | } |
243 | | |
244 | 186 | bool CopyBuffers() { |
245 | 186 | if (has_input_source_) { |
246 | 0 | JxlPixelFormat format{4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; |
247 | 0 | input_source_.get_color_channels_pixel_format(input_source_.opaque, |
248 | 0 | &format); |
249 | 0 | size_t row_offset; |
250 | 0 | { |
251 | 0 | auto buffer = |
252 | 0 | GetColorBuffer(input_source_, 0, 0, xsize, ysize, &row_offset); |
253 | 0 | if (!buffer) return false; |
254 | 0 | channels_[0].CopyFromBuffer(buffer.get(), format, xsize, ysize, |
255 | 0 | row_offset); |
256 | 0 | } |
257 | 0 | for (size_t ec = 0; ec + 1 < channels_.size(); ++ec) { |
258 | 0 | input_source_.get_extra_channel_pixel_format(input_source_.opaque, ec, |
259 | 0 | &format); |
260 | 0 | auto buffer = GetExtraChannelBuffer(input_source_, ec, 0, 0, xsize, |
261 | 0 | ysize, &row_offset); |
262 | 0 | if (!buffer) continue; |
263 | 0 | channels_[1 + ec].CopyFromBuffer(buffer.get(), format, xsize, ysize, |
264 | 0 | row_offset); |
265 | 0 | } |
266 | 0 | has_input_source_ = false; |
267 | 186 | } else { |
268 | 186 | channels_[0].CopyBuffer(); |
269 | 186 | } |
270 | 186 | return true; |
271 | 186 | } |
272 | | |
273 | 0 | bool StreamingInput() const { return has_input_source_; } |
274 | | |
275 | | const size_t xsize; |
276 | | const size_t ysize; |
277 | | |
278 | | private: |
279 | 469 | void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) { |
280 | 469 | *pixel_format = channels_[0].format_; |
281 | 469 | } |
282 | | |
283 | | const void* GetColorChannelDataAt(size_t xpos, size_t ypos, size_t x_size, |
284 | 283 | size_t y_size, size_t* row_offset) { |
285 | 283 | return channels_[0].GetDataAt(xpos, ypos, x_size, y_size, row_offset); |
286 | 283 | } |
287 | | |
288 | | void GetExtraChannelPixelFormat(size_t ec_index, |
289 | 0 | JxlPixelFormat* pixel_format) { |
290 | 0 | JXL_DASSERT(1 + ec_index < channels_.size()); |
291 | 0 | *pixel_format = channels_[1 + ec_index].format_; |
292 | 0 | } |
293 | | |
294 | | const void* GetExtraChannelDataAt(size_t ec_index, size_t xpos, size_t ypos, |
295 | | size_t x_size, size_t y_size, |
296 | 0 | size_t* row_offset) { |
297 | 0 | JXL_DASSERT(1 + ec_index < channels_.size()); |
298 | 0 | return channels_[1 + ec_index].GetDataAt(xpos, ypos, x_size, y_size, |
299 | 0 | row_offset); |
300 | 0 | } |
301 | | |
302 | 283 | void ReleaseCurrentData(const void* buffer) { |
303 | | // No dynamic memory is allocated in GetColorChannelDataAt or |
304 | | // GetExtraChannelDataAt. Therefore, no cleanup is required here. |
305 | 283 | } |
306 | | |
307 | | JxlChunkedFrameInputSource input_source_ = {}; |
308 | | bool has_input_source_ = false; |
309 | | std::unique_ptr<jpeg::JPEGData> jpeg_data_; |
310 | | struct Channel { |
311 | | const uint8_t* buffer_ = nullptr; |
312 | | size_t buffer_size_; |
313 | | JxlPixelFormat format_; |
314 | | size_t xsize_; |
315 | | size_t ysize_; |
316 | | size_t bytes_per_pixel_; |
317 | | size_t stride_; |
318 | | std::vector<uint8_t> copy_; |
319 | | |
320 | | void SetFormatAndDimensions(JxlPixelFormat format, size_t x_size, |
321 | 283 | size_t y_size) { |
322 | 283 | format_ = format; |
323 | 283 | xsize_ = x_size; |
324 | 283 | ysize_ = y_size; |
325 | 283 | bytes_per_pixel_ = BytesPerPixel(format_); |
326 | 283 | const size_t last_row_size = xsize_ * bytes_per_pixel_; |
327 | 283 | const size_t align = format_.align; |
328 | 283 | stride_ = (align > 1 ? jxl::DivCeil(last_row_size, align) * align |
329 | 283 | : last_row_size); |
330 | 283 | } |
331 | | |
332 | | bool SetFromBuffer(const uint8_t* buffer, size_t size, |
333 | 283 | JxlPixelFormat format, size_t x_size, size_t y_size) { |
334 | 283 | SetFormatAndDimensions(format, x_size, y_size); |
335 | 283 | buffer_ = buffer; |
336 | 283 | buffer_size_ = size; |
337 | 283 | const size_t min_buffer_size = |
338 | 283 | stride_ * (ysize_ - 1) + xsize_ * bytes_per_pixel_; |
339 | 283 | return min_buffer_size <= size; |
340 | 283 | } |
341 | | |
342 | | void CopyFromBuffer(const void* buffer, JxlPixelFormat format, |
343 | 0 | size_t x_size, size_t y_size, size_t row_offset) { |
344 | 0 | SetFormatAndDimensions(format, x_size, y_size); |
345 | 0 | buffer_ = nullptr; |
346 | 0 | copy_.resize(y_size * stride_); |
347 | 0 | for (size_t y = 0; y < y_size; ++y) { |
348 | 0 | memcpy(copy_.data() + y * stride_, |
349 | 0 | reinterpret_cast<const uint8_t*>(buffer) + y * row_offset, |
350 | 0 | stride_); |
351 | 0 | } |
352 | 0 | } |
353 | | |
354 | 186 | void CopyBuffer() { |
355 | 186 | if (buffer_) { |
356 | 186 | copy_ = std::vector<uint8_t>(buffer_, buffer_ + buffer_size_); |
357 | 186 | buffer_ = nullptr; |
358 | 186 | } |
359 | 186 | } |
360 | | |
361 | | const void* GetDataAt(size_t xpos, size_t ypos, size_t x_size, |
362 | 283 | size_t y_size, size_t* row_offset) const { |
363 | 283 | const uint8_t* buffer = copy_.empty() ? buffer_ : copy_.data(); |
364 | 283 | JXL_DASSERT(ypos + y_size <= ysize_); |
365 | 283 | JXL_DASSERT(xpos + x_size <= xsize_); |
366 | 283 | JXL_DASSERT(buffer); |
367 | 283 | *row_offset = stride_; |
368 | 283 | return buffer + ypos * stride_ + xpos * bytes_per_pixel_; |
369 | 283 | } |
370 | | }; |
371 | | std::vector<Channel> channels_; |
372 | | }; |
373 | | |
374 | | struct JxlEncoderQueuedFrame { |
375 | | JxlEncoderFrameSettingsValues option_values; |
376 | | JxlEncoderChunkedFrameAdapter frame_data; |
377 | | std::vector<uint8_t> ec_initialized; |
378 | | }; |
379 | | |
380 | | struct JxlEncoderQueuedBox { |
381 | | BoxType type; |
382 | | std::vector<uint8_t> contents; |
383 | | bool compress_box; |
384 | | }; |
385 | | |
386 | | using FJXLFrameUniquePtr = |
387 | | std::unique_ptr<JxlFastLosslessFrameState, |
388 | | decltype(&JxlFastLosslessFreeFrameState)>; |
389 | | |
390 | | // Either a frame, or a box, not both. |
391 | | // Can also be a FJXL frame. |
392 | | struct JxlEncoderQueuedInput { |
393 | | explicit JxlEncoderQueuedInput(const JxlMemoryManager& memory_manager) |
394 | 227 | : frame(nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)), |
395 | 227 | box(nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)) {} |
396 | | MemoryManagerUniquePtr<JxlEncoderQueuedFrame> frame; |
397 | | MemoryManagerUniquePtr<JxlEncoderQueuedBox> box; |
398 | | FJXLFrameUniquePtr fast_lossless_frame = {nullptr, |
399 | | JxlFastLosslessFreeFrameState}; |
400 | | }; |
401 | | |
402 | | static constexpr size_t kSmallBoxHeaderSize = 8; |
403 | | static constexpr size_t kLargeBoxHeaderSize = 16; |
404 | | static constexpr size_t kLargeBoxContentSizeThreshold = |
405 | | 0x100000000ull - kSmallBoxHeaderSize; |
406 | | |
407 | | size_t WriteBoxHeader(const jxl::BoxType& type, size_t size, bool unbounded, |
408 | | bool force_large_box, uint8_t* output); |
409 | | |
410 | | // Appends a JXL container box header with given type, size, and unbounded |
411 | | // properties to output. |
412 | | template <typename T> |
413 | | void AppendBoxHeader(const jxl::BoxType& type, size_t size, bool unbounded, |
414 | | T* output) { |
415 | | size_t current_size = output->size(); |
416 | | output->resize(current_size + kLargeBoxHeaderSize); |
417 | | size_t header_size = |
418 | | WriteBoxHeader(type, size, unbounded, /*force_large_box=*/false, |
419 | | output->data() + current_size); |
420 | | output->resize(current_size + header_size); |
421 | | } |
422 | | |
423 | | } // namespace jxl |
424 | | |
425 | | class JxlOutputProcessorBuffer; |
426 | | |
427 | | class JxlEncoderOutputProcessorWrapper { |
428 | | friend class JxlOutputProcessorBuffer; |
429 | | |
430 | | public: |
431 | | explicit JxlEncoderOutputProcessorWrapper(JxlMemoryManager* memory_manager) |
432 | 557 | : memory_manager_(memory_manager) {} |
433 | | JxlEncoderOutputProcessorWrapper(JxlMemoryManager* memory_manager, |
434 | | JxlEncoderOutputProcessor processor) |
435 | 0 | : memory_manager_(memory_manager), |
436 | | external_output_processor_( |
437 | 0 | jxl::make_unique<JxlEncoderOutputProcessor>(processor)) {} |
438 | | |
439 | 0 | bool HasAvailOut() const { return avail_out_ != nullptr; } |
440 | | |
441 | | // Caller can never overwrite a previously-written buffer. Asking for a buffer |
442 | | // with `min_size` such that `position + min_size` overlaps with a |
443 | | // previously-written buffer is invalid. |
444 | | jxl::StatusOr<JxlOutputProcessorBuffer> GetBuffer(size_t min_size, |
445 | | size_t requested_size = 0); |
446 | | |
447 | | jxl::Status Seek(size_t pos); |
448 | | |
449 | | jxl::Status SetFinalizedPosition(); |
450 | | |
451 | 811 | size_t CurrentPosition() const { return position_; } |
452 | | |
453 | | jxl::Status SetAvailOut(uint8_t** next_out, size_t* avail_out); |
454 | | |
455 | 0 | bool WasStopRequested() const { return stop_requested_; } |
456 | 214 | bool OutputProcessorSet() const { |
457 | 214 | return external_output_processor_ != nullptr; |
458 | 214 | } |
459 | 1.01k | bool HasOutputToWrite() const { |
460 | 1.01k | return output_position_ < finalized_position_; |
461 | 1.01k | } |
462 | | |
463 | | // TODO(eustas): consider extra copy elimination |
464 | | jxl::Status CopyOutput(std::vector<uint8_t>& output); |
465 | | |
466 | | private: |
467 | | jxl::Status ReleaseBuffer(size_t bytes_used); |
468 | | |
469 | | // Tries to write all the bytes up to the finalized position. |
470 | | jxl::Status FlushOutput(uint8_t** next_out, size_t* avail_out); |
471 | | |
472 | | bool AppendBufferToExternalProcessor(void* data, size_t count); |
473 | | |
474 | | struct InternalBuffer { |
475 | | explicit InternalBuffer(JxlMemoryManager* memory_manager) |
476 | 701 | : owned_data(memory_manager) { |
477 | 701 | JXL_DASSERT(memory_manager != nullptr); |
478 | 701 | } |
479 | | // Bytes in the range `[output_position_ - start_of_the_buffer, |
480 | | // written_bytes)` need to be flushed out. |
481 | | size_t written_bytes = 0; |
482 | | // If data has been buffered, it is stored in `owned_data`. |
483 | | jxl::PaddedBytes owned_data; |
484 | | }; |
485 | | |
486 | | // Invariant: `internal_buffers_` does not contain chunks that are entirely |
487 | | // below the output position. |
488 | | std::map<size_t, InternalBuffer> internal_buffers_; |
489 | | |
490 | | uint8_t** next_out_ = nullptr; |
491 | | size_t* avail_out_ = nullptr; |
492 | | // Where the next GetBuffer call will write bytes to. |
493 | | size_t position_ = 0; |
494 | | // The position of the last SetFinalizedPosition call. |
495 | | size_t finalized_position_ = 0; |
496 | | // Either the position of the `external_output_processor_` or the position |
497 | | // `next_out_` points to. |
498 | | size_t output_position_ = 0; |
499 | | |
500 | | bool stop_requested_ = false; |
501 | | bool has_buffer_ = false; |
502 | | |
503 | | JxlMemoryManager* memory_manager_; |
504 | | std::unique_ptr<JxlEncoderOutputProcessor> external_output_processor_; |
505 | | }; |
506 | | |
507 | | class JxlOutputProcessorBuffer { |
508 | | public: |
509 | 521 | size_t size() const { return size_; }; |
510 | 76 | uint8_t* data() { return data_; } |
511 | | |
512 | | JxlOutputProcessorBuffer(uint8_t* buffer, size_t size, size_t bytes_used, |
513 | | JxlEncoderOutputProcessorWrapper* wrapper) |
514 | 2.10k | : data_(buffer), |
515 | 2.10k | size_(size), |
516 | 2.10k | bytes_used_(bytes_used), |
517 | 2.10k | wrapper_(wrapper) {} |
518 | 2.10k | ~JxlOutputProcessorBuffer() { |
519 | 2.10k | jxl::Status result = release(); |
520 | 2.10k | (void)result; |
521 | 2.10k | JXL_DASSERT(result); |
522 | 2.10k | } |
523 | | |
524 | | JxlOutputProcessorBuffer(const JxlOutputProcessorBuffer&) = delete; |
525 | | JxlOutputProcessorBuffer(JxlOutputProcessorBuffer&& other) noexcept |
526 | 1.40k | : JxlOutputProcessorBuffer(other.data_, other.size_, other.bytes_used_, |
527 | 1.40k | other.wrapper_) { |
528 | 1.40k | other.data_ = nullptr; |
529 | 1.40k | other.size_ = 0; |
530 | 1.40k | } |
531 | | |
532 | 753 | jxl::Status advance(size_t count) { |
533 | 753 | JXL_ENSURE(count <= size_); |
534 | 753 | data_ += count; |
535 | 753 | size_ -= count; |
536 | 753 | bytes_used_ += count; |
537 | 753 | return true; |
538 | 753 | } |
539 | | |
540 | 2.10k | jxl::Status release() { |
541 | 2.10k | jxl::Status result = jxl::OkStatus(); |
542 | 2.10k | if (this->data_) { |
543 | 701 | result = wrapper_->ReleaseBuffer(bytes_used_); |
544 | 701 | } |
545 | 2.10k | data_ = nullptr; |
546 | 2.10k | size_ = 0; |
547 | 2.10k | return result; |
548 | 2.10k | } |
549 | | |
550 | 677 | jxl::Status append(const void* data, size_t count) { |
551 | 677 | memcpy(data_, data, count); |
552 | 677 | JXL_RETURN_IF_ERROR(advance(count)); |
553 | 677 | return true; |
554 | 677 | } |
555 | | |
556 | | template <typename T> |
557 | 104 | jxl::Status append(const T& data) { |
558 | 104 | static_assert(sizeof(*std::begin(data)) == 1, "Cannot append non-bytes"); |
559 | 104 | JXL_RETURN_IF_ERROR( |
560 | 104 | append(&*std::begin(data), std::end(data) - std::begin(data))); |
561 | 104 | return true; |
562 | 104 | } jxl::Status JxlOutputProcessorBuffer::append<std::__1::array<unsigned char, 32ul> >(std::__1::array<unsigned char, 32ul> const&) Line | Count | Source | 557 | 52 | jxl::Status append(const T& data) { | 558 | 52 | static_assert(sizeof(*std::begin(data)) == 1, "Cannot append non-bytes"); | 559 | 52 | JXL_RETURN_IF_ERROR( | 560 | 52 | append(&*std::begin(data), std::end(data) - std::begin(data))); | 561 | 52 | return true; | 562 | 52 | } |
jxl::Status JxlOutputProcessorBuffer::append<std::__1::array<unsigned char, 8ul> >(std::__1::array<unsigned char, 8ul> const&) Line | Count | Source | 557 | 14 | jxl::Status append(const T& data) { | 558 | 14 | static_assert(sizeof(*std::begin(data)) == 1, "Cannot append non-bytes"); | 559 | 14 | JXL_RETURN_IF_ERROR( | 560 | 14 | append(&*std::begin(data), std::end(data) - std::begin(data))); | 561 | 14 | return true; | 562 | 14 | } |
jxl::Status JxlOutputProcessorBuffer::append<jxl::PaddedBytes>(jxl::PaddedBytes const&) Line | Count | Source | 557 | 38 | jxl::Status append(const T& data) { | 558 | 38 | static_assert(sizeof(*std::begin(data)) == 1, "Cannot append non-bytes"); | 559 | 38 | JXL_RETURN_IF_ERROR( | 560 | 38 | append(&*std::begin(data), std::end(data) - std::begin(data))); | 561 | 38 | return true; | 562 | 38 | } |
|
563 | | |
564 | | JxlOutputProcessorBuffer& operator=(const JxlOutputProcessorBuffer&) = delete; |
565 | | JxlOutputProcessorBuffer& operator=( |
566 | 0 | JxlOutputProcessorBuffer&& other) noexcept { |
567 | 0 | data_ = other.data_; |
568 | 0 | size_ = other.size_; |
569 | 0 | wrapper_ = other.wrapper_; |
570 | 0 | return *this; |
571 | 0 | } |
572 | | |
573 | | private: |
574 | | uint8_t* data_; |
575 | | size_t size_; |
576 | | size_t bytes_used_; |
577 | | JxlEncoderOutputProcessorWrapper* wrapper_; |
578 | | }; |
579 | | |
580 | | template <typename T> |
581 | | jxl::Status AppendData(JxlEncoderOutputProcessorWrapper& output_processor, |
582 | 532 | const T& data) { |
583 | 532 | size_t size = std::end(data) - std::begin(data); |
584 | 532 | size_t written = 0; |
585 | 1.05k | while (written < size) { |
586 | 521 | JXL_ASSIGN_OR_RETURN(auto buffer, |
587 | 521 | output_processor.GetBuffer(1, size - written)); |
588 | 521 | size_t n = std::min(size - written, buffer.size()); |
589 | 521 | JXL_RETURN_IF_ERROR(buffer.append(data.data() + written, n)); |
590 | 521 | written += n; |
591 | 521 | } |
592 | 532 | return jxl::OkStatus(); |
593 | 532 | } jxl::Status AppendData<jxl::PaddedBytes>(JxlEncoderOutputProcessorWrapper&, jxl::PaddedBytes const&) Line | Count | Source | 582 | 469 | const T& data) { | 583 | 469 | size_t size = std::end(data) - std::begin(data); | 584 | 469 | size_t written = 0; | 585 | 927 | while (written < size) { | 586 | 458 | JXL_ASSIGN_OR_RETURN(auto buffer, | 587 | 458 | output_processor.GetBuffer(1, size - written)); | 588 | 458 | size_t n = std::min(size - written, buffer.size()); | 589 | 458 | JXL_RETURN_IF_ERROR(buffer.append(data.data() + written, n)); | 590 | 458 | written += n; | 591 | 458 | } | 592 | 469 | return jxl::OkStatus(); | 593 | 469 | } |
jxl::Status AppendData<std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > >(JxlEncoderOutputProcessorWrapper&, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&) Line | Count | Source | 582 | 63 | const T& data) { | 583 | 63 | size_t size = std::end(data) - std::begin(data); | 584 | 63 | size_t written = 0; | 585 | 126 | while (written < size) { | 586 | 63 | JXL_ASSIGN_OR_RETURN(auto buffer, | 587 | 63 | output_processor.GetBuffer(1, size - written)); | 588 | 63 | size_t n = std::min(size - written, buffer.size()); | 589 | 63 | JXL_RETURN_IF_ERROR(buffer.append(data.data() + written, n)); | 590 | 63 | written += n; | 591 | 63 | } | 592 | 63 | return jxl::OkStatus(); | 593 | 63 | } |
Unexecuted instantiation: jxl::Status AppendData<jxl::Span<unsigned char const> >(JxlEncoderOutputProcessorWrapper&, jxl::Span<unsigned char const> const&) |
594 | | |
595 | | // Internal use only struct, can only be initialized correctly by |
596 | | // JxlEncoderCreate. |
597 | | struct JxlEncoder { |
598 | 230 | JxlEncoder() : output_processor(&memory_manager) {} |
599 | | JxlMemoryManager memory_manager; |
600 | | jxl::MemoryManagerUniquePtr<jxl::ThreadPool> thread_pool{ |
601 | | nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)}; |
602 | | std::vector<jxl::MemoryManagerUniquePtr<JxlEncoderFrameSettings>> |
603 | | encoder_options; |
604 | | |
605 | | size_t num_queued_frames; |
606 | | size_t num_queued_boxes; |
607 | | std::vector<jxl::JxlEncoderQueuedInput> input_queue; |
608 | | JxlEncoderOutputProcessorWrapper output_processor; |
609 | | |
610 | | // How many codestream bytes have been written, i.e., |
611 | | // content of jxlc and jxlp boxes. Frame index box jxli |
612 | | // requires position indices to point to codestream bytes, |
613 | | // so we need to keep track of the total of flushed or queue |
614 | | // codestream bytes. These bytes may be in a single jxlc box |
615 | | // or across multiple jxlp boxes. |
616 | | size_t codestream_bytes_written_end_of_frame; |
617 | | jxl::JxlEncoderFrameIndexBox frame_index_box; |
618 | | |
619 | | JxlCmsInterface cms; |
620 | | bool cms_set; |
621 | | |
622 | | // Force using the container even if not needed |
623 | | bool use_container; |
624 | | // User declared they will add metadata boxes |
625 | | bool use_boxes; |
626 | | |
627 | | // TODO(lode): move level into jxl::CompressParams since some C++ |
628 | | // implementation decisions should be based on it: level 10 allows more |
629 | | // features to be used. |
630 | | bool store_jpeg_metadata; |
631 | | int32_t codestream_level; |
632 | | jxl::CodecMetadata metadata; |
633 | | std::vector<uint8_t> jpeg_metadata; |
634 | | |
635 | | jxl::CompressParams last_used_cparams; |
636 | | JxlBasicInfo basic_info; |
637 | | |
638 | | JxlEncoderError error = JxlEncoderError::JXL_ENC_ERR_OK; |
639 | | |
640 | | // Encoder wrote a jxlp (partial codestream) box, so any next codestream |
641 | | // parts must also be written in jxlp boxes, a single jxlc box cannot be |
642 | | // used. The counter is used for the 4-byte jxlp box index header. |
643 | | size_t jxlp_counter; |
644 | | |
645 | | // Wrote any output at all, so wrote the data before the first user added |
646 | | // frame or box, such as signature, basic info, ICC profile or jpeg |
647 | | // reconstruction box. |
648 | | bool wrote_bytes; |
649 | | |
650 | | bool frames_closed; |
651 | | bool boxes_closed; |
652 | | bool basic_info_set; |
653 | | bool color_encoding_set; |
654 | | bool intensity_target_set; |
655 | | bool allow_expert_options = false; |
656 | | int brotli_effort = -1; |
657 | | |
658 | | // Takes the first frame in the input_queue, encodes it, and appends |
659 | | // the bytes to the output_byte_queue. |
660 | | jxl::Status ProcessOneEnqueuedInput(); |
661 | | |
662 | 585 | bool MustUseContainer() const { |
663 | 585 | return use_container || (codestream_level != 5 && codestream_level != -1) || |
664 | 585 | store_jpeg_metadata || use_boxes; |
665 | 585 | } |
666 | | |
667 | | // `write_box` must never seek before the position the output wrapper was at |
668 | | // the moment of the call, and must leave the output wrapper such that its |
669 | | // position is one byte past the end of the written box. |
670 | | template <typename WriteBox> |
671 | | jxl::Status AppendBox(const jxl::BoxType& type, bool unbounded, |
672 | | size_t box_max_size, const WriteBox& write_box); |
673 | | |
674 | | template <typename BoxContents> |
675 | | jxl::Status AppendBoxWithContents(const jxl::BoxType& type, |
676 | | const BoxContents& contents); |
677 | | }; |
678 | | |
679 | | struct JxlEncoderFrameSettings { |
680 | | JxlEncoder* enc; |
681 | | jxl::JxlEncoderFrameSettingsValues values; |
682 | | }; |
683 | | |
684 | | struct JxlEncoderStats { |
685 | | std::unique_ptr<jxl::AuxOut> aux_out; |
686 | | }; |
687 | | |
688 | | #endif // LIB_JXL_ENCODE_INTERNAL_H_ |