/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 | 2.73k | bool StoreFrameIndexBox() { |
84 | 2.73k | for (auto e : entries) { |
85 | 2.73k | if (e.to_be_indexed) { |
86 | 0 | return true; |
87 | 0 | } |
88 | 2.73k | } |
89 | 2.73k | return false; |
90 | 2.73k | } |
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 | 2.82k | 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 | 2.82k | 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 | 2.82k | JxlEncoderFrameIndexBoxEntry e; |
114 | 2.82k | e.to_be_indexed = to_be_indexed; |
115 | 2.82k | e.OFFi = OFFi; |
116 | 2.82k | e.duration = duration; |
117 | 2.82k | entries.push_back(e); |
118 | 2.82k | } |
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 | 2.39k | constexpr BoxType MakeBoxType(const char* type) { |
141 | 2.39k | return BoxType( |
142 | 2.39k | {{static_cast<uint8_t>(type[0]), static_cast<uint8_t>(type[1]), |
143 | 2.39k | static_cast<uint8_t>(type[2]), static_cast<uint8_t>(type[3])}}); |
144 | 2.39k | } |
145 | | |
146 | | |
147 | 8.01k | static JXL_INLINE size_t BitsPerChannel(JxlDataType data_type) { |
148 | 8.01k | switch (data_type) { |
149 | 2.90k | case JXL_TYPE_UINT8: |
150 | 2.90k | return 8; |
151 | 2.10k | case JXL_TYPE_UINT16: |
152 | 2.10k | return 16; |
153 | 3.00k | case JXL_TYPE_FLOAT: |
154 | 3.00k | return 32; |
155 | 0 | case JXL_TYPE_FLOAT16: |
156 | 0 | return 16; |
157 | 0 | default: |
158 | 0 | return 0; // signals unhandled JxlDataType |
159 | 8.01k | } |
160 | 8.01k | } encode.cc:jxl::BitsPerChannel(JxlDataType) Line | Count | Source | 147 | 4.00k | static JXL_INLINE size_t BitsPerChannel(JxlDataType data_type) { | 148 | 4.00k | switch (data_type) { | 149 | 1.45k | case JXL_TYPE_UINT8: | 150 | 1.45k | return 8; | 151 | 1.05k | case JXL_TYPE_UINT16: | 152 | 1.05k | return 16; | 153 | 1.50k | case JXL_TYPE_FLOAT: | 154 | 1.50k | return 32; | 155 | 0 | case JXL_TYPE_FLOAT16: | 156 | 0 | return 16; | 157 | 0 | default: | 158 | 0 | return 0; // signals unhandled JxlDataType | 159 | 4.00k | } | 160 | 4.00k | } |
Unexecuted instantiation: enc_fast_lossless.cc:jxl::BitsPerChannel(JxlDataType) enc_frame.cc:jxl::BitsPerChannel(JxlDataType) Line | Count | Source | 147 | 4.00k | static JXL_INLINE size_t BitsPerChannel(JxlDataType data_type) { | 148 | 4.00k | switch (data_type) { | 149 | 1.45k | case JXL_TYPE_UINT8: | 150 | 1.45k | return 8; | 151 | 1.05k | case JXL_TYPE_UINT16: | 152 | 1.05k | return 16; | 153 | 1.50k | case JXL_TYPE_FLOAT: | 154 | 1.50k | return 32; | 155 | 0 | case JXL_TYPE_FLOAT16: | 156 | 0 | return 16; | 157 | 0 | default: | 158 | 0 | return 0; // signals unhandled JxlDataType | 159 | 4.00k | } | 160 | 4.00k | } |
Unexecuted instantiation: enc_patch_dictionary.cc:jxl::BitsPerChannel(JxlDataType) Unexecuted instantiation: enc_cache.cc:jxl::BitsPerChannel(JxlDataType) |
161 | | |
162 | 4.00k | static JXL_INLINE size_t BytesPerPixel(JxlPixelFormat format) { |
163 | 4.00k | return format.num_channels * BitsPerChannel(format.data_type) / 8; |
164 | 4.00k | } encode.cc:jxl::BytesPerPixel(JxlPixelFormat) Line | Count | Source | 162 | 4.00k | static JXL_INLINE size_t BytesPerPixel(JxlPixelFormat format) { | 163 | 4.00k | return format.num_channels * BitsPerChannel(format.data_type) / 8; | 164 | 4.00k | } |
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 | 3.66k | size_t xsize, size_t ysize, size_t* row_offset) { |
172 | 3.66k | return ScopedInputBuffer( |
173 | 3.66k | input.get_color_channel_data_at(input.opaque, xpos, ypos, xsize, ysize, |
174 | 3.66k | row_offset), |
175 | 3.66k | [&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 | 3.66k | [&input](const void* p) { input.release_buffer(input.opaque, p); }); |
|
176 | 3.66k | } 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 | 3.66k | size_t xsize, size_t ysize, size_t* row_offset) { | 172 | 3.66k | return ScopedInputBuffer( | 173 | 3.66k | input.get_color_channel_data_at(input.opaque, xpos, ypos, xsize, ysize, | 174 | 3.66k | row_offset), | 175 | 3.66k | [&input](const void* p) { input.release_buffer(input.opaque, p); }); | 176 | 3.66k | } |
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 | 339 | size_t ypos, size_t xsize, size_t ysize, size_t* row_offset) { |
181 | 339 | return ScopedInputBuffer( |
182 | 339 | input.get_extra_channel_data_at(input.opaque, ec_index, xpos, ypos, xsize, |
183 | 339 | ysize, row_offset), |
184 | 339 | [&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*) constenc_frame.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*)::{lambda(void const*)#1}::operator()(void const*) constLine | Count | Source | 184 | 339 | [&input](const void* p) { input.release_buffer(input.opaque, p); }); |
|
185 | 339 | } 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*) enc_frame.cc:jxl::GetExtraChannelBuffer(JxlChunkedFrameInputSource&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long*) Line | Count | Source | 180 | 339 | size_t ypos, size_t xsize, size_t ysize, size_t* row_offset) { | 181 | 339 | return ScopedInputBuffer( | 182 | 339 | input.get_extra_channel_data_at(input.opaque, ec_index, xpos, ypos, xsize, | 183 | 339 | ysize, row_offset), | 184 | 339 | [&input](const void* p) { input.release_buffer(input.opaque, p); }); | 185 | 339 | } |
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 | 3.66k | : 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 | 4.00k | JxlPixelFormat format) { |
201 | 4.00k | if (channel >= channels_.size()) return false; |
202 | 4.00k | if (!channels_[channel].SetFromBuffer(buffer, size, format, xsize, ysize)) { |
203 | 0 | return false; |
204 | 0 | } |
205 | 4.00k | if (channel > 0) channels_[channel].CopyBuffer(); |
206 | 4.00k | return true; |
207 | 4.00k | } |
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 | 11.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 | 6.49k | JxlChunkedFrameInputSource GetInputSource() { |
221 | 6.49k | if (has_input_source_) { |
222 | 0 | return input_source_; |
223 | 0 | } |
224 | 6.49k | return JxlChunkedFrameInputSource{ |
225 | 6.49k | this, |
226 | 6.49k | METHOD_TO_C_CALLBACK( |
227 | 6.49k | &JxlEncoderChunkedFrameAdapter::GetColorChannelsPixelFormat), |
228 | 6.49k | METHOD_TO_C_CALLBACK( |
229 | 6.49k | &JxlEncoderChunkedFrameAdapter::GetColorChannelDataAt), |
230 | 6.49k | METHOD_TO_C_CALLBACK( |
231 | 6.49k | &JxlEncoderChunkedFrameAdapter::GetExtraChannelPixelFormat), |
232 | 6.49k | METHOD_TO_C_CALLBACK( |
233 | 6.49k | &JxlEncoderChunkedFrameAdapter::GetExtraChannelDataAt), |
234 | 6.49k | METHOD_TO_C_CALLBACK( |
235 | 6.49k | &JxlEncoderChunkedFrameAdapter::ReleaseCurrentData)}; |
236 | 6.49k | } |
237 | | |
238 | 2.82k | bool CopyBuffers() { |
239 | 2.82k | 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 | format.align = 0; // .align must be ignored |
244 | 0 | size_t row_offset; |
245 | 0 | { |
246 | 0 | auto buffer = |
247 | 0 | GetColorBuffer(input_source_, 0, 0, xsize, ysize, &row_offset); |
248 | 0 | if (!buffer) return false; |
249 | 0 | if (!channels_[0].CopyFromBuffer(buffer.get(), format, xsize, ysize, |
250 | 0 | row_offset)) { |
251 | 0 | return false; |
252 | 0 | } |
253 | 0 | } |
254 | 0 | for (size_t ec = 0; ec + 1 < channels_.size(); ++ec) { |
255 | 0 | input_source_.get_extra_channel_pixel_format(input_source_.opaque, ec, |
256 | 0 | &format); |
257 | 0 | format.align = 0; // .align must be ignored |
258 | 0 | auto buffer = GetExtraChannelBuffer(input_source_, ec, 0, 0, xsize, |
259 | 0 | ysize, &row_offset); |
260 | 0 | if (!buffer) continue; |
261 | 0 | if (!channels_[1 + ec].CopyFromBuffer(buffer.get(), format, xsize, |
262 | 0 | ysize, row_offset)) { |
263 | 0 | return false; |
264 | 0 | } |
265 | 0 | } |
266 | 0 | has_input_source_ = false; |
267 | 2.82k | } else { |
268 | 2.82k | channels_[0].CopyBuffer(); |
269 | 2.82k | } |
270 | 2.82k | return true; |
271 | 2.82k | } |
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 | 6.49k | void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) { |
280 | 6.49k | *pixel_format = channels_[0].format_; |
281 | 6.49k | } |
282 | | |
283 | | const void* GetColorChannelDataAt(size_t xpos, size_t ypos, size_t x_size, |
284 | 3.66k | size_t y_size, size_t* row_offset) { |
285 | 3.66k | return channels_[0].GetDataAt(xpos, ypos, x_size, y_size, row_offset); |
286 | 3.66k | } |
287 | | |
288 | | void GetExtraChannelPixelFormat(size_t ec_index, |
289 | 339 | JxlPixelFormat* pixel_format) { |
290 | 339 | JXL_DASSERT(1 + ec_index < channels_.size()); |
291 | 339 | *pixel_format = channels_[1 + ec_index].format_; |
292 | 339 | } |
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 | 339 | size_t* row_offset) { |
297 | 339 | JXL_DASSERT(1 + ec_index < channels_.size()); |
298 | 339 | return channels_[1 + ec_index].GetDataAt(xpos, ypos, x_size, y_size, |
299 | 339 | row_offset); |
300 | 339 | } |
301 | | |
302 | 4.00k | void ReleaseCurrentData(const void* buffer) { |
303 | | // No dynamic memory is allocated in GetColorChannelDataAt or |
304 | | // GetExtraChannelDataAt. Therefore, no cleanup is required here. |
305 | 4.00k | } |
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 | | bool SetFormatAndDimensions(JxlPixelFormat format, size_t x_size, |
321 | 4.00k | size_t y_size) { |
322 | 4.00k | format_ = format; |
323 | 4.00k | xsize_ = x_size; |
324 | 4.00k | ysize_ = y_size; |
325 | 4.00k | bytes_per_pixel_ = BytesPerPixel(format_); |
326 | 4.00k | size_t last_row_size; |
327 | 4.00k | if (!SafeMul(xsize_, bytes_per_pixel_, last_row_size)) return false; |
328 | 4.00k | if (!SafeRoundUpTo(last_row_size, format_.align, stride_)) return false; |
329 | 4.00k | size_t total_size; |
330 | 4.00k | if (!SafeMul(ysize_, stride_, total_size)) return false; |
331 | 4.00k | return true; |
332 | 4.00k | } |
333 | | |
334 | | bool SetFromBuffer(const uint8_t* buffer, size_t size, |
335 | 4.00k | JxlPixelFormat format, size_t x_size, size_t y_size) { |
336 | 4.00k | if (!SetFormatAndDimensions(format, x_size, y_size)) return false; |
337 | 4.00k | if (ysize_ == 0) return false; |
338 | 4.00k | buffer_ = buffer; |
339 | 4.00k | buffer_size_ = size; |
340 | | // Safe: SetFormatAndDimensions() checked ysize_ * stride_. |
341 | 4.00k | const size_t min_buffer_size = |
342 | 4.00k | stride_ * (ysize_ - 1) + xsize_ * bytes_per_pixel_; |
343 | 4.00k | return min_buffer_size <= size; |
344 | 4.00k | } |
345 | | |
346 | | bool CopyFromBuffer(const void* buffer, JxlPixelFormat format, |
347 | 0 | size_t x_size, size_t y_size, size_t row_offset) { |
348 | 0 | if (!SetFormatAndDimensions(format, x_size, y_size)) return false; |
349 | 0 | JXL_ENSURE(stride_ <= row_offset); |
350 | 0 | buffer_ = nullptr; |
351 | | // Safe: SetFormatAndDimensions() checked y_size * stride_. |
352 | 0 | copy_.resize(y_size * stride_); |
353 | 0 | for (size_t y = 0; y < y_size; ++y) { |
354 | 0 | memcpy(copy_.data() + y * stride_, |
355 | 0 | reinterpret_cast<const uint8_t*>(buffer) + y * row_offset, |
356 | 0 | stride_); |
357 | 0 | } |
358 | 0 | return true; |
359 | 0 | } |
360 | | |
361 | 3.16k | void CopyBuffer() { |
362 | 3.16k | if (buffer_) { |
363 | 3.16k | copy_ = std::vector<uint8_t>(buffer_, buffer_ + buffer_size_); |
364 | 3.16k | buffer_ = nullptr; |
365 | 3.16k | } |
366 | 3.16k | } |
367 | | |
368 | | const void* GetDataAt(size_t xpos, size_t ypos, size_t x_size, |
369 | 4.00k | size_t y_size, size_t* row_offset) const { |
370 | 4.00k | const uint8_t* buffer = copy_.empty() ? buffer_ : copy_.data(); |
371 | 4.00k | JXL_DASSERT(ypos + y_size <= ysize_); |
372 | 4.00k | JXL_DASSERT(xpos + x_size <= xsize_); |
373 | 4.00k | JXL_DASSERT(buffer); |
374 | 4.00k | *row_offset = stride_; |
375 | 4.00k | return buffer + ypos * stride_ + xpos * bytes_per_pixel_; |
376 | 4.00k | } |
377 | | }; |
378 | | std::vector<Channel> channels_; |
379 | | }; |
380 | | |
381 | | struct JxlEncoderQueuedFrame { |
382 | | JxlEncoderFrameSettingsValues option_values; |
383 | | JxlEncoderChunkedFrameAdapter frame_data; |
384 | | std::vector<uint8_t> ec_initialized; |
385 | | }; |
386 | | |
387 | | struct JxlEncoderQueuedBox { |
388 | | BoxType type; |
389 | | std::vector<uint8_t> contents; |
390 | | bool compress_box; |
391 | | }; |
392 | | |
393 | | using FJXLFrameUniquePtr = |
394 | | std::unique_ptr<JxlFastLosslessFrameState, |
395 | | decltype(&JxlFastLosslessFreeFrameState)>; |
396 | | |
397 | | // Either a frame, or a box, not both. |
398 | | // Can also be a FJXL frame. |
399 | | struct JxlEncoderQueuedInput { |
400 | | explicit JxlEncoderQueuedInput(const JxlMemoryManager& memory_manager) |
401 | 2.86k | : frame(nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)), |
402 | 2.86k | box(nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)) {} |
403 | | MemoryManagerUniquePtr<JxlEncoderQueuedFrame> frame; |
404 | | MemoryManagerUniquePtr<JxlEncoderQueuedBox> box; |
405 | | FJXLFrameUniquePtr fast_lossless_frame = {nullptr, |
406 | | JxlFastLosslessFreeFrameState}; |
407 | | int output_mode = -1; // effective output mode, resolved at queue time |
408 | | }; |
409 | | |
410 | | static constexpr size_t kSmallBoxHeaderSize = 8; |
411 | | static constexpr size_t kLargeBoxHeaderSize = 16; |
412 | | static constexpr size_t kLargeBoxContentSizeThreshold = |
413 | | 0x100000000ull - kSmallBoxHeaderSize; |
414 | | |
415 | | size_t WriteBoxHeader(const jxl::BoxType& type, size_t size, bool unbounded, |
416 | | bool force_large_box, uint8_t* output); |
417 | | |
418 | | // Appends a JXL container box header with given type, size, and unbounded |
419 | | // properties to output. |
420 | | template <typename T> |
421 | | void AppendBoxHeader(const jxl::BoxType& type, size_t size, bool unbounded, |
422 | 1.58k | T* output) { |
423 | 1.58k | size_t current_size = output->size(); |
424 | 1.58k | output->resize(current_size + kLargeBoxHeaderSize); |
425 | 1.58k | size_t header_size = |
426 | 1.58k | WriteBoxHeader(type, size, unbounded, /*force_large_box=*/false, |
427 | 1.58k | output->data() + current_size); |
428 | 1.58k | output->resize(current_size + header_size); |
429 | 1.58k | } |
430 | | |
431 | | // Returns the JXL container signature box and ftyp box. |
432 | | // ftyp_version: 0 = standard delivery order, 1 = out-of-order jxlp boxes. |
433 | 811 | inline std::vector<uint8_t> MakeContainerHeader(int ftyp_version) { |
434 | 811 | std::vector<uint8_t> out(kJxlSignatureBox.begin(), kJxlSignatureBox.end()); |
435 | | // ftyp box: major brand "jxl ", minor version, compatible brand "jxl ". |
436 | 811 | const uint8_t ftyp[] = {'j', 'x', 'l', ' ', |
437 | 811 | 0, 0, 0, static_cast<uint8_t>(ftyp_version), |
438 | 811 | 'j', 'x', 'l', ' '}; |
439 | 811 | AppendBoxHeader(MakeBoxType("ftyp"), sizeof(ftyp), /*unbounded=*/false, &out); |
440 | 811 | out.insert(out.end(), ftyp, ftyp + sizeof(ftyp)); |
441 | 811 | return out; |
442 | 811 | } |
443 | | |
444 | | } // namespace jxl |
445 | | |
446 | | class JxlOutputProcessorBuffer; |
447 | | |
448 | | class JxlEncoderOutputProcessorWrapper { |
449 | | friend class JxlOutputProcessorBuffer; |
450 | | |
451 | | public: |
452 | | explicit JxlEncoderOutputProcessorWrapper(JxlMemoryManager* memory_manager) |
453 | 6.57k | : memory_manager_(memory_manager) {} |
454 | | JxlEncoderOutputProcessorWrapper(JxlMemoryManager* memory_manager, |
455 | | JxlEncoderOutputProcessor processor) |
456 | 0 | : memory_manager_(memory_manager), |
457 | | external_output_processor_( |
458 | 0 | jxl::make_unique<JxlEncoderOutputProcessor>(processor)) {} |
459 | | |
460 | 0 | bool HasAvailOut() const { return avail_out_ != nullptr; } |
461 | | |
462 | | // Caller can never overwrite a previously-written buffer. Asking for a buffer |
463 | | // with `min_size` such that `position + min_size` overlaps with a |
464 | | // previously-written buffer is invalid. |
465 | | jxl::StatusOr<JxlOutputProcessorBuffer> GetBuffer(size_t min_size, |
466 | | size_t requested_size = 0); |
467 | | |
468 | | jxl::Status Seek(size_t pos); |
469 | | |
470 | | jxl::Status SetFinalizedPosition(); |
471 | | |
472 | 9.36k | size_t CurrentPosition() const { return position_; } |
473 | | |
474 | | jxl::Status SetAvailOut(uint8_t** next_out, size_t* avail_out); |
475 | | |
476 | 0 | bool WasStopRequested() const { return stop_requested_; } |
477 | 2.87k | bool OutputProcessorSet() const { |
478 | 2.87k | return external_output_processor_ != nullptr; |
479 | 2.87k | } |
480 | 9.04k | bool HasOutputToWrite() const { |
481 | 9.04k | return output_position_ < finalized_position_; |
482 | 9.04k | } |
483 | | |
484 | | // TODO(eustas): consider extra copy elimination |
485 | | jxl::Status CopyOutput(std::vector<uint8_t>& output); |
486 | | |
487 | | private: |
488 | | jxl::Status ReleaseBuffer(size_t bytes_used); |
489 | | |
490 | | // Tries to write all the bytes up to the finalized position. |
491 | | jxl::Status FlushOutput(uint8_t** next_out, size_t* avail_out); |
492 | | |
493 | | bool AppendBufferToExternalProcessor(void* data, size_t count); |
494 | | |
495 | | struct InternalBuffer { |
496 | | explicit InternalBuffer(JxlMemoryManager* memory_manager) |
497 | 11.2k | : owned_data(memory_manager) { |
498 | 11.2k | JXL_DASSERT(memory_manager != nullptr); |
499 | 11.2k | } |
500 | | // Bytes in the range `[output_position_ - start_of_the_buffer, |
501 | | // written_bytes)` need to be flushed out. |
502 | | size_t written_bytes = 0; |
503 | | // If data has been buffered, it is stored in `owned_data`. |
504 | | jxl::PaddedBytes owned_data; |
505 | | }; |
506 | | |
507 | | // Invariant: `internal_buffers_` does not contain chunks that are entirely |
508 | | // below the output position. |
509 | | std::map<size_t, InternalBuffer> internal_buffers_; |
510 | | |
511 | | uint8_t** next_out_ = nullptr; |
512 | | size_t* avail_out_ = nullptr; |
513 | | // Where the next GetBuffer call will write bytes to. |
514 | | size_t position_ = 0; |
515 | | // The position of the last SetFinalizedPosition call. |
516 | | size_t finalized_position_ = 0; |
517 | | // Either the position of the `external_output_processor_` or the position |
518 | | // `next_out_` points to. |
519 | | size_t output_position_ = 0; |
520 | | |
521 | | bool stop_requested_ = false; |
522 | | bool has_buffer_ = false; |
523 | | |
524 | | JxlMemoryManager* memory_manager_; |
525 | | std::unique_ptr<JxlEncoderOutputProcessor> external_output_processor_; |
526 | | }; |
527 | | |
528 | | class JxlOutputProcessorBuffer { |
529 | | public: |
530 | 11.1k | size_t size() const { return size_; }; |
531 | 82 | uint8_t* data() { return data_; } |
532 | | |
533 | | JxlOutputProcessorBuffer(uint8_t* buffer, size_t size, size_t bytes_used, |
534 | | JxlEncoderOutputProcessorWrapper* wrapper) |
535 | 33.8k | : data_(buffer), |
536 | 33.8k | size_(size), |
537 | 33.8k | bytes_used_(bytes_used), |
538 | 33.8k | wrapper_(wrapper) {} |
539 | 33.8k | ~JxlOutputProcessorBuffer() { |
540 | 33.8k | jxl::Status result = release(); |
541 | 33.8k | (void)result; |
542 | 33.8k | JXL_DASSERT(result); |
543 | 33.8k | } |
544 | | |
545 | | JxlOutputProcessorBuffer(const JxlOutputProcessorBuffer&) = delete; |
546 | | JxlOutputProcessorBuffer(JxlOutputProcessorBuffer&& other) noexcept |
547 | 22.5k | : JxlOutputProcessorBuffer(other.data_, other.size_, other.bytes_used_, |
548 | 22.5k | other.wrapper_) { |
549 | 22.5k | other.data_ = nullptr; |
550 | 22.5k | other.size_ = 0; |
551 | 22.5k | } |
552 | | |
553 | 11.3k | jxl::Status advance(size_t count) { |
554 | 11.3k | JXL_ENSURE(count <= size_); |
555 | 11.3k | data_ += count; |
556 | 11.3k | size_ -= count; |
557 | 11.3k | bytes_used_ += count; |
558 | 11.3k | return true; |
559 | 11.3k | } |
560 | | |
561 | 33.8k | jxl::Status release() { |
562 | 33.8k | jxl::Status result = jxl::OkStatus(); |
563 | 33.8k | if (this->data_) { |
564 | 11.2k | result = wrapper_->ReleaseBuffer(bytes_used_); |
565 | 11.2k | } |
566 | 33.8k | data_ = nullptr; |
567 | 33.8k | size_ = 0; |
568 | 33.8k | return result; |
569 | 33.8k | } |
570 | | |
571 | 11.2k | jxl::Status append(const void* data, size_t count) { |
572 | 11.2k | memcpy(data_, data, count); |
573 | 11.2k | JXL_RETURN_IF_ERROR(advance(count)); |
574 | 11.2k | return true; |
575 | 11.2k | } |
576 | | |
577 | | template <typename T> |
578 | 41 | jxl::Status append(const T& data) { |
579 | 41 | static_assert(sizeof(*std::begin(data)) == 1, "Cannot append non-bytes"); |
580 | 41 | JXL_RETURN_IF_ERROR( |
581 | 41 | append(&*std::begin(data), std::end(data) - std::begin(data))); |
582 | 41 | return true; |
583 | 41 | } |
584 | | |
585 | | JxlOutputProcessorBuffer& operator=(const JxlOutputProcessorBuffer&) = delete; |
586 | | JxlOutputProcessorBuffer& operator=( |
587 | 0 | JxlOutputProcessorBuffer&& other) noexcept { |
588 | 0 | data_ = other.data_; |
589 | 0 | size_ = other.size_; |
590 | 0 | wrapper_ = other.wrapper_; |
591 | 0 | return *this; |
592 | 0 | } |
593 | | |
594 | | private: |
595 | | uint8_t* data_; |
596 | | size_t size_; |
597 | | size_t bytes_used_; |
598 | | JxlEncoderOutputProcessorWrapper* wrapper_; |
599 | | }; |
600 | | |
601 | | template <typename T> |
602 | | jxl::Status AppendData(JxlEncoderOutputProcessorWrapper& output_processor, |
603 | 11.6k | const T& data) { |
604 | 11.6k | size_t size = std::end(data) - std::begin(data); |
605 | 11.6k | size_t written = 0; |
606 | 22.7k | while (written < size) { |
607 | 11.1k | JXL_ASSIGN_OR_RETURN(auto buffer, |
608 | 11.1k | output_processor.GetBuffer(1, size - written)); |
609 | 11.1k | size_t n = std::min(size - written, buffer.size()); |
610 | 11.1k | JXL_RETURN_IF_ERROR(buffer.append(data.data() + written, n)); |
611 | 11.1k | written += n; |
612 | 11.1k | } |
613 | 11.6k | return jxl::OkStatus(); |
614 | 11.6k | } 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 | 603 | 2.35k | const T& data) { | 604 | 2.35k | size_t size = std::end(data) - std::begin(data); | 605 | 2.35k | size_t written = 0; | 606 | 4.71k | while (written < size) { | 607 | 2.35k | JXL_ASSIGN_OR_RETURN(auto buffer, | 608 | 2.35k | output_processor.GetBuffer(1, size - written)); | 609 | 2.35k | size_t n = std::min(size - written, buffer.size()); | 610 | 2.35k | JXL_RETURN_IF_ERROR(buffer.append(data.data() + written, n)); | 611 | 2.35k | written += n; | 612 | 2.35k | } | 613 | 2.35k | return jxl::OkStatus(); | 614 | 2.35k | } |
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 | 603 | 9.28k | const T& data) { | 604 | 9.28k | size_t size = std::end(data) - std::begin(data); | 605 | 9.28k | size_t written = 0; | 606 | 18.0k | while (written < size) { | 607 | 8.78k | JXL_ASSIGN_OR_RETURN(auto buffer, | 608 | 8.78k | output_processor.GetBuffer(1, size - written)); | 609 | 8.78k | size_t n = std::min(size - written, buffer.size()); | 610 | 8.78k | JXL_RETURN_IF_ERROR(buffer.append(data.data() + written, n)); | 611 | 8.78k | written += n; | 612 | 8.78k | } | 613 | 9.28k | return jxl::OkStatus(); | 614 | 9.28k | } |
|
615 | | |
616 | | // Internal use only struct, can only be initialized correctly by |
617 | | // JxlEncoderCreate. |
618 | | struct JxlEncoder { |
619 | 2.86k | JxlEncoder() : output_processor(&memory_manager) {} |
620 | | JxlMemoryManager memory_manager; |
621 | | jxl::MemoryManagerUniquePtr<jxl::ThreadPool> thread_pool{ |
622 | | nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)}; |
623 | | std::vector<jxl::MemoryManagerUniquePtr<JxlEncoderFrameSettings>> |
624 | | encoder_options; |
625 | | |
626 | | size_t num_queued_frames; |
627 | | size_t num_queued_boxes; |
628 | | std::vector<jxl::JxlEncoderQueuedInput> input_queue; |
629 | | JxlEncoderOutputProcessorWrapper output_processor; |
630 | | |
631 | | // How many codestream bytes have been written, i.e., |
632 | | // content of jxlc and jxlp boxes. Frame index box jxli |
633 | | // requires position indices to point to codestream bytes, |
634 | | // so we need to keep track of the total of flushed or queue |
635 | | // codestream bytes. These bytes may be in a single jxlc box |
636 | | // or across multiple jxlp boxes. |
637 | | size_t codestream_bytes_written_end_of_frame; |
638 | | jxl::JxlEncoderFrameIndexBox frame_index_box; |
639 | | |
640 | | JxlCmsInterface cms; |
641 | | bool cms_set; |
642 | | |
643 | | // Force using the container even if not needed |
644 | | bool use_container; |
645 | | // User declared they will add metadata boxes |
646 | | bool use_boxes; |
647 | | // -1 = no container written yet; 0 = ftyp v0 written; 1 = ftyp v1 written. |
648 | | int container_ftyp_version = -1; |
649 | | |
650 | | // TODO(lode): move level into jxl::CompressParams since some C++ |
651 | | // implementation decisions should be based on it: level 10 allows more |
652 | | // features to be used. |
653 | | bool store_jpeg_metadata; |
654 | | int32_t codestream_level; |
655 | | jxl::CodecMetadata metadata; |
656 | | std::vector<uint8_t> jpeg_metadata; |
657 | | |
658 | | jxl::CompressParams last_used_cparams; |
659 | | JxlBasicInfo basic_info; |
660 | | |
661 | | JxlEncoderError error = JxlEncoderError::JXL_ENC_ERR_OK; |
662 | | |
663 | | // Encoder wrote a jxlp (partial codestream) box, so any next codestream |
664 | | // parts must also be written in jxlp boxes, a single jxlc box cannot be |
665 | | // used. The counter is used for the 4-byte jxlp box index header. |
666 | | size_t jxlp_counter; |
667 | | |
668 | | // Wrote any output at all, so wrote the data before the first user added |
669 | | // frame or box, such as signature, basic info, ICC profile or jpeg |
670 | | // reconstruction box. |
671 | | bool wrote_bytes; |
672 | | |
673 | | bool frames_closed; |
674 | | bool boxes_closed; |
675 | | bool basic_info_set; |
676 | | bool color_encoding_set; |
677 | | bool intensity_target_set; |
678 | | bool allow_expert_options = false; |
679 | | int brotli_effort = -1; |
680 | | |
681 | | // Takes the first frame in the input_queue, encodes it, and appends |
682 | | // the bytes to the output_byte_queue. |
683 | | jxl::Status ProcessOneEnqueuedInput(); |
684 | | |
685 | 8.38k | bool MustUseContainer(int output_mode = 0) const { |
686 | 8.38k | return container_ftyp_version >= 0 || use_container || |
687 | 6.83k | (codestream_level != 5 && codestream_level != -1) || |
688 | 6.06k | store_jpeg_metadata || use_boxes || output_mode == 2; |
689 | 8.38k | } |
690 | | |
691 | | // `write_box` must never seek before the position the output wrapper was at |
692 | | // the moment of the call, and must leave the output wrapper such that its |
693 | | // position is one byte past the end of the written box. |
694 | | template <typename WriteBox> |
695 | | jxl::Status AppendBox(const jxl::BoxType& type, bool unbounded, |
696 | | size_t box_max_size, const WriteBox& write_box); |
697 | | |
698 | | template <typename BoxContents> |
699 | | jxl::Status AppendBoxWithContents(const jxl::BoxType& type, |
700 | | const BoxContents& contents); |
701 | | }; |
702 | | |
703 | | struct JxlEncoderFrameSettings { |
704 | | JxlEncoder* enc; |
705 | | jxl::JxlEncoderFrameSettingsValues values; |
706 | | }; |
707 | | |
708 | | struct JxlEncoderStats { |
709 | | std::unique_ptr<jxl::AuxOut> aux_out; |
710 | | }; |
711 | | |
712 | | #endif // LIB_JXL_ENCODE_INTERNAL_H_ |