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