Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/decode_to_jpeg.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
#ifndef LIB_JXL_DECODE_TO_JPEG_H_
7
#define LIB_JXL_DECODE_TO_JPEG_H_
8
9
// JPEG XL to JPEG bytes decoder logic. The JxlToJpegDecoder class keeps track
10
// of the decoder state needed to parse the JPEG reconstruction box and provide
11
// the reconstructed JPEG to the output buffer.
12
13
#include <jxl/decode.h>
14
#include <stdint.h>
15
#include <stdlib.h>
16
17
#include <algorithm>
18
#include <cstring>
19
#include <memory>
20
#include <utility>
21
#include <vector>
22
23
#include "lib/jxl/base/status.h"
24
#include "lib/jxl/common.h"  // JPEGXL_ENABLE_TRANSCODE_JPEG
25
#include "lib/jxl/image_bundle.h"
26
#include "lib/jxl/jpeg/jpeg_data.h"
27
#if JPEGXL_ENABLE_TRANSCODE_JPEG
28
#include "lib/jxl/jpeg/dec_jpeg_data_writer.h"
29
#endif  // JPEGXL_ENABLE_TRANSCODE_JPEG
30
31
namespace jxl {
32
33
#if JPEGXL_ENABLE_TRANSCODE_JPEG
34
35
class JxlToJpegDecoder {
36
 public:
37
26.3k
  JxlToJpegDecoder() { Reset(); }
38
39
  // Returns whether an output buffer is set.
40
134k
  bool IsOutputSet() const { return next_out_ != nullptr; }
41
42
  // Returns whether the decoder is parsing a boxa JPEG box was parsed.
43
0
  bool IsParsingBox() const { return inside_box_; }
44
45
  // Sets the output buffer used when producing JPEG output.
46
0
  JxlDecoderStatus SetOutputBuffer(uint8_t* data, size_t size) {
47
0
    if (next_out_) return JXL_DEC_ERROR;
48
0
    next_out_ = data;
49
0
    avail_size_ = size;
50
0
    return JXL_DEC_SUCCESS;
51
0
  }
52
53
  // Releases the buffer set with SetOutputBuffer().
54
0
  size_t ReleaseOutputBuffer() {
55
0
    size_t result = avail_size_;
56
0
    next_out_ = nullptr;
57
0
    avail_size_ = 0;
58
0
    return result;
59
0
  }
60
61
  // Sets all per-decode state to its initial value, forgetting the
62
  // caller-owned output buffer. Used by the constructor and when the decoder is
63
  // rewound or reset, after which the buffer the caller passed to
64
  // SetOutputBuffer() may no longer be valid.
65
52.6k
  void Reset() {
66
52.6k
    buffer_.clear();
67
52.6k
    buffer_.shrink_to_fit();
68
52.6k
    jpeg_data_.reset();
69
52.6k
    inside_box_ = false;
70
52.6k
    box_until_eof_ = false;
71
52.6k
    box_size_ = 0;
72
52.6k
    next_out_ = nullptr;
73
52.6k
    avail_size_ = 0;
74
52.6k
  }
75
76
0
  void StartBox(bool box_until_eof, size_t contents_size) {
77
    // A new box implies that we clear the buffer.
78
0
    buffer_.clear();
79
0
    inside_box_ = true;
80
0
    if (box_until_eof) {
81
0
      box_until_eof_ = true;
82
0
    } else {
83
0
      box_size_ = contents_size;
84
0
    }
85
0
  }
86
87
  // Consumes data from next_in/avail_in to reconstruct JPEG data.
88
  // Uses box_size_, inside_box_ and box_until_eof_ to calculate how much to
89
  // consume. Potentially stores unparsed data in buffer_.
90
  // Potentially populates jpeg_data_. Potentially updates inside_box_.
91
  // Returns JXL_DEC_JPEG_RECONSTRUCTION when finished, JXL_DEC_NEED_MORE_INPUT
92
  // if more input is needed, JXL_DEC_ERROR on parsing error.
93
  JxlDecoderStatus Process(const uint8_t** next_in, size_t* avail_in);
94
95
  // Returns non-owned copy of the JPEGData, only after Process finished and
96
  // the JPEGData was not yet moved to an image bundle with
97
  // SetImageBundleJpegData.
98
0
  jpeg::JPEGData* GetJpegData() { return jpeg_data_.get(); }
99
100
  // Returns how many exif or xmp app markers are present in the JPEG data. A
101
  // return value higher than 1 would require multiple exif boxes or multiple
102
  // xmp boxes in the container format, and this is not supported by the API and
103
  // considered an error. May only be called after Process returned success.
104
  static size_t NumExifMarkers(const jpeg::JPEGData& jpeg_data);
105
  static size_t NumXmpMarkers(const jpeg::JPEGData& jpeg_data);
106
107
  // Returns box content size for metadata, using the known data from the app
108
  // markers.
109
  static JxlDecoderStatus ExifBoxContentSize(const jpeg::JPEGData& jpeg_data,
110
                                             size_t* size);
111
  static JxlDecoderStatus XmlBoxContentSize(const jpeg::JPEGData& jpeg_data,
112
                                            size_t* size);
113
114
  // Returns JXL_DEC_ERROR if there is no exif/XMP marker or the data size
115
  // does not match, or this function is called before Process returned
116
  // success, JXL_DEC_SUCCESS otherwise. As input, provide the full box contents
117
  // but not the box header. In case of exif, this includes the 4-byte TIFF
118
  // header, even though it won't be copied into the JPEG.
119
  static JxlDecoderStatus SetExif(const uint8_t* data, size_t size,
120
                                  jpeg::JPEGData* jpeg_data);
121
  static JxlDecoderStatus SetXmp(const uint8_t* data, size_t size,
122
                                 jpeg::JPEGData* jpeg_data);
123
124
  // Sets the JpegData of the ImageBundle passed if there is anything to set.
125
  // Releases the JpegData from this decoder if set.
126
60.8k
  Status SetImageBundleJpegData(ImageBundle* ib) {
127
60.8k
    if (IsOutputSet() && jpeg_data_ != nullptr) {
128
0
      if (!jpeg::SetJPEGDataFromICC(ib->metadata()->color_encoding.ICC(),
129
0
                                    jpeg_data_.get())) {
130
0
        return false;
131
0
      }
132
0
      ib->jpeg_data = std::move(jpeg_data_);
133
0
    }
134
60.8k
    return true;
135
60.8k
  }
136
137
0
  JxlDecoderStatus WriteOutput(const jpeg::JPEGData& jpeg_data) {
138
    // Copy JPEG bytestream if desired.
139
0
    uint8_t* tmp_next_out = next_out_;
140
0
    size_t tmp_avail_size = avail_size_;
141
0
    auto write = [&tmp_next_out, &tmp_avail_size](const uint8_t* buf,
142
0
                                                  size_t len) {
143
0
      size_t to_write = std::min<size_t>(tmp_avail_size, len);
144
0
      if (to_write != 0) memcpy(tmp_next_out, buf, to_write);
145
0
      tmp_next_out += to_write;
146
0
      tmp_avail_size -= to_write;
147
0
      return to_write;
148
0
    };
149
0
    Status write_result = jpeg::WriteJpeg(jpeg_data, write);
150
0
    if (!write_result) {
151
0
      if (tmp_avail_size == 0) {
152
0
        return JXL_DEC_JPEG_NEED_MORE_OUTPUT;
153
0
      }
154
0
      return JXL_DEC_ERROR;
155
0
    }
156
0
    next_out_ = tmp_next_out;
157
0
    avail_size_ = tmp_avail_size;
158
0
    return JXL_DEC_SUCCESS;
159
0
  }
160
161
 private:
162
  // Content of the most recently parsed JPEG reconstruction box if any.
163
  std::vector<uint8_t> buffer_;
164
165
  // Decoded content of the most recently parsed JPEG reconstruction box is
166
  // stored here.
167
  std::unique_ptr<jpeg::JPEGData> jpeg_data_;
168
169
  // True if the decoder is currently reading bytes inside a JPEG reconstruction
170
  // box.
171
  bool inside_box_;
172
173
  // True if the JPEG reconstruction box had undefined size (all remaining
174
  // bytes).
175
  bool box_until_eof_;
176
  // Size of most recently parsed JPEG reconstruction box contents.
177
  size_t box_size_;
178
179
  // Next bytes to write JPEG reconstruction to.
180
  uint8_t* next_out_;
181
  // Available bytes to write JPEG reconstruction to.
182
  size_t avail_size_;
183
};
184
185
#else
186
187
// Fake class that disables support for decoding JPEG XL to JPEG.
188
class JxlToJpegDecoder {
189
 public:
190
  bool IsOutputSet() const { return false; }
191
  bool IsParsingBox() const { return false; }
192
193
  JxlDecoderStatus SetOutputBuffer(uint8_t* /* data */, size_t /* size */) {
194
    return JXL_DEC_ERROR;
195
  }
196
  size_t ReleaseOutputBuffer() { return 0; }
197
198
  void Reset() {}
199
200
  void StartBox(bool /* box_until_eof */, size_t /* contents_size */) {}
201
202
  JxlDecoderStatus Process(const uint8_t** next_in, size_t* avail_in) {
203
    return JXL_DEC_ERROR;
204
  }
205
  jpeg::JPEGData* GetJpegData() { return nullptr; }
206
207
  Status SetImageBundleJpegData(ImageBundle* /* ib */) { return true; }
208
209
  static size_t NumExifMarkers(const jpeg::JPEGData& /*jpeg_data*/) {
210
    return 0;
211
  }
212
  static size_t NumXmpMarkers(const jpeg::JPEGData& /*jpeg_data*/) { return 0; }
213
  static size_t ExifBoxContentSize(const jpeg::JPEGData& /*jpeg_data*/,
214
                                   size_t* /*size*/) {
215
    return JXL_DEC_ERROR;
216
  }
217
  static size_t XmlBoxContentSize(const jpeg::JPEGData& /*jpeg_data*/,
218
                                  size_t* /*size*/) {
219
    return JXL_DEC_ERROR;
220
  }
221
  static JxlDecoderStatus SetExif(const uint8_t* /*data*/, size_t /*size*/,
222
                                  jpeg::JPEGData* /*jpeg_data*/) {
223
    return JXL_DEC_ERROR;
224
  }
225
  static JxlDecoderStatus SetXmp(const uint8_t* /*data*/, size_t /*size*/,
226
                                 jpeg::JPEGData* /*jpeg_data*/) {
227
    return JXL_DEC_ERROR;
228
  }
229
230
  JxlDecoderStatus WriteOutput(const jpeg::JPEGData& /* jpeg_data */) {
231
    return JXL_DEC_SUCCESS;
232
  }
233
};
234
235
#endif  // JPEGXL_ENABLE_TRANSCODE_JPEG
236
237
}  // namespace jxl
238
239
#endif  // LIB_JXL_DECODE_TO_JPEG_H_