1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
6 #include "lib/jxl/jpeg/enc_jpeg_data.h"
8 #include <brotli/encode.h>
10 #include "lib/jxl/codec_in_out.h"
11 #include "lib/jxl/enc_fields.h"
12 #include "lib/jxl/image_bundle.h"
13 #include "lib/jxl/jpeg/enc_jpeg_data_reader.h"
14 #include "lib/jxl/luminance.h"
15 #include "lib/jxl/sanitizers.h"
22 constexpr int BITS_IN_JSAMPLE = 8;
23 using ByteSpan = Span<const uint8_t>;
25 // TODO(eustas): move to jpeg_data, to use from codec_jpg as well.
26 // See if there is a canonically chunked ICC profile and mark corresponding
27 // app-tags with AppMarkerType::kICC.
28 Status DetectIccProfile(JPEGData& jpeg_data) {
29 JXL_DASSERT(jpeg_data.app_data.size() == jpeg_data.app_marker_type.size());
31 size_t num_icc_jpeg = 0;
32 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
33 const auto& app = jpeg_data.app_data[i];
35 if (app[pos++] != 0xE2) continue;
36 // At least APPn + size; otherwise it should be intermarker-data.
37 JXL_DASSERT(app.size() >= 3);
38 size_t tag_length = (app[pos] << 8) + app[pos + 1];
40 JXL_DASSERT(app.size() == tag_length + 1);
41 // Empty payload is 2 bytes for tag length itself + signature
42 if (tag_length < 2 + sizeof kIccProfileTag) continue;
44 if (memcmp(&app[pos], kIccProfileTag, sizeof kIccProfileTag) != 0) continue;
45 pos += sizeof kIccProfileTag;
46 uint8_t chunk_id = app[pos++];
47 uint8_t num_chunks = app[pos++];
48 if (chunk_id != num_icc + 1) continue;
49 if (num_icc_jpeg == 0) num_icc_jpeg = num_chunks;
50 if (num_icc_jpeg != num_chunks) continue;
52 jpeg_data.app_marker_type[i] = AppMarkerType::kICC;
54 if (num_icc != num_icc_jpeg) {
55 return JXL_FAILURE("Invalid ICC chunks");
60 bool GetMarkerPayload(const uint8_t* data, size_t size, ByteSpan* payload) {
66 size_t internal_size = (hi << 8u) | lo;
67 // Second byte of marker is not counted towards size.
68 if (internal_size != size - 1) {
71 // cut second marker byte and "length" from payload.
72 *payload = ByteSpan(data, size);
73 payload->remove_prefix(3);
77 Status DetectBlobs(jpeg::JPEGData& jpeg_data) {
78 JXL_DASSERT(jpeg_data.app_data.size() == jpeg_data.app_marker_type.size());
79 bool have_exif = false, have_xmp = false;
80 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
81 auto& marker = jpeg_data.app_data[i];
82 if (marker.empty() || marker[0] != kApp1) {
86 if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) {
87 // Something is wrong with this marker; does not care.
90 if (!have_exif && payload.size() >= sizeof kExifTag &&
91 !memcmp(payload.data(), kExifTag, sizeof kExifTag)) {
92 jpeg_data.app_marker_type[i] = AppMarkerType::kExif;
95 if (!have_xmp && payload.size() >= sizeof kXMPTag &&
96 !memcmp(payload.data(), kXMPTag, sizeof kXMPTag)) {
97 jpeg_data.app_marker_type[i] = AppMarkerType::kXMP;
104 Status ParseChunkedMarker(const jpeg::JPEGData& src, uint8_t marker_type,
105 const ByteSpan& tag, IccBytes* output,
106 bool allow_permutations = false) {
109 std::vector<ByteSpan> chunks;
110 std::vector<bool> presence;
111 size_t expected_number_of_parts = 0;
112 bool is_first_chunk = true;
114 for (const auto& marker : src.app_data) {
115 if (marker.empty() || marker[0] != marker_type) {
119 if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) {
120 // Something is wrong with this marker; does not care.
123 if ((payload.size() < tag.size()) ||
124 memcmp(payload.data(), tag.data(), tag.size()) != 0) {
127 payload.remove_prefix(tag.size());
128 if (payload.size() < 2) {
129 return JXL_FAILURE("Chunk is too small.");
131 uint8_t index = payload[0];
132 uint8_t total = payload[1];
134 if (!allow_permutations) {
135 if (index != ordinal) return JXL_FAILURE("Invalid chunk order.");
138 payload.remove_prefix(2);
140 JXL_RETURN_IF_ERROR(total != 0);
141 if (is_first_chunk) {
142 is_first_chunk = false;
143 expected_number_of_parts = total;
144 // 1-based indices; 0-th element is added for convenience.
145 chunks.resize(total + 1);
146 presence.resize(total + 1);
148 JXL_RETURN_IF_ERROR(expected_number_of_parts == total);
151 if (index == 0 || index > total) {
152 return JXL_FAILURE("Invalid chunk index.");
155 if (presence[index]) {
156 return JXL_FAILURE("Duplicate chunk.");
158 presence[index] = true;
159 chunks[index] = payload;
162 for (size_t i = 0; i < expected_number_of_parts; ++i) {
163 // 0-th element is not used.
164 size_t index = i + 1;
165 if (!presence[index]) {
166 return JXL_FAILURE("Missing chunk.");
168 chunks[index].AppendTo(output);
174 Status SetBlobsFromJpegData(const jpeg::JPEGData& jpeg_data, Blobs* blobs) {
175 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
176 auto& marker = jpeg_data.app_data[i];
177 if (marker.empty() || marker[0] != kApp1) {
181 if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) {
182 // Something is wrong with this marker; does not care.
185 if (payload.size() >= sizeof kExifTag &&
186 !memcmp(payload.data(), kExifTag, sizeof kExifTag)) {
187 if (blobs->exif.empty()) {
188 blobs->exif.resize(payload.size() - sizeof kExifTag);
189 memcpy(blobs->exif.data(), payload.data() + sizeof kExifTag,
190 payload.size() - sizeof kExifTag);
193 "ReJPEG: multiple Exif blobs, storing only first one in the JPEG "
197 if (payload.size() >= sizeof kXMPTag &&
198 !memcmp(payload.data(), kXMPTag, sizeof kXMPTag)) {
199 if (blobs->xmp.empty()) {
200 blobs->xmp.resize(payload.size() - sizeof kXMPTag);
201 memcpy(blobs->xmp.data(), payload.data() + sizeof kXMPTag,
202 payload.size() - sizeof kXMPTag);
205 "ReJPEG: multiple XMP blobs, storing only first one in the JPEG "
213 static inline bool IsJPG(const Span<const uint8_t> bytes) {
214 return bytes.size() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xD8;
219 void SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
220 ColorEncoding* color_encoding) {
221 IccBytes icc_profile;
222 if (!ParseChunkedMarker(jpg, kApp2, ByteSpan(kIccProfileTag), &icc_profile)) {
223 JXL_WARNING("ReJPEG: corrupted ICC profile\n");
227 if (icc_profile.empty()) {
228 bool is_gray = (jpg.components.size() == 1);
229 *color_encoding = ColorEncoding::SRGB(is_gray);
231 color_encoding->SetICCRaw(std::move(icc_profile));
235 Status EncodeJPEGData(JPEGData& jpeg_data, std::vector<uint8_t>* bytes,
236 const CompressParams& cparams) {
238 jpeg_data.app_marker_type.resize(jpeg_data.app_data.size(),
239 AppMarkerType::kUnknown);
240 JXL_RETURN_IF_ERROR(DetectIccProfile(jpeg_data));
241 JXL_RETURN_IF_ERROR(DetectBlobs(jpeg_data));
243 size_t total_data = 0;
244 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
245 if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) {
248 total_data += jpeg_data.app_data[i].size();
250 for (size_t i = 0; i < jpeg_data.com_data.size(); i++) {
251 total_data += jpeg_data.com_data[i].size();
253 for (size_t i = 0; i < jpeg_data.inter_marker_data.size(); i++) {
254 total_data += jpeg_data.inter_marker_data[i].size();
256 total_data += jpeg_data.tail_data.size();
257 size_t brotli_capacity = BrotliEncoderMaxCompressedSize(total_data);
260 JXL_RETURN_IF_ERROR(Bundle::Write(jpeg_data, &writer, 0, nullptr));
261 writer.ZeroPadToByte();
263 PaddedBytes serialized_jpeg_data = std::move(writer).TakeBytes();
264 bytes->reserve(serialized_jpeg_data.size() + brotli_capacity);
265 Bytes(serialized_jpeg_data).AppendTo(bytes);
268 BrotliEncoderState* brotli_enc =
269 BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
270 int effort = cparams.brotli_effort;
271 if (effort < 0) effort = 11 - static_cast<int>(cparams.speed_tier);
272 BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, effort);
273 size_t initial_size = bytes->size();
274 BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_SIZE_HINT, total_data);
275 bytes->resize(initial_size + brotli_capacity);
277 auto br_append = [&](const std::vector<uint8_t>& data, bool last) {
278 size_t available_in = data.size();
279 const uint8_t* in = data.data();
280 uint8_t* out = &(*bytes)[initial_size + enc_size];
282 uint8_t* out_before = out;
283 msan::MemoryIsInitialized(in, available_in);
284 JXL_CHECK(BrotliEncoderCompressStream(
285 brotli_enc, last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
286 &available_in, &in, &brotli_capacity, &out, &enc_size));
287 msan::UnpoisonMemory(out_before, out - out_before);
288 } while (BrotliEncoderHasMoreOutput(brotli_enc) || available_in > 0);
291 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
292 if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) {
295 br_append(jpeg_data.app_data[i], /*last=*/false);
297 for (size_t i = 0; i < jpeg_data.com_data.size(); i++) {
298 br_append(jpeg_data.com_data[i], /*last=*/false);
300 for (size_t i = 0; i < jpeg_data.inter_marker_data.size(); i++) {
301 br_append(jpeg_data.inter_marker_data[i], /*last=*/false);
303 br_append(jpeg_data.tail_data, /*last=*/true);
304 BrotliEncoderDestroyInstance(brotli_enc);
305 bytes->resize(initial_size + enc_size);
309 Status DecodeImageJPG(const Span<const uint8_t> bytes, CodecInOut* io) {
310 if (!IsJPG(bytes)) return false;
312 io->frames.reserve(1);
313 io->frames.emplace_back(&io->metadata.m);
314 io->Main().jpeg_data = make_unique<jpeg::JPEGData>();
315 jpeg::JPEGData* jpeg_data = io->Main().jpeg_data.get();
316 if (!jpeg::ReadJpeg(bytes.data(), bytes.size(), jpeg::JpegReadMode::kReadAll,
318 return JXL_FAILURE("Error reading JPEG");
320 SetColorEncodingFromJpegData(*jpeg_data, &io->metadata.m.color_encoding);
321 JXL_RETURN_IF_ERROR(SetBlobsFromJpegData(*jpeg_data, &io->blobs));
322 size_t nbcomp = jpeg_data->components.size();
323 if (nbcomp != 1 && nbcomp != 3) {
324 return JXL_FAILURE("Cannot recompress JPEGs with neither 1 nor 3 channels");
326 YCbCrChromaSubsampling cs;
328 uint8_t hsample[3], vsample[3];
329 for (size_t i = 0; i < nbcomp; i++) {
330 hsample[i] = jpeg_data->components[i].h_samp_factor;
331 vsample[i] = jpeg_data->components[i].v_samp_factor;
333 JXL_RETURN_IF_ERROR(cs.Set(hsample, vsample));
334 } else if (nbcomp == 1) {
335 uint8_t hsample[3], vsample[3];
336 for (size_t i = 0; i < 3; i++) {
337 hsample[i] = jpeg_data->components[0].h_samp_factor;
338 vsample[i] = jpeg_data->components[0].v_samp_factor;
340 JXL_RETURN_IF_ERROR(cs.Set(hsample, vsample));
344 const auto& markers = jpeg_data->marker_order;
345 // If there is a JFIF marker, this is YCbCr. Otherwise...
346 if (std::find(markers.begin(), markers.end(), 0xE0) == markers.end()) {
347 // Try to find an 'Adobe' marker.
348 size_t app_markers = 0;
350 for (; i < markers.size(); i++) {
351 // This is an APP marker.
352 if ((markers[i] & 0xF0) == 0xE0) {
353 JXL_CHECK(app_markers < jpeg_data->app_data.size());
355 if (markers[i] == 0xEE) {
356 const auto& data = jpeg_data->app_data[app_markers];
357 if (data.size() == 15 && data[3] == 'A' && data[4] == 'd' &&
358 data[5] == 'o' && data[6] == 'b' && data[7] == 'e') {
360 is_rgb = data[14] == 0;
368 if (i == markers.size()) {
369 // No 'Adobe' marker, guess from component IDs.
370 is_rgb = nbcomp == 3 && jpeg_data->components[0].id == 'R' &&
371 jpeg_data->components[1].id == 'G' &&
372 jpeg_data->components[2].id == 'B';
377 io->Main().chroma_subsampling = cs;
378 io->Main().color_transform =
379 (!is_rgb || nbcomp == 1) ? ColorTransform::kYCbCr : ColorTransform::kNone;
381 io->metadata.m.SetIntensityTarget(kDefaultIntensityTarget);
382 io->metadata.m.SetUintSamples(BITS_IN_JSAMPLE);
383 io->SetFromImage(Image3F(jpeg_data->width, jpeg_data->height),
384 io->metadata.m.color_encoding);
385 SetIntensityTarget(&io->metadata.m);