/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 |