/
decode.cc
2321 lines (2025 loc) · 86.8 KB
/
decode.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "jxl/decode.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/dec_frame.h"
#include "lib/jxl/dec_modular.h"
#include "lib/jxl/dec_reconstruct.h"
#include "lib/jxl/decode_to_jpeg.h"
#include "lib/jxl/fields.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/icc_codec.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/loop_filter.h"
#include "lib/jxl/memory_manager_internal.h"
#include "lib/jxl/toc.h"
namespace {
// If set (by fuzzer) then some operations will fail, if those would require
// allocating large objects. Actual memory usage might be two orders of
// magnitude bigger.
// TODO(eustas): this is a poor-mans replacement for memory-manager approach;
// remove, once memory-manager actually works.
size_t memory_limit_base_ = 0;
size_t cpu_limit_base_ = 0;
size_t used_cpu_base_ = 0;
bool CheckSizeLimit(size_t xsize, size_t ysize) {
if (!memory_limit_base_) return true;
if (xsize == 0 || ysize == 0) return true;
size_t num_pixels = xsize * ysize;
if (num_pixels / xsize != ysize) return false; // overflow
if (num_pixels > memory_limit_base_) return false;
return true;
}
// Checks if a + b > size, taking possible integer overflow into account.
bool OutOfBounds(size_t a, size_t b, size_t size) {
size_t pos = a + b;
if (pos > size) return true;
if (pos < a) return true; // overflow happened
return false;
}
// Checks if a + b + c > size, taking possible integer overflow into account.
bool OutOfBounds(size_t a, size_t b, size_t c, size_t size) {
size_t pos = a + b;
if (pos < b) return true; // overflow happened
pos += c;
if (pos < c) return true; // overflow happened
if (pos > size) return true;
return false;
}
bool SumOverflows(size_t a, size_t b, size_t c) {
size_t sum = a + b;
if (sum < b) return true;
sum += c;
if (sum < c) return true;
return false;
}
JXL_INLINE size_t InitialBasicInfoSizeHint() {
// Amount of bytes before the start of the codestream in the container format,
// assuming that the codestream is the first box after the signature and
// filetype boxes. 12 bytes signature box + 20 bytes filetype box + 16 bytes
// codestream box length + name + optional XLBox length.
const size_t container_header_size = 48;
// Worst-case amount of bytes for basic info of the JPEG XL codestream header,
// that is all information up to and including extra_channel_bits. Up to
// around 2 bytes signature + 8 bytes SizeHeader + 31 bytes ColorEncoding + 4
// bytes rest of ImageMetadata + 5 bytes part of ImageMetadata2.
// TODO(lode): recompute and update this value when alpha_bits is moved to
// extra channels info.
const size_t max_codestream_basic_info_size = 50;
return container_header_size + max_codestream_basic_info_size;
}
// Debug-printing failure macro similar to JXL_FAILURE, but for the status code
// JXL_DEC_ERROR
#ifdef JXL_CRASH_ON_ERROR
#define JXL_API_ERROR(format, ...) \
(::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
::jxl::Abort(), JXL_DEC_ERROR)
#else // JXL_CRASH_ON_ERROR
#define JXL_API_ERROR(format, ...) \
(((JXL_DEBUG_ON_ERROR) && \
::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__)), \
JXL_DEC_ERROR)
#endif // JXL_CRASH_ON_ERROR
JxlDecoderStatus ConvertStatus(JxlDecoderStatus status) { return status; }
JxlDecoderStatus ConvertStatus(jxl::Status status) {
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
}
JxlSignature ReadSignature(const uint8_t* buf, size_t len, size_t* pos) {
if (*pos >= len) return JXL_SIG_NOT_ENOUGH_BYTES;
buf += *pos;
len -= *pos;
// JPEG XL codestream: 0xff 0x0a
if (len >= 1 && buf[0] == 0xff) {
if (len < 2) {
return JXL_SIG_NOT_ENOUGH_BYTES;
} else if (buf[1] == jxl::kCodestreamMarker) {
*pos += 2;
return JXL_SIG_CODESTREAM;
} else {
return JXL_SIG_INVALID;
}
}
// JPEG XL container
if (len >= 1 && buf[0] == 0) {
if (len < 12) {
return JXL_SIG_NOT_ENOUGH_BYTES;
} else if (buf[1] == 0 && buf[2] == 0 && buf[3] == 0xC && buf[4] == 'J' &&
buf[5] == 'X' && buf[6] == 'L' && buf[7] == ' ' &&
buf[8] == 0xD && buf[9] == 0xA && buf[10] == 0x87 &&
buf[11] == 0xA) {
*pos += 12;
return JXL_SIG_CONTAINER;
} else {
return JXL_SIG_INVALID;
}
}
return JXL_SIG_INVALID;
}
} // namespace
uint32_t JxlDecoderVersion(void) {
return JPEGXL_MAJOR_VERSION * 1000000 + JPEGXL_MINOR_VERSION * 1000 +
JPEGXL_PATCH_VERSION;
}
JxlSignature JxlSignatureCheck(const uint8_t* buf, size_t len) {
size_t pos = 0;
return ReadSignature(buf, len, &pos);
}
namespace {
size_t BitsPerChannel(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_BOOLEAN:
return 1;
case JXL_TYPE_UINT8:
return 8;
case JXL_TYPE_UINT16:
return 16;
case JXL_TYPE_UINT32:
return 32;
case JXL_TYPE_FLOAT:
return 32;
case JXL_TYPE_FLOAT16:
return 16;
// No default, give compiler error if new type not handled.
}
return 0; // Indicate invalid data type.
}
enum class DecoderStage : uint32_t {
kInited, // Decoder created, no JxlDecoderProcessInput called yet
kStarted, // Running JxlDecoderProcessInput calls
kFinished, // Everything done, nothing left to process
kError, // Error occurred, decoder object no longer usable
};
enum class FrameStage : uint32_t {
kHeader, // Must parse frame header. dec->frame_start must be set up
// correctly already.
kTOC, // Must parse TOC
kFull, // Must parse full pixels
kFullOutput, // Must output full pixels
};
// Manages the sections for the FrameDecoder based on input bytes received.
struct Sections {
// sections_begin = position in the frame where the sections begin, after
// the frame header and TOC, so sections_begin = sum of frame header size and
// TOC size.
Sections(jxl::FrameDecoder* frame_dec, size_t frame_size,
size_t sections_begin)
: frame_dec_(frame_dec),
frame_size_(frame_size),
sections_begin_(sections_begin) {}
Sections(const Sections&) = delete;
Sections& operator=(const Sections&) = delete;
Sections(Sections&&) = delete;
Sections& operator=(Sections&&) = delete;
~Sections() {
// Avoid memory leaks if the JXL decoder quits early and doesn't end up
// calling CloseInput().
CloseInput();
}
// frame_dec_ must have been Inited already, but not yet done ProcessSections.
JxlDecoderStatus Init() {
section_received.resize(frame_dec_->NumSections(), 0);
const auto& offsets = frame_dec_->SectionOffsets();
const auto& sizes = frame_dec_->SectionSizes();
// Ensure none of the sums of section offset and size overflow.
for (size_t i = 0; i < frame_dec_->NumSections(); i++) {
if (OutOfBounds(sections_begin_, offsets[i], sizes[i], frame_size_)) {
return JXL_API_ERROR("section out of bounds");
}
}
return JXL_DEC_SUCCESS;
}
// Sets the input data for the frame. The frame pointer must point to the
// beginning of the frame, size is the amount of bytes gotten so far and
// should increase with next calls until the full frame is loaded.
// TODO(lode): allow caller to provide only later chunks of memory when
// earlier sections are fully processed already.
void SetInput(const uint8_t* frame, size_t size) {
const auto& offsets = frame_dec_->SectionOffsets();
const auto& sizes = frame_dec_->SectionSizes();
for (size_t i = 0; i < frame_dec_->NumSections(); i++) {
if (section_received[i]) continue;
if (!OutOfBounds(sections_begin_, offsets[i], sizes[i], size)) {
section_received[i] = 1;
section_info.emplace_back(jxl::FrameDecoder::SectionInfo{nullptr, i});
section_status.emplace_back();
}
}
// Reset all the bitreaders, because the address of the frame pointer may
// change, even if it always represents the same frame start.
for (size_t i = 0; i < section_info.size(); i++) {
size_t id = section_info[i].id;
JXL_ASSERT(section_info[i].br == nullptr);
section_info[i].br = new jxl::BitReader(jxl::Span<const uint8_t>(
frame + sections_begin_ + offsets[id], sizes[id]));
}
}
JxlDecoderStatus CloseInput() {
bool out_of_bounds = false;
for (size_t i = 0; i < section_info.size(); i++) {
if (!section_info[i].br) continue;
if (!section_info[i].br->AllReadsWithinBounds()) {
// Mark out of bounds section, but keep closing and deleting the next
// ones as well.
out_of_bounds = true;
}
JXL_ASSERT(section_info[i].br->Close());
delete section_info[i].br;
section_info[i].br = nullptr;
}
if (out_of_bounds) {
// If any bit reader indicates out of bounds, it's an error, not just
// needing more input, since we ensure only bit readers containing
// a complete section are provided to the FrameDecoder.
return JXL_API_ERROR("frame out of bounds");
}
return JXL_DEC_SUCCESS;
}
// Not managed by us.
jxl::FrameDecoder* frame_dec_;
size_t frame_size_;
size_t sections_begin_;
std::vector<jxl::FrameDecoder::SectionInfo> section_info;
std::vector<jxl::FrameDecoder::SectionStatus> section_status;
std::vector<char> section_received;
};
/*
Given list of frame references to storage slots, and storage slots in which this
frame is saved, computes which frames are required to decode the frame at the
given index and any frames after it. The frames on which this depends are
returned as a vector of their indices, in no particular order. The given index
must be smaller than saved_as.size(), and references.size() must equal
saved_as.size(). Any frames beyond saved_as and references are considered
unknown future frames and must be treated as if something depends on them.
*/
std::vector<size_t> GetFrameDependencies(size_t index,
const std::vector<int>& saved_as,
const std::vector<int>& references) {
JXL_ASSERT(references.size() == saved_as.size());
JXL_ASSERT(index < references.size());
std::vector<size_t> result;
constexpr size_t kNumStorage = 8;
// value which indicates nothing is stored in this storage slot
const size_t invalid = references.size();
// for each of the 8 storage slots, a vector that translates frame index to
// frame stored in this storage slot at this point, that is, the last
// frame that was stored in this slot before or at this index.
std::array<std::vector<size_t>, kNumStorage> storage;
for (size_t s = 0; s < kNumStorage; ++s) {
storage[s].resize(saved_as.size());
int mask = 1 << s;
size_t id = invalid;
for (size_t i = 0; i < saved_as.size(); ++i) {
if (saved_as[i] & mask) {
id = i;
}
storage[s][i] = id;
}
}
std::vector<char> seen(index + 1, 0);
std::vector<size_t> stack;
stack.push_back(index);
seen[index] = 1;
// For frames after index, assume they can depend on any of the 8 storage
// slots, so push the frame for each stored reference to the stack and result.
// All frames after index are treated as having unknown references and with
// the possibility that there are more frames after the last known.
// TODO(lode): take values of saved_as and references after index, and a
// input flag indicating if they are all frames of the image, to further
// optimize this.
for (size_t s = 0; s < kNumStorage; ++s) {
size_t frame_ref = storage[s][index];
if (frame_ref == invalid) continue;
if (seen[frame_ref]) continue;
stack.push_back(frame_ref);
seen[frame_ref] = 1;
result.push_back(frame_ref);
}
while (!stack.empty()) {
size_t frame_index = stack.back();
stack.pop_back();
if (frame_index == 0) continue; // first frame cannot have references
for (size_t s = 0; s < kNumStorage; ++s) {
int mask = 1 << s;
if (!(references[frame_index] & mask)) continue;
size_t frame_ref = storage[s][frame_index - 1];
if (frame_ref == invalid) continue;
if (seen[frame_ref]) continue;
stack.push_back(frame_ref);
seen[frame_ref] = 1;
result.push_back(frame_ref);
}
}
return result;
}
// Parameters for user-requested extra channel output.
struct ExtraChannelOutput {
JxlPixelFormat format;
void* buffer;
size_t buffer_size;
};
} // namespace
// NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding)
struct JxlDecoderStruct {
JxlDecoderStruct() = default;
JxlMemoryManager memory_manager;
std::unique_ptr<jxl::ThreadPool> thread_pool;
DecoderStage stage;
// Status of progression, internal.
bool got_signature;
bool first_codestream_seen;
// Indicates we know that we've seen the last codestream, however this is not
// guaranteed to be true for the last box because a jxl file may have multiple
// "jxlp" boxes and it is possible (and permitted) that the last one is not a
// final box that uses size 0 to indicate the end.
bool last_codestream_seen;
bool got_basic_info;
size_t header_except_icc_bits = 0; // To skip everything before ICC.
bool got_all_headers; // Codestream metadata headers.
bool post_headers; // Already decoding pixels.
jxl::ICCReader icc_reader;
// This means either we actually got the preview image, or determined we
// cannot get it or there is none.
bool got_preview_image;
// Position of next_in in the original file including box format if present
// (as opposed to position in the codestream)
size_t file_pos;
size_t box_begin;
size_t box_end;
bool skip_box;
// Begin and end of the content of the current codestream box. This could be
// a partial codestream box.
// codestream_begin 0 is used to indicate the begin is not yet known.
// codestream_end 0 is used to indicate uncapped (until end of file, for the
// last box if this box doesn't indicate its actual size).
// Not used if the file is a direct codestream.
size_t codestream_begin;
size_t codestream_end;
// Settings
bool keep_orientation;
// Bitfield, for which informative events (JXL_DEC_BASIC_INFO, etc...) the
// decoder returns a status. By default, do not return for any of the events,
// only return when the decoder cannot continue because it needs more input or
// output data.
int events_wanted;
int orig_events_wanted;
// Fields for reading the basic info from the header.
size_t basic_info_size_hint;
bool have_container;
// Whether the preview out buffer was set. It is possible for the buffer to
// be nullptr and buffer_set to be true, indicating it was deliberately
// set to nullptr.
bool preview_out_buffer_set;
// Idem for the image buffer.
bool image_out_buffer_set;
// Owned by the caller, buffers for DC image and full resolution images
void* preview_out_buffer;
void* image_out_buffer;
JxlImageOutCallback image_out_callback;
void* image_out_opaque;
size_t preview_out_size;
size_t image_out_size;
JxlPixelFormat preview_out_format;
JxlPixelFormat image_out_format;
// For extra channels. Empty if no extra channels are requested, and they are
// reset each frame
std::vector<ExtraChannelOutput> extra_channel_output;
jxl::CodecMetadata metadata;
std::unique_ptr<jxl::ImageBundle> ib;
// ColorEncoding to use for xyb encoded image with ICC profile.
jxl::ColorEncoding default_enc;
std::unique_ptr<jxl::PassesDecoderState> passes_state;
std::unique_ptr<jxl::FrameDecoder> frame_dec;
std::unique_ptr<Sections> sections;
// The FrameDecoder is initialized, and not yet finalized
bool frame_dec_in_progress;
// headers and TOC for the current frame. When got_toc is true, this is
// always the frame header of the last frame of the current still series,
// that is, the displayed frame.
std::unique_ptr<jxl::FrameHeader> frame_header;
// Start of the current frame being processed, as offset from the beginning of
// the codestream.
size_t frame_start;
size_t frame_size;
FrameStage frame_stage;
// The currently processed frame is the last of the current composite still,
// and so must be returned as pixels
bool is_last_of_still;
// The currently processed frame is the last of the codestream
bool is_last_total;
// How many frames to skip.
size_t skip_frames;
// Skipping the current frame. May be false if skip_frames was just set to
// a positive value while already processing a current frame, then
// skipping_frame will be enabled only for the next frame.
bool skipping_frame;
// Amount of internal frames and external frames started. External frames are
// user-visible frames, internal frames includes all external frames and
// also invisible frames such as patches, blending-only and dc_level frames.
size_t internal_frames;
size_t external_frames;
// For each internal frame, which storage locations it references, and which
// storage locations it is stored in, using the bit mask as defined in
// FrameDecoder::References and FrameDecoder::SaveAs.
std::vector<int> frame_references;
std::vector<int> frame_saved_as;
// Translates external frame index to internal frame index. The external
// index is the index of user-visible frames. The internal index can be larger
// since non-visible frames (such as frames with patches, ...) are included.
std::vector<size_t> frame_external_to_internal;
// Whether the frame with internal index is required to decode the frame
// being skipped to or any frames after that. If no skipping is active,
// this vector is ignored. If the current internal frame index is beyond this
// vector, it must be treated as a required frame.
std::vector<char> frame_required;
// Codestream input data is stored here, when the decoder takes in and stores
// the user input bytes. If the decoder does not do that (e.g. in one-shot
// case), this field is unused.
// TODO(lode): avoid needing this field once the C++ decoder doesn't need
// all bytes at once, to save memory. Find alternative to std::vector doubling
// strategy to prevent some memory usage.
std::vector<uint8_t> codestream;
jxl::JxlToJpegDecoder jpeg_decoder;
// Position in the actual codestream, which codestream.begin() points to.
// Non-zero once earlier parts of the codestream vector have been erased.
size_t codestream_pos;
// Statistics which CodecInOut can keep
uint64_t dec_pixels;
const uint8_t* next_in;
size_t avail_in;
};
// TODO(zond): Make this depend on the data loaded into the decoder.
JxlDecoderStatus JxlDecoderDefaultPixelFormat(const JxlDecoder* dec,
JxlPixelFormat* format) {
if (!dec->got_basic_info) return JXL_DEC_NEED_MORE_INPUT;
*format = {4, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
return JXL_DEC_SUCCESS;
}
void JxlDecoderReset(JxlDecoder* dec) {
dec->thread_pool.reset();
dec->stage = DecoderStage::kInited;
dec->got_signature = false;
dec->first_codestream_seen = false;
dec->last_codestream_seen = false;
dec->got_basic_info = false;
dec->header_except_icc_bits = 0;
dec->got_all_headers = false;
dec->post_headers = false;
dec->icc_reader.Reset();
dec->got_preview_image = false;
dec->file_pos = 0;
dec->box_begin = 0;
dec->box_end = 0;
dec->skip_box = false;
dec->codestream_pos = 0;
dec->codestream_begin = 0;
dec->codestream_end = 0;
dec->keep_orientation = false;
dec->events_wanted = 0;
dec->orig_events_wanted = 0;
dec->basic_info_size_hint = InitialBasicInfoSizeHint();
dec->have_container = 0;
dec->preview_out_buffer_set = false;
dec->image_out_buffer_set = false;
dec->preview_out_buffer = nullptr;
dec->image_out_buffer = nullptr;
dec->image_out_callback = nullptr;
dec->image_out_opaque = nullptr;
dec->preview_out_size = 0;
dec->image_out_size = 0;
dec->extra_channel_output.clear();
dec->dec_pixels = 0;
dec->next_in = 0;
dec->avail_in = 0;
dec->passes_state.reset(nullptr);
dec->frame_dec.reset(nullptr);
dec->sections.reset(nullptr);
dec->frame_dec_in_progress = false;
dec->ib.reset();
dec->metadata = jxl::CodecMetadata();
dec->frame_header.reset(new jxl::FrameHeader(&dec->metadata));
dec->codestream.clear();
dec->frame_stage = FrameStage::kHeader;
dec->frame_start = 0;
dec->frame_size = 0;
dec->is_last_of_still = false;
dec->is_last_total = false;
dec->skip_frames = 0;
dec->skipping_frame = false;
dec->internal_frames = 0;
dec->external_frames = 0;
dec->frame_references.clear();
dec->frame_saved_as.clear();
dec->frame_external_to_internal.clear();
dec->frame_required.clear();
}
JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager) {
JxlMemoryManager local_memory_manager;
if (!jxl::MemoryManagerInit(&local_memory_manager, memory_manager))
return nullptr;
void* alloc =
jxl::MemoryManagerAlloc(&local_memory_manager, sizeof(JxlDecoder));
if (!alloc) return nullptr;
// Placement new constructor on allocated memory
JxlDecoder* dec = new (alloc) JxlDecoder();
dec->memory_manager = local_memory_manager;
JxlDecoderReset(dec);
return dec;
}
void JxlDecoderDestroy(JxlDecoder* dec) {
if (dec) {
// Call destructor directly since custom free function is used.
dec->~JxlDecoder();
jxl::MemoryManagerFree(&dec->memory_manager, dec);
}
}
void JxlDecoderRewind(JxlDecoder* dec) {
int keep_orientation = dec->keep_orientation;
int events_wanted = dec->orig_events_wanted;
std::vector<int> frame_references;
std::vector<int> frame_saved_as;
std::vector<size_t> frame_external_to_internal;
std::vector<char> frame_required;
frame_references.swap(dec->frame_references);
frame_saved_as.swap(dec->frame_saved_as);
frame_external_to_internal.swap(dec->frame_external_to_internal);
frame_required.swap(dec->frame_required);
JxlDecoderReset(dec);
dec->keep_orientation = keep_orientation;
dec->events_wanted = events_wanted;
dec->orig_events_wanted = events_wanted;
frame_references.swap(dec->frame_references);
frame_saved_as.swap(dec->frame_saved_as);
frame_external_to_internal.swap(dec->frame_external_to_internal);
frame_required.swap(dec->frame_required);
}
void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount) {
// Increment amount, rather than set it: making the amount smaller is
// impossible because the decoder may already have skipped frames required to
// decode earlier frames, and making the amount larger compared to an existing
// amount is impossible because if JxlDecoderSkipFrames is called in the
// middle of already skipping frames, the user cannot know how many frames
// have already been skipped internally so far so an absolute value cannot
// be defined.
dec->skip_frames += amount;
dec->frame_required.clear();
size_t next_frame = dec->external_frames + dec->skip_frames;
// A frame that has been seen before a rewind
if (next_frame < dec->frame_external_to_internal.size()) {
size_t internal_index = dec->frame_external_to_internal[next_frame];
if (internal_index < dec->frame_saved_as.size()) {
std::vector<size_t> deps = GetFrameDependencies(
internal_index, dec->frame_saved_as, dec->frame_references);
dec->frame_required.resize(internal_index + 1, 0);
for (size_t i = 0; i < deps.size(); i++) {
JXL_ASSERT(deps[i] < dec->frame_required.size());
dec->frame_required[deps[i]] = 1;
}
}
}
}
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner,
void* parallel_runner_opaque) {
if (dec->thread_pool) return JXL_API_ERROR("parallel runner already set");
dec->thread_pool.reset(
new jxl::ThreadPool(parallel_runner, parallel_runner_opaque));
return JXL_DEC_SUCCESS;
}
size_t JxlDecoderSizeHintBasicInfo(const JxlDecoder* dec) {
if (dec->got_basic_info) return 0;
return dec->basic_info_size_hint;
}
JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec, int events_wanted) {
if (dec->stage != DecoderStage::kInited) {
return JXL_DEC_ERROR; // Cannot subscribe to events after having started.
}
if (events_wanted & 63) {
return JXL_DEC_ERROR; // Can only subscribe to informative events.
}
dec->events_wanted = events_wanted;
dec->orig_events_wanted = events_wanted;
return JXL_DEC_SUCCESS;
}
JxlDecoderStatus JxlDecoderSetKeepOrientation(JxlDecoder* dec,
JXL_BOOL keep_orientation) {
if (dec->stage != DecoderStage::kInited) {
return JXL_API_ERROR("Must set keep_orientation option before starting");
}
dec->keep_orientation = !!keep_orientation;
return JXL_DEC_SUCCESS;
}
namespace jxl {
namespace {
template <class T>
bool CanRead(Span<const uint8_t> data, BitReader* reader, T* JXL_RESTRICT t) {
// Use a copy of the bit reader because CanRead advances bits.
BitReader reader2(data);
reader2.SkipBits(reader->TotalBitsConsumed());
bool result = Bundle::CanRead(&reader2, t);
JXL_ASSERT(reader2.Close());
return result;
}
// Returns JXL_DEC_SUCCESS if the full bundle was successfully read, status
// indicating either error or need more input otherwise.
template <class T>
JxlDecoderStatus ReadBundle(Span<const uint8_t> data, BitReader* reader,
T* JXL_RESTRICT t) {
if (!CanRead(data, reader, t)) {
return JXL_DEC_NEED_MORE_INPUT;
}
if (!Bundle::Read(reader, t)) {
return JXL_DEC_ERROR;
}
return JXL_DEC_SUCCESS;
}
#define JXL_API_RETURN_IF_ERROR(expr) \
{ \
JxlDecoderStatus status_ = ConvertStatus(expr); \
if (status_ != JXL_DEC_SUCCESS) return status_; \
}
std::unique_ptr<BitReader, std::function<void(BitReader*)>> GetBitReader(
Span<const uint8_t> span) {
BitReader* reader = new BitReader(span);
return std::unique_ptr<BitReader, std::function<void(BitReader*)>>(
reader, [](BitReader* reader) {
// We can't allow Close to abort the program if the reader is out of
// bounds, or all return paths in the code, even those that already
// return failure, would have to manually call AllReadsWithinBounds().
// Invalid JXL codestream should not cause program to quit.
(void)reader->AllReadsWithinBounds();
(void)reader->Close();
delete reader;
});
}
JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec, const uint8_t* in,
size_t size) {
size_t pos = 0;
// Check and skip the codestream signature
JxlSignature signature = ReadSignature(in, size, &pos);
if (signature == JXL_SIG_NOT_ENOUGH_BYTES) {
return JXL_DEC_NEED_MORE_INPUT;
}
if (signature == JXL_SIG_CONTAINER) {
// There is a container signature where we expect a codestream, container
// is handled at a higher level already.
return JXL_API_ERROR("invalid: nested container");
}
if (signature != JXL_SIG_CODESTREAM) {
return JXL_API_ERROR("invalid signature");
}
Span<const uint8_t> span(in + pos, size - pos);
auto reader = GetBitReader(span);
JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dec->metadata.size));
dec->metadata.m.nonserialized_only_parse_basic_info = true;
JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dec->metadata.m));
dec->metadata.m.nonserialized_only_parse_basic_info = false;
dec->got_basic_info = true;
dec->basic_info_size_hint = 0;
if (!CheckSizeLimit(dec->metadata.size.xsize(), dec->metadata.size.ysize())) {
return JXL_API_ERROR("image is too large");
}
return JXL_DEC_SUCCESS;
}
// Reads all codestream headers (but not frame headers)
JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
size_t size) {
size_t pos = 0;
// Check and skip the codestream signature
JxlSignature signature = ReadSignature(in, size, &pos);
if (signature == JXL_SIG_CONTAINER) {
return JXL_API_ERROR("invalid: nested container");
}
if (signature != JXL_SIG_CODESTREAM) {
return JXL_API_ERROR("invalid signature");
}
Span<const uint8_t> span(in + pos, size - pos);
auto reader = GetBitReader(span);
if (dec->header_except_icc_bits != 0) {
// Headers were decoded already.
reader->SkipBits(dec->header_except_icc_bits);
} else {
SizeHeader dummy_size_header;
JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dummy_size_header));
// We already decoded the metadata to dec->metadata.m, no reason to
// overwrite it, use a dummy metadata instead.
ImageMetadata dummy_metadata;
JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dummy_metadata));
JXL_API_RETURN_IF_ERROR(
ReadBundle(span, reader.get(), &dec->metadata.transform_data));
}
dec->header_except_icc_bits = reader->TotalBitsConsumed();
if (dec->metadata.m.color_encoding.WantICC()) {
jxl::Status status = dec->icc_reader.Init(reader.get(), memory_limit_base_);
// Always check AllReadsWithinBounds, not all the C++ decoder implementation
// handles reader out of bounds correctly yet (e.g. context map). Not
// checking AllReadsWithinBounds can cause reader->Close() to trigger an
// assert, but we don't want library to quit program for invalid codestream.
if (!reader->AllReadsWithinBounds()) {
return JXL_DEC_NEED_MORE_INPUT;
}
if (!status) {
if (status.code() == StatusCode::kNotEnoughBytes) {
return JXL_DEC_NEED_MORE_INPUT;
}
// Other non-successful status is an error
return JXL_DEC_ERROR;
}
PaddedBytes icc;
status = dec->icc_reader.Process(reader.get(), &icc);
if (!status) {
if (status.code() == StatusCode::kNotEnoughBytes) {
return JXL_DEC_NEED_MORE_INPUT;
}
// Other non-successful status is an error
return JXL_DEC_ERROR;
}
if (!dec->metadata.m.color_encoding.SetICCRaw(std::move(icc))) {
return JXL_DEC_ERROR;
}
}
dec->got_all_headers = true;
JXL_API_RETURN_IF_ERROR(reader->JumpToByteBoundary());
dec->frame_start = pos + reader->TotalBitsConsumed() / jxl::kBitsPerByte;
if (!dec->passes_state) {
dec->passes_state.reset(new jxl::PassesDecoderState());
}
dec->default_enc =
ColorEncoding::LinearSRGB(dec->metadata.m.color_encoding.IsGray());
JXL_API_RETURN_IF_ERROR(dec->passes_state->output_encoding_info.Set(
dec->metadata, dec->default_enc));
return JXL_DEC_SUCCESS;
}
static size_t GetStride(const JxlDecoder* dec, const JxlPixelFormat& format,
const jxl::ImageBundle* frame = nullptr) {
size_t xsize = dec->metadata.xsize();
if (!dec->keep_orientation && dec->metadata.m.orientation > 4) {
xsize = dec->metadata.ysize();
}
if (frame) {
xsize = dec->keep_orientation ? frame->xsize() : frame->oriented_xsize();
}
size_t stride = xsize * (BitsPerChannel(format.data_type) *
format.num_channels / jxl::kBitsPerByte);
if (format.align > 1) {
stride = jxl::DivCeil(stride, format.align) * format.align;
}
return stride;
}
// Internal wrapper around jxl::ConvertToExternal which converts the stride,
// format and orientation and allows to choose whether to get all RGB(A)
// channels or alternatively get a single extra channel.
// If want_extra_channel, a valid index to a single extra channel must be
// given, the output must be single-channel, and format.num_channels is ignored
// and treated as if it is 1.
static JxlDecoderStatus ConvertImageInternal(
const JxlDecoder* dec, const jxl::ImageBundle& frame,
const JxlPixelFormat& format, bool want_extra_channel,
size_t extra_channel_index, void* out_image, size_t out_size,
JxlImageOutCallback out_callback, void* out_opaque) {
// TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data
// color/grayscale format
const size_t stride = GetStride(dec, format, &frame);
bool float_format = format.data_type == JXL_TYPE_FLOAT ||
format.data_type == JXL_TYPE_FLOAT16;
jxl::Orientation undo_orientation = dec->keep_orientation
? jxl::Orientation::kIdentity
: dec->metadata.m.GetOrientation();
jxl::Status status(true);
if (want_extra_channel) {
status = jxl::ConvertToExternal(
frame.extra_channels()[extra_channel_index],
BitsPerChannel(format.data_type), float_format, format.endianness,
stride, dec->thread_pool.get(), out_image, out_size,
/*out_callback=*/out_callback,
/*out_opaque=*/out_opaque, undo_orientation);
} else {
status = jxl::ConvertToExternal(
frame, BitsPerChannel(format.data_type), float_format,
format.num_channels, format.endianness, stride, dec->thread_pool.get(),
out_image, out_size,
/*out_callback=*/out_callback,
/*out_opaque=*/out_opaque, undo_orientation);
}
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
}
// Parses the FrameHeader and the total frame_size, given the initial bytes
// of the frame up to and including the TOC.
// TODO(lode): merge this with FrameDecoder
JxlDecoderStatus ParseFrameHeader(jxl::FrameHeader* frame_header,
const uint8_t* in, size_t size, size_t pos,
bool is_preview, size_t* frame_size,
int* saved_as) {
if (pos >= size) {
return JXL_DEC_NEED_MORE_INPUT;
}
Span<const uint8_t> span(in + pos, size - pos);
auto reader = GetBitReader(span);
frame_header->nonserialized_is_preview = is_preview;
jxl::Status status = DecodeFrameHeader(reader.get(), frame_header);
jxl::FrameDimensions frame_dim = frame_header->ToFrameDimensions();
if (!CheckSizeLimit(frame_dim.xsize_upsampled_padded,
frame_dim.ysize_upsampled_padded)) {
return JXL_API_ERROR("frame is too large");
}
if (status.code() == StatusCode::kNotEnoughBytes) {
// TODO(lode): prevent asking for way too much input bytes in case of
// invalid header that the decoder thinks is a very long user extension
// instead. Example: fields can currently print something like this:
// "../lib/jxl/fields.cc:416: Skipping 71467322-bit extension(s)"
// Maybe fields.cc should return error in the above case rather than
// print a message.
return JXL_DEC_NEED_MORE_INPUT;
} else if (!status) {
return JXL_API_ERROR("invalid frame header");
}
// Read TOC.
uint64_t groups_total_size;
const bool has_ac_global = true;
const size_t toc_entries =
NumTocEntries(frame_dim.num_groups, frame_dim.num_dc_groups,
frame_header->passes.num_passes, has_ac_global);
std::vector<uint64_t> group_offsets;
std::vector<uint32_t> group_sizes;
status = ReadGroupOffsets(toc_entries, reader.get(), &group_offsets,
&group_sizes, &groups_total_size);
// TODO(lode): we're actually relying on AllReadsWithinBounds() here
// instead of on status.code(), change the internal TOC C++ code to
// correctly set the status.code() instead so we can rely on that one.
if (!reader->AllReadsWithinBounds() ||
status.code() == StatusCode::kNotEnoughBytes) {
return JXL_DEC_NEED_MORE_INPUT;
} else if (!status) {
return JXL_API_ERROR("invalid toc entries");
}
JXL_DASSERT((reader->TotalBitsConsumed() % kBitsPerByte) == 0);
JXL_API_RETURN_IF_ERROR(reader->JumpToByteBoundary());
size_t header_size = (reader->TotalBitsConsumed() >> 3);
*frame_size = header_size + groups_total_size;
if (saved_as != nullptr) {
*saved_as = FrameDecoder::SavedAs(*frame_header);
}