Coverage Report

Created: 2025-06-16 07:00

/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/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
0
                                           size_t* avail_in) {
28
0
  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
0
  Span<const uint8_t> to_decode;
35
0
  if (box_until_eof_) {
36
    // Until EOF means consume all data.
37
0
    to_decode = Bytes(*next_in, *avail_in);
38
0
    *next_in += *avail_in;
39
0
    *avail_in = 0;
40
0
  } else {
41
    // Defined size means consume min(available, needed).
42
0
    size_t avail_recon_in =
43
0
        std::min<size_t>(*avail_in, box_size_ - buffer_.size());
44
0
    to_decode = Bytes(*next_in, avail_recon_in);
45
0
    *next_in += avail_recon_in;
46
0
    *avail_in -= avail_recon_in;
47
0
  }
48
0
  bool old_data_exists = !buffer_.empty();
49
0
  if (old_data_exists) {
50
    // Append incoming data to buffer if we already had data in the buffer.
51
0
    buffer_.insert(buffer_.end(), to_decode.data(),
52
0
                   to_decode.data() + to_decode.size());
53
0
    to_decode = Bytes(buffer_.data(), buffer_.size());
54
0
  }
55
0
  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
0
  if (box_until_eof_ || to_decode.size() == box_size_) {
60
    // If undefined size, or the right size, try to decode.
61
0
    jpeg_data_ = make_unique<jpeg::JPEGData>();
62
0
    const auto status = jpeg::DecodeJPEGData(to_decode, jpeg_data_.get());
63
0
    if (status.IsFatalError()) return JXL_DEC_ERROR;
64
0
    if (status) {
65
      // Successful decoding, emit event after updating state to track that we
66
      // are no longer parsing JPEG reconstruction data.
67
0
      inside_box_ = false;
68
0
      return JXL_DEC_JPEG_RECONSTRUCTION;
69
0
    }
70
0
    if (box_until_eof_) {
71
      // Unsuccessful decoding and undefined size, assume incomplete data. Copy
72
      // the data if we haven't already.
73
0
      if (!old_data_exists) {
74
0
        buffer_.insert(buffer_.end(), to_decode.data(),
75
0
                       to_decode.data() + to_decode.size());
76
0
      }
77
0
    } else {
78
      // Unsuccessful decoding of correct amount of data, assume error.
79
0
      return JXL_DEC_ERROR;
80
0
    }
81
0
  } else {
82
    // Not enough data, copy the data if we haven't already.
83
0
    if (!old_data_exists) {
84
0
      buffer_.insert(buffer_.end(), to_decode.data(),
85
0
                     to_decode.data() + to_decode.size());
86
0
    }
87
0
  }
88
0
  return JXL_DEC_NEED_MORE_INPUT;
89
0
}
90
91
0
size_t JxlToJpegDecoder::NumExifMarkers(const jpeg::JPEGData& jpeg_data) {
92
0
  size_t num = 0;
93
0
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
94
0
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) {
95
0
      num++;
96
0
    }
97
0
  }
98
0
  return num;
99
0
}
100
101
0
size_t JxlToJpegDecoder::NumXmpMarkers(const jpeg::JPEGData& jpeg_data) {
102
0
  size_t num = 0;
103
0
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
104
0
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) {
105
0
      num++;
106
0
    }
107
0
  }
108
0
  return num;
109
0
}
110
111
JxlDecoderStatus JxlToJpegDecoder::ExifBoxContentSize(
112
0
    const jpeg::JPEGData& jpeg_data, size_t* size) {
113
0
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
114
0
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) {
115
0
      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
0
      *size = jpeg_data.app_data[i].size() + 4 - 3 - sizeof(jpeg::kExifTag);
122
0
      return JXL_DEC_SUCCESS;
123
0
    }
124
0
  }
125
0
  return JXL_DEC_ERROR;
126
0
}
127
128
JxlDecoderStatus JxlToJpegDecoder::XmlBoxContentSize(
129
0
    const jpeg::JPEGData& jpeg_data, size_t* size) {
130
0
  for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) {
131
0
    if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) {
132
0
      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
0
      *size = jpeg_data.app_data[i].size() - 3 - sizeof(jpeg::kXMPTag);
137
0
      return JXL_DEC_SUCCESS;
138
0
    }
139
0
  }
140
0
  return JXL_DEC_ERROR;
141
0
}
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