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