Coverage Report

Created: 2026-02-14 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/decode_to_jpeg.cc
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
#include "lib/jxl/decode_to_jpeg.h"
7
8
#include <jxl/decode.h>
9
10
#include <algorithm>
11
#include <cstddef>
12
#include <cstdint>
13
#include <cstring>
14
15
#include "lib/jxl/base/common.h"
16
#include "lib/jxl/base/span.h"
17
#include "lib/jxl/base/status.h"
18
#include "lib/jxl/common.h"  // JPEGXL_ENABLE_TRANSCODE_JPEG
19
#include "lib/jxl/jpeg/dec_jpeg_data.h"
20
#include "lib/jxl/jpeg/jpeg_data.h"
21
22
namespace jxl {
23
24
#if JPEGXL_ENABLE_TRANSCODE_JPEG
25
26
JxlDecoderStatus JxlToJpegDecoder::Process(const uint8_t** next_in,
27
20.1k
                                           size_t* avail_in) {
28
20.1k
  if (!inside_box_) {
29
0
    JXL_WARNING(
30
0
        "processing of JPEG reconstruction data outside JPEG reconstruction "
31
0
        "box");
32
0
    return JXL_DEC_ERROR;
33
0
  }
34
20.1k
  Span<const uint8_t> to_decode;
35
20.1k
  if (box_until_eof_) {
36
    // Until EOF means consume all data.
37
16.8k
    to_decode = Bytes(*next_in, *avail_in);
38
16.8k
    *next_in += *avail_in;
39
16.8k
    *avail_in = 0;
40
16.8k
  } else {
41
    // Defined size means consume min(available, needed).
42
3.27k
    size_t avail_recon_in =
43
3.27k
        std::min<size_t>(*avail_in, box_size_ - buffer_.size());
44
3.27k
    to_decode = Bytes(*next_in, avail_recon_in);
45
3.27k
    *next_in += avail_recon_in;
46
3.27k
    *avail_in -= avail_recon_in;
47
3.27k
  }
48
20.1k
  bool old_data_exists = !buffer_.empty();
49
20.1k
  if (old_data_exists) {
50
    // Append incoming data to buffer if we already had data in the buffer.
51
18.7k
    buffer_.insert(buffer_.end(), to_decode.data(),
52
18.7k
                   to_decode.data() + to_decode.size());
53
18.7k
    to_decode = Bytes(buffer_.data(), buffer_.size());
54
18.7k
  }
55
20.1k
  if (!box_until_eof_ && to_decode.size() > box_size_) {
56
0
    JXL_WARNING("JPEG reconstruction data to decode larger than expected");
57
0
    return JXL_DEC_ERROR;
58
0
  }
59
20.1k
  if (box_until_eof_ || to_decode.size() == box_size_) {
60
    // If undefined size, or the right size, try to decode.
61
17.2k
    jpeg_data_ = make_unique<jpeg::JPEGData>();
62
17.2k
    const auto status = jpeg::DecodeJPEGData(to_decode, jpeg_data_.get());
63
17.2k
    if (status.IsFatalError()) return JXL_DEC_ERROR;
64
17.1k
    if (status) {
65
      // Successful decoding, emit event after updating state to track that we
66
      // are no longer parsing JPEG reconstruction data.
67
433
      inside_box_ = false;
68
433
      return JXL_DEC_JPEG_RECONSTRUCTION;
69
433
    }
70
16.7k
    if (box_until_eof_) {
71
      // Unsuccessful decoding and undefined size, assume incomplete data. Copy
72
      // the data if we haven't already.
73
16.6k
      if (!old_data_exists) {
74
730
        buffer_.insert(buffer_.end(), to_decode.data(),
75
730
                       to_decode.data() + to_decode.size());
76
730
      }
77
16.6k
    } else {
78
      // Unsuccessful decoding of correct amount of data, assume error.
79
2
      return JXL_DEC_ERROR;
80
2
    }
81
16.7k
  } else {
82
    // Not enough data, copy the data if we haven't already.
83
2.84k
    if (!old_data_exists) {
84
277
      buffer_.insert(buffer_.end(), to_decode.data(),
85
277
                     to_decode.data() + to_decode.size());
86
277
    }
87
2.84k
  }
88
19.5k
  return JXL_DEC_NEED_MORE_INPUT;
89
20.1k
}
90
91
433
size_t JxlToJpegDecoder::NumExifMarkers(const jpeg::JPEGData& jpeg_data) {
92
433
  size_t num = 0;
93
941
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
94
508
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) {
95
19
      num++;
96
19
    }
97
508
  }
98
433
  return num;
99
433
}
100
101
433
size_t JxlToJpegDecoder::NumXmpMarkers(const jpeg::JPEGData& jpeg_data) {
102
433
  size_t num = 0;
103
941
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
104
508
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) {
105
24
      num++;
106
24
    }
107
508
  }
108
433
  return num;
109
433
}
110
111
JxlDecoderStatus JxlToJpegDecoder::ExifBoxContentSize(
112
8
    const jpeg::JPEGData& jpeg_data, size_t* size) {
113
37
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
114
37
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) {
115
8
      if (jpeg_data.app_data[i].size() < 3 + sizeof(jpeg::kExifTag)) {
116
        // too small for app marker header
117
0
        return JXL_DEC_ERROR;
118
0
      }
119
      // The first 4 bytes are the TIFF header from the box contents, and are
120
      // not included in the JPEG
121
8
      *size = jpeg_data.app_data[i].size() + 4 - 3 - sizeof(jpeg::kExifTag);
122
8
      return JXL_DEC_SUCCESS;
123
8
    }
124
37
  }
125
0
  return JXL_DEC_ERROR;
126
8
}
127
128
JxlDecoderStatus JxlToJpegDecoder::XmlBoxContentSize(
129
5
    const jpeg::JPEGData& jpeg_data, size_t* size) {
130
18
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
131
18
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) {
132
5
      if (jpeg_data.app_data[i].size() < 3 + sizeof(jpeg::kXMPTag)) {
133
        // too small for app marker header
134
0
        return JXL_DEC_ERROR;
135
0
      }
136
5
      *size = jpeg_data.app_data[i].size() - 3 - sizeof(jpeg::kXMPTag);
137
5
      return JXL_DEC_SUCCESS;
138
5
    }
139
18
  }
140
0
  return JXL_DEC_ERROR;
141
5
}
142
143
JxlDecoderStatus JxlToJpegDecoder::SetExif(const uint8_t* data, size_t size,
144
0
                                           jpeg::JPEGData* jpeg_data) {
145
0
  for (size_t i = 0; i < jpeg_data->app_data.size(); ++i) {
146
0
    if (jpeg_data->app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) {
147
0
      if (jpeg_data->app_data[i].size() !=
148
0
          size + 3 + sizeof(jpeg::kExifTag) - 4)
149
0
        return JXL_DEC_ERROR;
150
      // The first 9 bytes are used for JPEG marker header.
151
0
      jpeg_data->app_data[i][0] = 0xE1;
152
      // The second and third byte are already filled in correctly
153
0
      memcpy(jpeg_data->app_data[i].data() + 3, jpeg::kExifTag,
154
0
             sizeof(jpeg::kExifTag));
155
      // The first 4 bytes are the TIFF header from the box contents, and are
156
      // not included in the JPEG
157
0
      memcpy(jpeg_data->app_data[i].data() + 3 + sizeof(jpeg::kExifTag),
158
0
             data + 4, size - 4);
159
0
      return JXL_DEC_SUCCESS;
160
0
    }
161
0
  }
162
0
  return JXL_DEC_ERROR;
163
0
}
164
JxlDecoderStatus JxlToJpegDecoder::SetXmp(const uint8_t* data, size_t size,
165
0
                                          jpeg::JPEGData* jpeg_data) {
166
0
  for (size_t i = 0; i < jpeg_data->app_data.size(); ++i) {
167
0
    if (jpeg_data->app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) {
168
0
      if (jpeg_data->app_data[i].size() != size + 3 + sizeof(jpeg::kXMPTag))
169
0
        return JXL_DEC_ERROR;
170
      // The first 9 bytes are used for JPEG marker header.
171
0
      jpeg_data->app_data[i][0] = 0xE1;
172
      // The second and third byte are already filled in correctly
173
0
      memcpy(jpeg_data->app_data[i].data() + 3, jpeg::kXMPTag,
174
0
             sizeof(jpeg::kXMPTag));
175
0
      memcpy(jpeg_data->app_data[i].data() + 3 + sizeof(jpeg::kXMPTag), data,
176
0
             size);
177
0
      return JXL_DEC_SUCCESS;
178
0
    }
179
0
  }
180
0
  return JXL_DEC_ERROR;
181
0
}
182
183
#endif  // JPEGXL_ENABLE_TRANSCODE_JPEG
184
185
}  // namespace jxl