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