Coverage Report

Created: 2025-07-16 07:53

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